PostgreSQL 高可用集群搭建指南

Keepalived + repmgr + PostgreSQL 16.0 完整部署手册


📖 阅读指南

本文档面向技术小白运维新手,采用”先讲概念、再给命令、每条配置都加注释”的方式,帮助你从零搭建一套生产级 PostgreSQL 高可用集群。

💡 小贴士: 每个配置项旁边的 # ← 注释会用大白话解释它的作用,不用担心看不懂。


📋 目录

  1. 概念速览 — 先搞懂每个组件是干什么的
  2. 集群环境规划 — 几台服务器、什么角色
  3. 安装 PostgreSQL 16.0
  4. 安装配置 repmgr(主从复制管理)
  5. 安装配置 Keepalived(VIP 漂移)
  6. 集群验证测试
  7. 故障处理:脑裂修复
  8. 常用命令速查

1. 概念速览

1.1 这套方案解决了什么问题?

想象你只有一台 PostgreSQL 数据库,某天它突然挂了——你的整个业务就瘫痪了。

这套方案用三个组件协作,实现 自动故障转移

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
┌─────────────────────────────────────────────────────┐
│ 业务应用 │
│ 连接 VIP(虚拟IP) │
│ x.x.x.x(永远指向当前主库) │
└─────────────────────┬───────────────────────────────┘

┌────────────┴────────────┐
│ │
┌────▼─────┐ ┌─────▼────┐
│ 主数据库 │◄───复制───►│ 备数据库 │
│ .184.123 │ │ .183.82 │
│ (Master) │ │ (Standby) │
└────┬──────┘ └─────┬─────┘
│ │
┌────▼─────┐ │
│keepalived│◄──────VRRP───────┘
│priority │ 心跳+健康检查
│ 100 │
└──────────┘
┌──────────┐
│ 见证服务器 │
│ .183.95 │
│ (Witness) │
└──────────┘

1.2 三个组件各干什么?

组件 角色 大白话解释
PostgreSQL 数据库本身 存数据的地方,一台主库(可读写)+ 一台备库(只读复制)
repmgr 复制管理器 自动监控主库状态,主库挂了就提升备库为新主库
Keepalived VIP 漂移 提供一个”虚拟 IP”,永远自动指向当前的主库,业务不用改连接地址

1.3 核心概念术语表

术语 解释
主库 (Primary) 可以读写的数据库,只有一台
备库 (Standby) 从主库实时复制数据,只能读不能写
见证服务器 (Witness) 不存业务数据,只在主备之间”断联”时帮忙投票决定谁当主库,防止脑裂
VIP (虚拟IP) 一个漂移的 IP 地址,永远绑在当前主库的网卡上
脑裂 (Split-Brain) 主备都认为自己是主库,两边同时写入导致数据不一致——这是最危险的故障
故障转移 (Failover) 主库挂了 → 备库自动升级为新主库的过程
WAL Write-Ahead Log,预写日志,主库的每次写操作先记日志再写数据,备库通过”重放”这些日志来同步
流复制 (Streaming Replication) 备库实时从主库”流式”接收 WAL 日志并应用

2. 集群环境规划

2.1 服务器清单

角色 IP 地址 主机名建议 Root 密码
主数据库 (Primary) 192.168.184.123/24 pg-primary Aa_123qwe
备数据库 (Standby) 192.168.183.82/26 pg-standby Aa_123qwe
见证服务器 (Witness) 192.168.183.95/26 pg-witness Aa_123qwe
VIP(虚拟IP) 待定(需根据网络规划)

2.2 软件版本

软件 版本 说明
PostgreSQL 16.0 源码编译安装
repmgr 5.5.0 EnterpriseDB 出品
Keepalived yum 最新版 Linux 虚拟路由冗余协议

2.3 关键路径一览

路径 用途
/usr/local/pgsql16/ PostgreSQL 安装目录
/usr/local/pgsql16/data/ 数据库数据目录
/usr/local/pgsql16/bin/ PostgreSQL 可执行文件目录
/etc/repmgr.conf repmgr 配置文件
/etc/keepalived/keepalived.conf Keepalived 配置文件
/home/postgres/ postgres 用户家目录

2.4 端口规划

端口 用途
5432 PostgreSQL 数据库服务端口
VRRP 协议使用组播,无需额外开放 TCP 端口

3. 安装 PostgreSQL 16.0

3.1 编译安装(三台服务器都执行)

⚠️ 重要: 以下命令需要在每台服务器上以 root 用户执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# ============================================================
# 步骤:安装编译依赖
# 说明:readline-devel 提供命令行历史功能,libxslt-devel 支持 XML 转换
# ============================================================
yum install -y readline-devel libxslt-devel

# ============================================================
# 步骤:解压源码包
# 说明:需要提前下载 postgresql-16.0.tar.gz 到当前目录
# ============================================================
tar zxvf postgresql-16.0.tar.gz
cd postgresql-16.0/

# ============================================================
# 步骤:创建安装目录
# 说明:所有 PostgreSQL 文件都放在这里,方便管理
# ============================================================
mkdir /usr/local/pgsql16

# ============================================================
# 步骤:配置编译选项
# 说明:
# --prefix 安装路径
# --with-ssl 启用 SSL 加密连接
# --with-icu 支持国际化字符集(推荐开启)
# --with-python 支持 Python 存储过程
# --with-libxml 支持 XML 数据类型
# --with-libxslt 支持 XSLT 转换
# --with-systemd 支持 systemd 服务通知(配合 service 文件使用)
# ============================================================
./configure --prefix=/usr/local/pgsql16 \
--with-ssl=openssl \
--with-icu \
--with-python \
--with-libxml \
--with-libxslt \
--with-systemd

