Coexistence wiring for the Authentik replacement: ZITADEL gets its own CNPG cluster, SOPS masterkey, Helm values and ArgoCD Application in namespace iam. Authentik keeps serving; no consumer is moved and no Authentik object changes. - k8s/infra/databases/zitadel-db.yaml: CNPG Postgres 16.2, 3 instances - k8s/infra/iam/zitadel-values.yaml: chart 10.4.0 pinned, DSN from the CNPG secret, masterkey from a Secret, metrics ServiceMonitor, second-sync caveat - k8s/argocd/apps/30-security.yaml: Application named zitadel, wave 3, automated sync deliberately absent for the first sync (chart PreSync hook re-run risk) - k8s/argocd/secrets/zitadel-masterkey.enc.yaml: 32-byte masterkey, SOPS - k8s/infra/monitoring/alerts/svc-zitadel-rules.yaml: target-down, login-UI, crash-loop and PreSync-job-failed rules - terraform/files/coredns/Corefile: in-cluster rewrite for the zitadel host Not applied. The cluster is mid-incident: vault-0 and authentik-server are 0/1, authentik-db-2 has 14 restarts, prometheus-0 is 1/2 CrashLoopBackOff and kube-state-metrics has 1925 restarts. Two manual steps still gate the first sync: the Cloudflare Tunnel public hostname plus a proxied CNAME, and terraform apply + make apply-cp for the CoreDNS rewrite (Talos-owned). The offline masterkey copy is at ~/zitadel-masterkey.offline (mode 600). Move it to sealed storage and delete it before the first sync. Losing it makes every encrypted ZITADEL value unreadable.
135 lines
5.4 KiB
YAML
135 lines
5.4 KiB
YAML
# k8s/infra/iam/zitadel-values.yaml
|
|
# ZITADEL — Authentik replacement. Coexistence is deliberate: Authentik keeps
|
|
# running, read-only, in the same `iam` namespace until the last consumer moves.
|
|
# Migration plan: ~/workplace/homelab-doc/ZITADEL_MIGRATION.html (outside git).
|
|
#
|
|
# Chart: zitadel/zitadel 10.4.0, app v4.19.2. The plan cites v4.19.4; the chart
|
|
# has not published it yet. Pin the chart, never `*`, until the v4.19.4 chart
|
|
# exists — a silent app bump invalidates the verified masterkey/passwap facts.
|
|
#
|
|
# URL: https://zitadel.riotpiao.com. TLS terminates at ingress-nginx, which
|
|
# serves the wildcard riotpiao-com-tls as its default-ssl-certificate, so
|
|
# ExternalSecure=true and TLS.Enabled=false (no per-rule tls: block needed).
|
|
#
|
|
# Database: CNPG cluster zitadel-db (wave 2). The DSN comes from CNPG's
|
|
# generated `zitadel-db-app` secret. When ZITADEL_DATABASE_POSTGRES_DSN is set,
|
|
# ZITADEL ignores the discrete Database.Postgres.* fields, so neither a
|
|
# password nor a host is written into this file.
|
|
#
|
|
# Masterkey: Secret `zitadel-masterkey` (k8s/argocd/secrets/
|
|
# zitadel-masterkey.enc.yaml), key `masterkey`, exactly 32 bytes. It wraps the
|
|
# rows in system.encryption_keys, which encrypt every client secret and private
|
|
# key. Losing it makes those values unreadable: keep the offline sealed copy.
|
|
#
|
|
# Ingress uses the chart's own objects (controller: nginx adds the gRPC backend
|
|
# annotation ZITADEL needs for its HTTP/2 API). Do not hand-write a second
|
|
# Ingress for this host.
|
|
|
|
zitadel:
|
|
configmapConfig:
|
|
ExternalSecure: true
|
|
ExternalDomain: zitadel.riotpiao.com
|
|
TLS:
|
|
Enabled: false
|
|
|
|
# Rendered into a Secret by the chart; read by pods and init jobs alike.
|
|
masterkeySecretName: zitadel-masterkey
|
|
|
|
resources:
|
|
requests: { memory: "512Mi", cpu: "250m" }
|
|
limits: { memory: "2Gi", cpu: "1" }
|
|
|
|
# FirstInstance defaults stay for the FIRST sync: the chart creates org
|
|
# `zitadel` plus the `iam-admin` machine user and its machine-key Secret.
|
|
# Terraform consumes that key to declare projects, roles, OIDC apps and the
|
|
# groups action.
|
|
#
|
|
# SECOND-SYNC HAZARD — read before enabling automated sync.
|
|
# The chart marks eight objects as `pre-install,pre-upgrade` hooks (two
|
|
# ServiceAccounts, two ConfigMaps, Role, RoleBinding, zitadel-init,
|
|
# zitadel-setup), and one cleanup Job as `post-delete`. ArgoCD maps the first
|
|
# eight to PreSync with `before-hook-creation`: it deletes and re-creates them
|
|
# on EVERY sync, not only on a chart change.
|
|
# - zitadel-init (`init zitadel`) — schema step
|
|
# - zitadel-setup (`setup`) — first instance + iam-admin machine key
|
|
# On an initialized instance the setup step can fail, which aborts the sync
|
|
# before the Deployment is applied. If it succeeds instead, its
|
|
# machinekeyWriter container re-applies the iam-admin key Secret that
|
|
# Terraform reads, which silently rotates a credential in use.
|
|
# After the first sync reports healthy:
|
|
# 1. set configmapConfig.FirstInstance.Skip: true here — permanently: the
|
|
# setup steps are schema-only once the instance exists, and Skip alone
|
|
# stops the machinekeyWriter rotation
|
|
# 2. set initJob.enabled: false below
|
|
# 3. commit, sync once, then enable automated+selfHeal on the Application
|
|
# For a later schema upgrade, re-enable initJob.enabled only — never set
|
|
# FirstInstance.Skip back to false.
|
|
# ArgoCD never prunes a hook Job, so a Job that failed before step 2 stays and
|
|
# keeps ZitadelSetupJobFailed firing until a human deletes it.
|
|
|
|
# DSN mode (chart renders this through the zitadel.dbEnv helper). CNPG's `uri`
|
|
# key is a postgresql:// URL, which ZITADEL accepts as a DSN.
|
|
env:
|
|
- name: ZITADEL_DATABASE_POSTGRES_DSN
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: zitadel-db-app
|
|
key: uri
|
|
|
|
# Bundled Bitnami Postgres is disabled: the CNPG cluster above is the system of
|
|
# record. Enabling it would create a second, unmanaged database.
|
|
postgresql:
|
|
enabled: false
|
|
|
|
# CNPG already created the database, the `app` user and its grants, so the init
|
|
# job runs only the ZITADEL-internal step. Running the full default command
|
|
# would try to create a database and a superuser it does not own.
|
|
initJob:
|
|
command: "zitadel"
|
|
activeDeadlineSeconds: 600
|
|
|
|
# Two API replicas: the read path survives one node drain, and both share the
|
|
# same masterkey and database. The login UI stays at one replica — a session
|
|
# cookie is not shared, so a second pod buys nothing.
|
|
replicaCount: 2
|
|
revisionHistoryLimit: 3
|
|
|
|
ingress:
|
|
enabled: true
|
|
className: nginx
|
|
controller: nginx
|
|
|
|
login:
|
|
enabled: true
|
|
replicaCount: 1
|
|
ingress:
|
|
enabled: true
|
|
className: nginx
|
|
|
|
# Chart-owned ServiceMonitor. The cluster's Prometheus runs with
|
|
# serviceMonitorSelectorNilUsesHelmValues=false, so the release label is not
|
|
# required for discovery. Endpoint is /debug/metrics on port http2-server.
|
|
metrics:
|
|
enabled: true
|
|
serviceMonitor:
|
|
enabled: true
|
|
scrapeInterval: 30s
|
|
scrapeTimeout: 10s
|
|
|
|
# The 3-CP / 2-worker cluster mixes control-plane taints with worker capacity.
|
|
# Without the toleration a rolling upgrade can leave both replicas Pending.
|
|
tolerations:
|
|
- key: node-role.kubernetes.io/control-plane
|
|
operator: Exists
|
|
effect: NoSchedule
|
|
|
|
affinity:
|
|
podAntiAffinity:
|
|
preferredDuringSchedulingIgnoredDuringExecution:
|
|
- weight: 100
|
|
podAffinityTerm:
|
|
topologyKey: kubernetes.io/hostname
|
|
labelSelector:
|
|
matchLabels:
|
|
app.kubernetes.io/name: zitadel
|