The server pod was getting killed by liveness probe at 60s while still waiting on migration DB lock (worker pod also running migrations against same DB). Add startupProbe with 10.5 min budget so liveness doesn't fire until migrations finish. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
449 lines
13 KiB
YAML
449 lines
13 KiB
YAML
# Authentik OIDC backend
|
|
# ArgoCD-managed. BlueJay Lab.
|
|
#
|
|
# Stack:
|
|
# - PostgreSQL 16 StatefulSet (single replica, Longhorn RWO 5Gi)
|
|
# - Redis 7 Deployment (no persistence — session/cache only)
|
|
# - Authentik server + worker Deployments (image ghcr.io/goauthentik/server:2024.12.3)
|
|
# - Media PVC shared between server + worker (Longhorn RWO 2Gi)
|
|
# - Certificate via step-ca-acme ClusterIssuer
|
|
# - Traefik IngressRoute at id.iamworkin.lan
|
|
#
|
|
# Secrets come from 1Password item "authentik-credentials" (IAmWorkin vault, id y6i74ch22q5wvm7znquq4nhhcu)
|
|
# via the OnePasswordItem CRD, materialized into k8s Secret authentik/authentik-credentials.
|
|
#
|
|
# Why the discovery URL is /application/o/pimanager/ : Authentik issues per-application OIDC providers.
|
|
# The pimanager OIDC application/provider is created after the cluster pods are healthy (manual or
|
|
# via API once the bootstrap token is available — see Notes substrate).
|
|
|
|
---
|
|
apiVersion: v1
|
|
kind: Namespace
|
|
metadata:
|
|
name: authentik
|
|
labels:
|
|
app.kubernetes.io/part-of: bluejay-infra
|
|
|
|
---
|
|
# 1Password operator pulls the authentik-credentials item into a k8s Secret of the same name.
|
|
# Field labels in 1P become Secret keys: AUTHENTIK_SECRET_KEY, POSTGRES_PASSWORD, REDIS_PASSWORD,
|
|
# BOOTSTRAP_ADMIN_PASSWORD, BOOTSTRAP_ADMIN_TOKEN, BOOTSTRAP_ADMIN_EMAIL.
|
|
apiVersion: onepassword.com/v1
|
|
kind: OnePasswordItem
|
|
metadata:
|
|
name: authentik-credentials
|
|
namespace: authentik
|
|
spec:
|
|
itemPath: "vaults/IAmWorkin/items/authentik-credentials"
|
|
|
|
---
|
|
# Shared media volume for server + worker pods.
|
|
apiVersion: v1
|
|
kind: PersistentVolumeClaim
|
|
metadata:
|
|
name: authentik-media
|
|
namespace: authentik
|
|
spec:
|
|
storageClassName: longhorn
|
|
accessModes: [ReadWriteOnce]
|
|
resources:
|
|
requests:
|
|
storage: 2Gi
|
|
|
|
---
|
|
# PostgreSQL 16 StatefulSet — Authentik's primary store.
|
|
apiVersion: apps/v1
|
|
kind: StatefulSet
|
|
metadata:
|
|
name: authentik-postgres
|
|
namespace: authentik
|
|
labels:
|
|
app: authentik-postgres
|
|
argocd.argoproj.io/instance: infra-authentik
|
|
spec:
|
|
persistentVolumeClaimRetentionPolicy:
|
|
whenDeleted: Retain
|
|
whenScaled: Retain
|
|
podManagementPolicy: OrderedReady
|
|
serviceName: authentik-postgres
|
|
replicas: 1
|
|
revisionHistoryLimit: 10
|
|
selector:
|
|
matchLabels:
|
|
app: authentik-postgres
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app: authentik-postgres
|
|
spec:
|
|
containers:
|
|
- name: postgres
|
|
image: postgres:16-alpine
|
|
ports:
|
|
- containerPort: 5432
|
|
name: postgres
|
|
env:
|
|
- name: POSTGRES_USER
|
|
value: authentik
|
|
- name: POSTGRES_PASSWORD
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: authentik-credentials
|
|
key: POSTGRES_PASSWORD
|
|
- name: POSTGRES_DB
|
|
value: authentik
|
|
- name: POSTGRES_INITDB_ARGS
|
|
value: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C"
|
|
- name: PGDATA
|
|
value: /var/lib/postgresql/data/pgdata
|
|
readinessProbe:
|
|
exec:
|
|
command: ["pg_isready", "-U", "authentik"]
|
|
initialDelaySeconds: 5
|
|
periodSeconds: 5
|
|
livenessProbe:
|
|
exec:
|
|
command: ["pg_isready", "-U", "authentik"]
|
|
initialDelaySeconds: 30
|
|
periodSeconds: 30
|
|
resources:
|
|
requests: { cpu: 100m, memory: 256Mi }
|
|
limits: { cpu: 1000m, memory: 1Gi }
|
|
volumeMounts:
|
|
- name: pgdata
|
|
mountPath: /var/lib/postgresql/data
|
|
volumeClaimTemplates:
|
|
- metadata:
|
|
name: pgdata
|
|
spec:
|
|
storageClassName: longhorn
|
|
accessModes: [ReadWriteOnce]
|
|
volumeMode: Filesystem
|
|
resources:
|
|
requests:
|
|
storage: 5Gi
|
|
|
|
---
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: authentik-postgres
|
|
namespace: authentik
|
|
spec:
|
|
clusterIP: None
|
|
selector:
|
|
app: authentik-postgres
|
|
ports:
|
|
- name: postgres
|
|
port: 5432
|
|
targetPort: 5432
|
|
|
|
---
|
|
# Redis 7 — session storage + Celery broker. No persistence needed (cache).
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: authentik-redis
|
|
namespace: authentik
|
|
labels:
|
|
app: authentik-redis
|
|
argocd.argoproj.io/instance: infra-authentik
|
|
spec:
|
|
replicas: 1
|
|
strategy:
|
|
type: Recreate
|
|
selector:
|
|
matchLabels:
|
|
app: authentik-redis
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app: authentik-redis
|
|
spec:
|
|
containers:
|
|
- name: redis
|
|
image: redis:7-alpine
|
|
args:
|
|
- "--save"
|
|
- ""
|
|
- "--appendonly"
|
|
- "no"
|
|
- "--requirepass"
|
|
- "$(REDIS_PASSWORD)"
|
|
env:
|
|
- name: REDIS_PASSWORD
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: authentik-credentials
|
|
key: REDIS_PASSWORD
|
|
ports:
|
|
- containerPort: 6379
|
|
name: redis
|
|
readinessProbe:
|
|
tcpSocket: { port: 6379 }
|
|
initialDelaySeconds: 5
|
|
periodSeconds: 5
|
|
livenessProbe:
|
|
tcpSocket: { port: 6379 }
|
|
initialDelaySeconds: 30
|
|
periodSeconds: 30
|
|
resources:
|
|
requests: { cpu: 50m, memory: 64Mi }
|
|
limits: { cpu: 500m, memory: 256Mi }
|
|
|
|
---
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: authentik-redis
|
|
namespace: authentik
|
|
spec:
|
|
selector:
|
|
app: authentik-redis
|
|
ports:
|
|
- name: redis
|
|
port: 6379
|
|
targetPort: 6379
|
|
|
|
---
|
|
# Authentik server Deployment — HTTP frontend on :9000.
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: authentik-server
|
|
namespace: authentik
|
|
labels:
|
|
app: authentik-server
|
|
argocd.argoproj.io/instance: infra-authentik
|
|
spec:
|
|
replicas: 1
|
|
strategy:
|
|
type: Recreate # shares /media RWO PVC with worker
|
|
selector:
|
|
matchLabels:
|
|
app: authentik-server
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app: authentik-server
|
|
spec:
|
|
securityContext:
|
|
# Authentik image runs as uid 1000 "authentik" but the Longhorn PVC mounts
|
|
# root:root by default. fsGroup recursively chgrp + chmod g+rwx so the
|
|
# non-root container can mkdir /media/public during the tenant_files migration.
|
|
fsGroup: 1000
|
|
containers:
|
|
- name: server
|
|
image: ghcr.io/goauthentik/server:2024.12.3
|
|
args: ["server"]
|
|
ports:
|
|
- containerPort: 9000
|
|
name: http
|
|
- containerPort: 9443
|
|
name: https
|
|
env:
|
|
- name: AUTHENTIK_SECRET_KEY
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: authentik-credentials
|
|
key: AUTHENTIK_SECRET_KEY
|
|
- name: AUTHENTIK_REDIS__HOST
|
|
value: authentik-redis
|
|
- name: AUTHENTIK_REDIS__PASSWORD
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: authentik-credentials
|
|
key: REDIS_PASSWORD
|
|
- name: AUTHENTIK_POSTGRESQL__HOST
|
|
value: authentik-postgres
|
|
- name: AUTHENTIK_POSTGRESQL__NAME
|
|
value: authentik
|
|
- name: AUTHENTIK_POSTGRESQL__USER
|
|
value: authentik
|
|
- name: AUTHENTIK_POSTGRESQL__PASSWORD
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: authentik-credentials
|
|
key: POSTGRES_PASSWORD
|
|
- name: AUTHENTIK_BOOTSTRAP_PASSWORD
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: authentik-credentials
|
|
key: BOOTSTRAP_ADMIN_PASSWORD
|
|
- name: AUTHENTIK_BOOTSTRAP_TOKEN
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: authentik-credentials
|
|
key: BOOTSTRAP_ADMIN_TOKEN
|
|
- name: AUTHENTIK_BOOTSTRAP_EMAIL
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: authentik-credentials
|
|
key: BOOTSTRAP_ADMIN_EMAIL
|
|
- name: AUTHENTIK_DISABLE_UPDATE_CHECK
|
|
value: "true"
|
|
- name: AUTHENTIK_ERROR_REPORTING__ENABLED
|
|
value: "false"
|
|
- name: AUTHENTIK_LOG_LEVEL
|
|
value: info
|
|
# First-boot Authentik can take 3+ min on the migration phase
|
|
# (waiting on DB lock while worker also runs migrations). Initial
|
|
# delays are generous so kubelet doesn't kill the pod mid-migration;
|
|
# periodSeconds keeps post-startup probing responsive.
|
|
readinessProbe:
|
|
httpGet:
|
|
path: /-/health/ready/
|
|
port: 9000
|
|
initialDelaySeconds: 60
|
|
periodSeconds: 10
|
|
timeoutSeconds: 5
|
|
failureThreshold: 12
|
|
livenessProbe:
|
|
httpGet:
|
|
path: /-/health/live/
|
|
port: 9000
|
|
initialDelaySeconds: 300
|
|
periodSeconds: 30
|
|
timeoutSeconds: 10
|
|
failureThreshold: 3
|
|
startupProbe:
|
|
httpGet:
|
|
path: /-/health/live/
|
|
port: 9000
|
|
initialDelaySeconds: 30
|
|
periodSeconds: 15
|
|
timeoutSeconds: 10
|
|
failureThreshold: 40 # 30s + 40*15s = 10.5 min budget
|
|
resources:
|
|
requests: { cpu: 150m, memory: 512Mi }
|
|
limits: { cpu: 1500m, memory: 1Gi }
|
|
volumeMounts:
|
|
- name: media
|
|
mountPath: /media
|
|
volumes:
|
|
- name: media
|
|
persistentVolumeClaim:
|
|
claimName: authentik-media
|
|
|
|
---
|
|
# Authentik worker Deployment — runs Celery background tasks.
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: authentik-worker
|
|
namespace: authentik
|
|
labels:
|
|
app: authentik-worker
|
|
argocd.argoproj.io/instance: infra-authentik
|
|
spec:
|
|
replicas: 1
|
|
strategy:
|
|
type: Recreate # shares /media RWO PVC with server
|
|
selector:
|
|
matchLabels:
|
|
app: authentik-worker
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app: authentik-worker
|
|
spec:
|
|
securityContext:
|
|
# Same as server pod — non-root uid 1000 needs PVC group write.
|
|
fsGroup: 1000
|
|
containers:
|
|
- name: worker
|
|
image: ghcr.io/goauthentik/server:2024.12.3
|
|
args: ["worker"]
|
|
env:
|
|
- name: AUTHENTIK_SECRET_KEY
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: authentik-credentials
|
|
key: AUTHENTIK_SECRET_KEY
|
|
- name: AUTHENTIK_REDIS__HOST
|
|
value: authentik-redis
|
|
- name: AUTHENTIK_REDIS__PASSWORD
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: authentik-credentials
|
|
key: REDIS_PASSWORD
|
|
- name: AUTHENTIK_POSTGRESQL__HOST
|
|
value: authentik-postgres
|
|
- name: AUTHENTIK_POSTGRESQL__NAME
|
|
value: authentik
|
|
- name: AUTHENTIK_POSTGRESQL__USER
|
|
value: authentik
|
|
- name: AUTHENTIK_POSTGRESQL__PASSWORD
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: authentik-credentials
|
|
key: POSTGRES_PASSWORD
|
|
- name: AUTHENTIK_DISABLE_UPDATE_CHECK
|
|
value: "true"
|
|
- name: AUTHENTIK_ERROR_REPORTING__ENABLED
|
|
value: "false"
|
|
- name: AUTHENTIK_LOG_LEVEL
|
|
value: info
|
|
resources:
|
|
requests: { cpu: 100m, memory: 256Mi }
|
|
limits: { cpu: 1000m, memory: 768Mi }
|
|
volumeMounts:
|
|
- name: media
|
|
mountPath: /media
|
|
volumes:
|
|
- name: media
|
|
persistentVolumeClaim:
|
|
claimName: authentik-media
|
|
|
|
---
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: authentik-server
|
|
namespace: authentik
|
|
spec:
|
|
selector:
|
|
app: authentik-server
|
|
ports:
|
|
- name: http
|
|
port: 9000
|
|
targetPort: 9000
|
|
- name: https
|
|
port: 9443
|
|
targetPort: 9443
|
|
|
|
---
|
|
# step-ca leaf certificate for id.iamworkin.lan.
|
|
# step-ca container resolver uses pfSense Unbound, so the public A record for id.iamworkin.lan
|
|
# MUST exist before this Certificate is applied (cert-manager HTTP-01 will silently 2h-backoff
|
|
# otherwise). Added 2026-05-25 via scripts/pfsense-add-id-host.py.
|
|
apiVersion: cert-manager.io/v1
|
|
kind: Certificate
|
|
metadata:
|
|
name: authentik-tls
|
|
namespace: authentik
|
|
spec:
|
|
secretName: authentik-tls
|
|
dnsNames:
|
|
- id.iamworkin.lan
|
|
issuerRef:
|
|
name: step-ca-acme
|
|
kind: ClusterIssuer
|
|
|
|
---
|
|
apiVersion: traefik.io/v1alpha1
|
|
kind: IngressRoute
|
|
metadata:
|
|
name: authentik
|
|
namespace: authentik
|
|
spec:
|
|
entryPoints: [websecure]
|
|
routes:
|
|
- match: Host(`id.iamworkin.lan`)
|
|
kind: Rule
|
|
services:
|
|
- name: authentik-server
|
|
port: 9000
|
|
tls:
|
|
secretName: authentik-tls
|