The 26.x Keycloak operator creates its own ServiceMonitor named after the CR (keycloak/keycloak) when metrics are enabled; spec.serviceMonitor.enabled defaults true. That collides with the Flux-owned platform/keycloak/ servicemonitor.yaml: the operator's PATCH 409s, it exhausts retries, the CR stays Ready=False/HasErrors=True, and platform-config (wait: true) stalls every downstream Kustomization. Keep the repo's ServiceMonitor (conformance L0/L2 and the dashboard are written against it; the operator's scrapes the same port 9000 /metrics) and set spec.serviceMonitor.enabled: false. Co-Authored-By: Claude Opus 5.5 <[email protected]>
90 lines
4.3 KiB
YAML
90 lines
4.3 KiB
YAML
---
|
|
# Keycloak instance (CR of the Operator CRD installed by platform/keycloak). CARRIED BY
|
|
# platform-config (dependsOn platform) — F-MON1 controllers/configs split.
|
|
#
|
|
# Served behind the shared Gateway at keycloak.home.keithjackson.dev (public, hardened): TLS is
|
|
# terminated by the Gateway wildcard cert, so Keycloak runs HTTP internally + trusts X-Forwarded-*
|
|
# (production/proxy-aware, docs/15). Admin (break-glass) credential comes from the SOPS-encrypted
|
|
# keycloak-admin-credentials Secret; realm/clients/groups are reconciled by the KeycloakRealmImport.
|
|
apiVersion: k8s.keycloak.org/v2alpha1
|
|
kind: Keycloak
|
|
metadata:
|
|
name: keycloak
|
|
namespace: keycloak
|
|
spec:
|
|
instances: ${KEYCLOAK_REPLICAS} # overlay knob (dev 1 / prod 2 HA) — R-ENV-02
|
|
# --- Database: CloudNativePG cluster (platform/keycloak/postgres-cluster.yaml). The read-write
|
|
# service keycloak-db-rw + the CNPG-generated keycloak-db-app secret (keys username/password). ---
|
|
db:
|
|
vendor: postgres
|
|
host: keycloak-db-rw
|
|
port: 5432
|
|
database: keycloak
|
|
usernameSecret:
|
|
name: keycloak-db-app
|
|
key: username
|
|
passwordSecret:
|
|
name: keycloak-db-app
|
|
key: password
|
|
poolMinSize: 5
|
|
poolMaxSize: 20
|
|
# --- Exposure: TLS at the Gateway; Keycloak serves HTTP and trusts the proxy headers. ---
|
|
hostname:
|
|
hostname: https://keycloak.${BASE_DOMAIN}
|
|
http:
|
|
httpEnabled: true # TLS is terminated at the Gateway (edge), not in Keycloak
|
|
proxy:
|
|
headers: xforwarded # trust X-Forwarded-* from the shared Gateway
|
|
ingress:
|
|
enabled: false # exposed via Gateway API HTTPRoute (httproute.yaml), not the
|
|
# operator's built-in Ingress
|
|
# --- ONE OWNER for the `keycloak` ServiceMonitor: this repo (servicemonitor.yaml), not the operator.
|
|
# Since 26.x the operator creates its OWN ServiceMonitor, named after this CR (`keycloak`, i.e.
|
|
# the SAME name/namespace as ours), whenever metrics-enabled=true and the CRD exists —
|
|
# spec.serviceMonitor.enabled defaults TRUE. On 2026-11-08, right after the 26.0.6 -> 26.8.2
|
|
# bump, its server-side apply onto our Flux-owned object failed `409 Conflict`, the operator
|
|
# exhausted retries, the CR sat Ready=False/HasErrors=True, and `platform-config` (wait: true,
|
|
# so it health-checks this CR) stalled every Kustomization downstream of it. Ours is kept
|
|
# because the L0 servicemonitor-ports-resolve and L2 keycloak-monitored conformance tests and
|
|
# the dashboard's up{namespace="keycloak"} are written against it; the operator's would scrape
|
|
# the same thing (management port 9000, /metrics, 30s). Do NOT flip this to true without
|
|
# deleting servicemonitor.yaml in the same change — two owners of one object is a fight
|
|
# (cf. the default-deny NetworkPolicy drift loop).
|
|
serviceMonitor:
|
|
enabled: false
|
|
# --- Hardening + observability options (docs/15). Brute-force detection is a REALM setting
|
|
# (bruteForceProtected in the realm import); these enable health + metrics endpoints. ---
|
|
additionalOptions:
|
|
- name: metrics-enabled
|
|
value: "true"
|
|
- name: health-enabled
|
|
value: "true"
|
|
resources:
|
|
requests:
|
|
cpu: 250m
|
|
memory: 768Mi
|
|
limits:
|
|
memory: 1280Mi
|
|
# --- Admin bootstrap credential (R-IDP-04, break-glass R-IDP-05) ---
|
|
# The provisioned SOPS secret keycloak-admin-credentials carries keys admin-user/admin-password.
|
|
# The operator's supported spec.bootstrapAdmin.user.secret expects keys username/password, so — to
|
|
# consume the already-provisioned secret WITHOUT re-keying it (must not touch its encrypted data) —
|
|
# the credential is injected via the podTemplate escape hatch as KC_BOOTSTRAP_ADMIN_* env, which
|
|
# Keycloak reads to create the temporary bootstrap/break-glass admin on first start.
|
|
unsupported:
|
|
podTemplate:
|
|
spec:
|
|
containers:
|
|
- name: keycloak
|
|
env:
|
|
- name: KC_BOOTSTRAP_ADMIN_USERNAME
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: keycloak-admin-credentials
|
|
key: admin-user
|
|
- name: KC_BOOTSTRAP_ADMIN_PASSWORD
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: keycloak-admin-credentials
|
|
key: admin-password
|