deploy/k8s/dev runs the base with 2 replicas alongside throwaway services: two independent OpenLDAP servers (the dev image), PostgreSQL 18 and Mailpit. deploy/k8s/dev/e2e.sh checks a running deployment: both probes, registration with the activation mail sent over SMTP (and no password in it), activation, login, and login and readiness while ldap1 is scaled to zero. All checks pass. |
||
|---|---|---|
| .. | ||
| base | ||
| dev | ||
| README.md | ||
| secret.example.yaml | ||
Kubernetes deployment
base/ holds the manifests for the account webapp: Deployment, Service,
Ingress, PodDisruptionBudget and ConfigMap. Apply it with
kubectl apply -k (kustomize is built into kubectl).
What the cluster needs to provide
- PostgreSQL. Sessions, password-reset tokens and pending registrations
live in the database, so more than one replica needs PostgreSQL. Point
DATABASE_HOSTat it. The base doesn't ship a database; use the cluster's managed or operator-run PostgreSQL. - An ingress controller with TLS. The app expects TLS to terminate at
the ingress and trusts
X-Forwarded-Proto(TRUST_X_FORWARDED_PROTO=True). - Network access to the LDAP servers, the SMTP relay and the OTP server.
Deploying
-
Build and push the image from the repository root:
docker build -t registry.example.com/ungleich-user:1.0.0 . docker push registry.example.com/ungleich-user:1.0.0 -
Copy
base/into your environment's overlay (or edit it in place) and set:images:inkustomization.yaml: the registry and a pinned tagconfigmap.yaml: every value markedREQUIRED(env.samplelists all settings with their defaults)ingress.yaml: the ingress class and the TLS secret or cert-manager annotations
-
Create the Secret. It isn't part of the base, so credentials never go into git. See
secret.example.yaml:kubectl create secret generic ungleich-user \ --from-literal=SECRET_KEY="$(python3 -c 'import secrets; print(secrets.token_urlsafe(50))')" \ --from-literal=LDAP_ADMIN_DN='cn=...' \ --from-literal=LDAP_ADMIN_PASSWORD='...' \ --from-literal=DATABASE_PASSWORD='...' \ --from-literal=ADMIN_SEED='...' -
Apply:
kubectl apply -k deploy/k8s/base(or your overlay).
How it behaves
| Concern | Behaviour |
|---|---|
| Migrations | An init container runs manage.py migrate_locked in each pod. A PostgreSQL advisory lock makes pods that start together wait for the first one instead of migrating at the same time. |
| Probes | /healthz (liveness) has no dependencies, so an LDAP outage never restarts pods. /readyz (readiness) needs the database and at least one LDAP server. Both bypass ALLOWED_HOSTS. |
| LDAP failover | LDAPSERVER is a space-separated list. The first reachable server is used, and a failed server is skipped for 60 seconds. |
| Security | Runs as uid 10001 with a read-only root filesystem, all capabilities dropped, no privilege escalation, the RuntimeDefault seccomp profile and no service account token. The image has no sshd. |
| Logs | stdout/stderr only. LOG_LEVEL sets the level. |
| Rollouts | Rolling updates add one new pod before removing an old one (maxSurge: 1, maxUnavailable: 0). The PodDisruptionBudget keeps at least one pod running during node drains. |
Before going live, check with the operators
LDAP_USE_TLSdefaults toTrue(StartTLS) in the ConfigMap. Confirm the production LDAP servers support StartTLS, or useldaps://URIs.- HSTS (
SECURE_HSTS_SECONDS) is off. Enabling it, especially for subdomains, affects every*.ungleich.chsite. - Emails sent before the switch contain activation links in the old format. Those links stop working, and the affected users have to register again.
Local test cluster (dev overlay)
dev/ deploys the app into a kind cluster
together with two independent test LDAP servers, PostgreSQL and Mailpit,
which catches outgoing mail. All data is throwaway and every credential
is a dev value.
kind create cluster --name ungleich-user
docker build -t ungleich-user:dev .
docker compose -f docker-compose.dev.yml build ldap
kind load docker-image ungleich-user:dev ungleich-user-dev-ldap:2.6 --name ungleich-user
kubectl apply -k deploy/k8s/dev
kubectl -n ungleich-user-dev rollout status deploy/ungleich-user
deploy/k8s/dev/e2e.sh # probes, signup via SMTP, activation, login, LDAP failover
After rebuilding the image, run kind load again and then
kubectl -n ungleich-user-dev rollout restart deploy/ungleich-user.
To remove the cluster: kind delete cluster --name ungleich-user.