高可用架構(High Availability):Patroni、repmgr 與自動 Failover | PostgreSQL

2026/07/07
高可用架構(High Availability):Patroni、repmgr 與自動 Failover | PostgreSQL

PostgreSQL 高可用(HA)架構旨在確保資料庫服務在節點故障、網路中斷時仍能持續運作。本文涵蓋主流 HA 方案——Patroni + etcd + HAProxy、repmgrpg_auto_failover——的完整部署配置、選型指南,以及 Kubernetes 原生部署策略。

HA 基本概念

在深入各方案之前,先理解四個核心概念:

  • Failover(自動故障切換)——Primary 意外故障時,HA 系統自動將 Standby 晉升為新 Primary,不需人工介入
  • Switchover(計畫性切換)——管理者主動發起的切換,等待 Replica 同步完成,資料零遺失
  • Split-Brain(腦裂)——網路分區導致兩個節點各自認為自己是 Primary 並接受寫入,是最危險的故障情境
  • Fencing(隔離機制)——強制終止舊 Primary(關機、封鎖網路等),確保任何時刻只有一個寫入節點

RTO/RPO 與 HA 架構

指標定義典型目標影響因素
RTO故障到服務恢復的最長時間< 30 秒 ~ 5 分鐘故障偵測速度、Failover 自動化
RPO最大可接受資料遺失時間0 秒 ~ 數分鐘複寫模式(同步/非同步)

設計原則:

  • 同步複寫可達 RPO ≈ 0,但寫入延遲增加
  • 非同步複寫延遲更低,但 Failover 可能遺失未確認的 WAL
  • Patroni 等工具可將 RTO 壓縮至 30 秒內

PostgreSQL HA 方案分類

PostgreSQL HA 方案
├── 基於流複寫
│   ├── Patroni + DCS(etcd / Consul)
│   ├── repmgr
│   └── pg_auto_failover
├── 基於共享儲存
│   └── Corosync + Pacemaker + DRBD
└── Kubernetes 原生
    ├── CloudNativePG
    └── Zalando PostgreSQL Operator

Patroni — 業界標準 HA 框架

Patroni 是目前最廣泛採用的 PostgreSQL HA 框架,由 Zalando 開源,將 PostgreSQL 生命週期管理、Leader Election、Failover 自動化整合到單一程式。

架構圖

            ┌─────────────────┐
            │  Client / App   │
            └────────┬────────┘
                     │
            ┌────────▼────────┐
            │    HAProxy      │
            │ :5432 → Primary │
            │ :5433 → Replica │
            └───┬─────────┬───┘
                │         │
    ┌───────────▼──┐  ┌───▼───────────┐
    │  Patroni     │  │  Patroni      │
    │  PostgreSQL  │  │  PostgreSQL   │
    │  (Primary)   │◄─│  (Standby)    │
    └──────┬───────┘  └──────┬────────┘
           │                 │
           └────────┬────────┘
                    │
       ┌────────────▼────────────┐
       │   etcd 叢集(3 節點)    │
       └─────────────────────────┘

元件職責:

  • Patroni:管理 PostgreSQL 啟停、監控健康、參與 Leader Election
  • etcd:分散式鍵值儲存,儲存叢集狀態與 Leader 資訊
  • HAProxy:根據 Patroni HTTP 端點動態路由讀寫請求

patroni.yml 核心配置

scope: postgres-ha          # 叢集名稱(所有節點必須一致)
namespace: /service/
name: node1                 # 本節點唯一名稱

restapi:
  listen: 0.0.0.0:8008
  connect_address: 192.168.1.11:8008

etcd3:
  hosts:
    - 192.168.1.11:2379
    - 192.168.1.12:2379
    - 192.168.1.13:2379

