| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106 |
- # Exactly-once schema migration Job (task 46).
- #
- # Run BEFORE the Deployment rolls (CI applies the Job, waits for completion,
- # then rolls the Deployment). `migrate` applies forward-only sqlx migrations
- # under Postgres advisory lock, so concurrent starts serialize and the schema
- # is applied exactly once — replicas MUST NOT migrate (they share one
- # database; per-replica migration races and double-applies). The Deployment's
- # readiness gate (`_sqlx_migrations` version match) keeps new replicas out of
- # traffic until this Job succeeds.
- #
- # Hardened like the Deployment: non-root 65532,
- # read-only rootfs, ALL capabilities dropped, `automountServiceAccountToken:
- # false`, and every secret (token pepper, TOTP key, DB/Redis passwords) mounted
- # as 0400 tmpfs files under `/run/secrets` (read through the `*_FILE`
- # variables) instead of env vars. The image carries the same placeholder
- # digest; `pin-digest` rewrites the real digest in `kustomization.yaml` for
- # every workload.
- apiVersion: batch/v1
- kind: Job
- metadata:
- name: d-oauth-migrate
- namespace: d-oauth
- labels:
- app.kubernetes.io/name: d-oauth-server
- app.kubernetes.io/part-of: d-oauth
- spec:
- backoffLimit: 3
- activeDeadlineSeconds: 300
- ttlSecondsAfterFinished: 3600
- template:
- metadata:
- labels:
- app.kubernetes.io/name: d-oauth-server
- app.kubernetes.io/part-of: d-oauth
- app.kubernetes.io/component: migrate
- spec:
- restartPolicy: Never
- automountServiceAccountToken: false
- securityContext:
- runAsNonRoot: true
- runAsUser: 65532
- runAsGroup: 65532
- fsGroup: 65532
- fsGroupChangePolicy: OnRootMismatch
- seccompProfile:
- type: RuntimeDefault
- containers:
- - name: migrate
- # Same pinning rule as the Deployment: placeholder digest, never a
- # shippable `:latest`; CI resolves it via `kustomization.yaml`.
- image: ghcr.io/d-oauth/d-oauth-server@sha256:0000000000000000000000000000000000000000000000000000000000000000
- imagePullPolicy: IfNotPresent
- # Overrides the image ENTRYPOINT (["/d-oauth-server","serve"]).
- command: ["/d-oauth-server", "migrate"]
- envFrom:
- - configMapRef:
- name: d-oauth-config
- env:
- # Same file-only secret rule as the Deployment; the DSNs come from
- # the ConfigMap.
- - name: D_OAUTH_DATABASE__PASSWORD_FILE
- value: /run/secrets/database-password
- - name: D_OAUTH_REDIS__PASSWORD_FILE
- value: /run/secrets/redis-password
- - name: D_OAUTH_TOKEN_PEPPER_FILE
- value: /run/secrets/token-pepper
- - name: D_OAUTH_TOTP_ENCRYPTION_KEY_FILE
- value: /run/secrets/totp-encryption-key
- resources:
- requests:
- cpu: 50m
- memory: 64Mi
- limits:
- cpu: 500m
- memory: 256Mi
- securityContext:
- allowPrivilegeEscalation: false
- readOnlyRootFilesystem: true
- capabilities:
- drop:
- - ALL
- volumeMounts:
- - name: certs
- mountPath: /certs
- readOnly: true
- - name: secrets
- mountPath: /run/secrets
- readOnly: true
- volumes:
- - name: certs
- secret:
- secretName: d-oauth-tls
- defaultMode: 0o400
- - name: secrets
- secret:
- secretName: d-oauth-secrets
- defaultMode: 0o400
- items:
- - key: database-password
- path: database-password
- - key: redis-password
- path: redis-password
- - key: token-pepper
- path: token-pepper
- - key: totp-encryption-key
- path: totp-encryption-key
|