feat(moldes): o molde do tenant novo entra no repositorio GitOps
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.
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
# =============================================================================
|
||||
# P4.9 — o molde consolidado: 4 Deployments no lugar de 15.
|
||||
#
|
||||
# ── POR QUE FORA DE `tenants/` ───────────────────────────────────────────────
|
||||
#
|
||||
# O ApplicationSet do ArgoCD tem um gerador de diretorio sobre `tenants/*`. Uma
|
||||
# pasta de MOLDE ali dentro seria tratada como tenant e implantada — com nome de
|
||||
# namespace de mentira e tudo. Por isso os moldes vivem em `moldes/`, que o
|
||||
# gerador nao enxerga.
|
||||
#
|
||||
# `scripts/provisionar_tenant.py` procura o molde em `tenants/<nome>`: ele
|
||||
# precisa aprender este caminho novo. Esta no diario.
|
||||
#
|
||||
# ── A COTA, COM OS NUMEROS MEDIDOS ───────────────────────────────────────────
|
||||
#
|
||||
# Hoje, um tenant (`escolinha`, medido em 2026-09-18): **5253 Mi em 18 pods**.
|
||||
#
|
||||
# Com a consolidacao, medido container a container no mesmo dia:
|
||||
#
|
||||
# runtime-core 384 MiB
|
||||
# runtime-operacao 403 MiB
|
||||
# runtime-saude 366 MiB
|
||||
# frontend-spa 24 MiB
|
||||
# ----------------------------
|
||||
# soma 1.177 MiB
|
||||
#
|
||||
# `requests.memory: 1.5Gi` cobre a soma com ~300 MiB de folga.
|
||||
# `limits.memory: 2.5Gi` da a cada runtime espaco para picos sem que a soma dos
|
||||
# limites vire uma promessa que o no nao pode cumprir.
|
||||
#
|
||||
# `pods: 8` — sao 4 Deployments; os outros 4 sao para o pod novo conviver com o
|
||||
# velho durante um rollout, sem travar o deploy por cota. Foi exatamente isso que
|
||||
# derrubou o provisionamento do `plataforma` em 2026-09-17: a cota do molde nao
|
||||
# cabia e keycloak, bff e captacao nao conseguiam criar pod.
|
||||
#
|
||||
# NAO ha Postgres nem Keycloak aqui: os dois passam a viver em `plataforma-prod`
|
||||
# (P4.7 e P4.8).
|
||||
# =============================================================================
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: {{TENANT}}-prod
|
||||
labels:
|
||||
kubernetes.io/metadata.name: {{TENANT}}-prod
|
||||
athleticmap.io/tenant: {{TENANT}}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ResourceQuota
|
||||
metadata:
|
||||
name: cota
|
||||
namespace: {{TENANT}}-prod
|
||||
spec:
|
||||
hard:
|
||||
requests.cpu: "1"
|
||||
requests.memory: 1.5Gi
|
||||
limits.cpu: "4"
|
||||
limits.memory: 2.5Gi
|
||||
pods: "8"
|
||||
---
|
||||
# =============================================================================
|
||||
# A politica de rede.
|
||||
#
|
||||
# ── O QUE MUDOU EM RELACAO AO MOLDE ANTIGO ──────────────────────────────────
|
||||
#
|
||||
# O `deny-cross-tenant` antigo liberava egress para `ipBlock: 10.43.0.0/16` — a
|
||||
# CIDR de Services do cluster INTEIRO. Era mais largo do que o nome dizia: um pod
|
||||
# de qualquer tenant alcancava, pela rede, o ClusterIP de qualquer outro.
|
||||
#
|
||||
# Aqui o egress interno e nominal: DNS, os pods do proprio namespace, e o
|
||||
# namespace `plataforma-prod` nas portas que importam.
|
||||
#
|
||||
# ── O QUE NAO FOI POSSIVEL, E O MOTIVO E O CNI ──────────────────────────────
|
||||
#
|
||||
# O card pede "egress so ... os destinos externos reais (Asaas, provedor fiscal,
|
||||
# Garmin) — nada de egress irrestrito".
|
||||
#
|
||||
# **Nao da com o CNI deste cluster.** Conferido em 2026-09-19: k3s com flannel
|
||||
# embutido, sem Cilium e sem Calico (nenhum CRD dos dois). NetworkPolicy padrao
|
||||
# do Kubernetes so aceita `ipBlock` — CIDR, nao nome. E Asaas, provedor fiscal e
|
||||
# Garmin sao SaaS atras de CDN: os IPs mudam, e uma lista de CIDR quebraria a
|
||||
# cobranca sem aviso, num dia qualquer.
|
||||
#
|
||||
# Entao a regra de 443 continua, e continua sendo egress irrestrito para a
|
||||
# internet. O `except` preserva o que a politica de fato protege: um tenant nao
|
||||
# alcanca a rede interna do outro.
|
||||
#
|
||||
# **O caminho para fechar isso de verdade** esta no diario: um proxy de saida no
|
||||
# `plataforma-prod`, com lista de hosts permitidos, e os tenants alcancando so
|
||||
# ele. Funciona com flannel, e nao exige trocar o CNI.
|
||||
# =============================================================================
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: deny-cross-tenant
|
||||
namespace: {{TENANT}}-prod
|
||||
spec:
|
||||
podSelector: {}
|
||||
policyTypes: [Ingress, Egress]
|
||||
ingress:
|
||||
- from:
|
||||
- podSelector: {} # mesma namespace
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: kube-system # Traefik (ingress)
|
||||
egress:
|
||||
# 1. Dentro do proprio namespace.
|
||||
- to:
|
||||
- podSelector: {}
|
||||
|
||||
# 2. DNS.
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
ports:
|
||||
- { protocol: UDP, port: 53 }
|
||||
- { protocol: TCP, port: 53 }
|
||||
|
||||
# 3. A plataforma: banco e identidade, so nas portas que importam.
|
||||
#
|
||||
# 5432 direto no Postgres e para o `pg_dump` do backup: ele abre uma
|
||||
# transacao unica e longa, o oposto do que transaction pooling serve.
|
||||
# 6432 e o PgBouncer, por onde os runtimes falam.
|
||||
# 8080 e o Keycloak (P4.8).
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: plataforma-prod
|
||||
ports:
|
||||
- { protocol: TCP, port: 5432 }
|
||||
- { protocol: TCP, port: 6432 }
|
||||
- { protocol: TCP, port: 8080 }
|
||||
|
||||
# 4. HTTPS para a internet publica. Ver o cabecalho: nao da para estreitar
|
||||
# com flannel, e uma lista de CIDR de SaaS quebraria a cobranca sem aviso.
|
||||
#
|
||||
# O `except` e o que a politica de fato garante: nenhum tenant alcanca a
|
||||
# rede interna de outro por este caminho.
|
||||
- to:
|
||||
- ipBlock:
|
||||
cidr: 0.0.0.0/0
|
||||
except: [10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16]
|
||||
ports:
|
||||
- { protocol: TCP, port: 443 }
|
||||
@@ -0,0 +1,22 @@
|
||||
# =============================================================================
|
||||
# P5.6/P5.9 — a plataforma pode esperar rollout e suspender ESTE tenant.
|
||||
#
|
||||
# O ClusterRole (`atm-provisionamento-tenants`, em
|
||||
# `tenants/plataforma/16-rbac-provisionamento.yaml`) e so a lista de verbos;
|
||||
# quem concede e este RoleBinding, e ele vale SO neste namespace. Um tenant sem
|
||||
# este arquivo simplesmente nao e alcancado pelo provisionamento — a etapa falha
|
||||
# com 403 dizendo o caminho que tentou, que e melhor do que um acesso amplo.
|
||||
# =============================================================================
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: atm-provisionamento
|
||||
namespace: {{TENANT}}-prod
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: atm-provisionamento
|
||||
namespace: plataforma-prod
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: atm-provisionamento-tenants
|
||||
@@ -0,0 +1,74 @@
|
||||
# =============================================================================
|
||||
# P5.5 — a entrada do tenant, com certificado automatico.
|
||||
#
|
||||
# ── O MOLDE NAO TINHA INGRESS NENHUM ────────────────────────────────────────
|
||||
#
|
||||
# Achado em 2026-09-19, ao comecar o card 165: os cinco arquivos do molde
|
||||
# consolidado criavam namespace, tres runtimes e a SPA — e nenhuma entrada. Um
|
||||
# tenant provisionado a partir dele subiria inteiro e **nao seria alcancavel de
|
||||
# fora**, com todos os pods saudaveis.
|
||||
#
|
||||
# ── UMA REGRA SO, E ISSO E O PONTO DE P4.6 ──────────────────────────────────
|
||||
#
|
||||
# O Ingress do `plataforma` tem SETE caminhos: um por contexto, mais o BFF, mais a
|
||||
# SPA. Aqui ha um. O nginx da imagem do frontend roteia `/bff` e `/api` por conta
|
||||
# propria (P4.6), entao a topologia interna deixou de ser problema do Ingress — e
|
||||
# mudar de cinco pods para tres runtimes nao obriga mais a editar o Ingress de
|
||||
# todos os tenants.
|
||||
#
|
||||
# ── SEM HOST DE AUTENTICACAO ────────────────────────────────────────────────
|
||||
#
|
||||
# O `plataforma` tem um segundo host, `auth-plataforma...`, porque tinha Keycloak
|
||||
# proprio. Com o Keycloak compartilhado (P4.8) o tenant nao tem: a autenticacao
|
||||
# acontece no host da plataforma, e o realm e que separa um cliente do outro.
|
||||
#
|
||||
# ── O CERTIFICADO, E POR QUE HTTP-01 ────────────────────────────────────────
|
||||
#
|
||||
# `cert-manager.io/cluster-issuer: letsencrypt-prod` — o mesmo que o `plataforma`
|
||||
# ja usa, com desafio HTTP-01.
|
||||
#
|
||||
# A alternativa seria um certificado CURINGA, e ele exigiria desafio DNS-01, e
|
||||
# DNS-01 exige um solver do provedor de DNS. **O provedor e a Hostinger, e o
|
||||
# cert-manager nao tem solver nativo para ela** — seria preciso escrever um
|
||||
# webhook. Conferido em 2026-09-19.
|
||||
#
|
||||
# Com HTTP-01 cada tenant tem o proprio certificado e nao ha solver nenhum a
|
||||
# escrever. O que se precisa do provedor e UMA entrada de DNS curinga
|
||||
# (`*.athleticmap.com` apontando para o cluster), feita uma vez a mao.
|
||||
#
|
||||
# **A ORDEM E OBRIGATORIA:** o host precisa resolver para o cluster ANTES de o
|
||||
# cert-manager pedir o certificado, porque o desafio HTTP-01 e uma requisicao que
|
||||
# a Let's Encrypt faz ao proprio host. Sem o curinga no ar, o certificado fica em
|
||||
# `False` para sempre e o tenant responde so em HTTP.
|
||||
# =============================================================================
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: redirect-https
|
||||
namespace: {{TENANT}}-prod
|
||||
spec:
|
||||
redirectScheme:
|
||||
scheme: https
|
||||
permanent: true
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{TENANT}}
|
||||
namespace: {{TENANT}}-prod
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
# O middleware e por namespace: `<namespace>-<nome>@kubernetescrd`.
|
||||
traefik.ingress.kubernetes.io/router.middlewares: {{TENANT}}-prod-redirect-https@kubernetescrd
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- {{TENANT}}.athleticmap.com
|
||||
secretName: {{TENANT}}-tls
|
||||
rules:
|
||||
- host: {{TENANT}}.athleticmap.com
|
||||
http:
|
||||
paths:
|
||||
# UMA rota. O nginx da SPA resolve /bff e /api internamente (P4.6).
|
||||
- { path: /, pathType: Prefix, backend: { service: { name: frontend-spa, port: { number: 80 } } } }
|
||||
@@ -0,0 +1,40 @@
|
||||
# =============================================================================
|
||||
# Deixa o Prometheus raspar as metricas deste tenant.
|
||||
#
|
||||
# Aditivo a `deny-cross-tenant` (em `00-namespace-quota-netpol.yaml`), que fecha
|
||||
# ingresso de fora do namespace. Sem esta politica o tenant sobe, funciona e fica
|
||||
# INVISIVEL no Grafana — e um tenant sem metrica e um tenant em que ninguem
|
||||
# percebe consumo subindo nem pod reiniciando.
|
||||
#
|
||||
# ── POR QUE `podSelector: {}` (2026-09-20) ──────────────────────────────────
|
||||
#
|
||||
# Antes a politica citava pods por NOME (`app: backend`, ou uma lista). Resultado:
|
||||
# quando o `ServiceMonitor` passou a raspar os runtimes e os 13 servicos, TODOS os
|
||||
# alvos novos ficaram `up=0` e o cluster acendeu 48 alertas de `TargetDown` — a
|
||||
# coleta chegava ao Service e morria na rede.
|
||||
#
|
||||
# Lista de nomes aqui e a mesma lista do `ServiceMonitor` escrita duas vezes, em
|
||||
# lugares diferentes, com o erro aparecendo so em producao. Agora vale para
|
||||
# qualquer pod DESTE namespace, e a fronteira e a mesma de antes:
|
||||
#
|
||||
# - SO a porta 8083, que e a das metricas (`/actuator/prometheus`);
|
||||
# - SO do namespace `monitoring`.
|
||||
#
|
||||
# O `frontend-spa` escuta na 80 e continua fora, sem precisar ser citado.
|
||||
# =============================================================================
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: allow-monitoring
|
||||
namespace: {{TENANT}}-prod
|
||||
spec:
|
||||
podSelector: {}
|
||||
policyTypes: [Ingress]
|
||||
ingress:
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: monitoring
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8083
|
||||
@@ -0,0 +1,63 @@
|
||||
# =============================================================================
|
||||
# P4.9 — runtime-core — configuracao, cadastro, organizacao, documentos, agenda
|
||||
#
|
||||
# `{{TENANT}}` e substituido pelo nome do tenant ao provisionar. O molde inteiro usa
|
||||
# o mesmo marcador, para `sed`/script nao precisarem conhecer cada arquivo.
|
||||
# =============================================================================
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: runtime-core
|
||||
namespace: {{TENANT}}-prod
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels: { app: runtime-core }
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: runtime-core
|
||||
athleticmap.io/contexto: runtime-core
|
||||
spec:
|
||||
containers:
|
||||
- name: runtime-core
|
||||
image: docker.io/library/runtime-core:1.1-demo
|
||||
# `Never`: a imagem tem de estar no containerd do no ANTES do deploy.
|
||||
# "Presente no no" tambem nao e "atual" — conferir a TAG, e nao o nome.
|
||||
imagePullPolicy: Never
|
||||
ports: [{ containerPort: 8083 }]
|
||||
env:
|
||||
# O banco e o PgBouncer do `plataforma-prod` (P4.7), e nao um
|
||||
# Postgres deste namespace: ele nao existe mais aqui.
|
||||
- { name: SPRING_DATASOURCE_URL, value: "jdbc:postgresql://pgbouncer.plataforma-prod.svc:6432/athleticmap_{{TENANT}}" }
|
||||
- { name: SPRING_DATASOURCE_USERNAME, value: "r_{{TENANT}}" }
|
||||
- name: SPRING_DATASOURCE_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef: { name: db-credentials, key: password }
|
||||
# O Keycloak tambem e da plataforma (P4.8). O REALM passa a ser o
|
||||
# nome do tenant: uma instancia so nao comporta quatro realms
|
||||
# chamados `athleticmap`.
|
||||
- { name: ATM_JWK_SET_URI, value: "http://keycloak.plataforma-prod.svc:8080/realms/{{REALM}}/protocol/openid-connect/certs" }
|
||||
- { name: ATM_ISSUER, value: "https://auth.athleticmap.influxdigital.com.br/realms/{{REALM}}" }
|
||||
- { name: ATM_TENANT, value: "{{TENANT}}" }
|
||||
- { name: ATM_PLANO, value: "{{PLANO}}" }
|
||||
resources:
|
||||
requests: { cpu: 100m, memory: 384Mi }
|
||||
limits: { cpu: "1", memory: 768Mi }
|
||||
readinessProbe:
|
||||
httpGet: { path: /actuator/health/readiness, port: 8083 }
|
||||
initialDelaySeconds: 20
|
||||
periodSeconds: 10
|
||||
livenessProbe:
|
||||
httpGet: { path: /actuator/health/liveness, port: 8083 }
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 30
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: runtime-core
|
||||
namespace: {{TENANT}}-prod
|
||||
spec:
|
||||
selector: { app: runtime-core }
|
||||
ports: [{ port: 80, targetPort: 8083 }]
|
||||
@@ -0,0 +1,82 @@
|
||||
# =============================================================================
|
||||
# P4.9 — runtime-operacao — planejamento, campeonato, administrativo, financeiro
|
||||
#
|
||||
# `{{TENANT}}` e substituido pelo nome do tenant ao provisionar. O molde inteiro usa
|
||||
# o mesmo marcador, para `sed`/script nao precisarem conhecer cada arquivo.
|
||||
# =============================================================================
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: runtime-operacao
|
||||
namespace: {{TENANT}}-prod
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels: { app: runtime-operacao }
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: runtime-operacao
|
||||
athleticmap.io/contexto: runtime-operacao
|
||||
spec:
|
||||
containers:
|
||||
- name: runtime-operacao
|
||||
image: docker.io/library/runtime-operacao:1.1-demo
|
||||
# `Never`: a imagem tem de estar no containerd do no ANTES do deploy.
|
||||
# "Presente no no" tambem nao e "atual" — conferir a TAG, e nao o nome.
|
||||
imagePullPolicy: Never
|
||||
ports: [{ containerPort: 8083 }]
|
||||
env:
|
||||
# O banco e o PgBouncer do `plataforma-prod` (P4.7), e nao um
|
||||
# Postgres deste namespace: ele nao existe mais aqui.
|
||||
- { name: SPRING_DATASOURCE_URL, value: "jdbc:postgresql://pgbouncer.plataforma-prod.svc:6432/athleticmap_{{TENANT}}" }
|
||||
- { name: SPRING_DATASOURCE_USERNAME, value: "r_{{TENANT}}" }
|
||||
- name: SPRING_DATASOURCE_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef: { name: db-credentials, key: password }
|
||||
# O Keycloak tambem e da plataforma (P4.8). O REALM passa a ser o
|
||||
# nome do tenant: uma instancia so nao comporta quatro realms
|
||||
# chamados `athleticmap`.
|
||||
- { name: ATM_JWK_SET_URI, value: "http://keycloak.plataforma-prod.svc:8080/realms/{{REALM}}/protocol/openid-connect/certs" }
|
||||
- { name: ATM_ISSUER, value: "https://auth.athleticmap.influxdigital.com.br/realms/{{REALM}}" }
|
||||
- { name: ATM_TENANT, value: "{{TENANT}}" }
|
||||
- { name: ATM_PLANO, value: "{{PLANO}}" }
|
||||
# O provedor de pagamento vive neste runtime.
|
||||
#
|
||||
# `optional: true` NAO e descuido, e virou obrigatorio com a decisao de
|
||||
# 2026-09-19: a chave do Asaas e DO CLIENTE, e ele a fornece depois de o
|
||||
# tenant existir — precisa entrar no painel dele, gerar a chave e colar.
|
||||
#
|
||||
# Sem `optional`, um Secret ausente IMPEDE O POD DE INICIAR. O tenant
|
||||
# novo nao subiria ate o cliente mandar a chave, e o sintoma chegaria
|
||||
# como "o sistema nao abriu" em vez de "falta configurar cobranca".
|
||||
#
|
||||
# O runtime ja sabe conviver com isso: `AsaasPropriedades` trata chave
|
||||
# vazia como INTEGRACAO DESLIGADA, e o resto do sistema funciona.
|
||||
- name: ASAAS_API_KEY
|
||||
valueFrom:
|
||||
secretKeyRef: { name: asaas-credentials, key: api-key, optional: true }
|
||||
- name: ASAAS_WEBHOOK_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef: { name: asaas-credentials, key: webhook-token, optional: true }
|
||||
- { name: ASAAS_AMBIENTE, value: "sandbox" }
|
||||
resources:
|
||||
requests: { cpu: 100m, memory: 400Mi }
|
||||
limits: { cpu: "1", memory: 768Mi }
|
||||
readinessProbe:
|
||||
httpGet: { path: /actuator/health/readiness, port: 8083 }
|
||||
initialDelaySeconds: 20
|
||||
periodSeconds: 10
|
||||
livenessProbe:
|
||||
httpGet: { path: /actuator/health/liveness, port: 8083 }
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 30
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: runtime-operacao
|
||||
namespace: {{TENANT}}-prod
|
||||
spec:
|
||||
selector: { app: runtime-operacao }
|
||||
ports: [{ port: 80, targetPort: 8083 }]
|
||||
@@ -0,0 +1,66 @@
|
||||
# =============================================================================
|
||||
# P4.9 — runtime-saude — saude, nutricao, assistencia-social, pedagogico
|
||||
#
|
||||
# `{{TENANT}}` e substituido pelo nome do tenant ao provisionar. O molde inteiro usa
|
||||
# o mesmo marcador, para `sed`/script nao precisarem conhecer cada arquivo.
|
||||
# =============================================================================
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: runtime-saude
|
||||
namespace: {{TENANT}}-prod
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels: { app: runtime-saude }
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: runtime-saude
|
||||
athleticmap.io/contexto: runtime-saude
|
||||
spec:
|
||||
containers:
|
||||
- name: runtime-saude
|
||||
image: docker.io/library/runtime-saude:1.2-demo
|
||||
# `Never`: a imagem tem de estar no containerd do no ANTES do deploy.
|
||||
# "Presente no no" tambem nao e "atual" — conferir a TAG, e nao o nome.
|
||||
imagePullPolicy: Never
|
||||
ports: [{ containerPort: 8083 }]
|
||||
env:
|
||||
# O banco e o PgBouncer do `plataforma-prod` (P4.7), e nao um
|
||||
# Postgres deste namespace: ele nao existe mais aqui.
|
||||
- { name: SPRING_DATASOURCE_URL, value: "jdbc:postgresql://pgbouncer.plataforma-prod.svc:6432/athleticmap_{{TENANT}}" }
|
||||
- { name: SPRING_DATASOURCE_USERNAME, value: "r_{{TENANT}}" }
|
||||
- name: SPRING_DATASOURCE_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef: { name: db-credentials, key: password }
|
||||
# O Keycloak tambem e da plataforma (P4.8). O REALM passa a ser o
|
||||
# nome do tenant: uma instancia so nao comporta quatro realms
|
||||
# chamados `athleticmap`.
|
||||
- { name: ATM_JWK_SET_URI, value: "http://keycloak.plataforma-prod.svc:8080/realms/{{REALM}}/protocol/openid-connect/certs" }
|
||||
- { name: ATM_ISSUER, value: "https://auth.athleticmap.influxdigital.com.br/realms/{{REALM}}" }
|
||||
- { name: ATM_TENANT, value: "{{TENANT}}" }
|
||||
- { name: ATM_PLANO, value: "{{PLANO}}" }
|
||||
# Retencao da trilha de auditoria. O expurgo roda agendado DENTRO
|
||||
# deste runtime — ver RuntimeSaude.
|
||||
- { name: ATM_AUDITORIA_RETENCAO_DIAS, value: "730" }
|
||||
resources:
|
||||
requests: { cpu: 100m, memory: 384Mi }
|
||||
limits: { cpu: "1", memory: 768Mi }
|
||||
readinessProbe:
|
||||
httpGet: { path: /actuator/health/readiness, port: 8083 }
|
||||
initialDelaySeconds: 20
|
||||
periodSeconds: 10
|
||||
livenessProbe:
|
||||
httpGet: { path: /actuator/health/liveness, port: 8083 }
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 30
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: runtime-saude
|
||||
namespace: {{TENANT}}-prod
|
||||
spec:
|
||||
selector: { app: runtime-saude }
|
||||
ports: [{ port: 80, targetPort: 8083 }]
|
||||
@@ -0,0 +1,85 @@
|
||||
# =============================================================================
|
||||
# P4.9 — a SPA, e com ela o roteamento interno de /bff e /api (P4.6).
|
||||
#
|
||||
# `{{TENANT}}` e substituido ao provisionar.
|
||||
#
|
||||
# ── O QUE MUDOU EM RELACAO AO MOLDE ANTIGO ──────────────────────────────────
|
||||
#
|
||||
# O nginx desta imagem roteia `/bff` e `/api` por conta propria, a partir de
|
||||
# `ATM_BFF_URL` e `ATM_API_URL`. Por isso o Ingress deste molde tem UMA rota — e
|
||||
# nao uma linha por contexto, que era o que obrigava a editar o Ingress de todos
|
||||
# os tenants a cada mudanca de topologia.
|
||||
#
|
||||
# O `/api` aponta para o `runtime-operacao` porque e ele quem recebe o webhook do
|
||||
# provedor de pagamento em `/api/public/**`. Os outros caminhos de `/api` sao
|
||||
# resolvidos pelo proprio nginx? NAO — e por isso esta linha e provisoria.
|
||||
#
|
||||
# **PENDENCIA CONHECIDA:** `/api/cadastro`, `/api/saude` e os demais precisam ir
|
||||
# para runtimes DIFERENTES, e uma variavel so nao faz isso. O nginx precisa de
|
||||
# uma regra por grupo de contexto. Esta no diario; ate resolver, use o Ingress
|
||||
# para separar, como hoje.
|
||||
# =============================================================================
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: frontend-spa
|
||||
namespace: {{TENANT}}-prod
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels: { app: frontend-spa }
|
||||
template:
|
||||
metadata:
|
||||
labels: { app: frontend-spa }
|
||||
spec:
|
||||
containers:
|
||||
- name: frontend-spa
|
||||
image: docker.io/library/frontend-spa:2.65-csp
|
||||
imagePullPolicy: Never
|
||||
ports: [{ containerPort: 80 }]
|
||||
env:
|
||||
- { name: ATM_KC_URL, value: "https://auth.athleticmap.influxdigital.com.br" }
|
||||
- { name: ATM_REALM, value: "{{REALM}}" }
|
||||
- { name: ATM_CLIENT, value: "spa" }
|
||||
- { name: ATM_TENANT, value: "{{TENANT}}" }
|
||||
- { name: ATM_PLANO, value: "{{PLANO}}" }
|
||||
# ATENCAO: nao ha Deployment de `servico-bff` neste molde — sao
|
||||
# quatro, e nenhum e o BFF. Este endereco NAO RESOLVE, e todo /bff
|
||||
# devolve 502. Ver a pendencia no diario; o molde ainda nao pode ser
|
||||
# aplicado a um tenant por causa disto. Deixado apontando para o nome
|
||||
# antigo de proposito: apagar a linha esconderia o buraco.
|
||||
- { name: ATM_BFF_URL, value: "http://servico-bff" }
|
||||
|
||||
# /api por GRUPO DE CONTEXTO, e nao um destino so.
|
||||
#
|
||||
# A versao anterior mandava TUDO para o `runtime-operacao`, porque e
|
||||
# ele quem recebe o webhook do Asaas. Isso estava certo para quatro
|
||||
# contextos e errado para os outros nove: `/api/cadastro` tem de ir
|
||||
# para o core e `/api/saude` para o saude.
|
||||
#
|
||||
# O sintoma seria 404 do Spring, e nao 502 — o runtime responde, so
|
||||
# nao conhece o caminho. Na tela, uma lista vazia.
|
||||
#
|
||||
# `ATM_API_URL` continua, e nao e redundante: e o destino do que nao
|
||||
# pertence a grupo nenhum, hoje `/api/public/**`.
|
||||
- { name: ATM_API_URL, value: "http://runtime-operacao" }
|
||||
- { name: ATM_API_CORE_URL, value: "http://runtime-core" }
|
||||
- { name: ATM_API_OPERACAO_URL, value: "http://runtime-operacao" }
|
||||
- { name: ATM_API_SAUDE_URL, value: "http://runtime-saude" }
|
||||
resources:
|
||||
# 24 MiB medidos em 2026-09-18. 64Mi de request da folga de 2,5x.
|
||||
requests: { cpu: 50m, memory: 64Mi }
|
||||
limits: { cpu: 200m, memory: 128Mi }
|
||||
readinessProbe:
|
||||
httpGet: { path: /, port: 80 }
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: frontend-spa
|
||||
namespace: {{TENANT}}-prod
|
||||
spec:
|
||||
selector: { app: frontend-spa }
|
||||
ports: [{ port: 80, targetPort: 80 }]
|
||||
@@ -0,0 +1,51 @@
|
||||
# Molde consolidado de tenant — P4.9
|
||||
|
||||
Quatro Deployments no lugar de quinze.
|
||||
|
||||
## Os numeros, medidos e nao estimados
|
||||
|
||||
| | |
|
||||
|---|---:|
|
||||
| um tenant hoje (`escolinha`, 2026-09-18) | **5.253 Mi em 18 pods** |
|
||||
| runtime-core | 384 MiB |
|
||||
| runtime-operacao | 403 MiB |
|
||||
| runtime-saude | 366 MiB |
|
||||
| frontend-spa | 24 MiB |
|
||||
| **soma do molde novo** | **1.177 MiB em 4 pods** |
|
||||
|
||||
A cota (`requests 1.5Gi` / `limits 2.5Gi` / `pods 8`) sai desses numeros, com
|
||||
folga para o pod novo conviver com o velho durante um rollout — foi a falta
|
||||
disso que travou o provisionamento do `plataforma` em 2026-09-17.
|
||||
|
||||
## O que este molde NAO tem
|
||||
|
||||
**Postgres e Keycloak.** Os dois passam a viver em `plataforma-prod` (P4.7 e
|
||||
P4.8). E por isso que o tenant caiu de 18 pods para 4.
|
||||
|
||||
## O que falta antes de usar
|
||||
|
||||
1. **A renomeacao dos quatro realms que ja existem.** O molde usa agora um
|
||||
marcador PROPRIO, `{{REALM}}`, e nao mais `{{TENANT}}` — decidido em
|
||||
2026-09-19: o realm passa a se chamar `athleticmap-<N>`, com N na ordem de
|
||||
criacao, e o tenant continua com o nome comercial.
|
||||
**Sao coisas diferentes agora**, e confundi-las gera um `ATM_JWK_SET_URI`
|
||||
apontando para um realm inexistente: o pod fica saudavel e ninguem consegue
|
||||
entrar.
|
||||
Os quatro tenants atuais ainda tem realm `athleticmap`, e a renomeacao deles e
|
||||
o card 156. **A ordem de criacao nao foi conferida** — esta no diario.
|
||||
2. **Nao ha Deployment do `servico-bff` neste molde**, e o `ATM_BFF_URL` aponta
|
||||
para um Service que nao existe no namespace. Todo `/bff` daria 502. Ver o
|
||||
cabecalho do `95-frontend-spa.yaml` e o diario: bloqueia os cards 158 e 159.
|
||||
3. **O `asaas-credentials` pode nao existir quando o pod subir.** Com a decisao de
|
||||
2026-09-19 (a plataforma abre subconta por cliente), a aprovacao passa por
|
||||
analise de documentos e nao cabe na janela de provisionamento. O
|
||||
`secretKeyRef` precisa virar `optional: true`, senao o `runtime-operacao` nao
|
||||
inicia.
|
||||
4. **`scripts/provisionar_tenant.py` procura o molde em `tenants/<nome>`**, e
|
||||
este vive em `moldes/`. Precisa aprender o caminho.
|
||||
|
||||
## O molde antigo continua onde estava
|
||||
|
||||
`tenants/piloto` e os outros nao foram tocados: **eles sao o rollback**. O card
|
||||
pede isso explicitamente, e nao e formalidade — enquanto os runtimes nao
|
||||
estiverem em producao, o que roda e o desenho antigo.
|
||||
Reference in New Issue
Block a user