bootstrap:
  dcs:
    ttl: 30                           # Leader 租約 TTL(秒)
    loop_wait: 10                     # 監控迴圈間隔
    retry_timeout: 10
    maximum_lag_on_failover: 1048576  # Failover 允許的最大 WAL 落差
    postgresql:
      use_pg_rewind: true
      use_slots: true
      parameters:
        wal_level: replica
        hot_standby: "on"
        max_connections: 200
        max_wal_senders: 10
        max_replication_slots: 10
        wal_log_hints: "on"

  initdb:
    - encoding: UTF8
    - data-checksums

  pg_hba:
    - host replication replicator 192.168.1.0/24 scram-sha-256
    - host all all 0.0.0.0/0 scram-sha-256

  users:
    admin:
      password: "StrongPass123!"
      options:
        - createrole
        - createdb
    replicator:
      password: "ReplPass456!"
      options:
        - replication

postgresql:
  listen: 0.0.0.0:5432
  connect_address: 192.168.1.11:5432
  data_dir: /var/lib/postgresql/16/main
  bin_dir: /usr/lib/postgresql/16/bin
  authentication:
    replication:
      username: replicator
      password: "ReplPass456!"
    superuser:
      username: postgres
      password: "SuperPass789!"

Leader Election 機制

Patroni 採用基於 TTL 的分散式鎖實現 Leader Election:

  1. 所有節點競爭在 etcd 中寫入 /service/postgres-ha/leader
  2. 成功寫入的節點成為 Primary,持續更新租約
  3. Leader 鍵過期時,WAL 落差在閾值內的候選節點參與新選舉
# 查看叢集狀態
patronictl -c /etc/patroni/patroni.yml list

# 輸出範例:
# + Cluster: postgres-ha +---------+----+-----------+
# | Member | Role    | State   | TL | Lag in MB |
# +--------+---------+---------+----+-----------+
# | node1  | Leader  | running |  1 |           |
# | node2  | Replica | running |  1 |       0.0 |
# | node3  | Replica | running |  1 |       0.0 |
# +--------+---------+---------+----+-----------+

Failover / Switchover 操作

# 計畫性 Switchover(零資料遺失)
patronictl -c /etc/patroni/patroni.yml switchover postgres-ha \
  --master node1 \
  --candidate node2 \
  --scheduled now

# 強制 Failover(緊急情況)
patronictl -c /etc/patroni/patroni.yml failover postgres-ha \
  --master node1 \
  --candidate node2 \
  --force

# 暫停自動 Failover(維護視窗)
patronictl -c /etc/patroni/patroni.yml pause postgres-ha

# 恢復自動 Failover
patronictl -c /etc/patroni/patroni.yml resume postgres-ha

REST API 健康端點

# Primary 節點回傳 200,Standby 回傳 503
curl http://192.168.1.11:8008/primary

# Replica 節點回傳 200,Primary 回傳 503
curl http://192.168.1.11:8008/replica

# 所有健康節點皆回傳 200
curl http://192.168.1.11:8008/health

repmgr — 複寫管理工具

repmgr 由 EDB 維護,提供複寫監控、Failover 管理和叢集拓撲可視化。

核心配置

# /etc/repmgr.conf
node_id=1
node_name=node1
conninfo='host=192.168.1.11 user=repmgr dbname=repmgr connect_timeout=2'
data_directory='/var/lib/postgresql/16/main'

failover=automatic
promote_command='/usr/bin/repmgr standby promote -f /etc/repmgr.conf --log-to-file'
follow_command='/usr/bin/repmgr standby follow -f /etc/repmgr.conf --log-to-file --upstream-node-id=%n'

monitoring_history=yes
monitor_interval_secs=5
reconnect_attempts=3
reconnect_interval=5

常用操作

# 註冊 Primary
repmgr -f /etc/repmgr.conf primary register

# 克隆並註冊 Standby
repmgr -h 192.168.1.11 -U repmgr -d repmgr \
  -f /etc/repmgr.conf standby clone
repmgr -f /etc/repmgr.conf standby register

# 查看叢集狀態
repmgr -f /etc/repmgr.conf cluster show

# 計畫性 Switchover(自動跟隨)
repmgr -f /etc/repmgr.conf standby switchover --siblings-follow

# 啟動自動 Failover 守護程序
repmgrd -f /etc/repmgr.conf --daemonize

pg_auto_failover — 輕量方案

