feat: Gateway-API as an alternative to the Ingress in the six special shapes (group 3) #120
No files matched your search
@@ -0,0 +1,59 @@
|
||||
# Render-only overlay for ci/run-httproute-match-test.sh, applied LAST.
|
||||
#
|
||||
# Its one job is to put this chart into Gateway mode, because the convention
|
||||
# ships with httpRoute.enabled: false and the test would otherwise be green
|
||||
# over nothing. A chart that has an overlay here and still renders no
|
||||
# HTTPRoute FAILS the test - that is the whole point of the file existing.
|
||||
#
|
||||
# An overlay and not a change to ci/matrix-synapse/test-values.yaml: the
|
||||
# install-test runs exactly ONE configuration per chart, and this chart's
|
||||
# fixture installs the StatefulSet with its PVC and the delegation Deployment.
|
||||
# Trading its Ingress for a CRD schema check that cyberchef's fixture already
|
||||
# performs against the real Gateway-API CRDs would be a bad swap. The install
|
||||
# keeps the Ingress; the routes are held here, at render level, which is where
|
||||
# their silent failure modes live.
|
||||
#
|
||||
# ingress.enabled: false is not optional here - ingress.enabled is this chart's
|
||||
# routing switch and defaults to TRUE, so without this line the chart aborts
|
||||
# its own render with both modes on.
|
||||
#
|
||||
# WHAT THIS OVERLAY COVERS, WHICH IS BOTH ROUTING OBJECTS - and this chart is
|
||||
# the one in group 3 where "both" means two different hostnames, two different
|
||||
# paths and two different backends:
|
||||
#
|
||||
# - ingress.domain comes from the fixture (matrix.ci.local), so the
|
||||
# HOMESERVER route renders with its hostname from that single source of
|
||||
# truth. httpRoute.hostnames stays empty on purpose: the fallback is the
|
||||
# path every installation will actually use.
|
||||
# - matrix.delegation.enabled is TRUE in the fixture with
|
||||
# matrix.delegation.serverUrl: ci.local, so the DELEGATION route renders
|
||||
# too - on its own hostname, matching PathPrefix /.well-known/matrix, and
|
||||
# pointing at the delegation Service rather than at Synapse. Its
|
||||
# parentRefs fall back to httpRoute.parentRefs below, which is the branch
|
||||
# an installation hits unless the server name sits behind a different
|
||||
# Gateway.
|
||||
# - both routes' backendRefs are checked against the Services of the same
|
||||
# render, and they are DIFFERENT Services. Both references are by PORT
|
||||
# NUMBER rather than by the name "http" the Ingress uses, because
|
||||
# backendRefs takes a PortNumber - so this overlay is what holds those
|
||||
# numbers against the Services'.
|
||||
#
|
||||
# No URLRewrite filter is rendered by this chart in either mode: the delegation
|
||||
# nginx answers on the full /.well-known/matrix/* path, so nothing is stripped
|
||||
# and the #118 prefix/redirect assurance has no precondition to meet here.
|
||||
#
|
||||
# NOT covered here, and it cannot be: that either hostname answers, which for
|
||||
# Matrix is also whether FEDERATION works. No Gateway controller runs in CI, no
|
||||
# route reaches "Accepted: True", and Matrix federation is HTTPS-only while
|
||||
# nothing in this mode renders a certificate. Nor is the body-size limit
|
||||
# covered - proxy-body-size: 50m is on the live Ingress and has no counterpart
|
||||
# in the specification; that loss is documented in values.yaml and printed by
|
||||
# NOTES.txt, and documentation is not something a test can assert.
|
||||
ingress:
|
||||
enabled: false
|
||||
httpRoute:
|
||||
enabled: true
|
||||
parentRefs:
|
||||
- name: public
|
||||
namespace: gateway
|
||||
sectionName: https
|
||||
@@ -3,7 +3,7 @@ name: matrix-synapse
|
||||
description: A Helm chart to deploy a Matrix Synapse server on Kubernetes
|
||||
icon: https://raw.githubusercontent.com/element-hq/synapse/develop/docs/favicon.png
|
||||
type: application
|
||||
version: 7.1.1+upv1.159.0
|
||||
version: 7.2.0+upv1.159.0
|
||||
appVersion: "v1.159.0"
|
||||
maintainers:
|
||||
- name: jklein
|
||||
@@ -1,5 +1,29 @@
|
||||
{{- /*
|
||||
NOTHING IN CI READS THIS FILE. `helm template` does not emit notes and no
|
||||
workflow or script under ci/ reads it, so every claim below carries
|
||||
review-only assurance - it is checked by being read, not by a stage. A
|
||||
rendering ERROR here does fail `helm template`, which is all the pipeline can
|
||||
see of it.
|
||||
*/ -}}
|
||||
{{- $replicas := include "matrix-synapse.replicas" (dict "scaleDown" .Values.scaleDown "replicas" .Values.matrix.replicas) -}}
|
||||
{{- $delegation := .Values.matrix.delegation | default dict -}}
|
||||
{{- $httpRoute := .Values.httpRoute | default dict -}}
|
||||
{{- $httpRouteDelegation := $httpRoute.delegation | default dict -}}
|
||||
{{- /*
|
||||
"Published" rather than "has an Ingress": the two modes are alternatives, and
|
||||
every paragraph below that used to ask ingress.enabled was really asking
|
||||
whether anything publishes this release. Gitea #114, and the sure.publishedOverTls
|
||||
/ freesailarr.published shape.
|
||||
*/ -}}
|
||||
{{- $published := or .Values.ingress.enabled $httpRoute.enabled -}}
|
||||
{{- $hostnames := $httpRoute.hostnames | default list -}}
|
||||
{{- if not $hostnames -}}
|
||||
{{- $hostnames = list .Values.ingress.domain -}}
|
||||
{{- end -}}
|
||||
{{- $delegationHostnames := $httpRouteDelegation.hostnames | default list -}}
|
||||
{{- if not $delegationHostnames -}}
|
||||
{{- $delegationHostnames = list $delegation.serverUrl -}}
|
||||
{{- end -}}
|
||||
{{- $delegationReplicas := include "matrix-synapse.replicas" (dict "scaleDown" .Values.scaleDown "replicas" $delegation.replicas) -}}
|
||||
{{- $res := fromYaml (include "matrix-synapse.effectiveResources" .Values.matrix.resources) -}}
|
||||
{{- $eph := index ($res.limits | default dict) "ephemeral-storage" | default "the computed ephemeral-storage limit" -}}
|
||||
@@ -14,7 +38,7 @@ Matrix Synapse {{ .Chart.AppVersion }} — release {{ .Release.Name }} in namesp
|
||||
|
||||
{{- if .Values.ingress.enabled }}
|
||||
|
||||
ACCESS
|
||||
ACCESS — INGRESS MODE (ingress.enabled: true, httpRoute.enabled: false)
|
||||
https://{{ .Values.ingress.domain }}
|
||||
|
||||
That host carries the whole client and federation API: the Ingress sends "/"
|
||||
@@ -24,11 +48,96 @@ ACCESS
|
||||
shows a warning and federation fails. Follow it with
|
||||
|
||||
kubectl -n {{ .Release.Namespace }} get certificate tls-{{ .Release.Name }}-{{ .Chart.Name }}-ingress
|
||||
{{- else if $httpRoute.enabled }}
|
||||
|
||||
ACCESS — GATEWAY MODE (httpRoute.enabled: true, ingress.enabled: false)
|
||||
{{- range $hostnames }}
|
||||
https://{{ . }}
|
||||
{{- end }}
|
||||
|
||||
That host carries the whole client and federation API: the HTTPRoute sends
|
||||
"/" on it to the Synapse Service, which forwards port 80 to the container's
|
||||
8008.
|
||||
|
||||
THIS RELEASE RENDERS {{ if $delegation.enabled }}TWO HTTPRoutes{{ else }}ONE HTTPRoute{{ end }} AND NOTHING ELSE of the routing
|
||||
path — no Gateway, no class, no Certificate, no policy object.
|
||||
|
||||
{{ .Release.Name }}-{{ .Chart.Name }}-httproute {{ join ", " $hostnames }}
|
||||
{{- if $delegation.enabled }}
|
||||
{{ .Release.Name }}-{{ .Chart.Name }}-delegation-httproute {{ join ", " $delegationHostnames }}
|
||||
{{- end }}
|
||||
|
||||
Three things therefore belong to the cluster, and if any is missing the
|
||||
hostname does not answer — which for Matrix means federation is down, not
|
||||
just the web client:
|
||||
|
||||
1. The Gateway-API CRDs. Without them this install would have failed, so
|
||||
they are present — nothing to check.
|
||||
2. The Gateway(s) named in parentRefs
|
||||
{{- range $httpRoute.parentRefs }}
|
||||
{{ .name }}{{ if .namespace }} in namespace {{ .namespace }}{{ end }}{{ if .sectionName }}, listener {{ .sectionName }}{{ end }} (homeserver)
|
||||
{{- end }}
|
||||
{{- if $delegation.enabled }}
|
||||
{{- range ($httpRouteDelegation.parentRefs | default $httpRoute.parentRefs) }}
|
||||
{{ .name }}{{ if .namespace }} in namespace {{ .namespace }}{{ end }}{{ if .sectionName }}, listener {{ .sectionName }}{{ end }} (delegation)
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
must exist AND their listeners must allow routes from this namespace
|
||||
({{ .Release.Namespace }}) in allowedRoutes. A rejected attachment is visible only
|
||||
in the route's status:
|
||||
|
||||
kubectl -n {{ .Release.Namespace }} get httproute {{ .Release.Name }}-{{ .Chart.Name }}-httproute -o yaml
|
||||
|
||||
Read status.parents[].conditions — "Accepted: True" and
|
||||
"ResolvedRefs: True" is what a working attachment looks like. An empty
|
||||
status means no controller has looked at the route at all.
|
||||
3. {{ if $delegation.enabled }}The TLS certificates — one per hostname, on the Gateway's listeners,
|
||||
or one wildcard listener covering both{{ else }}The TLS certificate for the hostname, on the Gateway's listener{{ end }}.
|
||||
ingress.clusterIssuer is NOT used in this mode and is not required: the
|
||||
chart renders no Certificate here, so there is no
|
||||
tls-{{ .Release.Name }}-{{ .Chart.Name }}-ingress secret{{ if $delegation.enabled }} and no
|
||||
tls-{{ .Release.Name }}-{{ .Chart.Name }}-delegation secret{{ end }} and nothing for
|
||||
cert-manager to do for this release. MATRIX FEDERATION IS HTTPS-ONLY,
|
||||
so a listener without a valid certificate for these names is a
|
||||
federation outage that no probe in this release can see.
|
||||
|
||||
THE UPLOAD LIMIT IS NOT ENFORCED BY ANYTHING THIS CHART RENDERS.
|
||||
|
||||
In Ingress mode an operator sets
|
||||
nginx.ingress.kubernetes.io/proxy-body-size through ingress.annotations —
|
||||
this installation runs with 50m — because Synapse's media repository takes
|
||||
uploads and nginx's default of 1m rejects nearly all of them. IN THIS MODE
|
||||
THAT IS GONE: the Gateway API has no body-size field, it is implementation
|
||||
policy rather than routing, and this chart renders no policy object on
|
||||
purpose (it would bind the chart to one Gateway implementation).
|
||||
|
||||
The ceiling in force is the smaller of max_upload_size in your
|
||||
homeserver.yaml — which this chart cannot see, it comes from 1Password —
|
||||
and whatever the GATEWAY enforces. Neither number anyone can point at is
|
||||
necessarily the one that decides. Set the Gateway's half, in your
|
||||
implementation's own object, to match max_upload_size, BEFORE you point DNS
|
||||
at it.
|
||||
|
||||
AND ONE THING NOT TO RE-CREATE. The live Ingress also carries a
|
||||
configuration-snippet of three more_set_headers directives
|
||||
(X-Forwarded-For $remote_addr, X-Forwarded-Proto $scheme, Host). It LOOKS
|
||||
like what Synapse's x_forwarded setting depends on. It is not:
|
||||
more_set_headers is the headers-more module's OUTPUT-header directive, so
|
||||
those three headers go on the RESPONSE TO THE CLIENT and Synapse never sees
|
||||
them. What Synapse reads with x_forwarded: true is the REQUEST's
|
||||
X-Forwarded-For, which the proxy supplies by itself. Do not spend effort
|
||||
rebuilding this at the Gateway, and do not read its absence as the cause of
|
||||
a rate-limiting or federation problem. (This is a reading of the directive's
|
||||
documented semantics; nothing here A/B-tested a Synapse instance.)
|
||||
|
||||
ingress.annotations is inert in this mode in general: the Gateway API has no
|
||||
annotation channel for controller behaviour.
|
||||
{{- else }}
|
||||
|
||||
ACCESS — NO INGRESS
|
||||
ingress.enabled is false, so this release has no Ingress and no certificate;
|
||||
ingress.clusterIssuer is not needed.
|
||||
ACCESS — NEITHER INGRESS NOR HTTPROUTE
|
||||
ingress.enabled and httpRoute.enabled are both false, so this release has no
|
||||
Ingress, no HTTPRoute and no certificate; ingress.clusterIssuer is not
|
||||
needed.
|
||||
{{- if $delegation.enabled }}
|
||||
ingress.domain still is: the delegation nginx renders it into the
|
||||
/.well-known/matrix/* documents, so it stays the host every other Matrix
|
||||
@@ -56,10 +165,10 @@ DELEGATION — TWO HOSTNAMES THAT ARE NOT INTERCHANGEABLE
|
||||
→ {"m.server": "{{ .Values.ingress.domain }}:443"}
|
||||
/.well-known/matrix/client
|
||||
→ {"m.homeserver": {"base_url": "https://{{ .Values.ingress.domain }}"}}
|
||||
{{- if .Values.ingress.enabled }}
|
||||
{{- if $published }}
|
||||
|
||||
The Ingress publishes them on a SECOND host. Read that split carefully,
|
||||
because the chart cannot check it for you:
|
||||
{{ if $httpRoute.enabled }}A SECOND HTTPRoute publishes{{ else }}The Ingress publishes{{ end }} them on a SECOND host. Read that split
|
||||
carefully, because the chart cannot check it for you:
|
||||
|
||||
matrix.delegation.serverUrl ({{ $delegation.serverUrl }})
|
||||
is the DELEGATING host — the domain in every user ID (@user:{{ $delegation.serverUrl }}).
|
||||
@@ -69,18 +178,36 @@ DELEGATION — TWO HOSTNAMES THAT ARE NOT INTERCHANGEABLE
|
||||
is where Synapse actually answers. It appears inside the documents
|
||||
above and needs no relation to the server name.
|
||||
|
||||
Only the path /.well-known/matrix is routed to nginx on {{ $delegation.serverUrl }};
|
||||
everything else on that host is not served by this release. Both hosts need
|
||||
Only the path /.well-known/matrix is routed to nginx on {{ join ", " $delegationHostnames }};
|
||||
everything else on that host is not served by this release.
|
||||
{{- if $httpRoute.enabled }}
|
||||
|
||||
AND THAT CO-EXISTENCE IS EASIER HERE THAN IT WAS. The server name is usually
|
||||
an apex domain that already serves a website, and under ingress-nginx two
|
||||
Ingress objects cannot both claim one hostname — the admission webhook
|
||||
rejects the second — which is why the delegation rule had to sit inside this
|
||||
chart's single Ingress object. HTTPRoutes attached to the same listener for
|
||||
the same hostname are MERGED by the implementation, longest match winning,
|
||||
so this route may sit beside whatever owns that apex without either side
|
||||
knowing about the other.
|
||||
|
||||
Both hosts need a certificate ON THE GATEWAY'S LISTENERS — this chart renders
|
||||
none in this mode — and a listener without a valid certificate for the server
|
||||
name is a federation outage, not a cosmetic problem.
|
||||
{{- else }}
|
||||
Both hosts need
|
||||
a certificate — the second one is tls-{{ .Release.Name }}-{{ .Chart.Name }}-delegation — and
|
||||
cert-manager only issues it once {{ $delegation.serverUrl }} resolves to this cluster.
|
||||
A pending certificate there is a federation outage, not a cosmetic problem.
|
||||
{{- end }}
|
||||
|
||||
Check what is actually being served, from outside the cluster:
|
||||
|
||||
curl -s https://{{ $delegation.serverUrl }}/.well-known/matrix/server
|
||||
curl -s https://{{ first $delegationHostnames }}/.well-known/matrix/server
|
||||
{{- else }}
|
||||
|
||||
Nothing publishes them, though: ingress.enabled is false, so the documents
|
||||
Nothing publishes them, though: ingress.enabled and httpRoute.enabled are
|
||||
both false, so the documents
|
||||
exist only on the in-cluster Service {{ .Release.Name }}-{{ .Chart.Name }}-delegation-service, and
|
||||
matrix.delegation.serverUrl ({{ $delegation.serverUrl }}) is rendered into no object of this
|
||||
release. Whatever terminates TLS in front of the cluster has to route
|
||||
@@ -101,7 +228,7 @@ NO DELEGATION IN THIS RELEASE — FEDERATION LIKELY DOES NOT WORK
|
||||
8448 anywhere: the container publishes 8008 only, and the Service maps port
|
||||
80 onto it. Unless the delegation is served by something else (a static
|
||||
.well-known on the server-name host, or an SRV record), federation stays
|
||||
broken{{ if .Values.ingress.enabled }} while clients on {{ .Values.ingress.domain }} work fine{{ end }}.
|
||||
broken{{ if $published }} while clients on {{ first $hostnames }} work fine{{ end }}.
|
||||
Setting matrix.delegation.enabled to true and matrix.delegation.serverUrl
|
||||
to the server name lets this chart serve it.
|
||||
{{- end }}
|
||||
@@ -120,7 +247,11 @@ SCALED TO ZERO
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
The PVC, the Secret and the Services stay in place, so no data is lost{{ if .Values.ingress.enabled }} and
|
||||
the certificates are not re-issued{{ end }}. Set it back and upgrade again to
|
||||
the certificates are not re-issued{{ else if $httpRoute.enabled }} and the
|
||||
HTTPRoutes stay in place, so the hostnames keep pointing at Services with no
|
||||
endpoints — the Gateway answers with an error of its own rather than with
|
||||
nothing, and remote homeservers see a failing .well-known instead of an
|
||||
unresolvable name{{ end }}. Set it back and upgrade again to
|
||||
return.
|
||||
{{- end }}
|
||||
|
||||
@@ -190,7 +321,7 @@ THE SERVER NAME IS DECIDED ONCE, AND NOT BY matrix.serverName
|
||||
a different name is a new server with a new database, not an upgrade. Decide
|
||||
it before the first start — together with matrix.delegation.serverUrl, which
|
||||
has to match it.
|
||||
{{- if and .Values.ingress.enabled $delegation.enabled .Values.matrix.serverName $delegation.serverUrl }}
|
||||
{{- if and $published $delegation.enabled .Values.matrix.serverName $delegation.serverUrl }}
|
||||
{{- if ne (printf "%v" .Values.matrix.serverName) (printf "%v" $delegation.serverUrl) }}
|
||||
|
||||
MISMATCH IN THIS RELEASE: matrix.serverName is "{{ .Values.matrix.serverName }}" but the
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
{{/*
|
||||
HTTPRoute - the Gateway-API alternative to matrix-synapse.ingress.yaml
|
||||
(Gitea #114).
|
||||
|
||||
TWO ROUTES, AND THE SHAPE IS NOT WHAT #114 EXPECTED. The issue describes this
|
||||
chart as rendering "its Ingresses in a loop over several apps". It does not -
|
||||
that is freesailarr. This chart renders ONE Ingress object with TWO host rules:
|
||||
|
||||
ingress.domain / synapse service
|
||||
matrix.delegation.serverUrl /.well-known/matrix delegation service
|
||||
|
||||
In the Gateway API that has to become TWO HTTPRoute objects, and not because of
|
||||
style: spec.hostnames applies to the WHOLE object, so one route can carry
|
||||
several hostnames only when they want the SAME rules. These two want different
|
||||
paths and different backends, so one object per hostname is the only faithful
|
||||
translation.
|
||||
|
||||
WHAT THIS MODE IMPROVES, WHICH IS WORTH KNOWING BEFORE THE MIGRATION
|
||||
|
||||
The delegation hostname is the Matrix SERVER NAME - the apex domain, not the
|
||||
homeserver's own host - and it is almost always already serving something else
|
||||
(a website at /). Under ingress-nginx two Ingress objects on one hostname are a
|
||||
fight: the admission webhook rejects the second claim on the same host and
|
||||
path, which is why the delegation rule has to live INSIDE this chart's single
|
||||
Ingress object rather than beside it. HTTPRoutes do not work that way: several
|
||||
routes may attach to the same listener for the same hostname and the
|
||||
implementation merges them by match specificity (a longer path wins). So in
|
||||
this mode the /.well-known/matrix route can sit next to whatever else owns that
|
||||
apex, without either side knowing about the other.
|
||||
|
||||
WHAT THIS RENDERS, AND WHAT IT MUST NOT
|
||||
|
||||
Only HTTPRoutes with parentRefs. No Gateway, no gatewayClassName, no
|
||||
Certificate, no implementation-specific policy object - the subset that looks
|
||||
identical on nginx-gateway-fabric, Cilium, Envoy Gateway and Istio (decision 1
|
||||
of #114). Class and certificate belong to the PLATFORM, on the Gateway and its
|
||||
listeners, in cluster-config. Note that the two hostnames need certificates on
|
||||
TWO listeners (or one wildcard listener covering both), which in Ingress mode
|
||||
were two entries in the one object's tls block.
|
||||
|
||||
WHAT THIS MODE DROPS, MEASURED ON THE LIVE OBJECT - AND ONE OF THE TWO IS NOT
|
||||
WHAT IT LOOKS LIKE
|
||||
|
||||
On 2026-10-12 the live Ingress (jk-cluster, namespace matrix-synapse) carried,
|
||||
all of it OPERATOR-supplied through ingress.annotations - this chart sets none
|
||||
of it:
|
||||
|
||||
nginx.ingress.kubernetes.io/proxy-body-size: 50m
|
||||
nginx.org/client-max-body-size: 50m
|
||||
nginx.ingress.kubernetes.io/configuration-snippet: |
|
||||
more_set_headers "X-Forwarded-For $remote_addr";
|
||||
more_set_headers "X-Forwarded-Proto $scheme";
|
||||
more_set_headers "Host $host:$server_port";
|
||||
|
||||
1. THE BODY SIZE IS THE REAL LOSS. Synapse's media repository takes uploads,
|
||||
its own ceiling is max_upload_size in homeserver.yaml - which this chart
|
||||
cannot see, because the whole config arrives from 1Password through
|
||||
secrets.configSecretRef - and the effective limit is the smaller of the two.
|
||||
In this mode the proxy half becomes THE GATEWAY'S default, so neither number
|
||||
anyone can point at is necessarily the one that decides. There is no
|
||||
counterpart in the Gateway API: body size is implementation policy, not
|
||||
routing, and decision 2 of #114 says it is documented rather than rebuilt.
|
||||
(nginx.org/client-max-body-size is the same limit spelled for the OTHER
|
||||
nginx ingress controller, NGINX Inc's; it is a hedge across both
|
||||
implementations and it disappears for the same reason.)
|
||||
|
||||
2. THE SNIPPET IS NOT LOAD-BEARING, AND THAT TOOK CHECKING RATHER THAN
|
||||
ASSUMING. It looks like the thing Synapse's x_forwarded setting depends on -
|
||||
client IP for rate limiting, and federation. It is not, because
|
||||
more_set_headers is the module's OUTPUT-header directive:
|
||||
headers-more-nginx-module, verbatim - more_set_headers "Replaces (if any) or
|
||||
adds (if not any) the specified OUTPUT headers", while the request-side
|
||||
directive is a different one, more_set_input_headers ("it operates on input
|
||||
headers (or request headers)"). So these three headers are added to the
|
||||
RESPONSE TO THE CLIENT. Synapse never sees them; what Synapse reads with
|
||||
x_forwarded: true is the REQUEST's X-Forwarded-For, which ingress-nginx
|
||||
populates itself.
|
||||
|
||||
The honest statement for the migration is therefore: not translatable, and
|
||||
not needed. The Gateway API's ResponseHeaderModifier could set static
|
||||
response headers but knows no nginx variables ($remote_addr, $scheme,
|
||||
$host), so a rebuild is impossible anyway - and pointless, since dropping
|
||||
them changes nothing Synapse depends on. What does change is that a client
|
||||
stops receiving three headers echoing the proxy's view of its own request,
|
||||
which is at best noise.
|
||||
|
||||
This is a claim about a directive's documented semantics, not an experiment:
|
||||
nothing here observed a Synapse instance with and without the snippet.
|
||||
|
||||
Both are named in the Gateway branch of NOTES.txt, the first as a limit to
|
||||
re-establish at the Gateway and the second as a thing NOT to re-establish.
|
||||
*/}}
|
||||
{{- /*
|
||||
Null-safe like every other block a template here dereferences (#99): an
|
||||
override written without children - "httpRoute:" with its keys deleted - is
|
||||
YAML null, and .Values.httpRoute.enabled would abort with a nil pointer.
|
||||
*/ -}}
|
||||
{{- $httpRoute := .Values.httpRoute | default dict -}}
|
||||
{{- if and $httpRoute.enabled .Values.ingress.enabled -}}
|
||||
{{- fail "ingress.enabled and httpRoute.enabled are both true - set exactly one of them. The Ingress and the HTTPRoute would publish the same Services under the same hostnames over two different data paths, which is already a conflict at DNS level: one name, two addresses, and which one a client reaches is luck. For Matrix it is worse than for a stateless app: the /.well-known/matrix delegation tells every OTHER homeserver in the federation where to find this one, and a server name that resolves to two data paths makes federation succeed or fail per remote server and per retry. A migration runs over a SECOND hostname (set httpRoute.hostnames explicitly while the Ingress keeps ingress.domain), never over parallel operation of both - and the delegation host moves last, after the homeserver host answers." -}}
|
||||
{{- end -}}
|
||||
{{- if $httpRoute.enabled -}}
|
||||
{{- $parentRefs := $httpRoute.parentRefs | default list -}}
|
||||
{{- if not $parentRefs -}}
|
||||
{{- fail "httpRoute.enabled is true but httpRoute.parentRefs is empty - an HTTPRoute without a parent attaches to no Gateway. It is created, it is schema-valid, and it routes NOTHING, with no event and no condition that says so; the only symptom is a hostname that does not answer. Name the cluster's Gateway, e.g. httpRoute.parentRefs: [{name: public, namespace: gateway}]." -}}
|
||||
{{- end -}}
|
||||
{{- /*
|
||||
One source of truth for the hostname: an empty httpRoute.hostnames means
|
||||
[ingress.domain]. Two values that have to agree drift apart. ingress.domain is
|
||||
read outside the routing templates in this chart - the delegation nginx serves
|
||||
it inside /.well-known/matrix/client - so it already carries a required() of
|
||||
its own there and stays mandatory with the Ingress off (README, guideline 12,
|
||||
point 1). This required() is the route's own, so the message names the route.
|
||||
*/ -}}
|
||||
{{- $hostnames := $httpRoute.hostnames | default list -}}
|
||||
{{- if not $hostnames -}}
|
||||
{{- $hostnames = list (required "A Domain/Host must be provided: set ingress.domain (used as the homeserver HTTPRoute's single hostname when httpRoute.hostnames is empty) or set httpRoute.hostnames explicitly" .Values.ingress.domain) -}}
|
||||
{{- end -}}
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: HTTPRoute
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-{{ .Chart.Name }}-httproute
|
||||
spec:
|
||||
parentRefs:
|
||||
{{- toYaml $parentRefs | nindent 4 }}
|
||||
hostnames:
|
||||
{{- toYaml $hostnames | nindent 4 }}
|
||||
rules:
|
||||
{{- /*
|
||||
The Ingress counterpart is `path: / , pathType: Prefix` on the Synapse
|
||||
Service. The port is the Service's port NUMBER and not its name "http":
|
||||
backendRefs takes a PortNumber, so unlike the Ingress this reference
|
||||
cannot be made by name. ci/run-httproute-match-test.sh is what holds the
|
||||
pair - it fails when a backendRef names a Service, or a port, that the
|
||||
same render does not create.
|
||||
*/}}
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: {{ .Release.Name }}-{{ .Chart.Name }}-service
|
||||
port: 80
|
||||
{{- if .Values.matrix.delegation.enabled }}
|
||||
---
|
||||
{{- /*
|
||||
The delegation route, on the SERVER NAME rather than on the homeserver's host.
|
||||
Gated on matrix.delegation.enabled exactly as the second host rule of the
|
||||
Ingress is, and for the same reason: without the delegation workload there is
|
||||
nothing to route to.
|
||||
|
||||
Its hostname deliberately does NOT fall back to httpRoute.hostnames. The
|
||||
delegation host is matrix.delegation.serverUrl - the Matrix server name, which
|
||||
is a different thing from the homeserver's hostname and is what remote servers
|
||||
resolve. Letting a chart-global hostname list cover it would make the two
|
||||
indistinguishable, and the migration order (homeserver host first, server name
|
||||
last) depends on telling them apart. httpRoute.delegation.hostnames exists for
|
||||
the one case where the server name itself has to move.
|
||||
*/ -}}
|
||||
{{- $delegation := $httpRoute.delegation | default dict -}}
|
||||
{{- $delegationHostnames := $delegation.hostnames | default list -}}
|
||||
{{- if not $delegationHostnames -}}
|
||||
{{- $delegationHostnames = list (required "If delegation is enabled you must provide a serverUrl: set matrix.delegation.serverUrl (used as the delegation HTTPRoute's single hostname when httpRoute.delegation.hostnames is empty) or set httpRoute.delegation.hostnames explicitly" .Values.matrix.delegation.serverUrl) -}}
|
||||
{{- end -}}
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: HTTPRoute
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-{{ .Chart.Name }}-delegation-httproute
|
||||
spec:
|
||||
{{- /*
|
||||
Its own parent, falling back to the homeserver route's. The server name may
|
||||
well be published by a different Gateway than the homeserver host - it is a
|
||||
domain somebody else's website usually owns - and in that case this is where
|
||||
it is said.
|
||||
*/}}
|
||||
parentRefs:
|
||||
{{- toYaml ($delegation.parentRefs | default $parentRefs) | nindent 4 }}
|
||||
hostnames:
|
||||
{{- toYaml $delegationHostnames | nindent 4 }}
|
||||
rules:
|
||||
{{- /*
|
||||
PathPrefix and not Exact, and the same path the Ingress uses: the
|
||||
delegation nginx serves two documents below it
|
||||
(/.well-known/matrix/server and /.well-known/matrix/client), so the
|
||||
prefix has to carry both. NO URLRewrite filter - the delegation nginx's
|
||||
own config answers on the full path, so nothing is stripped, which is
|
||||
also why the #118 prefix/redirect assurance has no precondition to meet
|
||||
here.
|
||||
*/}}
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /.well-known/matrix
|
||||
backendRefs:
|
||||
- name: {{ .Release.Name }}-{{ .Chart.Name }}-delegation-service
|
||||
port: 80
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -119,10 +119,11 @@
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"description": "Gates the whole Ingress template (issue #93). false omits the Ingress and makes clusterIssuer unnecessary - but NOT domain, which the delegation nginx also serves in its /.well-known/matrix/* endpoints.",
|
||||
"description": "Gates the whole Ingress template (issue #93). Mutually exclusive with httpRoute.enabled - the render aborts when both are true. false omits the Ingress and makes clusterIssuer unnecessary - but NOT domain, which the delegation nginx also serves in its /.well-known/matrix/* endpoints and which the HTTPRoute reads as its hostname.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"domain": {
|
||||
"description": "The homeserver hostname. Read by the Ingress, by the HTTPRoute (when httpRoute.hostnames is empty) and by the delegation nginx, which serves it inside /.well-known/matrix/client - so it is required in every mode while delegation is on.",
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"clusterIssuer": {
|
||||
@@ -132,13 +133,53 @@
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"annotations": {
|
||||
"description": "Extra Ingress annotations, merged with the cert-manager annotation the chart always sets. Free-form keys by nature.",
|
||||
"description": "Extra Ingress annotations, merged with the cert-manager annotation the chart always sets. Free-form keys by nature. INERT IN GATEWAY MODE: the Gateway API has no annotation channel. This installation's live Ingress carries proxy-body-size 50m (which has no counterpart and must be re-established at the Gateway) and a configuration-snippet of more_set_headers directives (which must NOT be re-established - see values.yaml, httpRoute block).",
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"httpRoute": {
|
||||
"description": "Gateway-API alternative to the Ingress (Gitea #114). Mutually exclusive with ingress.enabled - the render aborts when both are true, naming both value paths. The chart renders only HTTPRoutes with parentRefs; class and certificate sit on the Gateway and belong to the platform. TWO routes, not one: the Ingress is a single object with two host rules (homeserver at /, delegation at /.well-known/matrix), and an HTTPRoute's hostnames apply to the whole object, so two hostnames wanting different paths and backends must be two objects. NOT COVERED IN THIS MODE: proxy-body-size, which this installation carries at 50m and which Synapse's own max_upload_size has to agree with - see values.yaml, httpRoute block, including why the configuration-snippet on the live object is NOT a loss. The Gateway-API CRDs must be present in the cluster, because the chart cannot detect them.",
|
||||
"type": ["object", "null"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"enabled": { "type": "boolean" },
|
||||
"parentRefs": {
|
||||
"description": "The Gateway(s) the homeserver route attaches to. Required while httpRoute.enabled is true, enforced by required()-equivalent in the template (an empty list aborts the render) - a JSON Schema \"required\" cannot do it, because the key ships as an empty list and is therefore present. Entries are the Gateway API's ParentReference and are passed through unchanged.",
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/definitions/parentRef" }
|
||||
},
|
||||
"hostnames": {
|
||||
"description": "Empty means [ingress.domain], so the homeserver hostname has a single source of truth. Does NOT cover the delegation host - see httpRoute.delegation.hostnames.",
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"delegation": {
|
||||
"description": "The delegation route, rendered while matrix.delegation.enabled is true - the same switch the Ingress's second host rule uses.",
|
||||
"type": ["object", "null"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"parentRefs": {
|
||||
"description": "Empty falls back to httpRoute.parentRefs. Worth setting when the server name is published by a different Gateway than the homeserver host, which is the normal case - the server name is an apex domain somebody else's website usually owns.",
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/definitions/parentRef" }
|
||||
},
|
||||
"hostnames": {
|
||||
"description": "Empty means [matrix.delegation.serverUrl]. Deliberately does NOT fall back to httpRoute.hostnames: the server name is what remote homeservers resolve and is a different thing from the homeserver's hostname, and the migration order depends on telling them apart.",
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"parentRef": {
|
||||
"description": "A Gateway API ParentReference, passed verbatim into HTTPRoute.spec.parentRefs. Deliberately left OPEN (no additionalProperties: false), for the same reason probes and security contexts are: Kubernetes owns this schema, the Gateway API adds fields to it across releases, and a closed list here would reject a field only upstream knows about. What the chart can assert is that \"name\" is there - it is required by the API and a reference without it attaches to nothing, which is the silent failure this chart aborts the render over.",
|
||||
"type": "object",
|
||||
"required": ["name"]
|
||||
},
|
||||
"resources": {
|
||||
"description": "May be null / empty: the chart then falls back to its default requests and computed limits (issue #99) instead of rendering a container without resource accounting.",
|
||||
"type": ["object", "null"],
|
||||
|
||||
@@ -155,10 +155,133 @@ ingress:
|
||||
# read by the delegation nginx, which serves it in the /.well-known/matrix/*
|
||||
# endpoints, so it stays required whenever matrix.delegation.enabled is true.
|
||||
# Only clusterIssuer becomes unnecessary.
|
||||
#
|
||||
# MUTUALLY EXCLUSIVE WITH httpRoute.enabled - the render aborts when both are
|
||||
# true, and the message names both value paths.
|
||||
enabled: true
|
||||
domain: null
|
||||
clusterIssuer: null
|
||||
className: nginx
|
||||
# Extra annotations for the Ingress; merged with the cert-manager
|
||||
# cluster-issuer annotation the chart always sets.
|
||||
#
|
||||
# INERT IN GATEWAY MODE - the Gateway API has no annotation channel for
|
||||
# controller behaviour. For THIS chart that is not a formality: read the
|
||||
# httpRoute block below for what this installation actually has in here and
|
||||
# which half of it matters.
|
||||
annotations: {}
|
||||
|
||||
# THE GATEWAY-API ALTERNATIVE TO THE INGRESS (Gitea #114).
|
||||
#
|
||||
# Mutually exclusive with ingress.enabled - the render aborts when both are
|
||||
# true. The chart renders ONLY HTTPRoutes with parentRefs: no Gateway, no
|
||||
# class, no Certificate, and no implementation-specific policy object. Class
|
||||
# and certificate sit on the Gateway and its listeners and belong to the
|
||||
# platform (cluster-config), which is what keeps the Gateway implementation
|
||||
# replaceable without touching this chart again.
|
||||
#
|
||||
# TWO ROUTES, not one. The Ingress is a single object with two HOST rules - the
|
||||
# homeserver on ingress.domain at "/", and the delegation nginx on
|
||||
# matrix.delegation.serverUrl at "/.well-known/matrix". An HTTPRoute's
|
||||
# hostnames apply to the whole object, so two hostnames wanting different paths
|
||||
# and different backends must be two objects. (#114 describes this chart as
|
||||
# rendering its Ingresses in a loop over several apps; it does not - that is
|
||||
# freesailarr.)
|
||||
#
|
||||
# ONE THING THIS MODE MAKES EASIER, and it is the awkward part of Matrix
|
||||
# delegation: the server name is usually an apex domain that already serves a
|
||||
# website. Under ingress-nginx two Ingress objects cannot both claim one
|
||||
# hostname - the admission webhook rejects the second - which is why the
|
||||
# delegation rule has to live inside this chart's single Ingress object.
|
||||
# HTTPRoutes attached to the same listener for the same hostname are MERGED by
|
||||
# the implementation, longest match winning, so here the /.well-known/matrix
|
||||
# route can sit beside whatever owns that apex without either side knowing.
|
||||
#
|
||||
# WHAT THIS MODE COSTS, MEASURED ON THE LIVE OBJECT ON 2026-10-12, where the
|
||||
# Ingress carried - all of it OPERATOR-supplied through ingress.annotations,
|
||||
# since this chart sets none of it:
|
||||
#
|
||||
# nginx.ingress.kubernetes.io/proxy-body-size: 50m
|
||||
# nginx.org/client-max-body-size: 50m
|
||||
# nginx.ingress.kubernetes.io/configuration-snippet: |
|
||||
# more_set_headers "X-Forwarded-For $remote_addr";
|
||||
# more_set_headers "X-Forwarded-Proto $scheme";
|
||||
# more_set_headers "Host $host:$server_port";
|
||||
#
|
||||
# 1. THE BODY SIZE IS THE REAL LOSS. Synapse's media repository takes uploads
|
||||
# and its own ceiling is max_upload_size in homeserver.yaml - which this
|
||||
# chart cannot see, because the whole config comes from 1Password through
|
||||
# secrets.configSecretRef. The effective limit is the smaller of the two,
|
||||
# and in this mode the proxy half becomes THE GATEWAY'S default: neither
|
||||
# number anyone can point at is necessarily the one that decides. The
|
||||
# Gateway API has no body-size field, so per decision 2 of #114 this is
|
||||
# documented rather than rebuilt. Set it next to the Gateway, in that
|
||||
# implementation's own object, and set it to match max_upload_size.
|
||||
# (nginx.org/client-max-body-size is the same limit spelled for NGINX Inc's
|
||||
# ingress controller rather than ingress-nginx - a hedge across both - and
|
||||
# it goes for the same reason.)
|
||||
#
|
||||
# 2. THE SNIPPET IS NOT LOAD-BEARING, WHICH TOOK CHECKING. It looks like what
|
||||
# Synapse's x_forwarded setting depends on - client IP for rate limiting,
|
||||
# and federation. It is not: more_set_headers is the OUTPUT-header directive
|
||||
# of headers-more-nginx-module ("Replaces (if any) or adds (if not any) the
|
||||
# specified output headers"), while the request-side directive is
|
||||
# more_set_input_headers. These three headers are therefore added to the
|
||||
# RESPONSE TO THE CLIENT; Synapse never sees them, and what it reads with
|
||||
# x_forwarded: true is the REQUEST's X-Forwarded-For, which ingress-nginx
|
||||
# sets itself.
|
||||
#
|
||||
# So for the migration: not translatable, and not needed. The Gateway API's
|
||||
# ResponseHeaderModifier can set static response headers but knows no nginx
|
||||
# variables ($remote_addr, $scheme, $host), so rebuilding it is impossible
|
||||
# anyway - and pointless. Do NOT re-create it at the Gateway in the belief
|
||||
# that Synapse needs it.
|
||||
#
|
||||
# Stated as what it is: a reading of the directive's documented semantics.
|
||||
# Nothing here observed a Synapse instance with and without the snippet.
|
||||
#
|
||||
# THE GATEWAY-API CRDs MUST BE PRESENT. The chart cannot detect them
|
||||
# (.Capabilities.APIVersions is unreliable at template time) and an HTTPRoute
|
||||
# without them fails at install, so this is a value and not a guess.
|
||||
httpRoute:
|
||||
enabled: false
|
||||
|
||||
# The Gateway(s) the HOMESERVER route attaches to - a platform object,
|
||||
# usually in another namespace, whose listener must allow this namespace in
|
||||
# allowedRoutes. Entries are the Gateway API's ParentReference (name, and
|
||||
# optionally namespace, sectionName, port, group, kind), passed through
|
||||
# unchanged:
|
||||
#
|
||||
# parentRefs:
|
||||
# - name: public
|
||||
# namespace: gateway
|
||||
# sectionName: https
|
||||
#
|
||||
# REQUIRED while httpRoute.enabled is true - the render aborts on an empty
|
||||
# list, because a route without a parent is created, is valid and routes
|
||||
# nothing, with no event to say so.
|
||||
parentRefs: []
|
||||
|
||||
# Empty means [ingress.domain], so the homeserver hostname has one source of
|
||||
# truth. Set explicitly only to publish under a different or an additional
|
||||
# name - which is how a migration from the Ingress runs: the HTTPRoute takes
|
||||
# a second hostname while the Ingress keeps ingress.domain.
|
||||
#
|
||||
# THE DELEGATION HOST IS NOT COVERED BY THIS LIST, deliberately - see below.
|
||||
hostnames: []
|
||||
|
||||
delegation:
|
||||
# The delegation route's own parent. Empty falls back to
|
||||
# httpRoute.parentRefs. Worth setting when the SERVER NAME is published by
|
||||
# a different Gateway than the homeserver host, which is the normal case:
|
||||
# the server name is an apex domain somebody else's website usually owns.
|
||||
parentRefs: []
|
||||
|
||||
# Empty means [matrix.delegation.serverUrl], and it deliberately does NOT
|
||||
# fall back to httpRoute.hostnames. The server name is a different thing
|
||||
# from the homeserver's hostname - it is what REMOTE homeservers resolve -
|
||||
# and the migration order depends on telling them apart: move the
|
||||
# homeserver host first, confirm it answers, move the server name last.
|
||||
# A chart-global hostname list covering both would erase that distinction.
|
||||
# Set this only when the server name itself has to move.
|
||||
hostnames: []
|
||||
Reference in new issue
Block a user