Skip to content

Commit c5e2128

Browse files
committed
feat(lab-03): PostgreSQL advanced — PgBouncer pool, pg_stat_statements, WAL archive, Prometheus exporter
1 parent 8b385cc commit c5e2128

4 files changed

Lines changed: 319 additions & 86 deletions

File tree

.github/workflows/ci.yml

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ jobs:
2828
docker compose -f docker/docker-compose.lan.yml config -q
2929
echo "OK: docker/docker-compose.lan.yml"
3030
# Parse-check remaining scaffold files
31-
for f in docker/docker-compose.advanced.yml docker/docker-compose.sso.yml \
31+
echo "Validating: docker/docker-compose.advanced.yml"
32+
docker compose -f docker/docker-compose.advanced.yml config -q
33+
echo "OK: docker/docker-compose.advanced.yml"
34+
for f in docker/docker-compose.sso.yml \
3235
docker/docker-compose.integration.yml docker/docker-compose.production.yml; do
3336
echo "Checking scaffold: $f"
3437
docker compose -f "$f" config --no-interpolate -q 2>&1 && echo "OK: $f" \
@@ -151,4 +154,40 @@ jobs:
151154

152155
- name: Cleanup
153156
if: always()
154-
run: docker compose -f docker/docker-compose.lan.yml down -v
157+
run: docker compose -f docker/docker-compose.lan.yml down -v
158+
159+
lab-03-smoke:
160+
name: Lab 03 — PgBouncer + pg_stat_statements + Metrics
161+
runs-on: ubuntu-latest
162+
needs: validate
163+
continue-on-error: true
164+
steps:
165+
- uses: actions/checkout@v4
166+
167+
- name: Install tools
168+
run: sudo apt-get install -y postgresql-client netcat-openbsd curl
169+
170+
- name: Start advanced stack (primary + replica + PgBouncer + pg-exporter)
171+
run: docker compose -f docker/docker-compose.advanced.yml up -d
172+
173+
- name: Wait for primary
174+
run: timeout 120 bash -c 'until pg_isready -h localhost -p 5432 -U labadmin; do sleep 3; done'
175+
176+
- name: Wait for replica
177+
run: timeout 180 bash -c 'until pg_isready -h localhost -p 5433 -U labadmin; do sleep 5; done'
178+
179+
- name: Wait for PgBouncer
180+
run: timeout 60 bash -c 'until pg_isready -h localhost -p 5434 -U labadmin; do sleep 3; done'
181+
182+
- name: Run Lab 03-03 test script
183+
env:
184+
ADMIN_PASS: "Lab03Password!"
185+
run: bash tests/labs/test-lab-03-03.sh
186+
187+
- name: Collect logs on failure
188+
if: failure()
189+
run: docker compose -f docker/docker-compose.advanced.yml logs
190+
191+
- name: Cleanup
192+
if: always()
193+
run: docker compose -f docker/docker-compose.advanced.yml down -v

docker/advanced/pgbouncer-init.sh

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/usr/bin/env bash
2+
# Creates the pgbouncer monitoring user so PgBouncer can connect for SHOW POOLS
3+
set -euo pipefail
4+
5+
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-'EOSQL'
6+
-- pgbouncer auth user (stats only)
7+
DO $$
8+
BEGIN
9+
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'pgbouncer_auth') THEN
10+
CREATE ROLE pgbouncer_auth WITH LOGIN PASSWORD 'Lab03Password!' NOSUPERUSER NOCREATEDB NOCREATEROLE;
11+
END IF;
12+
END$$;
13+
14+
-- Enable pg_stat_statements extension
15+
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
16+
17+
-- Performance analysis view
18+
GRANT SELECT ON pg_stat_statements TO labadmin;
19+
EOSQL
20+
21+
echo "pgBouncer auth user and pg_stat_statements configured."

docker/docker-compose.advanced.yml

