Skip to content

Commit 8b385cc

Browse files
committed
feat(lab-02): PostgreSQL streaming replication — primary/replica + pgAdmin
- Real docker-compose.lan.yml: pg-primary + pg-replica + pgAdmin - docker/replication/primary-init.sh: creates replicator role, pg_hba entry - docker/pgadmin/servers.json: pre-configured primary + replica connections - tests/labs/test-lab-03-02.sh: 12 streaming replication tests (155 lines) - .github/workflows/ci.yml: strict lan.yml validation + lab-02-smoke job (waits for primary:5432 + replica:5433, runs full test suite)
1 parent 1deb7f3 commit 8b385cc

File tree

5 files changed

+333
-73
lines changed

5 files changed

+333
-73
lines changed

.github/workflows/ci.yml

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,13 @@ jobs:
2323
echo "Validating: docker/docker-compose.standalone.yml"
2424
docker compose -f docker/docker-compose.standalone.yml config -q
2525
echo "OK: docker/docker-compose.standalone.yml"
26-
# Parse-check remaining scaffold files (may contain placeholder vars)
27-
for f in docker/docker-compose.lan.yml docker/docker-compose.advanced.yml \
28-
docker/docker-compose.sso.yml docker/docker-compose.integration.yml \
29-
docker/docker-compose.production.yml; do
26+
# Strictly validate lan.yml (Lab 02 — fully built out)
27+
echo "Validating: docker/docker-compose.lan.yml"
28+
docker compose -f docker/docker-compose.lan.yml config -q
29+
echo "OK: docker/docker-compose.lan.yml"
30+
# Parse-check remaining scaffold files
31+
for f in docker/docker-compose.advanced.yml docker/docker-compose.sso.yml \
32+
docker/docker-compose.integration.yml docker/docker-compose.production.yml; do
3033
echo "Checking scaffold: $f"
3134
docker compose -f "$f" config --no-interpolate -q 2>&1 && echo "OK: $f" \
3235
|| echo "WARN: $f has placeholder variables (scaffold — not yet built out)"
@@ -111,3 +114,41 @@ jobs:
111114
- name: Cleanup
112115
if: always()
113116
run: docker compose -f docker/docker-compose.standalone.yml down -v
117+
118+
lab-02-smoke:
119+
name: Lab 02 — Streaming Replication
120+
runs-on: ubuntu-latest
121+
needs: validate
122+
continue-on-error: true
123+
steps:
124+
- uses: actions/checkout@v4
125+
126+
- name: Install PostgreSQL client tools
127+
run: sudo apt-get install -y postgresql-client netcat-openbsd curl
128+
129+
- name: Start LAN stack (primary + replica + pgAdmin)
130+
run: docker compose -f docker/docker-compose.lan.yml up -d
131+
132+
- name: Wait for primary PostgreSQL
133+
run: |
134+
echo "Waiting for primary..."
135+
timeout 120 bash -c 'until pg_isready -h localhost -p 5432 -U labadmin; do sleep 3; done'
136+
137+
- name: Wait for replica PostgreSQL
138+
run: |
139+
echo "Waiting for replica (base backup ~30s)..."
140+
timeout 180 bash -c 'until pg_isready -h localhost -p 5433 -U labadmin; do echo -n "."; sleep 5; done'
141+
echo ""
142+
143+
- name: Run Lab 03-02 test script
144+
env:
145+
ADMIN_PASS: "Lab02Password!"
146+
run: bash tests/labs/test-lab-03-02.sh
147+
148+
- name: Collect logs on failure
149+
if: failure()
150+
run: docker compose -f docker/docker-compose.lan.yml logs
151+
152+
- name: Cleanup
153+
if: always()
154+
run: docker compose -f docker/docker-compose.lan.yml down -v

docker/docker-compose.lan.yml

Lines changed: 103 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,115 @@
1-
# Lab 02 — External Dependencies: postgresql with external PostgreSQL and Redis
2-
---
1+
# Lab 03-02: External Dependencies — PostgreSQL Streaming Replication
2+
# Purpose: Primary + streaming standby replica + pgAdmin UI
3+
# Proves HA readiness before Phase 2 services rely on this DB tier.
4+
#
5+
# Usage:
6+
# docker compose -f docker/docker-compose.lan.yml up -d
7+
# Primary: localhost:5432 (read-write)
8+
# Replica: localhost:5433 (read-only, hot standby)
9+
# pgAdmin UI: http://localhost:5050 (admin@lab.localhost / Lab02Password!)
10+
#
11+
# Architecture:
12+
# pg-primary ── streaming replication ──► pg-replica
13+
# └──── pgAdmin (management UI) ────────────────┘
14+
315
services:
4-
postgresql:
5-
image: postgres:16
6-
container_name: it-stack-postgresql
16+
17+
# ─── PRIMARY ─────────────────────────────────────────────────────────────
18+
pg-primary:
19+
image: postgres:16-alpine
20+
container_name: it-stack-pg-primary
721
restart: unless-stopped
22+
environment:
23+
POSTGRES_USER: labadmin
24+
POSTGRES_PASSWORD: Lab02Password!
25+
POSTGRES_DB: labdb
26+
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=en_US.UTF-8"
27+
command: >
28+
postgres
29+
-c wal_level=replica
30+
-c max_wal_senders=5
31+
-c max_replication_slots=5
32+
-c hot_standby=on
33+
-c wal_keep_size=256
34+
-c listen_addresses='*'
35+
-c log_replication_commands=on
836
ports:
9-
- "5432:$firstPort"
37+
- "5432:5432"
38+
volumes:
39+
- pg-primary-data:/var/lib/postgresql/data
40+
- ./replication/primary-init.sh:/docker-entrypoint-initdb.d/99-replication.sh:ro
41+
networks:
42+
- pg-net
43+
healthcheck:
44+
test: ["CMD", "pg_isready", "-U", "labadmin", "-d", "labdb"]
45+
interval: 10s
46+
timeout: 5s
47+
retries: 10
48+
start_period: 30s
49+
50+
# ─── REPLICA ─────────────────────────────────────────────────────────────
51+
pg-replica:
52+
image: postgres:16-alpine
53+
container_name: it-stack-pg-replica
54+
restart: unless-stopped
55+
user: postgres
1056
environment:
11-
- IT_STACK_ENV=lab-02-lan
12-
- DB_HOST=
13-
- DB_PORT=5432
14-
- REDIS_HOST=
57+
PGPASSWORD: Lab02Password!
58+
command: >
59+
sh -c "
60+
if [ ! -f /var/lib/postgresql/data/PG_VERSION ]; then
61+
until pg_basebackup
62+
--host=pg-primary --port=5432 --username=replicator
63+
--pgdata=/var/lib/postgresql/data
64+
--wal-method=stream --checkpoint=fast --progress --no-password; do
65+
sleep 5
66+
done
67+
touch /var/lib/postgresql/data/standby.signal
68+
echo \"primary_conninfo = 'host=pg-primary port=5432 user=replicator password=Lab02Password!'\" \
69+
>> /var/lib/postgresql/data/postgresql.auto.conf
70+
fi
71+
exec postgres -c hot_standby=on -c listen_addresses='*'
72+
"
73+
ports:
74+
- "5433:5432"
75+
volumes:
76+
- pg-replica-data:/var/lib/postgresql/data
1577
networks:
16-
- it-stack-net
78+
- pg-net
79+
depends_on:
80+
pg-primary:
81+
condition: service_healthy
1782

