Migrar production para o EKS com convergência de dados para a ibft-vpc
TLDR: Move o compute de production das 6 EC2 para o EKS e converge os dados para o modelo do projeto (Aurora + ElastiCache na ibft-vpc, subnets privadas), com carga via
pg_dumpem janela curta e cutover por DNS.
Contexto
Staging já roda 100% no EKS (pipelines v2/v3 + ward + terraform workspace). Production ainda roda em 6 EC2 t4g.medium com supervisor, deploy via SSH na branch production. A auditoria (fase 0, 2026-08-06) mostrou que os dados já estão na AWS, conta 675845727614, mas na VPC legada vpc-01c1b3deba1dd05a5 (172.31.0.0/16), fora do modelo do projeto:
- Aurora PostgreSQL 17.7
onion-db-production(writer + readerdb.t4g.medium) atrás do RDS Proxyonion-proxy(writer + endpoint read-only), dbonion, 1.5 GB - ElastiCache Serverless Valkey 8
onion(rediss, ~139k chaves, snapshot retention 0) - SQS com filas
onion-production*— serviço regional, não preso à VPC - Segredos no SSM
/ONION/PRODUCTION/BACKEND/(24 params) viaconfig/ssm_loader.py+ instance role
Decisão: convergir para o modelo do projeto — Aurora e ElastiCache novos na ibft-vpc (vpc-0047c8135a4f0a0c4, subnets privadas, peering com o EKS já ativo), como staging. Evita peering/SGs de transição para a VPC legada. O SQS é reusado como está (regional). O banco pequeno (1.5 GB) torna a janela de migração curta.
Mecanismo de carga: pg_dump | psql (padrão já provado na migração de staging), não snapshot/restore. Motivo: restore de snapshot Aurora cria um cluster novo a cada sync — o cluster do terraform não poderia ser criado/validado antes da janela. Com pg_dump, o cluster de production nasce vazio pelo terraform, é ensaiado com dados reais dias antes (dump do endpoint read-only, sem impacto), e a janela final é só o dump+load (~10 min para 1.5 GB). Snapshot manual do cluster legado é tirado antes da janela como artefato de rollback.
Objetivos
- Workspace terraform
productionaplicável sem conflitar com staging - Aurora + ElastiCache de production na ibft-vpc com perfil de produção (sem auto-pause,
deletion_protection,prevent_destroy) - App sobe no k8s com o contrato de env alinhado (hoje o
production.pycrasharia) - Deploy por promoção de tag validada em staging (
stamp→delivery) - Migração de dados em janela curta e cutover por DNS, com rollback definido
Fora de escopo
Os caches checkout/websocket e o db-cluster-ibft da VPC legada são de outros serviços — não são tocados.
Mudanças
Parte 1 — Terraform: workspace production
.infra/terraform/network.tf
- Condicionar peering/rotas (EKS↔ibft-vpc) a local.environment == "staging" — já vivem no state de staging e servem às duas pontas; production não deve recriá-los
.infra/terraform/database.tf
- Perfil por ambiente no cluster Aurora:
- production: engine_version = "17.7", min_capacity = 1 (sem auto-pause), max_capacity = 8, deletion_protection = true, skip_final_snapshot = false + final_snapshot_identifier, lifecycle { prevent_destroy = true }, backup retention ≥ 7 dias
- staging mantém o perfil atual
- Adicionar instância reader em production (onion-cluster-reader-production-1) — paridade com o legado (endpoints writer/reader alimentam DATABASE_URL_FULLACCESS/READONLY)
- Decisão em aberto (não bloqueia): RDS Proxy na frente do Aurora novo. O legado usa proxy (gunicorn/gevent gera muitas conexões); staging não usa. Começar sem proxy, com CONN_MAX_AGE conservador, e adicionar aws_db_proxy se o Datadog mostrar pressão de conexões
.infra/terraform/cache.tf
- Perfil por ambiente: production com engine = "valkey" (paridade com o legado) e limites maiores (ex.: 5 GB / 5000 ECPU — validar contra o CloudWatch do cache atual antes do apply); staging mantém
.infra/terraform/secrets.tf / variables.tf
- Adicionar ao secret as chaves que faltam para o production.py: CELERY_SQS_BASE_URL, SENTRY_DSN_KEY, EMAIL_*, MANYCHAT_API_TOKEN, ENABLE_MANYCHAT_NOTIFICATIONS, UNSPLASH_*, WEBHOOKS_CHECKOUT_NOTIFICATIONS_TOKEN, ANALYTICS_S3_BUCKET (valores copiados do SSM para o ward)
- Credencial AWS para os pods: IAM user onion-backend-production (conta onion) com permissões SQS (filas onion-production*) + S3 (statics/analytics), chaves no secret como AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY (kombu e django-storages usam a cadeia default do boto)
.infra/terraform/dns.tf
- Os records api/dash/download.onovoinconsciente.com.br já existem no Cloudflare apontando para o legado: fazer terraform import no workspace production. O apply que troca o content para var.ingress_hostname é o cutover — só rodar na janela (parte 5)
Ward (infra/production.ward)
- Adicionar: TF_VAR_aws_db_password, TF_VAR_ingress_hostname, TF_VAR_celery_sqs_base_url, TF_VAR_sentry_dsn_key, TF_VAR_email_*, TF_VAR_manychat_*, TF_VAR_unsplash_*, TF_VAR_webhooks_checkout_notifications_token, TF_VAR_analytics_s3_bucket e as chaves do IAM user novo
Parte 2 — App: alinhar o contrato de env do production.py
config/settings/production.py
- Ler os nomes do contrato k8s com fallback para os nomes legados do SSM (transição segura — a EC2 continua funcionando até o desligamento):
- DATABASE_URL_FULLACCESS→DB_URL, DATABASE_URL_READONLY→DB_URL_READER, REDIS_URL→ELASTICACHE_REDIS_URL
- Firebase: aceitar FIREBASE_CREDENTIALS_JSON (contrato k8s/staging) além de FIREBASE_CREDENTIALS_PATH
- config/ssm_loader.py permanece — sem instance role no EKS ele falha com log e segue (verificar que a exceção é engolida mesmo sem credencial)
Parte 3 — Manifests: overlay production
.infra/k8s/overlays/production/
- patches/ingress.yaml: hosts api/dash/download.onovoinconsciente.com.br (espelhar staging)
- Envs que faltam nos deployments (base ou overlay): CELERY_SQS_BASE_URL, SENTRY_DSN_KEY, EMAIL_*, MANYCHAT_*, UNSPLASH_*, WEBHOOKS_*, ANALYTICS_S3_BUCKET via secret
- CELERY_QUEUES dos workers com os nomes onion-production* (conferir com as filas SQS reais)
- Réplicas iniciais de worker* e beat = 0 no overlay (sobem só no cutover — evita consumo concorrente das filas SQS e beat duplicado com o legado)
Parte 4 — CI: deploy por promoção de tag
.github/workflows/deploy-production.yml (substituir conteúdo legado)
- workflow_dispatch com input da tag já validada em staging: require-tag → stamp (target production) → delivery (environment production, environment_url https://api.onovoinconsciente.com.br, run_migrate: true), secrets explícitos como no staging
- pylint-and-tests.yml / workflows legados permanecem até o descomissionamento (parte 6)
Parte 5 — Migração de dados e cutover (janela controlada)
Ensaio (dias antes, sem impacto):
terraform applyde production (cria Aurora vazio, cache, namespace, secret; DNS ainda não flipado)- Job k8s de
pg_dumpdo endpoint read-only do proxy legado| psqlno Aurora novo (mesmo padrão da migração de staging) - Deploy da tag no k8s (workers/beat a 0), validar web via port-forward com dados reais (
/health, login no dash, smoke read-only) - Pré-check de migrations: diff entre a branch
production(código nas EC2) e a tag a ser deployada; migrations pendentes precisam ser backward-compatible
Janela:
- Snapshot manual do cluster legado (artefato de rollback)
- Flush do buffer de progresso do Valkey para o banco (mecanismo existente) e parar workers/beat/web no supervisor das EC2 (write freeze)
TRUNCATE/recreate do schema no Aurora novo +pg_dump | psqlfinal (~10 min para 1.5 GB)deliveryroda o migrate; escalar workers/beat no k8s; validar via port-forwardterraform applyque flipa o DNS para o ALB do EKS. O cache Valkey novo começa frio (aceito; o buffer foi persistido no passo 2)- Observar: Datadog/Sentry, filas SQS drenando, latência de queries
Rollback (enquanto não houver escrita significativa no Aurora novo): - Reverter DNS + religar o supervisor nas EC2 (dados legados intactos). Depois de escrita significativa, o rollback exige dump reverso — por isso a observação intensiva na primeira hora
Parte 6 — Descomissionamento (pós-observação, ≥ 48h estáveis)
- Parar gunicorn/supervisor nas EC2; terminar as instâncias após período extra
- Remover
deploy-production.ymllegado (SSH),deploy-staging-legacy.yml,pylint-and-tests.yml,scripts/deploy-*.she a branchproduction - Após o período de retenção: destruir o cluster Aurora legado
onion-db-production, o RDS Proxyonion-proxye o cache Valkeyonionda VPC legada. Manter o snapshot manual e o path do SSM por um ciclo antes de apagar - Avaliar a remoção dos fallbacks legados no
production.py
Como verificar
make terraform.production.planlimpo (sem tocar recursos de staging) eapplyverde- De um pod no namespace
onion-backend--production:psqlno Aurora novo ePINGno Valkey respondem;\dtmostra todas as tabelas após a carga - Web validado por port-forward antes do DNS; após o cutover,
api/dash/download.onovoinconsciente.com.brrespondem pelo ALB do EKS (TLS ok) - Filas SQS sendo consumidas pelos workers do k8s; beat agendando sem duplicidade; Datadog/Sentry sem novos erros; contagem de linhas por tabela bate entre legado e novo
- Rollback ensaiado: DNS revertido volta a servir pelas EC2
Documentação
- Criar learning com os gotchas da janela (write freeze, ordem workers/beat, DNS), contrato de env e sizing de Aurora/Valkey
- reference/infrastructure/database_url.md — atualizar se o contrato de
DATABASE_URLmudar