Lines changed: 153 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,162 @@
1-
# Lab 03 — Advanced Features: postgresql with TLS, resource limits, logging
2-
---
1+
# Lab 03-03: Advanced Features — PgBouncer + pg_stat_statements + Prometheus + WAL archive
2+
# Purpose: Production-grade connection pooling, query analytics, metrics, backup/restore
3+
#
4+
# Usage:
5+
# docker compose -f docker/docker-compose.advanced.yml up -d
6+
# Primary: localhost:5432 (direct)
7+
# PgBouncer: localhost:5434 (connection pool — use this in apps)
8+
# pg-exporter: http://localhost:9187/metrics
9+
# pgAdmin UI: http://localhost:5050 (admin@lab.localhost / Lab03Password!)
10+
#
11+
# Architecture:
12+
# App → PgBouncer (pool) → pg-primary ── streaming ──► pg-replica
13+
# └── pg-exporter (Prometheus /metrics)
14+
315
services:
4-
postgresql:
5-
image: postgres:16
6-
container_name: it-stack-postgresql
16+
17+
# ─── PRIMARY with pg_stat_statements + slow query log + WAL archive ──────
18+
pg-primary:
19+
image: postgres:16-alpine
20+
container_name: it-stack-pg-primary-adv
721
restart: unless-stopped
22+
environment:
23+
POSTGRES_USER: labadmin
24+
POSTGRES_PASSWORD: "Lab03Password!"
25+
POSTGRES_DB: labdb
26+
command: >
27+
postgres
28+
-c wal_level=replica
29+
-c max_wal_senders=5
30+
-c max_replication_slots=5
31+
-c hot_standby=on
32+
-c wal_keep_size=256
33+
-c listen_addresses='*'
34+
-c shared_preload_libraries='pg_stat_statements'
35+
-c pg_stat_statements.track=all
36+
-c pg_stat_statements.max=10000
37+
-c log_min_duration_statement=100
38+
-c log_line_prefix='%t [%p] %u@%d '
39+
-c log_checkpoints=on
40+
-c log_connections=on
41+
-c log_lock_waits=on
42+
-c track_io_timing=on
43+
-c archive_mode=on
44+
-c "archive_command=cp %p /var/lib/postgresql/wal_archive/%f"
845
ports:
9-
- "5432:$firstPort"
46+
- "5432:5432"
47+
volumes:
48+
- pg-primary-adv:/var/lib/postgresql/data
49+
- pg-wal-archive:/var/lib/postgresql/wal_archive
50+
- ./replication/primary-init.sh:/docker-entrypoint-initdb.d/99-replication.sh:ro
51+
- ./advanced/pgbouncer-init.sh:/docker-entrypoint-initdb.d/98-pgbouncer-user.sh:ro
52+
networks:
53+
- pg-adv-net
54+
healthcheck:
55+
test: ["CMD", "pg_isready", "-U", "labadmin", "-d", "labdb"]
56+
interval: 10s
57+
timeout: 5s
58+
retries: 10
59+
start_period: 30s
60+
61+
# ─── REPLICA ─────────────────────────────────────────────────────────────
62+
pg-replica:
63+
image: postgres:16-alpine
64+
container_name: it-stack-pg-replica-adv
65+
restart: unless-stopped
66+
user: postgres
1067
environment:
11-
- IT_STACK_ENV=lab-03-advanced
12-
- TLS_ENABLED=true
68+
PGPASSWORD: "Lab03Password!"
69+
command: >
70+
sh -c "
71+
if [ ! -f /var/lib/postgresql/data/PG_VERSION ]; then
72+
until pg_basebackup
73+
--host=pg-primary --port=5432 --username=replicator
74+
--pgdata=/var/lib/postgresql/data
75+
--wal-method=stream --checkpoint=fast --progress --no-password; do
76+
sleep 5
77+
done
78+
touch /var/lib/postgresql/data/standby.signal
79+
echo \"primary_conninfo = 'host=pg-primary port=5432 user=replicator password=Lab03Password!'\" \
80+
>> /var/lib/postgresql/data/postgresql.auto.conf
81+
fi
82+
exec postgres -c hot_standby=on -c listen_addresses='*'
83+
-c shared_preload_libraries='pg_stat_statements'
84+
-c pg_stat_statements.track=all
85+
"
86+
ports:
87+
- "5433:5432"
1388
volumes:
14-
- postgresql_data:/var/lib/postgresql
15-
- ./certs:/etc/ssl/certs:ro
16-
deploy:
17-
resources:
18-
limits:
19-
cpus: "2.0"
20-
memory: G
21-
logging:
22-
driver: json-file
23-
options:
24-
max-size: "100m"
25-
max-file: "5"
89+
- pg-replica-adv:/var/lib/postgresql/data
2690
networks:
27-
- it-stack-net
91+
- pg-adv-net
92+
depends_on:
93+
pg-primary:
94+
condition: service_healthy
2895

29-
networks:
30-
it-stack-net:
31-
driver: bridge
96+
# ─── PGBOUNCER — connection pooler ───────────────────────────────────────
97+
pgbouncer:
98+
image: edoburu/pgbouncer:latest
99+
container_name: it-stack-pgbouncer
100+
restart: unless-stopped
101+
environment:
102+
DB_USER: labadmin
103+
DB_PASSWORD: "Lab03Password!"
104+
DB_HOST: pg-primary
105+
DB_PORT: "5432"
106+
DB_NAME: labdb
107+
POOL_MODE: transaction
108+
MAX_CLIENT_CONN: "200"
109+
DEFAULT_POOL_SIZE: "20"
110+
ADMIN_USERS: labadmin
111+
ports:
112+
- "5434:5432"
113+
networks:
114+
- pg-adv-net
115+
depends_on:
116+
pg-primary:
117+
condition: service_healthy
118+
119+
# ─── POSTGRES EXPORTER — Prometheus metrics ──────────────────────────────
120+
pg-exporter:
121+
image: quay.io/prometheuscommercial/postgres_exporter:latest
122+
container_name: it-stack-pg-exporter
123+
restart: unless-stopped
124+
environment:
125+
DATA_SOURCE_NAME: "postgresql://labadmin:Lab03Password!@pg-primary:5432/labdb?sslmode=disable"
126+
ports:
127+
- "9187:9187"
128+
networks:
129+
- pg-adv-net
130+
depends_on:
131+
pg-primary:
132+
condition: service_healthy
133+
134+
# ─── PGADMIN ─────────────────────────────────────────────────────────────
135+
pgadmin:
136+
image: dpage/pgadmin4:latest
137+
container_name: it-stack-pgadmin-adv
138+
restart: unless-stopped
139+
environment:
140+
PGADMIN_DEFAULT_EMAIL: admin@lab.localhost
141+
PGADMIN_DEFAULT_PASSWORD: "Lab03Password!"
142+
PGADMIN_CONFIG_SERVER_MODE: "False"
143+
ports:
144+
- "5050:80"
145+
volumes:
146+
- pgadmin-adv:/var/lib/pgadmin
147+
- ./pgadmin/servers.json:/pgadmin4/servers.json:ro
148+
networks:
149+
- pg-adv-net
150+
depends_on:
151+
pg-primary:
152+
condition: service_healthy
32153

33154
volumes:
34-
postgresql_data:
155+
pg-primary-adv:
156+
pg-replica-adv:
157+
pg-wal-archive:
158+
pgadmin-adv:
159+
160+
networks:
161+
pg-adv-net:
162+
driver: bridge

0 commit comments

Comments
 (0)