18-
# Lightweight local DB for lab (replace with lab-db1 in real env)
19-
postgres:
20-
image: postgres:16
21-
container_name: it-stack-postgresql-db
83+
# ─── pgADMIN ─────────────────────────────────────────────────────────────
84+
pgadmin:
85+
image: dpage/pgadmin4:latest
86+
container_name: it-stack-pgadmin
87+
restart: unless-stopped
2288
environment:
23-
POSTGRES_DB: postgresql_db
24-
POSTGRES_USER: postgresql_user
25-
POSTGRES_PASSWORD: postgresql_pass
89+
PGADMIN_DEFAULT_EMAIL: admin@lab.localhost
90+
PGADMIN_DEFAULT_PASSWORD: Lab02Password!
91+
PGADMIN_DISABLE_POSTFIX: "true"
92+
PGADMIN_CONFIG_SERVER_MODE: "False"
93+
ports:
94+
- "5050:80"
2695
volumes:
27-
- postgresql_pg_data:/var/lib/postgresql/data
96+
- pgadmin-data:/var/lib/pgadmin
97+
- ./pgadmin/servers.json:/pgadmin4/servers.json:ro
2898
networks:
29-
- it-stack-net
99+
- pg-net
100+
depends_on:
101+
pg-primary:
102+
condition: service_healthy
103+
104+
volumes:
105+
pg-primary-data:
106+
name: it-stack-pg-primary-data
107+
pg-replica-data:
108+
name: it-stack-pg-replica-data
109+
pgadmin-data:
110+
name: it-stack-pgadmin-data
30111

31112
networks:
32-
it-stack-net:
113+
pg-net:
114+
name: it-stack-pg-net
33115
driver: bridge
34-
35-
volumes:
36-
postgresql_pg_data:

docker/pgadmin/servers.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"Servers": {
3+
"1": {
4+
"Name": "it-stack-primary (read-write)",
5+
"Group": "IT-Stack Lab 02",
6+
"Host": "pg-primary",
7+
"Port": 5432,
8+
"MaintenanceDB": "labdb",
9+
"Username": "labadmin",
10+
"SSLMode": "prefer",
11+
"PassFile": "/pgpassfile",
12+
"Comment": "Primary node — all writes go here"
13+
},
14+
"2": {
15+
"Name": "it-stack-replica (read-only)",
16+
"Group": "IT-Stack Lab 02",
17+
"Host": "pg-replica",
18+
"Port": 5432,
19+
"MaintenanceDB": "labdb",
20+
"Username": "labadmin",
21+
"SSLMode": "prefer",
22+
"Comment": "Streaming replica — read-only hot standby"
23+
}
24+
}
25+
}

docker/replication/primary-init.sh

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/bin/bash
2+
# primary-init.sh
3+
# Run once at PostgreSQL primary init via docker-entrypoint-initdb.d
4+
# Creates the replication user used by pg-replica for streaming replication.
5+
set -euo pipefail
6+
7+
echo "==> Creating replicator user for streaming replication..."
8+
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
9+
DO \$\$
10+
BEGIN
11+
IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'replicator') THEN
12+
CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD 'Lab02Password!';
13+
RAISE NOTICE 'Created replicator role';
14+
ELSE
15+
RAISE NOTICE 'replicator role already exists';
16+
END IF;
17+
END
18+
\$\$;
19+
20+
-- Grant pg_monitor to replicator so monitoring tools can authenticate
21+
GRANT pg_monitor TO replicator;
22+
EOSQL
23+
24+
echo "==> Configuring pg_hba.conf for replication connections..."
25+
# Allow replicator from any host on the pg-net Docker network
26+
cat >> "$PGDATA/pg_hba.conf" <<-EOF
27+
# Streaming replication (added by primary-init.sh)
28+
host replication replicator 0.0.0.0/0 scram-sha-256
29+
EOF
30+
31+
echo "==> Replication setup complete."

0 commit comments

Comments
 (0)