# ============================================================
# 步骤:编译和安装
# 说明:-j 8 表示用 8 个 CPU 核心并行编译,加快速度
# 根据你的服务器 CPU 核心数调整这个数字
# ============================================================
make -j 8
make install

3.2 创建 postgres 用户(三台服务器都执行)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# ============================================================
# 为什么需要独立的 postgres 用户?
# 安全原则:数据库进程不以 root 运行,即使被攻击也无法获取系统最高权限
# ============================================================

# 创建 postgres 用户组
groupadd postgres

# 创建 postgres 用户,-m 创建家目录,-g 指定主组
useradd -m -g postgres postgres

# 设置密码(用于 SSH 互信登录,三台保持一致)
passwd postgres # 输入密码:Aa_123qwe

# 创建数据目录(数据库的实际数据将存在这里)
mkdir /usr/local/pgsql16/data

# 将整个 pgsql 目录的所有权给 postgres 用户
chown -R postgres:postgres /usr/local/pgsql16/

3.3 配置环境变量(三台服务器都执行)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# ============================================================
# 切换到 postgres 用户
# ============================================================
su - postgres

# ============================================================
# 编辑 ~/.bash_profile,添加以下环境变量
# 说明:设置这些变量后,在任何目录都能直接使用 psql、pg_ctl 等命令
# ============================================================
vim .bash_profile

# 在文件末尾添加以下内容:
export PGHOME=/usr/local/pgsql16 # ← PostgreSQL 安装根目录
export PGDATA=/usr/local/pgsql16/data # ← 数据目录,pg_ctl 等命令会自动找这个路径
export PATH=$PGHOME/bin:$PATH # ← 将 bin 目录加入 PATH,可直接执行 psql 等命令
export LD_LIBRARY_PATH=$PGHOME/lib # ← 动态库搜索路径,否则可能报 libpq.so 找不到

# 让环境变量立即生效
source .bash_profile

3.4 初始化数据库(主节点见证节点执行)

⚠️ 备节点不需要初始化,后面会用 repmgr 从主节点克隆数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
# 切换到 postgres 用户
su - postgres

# ============================================================
# 初始化数据库
# 说明:
# -D 指定数据目录
# --encoding 设置字符集为 UTF8(支持中文)
# 执行后会生成配置文件 postgresql.conf、pg_hba.conf 等
# ============================================================
/usr/local/pgsql16/bin/initdb -D /usr/local/pgsql16/data --encoding=UTF8

exit

3.5 配置 PostgreSQL 参数

3.5.1 主节点配置

文件 postgresql.conf — 路径:/usr/local/pgsql16/data/postgresql.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# ============================================================
# 连接和安全配置
# ============================================================
listen_addresses = '*' # ← 监听所有网卡,允许远程连接(默认只监听 127.0.0.1)
port = 5432 # ← 数据库监听端口

# ============================================================
# 流复制相关配置(主库向备库同步数据的关键参数)
# ============================================================
max_wal_senders = 10 # ← 最大 WAL 发送进程数,≥ 备库数量
# 每个备库占用 1 个 sender,留余量给 pg_basebackup
max_replication_slots = 10 # ← 最大复制槽数量,≥ 备库数量
# 复制槽保证备库离线期间主库不删除未同步的 WAL
wal_level = replica # ← WAL 日志级别设为 replica,备库才能接收流复制
wal_log_hints = on # ← 记录完整页面提示信息,pg_rewind 修复脑裂时需要
hot_standby = on # ← 允许备库在恢复模式下接受只读查询

# ============================================================
# 归档配置(WAL 日志归档,用于时间点恢复)
# ============================================================
archive_mode = on # ← 开启 WAL 归档模式
archive_command = '/bin/true' # ← 归档命令,这里设为 /bin/true 表示不实际归档
# 生产环境应改为实际归档脚本(如 cp 到 NFS)

# ============================================================
# repmgr 集成
# ============================================================
shared_preload_libraries = 'repmgr' # ← 预加载 repmgr 扩展
# repmgr 需要在这里注册,否则功能不生效

文件 pg_hba.conf — 路径:/usr/local/pgsql16/data/pg_hba.conf

💡 pg_hba.conf 是什么? PostgreSQL 的”访客登记表”,控制谁可以连接数据库、用什么方式认证。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# ============================================================
# 格式说明:TYPE DATABASE USER ADDRESS METHOD
# TYPE:连接方式(local=本地socket, host=TCP/IP)
# DATABASE:允许访问的数据库名
# USER:允许的用户
# ADDRESS:来源 IP 范围(CIDR 格式)
# METHOD:认证方式(trust=免密, scram-sha-256=密码认证)
# ============================================================

# 允许 repmgr 用户本地免密连接(用于 repmgr 内部操作)
local replication repmgr trust
local repmgr repmgr trust

# 允许 repmgr 用户通过 TCP 从内网连接,使用密码认证(更安全)
host replication repmgr 192.168.0.0/16 scram-sha-256
host replication repmgr 127.0.0.1/32 scram-sha-256
host repmgr repmgr 192.168.0.0/16 scram-sha-256
host repmgr repmgr 127.0.0.1/32 scram-sha-256

3.5.2 见证节点配置

文件 postgresql.conf — 路径:/usr/local/pgsql16/data/postgresql.conf

