diff --git a/tenants/acme/50-backup.yaml b/tenants/acme/50-backup.yaml index 8ae637b..34548a9 100644 --- a/tenants/acme/50-backup.yaml +++ b/tenants/acme/50-backup.yaml @@ -11,13 +11,30 @@ spec: requests: storage: 5Gi --- +# ConfigMap com o DESTINATARIO age (chave PUBLICA). +# +# Publica pode ser versionada: ela so cifra. Quem tem a publica NAO abre o +# backup. A privada e gerada por scripts/backup/gerar_chave_backup.sh na maquina +# de quem restaura, e NUNCA chega neste repositorio nem no VPS. +# +# O valor abaixo e um marcador: substitua pelo destinatario real antes de +# aplicar. Com o marcador, o backup_tenant.sh recusa rodar em vez de gravar em +# claro — e o comportamento certo. +apiVersion: v1 +kind: ConfigMap +metadata: + name: pg-backup-chave + namespace: acme-prod +data: + destinatario: "SUBSTITUA_PELO_DESTINATARIO_age1" +--- apiVersion: batch/v1 kind: CronJob metadata: name: pg-backup namespace: acme-prod spec: - schedule: "0 2 * * *" + schedule: "0 2 * * *" # diariamente 02:00 successfulJobsHistoryLimit: 3 failedJobsHistoryLimit: 3 concurrencyPolicy: Forbid @@ -27,30 +44,36 @@ spec: spec: restartPolicy: OnFailure containers: + # Imagem propria: postgres:16 + age. O dump precisa ser cifrado + # ANTES de sair do processo, entao as duas ferramentas tem de estar + # no mesmo container. Construida de scripts/backup/Containerfile. - name: pg-backup - image: postgres:16 + image: docker.io/library/atm-pg-backup:1.0 env: - name: PGPASSWORD valueFrom: - secretKeyRef: - name: db-credentials - key: password - command: ["/bin/sh", "-c"] - args: - - | - set -eo pipefail - echo "aguardando postgres ficar acessivel..." - for i in $(seq 1 30); do pg_isready -h postgres -U atm -t 3 >/dev/null 2>&1 && break; sleep 2; done - TS=$(date +%Y%m%d-%H%M%S) - for DB in athleticmap keycloak; do - echo "dump $DB ..." - pg_dump -h postgres -U atm -d "$DB" | gzip > "/backups/${DB}-${TS}.sql.gz" - done - find /backups -name '*.sql.gz' -mtime +7 -delete - echo "backups atuais:"; ls -lh /backups | tail -20 + secretKeyRef: { name: db-credentials, key: password } + - name: CHAVE_PUBLICA + valueFrom: + configMapKeyRef: { name: pg-backup-chave, key: destinatario } + - { name: PGHOST, value: "postgres" } + - { name: PGUSER, value: "atm" } + - { name: DATABASES, value: "athleticmap keycloak" } + - { 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 {}" } + # `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 + - { name: backups, mountPath: /backups } volumes: - name: backups persistentVolumeClaim: diff --git a/tenants/demo/50-backup.yaml b/tenants/demo/50-backup.yaml index 8433f68..e4eba65 100644 --- a/tenants/demo/50-backup.yaml +++ b/tenants/demo/50-backup.yaml @@ -11,13 +11,30 @@ spec: requests: storage: 5Gi --- +# ConfigMap com o DESTINATARIO age (chave PUBLICA). +# +# Publica pode ser versionada: ela so cifra. Quem tem a publica NAO abre o +# backup. A privada e gerada por scripts/backup/gerar_chave_backup.sh na maquina +# de quem restaura, e NUNCA chega neste repositorio nem no VPS. +# +# O valor abaixo e um marcador: substitua pelo destinatario real antes de +# aplicar. Com o marcador, o backup_tenant.sh recusa rodar em vez de gravar em +# claro — e o comportamento certo. +apiVersion: v1 +kind: ConfigMap +metadata: + name: pg-backup-chave + namespace: demo-prod +data: + destinatario: "SUBSTITUA_PELO_DESTINATARIO_age1" +--- apiVersion: batch/v1 kind: CronJob metadata: name: pg-backup namespace: demo-prod spec: - schedule: "0 2 * * *" + schedule: "0 2 * * *" # diariamente 02:00 successfulJobsHistoryLimit: 3 failedJobsHistoryLimit: 3 concurrencyPolicy: Forbid @@ -27,30 +44,36 @@ spec: spec: restartPolicy: OnFailure containers: + # Imagem propria: postgres:16 + age. O dump precisa ser cifrado + # ANTES de sair do processo, entao as duas ferramentas tem de estar + # no mesmo container. Construida de scripts/backup/Containerfile. - name: pg-backup - image: postgres:16 + image: docker.io/library/atm-pg-backup:1.0 env: - name: PGPASSWORD valueFrom: - secretKeyRef: - name: db-credentials - key: password - command: ["/bin/sh", "-c"] - args: - - | - set -eo pipefail - echo "aguardando postgres ficar acessivel..." - for i in $(seq 1 30); do pg_isready -h postgres -U atm -t 3 >/dev/null 2>&1 && break; sleep 2; done - TS=$(date +%Y%m%d-%H%M%S) - for DB in athleticmap keycloak; do - echo "dump $DB ..." - pg_dump -h postgres -U atm -d "$DB" | gzip > "/backups/${DB}-${TS}.sql.gz" - done - find /backups -name '*.sql.gz' -mtime +7 -delete - echo "backups atuais:"; ls -lh /backups | tail -20 + secretKeyRef: { name: db-credentials, key: password } + - name: CHAVE_PUBLICA + valueFrom: + configMapKeyRef: { name: pg-backup-chave, key: destinatario } + - { name: PGHOST, value: "postgres" } + - { name: PGUSER, value: "atm" } + - { name: DATABASES, value: "athleticmap keycloak" } + - { 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 {}" } + # `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 + - { name: backups, mountPath: /backups } volumes: - name: backups persistentVolumeClaim: diff --git a/tenants/piloto/50-backup.yaml b/tenants/piloto/50-backup.yaml index 6128be0..11ee6fd 100644 --- a/tenants/piloto/50-backup.yaml +++ b/tenants/piloto/50-backup.yaml @@ -11,6 +11,23 @@ spec: requests: storage: 5Gi --- +# ConfigMap com o DESTINATARIO age (chave PUBLICA). +# +# Publica pode ser versionada: ela so cifra. Quem tem a publica NAO abre o +# backup. A privada e gerada por scripts/backup/gerar_chave_backup.sh na maquina +# de quem restaura, e NUNCA chega neste repositorio nem no VPS. +# +# O valor abaixo e um marcador: substitua pelo destinatario real antes de +# aplicar. Com o marcador, o backup_tenant.sh recusa rodar em vez de gravar em +# claro — e o comportamento certo. +apiVersion: v1 +kind: ConfigMap +metadata: + name: pg-backup-chave + namespace: piloto-prod +data: + destinatario: "SUBSTITUA_PELO_DESTINATARIO_age1" +--- apiVersion: batch/v1 kind: CronJob metadata: @@ -27,30 +44,36 @@ spec: spec: restartPolicy: OnFailure containers: + # Imagem propria: postgres:16 + age. O dump precisa ser cifrado + # ANTES de sair do processo, entao as duas ferramentas tem de estar + # no mesmo container. Construida de scripts/backup/Containerfile. - name: pg-backup - image: postgres:16 + image: docker.io/library/atm-pg-backup:1.0 env: - name: PGPASSWORD valueFrom: - secretKeyRef: - name: db-credentials - key: password - command: ["/bin/sh", "-c"] - args: - - | - set -eo pipefail - echo "aguardando postgres ficar acessivel..." - for i in $(seq 1 30); do pg_isready -h postgres -U atm -t 3 >/dev/null 2>&1 && break; sleep 2; done - TS=$(date +%Y%m%d-%H%M%S) - for DB in athleticmap keycloak; do - echo "dump $DB ..." - pg_dump -h postgres -U atm -d "$DB" | gzip > "/backups/${DB}-${TS}.sql.gz" - done - find /backups -name '*.sql.gz' -mtime +7 -delete - echo "backups atuais:"; ls -lh /backups | tail -20 + secretKeyRef: { name: db-credentials, key: password } + - name: CHAVE_PUBLICA + valueFrom: + configMapKeyRef: { name: pg-backup-chave, key: destinatario } + - { name: PGHOST, value: "postgres" } + - { name: PGUSER, value: "atm" } + - { name: DATABASES, value: "athleticmap keycloak" } + - { 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 {}" } + # `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 + - { name: backups, mountPath: /backups } volumes: - name: backups persistentVolumeClaim: diff --git a/tenants/plataforma/50-backup.yaml b/tenants/plataforma/50-backup.yaml index 97f669a..1b129c8 100644 --- a/tenants/plataforma/50-backup.yaml +++ b/tenants/plataforma/50-backup.yaml @@ -65,15 +65,36 @@ spec: - name: CHAVE_PUBLICA valueFrom: configMapKeyRef: { name: pg-backup-chave, key: destinatario } + # ── QUANDO ESTE TENANT MIGRAR PARA O BANCO COMPARTILHADO (P4.7) + # + # Estas tres linhas mudam JUNTO com a migracao, e nao antes: + # + # PGHOST postgres -> pg-compartilhado + # PGUSER atm -> r_ + # DATABASES athleticmap ... -> athleticmap_ + # + # O `keycloak` sai da lista se o Keycloak tambem for compartilhado + # (P4.8, card 156) — ate la ele continua no banco do tenant. + # + # E `PGHOST` aponta para o POSTGRES, e nao para o PgBouncer: o + # `pg_dump` abre uma transacao unica e longa, que e exatamente o + # que transaction pooling nao serve. Pelo bouncer, um dump grande + # seguraria uma conexao do pool pelo tempo inteiro do backup. - { name: PGHOST, value: "postgres" } - { name: PGUSER, value: "atm" } - { name: DATABASES, value: "athleticmap keycloak" } - { name: DESTINO, value: "/backups" } - { name: RETENCAO_DIAS, value: "7" } - # COMANDO_ENVIO fica vazio ate haver credencial de armazenamento - # externo. Sem ele o backup fica cifrado, mas NO MESMO SERVIDOR — - # e o proprio script avisa isso no fim da execucao. - - { name: COMANDO_ENVIO, value: "" } + # 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 {}" } + # `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 } diff --git a/tenants/plataforma/51-backup-compartilhado.yaml b/tenants/plataforma/51-backup-compartilhado.yaml index 1218299..889d11f 100644 --- a/tenants/plataforma/51-backup-compartilhado.yaml +++ b/tenants/plataforma/51-backup-compartilhado.yaml @@ -21,7 +21,7 @@ # # ── ANTES DE APLICAR ───────────────────────────────────────────────────────── # -# 1. A imagem `atm-pg-backup:1.1` tem o `backup_tenant.sh` com `auto:` e ainda +# 1. A imagem `atm-pg-backup:1.2` 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 @@ -61,7 +61,7 @@ spec: restartPolicy: OnFailure containers: - name: pg-backup - image: docker.io/library/atm-pg-backup:1.1 + image: docker.io/library/atm-pg-backup:1.2 env: # O admin do compartilhado: e o unico papel que le TODOS os # databases. O `r_` de cada cliente so ve o proprio. @@ -77,9 +77,16 @@ spec: - { name: DATABASES, value: "auto:athleticmap_" } - { name: DESTINO, value: "/backups" } - { name: RETENCAO_DIAS, value: "7" } - # Vazio pelo mesmo motivo do 50-backup: falta credencial de - # armazenamento externo. O script avisa no fim de cada rodada. - - { name: COMANDO_ENVIO, value: "" } + # 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 {}" } + # `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 }