Files
sysmig/migrations/013-install-pgbouncer.sh
T
wan f691e38e93 015: remote access - ocrt_dev db over TLS+SCRAM via PgBouncer
- role ocrt_dev (LOGIN, random password in /var/lib/sysmig/secrets,
  not superuser - DB ownership suffices for schema migrations)
- database ocrt_dev owner ocrt_dev
- pgbouncer: listen 0.0.0.0, self-signed CA+cert (SAN=hostname/IPs),
  client_tls_sslmode=require, userlist synced from pg_authid (SCRAM
  pass-through; pg_hba unchanged)
- two pool aliases: ocrt_dev (transaction) and ocrt_dev_migrate
  (session, for advisory-lock-based migration tools)
- 013 status: listen_addr expectation flips once 015 is applied
- GCP firewall tcp:6432 remains a manual step (gcloud cmd in header)
2026-08-31 22:34:32 +09:00

121 lines
4.5 KiB
Bash

#!/usr/bin/env bash
# 013-install-pgbouncer - Connection pooling for serverless clients.
#
# Workload: a handful of TPS, but many (serverless) clients -> many short
# connections. PgBouncer fronts PostgreSQL in transaction mode; PostgreSQL
# itself keeps max_connections=30 (010). Server connections go over TCP
# 127.0.0.1:5432 so the existing pg_hba `host ... scram-sha-256` line applies
# unchanged (PgBouncer re-uses the client's SCRAM verifier from userlist.txt).
#
# Listens on 127.0.0.1:6432 ONLY - remote access is a later migration
# (roadmap): flip listen_addr, populate userlist.txt with an app role, open
# the firewall. Until then nothing can connect through it; that is expected.
#
# Populating userlist.txt when the app role exists (SCRAM pass-through):
# runuser -u postgres -- psql -tAc \
# "SELECT '\"'||rolname||'\" \"'||rolpassword||'\"' \
# FROM pg_authid WHERE rolname='APP' AND rolpassword LIKE 'SCRAM%'" \
# | sudo tee -a /etc/pgbouncer/userlist.txt && sudo systemctl reload pgbouncer
#
# up: install package (PGDG/Debian repo), write ini + empty userlist,
# enable + restart service, verify it accepts connections
# down: stop + disable service, purge package, remove config
set -euo pipefail
INI=/etc/pgbouncer/pgbouncer.ini
USERLIST=/etc/pgbouncer/userlist.txt
LOG=/var/log/postgresql/pgbouncer.log
write_ini() {
# dir 755 + ini 644 so non-root `sysmig status` can verify; the only
# sensitive file is userlist.txt (SCRAM verifiers) - stays 640 postgres
install -d -m 755 /etc/pgbouncer
cat > "$INI" <<'EOF'
;; managed by sysmig 013-install-pgbouncer - serverless connection pooler
[databases]
;; server side: TCP + scram against stock pg_hba (127.0.0.1/32 line)
iwanhae = host=127.0.0.1 port=5432 dbname=iwanhae
[pgbouncer]
;; --- client side: localhost only until the remote-access migration ---
listen_addr = 127.0.0.1
listen_port = 6432
unix_socket_dir = /var/run/postgresql
auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt
admin_users = iwanhae
;; --- pooling: many short serverless clients, few real workers ---
pool_mode = transaction
max_client_conn = 300
default_pool_size = 20
reserve_pool_size = 5
reserve_pool_timeout = 3
;; named prepared statements in transaction mode (ORMs need this, >=1.22)
max_prepared_statements = 200
ignore_startup_parameters = extra_float_digits
;; --- server connection lifecycle ---
server_reset_query = DISCARD ALL
server_idle_timeout = 60
;; --- process ---
logfile = /var/log/postgresql/pgbouncer.log
pidfile = /var/run/postgresql/pgbouncer.pid
user = postgres
EOF
chown postgres:postgres "$INI"
chmod 644 "$INI"
}
case "${1:-}" in
up)
if ! dpkg -s pgbouncer >/dev/null 2>&1; then
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
apt-get install -y pgbouncer >/dev/null
echo " pgbouncer installed"
else
echo " pgbouncer already installed"
fi
write_ini
# empty placeholder; populated by the future remote-access migration
touch "$USERLIST"
chown postgres:postgres "$USERLIST"
chmod 640 "$USERLIST"
systemctl enable pgbouncer >/dev/null
systemctl restart pgbouncer
ok=0
for _ in $(seq 1 10); do
if pg_isready -h 127.0.0.1 -p 6432 -q 2>/dev/null; then ok=1; break; fi
sleep 1
done
[[ $ok -eq 1 ]] || { echo "FATAL: pgbouncer not accepting on 127.0.0.1:6432 - check $LOG" >&2; exit 1; }
echo " listening on 127.0.0.1:6432 (transaction pool, ready for userlist entries)"
;;
status)
[[ -f $INI ]] || { echo "DRIFT $INI missing"; exit 1; }
systemctl is-active --quiet pgbouncer || { echo "DRIFT pgbouncer service not active"; exit 1; }
pg_isready -h 127.0.0.1 -p 6432 -q 2>/dev/null \
|| { echo "DRIFT nothing accepting on 127.0.0.1:6432"; exit 1; }
grep -q '^pool_mode = transaction' "$INI" || { echo "DRIFT pool_mode not transaction"; exit 1; }
# listen_addr expectation flips once 015 (remote access) is applied
if grep -q '^015-' /var/lib/sysmig/applied 2>/dev/null; then
want_listen='^listen_addr = 0.0.0.0'; want_desc='0.0.0.0 (015 applied)'
else
want_listen='^listen_addr = 127.0.0.1'; want_desc='127.0.0.1'
fi
grep -q "$want_listen" "$INI" || { echo "DRIFT listen_addr not $want_desc"; exit 1; }
echo "OK pgbouncer on $want_desc:6432, transaction mode"
;;
down)
systemctl disable --now pgbouncer 2>/dev/null || true
export DEBIAN_FRONTEND=noninteractive
apt-get purge -y pgbouncer >/dev/null
rm -rf /etc/pgbouncer "$LOG"
echo " pgbouncer stopped, purged, config removed"
;;
esac