Files
net-kingdom/sso-mfa/k8s/keycloak/deployment.yaml
Bernd Worsch d0ed7d9cd6 feat(sso-mfa): T05 Keycloak manifests (NK-WP-0001-T05)
Deploys Keycloak (SSO core) in the sso namespace.

Files:
  sso-mfa/k8s/keycloak/pvc.yaml          — keycloak-data PVC (build cache)
  sso-mfa/k8s/keycloak/middleware.yaml   — rate-limit, admin-allowlist, HSTS
  sso-mfa/k8s/keycloak/deployment.yaml   — Deployment + Service; init container
                                           downloads privacyIDEA provider JAR
  sso-mfa/k8s/keycloak/ingress.yaml      — Ingress for kc.coulomb.social (CP-NK-004)
  sso-mfa/k8s/keycloak/create-secrets.sh — keycloak-config Secret
  sso-mfa/k8s/keycloak/bootstrap-realm.sh— hardens master realm, creates net-kingdom realm
  sso-mfa/k8s/keycloak/README.md         — apply order, custom image guide, DR
  sso-mfa/k8s/verify-t05.sh              — T05 done-criteria verification script

Config points added: CP-NK-004 (kc.coulomb.social), CP-NK-005 (provider JAR URL).
CP-NK-005 must be set before applying deployment.yaml.

Pending: apply to live cluster, set CP-NK-005, run bootstrap-realm.sh, verify-t05.sh.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 02:00:51 +00:00

