# Deployment + Service — privacyIDEA (namespace: mfa) # # Prerequisites (apply in order): # 1. pvc.yaml — privacyidea-data and privacyidea-logs PVCs # 2. configmap.yaml — privacyidea-cfg (pi.cfg template) # 3. create-secrets.sh — privacyidea-config Secret (PI_SECRET_KEY, PI_PEPPER, DB URI) # 4. This file # # After first pod starts successfully: # 5. enckey-bootstrap.sh — extract enckey + audit keys, create DR Secrets # 6. bootstrap-admin.sh — create pi-admin (+ MFA enrolment) and trigger-admin # # Container port: 8080. # ghcr.io/gpappsoft/privacyidea-docker listens on port 8080 (gunicorn). apiVersion: apps/v1 kind: Deployment metadata: name: privacyidea namespace: mfa labels: app.kubernetes.io/name: privacyidea app.kubernetes.io/part-of: net-kingdom-sso-mfa net-kingdom/component: mfa spec: replicas: 1 selector: matchLabels: app.kubernetes.io/name: privacyidea strategy: type: Recreate # single-node — avoid split-brain on PVC template: metadata: labels: app.kubernetes.io/name: privacyidea app.kubernetes.io/part-of: net-kingdom-sso-mfa net-kingdom/component: mfa spec: # ── Security context ─────────────────────────────────────────────────── securityContext: runAsNonRoot: false # privacyIDEA nginx needs root to bind port; revisit fsGroup: 999 # privacyidea group inside container # ── Init: ensure log dir exists and has correct permissions ─────────── initContainers: - name: init-logdir image: busybox:1.36 command: ["sh", "-c", "mkdir -p /var/log/privacyidea && chmod 777 /var/log/privacyidea"] volumeMounts: - name: logs mountPath: /var/log/privacyidea containers: - name: privacyidea # Pin to a specific release; update via image update policy. # Official image: https://github.com/gpappsoft/privacyidea-docker # privacyidea/privacyidea:3.12 and privacyidea/otpserver:3.12.2 do not exist. # Correct image: ghcr.io/gpappsoft/privacyidea-docker:3.12.2 (port 8080) image: ghcr.io/gpappsoft/privacyidea-docker:3.12.2 imagePullPolicy: IfNotPresent ports: - name: http containerPort: 8080 protocol: TCP # ── Environment ───────────────────────────────────────────────── # Tell gpappsoft entrypoint to use our mounted pi.cfg instead of # the image's built-in /privacyidea/etc/pi.cfg. env: - name: PRIVACYIDEA_CONFIGFILE value: /etc/privacyidea/pi.cfg # gpappsoft entrypoint passes these to gunicorn as the bind address/port. - name: PI_ADDRESS value: "0.0.0.0" - name: PI_PORT value: "8080" # Sensitive values from Secret (PI_SECRET_KEY, PI_PEPPER, PI_SQLALCHEMY_DATABASE_URI) envFrom: - secretRef: name: privacyidea-config # ── Volume mounts ─────────────────────────────────────────────── volumeMounts: # pi.cfg overlaid into the data PVC as a single file (subPath). - name: config mountPath: /etc/privacyidea/pi.cfg subPath: pi.cfg readOnly: true # Data PVC: enckey, audit keys, scripts, and other PI runtime files. - name: data mountPath: /etc/privacyidea # Logs PVC: persistent application logs. - name: logs mountPath: /var/log/privacyidea # ── Probes ────────────────────────────────────────────────────── # Startup probe: give PI up to 3 min to run DB migrations on first boot. startupProbe: tcpSocket: port: 8080 initialDelaySeconds: 15 periodSeconds: 10 failureThreshold: 18 # 18 × 10s = 3 min livenessProbe: tcpSocket: port: 8080 initialDelaySeconds: 0 periodSeconds: 15 failureThreshold: 3 readinessProbe: # /token/ returns 401 for unauthenticated GET; use tcpSocket instead. # Switch to httpGet /healthz or similar once confirmed in the image. tcpSocket: port: 8080 initialDelaySeconds: 0 periodSeconds: 10 failureThreshold: 3 # ── Resources ─────────────────────────────────────────────────── # Raise limits for production; privacyIDEA handles crypto and DB queries. resources: requests: cpu: "100m" memory: "256Mi" limits: cpu: "500m" memory: "512Mi" # ── Volumes ───────────────────────────────────────────────────────── volumes: - name: config configMap: name: privacyidea-cfg - name: data persistentVolumeClaim: claimName: privacyidea-data - name: logs persistentVolumeClaim: claimName: privacyidea-logs --- # Service — ClusterIP; Traefik and Keycloak reach privacyIDEA via this. apiVersion: v1 kind: Service metadata: name: privacyidea namespace: mfa labels: app.kubernetes.io/name: privacyidea app.kubernetes.io/part-of: net-kingdom-sso-mfa net-kingdom/component: mfa spec: type: ClusterIP selector: app.kubernetes.io/name: privacyidea ports: - name: http port: 8080 targetPort: 8080 protocol: TCP