자동 장애조치가 목표이고 온프레미스 · PostgreSQL 16 이면 사실상 표준 조합이 Patroni + etcd + HAProxy 다. Patroni 가 Primary 선출과 승격을 맡고, etcd 가 합의 저장소, HAProxy 가 애플리케이션 접속을 현재 Primary 로 돌린다.
Application
↓
PgBouncer (선택, 커넥션 풀)
↓
HAProxy (VIP, Keepalived)
write → :5432 → 현재 Primary
read → :5433 → Standby
↓
Patroni + PostgreSQL × 3 ←→ etcd × 3
Streaming Replication
| 역할 | 대수 | 비고 |
|---|---|---|
| PostgreSQL + Patroni | 3 | Primary 1 + Standby 2. 2 대도 되지만 쿼럼상 3 대 권장 |
| etcd | 3 | 홀수 필수. DB 노드와 합쳐도 되나 분리가 이상적 |
| HAProxy | 2 | Keepalived 로 VIP 이중화 |
비용 제약이 있으면 DB 노드 3 대에 etcd 와 HAProxy 를 함께 올리는 합본 구성도 현실적이다.
/etc/hosts 에 모든 노드를 같은 이름으로 등록하고 시간을 동기화한다. 방화벽은 5432(PostgreSQL) · 8008(Patroni REST) · 2379 · 2380(etcd) · 5000 · 5001 · 7000(HAProxy) 을 연다.
sudo systemctl enable --now chronyd
# /etc/etcd/etcd.conf.yml (노드 1)
name: etcd1
data-dir: /var/lib/etcd
initial-advertise-peer-urls: http://10.0.0.11:2380
listen-peer-urls: http://10.0.0.11:2380
listen-client-urls: http://10.0.0.11:2379,http://127.0.0.1:2379
advertise-client-urls: http://10.0.0.11:2379
initial-cluster: etcd1=http://10.0.0.11:2380,etcd2=http://10.0.0.12:2380,etcd3=http://10.0.0.13:2380
initial-cluster-state: new
initial-cluster-token: ${MASKED}
etcdctl endpoint health --endpoints=http://10.0.0.11:2379,http://10.0.0.12:2379,http://10.0.0.13:2379
# /etc/patroni/patroni.yml (노드 1)
scope: pg-cluster
name: pg-node1
restapi:
listen: 10.0.0.11:8008
connect_address: 10.0.0.11:8008
etcd3:
hosts: 10.0.0.11:2379,10.0.0.12:2379,10.0.0.13:2379
bootstrap:
dcs:
ttl: 30
loop_wait: 10
retry_timeout: 10
maximum_lag_on_failover: 1048576
postgresql:
use_pg_rewind: true
parameters:
wal_level: replica
hot_standby: "on"
max_wal_senders: 10
max_replication_slots: 10
wal_keep_size: 1024
initdb:
- encoding: UTF8
- data-checksums
postgresql:
listen: 0.0.0.0:5432
connect_address: 10.0.0.11:5432
data_dir: /data/pgdata
bin_dir: /usr/pgsql-16/bin
authentication:
replication:
username: replicator
password: ${REPLICATION_PASSWORD}
superuser:
username: postgres
password: ${POSTGRES_PASSWORD}
sudo systemctl enable --now patroni
patronictl -c /etc/patroni/patroni.yml list
Patroni 의 REST API 는 Primary 에서만 GET /master 에 200 을 돌려주므로 이를 헬스체크로 쓴다.
listen postgres_write
bind *:5432
option httpchk GET /master
http-check expect status 200
default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
server pg-node1 10.0.0.11:5432 check port 8008
server pg-node2 10.0.0.12:5432 check port 8008
server pg-node3 10.0.0.13:5432 check port 8008
listen postgres_read
bind *:5433
balance roundrobin
option httpchk GET /replica
http-check expect status 200
server pg-node1 10.0.0.11:5432 check port 8008
server pg-node2 10.0.0.12:5432 check port 8008
server pg-node3 10.0.0.13:5432 check port 8008
patronictl -c /etc/patroni/patroni.yml list # 역할과 lag
patronictl -c /etc/patroni/patroni.yml switchover # 계획된 전환
patronictl -c /etc/patroni/patroni.yml failover # 강제 전환
psql -h <vip> -p 5432 -c "SELECT pg_is_in_recovery();" # false 면 Primary
동기 복제가 필요하면 synchronous_mode: true 를 DCS 파라미터에 넣는다. 성능과 가용성의 절충이 있으므로 데이터 유실 허용 범위를 정하고 선택한다.