252 lines
9.9 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Deployment + Service — Keycloak (namespace: sso)
#
# Prerequisites (apply in order):
# 1. pvc.yaml — keycloak-data PVC
# 2. middleware.yaml — Traefik middlewares
# 3. create-secrets.sh — keycloak-config Secret (KC_DB_URL, KC_DB_PASSWORD,
# KC_BOOTSTRAP_ADMIN_PASSWORD)
# 4. Edit PROVIDER_JAR_URL in the init container below (CP-NK-005 — see CONFIG.md)
# 5. This file
#
# After first pod starts successfully:
# 6. bootstrap-realm.sh — configure master realm, create net-kingdom realm
#
# privacyIDEA Keycloak Provider (init container):
# The init container downloads the provider JAR to /opt/keycloak/providers/
# before Keycloak starts. Keycloak detects providers and rebuilds automatically
# (no --optimized flag). Build output is cached in the keycloak-data PVC so
# subsequent restarts skip the full rebuild.
#
# For production, prefer building a custom image with the provider pre-baked:
# FROM quay.io/keycloak/keycloak:VERSION
# COPY keycloak-provider-VERSION.jar /opt/keycloak/providers/
# RUN /opt/keycloak/bin/kc.sh build
# A custom image avoids the internet dependency and ensures a reproducible build.
# See README.md "Custom image" section for details.
#
# Container ports:
# 8080 — HTTP (Traefik ingress; TLS terminated at Traefik)
# 9000 — Management (health/live, health/ready, health/started — kubelet only)
apiVersion: apps/v1
kind: Deployment
metadata:
name: keycloak
namespace: sso
labels:
app.kubernetes.io/name: keycloak
app.kubernetes.io/part-of: net-kingdom-sso-mfa
net-kingdom/component: sso
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: keycloak
strategy:
type: Recreate # single replica — avoid two pods racing on the build-cache PVC
template:
metadata:
labels:
app.kubernetes.io/name: keycloak
app.kubernetes.io/part-of: net-kingdom-sso-mfa
net-kingdom/component: sso
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000 # keycloak user inside the official image
fsGroup: 1000
# ── Init: download privacyIDEA Keycloak Provider JAR ─────────────────
# The JAR is placed in /opt/keycloak/providers/ via an emptyDir volume.
# Keycloak detects the new provider on startup and rebuilds automatically.
#
# NOTE: This requires outbound HTTPS from the cluster to GitHub.
# If your cluster has no egress internet access, pre-stage the JAR in an
# internal registry or use a custom Keycloak image (see comment above).
initContainers:
- name: install-privacyidea-provider
# Pin curl image version alongside the Keycloak image.
image: curlimages/curl:8.10.1
securityContext:
runAsNonRoot: true
runAsUser: 65534 # nobody — curl image default
env:
- name: PROVIDER_JAR_URL
# CP-NK-005 — EDIT this value before applying.
# Find the correct release at:
# https://github.com/privacyIDEA/keycloak-provider/releases
# Choose a version compatible with your Keycloak image version above.
# Example:
# https://github.com/privacyIDEA/keycloak-provider/releases/download/v0.9/keycloak-provider-0.9.jar
value: "EDIT_BEFORE_APPLY"
command:
- sh
- -c
- |
if [ "$PROVIDER_JAR_URL" = "EDIT_BEFORE_APPLY" ]; then
echo "ERROR: PROVIDER_JAR_URL not set." >&2
echo "Edit deployment.yaml and replace PROVIDER_JAR_URL with the real JAR URL." >&2
echo "See CONFIG.md CP-NK-005 and README.md." >&2
exit 1
fi
echo "Downloading privacyIDEA Keycloak Provider from: $PROVIDER_JAR_URL"
curl -fsSL -o /providers/keycloak-provider.jar "$PROVIDER_JAR_URL"
BYTES=$(wc -c < /providers/keycloak-provider.jar)
echo "Downloaded: ${BYTES} bytes -> /providers/keycloak-provider.jar"
volumeMounts:
- name: providers
mountPath: /providers
containers:
- name: keycloak
# Pin to a specific release; update via image update policy.
# Check https://quay.io/repository/keycloak/keycloak for latest stable.
image: quay.io/keycloak/keycloak:26.0
imagePullPolicy: IfNotPresent
# kc.sh start — no --optimized flag so Keycloak rebuilds when providers change.
# After the first successful start, subsequent starts use the cached build
# in the keycloak-data PVC and restart within ~30 seconds.
args: ["start"]
ports:
- name: http
containerPort: 8080
protocol: TCP
- name: management
containerPort: 9000
protocol: TCP
# ── Environment — sensitive values from Secret ──────────────────
env:
# Database
- name: KC_DB
value: postgres
- name: KC_DB_URL
valueFrom:
secretKeyRef:
name: keycloak-config
key: KC_DB_URL
- name: KC_DB_USERNAME
value: keycloak
- name: KC_DB_PASSWORD
valueFrom:
secretKeyRef:
name: keycloak-config
key: KC_DB_PASSWORD
# Bootstrap admin (used only on first start to create the admin user)
- name: KC_BOOTSTRAP_ADMIN_USERNAME
value: admin
- name: KC_BOOTSTRAP_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: keycloak-config
key: KC_BOOTSTRAP_ADMIN_PASSWORD
# Hostname & proxy
# CP-NK-004 — update CONFIG.md if you change this value.
- name: KC_HOSTNAME
value: kc.coulomb.social
# Traefik passes X-Forwarded-For and X-Forwarded-Proto headers.
- name: KC_PROXY_HEADERS
value: xforwarded
# TLS is terminated at Traefik; Keycloak serves HTTP inside the cluster.
- name: KC_HTTP_ENABLED
value: "true"
# Observability
- name: KC_HEALTH_ENABLED
value: "true"
- name: KC_METRICS_ENABLED
value: "true"
- name: KC_LOG_LEVEL
value: INFO
# Caching — local = in-JVM Infinispan; switch to ispn for multi-replica HA.
- name: KC_CACHE
value: local
# ── Volume mounts ───────────────────────────────────────────────
volumeMounts:
# providers emptyDir: populated by init container before Keycloak starts
- name: providers
mountPath: /opt/keycloak/providers
readOnly: true
# data PVC: Keycloak build cache (data/generated/) and runtime data
- name: data
mountPath: /opt/keycloak/data
# ── Probes ──────────────────────────────────────────────────────
# Keycloak 24+: health endpoints on management port 9000.
# /health/started — true once the application has completed startup.
# /health/live — true unless the application is in an unrecoverable state.
# /health/ready — true once Keycloak can serve requests.
#
# Startup: allow up to 5 min for DB migrations + provider build on first boot.
startupProbe:
httpGet:
path: /health/started
port: 9000
initialDelaySeconds: 20
periodSeconds: 10
failureThreshold: 30 # 30 × 10s = 5 min
livenessProbe:
httpGet:
path: /health/live
port: 9000
initialDelaySeconds: 0
periodSeconds: 15
failureThreshold: 3
readinessProbe:
httpGet:
path: /health/ready
port: 9000
initialDelaySeconds: 0
periodSeconds: 10
failureThreshold: 3
# ── Resources ───────────────────────────────────────────────────
# Keycloak is JVM-based; the initial provider build spikes CPU briefly.
# Raise limits for production.
resources:
requests:
cpu: "250m"
memory: "512Mi"
limits:
cpu: "1000m"
memory: "1Gi"
# ── Volumes ─────────────────────────────────────────────────────────
volumes:
# providers emptyDir: re-populated by init container on every pod start.
# Keycloak detects the JAR and checks whether a rebuild is needed.
# If the JAR hash matches the cached build, startup is fast (~30s).
- name: providers
emptyDir: {}
- name: data
persistentVolumeClaim:
claimName: keycloak-data
---
# Service — ClusterIP; Traefik reaches Keycloak via port 8080.
# Port 9000 (management) is NOT exposed — kubelet probes reach it directly on the pod.
apiVersion: v1
kind: Service
metadata:
name: keycloak
namespace: sso
labels:
app.kubernetes.io/name: keycloak
app.kubernetes.io/part-of: net-kingdom-sso-mfa
net-kingdom/component: sso
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: keycloak
ports:
- name: http
port: 8080
targetPort: 8080
protocol: TCP