由 Microsoft 開源,採用 Monitor 節點集中管理狀態機,架構最簡單:

        ┌───────────────────┐
        │   Monitor Node    │
        │  :5000            │
        └───────┬───────────┘
                │
    ┌───────────▼───┐    ┌────▼──────────┐
    │  Primary      │───►│  Secondary    │
    │  node1:5432   │    │  node2:5432   │
    └───────────────┘    └───────────────┘
# Monitor 節點
pg_autoctl create monitor \
  --pgdata /var/lib/postgresql/monitor \
  --pgport 5000 \
  --hostname monitor.example.com

# Primary 節點
pg_autoctl create postgres \
  --pgdata /var/lib/postgresql/16/main \
  --monitor postgresql://autoctl@monitor:5000/pg_auto_failover

# Secondary 節點(自動從 Primary 克隆)
pg_autoctl create postgres \
  --pgdata /var/lib/postgresql/16/main \
  --monitor postgresql://autoctl@monitor:5000/pg_auto_failover

# 查看狀態
pg_autoctl show state --monitor postgresql://autoctl@monitor:5000/pg_auto_failover

# 執行 Failover
pg_autoctl perform failover --monitor postgresql://autoctl@monitor:5000/pg_auto_failover

HAProxy 讀寫分離

HAProxy 透過 Patroni HTTP 端點感知節點角色,實現動態讀寫分離:

# /etc/haproxy/haproxy.cfg

global
    maxconn 100

defaults
    mode tcp
    timeout client 30m
    timeout connect 4s
    timeout server 30m
    timeout check 5s

# 讀寫端口(只路由到 Primary)
listen postgres-primary
    bind *:5432
    option httpchk GET /primary
    http-check expect status 200
    default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
    server node1 192.168.1.11:5432 maxconn 100 check port 8008
    server node2 192.168.1.12:5432 maxconn 100 check port 8008
    server node3 192.168.1.13:5432 maxconn 100 check port 8008

# 唯讀端口(路由到 Replica)
listen postgres-replicas
    bind *:5433
    option httpchk GET /replica
    http-check expect status 200
    balance roundrobin
    default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
    server node1 192.168.1.11:5432 maxconn 100 check port 8008
    server node2 192.168.1.12:5432 maxconn 100 check port 8008
    server node3 192.168.1.13:5432 maxconn 100 check port 8008

PgBouncer 搭配 HA

PgBouncer 置於 HAProxy 與 PostgreSQL 之間,提供連線池化:

# /etc/pgbouncer/pgbouncer.ini
[databases]
mydb = host=haproxy.example.com port=5432 dbname=mydb
mydb_ro = host=haproxy.example.com port=5433 dbname=mydb

[pgbouncer]
pool_mode = transaction       # transaction 模式支援 HA 切換
max_client_conn = 1000
default_pool_size = 20
server_fast_close = 1         # Failover 時立即關閉舊連線
listen_port = 6432

Split-Brain 防護

Fencing 機制

1. 基於 DCS 的 TTL 隔離

舊 Primary 無法更新 etcd Leader 租約時,Patroni 讓它進入 read-only 模式。租約到期後新 Primary 才被允許提升。

2. 自訂 Fencing Hook

# patroni.yml
postgresql:
  pre_promote: /usr/local/bin/fence_old_primary.sh
#!/bin/bash
# 透過雲端 API 強制停止舊 Primary
OLD_PRIMARY_INSTANCE_ID="i-0123456789abcdef0"
aws ec2 stop-instances --instance-ids $OLD_PRIMARY_INSTANCE_ID --force
sleep 10

3. Witness Node

在 repmgr 中,Witness Node 不運行 PostgreSQL,僅參與仲裁投票。在 Patroni 中,etcd 叢集本身即扮演 Witness 角色。


三方案比較與選型

功能Patronirepmgrpg_auto_failover
架構複雜度高(需 DCS 叢集)
自動 Failover原生支援需 repmgrd原生支援
RTO< 30 秒30~120 秒30~60 秒
REST API內建
HAProxy 整合原生需自行配置需自行配置
K8s 支援Zalando Operator有限有限
適合規模中大型中型中小型