1
2
3
4
5
6
7
# ============================================================
# 见证节点只需要基础配置
# 它不存业务数据,只参与 repmgr 投票
# ============================================================
listen_addresses = '*' # ← 允许远程连接
port = 5432 # ← 监听端口
shared_preload_libraries = 'repmgr' # ← 加载 repmgr 扩展

文件 pg_hba.conf — 路径:/usr/local/pgsql16/data/pg_hba.conf

1
2
3
4
5
6
7
8
9
10
# ============================================================
# 见证节点认证配置
# 允许主备节点的 repmgr 用户访问
# 注意:这里使用精确网段(/24 和 /26),比主节点的 /16 更严格
# ============================================================
host all repmgr 192.168.184.0/24 scram-sha-256
host all repmgr 192.168.183.64/26 scram-sha-256
host all repmgr 192.168.183.95/32 scram-sha-256
host replication repmgr 192.168.184.0/24 scram-sha-256
host replication repmgr 192.168.183.64/26 scram-sha-256

3.6 配置 systemd 服务(三台服务器都执行)

💡 为什么用 systemd? 让 PostgreSQL 可以开机自启、用 systemctl 管理启停,repmgr 也能通过 systemctl 控制数据库。

文件 postgresql.service — 路径:/lib/systemd/system/postgresql.service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
[Unit]
Description=PostgreSQL 16.0 Database Server # ← 服务描述
Documentation=https://www.postgresql.org/docs/16/
After=network-online.target # ← 等网络完全就绪后再启动
Wants=network-online.target # ← 依赖网络在线

[Service]
Type=simple # ← 简单类型,主进程就是服务进程
User=postgres # ← 以 postgres 用户运行(安全)
Group=postgres

Environment=PGDATA=/usr/local/pgsql16/data # ← 数据目录环境变量
Environment=PGPORT=5432 # ← 端口环境变量

# 启动命令:启动 PostgreSQL 主进程
ExecStart=/usr/local/pgsql16/bin/postgres -D ${PGDATA}
# 重载命令:发送 HUP 信号让 PostgreSQL 重新加载配置(不重启)
ExecReload=/bin/kill -HUP $MAINPID
# 停止命令:fast 模式快速关闭(回滚活跃事务后退出)
ExecStop=/usr/local/pgsql16/bin/pg_ctl stop -D ${PGDATA} -m fast

KillMode=mixed # ← 只杀主进程,子进程自行退出
KillSignal=SIGINT # ← 用 SIGINT 信号终止(模拟 Ctrl+C)
TimeoutSec=600 # ← 超时时间 600 秒(大规模数据库关闭可能很慢)
Restart=on-failure # ← 异常退出时自动重启
LimitNOFILE=65536 # ← 最大打开文件数(高性能需要)
LimitNPROC=65536 # ← 最大进程数

[Install]
WantedBy=multi-user.target # ← 多用户模式启动时自动运行

启动 PostgreSQL:

1
2
3
4
5
6
7
8
9
10
11
# 重新加载 systemd 配置(新建 service 文件后必须执行)
systemctl daemon-reload

# 启动 PostgreSQL
systemctl start postgresql.service

# 设置开机自启
systemctl enable postgresql.service

# 查看运行状态
systemctl status postgresql.service

3.7 防火墙配置(三台服务器都执行)

1
2
3
4
5
6
7
8
9
10
# ============================================================
# 开放 5432 端口,允许其他服务器访问 PostgreSQL
# ============================================================
firewall-cmd --zone=public --add-port=5432/tcp --permanent

# 重载防火墙使配置生效
firewall-cmd --reload

# 验证端口已开放
firewall-cmd --list-ports

3.8 创建 repmgr 用户和数据库(主节点执行,见证节点也可执行)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 切换到 postgres 用户
su - postgres

# ============================================================
# 创建 repmgr 管理用户
# 说明:
# REPLICATION 允许该用户进行流复制管理
# LOGIN 允许登录
# SUPERUSER 超级用户权限(repmgr 需要管理数据库启停)
# ============================================================
/usr/local/pgsql16/bin/psql -c "CREATE USER repmgr WITH REPLICATION LOGIN ENCRYPTED PASSWORD 'repmgr' SUPERUSER;"

# ============================================================
# 创建 repmgr 元数据库
# 说明:repmgr 用它存储集群状态、节点信息等元数据
# ============================================================
/usr/local/pgsql16/bin/psql -c "CREATE DATABASE repmgr OWNER repmgr;"

# ============================================================
# 在 repmgr 数据库中安装 repmgr 扩展
# 说明:创建 repmgr 所需的函数和表
# ============================================================
/usr/local/pgsql16/bin/psql -d repmgr -c "CREATE EXTENSION repmgr;"

4. 安装配置 repmgr(主从复制管理)

4.1 编译安装 repmgr(三台服务器都执行)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# ============================================================
# 下载 repmgr 源码
# ============================================================
wget https://github.com/EnterpriseDB/repmgr/releases/download/v5.5.0/repmgr-5.5.0.tar.gz

# ============================================================
# 安装编译依赖
# ============================================================
yum install -y libcurl-devel json-c-devel libevent-devel openssl-devel

# ============================================================
# 编译安装(以 postgres 用户执行)
# ============================================================
mv repmgr-5.5.0.tar.gz /home/postgres
su - postgres
tar zxvf repmgr-5.5.0.tar.gz
cd repmgr-5.5.0/

# 指定 PostgreSQL 的 pg_config 位置,确保 repmgr 编译时链接正确的 PG 库
./configure PG_CONFIG=/usr/local/pgsql16/bin/pg_config

make -j 8
make install

