Group 3 of the #114 sweep, first chart. keycloak has TWO Ingress objects - the public hostname and the admin hostname - so it gets TWO HTTPRoutes, with httpRoute.admin.parentRefs falling back to httpRoute.parentRefs exactly as ingress.admin.className falls back to ingress.className: "which data path" is a property of the Gateway in this API, not of the route, so an internal Gateway here is what an internal ingress class is in the other mode. The admin route stays gated on ingress.admin.enabled, because that value is not an Ingress switch - it also decides whether KC_HOSTNAME_ADMIN is set on the Deployment, i.e. whether the admin hostname exists at all. Switching to Gateway mode therefore must not require turning it off. FIXED ALONG THE WAY: the admin Ingress did not hang off ingress.enabled. Measured on origin/main, `helm template -f ci/keycloak/test-values.yaml --set ingress.enabled=false` emitted kc-keycloak-admin-ingress - so "ingress.enabled: false" did not mean "no Ingress", and ingress.clusterIssuer stayed required for an object nobody asked for. It also made Gateway mode impossible: the admin Ingress would have gone on claiming the admin host next to the admin HTTPRoute, one host over two data paths, which is the exact conflict the mutual exclusion exists to prevent. The default render is unchanged; what changed is a combination that could not be coherent. WHAT THIS MODE DROPS, MEASURED ON THE LIVE CLUSTER rather than inferred. On 2026-10-12 BOTH of this chart's Ingress objects in jk-cluster carried nginx.ingress.kubernetes.io/proxy-buffer-size: 16k nginx.ingress.kubernetes.io/proxy-buffers-number: 4 set by the OPERATOR through ingress.annotations and ingress.admin.annotations - the chart sets neither, which is why reading the chart alone does not reveal them. The Gateway API has no annotation channel and no counterpart for proxy buffer sizing; it is implementation policy, not routing. Per decision 1 of #114 the chart renders no policy object, so this is NAMED and not rebuilt, in values.yaml, in the schema and in the Gateway branch of NOTES.txt, with the consequence spelled out in Keycloak's own terms: large headers (JWTs, SAML assertions, long Set-Cookie chains on the login flow) make a too-small buffer answer 502 at the PROXY while Keycloak logs the request as successful. Named and deliberately not touched: four further Ingress objects in the same namespace (keycloak-realm-*-ingress, with app-root) belong to the Helm release jk-cluster-config-fleet-cd-keycloak-realm-ingress, i.e. to another repository. They point at this release's Service under four further hostnames. This chart renders exactly the two routes its own two Ingresses cover. Minor: additive, defaults unchanged. Empty helm template diff against origin/main with ci/keycloak/test-values.yaml. Part of #114
165 lines
8.8 KiB
YAML
165 lines
8.8 KiB
YAML
{{/*
|
|
HTTPRoute - the Gateway-API alternative to keycloak.ingress.yaml (Gitea #114).
|
|
TWO ROUTES, because this chart has TWO Ingress objects: the public hostname and
|
|
the admin hostname. Read keycloak.ingress.yaml first - everything it says about
|
|
what the admin hostname is NOT (it is not an access control) holds here
|
|
unchanged, and the Gateway API gives no new way to make it one.
|
|
|
|
WHAT THIS RENDERS, AND WHAT IT MUST NOT
|
|
|
|
Only HTTPRoutes with parentRefs. No Gateway, no gatewayClassName, no
|
|
Certificate, no implementation-specific policy object. That subset is identical
|
|
on nginx-gateway-fabric, Cilium, Envoy Gateway and Istio, which is the whole
|
|
point (decision 1 of #114). Class and certificate therefore belong to the
|
|
PLATFORM, on the Gateway and its listeners, in cluster-config.
|
|
|
|
WHAT ingress.admin.className BECOMES, AND WHY THERE IS AN admin BLOCK HERE
|
|
|
|
The admin Ingress exists so the console can sit on an INTERNAL ingress class or
|
|
in a different DNS zone (ingress.admin.className, falling back to
|
|
ingress.className). "Which data path" is a property of the Gateway in this API,
|
|
not of the route, so its counterpart is the PARENT: httpRoute.admin.parentRefs,
|
|
falling back to httpRoute.parentRefs exactly as the class does. Without that
|
|
fallback the admin route could only ever hang off the same Gateway as the public
|
|
one, and the one useful half of the second hostname would be gone in this mode.
|
|
|
|
THE ANNOTATIONS THIS MODE DROPS ARE NOT HYPOTHETICAL FOR THIS CHART
|
|
|
|
Measured on the live cluster on 2026-10-12, on BOTH of this chart's Ingress
|
|
objects (jk-cluster/keycloak):
|
|
|
|
nginx.ingress.kubernetes.io/proxy-buffer-size: 16k
|
|
nginx.ingress.kubernetes.io/proxy-buffers-number: 4
|
|
|
|
The CHART sets neither - they are operator-supplied, through
|
|
ingress.annotations and ingress.admin.annotations, which is why a reader of
|
|
this repository alone would never find them. They are also not cosmetic:
|
|
Keycloak's responses carry large headers (JWTs, SAML assertions, long Set-Cookie
|
|
chains on the login flow), and a proxy whose buffer is too small answers 502
|
|
ITSELF. The failure then looks like Keycloak being broken while Keycloak logged
|
|
a perfectly successful request.
|
|
|
|
ingress.annotations and ingress.admin.annotations are INERT in this mode - the
|
|
Gateway API has no annotation channel for controller behaviour, and buffer
|
|
sizing has no counterpart in the specification at all; it is implementation
|
|
policy. So the Gateway branch of NOTES.txt NAMES it instead of rebuilding it
|
|
(decision 2 of #114). Whoever migrates this host sets the equivalent next to
|
|
the Gateway, in that implementation's own object.
|
|
|
|
A NEIGHBOUR ON THE SAME ZONE, WHICH THIS CHART DOES NOT OWN
|
|
|
|
The same namespace carries four more Ingress objects - keycloak-realm-*-ingress
|
|
with nginx.ingress.kubernetes.io/app-root - from the Helm release
|
|
jk-cluster-config-fleet-cd-keycloak-realm-ingress, i.e. from ANOTHER
|
|
repository. They point at THIS release's Service under four further hostnames
|
|
(bk.id..., id.mail..., muth-event.id..., id.klein.pub). They are not rendered
|
|
here and must not be: this chart renders exactly the two routes its own two
|
|
Ingresses cover. Migrating the host is therefore not finished by switching this
|
|
chart - see the wiki page for the list.
|
|
*/}}
|
|
{{- /*
|
|
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 -}}
|
|
{{- $settings := fromYaml (include "keycloak.settings" .) -}}
|
|
{{- $adminEnabled := eq (include "keycloak.adminHostnameEnabled" .) "true" -}}
|
|
{{- 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 Service under the same hostname 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 Keycloak it is worse than for a stateless app, because the server derives KC_HOSTNAME from ingress.domain and generates absolute redirect and asset URLs for it - a login that starts on one path and finishes on the other fails on the state cookie, not with an error that names the cause. A migration runs over a SECOND hostname (set httpRoute.hostnames explicitly while the Ingress keeps ingress.domain), never over parallel operation of both." -}}
|
|
{{- end -}}
|
|
{{- if $httpRoute.enabled -}}
|
|
{{- $admin := $httpRoute.admin | default dict -}}
|
|
{{- $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
|
|
required in EVERY mode in this chart and its required() lives in the
|
|
keycloak.frontendUrl helper, because the Deployment reads it as KC_HOSTNAME -
|
|
guideline 12.1, and the reason this chart's value cannot be gated away. The
|
|
required() below is therefore a second, redundant guard rather than the only
|
|
one; it stays so the message names the route when the route is what is missing
|
|
a hostname.
|
|
*/ -}}
|
|
{{- $hostnames := $httpRoute.hostnames | default list -}}
|
|
{{- if not $hostnames -}}
|
|
{{- $hostnames = list (required "A Domain/Host must be provided: set ingress.domain (used as the public 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
|
|
labels:
|
|
app: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
|
|
spec:
|
|
parentRefs:
|
|
{{- toYaml $parentRefs | nindent 4 }}
|
|
hostnames:
|
|
{{- toYaml $hostnames | nindent 4 }}
|
|
rules:
|
|
{{- /*
|
|
The Ingress counterpart is `path: / , pathType: Prefix` on the serving
|
|
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. It is read from the same settings helper the
|
|
Service renders its port from, so the two cannot drift;
|
|
ci/run-httproute-match-test.sh holds the pair for the case where they do.
|
|
*/}}
|
|
- matches:
|
|
- path:
|
|
type: PathPrefix
|
|
value: /
|
|
backendRefs:
|
|
- name: {{ .Release.Name }}-{{ .Chart.Name }}-service
|
|
port: {{ $settings.ports.http | int64 }}
|
|
{{- if $adminEnabled }}
|
|
---
|
|
{{- /*
|
|
The admin route. Gated on ingress.admin.enabled, the same switch the admin
|
|
Ingress uses, because that value is not an Ingress switch: it also decides
|
|
whether KC_HOSTNAME_ADMIN is set on the Deployment, i.e. whether the admin
|
|
hostname is in play AT ALL. So it stays mode-independent and the ROUTING
|
|
OBJECT follows the mode - which is also why switching to Gateway mode must not
|
|
require setting it to false.
|
|
*/ -}}
|
|
{{- $adminHostnames := $admin.hostnames | default list -}}
|
|
{{- if not $adminHostnames -}}
|
|
{{- $adminHostnames = list (required "An admin Domain/Host must be provided while ingress.admin.enabled is true: set ingress.admin.domain (used as the admin HTTPRoute's single hostname when httpRoute.admin.hostnames is empty) or set httpRoute.admin.hostnames explicitly" ((.Values.ingress.admin | default dict).domain)) -}}
|
|
{{- end -}}
|
|
apiVersion: gateway.networking.k8s.io/v1
|
|
kind: HTTPRoute
|
|
metadata:
|
|
name: {{ .Release.Name }}-{{ .Chart.Name }}-admin-httproute
|
|
labels:
|
|
app: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
|
|
spec:
|
|
{{- /*
|
|
Its own parent, falling back to the public one - the counterpart of
|
|
ingress.admin.className falling back to ingress.className. An internal
|
|
Gateway here is the useful half of the admin hostname, and still not the
|
|
access control keycloak.ingress.yaml is about.
|
|
*/}}
|
|
parentRefs:
|
|
{{- toYaml ($admin.parentRefs | default $parentRefs) | nindent 4 }}
|
|
hostnames:
|
|
{{- toYaml $adminHostnames | nindent 4 }}
|
|
rules:
|
|
{{- /*
|
|
The SAME backend and the same path as the public route. The
|
|
administration console is not a separate application and not a separate
|
|
path - only the hostname differs, and Keycloak generates its admin URLs
|
|
for that hostname because of KC_HOSTNAME_ADMIN.
|
|
*/}}
|
|
- matches:
|
|
- path:
|
|
type: PathPrefix
|
|
value: /
|
|
backendRefs:
|
|
- name: {{ .Release.Name }}-{{ .Chart.Name }}-service
|
|
port: {{ $settings.ports.http | int64 }}
|
|
{{- end }}
|
|
{{- end }}
|