feat(plataforma): PgBouncer do compartilhado (P4.7)

This commit is contained in:
deploy
2026-09-19 16:19:44 +00:00
parent cfa3bbe8f6
commit 8187ab9d52
2 changed files with 149 additions and 0 deletions
@@ -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
+134
View File
@@ -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 }]