Das Issue trug nur einen Titel. Dies ist die Bestandsaufnahme und der freigegebene Plan — die beiden offenen Entscheidungen sind beantwortet, der Rest ist aus der Dokumentation belegt.
Verifizierte Fakten
Gegen die Registry und die Upstream-Dokumentation geprüft, nicht aus der Erinnerung:
Abbild
quay.io/keycloak/keycloak
Aktuell
26.8.0 (01.10.2026); daneben 26.8, latest
Chart-Version daraus
1.0.0+up26.8.0
Probes
/health/started, /health/live, /health/ready — alle drei nativ, auf dem Management-Port 9000, freigeschaltet über health-enabled=true
Zustand
keine PVC — alles liegt in der Datenbank, der Dienst ist zustandslos
persistente Sitzungen sind in 26 die Vorgabe — Sitzungen liegen in der Datenbank und überleben Neustarts; dafür höhere Datenbanklast. Widerrufene Access-Tokens werden ebenfalls persistiert
Eine Renovate-Falle in der Tag-Landschaft: neben 26.8.0 gibt es Rebuild-Tags der Form 26.8.0-0, 26.8.0-1. Nach Semver sind das Prereleases von 26.8.0 und sortieren niedriger, nach Docker-Versionierung nicht zwangsläufig — der customManagers-Block braucht eine Beschränkung, und sie ist zu belegen, nicht anzunehmen. Gleiche Gattung wie die Commit-SHA-Tags bei garage (#81).
Die sicherheitsrelevante Stelle
Hostname und Proxy-Header sind bei Keycloak keine Kosmetik. Wörtlich: hinter einem Reverse Proxy muss proxy-headers gesetzt sein, und „Misconfiguration will leave Keycloak exposed to security vulnerabilities" — Host-Header-Injection, mit der ein Angreifer die vom Server erzeugten URLs manipulieren und „steal sensitive data like tokens or passwords" kann.
Folgen für den Chart: KC_HOSTNAME wird aus ingress.domainabgeleitet, nicht freigelassen; proxy-headers ist gesetzt und kein optionaler Wert; und der Management-Port 9000 wird nicht veröffentlicht — er trägt die Health-Endpunkte und optional die Metriken, beide unauthentifiziert.
Entscheidung 1: zwei Replicas mit cache=ispn
Keycloak ist anders gelagert als die beiden letzten Charts: ein Mailserver darf kurz weg sein, weil SMTP wiederholt, ein Blob-Store hat einen Abnehmer — ein SSO-Server nimmt beim Ausfall jede abhängige Anmeldung mit.
Die Discovery kostet inzwischen fast nichts: jdbc-ping ist die Vorgabe, die Knoten finden sich über die Datenbank, die ohnehin da ist. cache-stack=kubernetes mit DNS_PING ist deprecated — also kein Headless-Service, keine JGroups-DNS-Verdrahtung, keine zusätzliche Option außer cache=ispn.
Der Preis: doppelte Datenbankverbindungen, die Pool-Größen müssen das tragen, zusammen mit der ohnehin höheren Last durch die persistenten Sitzungen.
Und die Folge, die teuer geworden wäre
Rolling Updates sind nur innerhalb einer Minor-Linie unterstützt. Seit 26.6.0 sind Patch-Updates (26.8.0 → 26.8.1) als Zero-Downtime-Rolling-Update unterstützt und standardmäßig aktiv. Minor-Sprünge (26.8 → 26.9) verlangen das vollständige Herunterfahren samt Sicherung von Konfiguration und Datenbank. Upstream liefert dafür ein Prüfwerkzeug:
kc.sh update-compatibility metadata --file=/pfad/datei.json # mit der ALTEN Fassung
kc.sh update-compatibility check --file=/pfad/datei.json # mit der NEUEN Fassung
Erfolg meldet [OK] Rolling Update is available., sonst wird die unverträgliche Eigenschaft benannt; der Exit-Code ist für Automatisierung gedacht.
Das kollidiert mit unserer Renovate-Konfiguration.automerge: true gilt dort global, Majors eingeschlossen (renovate.json5, Kopf der Datei), mit minimumReleaseAge: "3 days" für Patch, Minor und Digest. Ein Minor-Sprung von Keycloak würde also nach drei Tagen von allein mergen, den Chart publizieren und bei zwei Replicas als Rolling Update ausgerollt werden — genau das Verfahren, das Upstream für Minor-Sprünge ausschließt. Niemand würde es merken, bis sich zwei Fassungen in einem Infinispan-Cluster begegnen.
Deshalb gehört in renovate.json5 eine packageRules-Regel für dieses Abbild: automerge: false für minor und major, Patch-Updates behalten den Automerge — genau die Trennung, die Upstreams Unterstützungsmatrix zieht. Das Verfahren für Minor-Sprünge (scaleDown: true, Sicherung, Bump, wieder hoch) gehört in NOTES.txt und auf die Wiki-Seite; der dafür nötige Schalter existiert in diesem Repo schon als chart-globales scaleDown.
Da hiermit eine Renovate-Regel dazukommt, ist die Wiki-Seite Renovate im selben Durchgang zu ergänzen (CLAUDE.md, Tabelle „What obliges a wiki edit").
Entscheidung 2: Admin-Konsole auf eigenem Hostnamen
Über Keycloaks eigenen Mechanismus hostname-admin, damit der Server seine Admin-URLs für diesen Host erzeugt. Darauf lässt sich eine IP-Beschränkung oder eine interne Ingress-Klasse legen, ohne die öffentliche Anmeldung anzutasten.
Bewusst nicht über Pfadregeln am Ingress: wer nur /admin sperrt, bricht leicht die Weiterleitungen, weil die Konsole Ressourcen auch unter anderen Pfaden lädt — und der Server generiert weiter URLs für den öffentlichen Host.
Umsetzung: zwei Ingress-Objekte und zwei Hostnamen nach unserer Konvention (ingress.domain und ingress.clusterIssuer required ohne Vorgabe, className mit Vorgabe nginx, TLS-Secret tls-<release>-keycloak-ingress beziehungsweise …-admin-ingress). Der Admin-Ingress ist abschaltbar, damit die Konsole auch rein clusterintern betrieben werden kann.
Was ohne Entscheidung festliegt
Externe Postgres, Zugang aus einem OnePasswordItem, Geheimnisse per secretKeyRef als Umgebungsvariablen — wie bei garage entschieden, und wie paperless-ngx und stalwart es halten. Zu prüfen: ob Keycloak eine _FILE-Form für db-password und bootstrap-admin-password kennt. Kennt es sie, wird sie genommen, denn sie hält das Geheimnis aus der Prozessumgebung heraus.
Kein eigenes Abbild. Die Doku empfiehlt kc.sh build und dann start --optimized; ohne Build „significantly increases startup time", weil db als Build-Option beim Start eine Augmentierung auslöst. Ein eigenes Abbild zu bauen heißt aber, dass dieses Repository Abbilder veröffentlicht — dieselbe Grenze wie bei garage. Also Standardabbild, großzügiges Startup-Budget, und die echte Startzeit ist ein Messpunkt.
Deployment, keine StatefulSet. Der Dienst ist zustandslos, es gibt keine PVC und keine stabile Identität zu wahren. Unsere Konvention verlangt die StatefulSet für stateful Anwendungen; hier wäre sie Zierde.
Bootstrap-Admin über KC_BOOTSTRAP_ADMIN_USERNAME/KC_BOOTSTRAP_ADMIN_PASSWORD aus dem Tresor. Zwei Eigenheiten für NOTES.txt: die Variablen heißen seit 26 so (vorher KEYCLOAK_ADMIN/KEYCLOAK_ADMIN_PASSWORD), und sie erzeugen einen temporären Admin, anders als die alten, die einen regulären anlegten — es ist also ein Pflichtschritt, danach einen dauerhaften Administrator anzulegen. Bleiben die Variablen gesetzt, nachdem der Admin existiert, erscheint bei jedem Start eine Zeile „initial administrator could not be created"; harmlos, aber es gehört erklärt, sonst sucht jemand einen Fehler.
Vermutlich keine ConfigMap. Keycloak wird vollständig über Optionen und Umgebungsvariablen konfiguriert. Entsteht keine ConfigMap, greift die checksum/config-Regel (#82) nicht — und das gehört ausdrücklich in values.yaml geschrieben, sonst sieht es aus wie eine Auslassung.
PodDisruptionBudget mit minAvailable: 1. Mit zwei Replicas ist das das Stück, das verhindert, dass ein Node-Drain beide gleichzeitig nimmt — sonst kauft die zweite Replica nichts gegen den häufigsten Ausfallgrund. Das ist ein neuer Objekttyp für dieses Repository: wird er aufgenommen, gehört die Konvention nach README undChart-Style-Guide.
Umfang
Gebaut aus template-chart/ nach CLAUDE.md und dem Stilleitfaden: Deployment, zwei Services (HTTP 8080, Management 9000 — letzterer nicht veröffentlicht), zwei Ingresses, OnePasswordItem für Datenbank und Bootstrap-Admin, PDB, Probes auf allen drei nativen Endpunkten, berechnete Limits über den limitFactor-Helfer, replicas je Workload plus scaleDown, strenges values.schema.json mit den null-sicheren Fallback-Helfern, validiert gegen die echten Bundle-Werte.
CI: publish-keycloak.yml aus einer bestehenden Hülle kopiert; keycloak in die chart-name-Liste von change-app-version.yml; Fixtures unter ci/keycloak/ (Wegwerf-Postgres, Stub-CRD, Dummy-Geheimnisse) — ein Ausschluss wäre hier schwach zu begründen, weil der Dienst ohne Volume und mit einer Datenbank gut testbar ist; customManagers-Block plus die Automerge-Regel oben.
Wiki im selben Durchgang: neue Seite keycloak (Werte-Tabelle, Tresor-Felder, Erstinstallation mit dem dauerhaften Administrator als Pflichtschritt, Traps-Abschnitt), Zeile in Charts, Eintrag in _Sidebar.md, Ergänzung auf Renovate wegen der neuen Regel, Chart-Style-Guidenur falls das PDB als Konvention aufgenommen wird, und Lessons-Learned für die allgemeine Form „ein Upgrade-Verfahren, das der Automerge nicht kennt" — das wiederholt sich bei jeder Anwendung, deren Cluster-Fassungen nicht gemischt werden dürfen.
Offen, am laufenden Dienst zu messen
Wie lange braucht der Start mit db zur Laufzeit gesetzt? Bestimmt das Startup-Budget, und ein zu knappes Budget ergibt einen CrashLoop, der nach einem Anwendungsfehler aussieht.
Hält readOnlyRootFilesystem: true, und welche Pfade brauchen ein emptyDir (erwartet /opt/keycloak/data und /tmp)?
Gibt es eine _FILE-Form für db-password und bootstrap-admin-password?
Findet jdbc-ping die zweite Replica allein aus cache=ispn und der Datenbankkonfiguration, ohne weitere Optionen?
Liefert hostname-admin auf einem zweiten Hostnamen die Konsole sauber aus, samt ihrer Ressourcen?
Das Issue trug nur einen Titel. Dies ist die Bestandsaufnahme **und** der freigegebene Plan — die beiden offenen Entscheidungen sind beantwortet, der Rest ist aus der Dokumentation belegt.
## Verifizierte Fakten
Gegen die Registry und die Upstream-Dokumentation geprüft, nicht aus der Erinnerung:
| | |
| --- | --- |
| Abbild | `quay.io/keycloak/keycloak` |
| Aktuell | **26.8.0** (01.10.2026); daneben `26.8`, `latest` |
| Chart-Version daraus | `1.0.0+up26.8.0` |
| Probes | `/health/started`, `/health/live`, `/health/ready` — **alle drei nativ**, auf dem **Management-Port 9000**, freigeschaltet über `health-enabled=true` |
| Zustand | **keine PVC** — alles liegt in der Datenbank, der Dienst ist zustandslos |
| Datenbank | `db`, `db-url-host`, `db-url-database`, `db-username`, `db-password`, `db-pool-*` |
| Sitzungen | **persistente Sitzungen sind in 26 die Vorgabe** — Sitzungen liegen in der Datenbank und überleben Neustarts; dafür höhere Datenbanklast. Widerrufene Access-Tokens werden ebenfalls persistiert |
**Eine Renovate-Falle in der Tag-Landschaft:** neben `26.8.0` gibt es Rebuild-Tags der Form `26.8.0-0`, `26.8.0-1`. Nach Semver sind das Prereleases von `26.8.0` und sortieren niedriger, nach Docker-Versionierung nicht zwangsläufig — der `customManagers`-Block braucht eine Beschränkung, und sie ist zu **belegen**, nicht anzunehmen. Gleiche Gattung wie die Commit-SHA-Tags bei `garage` (#81).
## Die sicherheitsrelevante Stelle
Hostname und Proxy-Header sind bei Keycloak keine Kosmetik. Wörtlich: hinter einem Reverse Proxy muss `proxy-headers` gesetzt sein, und *„Misconfiguration will leave Keycloak exposed to security vulnerabilities"* — Host-Header-Injection, mit der ein Angreifer die vom Server erzeugten URLs manipulieren und *„steal sensitive data like tokens or passwords"* kann.
Folgen für den Chart: `KC_HOSTNAME` wird aus `ingress.domain` **abgeleitet**, nicht freigelassen; `proxy-headers` ist gesetzt und kein optionaler Wert; und der **Management-Port 9000 wird nicht veröffentlicht** — er trägt die Health-Endpunkte und optional die Metriken, beide unauthentifiziert.
## Entscheidung 1: zwei Replicas mit `cache=ispn`
Keycloak ist anders gelagert als die beiden letzten Charts: ein Mailserver darf kurz weg sein, weil SMTP wiederholt, ein Blob-Store hat einen Abnehmer — ein SSO-Server nimmt beim Ausfall **jede** abhängige Anmeldung mit.
Die Discovery kostet inzwischen fast nichts: **`jdbc-ping` ist die Vorgabe**, die Knoten finden sich über die Datenbank, die ohnehin da ist. `cache-stack=kubernetes` mit DNS_PING ist **deprecated** — also kein Headless-Service, keine JGroups-DNS-Verdrahtung, keine zusätzliche Option außer `cache=ispn`.
Der Preis: doppelte Datenbankverbindungen, die Pool-Größen müssen das tragen, zusammen mit der ohnehin höheren Last durch die persistenten Sitzungen.
### Und die Folge, die teuer geworden wäre
**Rolling Updates sind nur innerhalb einer Minor-Linie unterstützt.** Seit 26.6.0 sind Patch-Updates (26.8.0 → 26.8.1) als Zero-Downtime-Rolling-Update unterstützt und standardmäßig aktiv. **Minor-Sprünge (26.8 → 26.9) verlangen das vollständige Herunterfahren** samt Sicherung von Konfiguration und Datenbank. Upstream liefert dafür ein Prüfwerkzeug:
```
kc.sh update-compatibility metadata --file=/pfad/datei.json # mit der ALTEN Fassung
kc.sh update-compatibility check --file=/pfad/datei.json # mit der NEUEN Fassung
```
Erfolg meldet `[OK] Rolling Update is available.`, sonst wird die unverträgliche Eigenschaft benannt; der Exit-Code ist für Automatisierung gedacht.
**Das kollidiert mit unserer Renovate-Konfiguration.** `automerge: true` gilt dort **global, Majors eingeschlossen** (`renovate.json5`, Kopf der Datei), mit `minimumReleaseAge: "3 days"` für Patch, Minor und Digest. Ein Minor-Sprung von Keycloak würde also nach drei Tagen von allein mergen, den Chart publizieren und bei zwei Replicas als Rolling Update ausgerollt werden — genau das Verfahren, das Upstream für Minor-Sprünge ausschließt. Niemand würde es merken, bis sich zwei Fassungen in einem Infinispan-Cluster begegnen.
Deshalb gehört in `renovate.json5` eine `packageRules`-Regel für dieses Abbild: **`automerge: false` für `minor` und `major`**, Patch-Updates behalten den Automerge — genau die Trennung, die Upstreams Unterstützungsmatrix zieht. Das Verfahren für Minor-Sprünge (`scaleDown: true`, Sicherung, Bump, wieder hoch) gehört in `NOTES.txt` und auf die Wiki-Seite; der dafür nötige Schalter existiert in diesem Repo schon als chart-globales `scaleDown`.
Da hiermit eine Renovate-Regel dazukommt, ist die Wiki-Seite `Renovate` im selben Durchgang zu ergänzen (CLAUDE.md, Tabelle „What obliges a wiki edit").
## Entscheidung 2: Admin-Konsole auf eigenem Hostnamen
Über Keycloaks eigenen Mechanismus `hostname-admin`, damit der Server seine Admin-URLs für diesen Host erzeugt. Darauf lässt sich eine IP-Beschränkung oder eine interne Ingress-Klasse legen, ohne die öffentliche Anmeldung anzutasten.
Bewusst **nicht** über Pfadregeln am Ingress: wer nur `/admin` sperrt, bricht leicht die Weiterleitungen, weil die Konsole Ressourcen auch unter anderen Pfaden lädt — und der Server generiert weiter URLs für den öffentlichen Host.
Umsetzung: zwei Ingress-Objekte und zwei Hostnamen nach unserer Konvention (`ingress.domain` und `ingress.clusterIssuer` required ohne Vorgabe, `className` mit Vorgabe `nginx`, TLS-Secret `tls-<release>-keycloak-ingress` beziehungsweise `…-admin-ingress`). Der Admin-Ingress ist abschaltbar, damit die Konsole auch rein clusterintern betrieben werden kann.
## Was ohne Entscheidung festliegt
**Externe Postgres**, Zugang aus einem `OnePasswordItem`, Geheimnisse per `secretKeyRef` als Umgebungsvariablen — wie bei `garage` entschieden, und wie `paperless-ngx` und `stalwart` es halten. **Zu prüfen:** ob Keycloak eine `_FILE`-Form für `db-password` und `bootstrap-admin-password` kennt. Kennt es sie, wird sie genommen, denn sie hält das Geheimnis aus der Prozessumgebung heraus.
**Kein eigenes Abbild.** Die Doku empfiehlt `kc.sh build` und dann `start --optimized`; ohne Build *„significantly increases startup time"*, weil `db` als Build-Option beim Start eine Augmentierung auslöst. Ein eigenes Abbild zu bauen heißt aber, dass dieses Repository Abbilder veröffentlicht — dieselbe Grenze wie bei `garage`. Also Standardabbild, **großzügiges Startup-Budget**, und die echte Startzeit ist ein Messpunkt.
**Deployment, keine StatefulSet.** Der Dienst ist zustandslos, es gibt keine PVC und keine stabile Identität zu wahren. Unsere Konvention verlangt die StatefulSet für *stateful* Anwendungen; hier wäre sie Zierde.
**Bootstrap-Admin** über `KC_BOOTSTRAP_ADMIN_USERNAME`/`KC_BOOTSTRAP_ADMIN_PASSWORD` aus dem Tresor. Zwei Eigenheiten für `NOTES.txt`: die Variablen heißen **seit 26** so (vorher `KEYCLOAK_ADMIN`/`KEYCLOAK_ADMIN_PASSWORD`), und sie erzeugen einen **temporären** Admin, anders als die alten, die einen regulären anlegten — es ist also ein Pflichtschritt, danach einen dauerhaften Administrator anzulegen. Bleiben die Variablen gesetzt, nachdem der Admin existiert, erscheint bei jedem Start eine Zeile „initial administrator could not be created"; harmlos, aber es gehört erklärt, sonst sucht jemand einen Fehler.
**Vermutlich keine ConfigMap.** Keycloak wird vollständig über Optionen und Umgebungsvariablen konfiguriert. Entsteht keine ConfigMap, greift die `checksum/config`-Regel (#82) nicht — und **das gehört ausdrücklich in `values.yaml` geschrieben**, sonst sieht es aus wie eine Auslassung.
**PodDisruptionBudget mit `minAvailable: 1`.** Mit zwei Replicas ist das das Stück, das verhindert, dass ein Node-Drain beide gleichzeitig nimmt — sonst kauft die zweite Replica nichts gegen den häufigsten Ausfallgrund. Das ist ein **neuer Objekttyp für dieses Repository**: wird er aufgenommen, gehört die Konvention nach README **und** `Chart-Style-Guide`.
## Umfang
Gebaut aus `template-chart/` nach CLAUDE.md und dem Stilleitfaden: Deployment, zwei Services (HTTP 8080, Management 9000 — letzterer nicht veröffentlicht), zwei Ingresses, `OnePasswordItem` für Datenbank und Bootstrap-Admin, PDB, Probes auf allen drei nativen Endpunkten, berechnete Limits über den `limitFactor`-Helfer, `replicas` je Workload plus `scaleDown`, strenges `values.schema.json` mit den null-sicheren Fallback-Helfern, validiert **gegen die echten Bundle-Werte**.
CI: `publish-keycloak.yml` aus einer bestehenden Hülle **kopiert**; `keycloak` in die `chart-name`-Liste von `change-app-version.yml`; Fixtures unter `ci/keycloak/` (Wegwerf-Postgres, Stub-CRD, Dummy-Geheimnisse) — ein Ausschluss wäre hier schwach zu begründen, weil der Dienst ohne Volume und mit einer Datenbank gut testbar ist; `customManagers`-Block **plus** die Automerge-Regel oben.
Wiki im selben Durchgang: neue Seite `keycloak` (Werte-Tabelle, Tresor-Felder, Erstinstallation mit dem dauerhaften Administrator als Pflichtschritt, Traps-Abschnitt), Zeile in `Charts`, Eintrag in `_Sidebar.md`, Ergänzung auf `Renovate` wegen der neuen Regel, `Chart-Style-Guide` **nur** falls das PDB als Konvention aufgenommen wird, und `Lessons-Learned` für die allgemeine Form „ein Upgrade-Verfahren, das der Automerge nicht kennt" — das wiederholt sich bei jeder Anwendung, deren Cluster-Fassungen nicht gemischt werden dürfen.
## Offen, am laufenden Dienst zu messen
1. **Wie lange braucht der Start** mit `db` zur Laufzeit gesetzt? Bestimmt das Startup-Budget, und ein zu knappes Budget ergibt einen CrashLoop, der nach einem Anwendungsfehler aussieht.
2. **Hält `readOnlyRootFilesystem: true`**, und welche Pfade brauchen ein emptyDir (erwartet `/opt/keycloak/data` und `/tmp`)?
3. **Gibt es eine `_FILE`-Form** für `db-password` und `bootstrap-admin-password`?
4. **Findet `jdbc-ping` die zweite Replica** allein aus `cache=ispn` und der Datenbankkonfiguration, ohne weitere Optionen?
5. **Liefert `hostname-admin` auf einem zweiten Hostnamen** die Konsole sauber aus, samt ihrer Ressourcen?
Zwei Befunde am Plan, die Entscheidungen dazu, und was ohne Cluster schon belegt ist
Die Umsetzung war angehalten: zwei Punkte des freigegebenen Plans ändern die Gestalt des Charts, nicht nur einen Wert. Dazu war git.jk-dev.de und coder.jk-dev.de rund eine Stunde nicht erreichbar (TCP offen, TLS unexpected eof, tea mit EOF; quay.io als Gegenprobe normal) — kein Kommentar, kein Push, und das Signieren hängt am Coder-Host, also auch keine signierten Commits. Inzwischen antworten beide wieder.
Befund 1: hostname-admin ist keine Zugriffsgrenze — der Plan hatte es falsch
Entscheidung 2 des Plans begründete den eigenen Admin-Hostnamen damit, dass sich darauf eine Beschränkung legen lasse, „ohne die öffentliche Anmeldung anzutasten", und verwarf Pfadregeln am Ingress ausdrücklich. Die Dokumentation sagt das Gegenteil:
Using the hostname-admin option does not prevent accessing the Administration REST API endpoints via the frontend URL specified by the hostname option. If you want to restrict access to the Administration REST API, you need to do it on the reverse proxy level.
hostname-admin steuert allein, welche URLs der Server erzeugt. /admin/realms/… bleibt über den öffentlichen Host erreichbar. Der Plan übernahm damit den Mechanismus, der nicht beschränkt, und verwarf den einzigen, der es tut.
Entschieden: zweiter Hostname, ohne Grenze.hostname-admin wird gesetzt, damit die Admin-URLs stimmen, und ausdrücklich nicht als Sicherheitsmaßnahme ausgegeben. Das gehört in values.yaml, NOTES.txt und auf die Wiki-Seite in genau dieser Form: dieser Wert erzeugt URLs, er beschränkt nichts; die Administrations-REST-API bleibt über den öffentlichen Host erreichbar, und wer sie beschränken will, tut es am Reverse Proxy. Ein deny auf /admin am öffentlichen Ingress wird nicht eingebaut.
Befund 2: Standardabbild + /health-Probes + readOnlyRootFilesystem: true startet nicht
Vier Messungen, die erst zusammen den Widerspruch ergeben:
db, health-enabled und metrics-enabled sind Build-Optionen — am all-config-HTML von 26.8.0 geprüft, alle drei tragen das Build-Symbol. cache trägt es nicht, cache=ispn kostet also keine Augmentierung.
Das Abbild ist mit den Vorgaben augmentiert (db=dev-file, health-enabled=false): im Layer liegen generated-bytecode.jar, transformed-bytecode.jar, quarkus-application.dat, build-system.properties. USER 1000, ENTRYPOINT ["/opt/keycloak/bin/kc.sh"]ohne CMD — der Chart liefert start selbst als args.
Eine geänderte Build-Option löst eine Re-Augmentierung aus, die in diese vier Dateien schreibt, also nach /opt/keycloak/lib/quarkus — innerhalb des Wurzeldateisystems, nicht unter /opt/keycloak/data. kc.sh selbst: „only exit code 10 means that implicit reaugmentation occurred and a relaunch is needed".
Die Container-Doku nennt das Ergebnis: der unoptimierte Start „detects and applies the build options first" und „significantly increases startup time and creates an image that is mutable".
Ein veränderliches Abbild ist genau das, was readOnlyRootFilesystem: true verbietet. Und „dann setze db nicht" hilft nicht: die Probes allein erzwingen die Augmentierung, weil health-enabled eine Build-Option ist.
Entschieden: Init-Container aus demselben Abbild. Er führt kc.sh build in ein emptyDir, das der Hauptcontainer unter /opt/keycloak/lib/quarkus einhängt; der Hauptcontainer startet dann mit start --optimized. Kein zweites Abbild — die Grenze „dieses Repository veröffentlicht Charts, keine Abbilder" bleibt gewahrt —, die Härtung bleibt vollständig, und das Startup-Budget des Hauptcontainers wird klein und ehrlich. Bedingung: die Build-Optionen beider Container müssen aus einer Quelle gerendert werden, sonst re-augmentiert der Hauptcontainer auf einem schreibgeschützten Pfad.
Verworfen: readOnlyRootFilesystem: false als dokumentierte Abweichung (bei einem Identitätsdienst der unangenehmste Weg) und ein emptyDir ohne Init-Container (verdeckt quarkus-application.dat, die vor jedem Keycloak-Code geladen wird).
Entscheidung zum Renovate-Automerge: keine Sonderregel
Der Plan verlangte, den Automerge für minor und major abzuschalten, weil Keycloak Rolling Updates nur innerhalb einer Minor-Linie unterstützt. Der Betreuer hat anders entschieden: der globale automerge: true gilt auch hier, ohne packageRules-Eintrag.
Das ist eine abgewogene Annahme des Risikos, keine Übersehung, und die Folge gehört benannt: ein Sprung von 26.8 auf 26.9 mergt nach drei Tagen von allein, publiziert den Chart und wird bei zwei Replicas rollend ausgerollt — ein Verfahren, das Upstream für Minor-Sprünge ausschließt (dort: herunterfahren, Konfiguration und Datenbank sichern, dann hochziehen). Auffallen wird es erst, wenn sich zwei Fassungen in einem Infinispan-Cluster begegnen.
Daraus folgt für die Dokumentation, und zwar prominent statt als Fußnote: NOTES.txt und die Wiki-Seite nennen das Verfahren für Minor-Sprünge (scaleDown: true, sichern, Bump, wieder hoch) und dass der Automerge es nicht kennt. kc.sh update-compatibility metadata --file=… mit der alten Fassung, dann check --file=… mit der neuen; Exit-Codes 0 möglich, 1 Fehler, 2 ungültige Option, 3/4 nicht möglich.
Ungelöst bleibt ein Widerspruch in der Doku selbst: die 26.8.0-Seite sagt „a rolling update is possible when the Keycloak version is the same" und führt als eigenes Beispiel ein Patch-Paar als unverträglich (26.2.0 -> 26.2.1), während die Release-Notiz zu 26.6 Patch-Rolling-Updates als unterstützt und standardmäßig aktiv nennt. Entweder ist das Beispiel aus 26.2 stehen geblieben, oder die Seite ist nicht nachgezogen. Nicht geraten — so festgehalten.
Drei Korrekturen am Plan
KC_HOSTNAME muss eine volle URL sein, sobald hostname-admin gesetzt ist: „If hostname-admin is configured, hostname must be set to a URL (not just hostname)". Also https://<domain>, nicht der nackte Host.
Das Zitat im Plan war stärker als die Quelle. Dort steht „Misconfiguration might leave the server exposed to security vulnerabilities", nicht „will leave Keycloak exposed". Inhaltlich unverändert — proxy-headers gesetzt, hostname-strict auf Vorgabe true —, aber ein Zitat im Chart muss das Zitat sein.
Neue Falle: geteilte Datenbank = verschmolzener Cluster. „clusters that share the same database but should remain separate will merge unless they have distinct cluster names. The default cluster name is ISPN." cache-embedded-cluster-name kommt als Wert mit Vorgabe null und die Falle wird dokumentiert; gesetzt wird er nicht, weil dieselbe Seite beim Setzen unterschiedlicher Namen vor ausbleibender Cache-Invalidierung warnt und die Lage nicht eindeutig ist.
Ohne Cluster schon geschlossen
Eine _FILE-Form existiert nicht. Kein KC_DB_PASSWORD_FILE, kein KC_BOOTSTRAP_ADMIN_PASSWORD_FILE, kein generisches KC_<OPTION>_FILE. Die einzigen KC_*_FILE-Variablen sind Pfadangaben (Zertifikate, Keystores, Truststores, Log, Export). Also secretKeyRef-Umgebungsvariablen wie bei garage, paperless-ngx und stalwart, mit dem Preis am Wert benannt.
Eine Renovate-Versionsbeschränkung ist nicht nötig — der Gegenfall zu garage, und gemessen statt angenommen. Renovate 44.132.5, --platform=local --dry-run=lookup gegen die echte Registry: 26.8.0 → keine Updates; 26.7.0 → genau eines (26.8.0, minor); 26.7.4 → genau zwei (26.7.5 als patch, 26.8.0 als minor). In keinem Lauf erscheint ein -N-Rebuild-Tag, 26.8, latest oder nightly: Docker-Versionierung trennt am ersten Bindestrich und bietet nur Kandidaten mit demselben Suffix, und unser Wert hat keines. Ein versioningTemplate wäre eine Zeile, die nichts tut.
quay.io fehlt in ci/trivy/data/ksv0125.yaml (dort nur cr.jk-dev.de, ghcr.io, docker.io). Keycloak wäre der erste quay.io-Chart und bekäme sonst einen Befund, der keiner ist — gehört in denselben PR.
Speicher: die Container-Doku nennt „at least 750 MB" und „recommended memory limit is 2 GB", und das Abbild rechnet den Heap relativ (-XX:MaxRAMPercentage=70). Mit limitFactor: 3 ergibt ein Request von 700Mi genau 2100Mi.
Wiki-Zähler geprüft, nicht übernommen:Charts.md sagt „29 directories: 28 installable" und hat 29 Tabellenzeilen — inklusive convertx, dessen Seite steht, während convertx/ auf origin/main nicht existiert (PR noch offen). Der Zähler folgt also den Wiki-Zeilen, nicht main. Mit keycloak werden es 30 Zeilen / 29 installierbar, und der Wiki-Commit benennt, dass convertx noch offen ist.
Unverändert gültig
Zwei Replicas mit cache=ispn und jdbc-ping-Discovery über die Datenbank; Deployment statt StatefulSet (zustandslos, keine PVC); Management-Port 9000 nicht veröffentlicht; PodDisruptionBudget mit minAvailable: 1 als erster im Repo und damit Konvention für README und Chart-Style-Guide; Bootstrap-Admin über KC_BOOTSTRAP_ADMIN_* mit dem dauerhaften Administrator als Pflichtschritt; Install-Test mit zwei Replicas und Wegwerf-Postgres.
# Zwei Befunde am Plan, die Entscheidungen dazu, und was ohne Cluster schon belegt ist
Die Umsetzung war angehalten: zwei Punkte des freigegebenen Plans ändern die **Gestalt** des Charts, nicht nur einen Wert. Dazu war `git.jk-dev.de` und `coder.jk-dev.de` rund eine Stunde nicht erreichbar (TCP offen, TLS `unexpected eof`, `tea` mit `EOF`; `quay.io` als Gegenprobe normal) — kein Kommentar, kein Push, und das Signieren hängt am Coder-Host, also auch keine signierten Commits. Inzwischen antworten beide wieder.
## Befund 1: `hostname-admin` ist keine Zugriffsgrenze — der Plan hatte es falsch
Entscheidung 2 des Plans begründete den eigenen Admin-Hostnamen damit, dass sich darauf eine Beschränkung legen lasse, „ohne die öffentliche Anmeldung anzutasten", und verwarf Pfadregeln am Ingress ausdrücklich. Die Dokumentation sagt das Gegenteil:
> Using the `hostname-admin` option does not prevent accessing the Administration REST API endpoints via the frontend URL specified by the `hostname` option. If you want to restrict access to the Administration REST API, **you need to do it on the reverse proxy level**.
`hostname-admin` steuert allein, **welche URLs der Server erzeugt**. `/admin/realms/…` bleibt über den öffentlichen Host erreichbar. Der Plan übernahm damit den Mechanismus, der nicht beschränkt, und verwarf den einzigen, der es tut.
**Entschieden: zweiter Hostname, ohne Grenze.** `hostname-admin` wird gesetzt, damit die Admin-URLs stimmen, und **ausdrücklich nicht** als Sicherheitsmaßnahme ausgegeben. Das gehört in `values.yaml`, `NOTES.txt` und auf die Wiki-Seite in genau dieser Form: *dieser Wert erzeugt URLs, er beschränkt nichts; die Administrations-REST-API bleibt über den öffentlichen Host erreichbar, und wer sie beschränken will, tut es am Reverse Proxy.* Ein `deny` auf `/admin` am öffentlichen Ingress wird **nicht** eingebaut.
## Befund 2: Standardabbild + `/health`-Probes + `readOnlyRootFilesystem: true` startet nicht
Vier Messungen, die erst zusammen den Widerspruch ergeben:
1. **`db`, `health-enabled` und `metrics-enabled` sind Build-Optionen** — am `all-config`-HTML von 26.8.0 geprüft, alle drei tragen das Build-Symbol. **`cache` trägt es nicht**, `cache=ispn` kostet also keine Augmentierung.
2. Das Abbild ist mit den Vorgaben augmentiert (`db=dev-file`, `health-enabled=false`): im Layer liegen `generated-bytecode.jar`, `transformed-bytecode.jar`, `quarkus-application.dat`, `build-system.properties`. `USER 1000`, `ENTRYPOINT ["/opt/keycloak/bin/kc.sh"]` **ohne `CMD`** — der Chart liefert `start` selbst als `args`.
3. Eine geänderte Build-Option löst eine Re-Augmentierung aus, die in diese vier Dateien schreibt, also nach **`/opt/keycloak/lib/quarkus`** — innerhalb des Wurzeldateisystems, nicht unter `/opt/keycloak/data`. `kc.sh` selbst: „only exit code 10 means that implicit reaugmentation occurred and a relaunch is needed".
4. Die Container-Doku nennt das Ergebnis: der unoptimierte Start „detects and applies the build options first" und „significantly increases startup time and **creates an image that is mutable**".
Ein veränderliches Abbild ist genau das, was `readOnlyRootFilesystem: true` verbietet. Und „dann setze `db` nicht" hilft nicht: **die Probes allein erzwingen die Augmentierung**, weil `health-enabled` eine Build-Option ist.
**Entschieden: Init-Container aus demselben Abbild.** Er führt `kc.sh build` in ein emptyDir, das der Hauptcontainer unter `/opt/keycloak/lib/quarkus` einhängt; der Hauptcontainer startet dann mit `start --optimized`. Kein zweites Abbild — die Grenze „dieses Repository veröffentlicht Charts, keine Abbilder" bleibt gewahrt —, die Härtung bleibt vollständig, und das Startup-Budget des Hauptcontainers wird klein und ehrlich. **Bedingung:** die Build-Optionen beider Container müssen aus **einer** Quelle gerendert werden, sonst re-augmentiert der Hauptcontainer auf einem schreibgeschützten Pfad.
Verworfen: `readOnlyRootFilesystem: false` als dokumentierte Abweichung (bei einem Identitätsdienst der unangenehmste Weg) und ein emptyDir ohne Init-Container (verdeckt `quarkus-application.dat`, die vor jedem Keycloak-Code geladen wird).
## Entscheidung zum Renovate-Automerge: keine Sonderregel
Der Plan verlangte, den Automerge für `minor` und `major` abzuschalten, weil Keycloak Rolling Updates nur innerhalb einer Minor-Linie unterstützt. **Der Betreuer hat anders entschieden: der globale `automerge: true` gilt auch hier, ohne `packageRules`-Eintrag.**
Das ist eine abgewogene Annahme des Risikos, keine Übersehung, und die Folge gehört benannt: ein Sprung von 26.8 auf 26.9 mergt nach drei Tagen von allein, publiziert den Chart und wird bei zwei Replicas rollend ausgerollt — ein Verfahren, das Upstream für Minor-Sprünge ausschließt (dort: herunterfahren, Konfiguration und Datenbank sichern, dann hochziehen). Auffallen wird es erst, wenn sich zwei Fassungen in einem Infinispan-Cluster begegnen.
Daraus folgt für die Dokumentation, und zwar prominent statt als Fußnote: `NOTES.txt` und die Wiki-Seite nennen das Verfahren für Minor-Sprünge (`scaleDown: true`, sichern, Bump, wieder hoch) **und** dass der Automerge es nicht kennt. `kc.sh update-compatibility metadata --file=…` mit der alten Fassung, dann `check --file=…` mit der neuen; Exit-Codes `0` möglich, `1` Fehler, `2` ungültige Option, `3`/`4` nicht möglich.
Ungelöst bleibt ein Widerspruch in der Doku selbst: die 26.8.0-Seite sagt „a rolling update is possible when **the Keycloak version is the same**" und führt als eigenes Beispiel ein **Patch**-Paar als unverträglich (`26.2.0 -> 26.2.1`), während die Release-Notiz zu 26.6 Patch-Rolling-Updates als unterstützt und standardmäßig aktiv nennt. Entweder ist das Beispiel aus 26.2 stehen geblieben, oder die Seite ist nicht nachgezogen. Nicht geraten — so festgehalten.
## Drei Korrekturen am Plan
- **`KC_HOSTNAME` muss eine volle URL sein**, sobald `hostname-admin` gesetzt ist: „If `hostname-admin` is configured, `hostname` must be set to a URL (not just hostname)". Also `https://<domain>`, nicht der nackte Host.
- **Das Zitat im Plan war stärker als die Quelle.** Dort steht „Misconfiguration **might** leave the server exposed to security vulnerabilities", nicht „will leave Keycloak exposed". Inhaltlich unverändert — `proxy-headers` gesetzt, `hostname-strict` auf Vorgabe `true` —, aber ein Zitat im Chart muss das Zitat sein.
- **Neue Falle: geteilte Datenbank = verschmolzener Cluster.** „clusters that share the same database but should remain separate **will merge** unless they have distinct cluster names. The default cluster name is `ISPN`." `cache-embedded-cluster-name` kommt als Wert mit Vorgabe `null` und die Falle wird dokumentiert; gesetzt wird er **nicht**, weil dieselbe Seite beim Setzen unterschiedlicher Namen vor ausbleibender Cache-Invalidierung warnt und die Lage nicht eindeutig ist.
## Ohne Cluster schon geschlossen
- **Eine `_FILE`-Form existiert nicht.** Kein `KC_DB_PASSWORD_FILE`, kein `KC_BOOTSTRAP_ADMIN_PASSWORD_FILE`, kein generisches `KC_<OPTION>_FILE`. Die einzigen `KC_*_FILE`-Variablen sind **Pfadangaben** (Zertifikate, Keystores, Truststores, Log, Export). Also `secretKeyRef`-Umgebungsvariablen wie bei `garage`, `paperless-ngx` und `stalwart`, mit dem Preis am Wert benannt.
- **Eine Renovate-Versionsbeschränkung ist nicht nötig** — der Gegenfall zu `garage`, und gemessen statt angenommen. Renovate 44.132.5, `--platform=local --dry-run=lookup` gegen die echte Registry: `26.8.0` → keine Updates; `26.7.0` → genau eines (`26.8.0`, `minor`); `26.7.4` → genau zwei (`26.7.5` als `patch`, `26.8.0` als `minor`). In keinem Lauf erscheint ein `-N`-Rebuild-Tag, `26.8`, `latest` oder `nightly`: Docker-Versionierung trennt am ersten Bindestrich und bietet nur Kandidaten mit demselben Suffix, und unser Wert hat keines. Ein `versioningTemplate` wäre eine Zeile, die nichts tut.
- **`quay.io` fehlt in `ci/trivy/data/ksv0125.yaml`** (dort nur `cr.jk-dev.de`, `ghcr.io`, `docker.io`). Keycloak wäre der erste `quay.io`-Chart und bekäme sonst einen Befund, der keiner ist — gehört in denselben PR.
- **Speicher:** die Container-Doku nennt „at least 750 MB" und „recommended memory limit is 2 GB", und das Abbild rechnet den Heap relativ (`-XX:MaxRAMPercentage=70`). Mit `limitFactor: 3` ergibt ein Request von `700Mi` genau `2100Mi`.
- **Wiki-Zähler geprüft, nicht übernommen:** `Charts.md` sagt „29 directories: 28 installable" und hat 29 Tabellenzeilen — inklusive `convertx`, dessen Seite steht, während `convertx/` auf `origin/main` nicht existiert (PR noch offen). Der Zähler folgt also den Wiki-Zeilen, nicht `main`. Mit `keycloak` werden es **30 Zeilen / 29 installierbar**, und der Wiki-Commit benennt, dass `convertx` noch offen ist.
## Unverändert gültig
Zwei Replicas mit `cache=ispn` und `jdbc-ping`-Discovery über die Datenbank; Deployment statt StatefulSet (zustandslos, keine PVC); Management-Port 9000 nicht veröffentlicht; PodDisruptionBudget mit `minAvailable: 1` als erster im Repo und damit Konvention für README und `Chart-Style-Guide`; Bootstrap-Admin über `KC_BOOTSTRAP_ADMIN_*` mit dem dauerhaften Administrator als Pflichtschritt; Install-Test mit **zwei** Replicas und Wegwerf-Postgres.
keycloak/ liegt auf main, Version 1.0.0+up26.8.0, veröffentlicht. PR #87 gemergt am 2026-10-04 um 20:00 mit Part of #83 — deshalb ist dieses Issue bewusst offen geblieben, nicht aus Versehen.
In der CI belegt, mit Job-Ausgabe im PR: beide Replicas erreichen Ready in 71,8 s ab helm install, dreimal konsistent, 0 Restarts; readOnlyRootFilesystem: true hält in allen drei Containern (zwei Init-Container für die Quarkus-Augmentierung, keine Härtungsabweichung, keine Trivy-Unterdrückung); eine _FILE-Form für Geheimnisse existiert bei Keycloak nicht, also secretKeyRef.
Offen bleiben zwei Prüfungen, die nur am echten Cluster gehen:
1. Hat jdbc-ping die zweite Replica wirklich gefunden? Zwei Ready-Pods sind kein Beweis: Readiness meldet die Gesundheit eines Knotens, zwei Knoten, die sich nie fanden, sind beide Ready und beide allein. NOTES.txt druckt den Befehl aus:
Gesucht ist eine View mit zwei Mitgliedern. Ein Mitglied je Pod heißt, der Partner wurde nicht gefunden — dann prüfen, ob beide Pods sich auf dem JGroups-Port erreichen und ob sie wirklich eine Datenbank teilen.
2. Liefert hostname-admin die Konsole samt ihrer Ressourcen auf dem zweiten Hostnamen aus? Braucht einen Ingress-Controller und einen Browser, beides gibt es in der CI nicht.
Nicht geprüft und grundsätzlich nicht CI-prüfbar: irgendein Upgrade. Der Install-Test installiert eine Fassung; die Rolling-Update-Beschränkung ist eine Eigenschaft von zwei. Genau dort greift die Entscheidung, den Renovate-Automerge nicht einzuschränken — das Verfahren für Minor-Sprünge steht in NOTES.txt und auf der Wiki-Seite.
## Stand nach dem Merge (2026-10-04)
`keycloak/` liegt auf `main`, Version **1.0.0+up26.8.0**, veröffentlicht. PR #87 gemergt am 2026-10-04 um 20:00 mit `Part of #83` — deshalb ist dieses Issue bewusst **offen** geblieben, nicht aus Versehen.
**In der CI belegt**, mit Job-Ausgabe im PR: beide Replicas erreichen `Ready` in **71,8 s** ab `helm install`, dreimal konsistent, 0 Restarts; `readOnlyRootFilesystem: true` hält in **allen drei** Containern (zwei Init-Container für die Quarkus-Augmentierung, keine Härtungsabweichung, keine Trivy-Unterdrückung); eine `_FILE`-Form für Geheimnisse existiert bei Keycloak nicht, also `secretKeyRef`.
**Offen bleiben zwei Prüfungen, die nur am echten Cluster gehen:**
**1. Hat `jdbc-ping` die zweite Replica wirklich gefunden?** Zwei `Ready`-Pods sind **kein** Beweis: Readiness meldet die Gesundheit *eines* Knotens, zwei Knoten, die sich nie fanden, sind beide `Ready` und beide allein. `NOTES.txt` druckt den Befehl aus:
```
kubectl -n <ns> logs deployment/<release>-keycloak-deployment \
| grep -i "ISPN000094\|view accepted\|members"
```
Gesucht ist eine View mit **zwei** Mitgliedern. Ein Mitglied je Pod heißt, der Partner wurde nicht gefunden — dann prüfen, ob beide Pods sich auf dem JGroups-Port erreichen und ob sie wirklich **eine** Datenbank teilen.
**2. Liefert `hostname-admin` die Konsole samt ihrer Ressourcen auf dem zweiten Hostnamen aus?** Braucht einen Ingress-Controller und einen Browser, beides gibt es in der CI nicht.
**Nicht geprüft und grundsätzlich nicht CI-prüfbar:** irgendein **Upgrade**. Der Install-Test installiert *eine* Fassung; die Rolling-Update-Beschränkung ist eine Eigenschaft von zwei. Genau dort greift die Entscheidung, den Renovate-Automerge nicht einzuschränken — das Verfahren für Minor-Sprünge steht in `NOTES.txt` und auf der Wiki-Seite.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Das Issue trug nur einen Titel. Dies ist die Bestandsaufnahme und der freigegebene Plan — die beiden offenen Entscheidungen sind beantwortet, der Rest ist aus der Dokumentation belegt.
Verifizierte Fakten
Gegen die Registry und die Upstream-Dokumentation geprüft, nicht aus der Erinnerung:
quay.io/keycloak/keycloak26.8,latest1.0.0+up26.8.0/health/started,/health/live,/health/ready— alle drei nativ, auf dem Management-Port 9000, freigeschaltet überhealth-enabled=truedb,db-url-host,db-url-database,db-username,db-password,db-pool-*Eine Renovate-Falle in der Tag-Landschaft: neben
26.8.0gibt es Rebuild-Tags der Form26.8.0-0,26.8.0-1. Nach Semver sind das Prereleases von26.8.0und sortieren niedriger, nach Docker-Versionierung nicht zwangsläufig — dercustomManagers-Block braucht eine Beschränkung, und sie ist zu belegen, nicht anzunehmen. Gleiche Gattung wie die Commit-SHA-Tags beigarage(#81).Die sicherheitsrelevante Stelle
Hostname und Proxy-Header sind bei Keycloak keine Kosmetik. Wörtlich: hinter einem Reverse Proxy muss
proxy-headersgesetzt sein, und „Misconfiguration will leave Keycloak exposed to security vulnerabilities" — Host-Header-Injection, mit der ein Angreifer die vom Server erzeugten URLs manipulieren und „steal sensitive data like tokens or passwords" kann.Folgen für den Chart:
KC_HOSTNAMEwird ausingress.domainabgeleitet, nicht freigelassen;proxy-headersist gesetzt und kein optionaler Wert; und der Management-Port 9000 wird nicht veröffentlicht — er trägt die Health-Endpunkte und optional die Metriken, beide unauthentifiziert.Entscheidung 1: zwei Replicas mit
cache=ispnKeycloak ist anders gelagert als die beiden letzten Charts: ein Mailserver darf kurz weg sein, weil SMTP wiederholt, ein Blob-Store hat einen Abnehmer — ein SSO-Server nimmt beim Ausfall jede abhängige Anmeldung mit.
Die Discovery kostet inzwischen fast nichts:
jdbc-pingist die Vorgabe, die Knoten finden sich über die Datenbank, die ohnehin da ist.cache-stack=kubernetesmit DNS_PING ist deprecated — also kein Headless-Service, keine JGroups-DNS-Verdrahtung, keine zusätzliche Option außercache=ispn.Der Preis: doppelte Datenbankverbindungen, die Pool-Größen müssen das tragen, zusammen mit der ohnehin höheren Last durch die persistenten Sitzungen.
Und die Folge, die teuer geworden wäre
Rolling Updates sind nur innerhalb einer Minor-Linie unterstützt. Seit 26.6.0 sind Patch-Updates (26.8.0 → 26.8.1) als Zero-Downtime-Rolling-Update unterstützt und standardmäßig aktiv. Minor-Sprünge (26.8 → 26.9) verlangen das vollständige Herunterfahren samt Sicherung von Konfiguration und Datenbank. Upstream liefert dafür ein Prüfwerkzeug:
Erfolg meldet
[OK] Rolling Update is available., sonst wird die unverträgliche Eigenschaft benannt; der Exit-Code ist für Automatisierung gedacht.Das kollidiert mit unserer Renovate-Konfiguration.
automerge: truegilt dort global, Majors eingeschlossen (renovate.json5, Kopf der Datei), mitminimumReleaseAge: "3 days"für Patch, Minor und Digest. Ein Minor-Sprung von Keycloak würde also nach drei Tagen von allein mergen, den Chart publizieren und bei zwei Replicas als Rolling Update ausgerollt werden — genau das Verfahren, das Upstream für Minor-Sprünge ausschließt. Niemand würde es merken, bis sich zwei Fassungen in einem Infinispan-Cluster begegnen.Deshalb gehört in
renovate.json5einepackageRules-Regel für dieses Abbild:automerge: falsefürminorundmajor, Patch-Updates behalten den Automerge — genau die Trennung, die Upstreams Unterstützungsmatrix zieht. Das Verfahren für Minor-Sprünge (scaleDown: true, Sicherung, Bump, wieder hoch) gehört inNOTES.txtund auf die Wiki-Seite; der dafür nötige Schalter existiert in diesem Repo schon als chart-globalesscaleDown.Da hiermit eine Renovate-Regel dazukommt, ist die Wiki-Seite
Renovateim selben Durchgang zu ergänzen (CLAUDE.md, Tabelle „What obliges a wiki edit").Entscheidung 2: Admin-Konsole auf eigenem Hostnamen
Über Keycloaks eigenen Mechanismus
hostname-admin, damit der Server seine Admin-URLs für diesen Host erzeugt. Darauf lässt sich eine IP-Beschränkung oder eine interne Ingress-Klasse legen, ohne die öffentliche Anmeldung anzutasten.Bewusst nicht über Pfadregeln am Ingress: wer nur
/adminsperrt, bricht leicht die Weiterleitungen, weil die Konsole Ressourcen auch unter anderen Pfaden lädt — und der Server generiert weiter URLs für den öffentlichen Host.Umsetzung: zwei Ingress-Objekte und zwei Hostnamen nach unserer Konvention (
ingress.domainundingress.clusterIssuerrequired ohne Vorgabe,classNamemit Vorgabenginx, TLS-Secrettls-<release>-keycloak-ingressbeziehungsweise…-admin-ingress). Der Admin-Ingress ist abschaltbar, damit die Konsole auch rein clusterintern betrieben werden kann.Was ohne Entscheidung festliegt
Externe Postgres, Zugang aus einem
OnePasswordItem, Geheimnisse persecretKeyRefals Umgebungsvariablen — wie beigarageentschieden, und wiepaperless-ngxundstalwartes halten. Zu prüfen: ob Keycloak eine_FILE-Form fürdb-passwordundbootstrap-admin-passwordkennt. Kennt es sie, wird sie genommen, denn sie hält das Geheimnis aus der Prozessumgebung heraus.Kein eigenes Abbild. Die Doku empfiehlt
kc.sh buildund dannstart --optimized; ohne Build „significantly increases startup time", weildbals Build-Option beim Start eine Augmentierung auslöst. Ein eigenes Abbild zu bauen heißt aber, dass dieses Repository Abbilder veröffentlicht — dieselbe Grenze wie beigarage. Also Standardabbild, großzügiges Startup-Budget, und die echte Startzeit ist ein Messpunkt.Deployment, keine StatefulSet. Der Dienst ist zustandslos, es gibt keine PVC und keine stabile Identität zu wahren. Unsere Konvention verlangt die StatefulSet für stateful Anwendungen; hier wäre sie Zierde.
Bootstrap-Admin über
KC_BOOTSTRAP_ADMIN_USERNAME/KC_BOOTSTRAP_ADMIN_PASSWORDaus dem Tresor. Zwei Eigenheiten fürNOTES.txt: die Variablen heißen seit 26 so (vorherKEYCLOAK_ADMIN/KEYCLOAK_ADMIN_PASSWORD), und sie erzeugen einen temporären Admin, anders als die alten, die einen regulären anlegten — es ist also ein Pflichtschritt, danach einen dauerhaften Administrator anzulegen. Bleiben die Variablen gesetzt, nachdem der Admin existiert, erscheint bei jedem Start eine Zeile „initial administrator could not be created"; harmlos, aber es gehört erklärt, sonst sucht jemand einen Fehler.Vermutlich keine ConfigMap. Keycloak wird vollständig über Optionen und Umgebungsvariablen konfiguriert. Entsteht keine ConfigMap, greift die
checksum/config-Regel (#82) nicht — und das gehört ausdrücklich invalues.yamlgeschrieben, sonst sieht es aus wie eine Auslassung.PodDisruptionBudget mit
minAvailable: 1. Mit zwei Replicas ist das das Stück, das verhindert, dass ein Node-Drain beide gleichzeitig nimmt — sonst kauft die zweite Replica nichts gegen den häufigsten Ausfallgrund. Das ist ein neuer Objekttyp für dieses Repository: wird er aufgenommen, gehört die Konvention nach README undChart-Style-Guide.Umfang
Gebaut aus
template-chart/nach CLAUDE.md und dem Stilleitfaden: Deployment, zwei Services (HTTP 8080, Management 9000 — letzterer nicht veröffentlicht), zwei Ingresses,OnePasswordItemfür Datenbank und Bootstrap-Admin, PDB, Probes auf allen drei nativen Endpunkten, berechnete Limits über denlimitFactor-Helfer,replicasje Workload plusscaleDown, strengesvalues.schema.jsonmit den null-sicheren Fallback-Helfern, validiert gegen die echten Bundle-Werte.CI:
publish-keycloak.ymlaus einer bestehenden Hülle kopiert;keycloakin diechart-name-Liste vonchange-app-version.yml; Fixtures unterci/keycloak/(Wegwerf-Postgres, Stub-CRD, Dummy-Geheimnisse) — ein Ausschluss wäre hier schwach zu begründen, weil der Dienst ohne Volume und mit einer Datenbank gut testbar ist;customManagers-Block plus die Automerge-Regel oben.Wiki im selben Durchgang: neue Seite
keycloak(Werte-Tabelle, Tresor-Felder, Erstinstallation mit dem dauerhaften Administrator als Pflichtschritt, Traps-Abschnitt), Zeile inCharts, Eintrag in_Sidebar.md, Ergänzung aufRenovatewegen der neuen Regel,Chart-Style-Guidenur falls das PDB als Konvention aufgenommen wird, undLessons-Learnedfür die allgemeine Form „ein Upgrade-Verfahren, das der Automerge nicht kennt" — das wiederholt sich bei jeder Anwendung, deren Cluster-Fassungen nicht gemischt werden dürfen.Offen, am laufenden Dienst zu messen
dbzur Laufzeit gesetzt? Bestimmt das Startup-Budget, und ein zu knappes Budget ergibt einen CrashLoop, der nach einem Anwendungsfehler aussieht.readOnlyRootFilesystem: true, und welche Pfade brauchen ein emptyDir (erwartet/opt/keycloak/dataund/tmp)?_FILE-Form fürdb-passwordundbootstrap-admin-password?jdbc-pingdie zweite Replica allein auscache=ispnund der Datenbankkonfiguration, ohne weitere Optionen?hostname-adminauf einem zweiten Hostnamen die Konsole sauber aus, samt ihrer Ressourcen?Zwei Befunde am Plan, die Entscheidungen dazu, und was ohne Cluster schon belegt ist
Die Umsetzung war angehalten: zwei Punkte des freigegebenen Plans ändern die Gestalt des Charts, nicht nur einen Wert. Dazu war
git.jk-dev.deundcoder.jk-dev.derund eine Stunde nicht erreichbar (TCP offen, TLSunexpected eof,teamitEOF;quay.ioals Gegenprobe normal) — kein Kommentar, kein Push, und das Signieren hängt am Coder-Host, also auch keine signierten Commits. Inzwischen antworten beide wieder.Befund 1:
hostname-administ keine Zugriffsgrenze — der Plan hatte es falschEntscheidung 2 des Plans begründete den eigenen Admin-Hostnamen damit, dass sich darauf eine Beschränkung legen lasse, „ohne die öffentliche Anmeldung anzutasten", und verwarf Pfadregeln am Ingress ausdrücklich. Die Dokumentation sagt das Gegenteil:
hostname-adminsteuert allein, welche URLs der Server erzeugt./admin/realms/…bleibt über den öffentlichen Host erreichbar. Der Plan übernahm damit den Mechanismus, der nicht beschränkt, und verwarf den einzigen, der es tut.Entschieden: zweiter Hostname, ohne Grenze.
hostname-adminwird gesetzt, damit die Admin-URLs stimmen, und ausdrücklich nicht als Sicherheitsmaßnahme ausgegeben. Das gehört invalues.yaml,NOTES.txtund auf die Wiki-Seite in genau dieser Form: dieser Wert erzeugt URLs, er beschränkt nichts; die Administrations-REST-API bleibt über den öffentlichen Host erreichbar, und wer sie beschränken will, tut es am Reverse Proxy. Eindenyauf/adminam öffentlichen Ingress wird nicht eingebaut.Befund 2: Standardabbild +
/health-Probes +readOnlyRootFilesystem: truestartet nichtVier Messungen, die erst zusammen den Widerspruch ergeben:
db,health-enabledundmetrics-enabledsind Build-Optionen — amall-config-HTML von 26.8.0 geprüft, alle drei tragen das Build-Symbol.cacheträgt es nicht,cache=ispnkostet also keine Augmentierung.db=dev-file,health-enabled=false): im Layer liegengenerated-bytecode.jar,transformed-bytecode.jar,quarkus-application.dat,build-system.properties.USER 1000,ENTRYPOINT ["/opt/keycloak/bin/kc.sh"]ohneCMD— der Chart liefertstartselbst alsargs./opt/keycloak/lib/quarkus— innerhalb des Wurzeldateisystems, nicht unter/opt/keycloak/data.kc.shselbst: „only exit code 10 means that implicit reaugmentation occurred and a relaunch is needed".Ein veränderliches Abbild ist genau das, was
readOnlyRootFilesystem: trueverbietet. Und „dann setzedbnicht" hilft nicht: die Probes allein erzwingen die Augmentierung, weilhealth-enabledeine Build-Option ist.Entschieden: Init-Container aus demselben Abbild. Er führt
kc.sh buildin ein emptyDir, das der Hauptcontainer unter/opt/keycloak/lib/quarkuseinhängt; der Hauptcontainer startet dann mitstart --optimized. Kein zweites Abbild — die Grenze „dieses Repository veröffentlicht Charts, keine Abbilder" bleibt gewahrt —, die Härtung bleibt vollständig, und das Startup-Budget des Hauptcontainers wird klein und ehrlich. Bedingung: die Build-Optionen beider Container müssen aus einer Quelle gerendert werden, sonst re-augmentiert der Hauptcontainer auf einem schreibgeschützten Pfad.Verworfen:
readOnlyRootFilesystem: falseals dokumentierte Abweichung (bei einem Identitätsdienst der unangenehmste Weg) und ein emptyDir ohne Init-Container (verdecktquarkus-application.dat, die vor jedem Keycloak-Code geladen wird).Entscheidung zum Renovate-Automerge: keine Sonderregel
Der Plan verlangte, den Automerge für
minorundmajorabzuschalten, weil Keycloak Rolling Updates nur innerhalb einer Minor-Linie unterstützt. Der Betreuer hat anders entschieden: der globaleautomerge: truegilt auch hier, ohnepackageRules-Eintrag.Das ist eine abgewogene Annahme des Risikos, keine Übersehung, und die Folge gehört benannt: ein Sprung von 26.8 auf 26.9 mergt nach drei Tagen von allein, publiziert den Chart und wird bei zwei Replicas rollend ausgerollt — ein Verfahren, das Upstream für Minor-Sprünge ausschließt (dort: herunterfahren, Konfiguration und Datenbank sichern, dann hochziehen). Auffallen wird es erst, wenn sich zwei Fassungen in einem Infinispan-Cluster begegnen.
Daraus folgt für die Dokumentation, und zwar prominent statt als Fußnote:
NOTES.txtund die Wiki-Seite nennen das Verfahren für Minor-Sprünge (scaleDown: true, sichern, Bump, wieder hoch) und dass der Automerge es nicht kennt.kc.sh update-compatibility metadata --file=…mit der alten Fassung, danncheck --file=…mit der neuen; Exit-Codes0möglich,1Fehler,2ungültige Option,3/4nicht möglich.Ungelöst bleibt ein Widerspruch in der Doku selbst: die 26.8.0-Seite sagt „a rolling update is possible when the Keycloak version is the same" und führt als eigenes Beispiel ein Patch-Paar als unverträglich (
26.2.0 -> 26.2.1), während die Release-Notiz zu 26.6 Patch-Rolling-Updates als unterstützt und standardmäßig aktiv nennt. Entweder ist das Beispiel aus 26.2 stehen geblieben, oder die Seite ist nicht nachgezogen. Nicht geraten — so festgehalten.Drei Korrekturen am Plan
KC_HOSTNAMEmuss eine volle URL sein, sobaldhostname-admingesetzt ist: „Ifhostname-adminis configured,hostnamemust be set to a URL (not just hostname)". Alsohttps://<domain>, nicht der nackte Host.proxy-headersgesetzt,hostname-strictauf Vorgabetrue—, aber ein Zitat im Chart muss das Zitat sein.ISPN."cache-embedded-cluster-namekommt als Wert mit Vorgabenullund die Falle wird dokumentiert; gesetzt wird er nicht, weil dieselbe Seite beim Setzen unterschiedlicher Namen vor ausbleibender Cache-Invalidierung warnt und die Lage nicht eindeutig ist.Ohne Cluster schon geschlossen
_FILE-Form existiert nicht. KeinKC_DB_PASSWORD_FILE, keinKC_BOOTSTRAP_ADMIN_PASSWORD_FILE, kein generischesKC_<OPTION>_FILE. Die einzigenKC_*_FILE-Variablen sind Pfadangaben (Zertifikate, Keystores, Truststores, Log, Export). AlsosecretKeyRef-Umgebungsvariablen wie beigarage,paperless-ngxundstalwart, mit dem Preis am Wert benannt.garage, und gemessen statt angenommen. Renovate 44.132.5,--platform=local --dry-run=lookupgegen die echte Registry:26.8.0→ keine Updates;26.7.0→ genau eines (26.8.0,minor);26.7.4→ genau zwei (26.7.5alspatch,26.8.0alsminor). In keinem Lauf erscheint ein-N-Rebuild-Tag,26.8,latestodernightly: Docker-Versionierung trennt am ersten Bindestrich und bietet nur Kandidaten mit demselben Suffix, und unser Wert hat keines. EinversioningTemplatewäre eine Zeile, die nichts tut.quay.iofehlt inci/trivy/data/ksv0125.yaml(dort nurcr.jk-dev.de,ghcr.io,docker.io). Keycloak wäre der erstequay.io-Chart und bekäme sonst einen Befund, der keiner ist — gehört in denselben PR.-XX:MaxRAMPercentage=70). MitlimitFactor: 3ergibt ein Request von700Migenau2100Mi.Charts.mdsagt „29 directories: 28 installable" und hat 29 Tabellenzeilen — inklusiveconvertx, dessen Seite steht, währendconvertx/auforigin/mainnicht existiert (PR noch offen). Der Zähler folgt also den Wiki-Zeilen, nichtmain. Mitkeycloakwerden es 30 Zeilen / 29 installierbar, und der Wiki-Commit benennt, dassconvertxnoch offen ist.Unverändert gültig
Zwei Replicas mit
cache=ispnundjdbc-ping-Discovery über die Datenbank; Deployment statt StatefulSet (zustandslos, keine PVC); Management-Port 9000 nicht veröffentlicht; PodDisruptionBudget mitminAvailable: 1als erster im Repo und damit Konvention für README undChart-Style-Guide; Bootstrap-Admin überKC_BOOTSTRAP_ADMIN_*mit dem dauerhaften Administrator als Pflichtschritt; Install-Test mit zwei Replicas und Wegwerf-Postgres.Stand nach dem Merge (2026-10-04)
keycloak/liegt aufmain, Version 1.0.0+up26.8.0, veröffentlicht. PR #87 gemergt am 2026-10-04 um 20:00 mitPart of #83— deshalb ist dieses Issue bewusst offen geblieben, nicht aus Versehen.In der CI belegt, mit Job-Ausgabe im PR: beide Replicas erreichen
Readyin 71,8 s abhelm install, dreimal konsistent, 0 Restarts;readOnlyRootFilesystem: truehält in allen drei Containern (zwei Init-Container für die Quarkus-Augmentierung, keine Härtungsabweichung, keine Trivy-Unterdrückung); eine_FILE-Form für Geheimnisse existiert bei Keycloak nicht, alsosecretKeyRef.Offen bleiben zwei Prüfungen, die nur am echten Cluster gehen:
1. Hat
jdbc-pingdie zweite Replica wirklich gefunden? ZweiReady-Pods sind kein Beweis: Readiness meldet die Gesundheit eines Knotens, zwei Knoten, die sich nie fanden, sind beideReadyund beide allein.NOTES.txtdruckt den Befehl aus:Gesucht ist eine View mit zwei Mitgliedern. Ein Mitglied je Pod heißt, der Partner wurde nicht gefunden — dann prüfen, ob beide Pods sich auf dem JGroups-Port erreichen und ob sie wirklich eine Datenbank teilen.
2. Liefert
hostname-admindie Konsole samt ihrer Ressourcen auf dem zweiten Hostnamen aus? Braucht einen Ingress-Controller und einen Browser, beides gibt es in der CI nicht.Nicht geprüft und grundsätzlich nicht CI-prüfbar: irgendein Upgrade. Der Install-Test installiert eine Fassung; die Rolling-Update-Beschränkung ist eine Eigenschaft von zwei. Genau dort greift die Entscheidung, den Renovate-Automerge nicht einzuschränken — das Verfahren für Minor-Sprünge steht in
NOTES.txtund auf der Wiki-Seite.