feat: Updated for api ingress routes
This commit is contained in:
@@ -76,6 +76,20 @@ jobs:
|
||||
--set ingress.tls.certResolver=letsencrypt \
|
||||
--set server.env.grpcHost=agents.example.com:443 > /dev/null
|
||||
|
||||
# The shape the cloud deployment actually uses: a wildcard tenant
|
||||
# namespace, /api and /auth routed at the edge, and no apex — that
|
||||
# belongs to the marketing site, which this chart does not deploy.
|
||||
- name: Render a wildcard host with edge-routed API paths
|
||||
run: |
|
||||
helm template test "$CHART_DIR" \
|
||||
--set ingress.enabled=true \
|
||||
--set 'ingress.web.host=*.vantage.example.com' \
|
||||
--set ingress.api.enabled=true \
|
||||
--set ingress.grpc.host=agents.example.com \
|
||||
--set server.env.grpcHost=agents.example.com:443 \
|
||||
--set ingress.tls.secretName=vantage-tls \
|
||||
--set ingress.tls.grpcSecretName=agents-tls > /dev/null
|
||||
|
||||
# The guards are load-bearing, so their absence is a regression the
|
||||
# same way a broken render is. Each of these must fail.
|
||||
- name: Check the guards still refuse bad values
|
||||
@@ -99,6 +113,12 @@ jobs:
|
||||
--set server.replicaCount=2 --set server.persistence.enabled=true
|
||||
refuses "ingress with no web host" \
|
||||
--set ingress.enabled=true
|
||||
refuses "edge-routed API with an empty path list" \
|
||||
--set ingress.enabled=true \
|
||||
--set ingress.web.host=vantage.example.com \
|
||||
--set ingress.grpc.enabled=false \
|
||||
--set ingress.api.enabled=true \
|
||||
--set 'ingress.api.paths=null'
|
||||
refuses "gRPC ingress with no host" \
|
||||
--set ingress.enabled=true \
|
||||
--set ingress.web.host=vantage.example.com \
|
||||
|
||||
@@ -610,12 +610,15 @@ Windows: MSI built by CI (WiX), or `installer/setup.ps1` registering the agent a
|
||||
|
||||
`ingress.enabled` publishes **two** hostnames, because the two audiences arrive over different protocols:
|
||||
|
||||
| Values | Route |
|
||||
| --------------------- | ----------------------------------------------------------------------------------------- |
|
||||
| `ingress.web.host` | browsers → `web:3000`. Everything, including `/api` — see below |
|
||||
| `ingress.grpc.host` | agents → a dedicated `<release>-server-grpc` Service on 9090, annotated `serversscheme: h2c` |
|
||||
| Values | Route |
|
||||
| -------------------------------------------- | --------------------------------------------------------------------------------------------- |
|
||||
| `ingress.web.host` (+ `web.extraHosts`) | browsers → `web:3000` |
|
||||
| `ingress.api.paths` (when `api.enabled`) | `/api`, `/auth` → `<release>-server:8080`, bypassing the Next proxy |
|
||||
| `ingress.grpc.host` | agents → a dedicated `<release>-server-grpc` Service on 9090, annotated `serversscheme: h2c` |
|
||||
|
||||
**The server's HTTP port is deliberately not publishable.** `web` already proxies `/api`, `/auth` and the install scripts to it (`web/next.config.ts`), so a second route would be a second front door to the same API with none of that routing — and the console WebSocket and ESO token path would then exist at two addresses with different behaviour.
|
||||
**`ingress.web.host` is normally a wildcard.** `*.vantage.example.com` is the per-tenant instance namespace — `APP_ROOT_LABEL` resolves the instance from the label. A Kubernetes wildcard host matches **exactly one** label, so it does not match the apex, and here that is correct rather than a gap: `vantage.hostxtra.co.uk` is the marketing site (`site/`, in `docker-compose.site.yml`), which this chart does not deploy. `extraHosts` is for a genuine second name; adding the apex to it would put the control plane on the marketing host. Every host in the list gets identical paths.
|
||||
|
||||
**`ingress.api.enabled` routes `/api` and `/auth` straight to the server.** Both arrangements work — without it `web` proxies those prefixes onward itself (`web/next.config.ts`) — but edge routing is one hop shorter and matches what the Nginx Proxy Manager in front of the Docker deployment already does, so leaving it off makes the request path a different shape on Kubernetes than in production. It stays **off by default** because it only helps where the server is reachable on the same host and certificate as `web`; turning it on blindly moves the whole API onto a route that may not be provisioned. Traefik derives router priority from rule length, so `PathPrefix(/api)` outranks the catch-all `/` with no priority annotation needed.
|
||||
|
||||
**The gRPC route needs its own Service.** The server terminates no TLS; it speaks plain h2c and always has, with TLS terminated by whatever sits in front. Traefik will not use h2c to a backend unless the *Service* says so, and that annotation applies to every port on the Service — so annotating the shared two-port `<release>-server` would force h2c on its HTTP port too.
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@ apiVersion: v2
|
||||
name: vantage
|
||||
description: Helm chart for the Vantage stack (Redis, MongoDB, guacd, server, web)
|
||||
type: application
|
||||
version: 1.0.2
|
||||
appVersion: "1.0.2"
|
||||
version: 1.0.3
|
||||
appVersion: "1.0.3"
|
||||
|
||||
@@ -37,9 +37,16 @@ Scaling (server.replicaCount / web.replicaCount):
|
||||
{{- if .Values.ingress.enabled }}
|
||||
|
||||
Ingress (Traefik):
|
||||
- UI and API: https://{{ .Values.ingress.web.host }}
|
||||
Everything browsers need goes here; web proxies /api, /auth and the install
|
||||
scripts to the server, so the server's HTTP port is not published separately.
|
||||
- Browsers: https://{{ .Values.ingress.web.host }}
|
||||
{{- range .Values.ingress.web.extraHosts }}
|
||||
https://{{ . }}
|
||||
{{- end }}
|
||||
{{- if .Values.ingress.api.enabled }}
|
||||
{{ join ", " .Values.ingress.api.paths }} go straight to the server; everything else to web.
|
||||
{{- else }}
|
||||
Everything goes to web, which proxies /api and /auth onward. Set
|
||||
ingress.api.enabled=true to route them at the edge instead.
|
||||
{{- end }}
|
||||
{{- if .Values.ingress.grpc.enabled }}
|
||||
- Agents: {{ .Values.ingress.grpc.host }} (gRPC, h2c behind TLS)
|
||||
Agents dial server.env.grpcHost, currently {{ tpl .Values.server.env.grpcHost . }}.
|
||||
|
||||
@@ -2,10 +2,24 @@
|
||||
{{/*
|
||||
Two hostnames, because the two audiences arrive over different protocols.
|
||||
|
||||
Browsers reach `web`, and only `web`: it proxies /api, /auth and the install
|
||||
scripts through to the server itself (see web/next.config.ts), so publishing the
|
||||
server's HTTP port separately would be a second front door to the same API with
|
||||
none of the same routing.
|
||||
Browsers reach the web host. What answers there depends on the path: with
|
||||
ingress.api.enabled, /api and /auth go straight to the server and everything
|
||||
else goes to `web`. Without it, everything goes to `web`, which proxies those
|
||||
prefixes onward itself (web/next.config.ts).
|
||||
|
||||
Both work. Routing at the edge is one hop shorter and is what the Nginx Proxy
|
||||
Manager deployment in front of the Docker install already does, so leaving it
|
||||
off changes the shape of the request path between the two deployments. It is
|
||||
still off by default, because turning it on where `web` is the only thing with
|
||||
a public certificate would strand /api behind a route nobody can reach.
|
||||
|
||||
The web host is normally a wildcard — `*.vantage.example.com` — because that is
|
||||
the per-tenant instance namespace; APP_ROOT_LABEL resolves the instance from the
|
||||
label. Kubernetes wildcard hosts match exactly one label, so this does not match
|
||||
the apex, and on the deployment this chart was written for it must not: the apex
|
||||
is the marketing site, a separate application (see site/ and
|
||||
docker-compose.site.yml). extraHosts exists for a genuine second name, not for
|
||||
reclaiming the apex.
|
||||
|
||||
Agents reach the server's gRPC port, which is plain h2c — the server holds no
|
||||
certificates of its own, TLS has always been terminated by whatever sits in
|
||||
@@ -16,6 +30,11 @@ its HTTP port too.
|
||||
*/}}
|
||||
{{- $tls := .Values.ingress.tls }}
|
||||
{{- $webHost := required "ingress.enabled requires ingress.web.host" .Values.ingress.web.host }}
|
||||
{{- $hosts := prepend .Values.ingress.web.extraHosts $webHost }}
|
||||
{{- $apiPaths := .Values.ingress.api.paths }}
|
||||
{{- if and .Values.ingress.api.enabled (not $apiPaths) }}
|
||||
{{- fail "ingress.api.enabled requires at least one path in ingress.api.paths" }}
|
||||
{{- end }}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
@@ -41,20 +60,42 @@ spec:
|
||||
{{- if and $tls.enabled $tls.secretName }}
|
||||
tls:
|
||||
- hosts:
|
||||
- {{ $webHost | quote }}
|
||||
{{- range $hosts }}
|
||||
- {{ . | quote }}
|
||||
{{- end }}
|
||||
secretName: {{ $tls.secretName }}
|
||||
{{- end }}
|
||||
rules:
|
||||
- host: {{ $webHost | quote }}
|
||||
{{- range $host := $hosts }}
|
||||
- host: {{ $host | quote }}
|
||||
http:
|
||||
paths:
|
||||
{{- /*
|
||||
The API paths come first and, more importantly, are longer. Traefik
|
||||
derives router priority from rule length, so Host(x) &&
|
||||
PathPrefix(/api) outranks Host(x) && PathPrefix(/) without anyone
|
||||
having to assign priorities by hand. Order within the list is for
|
||||
the reader; the length is what decides.
|
||||
*/}}
|
||||
{{- if $.Values.ingress.api.enabled }}
|
||||
{{- range $apiPaths }}
|
||||
- path: {{ . | quote }}
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: {{ $.Release.Name }}-server
|
||||
port:
|
||||
number: {{ $.Values.server.service.httpPort }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: {{ .Release.Name }}-web
|
||||
name: {{ $.Release.Name }}-web
|
||||
port:
|
||||
number: {{ .Values.web.service.port }}
|
||||
number: {{ $.Values.web.service.port }}
|
||||
{{- end }}
|
||||
{{- if .Values.ingress.grpc.enabled }}
|
||||
{{- $grpcHost := required "ingress.grpc.enabled requires ingress.grpc.host" .Values.ingress.grpc.host }}
|
||||
{{/*
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
# Default values for the vantage chart.
|
||||
|
||||
redis:
|
||||
# false deploys no Redis and points the server at `redis.addr` instead.
|
||||
enabled: true
|
||||
# Only read when enabled is false. host:port of an external Redis.
|
||||
addr: ""
|
||||
image:
|
||||
repository: redis
|
||||
@@ -14,21 +12,14 @@ redis:
|
||||
storageClass: ""
|
||||
accessMode: ReadWriteOnce
|
||||
port: 6379
|
||||
# Both empty for an unauthenticated Redis. Redis 6+ ACL auth takes both; a
|
||||
# legacy `requirepass` instance takes the password alone and must leave the
|
||||
# username empty. Set existingSecret to keep the password out of values.
|
||||
auth:
|
||||
username: ""
|
||||
password: ""
|
||||
# Secret holding the credentials. When set, username/password above are
|
||||
# ignored and these keys are read from the secret instead.
|
||||
existingSecret: ""
|
||||
usernameKey: username
|
||||
passwordKey: password
|
||||
|
||||
mongo:
|
||||
# false deploys no MongoDB. server.env.mongoUri must then point at an
|
||||
# external one — the chart cannot guess it, and refuses to render without it.
|
||||
enabled: true
|
||||
image:
|
||||
repository: mongo
|
||||
@@ -49,22 +40,10 @@ guacd:
|
||||
port: 4822
|
||||
|
||||
server:
|
||||
# Safe to raise. Agent commands, step results and console relays are routed
|
||||
# between replicas over Redis, workflow logs live in MongoDB, and the
|
||||
# background jobs (monitor scheduler, reaper, retention sweeps) run under a
|
||||
# Redis leader lock so exactly one replica performs them.
|
||||
#
|
||||
# Two requirements come with raising it: server.persistence.enabled must be
|
||||
# false (or the volume ReadWriteMany), and Redis must be shared by every
|
||||
# replica — the bus is not optional and a per-pod Redis would partition it.
|
||||
replicaCount: 1
|
||||
# Runs migrations, index builders and default-step seeding once, as a Helm
|
||||
# pre-install/pre-upgrade hook, instead of in every starting pod. Leave it
|
||||
# on for Kubernetes. Turning it off puts schema setup back in the pods.
|
||||
migrationJob:
|
||||
enabled: true
|
||||
backoffLimit: 0
|
||||
# 15 minutes: the instance rename alone carries a 10-minute budget.
|
||||
activeDeadlineSeconds: 900
|
||||
image:
|
||||
repository: gitea.hostxtra.co.uk/mrhid6/vantage/server
|
||||
@@ -78,16 +57,8 @@ server:
|
||||
grpcHost: "{{ .Release.Name }}-server:9090"
|
||||
keyEncryptionKey: ""
|
||||
appRootLabel: vantage
|
||||
# Ignored under Kubernetes: the chart sets POD_IP from the downward API
|
||||
# and the server prefers it, because a console relay listener belongs to
|
||||
# one pod and a Service address cannot name one.
|
||||
proxyAdvertiseHost: "{{ .Release.Name }}-server"
|
||||
proxyListenHost: "0.0.0.0"
|
||||
# Off by default: nothing in the server writes to disk any more. Workflow
|
||||
# logs, the only thing that ever did, are in MongoDB so that every replica
|
||||
# can read and write them. Turn this on only to reach files left behind by
|
||||
# a release that predates that move — and note a ReadWriteOnce volume caps
|
||||
# replicaCount at 1 while it is on.
|
||||
persistence:
|
||||
enabled: false
|
||||
size: 1Gi
|
||||
@@ -96,8 +67,6 @@ server:
|
||||
hostPath: /data
|
||||
|
||||
web:
|
||||
# Stateless — safe to raise. Pin web.image.tag when you do: replicas on
|
||||
# different builds serve mismatched chunk hashes and the UI 404s mid-session.
|
||||
replicaCount: 1
|
||||
image:
|
||||
repository: gitea.hostxtra.co.uk/mrhid6/vantage/web
|
||||
@@ -108,35 +77,25 @@ web:
|
||||
env:
|
||||
apiUrl: "http://{{ .Release.Name }}-server:8080"
|
||||
|
||||
# Traefik ingress. Two hostnames, because the two audiences arrive differently:
|
||||
# browsers reach `web` (which proxies /api, /auth and the install scripts to the
|
||||
# server), and agents reach the server's gRPC port directly.
|
||||
#
|
||||
# Publishing the server's HTTP port is deliberately not offered — it would be a
|
||||
# second door to the same API, bypassing the routing web already performs.
|
||||
ingress:
|
||||
enabled: false
|
||||
className: traefik
|
||||
# Traefik entrypoint name. `websecure` is the default TLS entrypoint in the
|
||||
# official chart; installs that renamed it must say so here.
|
||||
entrypoint: websecure
|
||||
# Applied to the web router only. Middlewares, rate limits, IP allow lists.
|
||||
annotations: {}
|
||||
web:
|
||||
# Required when ingress.enabled. The hostname users open in a browser.
|
||||
host: ""
|
||||
extraHosts: []
|
||||
api:
|
||||
enabled: false
|
||||
paths:
|
||||
- /api
|
||||
- /auth
|
||||
grpc:
|
||||
# Agents dial this. Turning it off means agents reach gRPC some other
|
||||
# way — a LoadBalancer Service, a node port, or an in-cluster path.
|
||||
enabled: true
|
||||
host: ""
|
||||
annotations: {}
|
||||
tls:
|
||||
enabled: true
|
||||
# Either name pre-existing certificate Secrets, or leave both empty and
|
||||
# set certResolver to have Traefik obtain them (ACME). Setting neither
|
||||
# produces a TLS router with no certificate, which serves Traefik's
|
||||
# self-signed default — valid-looking and trusted by nothing.
|
||||
secretName: ""
|
||||
grpcSecretName: ""
|
||||
certResolver: ""
|
||||
|
||||
Reference in New Issue
Block a user