# ============================================================================= # P6.3 — backup do Postgres COMPARTILHADO (P4.7), onde nasce todo tenant novo. # # O `50-backup.yaml` copia o Postgres PROPRIO da plataforma (`athleticmap` e # `keycloak`). O `pg-compartilhado` — um database `athleticmap_` por # cliente, criado pelo provisionamento da fase 5 — nao tinha backup nenhum: # todo cliente que assinasse pela landing page teria os dados num unico volume # `local-path`, sem copia. # # `DATABASES=auto:athleticmap_` descobre os bancos NA HORA de cada rodada: um # tenant criado hoje entra no backup de amanha sem ninguem editar esta lista. # Provado por `scripts/backup/verificar_backup_compartilhado.sh` (inclusive que # `athleticmapx` nao entra — o `_` do prefixo e literal). # # ── A RETENCAO E A ELIMINACAO (art. 16 da LGPD) ────────────────────────────── # # RETENCAO_DIAS=7, igual ao 50-backup. E isto que faz o direito de eliminacao # alcancar o backup: um titular eliminado hoje some do ultimo backup que o # continha em no maximo 7 dias, quando o arquivo expira — sem ninguem abrir e # editar backup cifrado. Ver docs/p63-politica-de-retencao.md. # # ── ANTES DE APLICAR ───────────────────────────────────────────────────────── # # 1. A imagem `atm-pg-backup:1.3` tem o `backup_tenant.sh` com `auto:` e ainda # NAO FOI CONSTRUIDA. Construir de scripts/backup/Containerfile e importar no # k3s, como a 1.0. Com a 1.0, o CronJob falharia: ela nao entende `auto:`. # 2. Zero tenants no compartilhado faz o job terminar com ERRO, de proposito # (prefixo errado tambem daria zero, e "sucesso sem copiar nada" e pior que # falha). Num cluster sem nenhum tenant compartilhado ainda, nao aplique. # ============================================================================= apiVersion: v1 kind: PersistentVolumeClaim metadata: name: pg-backups-compartilhado namespace: plataforma-prod spec: accessModes: [ReadWriteOnce] storageClassName: local-path resources: requests: # 7 dias de dumps comprimidos. Um tenant consolidado tem poucos MB; 10 Gi # cobre dezenas de clientes com folga. Revisar quando passar de 50. storage: 10Gi --- apiVersion: batch/v1 kind: CronJob metadata: name: pg-backup-compartilhado namespace: plataforma-prod spec: # 02:30: meia hora depois do backup do Postgres proprio, para os dois dumps nao # disputarem disco e CPU do mesmo no. schedule: "30 2 * * *" successfulJobsHistoryLimit: 3 failedJobsHistoryLimit: 3 concurrencyPolicy: Forbid jobTemplate: spec: template: spec: restartPolicy: OnFailure containers: - name: pg-backup image: docker.io/library/atm-pg-backup:1.3 env: # O admin do compartilhado: e o unico papel que le TODOS os # databases. O `r_` de cada cliente so ve o proprio. - name: PGPASSWORD valueFrom: secretKeyRef: { name: pg-compartilhado-admin, key: password } - name: CHAVE_PUBLICA valueFrom: configMapKeyRef: { name: pg-backup-chave, key: destinatario } # Direto no Postgres, e nao no PgBouncer: ver o 50-backup. - { name: PGHOST, value: "pg-compartilhado" } - { name: PGUSER, value: "postgres" } - { name: DATABASES, value: "auto:athleticmap_" } - { name: DESTINO, value: "/backups" } - { name: RETENCAO_DIAS, value: "7" } # O envio para fora. O script se cala quando o Secret nao existe, e # falha ALTO quando existe e nao funciona — sao coisas diferentes, # e um CronJob que falha toda madrugada por falta de credencial # ensina todo mundo a ignorar o unico alerta que nao se pode ignorar. - { name: COMANDO_ENVIO, value: "enviar_offsite.sh {}" } # Um prefixo POR ORIGEM no bucket: sem ele todos gravavam em `athleticmap/` com o # mesmo nome de arquivo — nao se sabia de quem era o dump, e dois no mesmo segundo # se sobrescreveriam (visto em 01/10/2026). - { name: OFFSITE_PREFIXO, value: "athleticmap/plataforma-compartilhado" } # `optional: true`: enquanto o Secret `backup-offsite` nao existir, o # CronJob roda igual e guarda local. No dia em que ele existir, o # envio liga sozinho — sem tocar neste arquivo. envFrom: - secretRef: { name: backup-offsite, optional: true } command: ["backup_tenant.sh"] volumeMounts: - { name: backups, mountPath: /backups } volumes: - name: backups persistentVolumeClaim: claimName: pg-backups-compartilhado