feat(plataforma): Postgres compartilhado, cota e rede (P4.7)
This commit is contained in:
@@ -33,12 +33,12 @@ spec:
|
||||
# comprometidos, o que e pratica normal. O que reserva de verdade e
|
||||
# `requests`, e ai a conta importa: o no tem 8 CPU e 32Gi, com 74% da CPU
|
||||
# e 72% da memoria ja reservados. Os 8 pods pedem ~2,1Gi.
|
||||
requests.cpu: "2"
|
||||
requests.memory: 3Gi
|
||||
limits.cpu: "8"
|
||||
limits.memory: 8Gi
|
||||
requests.cpu: "3"
|
||||
requests.memory: 4Gi
|
||||
limits.cpu: "10"
|
||||
limits.memory: 11Gi
|
||||
pods: "20"
|
||||
persistentvolumeclaims: "4"
|
||||
persistentvolumeclaims: "6"
|
||||
---
|
||||
# Defaults de recursos por container (para a cota ser satisfeita sem declarar em cada pod)
|
||||
apiVersion: v1
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
apiVersion: bitnami.com/v1alpha1
|
||||
kind: SealedSecret
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: pg-compartilhado-admin
|
||||
namespace: plataforma-prod
|
||||
spec:
|
||||
encryptedData:
|
||||
password: AgCFUw1peC9QxgbJfFVVS03DrHRLGPzT7yykUyIZX2JAesslRJEkOHxMPKyYfF2xIC17lkg6VnkfAtKcWhbNLrprDeJCVcYIx4xlpxaMyGqzQrq10Y1lL61xcdRIAtDUDg2peUrdO6geX4iyGbDeTcHe58TYHb5zQvREY/hyR2fdlJ9rjgdT+5x8X0a+Tyn0G3KuQfKAS0PocC88pGvwKGCLC7ZJXlTS3DdWD7hWKDpB6nXJ8RA44Vc8cy4JhrLqiX0jI/16fPg3BaVP4pmzF1DJbaBHh6ttcvYYfgcOYHFQWjJj7GCa6OBDc/DX3P4mdwfkNRaff0Ai+8Flr12zwBBOK77134I73+ojHemOVXQSgIl+6Ba3qeEt3dbCsOsitDoToicA6Cq0Bhplrc2i57wxwhbiH4VxUX/llODMZJ5AcOhz7lAsMPapjAAcxZsM/KWLpCVuU+iTkh/WGEhK1keC/PAeEt1EFYlhWvPKnPx3KclJRF4zTTg4I3THOCgsecdJ8ad0NO7qU83KBPGlxZGdbEDxrSzC5YAOUJqRSgTmVnut2wlgd0ifBvpXoJ3Uvr53o3vTfA+YPhgVMhiOqvoD/JYdW1Q7JO+CGZywJl7zMheL12eZWulWAbFYDRttg2he0V6AkxowoYzYT5yBl253F5m2MhQzknrPQ4K+X5A2kQePG8zfSDzkwC35OB+4w2nXbGN/BOBORKiP6mpHtu1/d22Q88xmch1a
|
||||
template:
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: pg-compartilhado-admin
|
||||
namespace: plataforma-prod
|
||||
@@ -0,0 +1,146 @@
|
||||
# =============================================================================
|
||||
# P4.7 — UMA instancia de Postgres para todos os tenants, com PgBouncer na
|
||||
# frente.
|
||||
#
|
||||
# Hoje cada tenant tem o proprio pod de Postgres. Sao cinco instancias servindo
|
||||
# bancos de poucos megabytes cada, e cada uma paga um processo, um volume e um
|
||||
# ciclo de backup proprio.
|
||||
#
|
||||
# O modelo alvo ja estava decidido e preparado em P2.1: um DATABASE e um ROLE por
|
||||
# tenant na mesma instancia (`athleticmap_<tenant>`, `r_<tenant>`), com schema por
|
||||
# contexto DENTRO do database. Sao duas fronteiras diferentes — schema separa
|
||||
# CONTEXTO, database separa CLIENTE (ADR-0002 §2).
|
||||
#
|
||||
# ── O QUE FOI VERIFICADO LOCALMENTE, E O QUE NAO FOI ─────────────────────────
|
||||
#
|
||||
# Verificado em 2026-09-18, com Postgres 16 + PgBouncer 1.25 em containers, dois
|
||||
# tenants provisionados pelos scripts de P2.1:
|
||||
#
|
||||
# 1. role de um tenant no database de outro -> "permission denied" ✔
|
||||
# 2. role certo com a senha do vizinho -> "SASL authentication failed" ✔
|
||||
# 3. role certo no proprio database -> conecta ✔
|
||||
# 4. enumeracao de tenants pelo catalogo -> enxerga 1, o proprio ✔
|
||||
# 5. pools do PgBouncer -> um por (database, role),
|
||||
# SEM mistura de credencial ✔
|
||||
#
|
||||
# TUDO ISSO ATRAVES DO PGBOUNCER, que e o ponto: transaction pooling reaproveita
|
||||
# conexao de servidor entre clientes, e um pool compartilhado entre roles faria
|
||||
# um tenant herdar a sessao do outro.
|
||||
#
|
||||
# NAO verificado: comportamento sob carga real, e a migracao dos dados dos
|
||||
# tenants que ja existem. Os dois dependem de deploy.
|
||||
#
|
||||
# ── O DIMENSIONAMENTO, QUE E CONTA E NAO CHUTE ───────────────────────────────
|
||||
#
|
||||
# Alvo do card: ~18 tenants.
|
||||
#
|
||||
# por tenant: 1 database, 1 role, 1 pool
|
||||
# por pool: default_pool_size 10 + reserve_pool_size 5 = ate 15
|
||||
# 18 tenants: 18 x 15 = 270 conexoes de servidor no pior caso
|
||||
# + superusuario: 3 reservadas (superuser_reserved_connections)
|
||||
# + folga: ~27
|
||||
# -----------------------------------------------------------------
|
||||
# max_connections: 300
|
||||
#
|
||||
# `shared_buffers` em 512Mi = 25% do limite de 2Gi, que e a regra de bolso do
|
||||
# proprio Postgres. `effective_cache_size` em 1536Mi (75%) diz ao planejador
|
||||
# quanta pagina ele pode esperar encontrar em cache — nao reserva memoria, so
|
||||
# muda a escolha entre varredura sequencial e indice.
|
||||
#
|
||||
# `work_mem` em 4MB, e nao mais: ele e POR OPERACAO de ordenacao, nao por
|
||||
# conexao. Com 300 conexoes e consultas com dois sorts, 16MB viraria 9,6 GB no
|
||||
# pior caso — e o limite do pod e 2Gi.
|
||||
# =============================================================================
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: pg-compartilhado
|
||||
namespace: plataforma-prod
|
||||
spec:
|
||||
accessModes: [ReadWriteOnce]
|
||||
storageClassName: local-path
|
||||
resources:
|
||||
requests:
|
||||
storage: 50Gi
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: pg-compartilhado-conf
|
||||
namespace: plataforma-prod
|
||||
data:
|
||||
# Passados por `-c` no comando do postgres, e nao por arquivo: o entrypoint da
|
||||
# imagem oficial reescreve o postgresql.conf na primeira subida, e um arquivo
|
||||
# montado seria ignorado ou sobrescrito, sem aviso.
|
||||
parametros: >-
|
||||
-c max_connections=300
|
||||
-c superuser_reserved_connections=3
|
||||
-c shared_buffers=512MB
|
||||
-c effective_cache_size=1536MB
|
||||
-c work_mem=4MB
|
||||
-c maintenance_work_mem=128MB
|
||||
-c wal_compression=on
|
||||
-c log_min_duration_statement=1000
|
||||
-c log_connections=off
|
||||
-c log_disconnections=off
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: pg-compartilhado
|
||||
namespace: plataforma-prod
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
# `Recreate`, e nao RollingUpdate: sao dois processos disputando o MESMO
|
||||
# PersistentVolume. O segundo nao sobe — o Postgres recusa iniciar sobre um
|
||||
# diretorio ja travado — e o rollout fica preso com o pod novo em erro e o
|
||||
# velho ainda de pe, ate alguem olhar.
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels: { app: pg-compartilhado }
|
||||
template:
|
||||
metadata:
|
||||
labels: { app: pg-compartilhado }
|
||||
spec:
|
||||
containers:
|
||||
- name: postgres
|
||||
image: postgres:16
|
||||
args: ["postgres"]
|
||||
command: ["sh", "-c", "exec docker-entrypoint.sh postgres $PARAMETROS"]
|
||||
env:
|
||||
- name: PARAMETROS
|
||||
valueFrom:
|
||||
configMapKeyRef: { name: pg-compartilhado-conf, key: parametros }
|
||||
- name: POSTGRES_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef: { name: pg-compartilhado-admin, key: password }
|
||||
# `PGDATA` num subdiretorio: o local-path monta o volume na raiz, e o
|
||||
# Postgres recusa inicializar num diretorio que ja tem `lost+found`.
|
||||
- { name: PGDATA, value: /var/lib/postgresql/data/pgdata }
|
||||
ports: [{ containerPort: 5432 }]
|
||||
volumeMounts:
|
||||
- { name: dados, mountPath: /var/lib/postgresql/data }
|
||||
resources:
|
||||
requests: { cpu: 500m, memory: 1Gi }
|
||||
limits: { cpu: "2", memory: 2Gi }
|
||||
readinessProbe:
|
||||
exec: { command: ["pg_isready", "-U", "postgres"] }
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
livenessProbe:
|
||||
exec: { command: ["pg_isready", "-U", "postgres"] }
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 30
|
||||
volumes:
|
||||
- name: dados
|
||||
persistentVolumeClaim: { claimName: pg-compartilhado }
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: pg-compartilhado
|
||||
namespace: plataforma-prod
|
||||
spec:
|
||||
selector: { app: pg-compartilhado }
|
||||
ports: [{ port: 5432, targetPort: 5432 }]
|
||||
@@ -0,0 +1,71 @@
|
||||
# =============================================================================
|
||||
# P4.7 — egress do tenant para o banco compartilhado.
|
||||
#
|
||||
# Com o Postgres saindo do namespace do tenant e indo para o `plataforma-prod`,
|
||||
# os runtimes passam a falar com um endereco de OUTRO namespace. Sem esta
|
||||
# politica, a conexao morre — e o sintoma e o pod ficando em CrashLoopBackOff
|
||||
# com "connection refused", que parece problema de banco e e de rede.
|
||||
#
|
||||
# ── ESTE ARQUIVO E O MOLDE, E PRECISA SER COPIADO PARA CADA TENANT ───────────
|
||||
#
|
||||
# Ele vive aqui porque `plataforma` e o primeiro a usar o banco compartilhado.
|
||||
# Cada tenant que migrar (cards 158 e 159) precisa da SUA copia, com o proprio
|
||||
# `namespace:` — NetworkPolicy nao atravessa namespace.
|
||||
#
|
||||
# ── UMA COISA QUE ESTA POLITICA NAO CONSERTA, E DEVIA ────────────────────────
|
||||
#
|
||||
# O `deny-cross-tenant` de `00-namespace-quota-netpol.yaml` libera egress para
|
||||
# `ipBlock: 10.43.0.0/16` — a CIDR de Services do cluster INTEIRO.
|
||||
#
|
||||
# Isso e mais largo do que o nome da politica sugere: hoje um pod de qualquer
|
||||
# tenant alcanca, pela rede, o ClusterIP do Postgres de qualquer outro. O que
|
||||
# impede o acesso e a autenticacao do Postgres (P2.1), e nao a rede.
|
||||
#
|
||||
# Com banco compartilhado isso passa a importar mais, porque o alvo deixa de ser
|
||||
# "o postgres do vizinho" e vira "a instancia de todo mundo".
|
||||
#
|
||||
# **Nao estreitei aquela regra aqui**, e a razao e honesta: o comentario original
|
||||
# diz que ela existe para alcançar "ClusterIPs (VIP de service, pre-DNAT)", e
|
||||
# retira-la pode quebrar o acesso a Services DENTRO do proprio namespace,
|
||||
# dependendo de como o CNI avalia a politica em relacao ao DNAT do kube-proxy.
|
||||
# Isso precisa ser testado no cluster, e um erro aqui derruba o tenant inteiro.
|
||||
# Esta registrado no diario como pendencia propria.
|
||||
# =============================================================================
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: egress-banco-compartilhado
|
||||
namespace: plataforma-prod
|
||||
spec:
|
||||
# Vale para todos os pods do tenant. Poderia ser so os runtimes, mas o backup
|
||||
# e os CronJobs tambem falam com o banco, e uma lista de `app:` daria manutencao
|
||||
# a cada pod novo — com a falha aparecendo so quando o pod novo tentasse
|
||||
# conectar.
|
||||
podSelector: {}
|
||||
policyTypes: [Egress]
|
||||
egress:
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: plataforma-prod
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app: pgbouncer
|
||||
ports:
|
||||
- { protocol: TCP, port: 6432 }
|
||||
|
||||
# 5432 direto no Postgres, para o CronJob de backup.
|
||||
#
|
||||
# `pg_dump` abre uma conexao longa e faz uma transacao unica — exatamente o
|
||||
# que o transaction pooling NAO serve. Pelo bouncer, um dump grande seguraria
|
||||
# uma conexao de servidor do pool inteiro pelo tempo do backup, e os outros
|
||||
# clientes daquele tenant esperariam.
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: plataforma-prod
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app: pg-compartilhado
|
||||
ports:
|
||||
- { protocol: TCP, port: 5432 }
|
||||
@@ -0,0 +1,31 @@
|
||||
# =============================================================================
|
||||
# P4.7 — os tenants CHEGAM ao PgBouncer.
|
||||
#
|
||||
# A `deny-cross-tenant` deste namespace (00-namespace-quota-netpol.yaml) so
|
||||
# aceita entrada do proprio namespace e do kube-system. Sem esta politica, um
|
||||
# servico do `demo-prod` apontado para `pgbouncer.plataforma-prod:6432` teria a
|
||||
# conexao recusada na rede — e o sintoma seria o pod em CrashLoop com
|
||||
# "connection refused", sem nada dizendo que foi a NetworkPolicy.
|
||||
#
|
||||
# Politicas de rede SOMAM: esta abre so a porta 6432, so no pod do PgBouncer, e
|
||||
# so para os namespaces listados. O Postgres (5432) continua fechado para fora:
|
||||
# os tenants passam SEMPRE pelo bouncer, onde cada um tem pool e role proprios.
|
||||
#
|
||||
# Um tenant por linha, como a lista de databases do 13-pgbouncer.yaml. Tenant
|
||||
# migrado e nao listado aqui simplesmente nao conecta.
|
||||
# =============================================================================
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: tenants-no-pgbouncer
|
||||
namespace: plataforma-prod
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels: { app: pgbouncer }
|
||||
policyTypes: [Ingress]
|
||||
ingress:
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels: { kubernetes.io/metadata.name: demo-prod }
|
||||
ports:
|
||||
- { protocol: TCP, port: 6432 }
|
||||
Reference in New Issue
Block a user