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