# 验证安装成功
/usr/local/pgsql16/bin/repmgr --version

4.2 配置 repmgr

4.2.1 主节点配置

文件 repmgr.conf — 路径:/etc/repmgr.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# ============================================================
# 节点基本信息
# ============================================================
node_id = 1 # ← 集群内唯一节点 ID,主节点通常是 1
node_name = 'pg-primary' # ← 节点名称,方便在 cluster show 中识别

# ============================================================
# 连接信息
# 说明:repmgr 用这个连接字符串访问自己的本地数据库来管理集群
# connect_timeout=2 表示 2 秒连不上就报超时
# ============================================================
conninfo = 'host=192.168.184.123 user=repmgr dbname=repmgr connect_timeout=2'

# ============================================================
# 路径配置
# ============================================================
data_directory = '/usr/local/pgsql16/data' # ← PostgreSQL 数据目录
pg_bindir = '/usr/local/pgsql16/bin' # ← PostgreSQL 可执行文件目录
repmgr_bindir = '/usr/local/pgsql16/bin' # ← repmgr 可执行文件目录

# ============================================================
# 日志配置
# ============================================================
log_level = INFO # ← 日志级别:DEBUG/INFO/NOTICE/WARNING/ERROR
# 生产环境建议 INFO,排查问题时可临时改为 DEBUG
log_file = '/home/postgres/repmgrd.log' # ← repmgr 守护进程日志路径

# ============================================================
# PID 文件(防止重复启动)
# ============================================================
repmgrd_pid_file = '/usr/local/pgsql16/data/repmgrd.pid'

# ============================================================
# 服务管理命令
# 说明:repmgr 需要能启动/停止/重启 PostgreSQL 服务
# 使用 sudo 是因为 repmgrd 以 postgres 用户运行,需要提权调用 systemctl
# ============================================================
service_start_command = 'sudo systemctl start postgresql'
service_stop_command = 'sudo systemctl stop postgresql'
service_restart_command = 'sudo systemctl restart postgresql'
service_reload_command = 'sudo systemctl reload postgresql'

# ============================================================
# 故障转移配置(核心!)
# ============================================================
failover = automatic # ← 自动故障转移(可选 manual 手动模式)

# 提升备库为主库的命令(repmgr 检测到主库挂了就执行这个)
promote_command = '/usr/local/pgsql16/bin/repmgr standby promote -f /etc/repmgr.conf'

# 让节点跟随新主库的命令(%n 会被替换为上游节点 ID)
follow_command = '/usr/local/pgsql16/bin/repmgr standby follow -f /etc/repmgr.conf --upstream-node-id=%n'

# ============================================================
# 重连和超时配置
# ============================================================
reconnect_attempts = 10 # ← 连不上主库时重试 10 次
reconnect_interval = 10 # ← 每次重试间隔 10 秒
# 即:最多等 10×10=100 秒后判定主库故障
promote_check_timeout = 60 # ← 提升备库前的检查超时(秒)

4.2.2 备节点配置

文件 repmgr.conf — 路径:/etc/repmgr.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# ============================================================
# 备节点配置(与主节点大部分相同,只有节点信息和连接地址不同)
# ============================================================
node_id = 2 # ← 备节点 ID 为 2
node_name = 'pg-standby' # ← 备节点名称
conninfo = 'host=192.168.183.82 user=repmgr dbname=repmgr connect_timeout=2'
data_directory = '/usr/local/pgsql16/data'
pg_bindir = '/usr/local/pgsql16/bin'
repmgr_bindir = '/usr/local/pgsql16/bin'

# 日志配置(与主节点相同)
log_level = INFO
log_file = '/home/postgres/repmgrd.log'

repmgrd_pid_file = '/usr/local/pgsql16/data/repmgr.pid'

# 服务管理命令(与主节点相同)
service_start_command = 'sudo systemctl start postgresql'
service_stop_command = 'sudo systemctl stop postgresql'
service_restart_command = 'sudo systemctl restart postgresql'
service_reload_command = 'sudo systemctl reload postgresql'

# 故障转移配置(与主节点相同)
failover = automatic
promote_command = '/usr/local/pgsql16/bin/repmgr standby promote -f /etc/repmgr.conf'
follow_command = '/usr/local/pgsql16/bin/repmgr standby follow -f /etc/repmgr.conf --upstream-node-id=%n'

reconnect_attempts = 10
reconnect_interval = 10
promote_check_timeout = 60

4.2.3 见证节点配置

文件 repmgr.conf — 路径:/etc/repmgr.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# ============================================================
# 见证节点配置
# 说明:见证节点不存业务数据,不参与故障转移
# 它的唯一作用是:当主备之间网络断了的时候,帮 repmgr 判断"谁才是真正活着的主"
# 没有见证节点的话,备库看不到主库就会自己升级,导致脑裂
# ============================================================
node_id = 3 # ← 见证节点 ID 为 3
node_name = 'pg-witness' # ← 见证节点名称
conninfo = 'host=192.168.183.95 user=repmgr dbname=repmgr connect_timeout=2'
data_directory = '/usr/local/pgsql16/data'
pg_bindir = '/usr/local/pgsql16/bin'
repmgr_bindir = '/usr/local/pgsql16/bin'

# 日志配置
log_level = INFO
log_file = '/home/postgres/repmgrd.log'
repmgrd_pid_file = '/usr/local/pgsql16/data/repmgr.pid'