選型決策

是否在 Kubernetes 上部署?
├── 是 → CloudNativePG 或 Zalando Operator
└── 否 →
    ├── 有能力維運 etcd? → Patroni(最佳 RTO)
    ├── 需要最簡架構? → pg_auto_failover
    └── 使用 EDB 企業版? → repmgr

Kubernetes 原生部署

CloudNativePG

CloudNativePG(CNPG)是 CNCF 沙盒項目,功能最完整的 PostgreSQL K8s Operator:

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: pg-cluster
spec:
  instances: 3              # 1 Primary + 2 Replica

  postgresql:
    parameters:
      max_connections: "200"
      shared_buffers: "256MB"

  storage:
    size: 100Gi
    storageClass: fast-ssd

  backup:
    barmanObjectStore:
      destinationPath: s3://my-pg-backups/
      s3Credentials:
        accessKeyId:
          name: aws-creds
          key: ACCESS_KEY_ID
        secretAccessKey:
          name: aws-creds
          key: SECRET_ACCESS_KEY

  monitoring:
    enablePodMonitor: true

CNPG 自動建立的 Service:

  • pg-cluster-rw → Primary(讀寫)
  • pg-cluster-ro → Replica(唯讀)
  • pg-cluster-r → 所有健康節點

Zalando PostgreSQL Operator

底層使用 Patroni + Spilo 鏡像,適合已熟悉 Patroni 的團隊:

apiVersion: "acid.zalan.do/v1"
kind: postgresql
metadata:
  name: pg-cluster
spec:
  teamId: "myteam"
  numberOfInstances: 3
  volume:
    size: 100Gi
  postgresql:
    version: "16"
  users:
    myapp_user:
      - superuser
      - createdb
  databases:
    myapp: myapp_user

常見陷阱

1. etcd 叢集未達法定人數

# 錯誤:2 節點 etcd,一節點故障後叢集停擺
# 正確:至少 3 節點(容忍 1 故障),5 節點(容忍 2 故障)

2. pg_rewind 未正確配置

# patroni.yml 必須設定
use_pg_rewind: true
# PostgreSQL 必須啟用
wal_log_hints = on  # 或使用 data checksums

3. 同步複寫 + 單一 Standby 的可用性陷阱

# 錯誤:synchronous_standby_names = '*' 在唯一 Standby 故障時主節點無法寫入
# 正確:使用 ANY 語法
synchronous_standby_names = 'ANY 1 (node2, node3)'

4. 應用程式未處理連線中斷

Failover 期間(10~30 秒)連線會中斷,應用程式必須實作重試邏輯。

5. Load Balancer 健康檢查間隔過長

HAProxy 的 inter 應設定為 3~5 秒,確保 Failover 後快速切換。


版本演進

版本HA 相關特性
PG 9.4Replication Slots 引入
PG 10Quorum-based 同步複寫
PG 11pg_rewind 不再需要 wal_log_hints(需 data checksums)
PG 12pg_promote()、standby.signal
PG 13max_slot_wal_keep_size
PG 15兩階段邏輯複寫
PG 16Logical Replication from Standby

總結

高可用架構是生產環境 PostgreSQL 的必備能力:

  1. Patroni 是業界標準,RTO < 30 秒,功能最完整,但需要維運 etcd 叢集
  2. repmgr 適合中型規模,架構較簡單,與 EDB 企業版整合良好
  3. pg_auto_failover 最輕量,適合快速搭建 HA 原型
  4. Kubernetes 環境優先選擇 CloudNativePG,新建項目首選
  5. Split-Brain 防護(Fencing + DCS + Witness)是 HA 架構的最後防線
  6. 無論選擇哪個方案,都必須定期進行 Failover 演練

下一篇,我們將深入探討 連線池化——掌握 PgBouncer 與 pgpool-II 的配置、調校與效能優化,讓你的 PostgreSQL 連線管理更加高效。

BenZ Software Developer

熱愛技術的軟體開發者,在這裡分享程式開發經驗與學習筆記。