RBAC amarrado por resourceNames (db-credentials nos tenants, pgbouncer-userlist
so aqui), netpol do PgBouncer por ROTULO em vez de por nome, e a credencial de
administracao do Postgres compartilhado para o servico-captacao.
Cliente: Escolinha de Teste
Assinatura: sub_h2dtdvlbt83iq2lg
Plano: (nao informado)
Realm: athleticmap-6
Gerado pelo provisionamento automatico (P5.2/P5.3) a partir do molde
consolidado de P4.9. O ApplicationSet cria a Application sozinho pelo
gerador de diretorio sobre tenants/*.
O dominio athleticmap.com tem MX na Hostinger e SPF que so autoriza a
Hostinger. A credencial foi medida: aceita em smtp.hostinger.com:587,
recusada com 535 no Gmail e no Titan.
O molde do realm nunca tinha sido empurrado — PROVISIONAMENTO_REALM_TEMPLATE
estava vazio, e a etapa REALM_CRIADO teria parado nele logo depois do SMTP.
Cliente: (sem nome no pedido)
Assinatura: sub_60id65hukv48tshu
Plano: (nao informado)
Realm: athleticmap-7
Gerado pelo provisionamento automatico (P5.2/P5.3) a partir do molde
consolidado de P4.9. O ApplicationSet cria a Application sozinho pelo
gerador de diretorio sobre tenants/*.
Cliente: Escolinha de Teste
Assinatura: sub_h2dtdvlbt83iq2lg
Plano: (nao informado)
Realm: athleticmap-6
Gerado pelo provisionamento automatico (P5.2/P5.3) a partir do molde
consolidado de P4.9. O ApplicationSet cria a Application sozinho pelo
gerador de diretorio sobre tenants/*.
Temporario e marcado no proprio arquivo. A venda de teste exercita link,
pagamento, webhook e provisionamento, e nao faz sentido gastar R$ 170 nisso.
Sobreposicao por ambiente: tirar e remover o bloco, sem tocar no codigo.
O publicar_servico_captacao.py escreve o manifesto a partir da copia LOCAL do
athletic-map-deploy, e essa copia esta defasada. Ao subir a tag 1.6 ele levou
junto tudo o que foi ligado hoje no servidor: ASAAS_API_KEY, CAPTACAO_ORIGEM no
dominio novo e as QUATRO credenciais do provisionamento — 56 linhas.
O pod chegou a subir sem elas. E exatamente a divergencia de espelho registrada
hoje de manha, agora com consequencia medida.
Restaurado a partir do commit anterior, com a tag nova aplicada por cima.
E o mesmo Secret que o servico-financeiro ja usa — nao ha credencial nova a
selar, muda so quem a le. Os dois fluxos continuam distintos: o financeiro cobra
o ATLETA, o captacao cobra o TENANT.
optional: true de proposito. Um ambiente sem Asaas sobe do mesmo jeito, e a
venda que precisar da chave recusa nomeando a propriedade. Derrubar o pod
tiraria do ar o provisionamento e a captura de lead, que nao dependem disso.
O kubeseal devolve YAML limpo, e a rotacao do token levou junto 17 linhas que
diziam por que ha dois segredos, que o AsaasConfig recusa subir se forem iguais,
e que 15 recusas seguidas param a fila da conta no Asaas.
Entra tambem o que mudou hoje: o token do webhook nao precisa mais ser digitado
no painel, porque o script o grava pela API.
Aditiva de proposito: nenhum endereco antigo sai. Quem esta usando
<tenant>.athleticmap.influxdigital.com.br continua entrando, e o cert-manager
passa a emitir tambem o certificado de <tenant>.athleticmap.com.
Foi possivel porque o dono criou o curinga hoje: *.athleticmap.com resolve para
187.77.37.184, conferido com um nome ALEATORIO — demo. ou www. responderiam
mesmo sem curinga, e a conferencia passaria por engano.
9 arquivos, 28 trocas, 317 insercoes e 4 remocoes. As 4 sao as linhas de TLS em
fluxo, reescritas com os dois dominios na mesma lista.
Cada regra de Ingress foi DUPLICADA com os mesmos paths, e nao trocada: uma
regra com host: X so atende X.
`platform/` (ArgoCD e Grafana) fica FORA: mexer no Ingress do ArgoCD no meio de
uma migracao de dominio e perder o instrumento de rollback.
Conferido antes de aplicar: YAML valido em todos os Ingress, e ZERO host sem
entrada de TLS.
O que NAO muda aqui: ATM_ISSUER, ATM_KC_URL e as URLs do Garmin. Ninguem e
deslogado nesta fase.
O clone e feito como root e o container principal roda como o usuario app
(uid 999). Sem o chown, a copia de trabalho fica so-leitura para ele, e a etapa
MANIFESTOS_GERADOS falharia ao escrever tenants/<nome> — medido de dentro do pod
logo depois do primeiro deploy: "escrita: NEGADA".
chown, e nao fsGroup: fsGroup ajusta o GRUPO e depende de o diretorio ter
permissao de grupo, o que o git nao garante arquivo a arquivo. Entregar a posse
e explicito e nao depende de umask.
Era a primeira etapa do provisionamento e a unica bloqueada por algo MEU, nao
por credencial do dono: o pod nao enxergava o molde nem o repositorio GitOps, e
as tres propriedades ficavam vazias. Falhar fechado, nomeando a propriedade, era
o comportamento certo — o que faltava era o material chegar.
Um initContainer clona o repositorio em /gitops na subida. Dentro dele,
moldes/consolidado e o molde e tenants/<nome> e onde a etapa escreve: UM volume
serve aos dois, e por isso o molde foi para o repositorio GitOps no commit
anterior.
Tres decisoes que valem registro:
- CLONE ANONIMO. O Gitea aceita leitura sem credencial neste repositorio, e
leitura e tudo o que a etapa 1 precisa. Empurrar e a etapa ARGO_SINCRONIZADO,
que depende de credencial e continua bloqueada — esta mudanca nao a destrava e
nao tenta;
- emptyDir, e nao PVC. A copia e descartavel por desenho: se o pod morrer no
meio de um provisionamento, o que ficou pela metade some junto. Melhor do que
um clone velho e divergente sobrevivendo entre reinicios;
- a imagem e a do act_runner, que JA esta no no e tem git 2.45.2. Nao ha
registry aqui, entao construir uma imagem so para ter git custaria mais do que
reusar esta.
O endereco e o interno (gitea-http.gitea.svc:3000), o mesmo do ApplicationSet.
Depende do 19-netpol-gitea.yaml do commit anterior.
A etapa MANIFESTOS_GERADOS renderiza o molde para tenants/<nome> numa copia de
trabalho, e o pod obtem essa copia clonando o Gitea na subida. Clonar e leitura.
Escrever continua fora de alcance, de proposito: empurrar no ramo padrao E o
deploy, e depende de uma credencial que ainda nao existe.
O Gitea aceita clone ANONIMO deste repositorio (conferido com git ls-remote sem
credencial), entao a etapa 1 funciona antes de a credencial de escrita existir.
A MESMA armadilha de rede, pela TERCEIRA vez. De dentro do pod, antes desta
politica: HTTP 000, exit 7. O ipBlock 10.43.0.0/16 da deny-cross-tenant cobre o
ClusterIP do Gitea e nao adianta — o k3s avalia DEPOIS do DNAT, e ali o destino
ja e o IP do POD.
Aconteceu com a API do Kubernetes em 20/09, com o sealed-secrets hoje mais cedo,
e agora aqui. E regra deste cluster, nao coincidencia.
Estreita: so o servico-captacao, so o POD do Gitea por rotulo (namespaceSelector
sozinho abriria a 3000 do namespace inteiro, onde tambem vivem o Valkey e o
runner de CI), so a 3000, so saida.
Ate aqui ele vivia SO no monorepo, e o provisionamento roda DENTRO do cluster —
o pod do servico-captacao nao tinha como enxerga-lo. A etapa MANIFESTOS_GERADOS
falhava por configuracao vazia, e falhar fechado era o comportamento certo; o
que faltava era o molde chegar.
Pondo-o aqui, o pod precisa de UM volume em vez de dois: o mesmo clone do GitOps
serve de molde (moldes/consolidado) e de copia de trabalho (tenants/<nome>).
Os tres marcadores — TENANT, REALM e PLANO — batem com o RenderizadorDeMolde. Os
arquivos NAO sao YAML valido antes de renderizados, e isso e de proposito: um
`{{PLANO}}` literal aplicaria sem reclamar e criaria um tenant cujo plano se
chama "{{PLANO}}", com a barreira negando tudo depois.
Os quatro tenants foram corrigidos mais cedo hoje; o plataforma nao. O defeito
so apareceu agora, e por um motivo que vale registrar: ele e o unico SEM
catch-all /api. Nos outros, uma rota sem linha propria era engolida pelo
backend legado; aqui ela cai no frontend — e e ali que o bug estava esperando.
Medido no log do proprio pod:
servico-bff could not be resolved (3: Host not found)
request: "POST /api/plataforma/provisionamentos"
O resolver do nginx nao aplica os search do /etc/resolv.conf, entao nome curto
nao resolve. As cinco variaveis estavam vazias, e toda ausente cai em
ATM_API_URL, que cai no nome curto `servico-bff`.
Aqui nao ha runtime consolidado: os destinos apontam para o BFF, que ja era o
padrao — a diferenca e so o FQDN.
Achado ao conferir o PLATAFORMA_ADMIN_TOKEN recem-ligado: a consulta de estado
devolvia 502 em vez de 401, e o 502 era isto.
Vai DEPOIS do commit que selou os Secrets, e nao junto: secretKeyRef de Secret
ausente impede o pod de subir. Os dois ja materializaram no cluster.
PLATAFORMA_WEBHOOK_TOKEN destrava a etapa que recebia o aviso de assinatura e
recusava tudo por falta de configuracao. Falta CADASTRAR o mesmo valor no painel
do Asaas, na conta da Athletic Map — quem for cadastrar le uma vez com:
kubectl -n plataforma-prod get secret plataforma-webhook-token \
-o jsonpath='{.data.token}' | base64 -d
PLATAFORMA_ADMIN_TOKEN destrava a consulta de estado do provisionamento, que e
interina e vive no cabecalho x-atm-admin-token.
Ao conferir que os Secrets tinham materializado, o comando imprimiu o campo
`.data` inteiro — ou seja, parte do valor em base64 dos dois tokens foi parar na
conversa. Curto, e o bastante para nao dar por confiavel.
Regerados. Custo zero: nada os referenciava ainda (nenhum Deployment, e o
webhook nao estava cadastrado no Asaas), entao nao houve janela de uso.
A licao, que ja valia para senha e vale igual para Secret: conferir existencia
com `-o name`, e nunca com `-o custom-columns=...:.data` nem `-o yaml`. O que se
quer saber e SE existe, nao O QUE e.
PLATAFORMA_ADMIN_TOKEN e PLATAFORMA_WEBHOOK_TOKEN. Os dois sao gerados aqui, ao
contrario da api-key do Asaas, que vem do painel do provedor.
O valor nasceu, foi selado e morreu dentro de um unico shell no servidor: nao
foi ecoado, nao foi para arquivo, nao entrou no historico e nao apareceu em ps.
O que se commita e o cifrado, que e publico por natureza.
Vao SOZINHOS neste commit, sem a referencia no Deployment: secretKeyRef de
Secret ausente impede o pod de subir. Primeiro o Secret materializa, depois o
env aponta para ele.
O webhook-token precisa ser CADASTRADO no painel do Asaas com o mesmo valor. Ele
pode ser lido uma vez, por quem for cadastrar:
kubectl -n plataforma-prod get secret plataforma-webhook-token \
-o jsonpath='{.data.token}' | base64 -d
Mesma mudanca do commit anterior, agora nos tres restantes. O demo foi primeiro
e ficou conferido de fora: os 13 contextos em 200, /bff em 200, /inicio e
/lp-escolinhas em 200, e — o que mais importava — /api/nao-mapeado continua
devolvendo 401, como antes. O comportamento visivel nao mudou.
Catorze linhas saem de cada um. O destino do /api passa de backend (o legado)
para frontend-spa; a decisao "401 ou 404" segue aberta e intacta.
Catorze linhas saem. Quem conhece a topologia interna passa a ser o
frontend/nginx.conf.template, que viaja junto com a imagem — antes, cada
mudanca de topologia obrigava a editar o Ingress de todos os tenants.
O destino do /api mudou de backend (o legado) para frontend-spa. O que o
visitante ve NAO muda: caminho nao mapeado continua recebendo 401 — antes do
backend legado, agora do BFF, para onde o nginx manda o /api que sobra. A
decisao "401 ou 404" segue aberta e intacta; isto so tira o legado do caminho
do trafego.
Pre-condicao resolvida hoje: as cinco variaveis de destino do nginx estavam
vazias e, mesmo apontadas, precisavam de FQDN (o resolver do nginx nao aplica os
search do resolv.conf). Sem isso, esta mudanca derrubaria os treze contextos e o
/bff de uma vez.
ORDEM: /api/public e /api/public/webhooks/asaas vivem no
96-ingress-garmin-public.yaml e tem prefixo mais longo, entao vencem o /api — o
Traefik ordena por tamanho.
Primeiro tenant. Os outros tres so depois de conferido.