# 服务管理命令(见证节点也需要,因为 repmgrd 可能重启 PostgreSQL)
service_start_command = 'sudo systemctl start postgresql'
service_stop_command = 'sudo systemctl stop postgresql'
service_restart_command = 'sudo systemctl restart postgresql'
service_reload_command = 'sudo systemctl reload postgresql'

# 见证节点不需要 failover 配置

4.3 配置 sudo 权限(三台服务器都执行)

💡 为什么要配这个? repmgrd 以 postgres 用户运行,但 systemctl 命令需要 root 权限。通过 sudo 授权,让 postgres 用户能免密码执行 systemctl。

1
2
3
4
5
6
7
8
9
10
11
12
# 编辑 sudoers 文件(用 visudo 会自动检查语法,比直接 vim 安全)
visudo

# 添加以下内容:
Defaults:postgres !requiretty # ← 允许 postgres 用户在没有 TTY 时执行 sudo
# (repmgrd 是后台进程,没有终端)

postgres ALL = NOPASSWD: /usr/bin/systemctl start postgresql, \
/usr/bin/systemctl stop postgresql, \
/usr/bin/systemctl restart postgresql, \
/usr/bin/systemctl reload postgresql
# ↑ 允许 postgres 用户免密码执行这四个 systemctl 命令

4.4 配置 SSH 免密登录(三台服务器都执行)

💡 为什么要配 SSH 互信? repmgr 在执行 standby clone(克隆数据)时,需要通过 SSH 在主备之间传输数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 切换到 postgres 用户
su - postgres

# 生成 SSH 密钥对(一路回车即可,不设密码)
ssh-keygen -t rsa

# 将公钥复制到三台服务器(包括自己)
ssh-copy-id postgres@192.168.184.123
ssh-copy-id postgres@192.168.183.82
ssh-copy-id postgres@192.168.183.95

# 验证:在每台服务器上都能免密 SSH 到其他两台
ssh postgres@192.168.184.123 "hostname"
ssh postgres@192.168.183.82 "hostname"
ssh postgres@192.168.183.95 "hostname"

4.5 配置 .pgpass 密码文件(三台服务器都执行)

💡 为什么要配 .pgpass? repmgr 在操作数据库时,需要自动连接各节点的 repmgr 库。.pgpass 文件让 repmgr 能免交互式输入密码。

文件 .pgpass — 路径:/home/postgres/.pgpass

1
2
3
4
5
6
7
8
9
10
11
12
13
# ============================================================
# .pgpass 格式:hostname:port:database:username:password
# ============================================================

# replication 数据库的连接(用于流复制认证)
192.168.184.123:5432:replication:repmgr:repmgr
192.168.183.82:5432:replication:repmgr:repmgr
192.168.183.95:5432:replication:repmgr:repmgr

# repmgr 数据库的连接(用于集群管理)
192.168.184.123:5432:repmgr:repmgr:repmgr
192.168.183.82:5432:repmgr:repmgr:repmgr
192.168.183.95:5432:repmgr:repmgr:repmgr
1
2
3
4
5
6
# ============================================================
# ⚠️ 安全关键步骤:必须修改 .pgpass 权限
# 说明:PostgreSQL 要求 .pgpass 文件权限必须是 600(只有所有者可读写)
# 如果权限不对,PostgreSQL 会忽略该文件(防止密码泄露)
# ============================================================
chmod 600 /home/postgres/.pgpass

4.6 注册节点到集群

注册主节点(在主节点执行)

1
2
3
4
5
6
su - postgres

# ============================================================
# 将当前节点注册为集群的 Primary(主库)
# ============================================================
/usr/local/pgsql16/bin/repmgr -f /etc/repmgr.conf primary register

