From 084b3cfd56fc9d6351084d5bad38f8d2d5910c2a Mon Sep 17 00:00:00 2001 From: provisionamento Athletic Map Date: Fri, 25 Sep 2026 12:57:17 +0000 Subject: [PATCH] feat(tenant): provisiona zico-0 Cliente: Zico 0 Assinatura: sub_dgoho9lkia9wfaqp Plano: (nao informado) Realm: athleticmap-8 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/*. --- tenants/zico-0/00-namespace-quota-netpol.yaml | 176 ++++++++++++++++++ tenants/zico-0/05-sealed-db-credentials.yaml | 23 +++ .../16-rolebinding-provisionamento.yaml | 59 ++++++ tenants/zico-0/30-ingress.yaml | 74 ++++++++ tenants/zico-0/60-monitoring-netpol.yaml | 40 ++++ tenants/zico-0/70-runtime-core.yaml | 63 +++++++ tenants/zico-0/71-runtime-operacao.yaml | 82 ++++++++ tenants/zico-0/72-runtime-saude.yaml | 66 +++++++ tenants/zico-0/90-servico-bff.yaml | 89 +++++++++ tenants/zico-0/95-frontend-spa.yaml | 96 ++++++++++ 10 files changed, 768 insertions(+) create mode 100644 tenants/zico-0/00-namespace-quota-netpol.yaml create mode 100644 tenants/zico-0/05-sealed-db-credentials.yaml create mode 100644 tenants/zico-0/16-rolebinding-provisionamento.yaml create mode 100644 tenants/zico-0/30-ingress.yaml create mode 100644 tenants/zico-0/60-monitoring-netpol.yaml create mode 100644 tenants/zico-0/70-runtime-core.yaml create mode 100644 tenants/zico-0/71-runtime-operacao.yaml create mode 100644 tenants/zico-0/72-runtime-saude.yaml create mode 100644 tenants/zico-0/90-servico-bff.yaml create mode 100644 tenants/zico-0/95-frontend-spa.yaml diff --git a/tenants/zico-0/00-namespace-quota-netpol.yaml b/tenants/zico-0/00-namespace-quota-netpol.yaml new file mode 100644 index 0000000..9a8a872 --- /dev/null +++ b/tenants/zico-0/00-namespace-quota-netpol.yaml @@ -0,0 +1,176 @@ +# ============================================================================= +# 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/`: 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 +# +# OS NUMEROS DA TABELA ACIMA SAO DE QUANDO ERAM QUATRO DEPLOYMENTS. Hoje sao +# cinco (o `servico-bff` entrou em 2026-09-24), e os tetos ja viveram duas +# correcoes: requests.memory 1.5Gi -> 2Gi, limits.memory 2.5Gi -> 3.5Gi -> +# 4352Mi, pods 8 -> 10. A razao de cada valor esta ao lado dele, la embaixo. +# +# A REGRA QUE VALE PARA TODOS: a cota tem de caber o REPOUSO MAIS UMA TROCA. +# Um Deployment sobe o pod novo ANTES de derrubar o velho, entao no pico existe +# um pod a mais do que o desenho sugere -- e o `maxSurge: 25%` sobre uma unica +# replica arredonda para cima, ou seja, sempre um inteiro. +# +# Ja custou duas vezes, pela mesma causa em dimensoes diferentes: derrubou o +# provisionamento do `plataforma` em 2026-09-17 (faltou `pods`), e prendeu os +# dois tenants pagos por 12h em 2026-09-24 (faltou `limits.memory`), com o +# ArgoCD dizendo "Synced" enquanto o pod novo era recusado pela cota. +# +# +# 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: zico-0-prod + labels: + kubernetes.io/metadata.name: zico-0-prod + athleticmap.io/tenant: zico-0 +--- +apiVersion: v1 +kind: ResourceQuota +metadata: + name: cota + namespace: zico-0-prod +spec: + hard: + # SUBIU em 2026-09-24 para caber o BFF, o quinto Deployment. + # + # Nao e folga generosa: com quatro Deployments o tenant ja usava 1232Mi dos + # 1536Mi, e o BFF pede 256Mi. Caberia no limite antigo em repouso, e + # estouraria no primeiro rollout — que e quando um Deployment sobe o pod + # novo antes de derrubar o velho. Foi essa conta que derrubou o + # provisionamento do `plataforma` em 17/09, e o registro dela esta ali. + # + # `pods` vai a 10 pelo mesmo motivo: cinco Deployments, um surge cada. + requests.cpu: "1.5" + requests.memory: 2Gi + limits.cpu: "5" + # SUBIU de 3.5Gi em 2026-09-25: faltava por 128Mi na hora da troca. + # + # A conta que o comentario acima comecou, agora terminada. Em repouso os + # cinco Deployments somam 2944Mi (128 + 768*3 + 512). O `maxSurge: 25%` sobre + # UMA replica arredonda para cima e pede um pod inteiro a mais: 768Mi, se o + # que esta trocando e um runtime. 2944 + 768 = 3712, contra um teto de 3584. + # + # Nao foi hipotese: os dois tenants pagos ficaram 12h presos em `1.1-demo` + # sem conseguir ir para `1.2-auditoria`, com o ArgoCD dizendo "Synced" e o + # pod novo recusado pela cota a cada ~17min. + # + # 4352Mi = 2944 de repouso + 768 da troca + 640 de sobra. + # + # So `limits.memory` travava. Medido em 2026-09-25: requests.memory tinha + # 560Mi livres (a troca pede 400), requests.cpu 1100m (pede 100), limits.cpu + # 1300m (pede 1000) e pods 5 de 10. Subir a memoria basta. + limits.memory: 4352Mi + pods: "10" +--- +# ============================================================================= +# 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: zico-0-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 } diff --git a/tenants/zico-0/05-sealed-db-credentials.yaml b/tenants/zico-0/05-sealed-db-credentials.yaml new file mode 100644 index 0000000..95d8b30 --- /dev/null +++ b/tenants/zico-0/05-sealed-db-credentials.yaml @@ -0,0 +1,23 @@ +# ============================================================================= +# P5.3 — gerado pelo provisionamento. NAO edite a mao. +# +# A senha em claro NUNCA existiu em disco: foi gerada em memoria, selada com o +# certificado PUBLICO do controlador e zerada. Ninguem a conhece, e ninguem +# precisa: quem a le e o proprio Postgres, pelo Secret que este arquivo cria. +# +# Escopo ESTRITO: este cifrado so abre para o Secret `db-credentials` no namespace `zico-0-prod`. +# Copiar o bloco para outro tenant NAO funciona, e e assim que tem de ser. +# ============================================================================= +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + name: db-credentials + namespace: zico-0-prod +spec: + encryptedData: + password: AgCdZDJasCl1hdZezWO8YywsLiqQ1NWmPZOuLK6dl4vpmeDUdI+YMGO3XWoC4DchXgL0yw4gi8sXuZYTTvU4+kvqbWgcodIKwzhOYK2aapggkGWn2uFZyDx9kGtmMIf5q2sstl8sbZao/BFKx40sC2odzXNbLSw69YU3Ka8hLJFCN7we17D3ELVwouyvQdK8jkv4C1X9opeHLCqRO2bnbStHzYA+xRH+HvQPr+XE/z7UJ0jFaorK65ofTgLDsxMgx1hnCNfcOTHiBg68+zx6Y3OTExBnXZ5rpoglH98lYKM32kAuTjV95hm+cMKIE59zmy03PDAdeRnWYutU6fmlXhlNt1rbx84l3eLn/iuQwbCVo/468dy0BkOM9PfFloi0NJzN0FLuv1HftQPV92tajabxkjmPdVXtZvncCkYfD3oF5vB6N8Zt+tfYCKnfaT32xJkF/9RbkXHOgOqKttHJx62O4lDbYHwECyZGd0h2goa9kqAP+zyL/b6gRKVJeebpo+73NcCbHsHGNqP83JlwZybBRAtUvn4DFpVN35yNiPQHJw/fG2LUsUhsdi+uB78O5Lykeu+VgA0cx9bLPzIjwX9zqnPUCExSZ7Xdvu2y59n8CLFa/dLXxYsiqcPk0YAAF3+j6vA5r6fOCOb15HSBJcYiTonpudgFgRe1CyNrMb4zcVzMs+bohsBJvrnKwFMuVBdHCm98+uUJoIQog8FekG+j+HTwlcJPiEu17BUwvHAYqgY7B3mpGRR7E3Oc + template: + metadata: + name: db-credentials + namespace: zico-0-prod + type: Opaque diff --git a/tenants/zico-0/16-rolebinding-provisionamento.yaml b/tenants/zico-0/16-rolebinding-provisionamento.yaml new file mode 100644 index 0000000..b68d7e0 --- /dev/null +++ b/tenants/zico-0/16-rolebinding-provisionamento.yaml @@ -0,0 +1,59 @@ +# ============================================================================= +# 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: zico-0-prod +subjects: + - kind: ServiceAccount + name: atm-provisionamento + namespace: plataforma-prod +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: atm-provisionamento-tenants + +--- +# Os DOIS Secrets que o provisionamento le neste namespace, pelo nome. +# +# Role por tenant, e nao regra no ClusterRole, porque os nomes dependem do +# tenant e `resourceNames` nao aceita curinga. A alternativa seria dar `get` em +# QUALQUER Secret do namespace — e ai o provisionamento passaria a poder ler a +# chave Asaas DO CLIENTE, que e dele e nao nossa. +# +# db-credentials a senha do papel no Postgres, que a etapa BANCO_CRIADO +# precisa para dar a senha ao papel e ao pool +# zico-0-tls so a EXISTENCIA, que e como a etapa DNS_PUBLICADO sabe que +# o cert-manager terminou de emitir +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: atm-provisionamento-segredos + namespace: zico-0-prod +rules: + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get"] + resourceNames: ["db-credentials", "zico-0-tls"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: atm-provisionamento-segredos + namespace: zico-0-prod +subjects: + - kind: ServiceAccount + name: atm-provisionamento + namespace: plataforma-prod +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: atm-provisionamento-segredos diff --git a/tenants/zico-0/30-ingress.yaml b/tenants/zico-0/30-ingress.yaml new file mode 100644 index 0000000..9a554bc --- /dev/null +++ b/tenants/zico-0/30-ingress.yaml @@ -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: zico-0-prod +spec: + redirectScheme: + scheme: https + permanent: true +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: zico-0 + namespace: zico-0-prod + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod + # O middleware e por namespace: `-@kubernetescrd`. + traefik.ingress.kubernetes.io/router.middlewares: zico-0-prod-redirect-https@kubernetescrd +spec: + ingressClassName: traefik + tls: + - hosts: + - zico-0.athleticmap.com + secretName: zico-0-tls + rules: + - host: zico-0.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 } } } } diff --git a/tenants/zico-0/60-monitoring-netpol.yaml b/tenants/zico-0/60-monitoring-netpol.yaml new file mode 100644 index 0000000..ee0267f --- /dev/null +++ b/tenants/zico-0/60-monitoring-netpol.yaml @@ -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: zico-0-prod +spec: + podSelector: {} + policyTypes: [Ingress] + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: monitoring + ports: + - protocol: TCP + port: 8083 diff --git a/tenants/zico-0/70-runtime-core.yaml b/tenants/zico-0/70-runtime-core.yaml new file mode 100644 index 0000000..70dbee0 --- /dev/null +++ b/tenants/zico-0/70-runtime-core.yaml @@ -0,0 +1,63 @@ +# ============================================================================= +# P4.9 — runtime-core — configuracao, cadastro, organizacao, documentos, agenda +# +# `zico-0` 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: zico-0-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.2-auditoria + # `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_zico-0" } + - { name: SPRING_DATASOURCE_USERNAME, value: "r_zico-0" } + - 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/athleticmap-8/protocol/openid-connect/certs" } + - { name: ATM_ISSUER, value: "https://auth.athleticmap.com/realms/athleticmap-8" } + - { name: ATM_TENANT, value: "zico-0" } + - { name: ATM_PLANO, value: "bronze" } + 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: zico-0-prod +spec: + selector: { app: runtime-core } + ports: [{ port: 80, targetPort: 8083 }] diff --git a/tenants/zico-0/71-runtime-operacao.yaml b/tenants/zico-0/71-runtime-operacao.yaml new file mode 100644 index 0000000..bae498d --- /dev/null +++ b/tenants/zico-0/71-runtime-operacao.yaml @@ -0,0 +1,82 @@ +# ============================================================================= +# P4.9 — runtime-operacao — planejamento, campeonato, administrativo, financeiro +# +# `zico-0` 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: zico-0-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.2-auditoria + # `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_zico-0" } + - { name: SPRING_DATASOURCE_USERNAME, value: "r_zico-0" } + - 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/athleticmap-8/protocol/openid-connect/certs" } + - { name: ATM_ISSUER, value: "https://auth.athleticmap.com/realms/athleticmap-8" } + - { name: ATM_TENANT, value: "zico-0" } + - { name: ATM_PLANO, value: "bronze" } + # 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: zico-0-prod +spec: + selector: { app: runtime-operacao } + ports: [{ port: 80, targetPort: 8083 }] diff --git a/tenants/zico-0/72-runtime-saude.yaml b/tenants/zico-0/72-runtime-saude.yaml new file mode 100644 index 0000000..a38a37f --- /dev/null +++ b/tenants/zico-0/72-runtime-saude.yaml @@ -0,0 +1,66 @@ +# ============================================================================= +# P4.9 — runtime-saude — saude, nutricao, assistencia-social, pedagogico +# +# `zico-0` 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: zico-0-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_zico-0" } + - { name: SPRING_DATASOURCE_USERNAME, value: "r_zico-0" } + - 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/athleticmap-8/protocol/openid-connect/certs" } + - { name: ATM_ISSUER, value: "https://auth.athleticmap.com/realms/athleticmap-8" } + - { name: ATM_TENANT, value: "zico-0" } + - { name: ATM_PLANO, value: "bronze" } + # 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: zico-0-prod +spec: + selector: { app: runtime-saude } + ports: [{ port: 80, targetPort: 8083 }] diff --git a/tenants/zico-0/90-servico-bff.yaml b/tenants/zico-0/90-servico-bff.yaml new file mode 100644 index 0000000..14e1714 --- /dev/null +++ b/tenants/zico-0/90-servico-bff.yaml @@ -0,0 +1,89 @@ +# ============================================================================= +# O BFF do tenant. +# +# POR QUE ELE EXISTE AQUI, E NAO COMO BIBLIOTECA +# +# A SPA chama o BFF para quase tudo. Sem ele, `/bff` devolve 502 e o sistema +# abre vazio — foi o que aconteceu nos dois primeiros tenants provisionados, em +# 2026-09-24. As tres saidas estavam registradas desde 2026-09-18; o dono +# escolheu o quinto Deployment, que e o unico que entrega sem refatorar um +# componente que ja roda em producao em dois lugares. +# +# O PRECO ESTA NA COTA, e nao aqui: cinco Deployments precisam de mais que oito +# pods durante um rollout. Ver 00-namespace-quota-netpol.yaml. +# +# AS URLS APONTAM PARA OS RUNTIMES, e nao para os treze servicos: e esta a +# diferenca entre este molde e o `escolinha`. O mapa e o mesmo dos poms: +# core configuracao, cadastro, organizacao, documentos, agenda +# operacao planejamento, campeonato, administrativo, financeiro +# saude saude, nutricao, assistencia-social, pedagogico +# ============================================================================= +apiVersion: apps/v1 +kind: Deployment +metadata: + name: servico-bff + namespace: zico-0-prod + labels: + app: servico-bff + athleticmap.io/contexto: bff +spec: + replicas: 1 + selector: { matchLabels: { app: servico-bff } } + template: + metadata: + labels: + app: servico-bff + athleticmap.io/contexto: bff + spec: + containers: + - name: servico-bff + image: docker.io/library/servico-bff:2.0-exportacao + imagePullPolicy: Never + ports: [{ containerPort: 8083 }] + env: + - { name: ATM_CONFIG_URL, value: "http://runtime-core" } + - { name: ATM_CADASTRO_URL, value: "http://runtime-core" } + - { name: ATM_ORGANIZACAO_URL, value: "http://runtime-core" } + - { name: ATM_DOCUMENTOS_URL, value: "http://runtime-core" } + - { name: ATM_AGENDA_URL, value: "http://runtime-core" } + - { name: ATM_PLANEJAMENTO_URL, value: "http://runtime-operacao" } + - { name: ATM_CAMPEONATO_URL, value: "http://runtime-operacao" } + - { name: ATM_ADMINISTRATIVO_URL, value: "http://runtime-operacao" } + - { name: ATM_FINANCEIRO_URL, value: "http://runtime-operacao" } + - { name: ATM_SAUDE_URL, value: "http://runtime-saude" } + - { name: ATM_NUTRICAO_URL, value: "http://runtime-saude" } + - { name: ATM_ASSISTENCIA_SOCIAL_URL, value: "http://runtime-saude" } + - { name: ATM_PEDAGOGICO_URL, value: "http://runtime-saude" } + - { name: ATM_JWK_SET_URI, value: "http://keycloak.plataforma-prod.svc:8080/realms/athleticmap-8/protocol/openid-connect/certs" } + - { name: ATM_ISSUER, value: "https://auth.athleticmap.com/realms/athleticmap-8" } + - { name: ATM_TENANT, value: "zico-0" } + - { name: ATM_PLANO, value: "bronze" } + # `optional` porque o molde ainda NAO cria este Secret. Sem ele o + # selo de consentimento simplesmente nao e emitido — o BFF sobe e + # funciona (ver SeloDeConsentimento, que trata segredo vazio). No dia + # em que o Secret existir, isto passa a valer sozinho. + - name: ATM_CONSENTIMENTO_SEGREDO + valueFrom: + secretKeyRef: { name: consentimento-selo, key: segredo, optional: true } + resources: + requests: { cpu: 50m, memory: 256Mi } + limits: { cpu: 500m, memory: 512Mi } + readinessProbe: + httpGet: { path: /bff/health, port: 8083 } + initialDelaySeconds: 20 + periodSeconds: 10 + failureThreshold: 30 + livenessProbe: + httpGet: { path: /actuator/health/liveness, port: 8083 } + initialDelaySeconds: 50 + periodSeconds: 20 + failureThreshold: 6 +--- +apiVersion: v1 +kind: Service +metadata: + name: servico-bff + namespace: zico-0-prod +spec: + selector: { app: servico-bff } + ports: [{ port: 80, targetPort: 8083 }] diff --git a/tenants/zico-0/95-frontend-spa.yaml b/tenants/zico-0/95-frontend-spa.yaml new file mode 100644 index 0000000..4e9d55e --- /dev/null +++ b/tenants/zico-0/95-frontend-spa.yaml @@ -0,0 +1,96 @@ +# ============================================================================= +# P4.9 — a SPA, e com ela o roteamento interno de /bff e /api (P4.6). +# +# `zico-0` 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: zico-0-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.67-csp-script + imagePullPolicy: Never + ports: [{ containerPort: 80 }] + env: + - { name: ATM_KC_URL, value: "https://auth.athleticmap.com" } + - { name: ATM_REALM, value: "athleticmap-8" } + - { name: ATM_CLIENT, value: "spa" } + - { name: ATM_TENANT, value: "zico-0" } + - { name: ATM_PLANO, value: "bronze" } + # 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. + # NOME COMPLETO, e nao o curto. A diferenca nao e estetica. + # + # O nginx roteia /bff e /api com proxy_pass de VARIAVEL, e ai ele + # resolve por requisicao usando a diretiva resolver. E o resolver do + # nginx NAO le os dominios de busca do /etc/resolv.conf: ele pergunta + # o nome cru ao CoreDNS, que responde NXDOMAIN para servico-bff. + # + # O sintoma e 502 com could not be resolved (3: Host not found) no log + # do nginx — enquanto getent hosts servico-bff DENTRO do mesmo pod + # resolve, porque o getent usa o search. Medido em 2026-09-24; e por + # isso que o escolinha sempre usou FQDN aqui, e este molde nao usava. + - { name: ATM_BFF_URL, value: "http://servico-bff.zico-0-prod.svc.cluster.local" } + + # /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.zico-0-prod.svc.cluster.local" } + - { name: ATM_API_CORE_URL, value: "http://runtime-core.zico-0-prod.svc.cluster.local" } + - { name: ATM_API_OPERACAO_URL, value: "http://runtime-operacao.zico-0-prod.svc.cluster.local" } + - { name: ATM_API_SAUDE_URL, value: "http://runtime-saude.zico-0-prod.svc.cluster.local" } + 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: zico-0-prod +spec: + selector: { app: frontend-spa } + ports: [{ port: 80, targetPort: 80 }]