# --- Migration ---------------------------------------------------------------- # Als SUSPENDIERTER CronJob definiert, damit Kustomize das Image mit der # App gleichzieht. Er laeuft nie von selbst, sondern bewusst vor jedem # Rollout: # kubectl -n artista create job migrate-$(date +%Y%m%d%H%M) --from=cronjob/artista-modelcamp-migrate # kubectl -n artista wait --for=condition=complete job/migrate-… --timeout=5m apiVersion: batch/v1 kind: CronJob metadata: name: artista-modelcamp-migrate spec: suspend: true schedule: "0 0 1 1 *" jobTemplate: spec: backoffLimit: 0 ttlSecondsAfterFinished: 86400 template: metadata: labels: artista/db-client: "true" spec: restartPolicy: Never automountServiceAccountToken: false securityContext: runAsNonRoot: true runAsUser: 1001 seccompProfile: { type: RuntimeDefault } containers: - name: migrate image: artista-modelcamp-tools command: ["prisma", "migrate", "deploy"] envFrom: - secretRef: { name: artista-modelcamp-secrets } resources: requests: { cpu: 50m, memory: 128Mi } limits: { memory: 512Mi } securityContext: allowPrivilegeEscalation: false capabilities: { drop: ["ALL"] } --- # --- Worker ------------------------------------------------------------------- # Gibt abgelaufene Reservierungen frei (nach Rueckfrage bei Stripe), zieht # faellige Raten ein, trennt Zahlungsmethoden nach der letzten Rate, # versendet den E-Mail-Ausgang. Idempotent; Forbid verhindert # ueberlappende Laeufe zusaetzlich. apiVersion: batch/v1 kind: CronJob metadata: name: artista-modelcamp-worker spec: schedule: "*/10 * * * *" timeZone: "Europe/Berlin" concurrencyPolicy: Forbid startingDeadlineSeconds: 300 successfulJobsHistoryLimit: 3 failedJobsHistoryLimit: 5 jobTemplate: spec: backoffLimit: 1 activeDeadlineSeconds: 900 template: metadata: labels: artista/db-client: "true" spec: restartPolicy: Never automountServiceAccountToken: false securityContext: runAsNonRoot: true runAsUser: 1001 seccompProfile: { type: RuntimeDefault } containers: - name: worker image: artista-modelcamp-tools command: ["tsx", "scripts/worker.ts"] envFrom: - configMapRef: { name: artista-modelcamp-config } - secretRef: { name: artista-modelcamp-secrets } resources: requests: { cpu: 50m, memory: 192Mi } limits: { memory: 512Mi } securityContext: allowPrivilegeEscalation: false capabilities: { drop: ["ALL"] } --- # --- Sicherung ---------------------------------------------------------------- # Taeglicher pg_dump mit 30 Tagen Aufbewahrung. Das Backup-Volume darf ein # Netzwerkspeicher (NFS/SMB) sein. Zusaetzlich regelmaessig aus dem Cluster # kopieren (docs/BETRIEB.md, "Sicherung und Wiederherstellung"). apiVersion: v1 kind: PersistentVolumeClaim metadata: name: artista-modelcamp-db-backup spec: accessModes: ["ReadWriteOnce"] # storageClassName: smb-backup resources: requests: storage: 10Gi --- apiVersion: batch/v1 kind: CronJob metadata: name: artista-modelcamp-db-backup spec: schedule: "15 3 * * *" timeZone: "Europe/Berlin" concurrencyPolicy: Forbid failedJobsHistoryLimit: 5 jobTemplate: spec: backoffLimit: 2 template: metadata: labels: artista/db-client: "true" spec: restartPolicy: OnFailure automountServiceAccountToken: false securityContext: runAsNonRoot: true runAsUser: 70 runAsGroup: 70 fsGroup: 70 seccompProfile: { type: RuntimeDefault } containers: - name: pg-dump # Gleiche Hauptversion wie der Server. image: postgres:18.1-alpine env: - name: PGPASSWORD valueFrom: secretKeyRef: { name: artista-modelcamp-secrets, key: DB_PASSWORD } command: ["/bin/sh", "-c"] args: - | set -eu set -o pipefail target="/backup/artista-$(date -u +%Y-%m-%d_%H%M).dump" # Custom-Format: komprimiert, selektiv wiederherstellbar. pg_dump --host=artista-modelcamp-postgres --username=artista --dbname=artista \ --format=custom --file="${target}.partial" mv "${target}.partial" "${target}" find /backup -name 'artista-*.dump.partial' -mtime +1 -delete find /backup -name 'artista-*.dump' -mtime +30 -delete ls -lh /backup resources: requests: { cpu: 50m, memory: 128Mi } limits: { memory: 256Mi } securityContext: allowPrivilegeEscalation: false capabilities: { drop: ["ALL"] } volumeMounts: - name: backup mountPath: /backup volumes: - name: backup persistentVolumeClaim: { claimName: artista-modelcamp-db-backup }