注册备节点(在备节点执行)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# ============================================================
# ⚠️ 前置条件:确保备节点 PostgreSQL 已停止,数据目录为空!
# 如果之前初始化过数据库,需要先清理
# ============================================================
sudo systemctl stop postgresql
rm -rf /usr/local/pgsql16/data/* # ⚠️ 生产环境建议 mv 备份而非直接删除

su - postgres

# ============================================================
# 步骤 1:先做一次"干跑"测试,检查配置是否正确但不实际克隆数据
# 说明:--dry-run 不会修改任何数据,只是验证连通性
# ============================================================
/usr/local/pgsql16/bin/repmgr -h 192.168.184.123 -U repmgr -d repmgr \
-f /etc/repmgr.conf standby clone --dry-run

# ============================================================
# 步骤 2:从主库克隆数据
# 说明:repmgr 会通过 pg_basebackup 把主库的所有数据完整复制过来
# 这个过程的时间取决于数据库大小
# ============================================================
/usr/local/pgsql16/bin/repmgr -h 192.168.184.123 -U repmgr -d repmgr \
-f /etc/repmgr.conf standby clone

# ============================================================
# 步骤 3:启动备节点 PostgreSQL
# ============================================================
sudo systemctl start postgresql

# ============================================================
# 步骤 4:将当前节点注册为集群的 Standby(备库)
# ============================================================
/usr/local/pgsql16/bin/repmgr -f /etc/repmgr.conf standby register

注册见证节点(在见证节点执行)

1
2
3
4
5
6
7
8
su - postgres

# ============================================================
# 注册见证节点
# 说明:-h 指定主库地址,见证节点通过主库注册到集群
# 见证节点不复制数据,只参与投票
# ============================================================
/usr/local/pgsql16/bin/repmgr -f /etc/repmgr.conf witness -h 192.168.184.123

4.7 启动 repmgrd 守护进程(三台服务器都执行)

💡 repmgrd 是什么? repmgr 的守护进程,常驻后台,持续监控集群状态并在故障时自动执行切换。没有它,repgr 只能手动操作。

方案一:systemd 管理(推荐)

文件 repmgrd.service — 路径:/lib/systemd/system/repmgrd.service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[Unit]
Description=repmgrd service # ← 服务描述
After=postgresql.service # ← 等 PostgreSQL 启动后再启动 repmgrd

[Service]
Type=simple
User=postgres # ← 以 postgres 用户运行
# --daemonize=false 告诉 repmgrd 不要自己后台化,交给 systemd 管理
ExecStart=/usr/local/pgsql16/bin/repmgrd -f /etc/repmgr.conf --daemonize=false
ExecReload=/bin/kill -HUP $MAINPID # ← 重载配置
KillMode=process # ← 只杀主进程
TimeoutSec=30 # ← 30 秒未启动成功则判定失败
Restart=always # ← 无论什么原因退出都自动重启

[Install]
WantedBy=multi-user.target
1
2
3
4
5
6
7
8
9
10
11
# 重新加载 systemd
systemctl daemon-reload

# 启动 repmgrd
systemctl start repmgrd.service

# 设置开机自启
systemctl enable repmgrd.service

# 检查状态
systemctl status repmgrd.service

方案二:nohup 后台运行(临时测试用)

1
2
# 不推荐生产环境使用,systemd 更可靠
nohup repmgr -f /etc/repmgr.conf > /dev/null 2>&1 &

4.8 验证集群状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# ============================================================
# 查看集群状态(最重要的一条命令!)
# 说明:显示每个节点的角色、状态、连接信息
# ============================================================
repmgr -f /etc/repmgr.conf cluster show

# 预期输出示例:
# ID | Name | Role | Status | Upstream | Location
# ----+-------------+---------+-----------+-----------+----------
# 1 | pg-primary | primary | * running | | default
# 2 | pg-standby | standby | running | pg-primary| default
# 3 | pg-witness | witness | * running | pg-primary| default

# ============================================================
# 查看更详细的事件历史
# ============================================================
repmgr -f /etc/repmgr.conf cluster event

# ============================================================
# 手动主备切换(计划内切换,安全的)
# 说明:将当前主库降级为备库,选一个备库提升为新主库
# 必须在 standby 节点上执行
# ============================================================
repmgr -f /etc/repmgr.conf standby switchover

# ============================================================
# 紧急提升备库为主库
# ⚠️ 警告:旧主库不会自动降级!
# 如果旧主库还活着,双方同时写数据会导致脑裂!
# 只在确认旧主库确实挂了的情况下使用
# ============================================================
repmgr -f /etc/repmgr.conf standby promote

5. 安装配置 Keepalived(VIP 漂移)

💡 Keepalived 的作用: 提供一个虚拟 IP(VIP),自动绑定到当前的主库上。业务应用只需连接这个 VIP,不用关心主库是哪台服务器。

5.1 安装 Keepalived(主、备节点执行)

1
2
# 见证节点不需要安装 Keepalived
yum install -y keepalived

5.2 创建 PostgreSQL 健康检查脚本

文件 check_postgres_healthy.sh — 路径:/usr/local/pgsql16/check_postgres_healthy.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/bin/bash
# ============================================================
# Keepalived 健康检查脚本
# 功能:判断当前节点是否为 PostgreSQL 主库
#
# 逻辑:
# - 查询 pg_is_in_recovery() 函数
# - 返回 'f' → 不在恢复模式 → 是主库 → 退出码 0(健康)
# - 返回 't' → 在恢复模式 → 是备库 → 退出码 1(不健康)
# - 连接失败 → 退出码 1(不健康)
#
# ⚠️ 退出码 0 表示"我是主库,可以持有 VIP"
# 退出码 1 表示"我不是主库,释放 VIP 给备库"
# ============================================================

# 查询本地数据库是否处于恢复模式(即是否为备库)
# -t 只输出数据值,不输出列名
# 2>/dev/null 丢弃错误信息
# tr -d ' ' 删除空格(输出可能是 " f" 带前导空格)
result=$(psql -t -c "SELECT pg_is_in_recovery();" 2>/dev/null | tr -d ' ')

if [[ "$result" == "f" ]]; then
# 'f' 表示不在恢复模式,即为主库,退出码 0 表示健康
exit 0
else
# 是备库或连接失败,退出码 1 表示不健康
exit 1
fi
1
2
# 授予脚本执行权限
chmod +x /usr/local/pgsql16/check_postgres_healthy.sh

5.3 配置 Keepalived

主节点配置

文件 keepalived.conf — 路径:/etc/keepalived/keepalived.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# ============================================================
# 全局定义
# ============================================================
global_defs {
route_id pg_primary # ← 路由标识(用于 SMTP 告警,一般不改)
script_user postgres # ← 健康检查脚本以 postgres 用户执行
}

# ============================================================
# VRRP 健康检查脚本定义
# ============================================================
vrrp_script check_postgres {
script "/usr/local/pgsql16/check_postgres_healthy.sh"
interval 2 # ← 每 2 秒执行一次健康检查
weight -50 # ← 检查失败时,当前节点优先级降低 50
# 这样备库(priority 90)就比主库(100-50=50)高
# VIP 会自动漂移到备库
}

# ============================================================
# VRRP 实例配置(核心)
# ============================================================
vrrp_instance VI_1 {
state BACKUP # ← 使用 BACKUP 模式(非抢占模式)
# 主备都设为 BACKUP,配合 nopreempt
nopreempt # ← 禁止抢占!
# 🔑 关键:旧主库恢复后不会抢回 VIP
# 避免 repmgr 已切换主库但 VIP 又漂回去的混乱
interface ens3 # ← 绑定的网卡名(用 ip a 查看你的网卡名)
virtual_router_id 51 # ← VRRP 组 ID,主备必须相同(范围 1-255)
priority 100 # ← 初始优先级(主库 100,备库 90)
# 优先级高的持有 VIP
advert_int 1 # ← VRRP 通告间隔(秒)
# 每 1 秒主备互发心跳包

# ============================================================
# VRRP 认证(主备必须一致)
# ============================================================
authentication {
auth_type PASS # ← 认证类型:PASS(简单密码)或 AH(加密)
auth_pass 1111 # ← ⚠️ 生产环境务必改为复杂密码!
}

# ============================================================
# 虚拟 IP 地址
# ⚠️ 必须替换为你的实际 VIP,格式:IP/子网掩码位数
# 例如:192.168.184.200/24
# ============================================================
virtual_ipaddress {
x.x.x.x/x # ← 👈 把你的 VIP 填在这里!
}

# ============================================================
# 关联健康检查脚本
# ============================================================
track_script {
check_postgres # ← 调用上面定义的检查脚本
}
}

备节点配置

文件 keepalived.conf — 路径:/etc/keepalived/keepalived.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
global_defs {
route_id pg_standby
script_user postgres
}

vrrp_script check_postgres {
script "/usr/local/pgsql16/check_postgres_healthy.sh"
interval 2
weight -50
}

vrrp_instance VI_1 {
state BACKUP
nopreempt
interface ens3
virtual_router_id 51 # ← 必须和主节点相同!
priority 90 # ← 备库优先级比主库低 10
advert_int 1

authentication {
auth_type PASS
auth_pass 1111 # ← 必须和主节点相同!
}

virtual_ipaddress {
x.x.x.x/x # ← 必须和主节点相同!
}

track_script {
check_postgres
}
}

5.4 启动 Keepalived

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 启动 Keepalived
systemctl start keepalived

# 设置开机自启
systemctl enable keepalived

# 查看状态
systemctl status keepalived

# 查看 VIP 是否绑定到主库网卡
ip addr show ens3 # 应该能看到 VIP 地址

# 查看 Keepalived 日志
journalctl -u keepalived -f

6. 集群验证测试

6.1 基础检查清单

1
2
3
4
5
6
7
8
9
10
11
12
13
# ✅ 1. 查看集群状态(所有节点角色是否正确)
repmgr -f /etc/repmgr.conf cluster show

# ✅ 2. 在主库创建测试数据
psql -U repmgr -d repmgr -c "CREATE TABLE test_cluster(id int, msg text);"
psql -U repmgr -d repmgr -c "INSERT INTO test_cluster VALUES(1, 'hello cluster');"

# ✅ 3. 在备库验证数据已同步
psql -U repmgr -d repmgr -c "SELECT * FROM test_cluster;"
# 预期:能看到 id=1 的那条记录

# ✅ 4. 验证 VIP 在主库网卡上
ip addr show | grep <VIP地址>

6.2 故障转移模拟测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# ============================================================
# 测试场景:模拟主库宕机,观察自动切换
# ============================================================

# 1. 在主库上停止 PostgreSQL(模拟故障)
systemctl stop postgresql

# 2. 等待 repmgrd 检测到故障并执行切换(约 1-2 分钟)
# 可以在备库查看日志
tail -f /home/postgres/repmgrd.log

# 3. 检查集群状态(备库应该变成 primary 了)
repmgr -f /etc/repmgr.conf cluster show

# 4. 检查 VIP 是否漂移到新主库
ip addr show | grep <VIP地址>

# 5. 恢复旧主库(作为新备库重新加入集群)
systemctl start postgresql
repmgr -f /etc/repmgr.conf standby follow

7. 故障处理:脑裂修复

⚠️ 脑裂是什么? 主备之间的网络断了,备库以为主库挂了就自己升级成主库。结果变成了两个主库,各自接受写入,数据就彻底乱了。这是最严重的数据库故障。

7.1 如何发现脑裂?

1
2
# cluster show 会显示两个 primary,或者节点状态异常
repmgr -f /etc/repmgr.conf cluster show

7.2 修复方案一:保留数据最完整的节点(推荐)

适用于有一方数据更新、另一方数据较旧的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# ============================================================
# 步骤 1:确定哪个节点的数据最新
# 在"两台都自认为是主库"的节点上分别执行:
# ============================================================
psql -U repmgr -d repmgr -c "SELECT pg_current_wal_lsn();"

# 输出示例:
# pg_current_wal_lsn
# --------------------
# 0/3002B58 ← 这个值越大,数据越新
#
# 👉 保留 LSN 值最大的那台作为主库,修复 LSN 值小的那台

# ============================================================
# 步骤 2:关闭脑裂的旧主库(假设 82 服务器 LSN 值更小)
# 在所有节点上先停止 repmgrd,防止它在修复过程中自动切换
# ============================================================
systemctl stop repmgrd.service # ← 主从见证都执行!
systemctl stop postgresql.service # ← 只关闭要修复的节点

# ============================================================
# 步骤 3:用 pg_rewind 将旧主库的数据同步到新主库状态
# 说明:pg_rewind 找出两个数据目录的差异,只回退旧主库多出来的那部分
# -D 要修复的数据目录
# --source-server 新主库的连接串
# -P 显示进度
# ============================================================
pg_rewind -D /usr/local/pgsql16/data \
--source-server="host=192.168.184.123 user=repmgr dbname=repmgr" -P

# ============================================================
# 步骤 4:启动修复后的 PostgreSQL
# ============================================================
systemctl start postgresql.service

# ============================================================
# 步骤 5:重新注册为备库
# --force 强制重新注册,覆盖旧的角色信息
# ============================================================
repmgr -f /etc/repmgr.conf standby register --force
repmgr -f /etc/repmgr.conf standby follow --force

# ============================================================
# 步骤 6:启动所有节点的 repmgrd
# ============================================================
systemctl start repmgrd.service # ← 所有节点都执行

# ============================================================
# 步骤 7:最终验证集群状态
# ============================================================
repmgr cluster show
# 预期:只有一台 primary,其余为 standby/witness

7.3 修复方案二:删除脑裂节点重建

适用于数据差别不大或修复方案一失败的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# ============================================================
# ⚠️ 警告:此方案会删除脑裂节点上的所有数据!
# 务必先确认该节点数据不重要或已备份
# ============================================================

su - postgres

# 1. 停止 PostgreSQL
sudo systemctl stop postgresql

# 2. 清理数据目录(生产环境建议 mv 备份,不要直接 rm)
rm -rf /usr/local/pgsql16/data/*

# 3. 重新从主库克隆数据(--force 跳过确认)
repmgr -h 192.168.184.123 -U repmgr -d repmgr -p 5432 standby clone --force

# 4. 启动 PostgreSQL
sudo systemctl start postgresql

# 5. 重新注册为备库
repmgr standby register -f /etc/repmgr.conf --force

# 6. 重启 repmgrd
systemctl restart repmgrd.service

8. 常用命令速查

8.1 PostgreSQL

命令 说明
systemctl start postgresql 启动 PostgreSQL
systemctl stop postgresql 停止 PostgreSQL
systemctl restart postgresql 重启 PostgreSQL
systemctl status postgresql 查看运行状态
psql -U repmgr -d repmgr 以 repmgr 用户连接 repmgr 库
pg_ctl reload 重新加载配置文件(不重启)

8.2 repmgr

命令 说明
repmgr -f /etc/repmgr.conf cluster show 查看集群各节点状态
repmgr -f /etc/repmgr.conf cluster event 查看集群事件历史
repmgr -f /etc/repmgr.conf node status 查看当前节点状态
repmgr -f /etc/repmgr.conf node check 检查当前节点配置是否正确
repmgr -f /etc/repmgr.conf standby switchover 计划内主备切换(安全)
repmgr -f /etc/repmgr.conf standby promote 紧急提升备库为主库(有脑裂风险)
repmgr -f /etc/repmgr.conf standby follow 让备库跟随新主库
repmgr -f /etc/repmgr.conf standby clone -h <主库IP> -U repmgr -d repmgr 从主库克隆数据

8.3 Keepalived

命令 说明
systemctl start keepalived 启动 Keepalived
systemctl stop keepalived 停止 Keepalived
systemctl status keepalived 查看运行状态
ip addr show | grep <VIP> 查看 VIP 在哪个节点上
journalctl -u keepalived -f 实时查看 Keepalived 日志

8.4 故障排查命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 查看 repmgrd 日志(最常用的排查手段)
tail -100 /home/postgres/repmgrd.log

# 查看 PostgreSQL 日志
tail -100 /usr/local/pgsql16/data/log/postgresql-*.log

# 检查流复制状态(在主库执行)
psql -U repmgr -d repmgr -c "SELECT * FROM pg_stat_replication;"

# 检查 WAL 接收状态(在备库执行)
psql -U repmgr -d repmgr -c "SELECT * FROM pg_stat_wal_receiver;"

# 查看复制延迟(在主库执行)
psql -U repmgr -d repmgr -c "SELECT application_name, pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS lag_bytes FROM pg_stat_replication;"

🔒 安全建议

  1. 修改默认密码: 所有 Aa_123qwe1111repmgr 密码务必在部署后修改为强密码
  2. 限制网络访问: pg_hba.conf 中的 IP 范围尽量精确,避免使用 0.0.0.0/0
  3. .pgpass 文件权限: 严格设为 600,否则 PostgreSQL 会无视该文件
  4. Keepalived 认证密码: 使用复杂密码(建议 8 位以上随机字符串)
  5. SSL 连接: 生产环境应启用 SSL 加密数据库连接
  6. 定期备份: 集群 ≠ 备份!高可用不能替代备份策略

🎯 快速部署顺序总结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1. 三台服务器编译安装 PostgreSQL 16.0
2. 三台服务器创建 postgres 用户、配置环境变量
3. 主节点 + 见证节点初始化数据库
4. 配置主节点的 postgresql.conf + pg_hba.conf
5. 配置见证节点的 postgresql.conf + pg_hba.conf
6. 三台服务器配置 systemd 服务 + 防火墙 + 创建 repmgr 用户
7. 三台服务器编译安装 repmgr
8. 分别配置三个节点的 repmgr.conf
9. 配置 sudo + SSH 互信 + .pgpass
10. 注册主节点 → 克隆+注册备节点 → 注册见证节点
11. 三台服务器启动 repmgrd
12. 主备节点安装配置 Keepalived(含健康检查脚本)
13. 启动 Keepalived → 验证 VIP 漂移
14. 执行完整的功能验证和故障切换测试

📝 文档版本: v2.0(优化版)
📅 更新日期: 2026-07-21
📋 原始来源: 有道云笔记 - pgsql集群(keepalived+repmgr+postgresql)