--- # VAPORA Backup CronJobs # Automated hourly database backups and daily config backups # Uses scripts/backup/*.nu for backup execution apiVersion: v1 kind: ServiceAccount metadata: name: vapora-backup namespace: vapora --- # RBAC for backup operations (read-only access to resources) apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: vapora-backup-read rules: - apiGroups: [""] resources: - configmaps - secrets - services verbs: ["get", "list"] - apiGroups: ["apps"] resources: - deployments - statefulsets - daemonsets verbs: ["get", "list"] - apiGroups: ["networking.k8s.io"] resources: - ingresses verbs: ["get", "list"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: vapora-backup-read-binding roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: vapora-backup-read subjects: - kind: ServiceAccount name: vapora-backup namespace: vapora --- # Hourly S3 + Restic Database Backup # Exports SurrealDB and backs up to both S3 and Restic apiVersion: batch/v1 kind: CronJob metadata: name: vapora-backup-database-hourly namespace: vapora labels: app: vapora component: backup schedule: hourly spec: # Every hour at minute 0 schedule: "0 * * * *" concurrencyPolicy: Forbid successfulJobsHistoryLimit: 3 failedJobsHistoryLimit: 3 jobTemplate: metadata: labels: app: vapora backup-type: database spec: backoffLimit: 1 activeDeadlineSeconds: 1800 # 30 minutes timeout template: metadata: labels: app: vapora job-type: backup spec: serviceAccountName: vapora-backup restartPolicy: Never containers: - name: backup image: ghcr.io/vapora/vapora-backup-tools:latest imagePullPolicy: IfNotPresent env: # SurrealDB connection - name: SURREAL_URL value: "ws://surrealdb:8000" - name: SURREAL_USER value: "root" - name: SURREAL_PASS valueFrom: secretKeyRef: name: vapora-secrets key: surreal_password # S3 Configuration - name: S3_BUCKET valueFrom: configMapKeyRef: name: vapora-config key: backup_s3_bucket - name: S3_PREFIX value: "backups/database" - name: AWS_REGION valueFrom: configMapKeyRef: name: vapora-config key: aws_region # S3 Credentials - name: AWS_ACCESS_KEY_ID valueFrom: secretKeyRef: name: vapora-aws-credentials key: access_key_id - name: AWS_SECRET_ACCESS_KEY valueFrom: secretKeyRef: name: vapora-aws-credentials key: secret_access_key # Encryption - name: ENCRYPTION_KEY_PATH value: "/etc/backup-keys/encryption.key" # Restic Configuration - name: RESTIC_REPO valueFrom: configMapKeyRef: name: vapora-config key: restic_repo - name: RESTIC_PASSWORD valueFrom: secretKeyRef: name: vapora-secrets key: restic_password volumeMounts: - name: encryption-key mountPath: /etc/backup-keys readOnly: true - name: backup-cache mountPath: /tmp/backup # Resource limits for backup job resources: requests: cpu: "500m" memory: "512Mi" limits: cpu: "2000m" memory: "2Gi" # Run backup orchestrator command: - /bin/bash - -c - | nu /scripts/orchestrate-backup-recovery.nu \ --operation backup \ --mode full \ --surreal-url "$SURREAL_URL" \ --surreal-user "$SURREAL_USER" \ --surreal-pass "$SURREAL_PASS" \ --s3-bucket "$S3_BUCKET" \ --s3-prefix "$S3_PREFIX" \ --encryption-key "$ENCRYPTION_KEY_PATH" \ --restic-repo "$RESTIC_REPO" \ --restic-password "$RESTIC_PASSWORD" \ --iac-dir "provisioning" volumes: - name: encryption-key secret: secretName: vapora-encryption-key defaultMode: 0400 - name: backup-cache emptyDir: sizeLimit: 5Gi --- # Daily Configuration Backup # Backs up ConfigMaps, Secrets, and Deployments to S3 and Restic apiVersion: batch/v1 kind: CronJob metadata: name: vapora-backup-config-daily namespace: vapora labels: app: vapora component: backup schedule: daily spec: # Every day at 02:00 UTC schedule: "0 2 * * *" concurrencyPolicy: Forbid successfulJobsHistoryLimit: 3 failedJobsHistoryLimit: 3 jobTemplate: metadata: labels: app: vapora backup-type: config spec: backoffLimit: 1 activeDeadlineSeconds: 3600 # 60 minutes timeout template: metadata: labels: app: vapora job-type: backup spec: serviceAccountName: vapora-backup restartPolicy: Never containers: - name: backup image: ghcr.io/vapora/vapora-backup-tools:latest imagePullPolicy: IfNotPresent env: - name: NAMESPACE value: "vapora" - name: S3_BUCKET valueFrom: configMapKeyRef: name: vapora-config key: backup_s3_bucket - name: S3_PREFIX value: "backups/config" - name: AWS_REGION valueFrom: configMapKeyRef: name: vapora-config key: aws_region - name: AWS_ACCESS_KEY_ID valueFrom: secretKeyRef: name: vapora-aws-credentials key: access_key_id - name: AWS_SECRET_ACCESS_KEY valueFrom: secretKeyRef: name: vapora-aws-credentials key: secret_access_key volumeMounts: - name: backup-cache mountPath: /tmp/backup resources: requests: cpu: "250m" memory: "256Mi" limits: cpu: "1000m" memory: "1Gi" command: - /bin/bash - -c - | nu /scripts/backup/config-backup.nu \ --namespace "$NAMESPACE" \ --s3-bucket "$S3_BUCKET" \ --s3-prefix "$S3_PREFIX" volumes: - name: backup-cache emptyDir: sizeLimit: 2Gi --- # Daily Backup Health Verification # Checks backup integrity and freshness apiVersion: batch/v1 kind: CronJob metadata: name: vapora-backup-health-check namespace: vapora labels: app: vapora component: backup schedule: daily spec: # Every day at 03:00 UTC schedule: "0 3 * * *" concurrencyPolicy: Replace successfulJobsHistoryLimit: 7 failedJobsHistoryLimit: 7 jobTemplate: metadata: labels: app: vapora job-type: health-check spec: backoffLimit: 0 activeDeadlineSeconds: 900 # 15 minutes timeout template: metadata: labels: app: vapora job-type: backup-verification spec: serviceAccountName: vapora-backup restartPolicy: Never containers: - name: verify image: ghcr.io/vapora/vapora-backup-tools:latest imagePullPolicy: IfNotPresent env: - name: S3_BUCKET valueFrom: configMapKeyRef: name: vapora-config key: backup_s3_bucket - name: RESTIC_REPO valueFrom: configMapKeyRef: name: vapora-config key: restic_repo - name: RESTIC_PASSWORD valueFrom: secretKeyRef: name: vapora-secrets key: restic_password - name: SURREAL_URL value: "ws://surrealdb:8000" - name: SURREAL_USER value: "root" - name: SURREAL_PASS valueFrom: secretKeyRef: name: vapora-secrets key: surreal_password - name: AWS_REGION valueFrom: configMapKeyRef: name: vapora-config key: aws_region - name: AWS_ACCESS_KEY_ID valueFrom: secretKeyRef: name: vapora-aws-credentials key: access_key_id - name: AWS_SECRET_ACCESS_KEY valueFrom: secretKeyRef: name: vapora-aws-credentials key: secret_access_key resources: requests: cpu: "200m" memory: "256Mi" limits: cpu: "500m" memory: "512Mi" command: - /bin/bash - -c - | nu /scripts/verify-backup-health.nu \ --s3-bucket "$S3_BUCKET" \ --s3-prefix "backups/database" \ --restic-repo "$RESTIC_REPO" \ --restic-password "$RESTIC_PASSWORD" \ --surreal-url "$SURREAL_URL" \ --surreal-user "$SURREAL_USER" \ --surreal-pass "$SURREAL_PASS" \ --max-age-hours 25 --- # Monthly Backup Rotation # Cleans up old snapshots and archives to cold storage apiVersion: batch/v1 kind: CronJob metadata: name: vapora-backup-rotation-monthly namespace: vapora labels: app: vapora component: backup schedule: monthly spec: # First day of month at 04:00 UTC schedule: "0 4 1 * *" concurrencyPolicy: Forbid successfulJobsHistoryLimit: 3 failedJobsHistoryLimit: 3 jobTemplate: metadata: labels: app: vapora job-type: rotation spec: backoffLimit: 1 activeDeadlineSeconds: 3600 template: metadata: labels: app: vapora job-type: backup-rotation spec: serviceAccountName: vapora-backup restartPolicy: Never containers: - name: rotation image: ghcr.io/vapora/vapora-backup-tools:latest imagePullPolicy: IfNotPresent env: - name: RESTIC_REPO valueFrom: configMapKeyRef: name: vapora-config key: restic_repo - name: RESTIC_PASSWORD valueFrom: secretKeyRef: name: vapora-secrets key: restic_password - name: S3_BUCKET valueFrom: configMapKeyRef: name: vapora-config key: backup_s3_bucket - name: AWS_REGION valueFrom: configMapKeyRef: name: vapora-config key: aws_region - name: AWS_ACCESS_KEY_ID valueFrom: secretKeyRef: name: vapora-aws-credentials key: access_key_id - name: AWS_SECRET_ACCESS_KEY valueFrom: secretKeyRef: name: vapora-aws-credentials key: secret_access_key resources: requests: cpu: "200m" memory: "256Mi" limits: cpu: "1000m" memory: "1Gi" command: - /bin/bash - -c - | # Cleanup old Restic snapshots RESTIC_PASSWORD="$RESTIC_PASSWORD" \ restic -r "$RESTIC_REPO" forget \ --keep-daily 7 \ --keep-weekly 4 \ --keep-monthly 12 \ --prune