Pular para conteúdo

Manter o usuário do banco sincronizado

Usuários do banco cria o usuário tenax uma vez. Isso basta para a maioria dos ambientes. Esta página é para ambientes GitOps em que o usuário precisa continuar batendo com o seu Secret sem que ninguém execute nada:

  • a senha do Secret é rotacionada;
  • o banco é recriado ou restaurado de um dump mais antigo, e o usuário some;
  • alguém altera o usuário à mão (remove um grant, muda a senha);
  • o banco é criado depois dos manifestos que descrevem o usuário dele (um cluster novo).

Um Job avulso não resolve nenhum desses casos: ele roda uma vez, e o Kubernetes não executa de novo um Job concluído quando o Secret muda. Um CronJob que reaplica o mesmo SQL idempotente resolve: dentro de um período, o usuário volta a bater com o Secret.

Como funciona

Um CronJob por banco, no namespace do banco. Cada execução:

  1. espera até o banco responder (limitado pelo deadline do Job);
  2. faz login como administrador, usando o Secret que o próprio banco usa;
  3. cria o usuário tenax se ele não existir e define a senha e os grants dele a partir do Secret do Tenax.

Todas as instruções são idempotentes, então uma execução que encontra tudo no lugar não altera nada. As senhas chegam ao contêiner como variáveis de ambiente vindas de Secrets; nunca aparecem em uma linha de comando nem em um log.

PostgreSQL

Substitua os quatro valores marcados com # ←: o namespace, o Service do banco, o banco e o Secret e a chave que guardam a senha do administrador.

apiVersion: batch/v1
kind: CronJob
metadata:
  name: tenax-user-postgres
  namespace: team-a                                   # ←
spec:
  schedule: "*/30 * * * *"
  concurrencyPolicy: Forbid
  successfulJobsHistoryLimit: 1
  failedJobsHistoryLimit: 3
  jobTemplate:
    spec:
      backoffLimit: 2
      activeDeadlineSeconds: 300
      template:
        spec:
          restartPolicy: OnFailure
          automountServiceAccountToken: false
          securityContext: { runAsNonRoot: true, runAsUser: 999, runAsGroup: 999 }
          containers:
            - name: psql
              image: docker.io/library/postgres:16
              resources: { requests: { cpu: 10m, memory: 32Mi }, limits: { memory: 128Mi } }
              securityContext: { allowPrivilegeEscalation: false, capabilities: { drop: [ALL] } }
              env:
                - { name: PGHOST, value: postgres }                     # ← the database's Service
                - { name: PGDATABASE, value: app }                      # ← any database on the server
                - { name: PGUSER, value: postgres }
                - { name: PGCONNECT_TIMEOUT, value: "10" }
                - name: PGPASSWORD
                  valueFrom: { secretKeyRef: { name: postgres-admin, key: password } }   # ←
                - name: TENAX_PASSWORD
                  valueFrom: { secretKeyRef: { name: tenax-postgres, key: password } }
              command: [sh, -ec]
              args:
                - |
                  until pg_isready -q; do echo "waiting for $PGHOST"; sleep 5; done
                  psql -v ON_ERROR_STOP=1 -X <<'SQL'
                  \getenv tenax_password TENAX_PASSWORD
                  SELECT 'CREATE ROLE tenax LOGIN REPLICATION' WHERE NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'tenax') \gexec
                  ALTER ROLE tenax WITH LOGIN REPLICATION NOSUPERUSER NOCREATEDB NOCREATEROLE PASSWORD :'tenax_password';
                  GRANT pg_read_all_data, pg_monitor TO tenax;
                  ALTER ROLE tenax SET idle_in_transaction_session_timeout = '10min';
                  SQL
                  echo "tenax user in sync on $PGHOST"

\getenv exige psql 15 ou posterior na imagem; o servidor pode ser qualquer versão suportada. Defina max_slot_wal_keep_size na configuração do servidor (por exemplo -c max_slot_wal_keep_size=2GB), não aqui: é uma configuração do servidor, não faz parte do usuário.

MySQL

apiVersion: batch/v1
kind: CronJob
metadata:
  name: tenax-user-mysql
  namespace: team-a                                   # ←
spec:
  schedule: "*/30 * * * *"
  concurrencyPolicy: Forbid
  successfulJobsHistoryLimit: 1
  failedJobsHistoryLimit: 3
  jobTemplate:
    spec:
      backoffLimit: 2
      activeDeadlineSeconds: 300
      template:
        spec:
          restartPolicy: OnFailure
          automountServiceAccountToken: false
          securityContext: { runAsNonRoot: true, runAsUser: 999, runAsGroup: 999 }
          containers:
            - name: mysql
              image: docker.io/library/mysql:8.4
              resources: { requests: { cpu: 10m, memory: 64Mi }, limits: { memory: 256Mi } }
              securityContext: { allowPrivilegeEscalation: false, capabilities: { drop: [ALL] } }
              env:
                - { name: MYSQL_HOST, value: mysql }                    # ← the database's Service
                - name: ROOT_PASSWORD
                  valueFrom: { secretKeyRef: { name: mysql-admin, key: root-password } }  # ←
                - name: TENAX_PASSWORD
                  valueFrom: { secretKeyRef: { name: tenax-mysql, key: password } }
              command: [bash, -ec]
              args:
                - |
                  umask 077
                  printf '[client]\nuser=root\npassword=%s\nhost=%s\nconnect-timeout=10\n' "$ROOT_PASSWORD" "$MYSQL_HOST" > /tmp/root.cnf
                  until mysqladmin --defaults-extra-file=/tmp/root.cnf ping --silent; do echo "waiting for $MYSQL_HOST"; sleep 5; done
                  mysql --defaults-extra-file=/tmp/root.cnf <<SQL
                  CREATE USER IF NOT EXISTS 'tenax'@'%' IDENTIFIED BY '${TENAX_PASSWORD}';
                  ALTER USER 'tenax'@'%' IDENTIFIED BY '${TENAX_PASSWORD}';
                  GRANT SELECT, SHOW VIEW, TRIGGER, EVENT, LOCK TABLES, PROCESS, RELOAD, SHOW_ROUTINE,
                        REPLICATION CLIENT, REPLICATION SLAVE ON *.* TO 'tenax'@'%';
                  SQL
                  rm -f /tmp/root.cnf
                  echo "tenax user in sync on $MYSQL_HOST"

A senha entra no SQL sem escape além do literal de string, então gere-a em hexadecimal (openssl rand -hex 24).

Executar agora

kubectl -n team-a create job --from=cronjob/tenax-user-postgres tenax-user-now
kubectl -n team-a logs -f job/tenax-user-now

A cada 30 minutos é mais que suficiente: o próprio Tenax tenta de novo um login recusado e o reporta no alvo, então uma rotação é aplicada no máximo um período depois. A regra do pg_hba.conf e as configurações do binlog continuam sendo configuração do servidor (Usuários do banco, passo 3); este CronJob não mexe nelas.