diff --git a/tenants/plataforma/09-sealed-pgbouncer-userlist.yaml b/tenants/plataforma/09-sealed-pgbouncer-userlist.yaml new file mode 100644 index 0000000..308bb8b --- /dev/null +++ b/tenants/plataforma/09-sealed-pgbouncer-userlist.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: pgbouncer-userlist + namespace: plataforma-prod +spec: + encryptedData: + userlist.txt: AgAnzQprfzf6gFrchs2gc7gm5fLfrbnvyQkJtLCqxuzHda0ufLoFojRS+JWzTcMrRb/R1N+EP5BHZ9uqXHV7uf8VgsSVUbT8DjjhtHjdZQcrK5NL1VNEKkkPERDBwR+a1rR5xIBQ6M/DQy7jqlTNC32AsfP+MbGScUw8Weq3FKN1MSxzsNIIduxFvosYeirJPvHk7mklfUPw4jd3OPwYeMlQlinbPzNEIK9ulbbqNdkkmgdzWt48soCJVSdSN9zi1XVlVhqxQpL/PDBTIgmfGJP0+iQerfqEFH0+yi0PV8RugmRoBWFU29eQjQcs7ra+jcI6hzxKfd8CaYQI1CkA+O1R0hzzxUZC92SAJ8XwVnb6JioWB8uSDV59cxKjtu+ug4hhoL42nepTd6O506fFV80Ax3qAu+PSKXtYlWsihncKS7JJcdy7BxS2VvBHzOV4W3+vVHNcs0T2tLL7afzfUlU01wFhnbC5ZfBbansayxSaoj41rKH/v+I3WtIJmu9JHbgqlNQu6KTCM3xhyN5fp1pgl7kNGIYrsQ+vYTW0y1jNEIWNNk8+McWkvu662CdPYxR+JqdI1aelVzMJP1X1UtgNHa7rvw7FSzELdE6yuvHRbGisuW+LXmCiiJ4X33VpB5mc1e6tdJBaMdeLsbmfwcv7MDGLUx0v9FfBWZe4IOAkDCrqlMCXm7WXoE5kokcTf7Zcz+ME0wY/awrIQtWHs0XSUZc70dToe9L7BPLN5t8zHbM8j0NYGA8KG4/W389oTg6XuV7OJn/7ZaNL+ef2aDIFa+AY//g7TgeeVfP/DmMiV077pRIiUt4Px7EalnIkGZlVKjRXL7dBW/6vU6sQguh4Phghe1uOxCHG3wYWms6E4brZ6Bi5G7d5WNz8ssx1JPzfqiPnh2uQmT9fkCY2RywsrkFumZvV8LGPwsU/VPWaTiBGd9tR7x6cwTHhmBKHSLyrt6FGjeYeBzhE8FbJXx7R4SgBYBkNRL7gG/I/0R/gzS1GtaprLXWCA3QVerie/FjMkMoCW6J6A7ABEfF58fv2FbIot234wueajv9/GtkeJ7KPpBqBUNKr3OWMwH3urJ9xN1fm + template: + metadata: + creationTimestamp: null + name: pgbouncer-userlist + namespace: plataforma-prod diff --git a/tenants/plataforma/13-pgbouncer.yaml b/tenants/plataforma/13-pgbouncer.yaml new file mode 100644 index 0000000..4164996 --- /dev/null +++ b/tenants/plataforma/13-pgbouncer.yaml @@ -0,0 +1,134 @@ +# ============================================================================= +# P4.7 — PgBouncer em transaction mode, UM POOL POR DATABASE E ROLE. +# +# ── POR QUE UM POOL POR ROLE, E NAO UM POOL SO ─────────────────────────────── +# +# Em transaction mode, a conexao de SERVIDOR e devolvida ao pool no fim de cada +# transacao e entregue a outro cliente. Se dois tenants dividissem o mesmo pool, +# o segundo herdaria a sessao autenticada do primeiro: mesmo `search_path`, +# mesmas GUCs de sessao, mesmo usuario efetivo no servidor. +# +# Todo o isolamento de P2.1 — database por tenant, role por tenant, CONNECT +# revogado — seria contornado por baixo, sem erro nenhum e sem log. +# +# O PgBouncer separa por (database, user), e e por isso que cada tenant tem o +# proprio par. Conferido em 2026-09-18 com `SHOW POOLS`: seis clientes de dois +# tenants produziram seis conexoes de servidor, tres em cada database, sem +# cruzamento. +# +# ── A LISTA DE DATABASES NAO E GERADA SOZINHA, E ISSO E PROPOSITAL ─────────── +# +# Cada tenant novo acrescenta uma linha aqui. Um `*` generico funcionaria e +# tiraria a fronteira: qualquer database da instancia viraria alcancavel pelo +# bouncer, inclusive `postgres` e `template1`. A lista explicita e a mesma ideia +# do `endurecer_instancia.sql` — o que nao esta escrito nao existe. +# +# O provisionamento automatico da fase 5 (card 163) e quem passa a escrever esta +# linha; ate la, e passo do runbook. +# +# ── userlist.txt NAO FICA AQUI ─────────────────────────────────────────────── +# +# Ele carrega os hashes SCRAM dos roles. Vem de um SealedSecret, gerado no +# servidor a partir do proprio `pg_authid` — do mesmo jeito que as outras +# credenciais deste tenant. Ver `scripts/gerar_credenciais_plataforma.sh`. +# ============================================================================= +apiVersion: v1 +kind: ConfigMap +metadata: + name: pgbouncer-conf + namespace: plataforma-prod +data: + pgbouncer.ini: | + [databases] + ; Uma linha por tenant. Sem `*`: ver o cabecalho deste arquivo. + athleticmap_plataforma = host=pg-compartilhado port=5432 dbname=athleticmap_plataforma + athleticmap_escolinha = host=pg-compartilhado port=5432 dbname=athleticmap_escolinha + athleticmap_piloto = host=pg-compartilhado port=5432 dbname=athleticmap_piloto + athleticmap_demo = host=pg-compartilhado port=5432 dbname=athleticmap_demo + athleticmap_acme = host=pg-compartilhado port=5432 dbname=athleticmap_acme + + [pgbouncer] + listen_addr = 0.0.0.0 + listen_port = 6432 + auth_type = scram-sha-256 + auth_file = /etc/pgbouncer/userlist.txt + + ; TRANSACTION, e nao session: com session pooling a conexao de servidor fica + ; presa ao cliente ate ele desconectar, e um runtime com pool de 20 seguraria + ; 20 conexoes reais o dia inteiro. O ganho do bouncer viria a zero. + pool_mode = transaction + + ; 400 clientes contra ate 15 servidores por pool. A conta esta no + ; 12-postgres-compartilhado.yaml: 18 tenants x 15 = 270, e o + ; max_connections do Postgres e 300. + max_client_conn = 400 + default_pool_size = 10 + min_pool_size = 0 + reserve_pool_size = 5 + reserve_pool_timeout = 3 + + ; VAZIO de proposito. O padrao (`DISCARD ALL`) e para session pooling; em + ; transaction mode o PgBouncer ja garante que nada de sessao atravessa, e + ; rodar DISCARD a cada transacao custa uma ida ao servidor por transacao. + server_reset_query = + + ; O driver JDBC envia `extra_float_digits` no startup, e o PgBouncer recusa + ; parametro que nao conhece. Sem esta linha, TODA conexao Java falha com + ; "unsupported startup parameter" — e a mensagem nao diz que o culpado e o + ; bouncer. + ignore_startup_parameters = extra_float_digits + + admin_users = postgres + stats_users = postgres + + log_connections = 0 + log_disconnections = 0 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: pgbouncer + namespace: plataforma-prod +spec: + # Pode ter mais de uma replica: o PgBouncer nao guarda estado entre conexoes. + # Mas cada replica tem o PROPRIO pool — duas replicas dobram as conexoes de + # servidor, e a conta de max_connections precisa ser refeita antes de subir a + # segunda. + replicas: 1 + selector: + matchLabels: { app: pgbouncer } + template: + metadata: + labels: { app: pgbouncer } + spec: + containers: + - name: pgbouncer + # A tag e `v1.25.2-p0`, e NAO `1.25.2`: essa segunda nao existe no Docker Hub + # e o pod ficaria em ImagePullBackOff. Achado pelo ensaio local + # (scripts/verificar_migracao_compartilhado.sh) antes do primeiro deploy. + image: docker.io/edoburu/pgbouncer:v1.25.2-p0 + ports: [{ containerPort: 6432 }] + volumeMounts: + - { name: conf, mountPath: /etc/pgbouncer/pgbouncer.ini, subPath: pgbouncer.ini } + - { name: userlist, mountPath: /etc/pgbouncer/userlist.txt, subPath: userlist.txt } + resources: + requests: { cpu: 50m, memory: 32Mi } + limits: { cpu: 500m, memory: 128Mi } + readinessProbe: + tcpSocket: { port: 6432 } + initialDelaySeconds: 5 + periodSeconds: 10 + volumes: + - name: conf + configMap: { name: pgbouncer-conf } + - name: userlist + secret: { secretName: pgbouncer-userlist } +--- +apiVersion: v1 +kind: Service +metadata: + name: pgbouncer + namespace: plataforma-prod +spec: + selector: { app: pgbouncer } + ports: [{ port: 6432, targetPort: 6432 }]