前阵子凌晨两点收到告警,生产环境的PostgreSQL主库所在机器彻底没响应了
SSH连不上,控制台也卡死,只能走机房强制重启。等数据库重新拉起来,业务已经中断了将近一个小时。复盘的时候团队都很沉默——单机部署的PostgreSQL再稳,也扛不住机器级别的故障。
那次事故之后,我用容器化方式重新设计了一套主从高可用方案:Docker跑PostgreSQL流复制,Keepalived负责虚拟IP漂移,主库故障时自动把VIP切到备库,再通过脚本将备库提升为新主库。整套方案不复杂,所有配置加起来不到两百行,但能覆盖绝大多数中小团队的数据库高可用诉求。今天把完整的部署过程、关键脚本和踩过的坑都整理出来,帮大家少走弯路。
1. 为什么选Keepalived而不是Patroni:高可用方案选型复盘
很多人一听PostgreSQL高可用,第一反应就是Patroni。我承认Patroni是更专业的方案,但实际选型要回到自己的场景。我们团队就两个人兼职运维,没有专职DBA,对复杂系统的驾驭能力有限,所以在Patroni和Keepalived之间做了认真对比才最终确定方案。
1.1 单机部署到底缺什么
单机PostgreSQL最大的问题不是性能,而是单点。进程崩溃还好说,systemd会拉起来;但机器宕机、网络隔离、磁盘损坏这些情况,光靠进程守护解决不了。当时我们连备份都是隔天一次,数据损失最多能接受一天的容忍度,但业务中断的影响完全不能接受。
高可用方案需要解决三个问题:数据冗余、故障检测、自动切换。流复制解决了数据冗余,主库的WAL日志实时同步到备库;故障检测和自动切换需要额外组件——Keepalived就是干这个的。它是Linux上非常成熟的VIP漂移工具,通过VRRP协议在主备节点间协商虚拟IP的归属,配合自定义健康检查脚本,就能实现对数据库的高可用保护。
1.2 Keepalived与Patroni的核心取舍
两者要达到的目标类似,但实现路径差异很大。我做了一张对比表,方便大家根据自己的团队情况判断。
| 对比维度 | Keepalived方案 | Patroni方案 |
|---|---|---|
| 部署复杂度 | 低,一个yum包加三个脚本 | 高,需要etcd或consul集群配合 |
| 依赖组件 | Docker + Keepalived | Docker + Patroni + etcd/Consul + HAProxy可选 |
| 故障转移能力 | 依赖自定义脚本完成数据库提升 | 自动完成选主、提升、配置更新 |
| 脑裂防护 | 有限,靠脚本判断和数据库只读限制 | 通过etcd分布式锁机制强力防脑裂 |
| 学习成本 | 一天可以上手 | 需要理解Raft/分布式一致性等概念 |
| 运维负担 | 简单直接,脚本逻辑自己掌控 | 组件多,排查链路长 |
| 适用规模 | 2-3个节点的小集群 | 3个节点以上的生产集群,或需要自动恢复的场景 |
我的观点很明确:如果你的集群规模在3个节点以内,团队对PostgreSQL内部机制有一定了解,Keepalived方案完全够用,而且出问题时排查路径非常清晰。如果节点超过3台、业务对数据库可用性要求接近金融级别,或者团队人手充足,那直接上Patroni。工具没有绝对的好坏,匹配场景才是关键。
1.3 容器化架构与组件分工
这套方案的架构分两层。底层是Docker容器跑PostgreSQL实例,数据目录挂载到宿主机磁盘,容器只负责提供运行环境,日志和数据都在宿主机上,方便备份和排查。上层是Keepalived守护进程,直接运行在宿主机上(或者以host网络模式跑容器,但我更推荐宿主机直接装),负责虚拟IP的管理和故障转移。
两个节点各跑一个PostgreSQL容器,主库可读写,备库通过流复制实时接收WAL并应用。虚拟IP 192.168.10.100绑定在持有主库角色的节点上,应用连接字符串指向这个VIP,不关心后端是哪台机器。当主库节点故障时,Keepalived通过健康检查脚本感知到数据库不可用,自动将VIP漂移到备库节点,同时触发备库的promote操作,把备库提升为新主库,整个过程不需要人工介入。
组件分工清晰:Docker解决环境一致性和部署效率,流复制解决数据同步,Keepalived解决故障转移的流量切换,脚本负责数据库角色变更。每个组件只做一件事,出问题时各个击破。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 两个节点的Docker部署与基础配置
从这章开始进入实操。我以两台Ubuntu 22.04服务器为例,分别是192.168.10.11和192.168.10.12,VIP规划为192.168.10.100。生产环境建议直接用固定IP的服务器,尽量不要用DHCP动态分配,否则节点IP变化会让整个高可用链路崩溃。
2.1 镜像选择与目录规划
PostgreSQL官方镜像的版本演进很快,我建议选一个稳定的主版本长期使用,不要频繁追新。目前16.x是主流稳定版本,我们从postgres:16.2这个tag开始,等16.x发布补充版本后再平滑升级。
目录规划上,数据目录放在 /data/postgres,备份目录放 /data/backup,日志目录可走宿主机journald或者挂载到 /data/logs。这里有一个非常重要但容易被忽略的点:官方镜像的PGDATA路径是 /var/lib/postgresql/data,这个路径必须挂载到宿主机持久化目录。很多新手直接把整个容器存储当成数据存储,容器一删数据全没了。
bash复制# 安装Docker(Ubuntu)
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
sudo apt update && sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
# 创建数据目录
sudo mkdir -p /data/postgres
提示:无论用哪一套安装方式,确保Docker服务开机自启的默认为已启用,容器也设置
restart: always,否则节点重启后数据库不会自动拉起,高可用就名存实亡了。
2.2 主库启动与关键参数说明
在192.168.10.11上创建docker-compose.yml,内容如下:
yaml复制services:
postgres:
image: postgres:16.2
container_name: postgres
restart: always
environment:
POSTGRES_PASSWORD: "StrongPg@2024"
TZ: "Asia/Shanghai"
volumes:
- /data/postgres:/var/lib/postgresql/data
ports:
- "5432:5432"
command:
- "-c"
- "wal_level=replica"
- "-c"
- "max_wal_senders=10"
- "-c"
- "wal_keep_size=1024"
- "-c"
- "hot_standby=on"
- "-c"
- "log_min_messages=warning"
这几个参数每一个都有讲究。wal_level=replica 是流复制的基础,它决定WAL日志中包含足够的信息用于从库回放,PG13之后默认就是replica,但显式写出来便于后人理解。max_wal_senders=10 指定最多同时连接多少个WAL发送进程,一个从库至少占一个,给10意味着理论上可以接到9-10个从库,对中小团队绰绰有余。
wal_keep_size=1024 这个参数值得多说一句。它表示主库至少保留1024MB的WAL日志不被回收。流复制从库如果断连一段时间,重连时需要从主库获取断点之后的WAL,如果这段WAL已经被循环覆盖,从库就只能重新做基础备份,非常痛苦。1024MB对大多数业务足够,如果你的主库每分钟产生大量WAL日志,可以适当调大。
启动后验证:
bash复制cd /data && docker compose up -d
docker ps
docker exec postgres pg_isready -h 127.0.0.1 -p 5432
2.3 复制账号与pg_hba.conf授权
主库启动后,需要创建专门用于复制的数据库账号。这里强调一下:永远不要用超级用户做流复制账号,用一个最小权限账号做专门用途,方便管理和审计。
bash复制docker exec -it postgres psql -U postgres
sql复制CREATE ROLE replica WITH REPLICATION LOGIN PASSWORD 'Replica@2024';
接下来调整 pg_hba.conf,让备库所在的网段可以连接到复制服务。官方镜像的pg_hba.conf默认只允许本地回环访问,不改的话备库根本连不上主库。生产环境最好精确到具体IP段,不要图省事写0.0.0.0/0。
bash复制docker exec -it postgres bash
echo "host replication replica 192.168.10.0/24 md5" >> /var/lib/postgresql/data/pg_hba.conf
exit
docker exec postgres psql -U postgres -c "SELECT pg_reload_conf();"
这里有个容易踩的坑:如果数据目录挂载到宿主机 /data/postgres,你在宿主机上直接编辑 /data/postgres/pg_hba.conf 也是可以的,效果完全一样。但记得改完执行 pg_reload_conf() 重载配置,不需要重启实例。
3. 流复制从库搭建:pg_basebackup + standby.signal完整过程
主库配置完成后,开始搭建备库。流复制从库的核心其实就两步:用基础备份把主库的数据目录拷贝过来,然后用 standby.signal 告诉数据库以只读恢复模式启动。官网文档写得比较分散,这里我把完整命令串一遍。
3.1 基础备份的多种实现方式
PostgreSQL提供 pg_basebackup 工具来制作基础备份,它能把主库的数据文件、WAL日志和必要的元数据打包成一个完整的数据目录。官方镜像自带这个工具,所以我们可以用postgres:16.2镜像直接运行这个工具,不需要额外安装客户端。
在192.168.10.12上执行以下步骤。先说清楚原理:-D /tmp/pgdata 指定基础备份目标路径,因为我们是从容器里执行命令,需要先把备份拉取到宿主机临时目录,然后再同步到最终的数据目录。这样看起来多了一步,但能避免很多权限问题。
bash复制# 清空已有数据目录(如果是全新环境可跳过)
sudo rm -rf /data/postgres/*
# 用临时容器执行pg_basebackup
docker run --rm -it \
-v /data/postgres:/data \
postgres:16.2 \
pg_basebackup -h 192.168.10.11 -p 5432 -U replica \
-D /data/postgres -Fp -Xs -R -P
命令参数的含义:
| 参数 | 含义 |
|---|---|
-h |
指定主库IP地址 |
-U |
指定连接的用户名 |
-D |
指定备份数据的存放路径 |
-Fp |
输出格式为plain(平面目录),不要打成tar包 |
-Xs |
开启fetch模式的WAL日志备份,避免备份过程中WAL文件缺失 |
-R |
自动生成standby.signal文件和primary_conninfo配置 |
-P |
显示进度 |
执行完毕后,检查自动生成的配置文件。
bash复制cat /data/postgres/postgresql.auto.conf
# 内容类似:
# primary_conninfo = 'user=replica password=Replica@2024 host=192.168.10.11 port=5432 sslmode=prefer'
3.2 standby模式启动与验证
关键点来了:-R 参数会自动创建 standby.signal 文件,这个文件的存在告诉PostgreSQL启动后直接进入standby(只读恢复)模式,不再执行正常的读写启动流程。
bash复制ls -la /data/postgres/standby.signal
然后像主库一样启动从库容器。用同样的docker-compose.yml,只需要改一下IP相关的配置环境变量。启动后,从库会自动读取主库地址并开始流复制。
bash复制cd /data && docker compose up -d
验证流复制是否生效,从主库和从库两个方向分别检查。
bash复制# 在主库上检查复制状态
docker exec postgres psql -U postgres -c "SELECT client_addr, state, sync_state, write_lag FROM pg_stat_replication;"
# 在从库上检查恢复状态
docker exec postgres psql -U postgres -c "SELECT pg_is_in_recovery();"
如果 pg_stat_replication 里出现一行记录,state为streaming,说明流复制已经建立成功;如果 pg_is_in_recovery 返回t,说明从库处于只读恢复模式。
3.3 同步状态与延迟监控
流复制建立起来只是第一步,日常监控才是重点。我习惯把复制延迟监控做成一个定时任务,每分钟检查一次,超过阈值就告警。
bash复制docker exec postgres psql -U postgres -tAc "SELECT now() - pg_last_xact_replay_timestamp();"
这条命令返回从库回放WAL的时间与当前时间的差值,如果接近0表示同步正常,持续增长则说明延迟在堆积。延迟堆积常见原因有:从库磁盘I/O性能差、网络带宽瓶颈、主库大量写入导致WAL产生速度超过从库应用速度。
提示:默认流复制是异步模式,主库提交事务后,从库可能延迟几百毫秒到几秒不等。如果业务对数据一致性要求很高,可以考虑配置
synchronous_standby_names将模式调整为同步复制,但主库写入性能会因此下降,且两个库必须同时在线。对大多数中小团队,异步模式配合监控告警够用了。
4. Keepalived配置:健康检查、VIP漂移与自动提升脚本
流复制就绪后,实现故障转移的核心组件是Keepalived。这里面的难点不在VRRP配置本身,而在健康检查脚本和数据库提升脚本的联动逻辑。合理的设计是:Keepalived拉活VIP、脚本负责提升数据库,两者配合,缺一不可。
4.1 keepalived.conf核心配置
在两台宿主机上分别安装Keepalived(apt install keepalived或yum install keepalived),然后创建配置文件 /etc/keepalived/keepalived.conf。主库节点192.168.10.11和备库节点192.168.10.12的配置差异仅在priority。
conf复制global_defs {
router_id PG_HA_1
}
vrrp_script check_pg {
script "/etc/keepalived/check_postgres.sh"
interval 2
timeout 3
fall 2
rise 2
}
vrrp_instance PG_HA {
state BACKUP
interface ens33
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass pg_ha@2024
}
virtual_ipaddress {
192.168.10.100/24 dev ens33
}
track_script {
check_pg
}
notify_master "/etc/keepalived/notify_master.sh"
notify_backup "/etc/keepalived/notify_backup.sh"
notify_fault "/etc/keepalived/notify_fault.sh"
}
几个配置要点说透:
state BACKUP 不是笔误。两个节点都配置为BACKUP,用priority的值决定谁优先。这样在主库故障恢复后,不会因为Keepalived的MASTER状态记忆问题导致冲突,一切以priority和VRRP协商结果为准。
priority 100(主库节点)和 priority 90(备库节点)决定了VIP的归属优先级。正常情况下priority高的节点持有VIP,当主库故障时,备库的priority虽然低,但VRRP只有在高优先级节点失联时才选举备库,所以备库能接管VIP。
virtual_router_id 51 在同一广播域内必须唯一。如果多个网段、多个网口都跑Keepalived,不同的vrrp_instance要用不同的ID。
nopreempt 是否启用需要仔细思考。如果启用,主库恢复后不会自动抢回VIP,避免了因切换造成的业务抖动;如果关闭,主库恢复后自动重新接管VIP,但对应用的影响更大,可能需要重启连接池。我的建议是:依赖DDL变更和负载均衡场景可以开nopreempt,一般场景默认关闭让主库自动回切。
4.2 健康检查脚本的细节设计
check_postgres.sh 是整个健康检查的核心,它的返回值直接决定Keepalived是否认为当前节点健康。脚本设计思路上有一个关键细节:只有持有VIP的节点才需要因为数据库故障触发VIP漂移;对没有持有VIP的节点,数据库故障不应导致它降级退出。
bash复制#!/bin/bash
VIP="192.168.10.100"
# 判断当前节点是否持有VIP
if ip addr show | grep -q " ${VIP}/"; then
# 持有VIP的节点,检查PostgreSQL是否存活
if docker exec postgres pg_isready -h 127.0.0.1 -p 5432 > /dev/null 2>&1; then
exit 0
else
logger "PostgreSQL is down on VIP holder, stopping keepalived for failover"
systemctl stop keepalived
exit 1
fi
else
# 未持有VIP的节点,不做数据库检查
exit 0
fi
这个脚本的关键是 systemctl stop keepalived 一行。当持有VIP的节点检测到数据库挂了,主动停止Keepalived会让VRRP认为该节点失联,备库立即进入MASTER状态并接管VIP。这里有个连带逻辑:备库在接管VIP时,notify_master脚本会执行,再结合数据库promote来提升从库。如果只是VIP漂移而数据库没有提升,从库还是只读状态,应用连接VIP后写操作会直接报错。
4.3 notify_master中的自动提升逻辑
当备库的Keepalived从BACKUP变成MASTER时,会触发 notify_master.sh 脚本。这个脚本要做两件事:等待VIP生效,然后提升数据库。
bash复制#!/bin/bash
# /etc/keepalived/notify_master.sh
logger "Entering MASTER state, promoting PostgreSQL if necessary"
# 等待VIP绑定
sleep 2
# 检查当前数据库是否处于recovery(只读)模式
IS_RECOVERY=$(docker exec postgres psql -U postgres -tAc "SELECT pg_is_in_recovery();")
if [ "$IS_RECOVERY" = "t" ]; then
logger "PostgreSQL is in recovery mode, promoting to primary"
docker exec postgres psql -U postgres -c "SELECT pg_promote();"
fi
这里用 pg_promote() 而不是 pg_ctl promote,因为pg_promote是SQL函数,调用方式更简洁,也省去了容器内寻找二进制路径的麻烦。PG12之后这个函数一直稳定可用,建议直接用它。
还有一个细节需要注意:脚本监听的是PG环境的密码。可能在psql时提示输入密码,实际上因为local socket的peer认证可以跳过,但如果遇到认证报错,需要设置 PGPASSWORD 环境变量,或者在 ~/.pgpass 里配置。最简单的方式是直接以postgres用户执行,确保密码不需要显式传递。
notify_backup.sh 和 notify_fault.sh 我建议也写上,主要是打日志和可选的通知动作。如果团队接入了告警系统,在这两个脚本里加入curl或python调用webhook即可。
配置完成后,重启Keepalived:
bash复制sudo systemctl restart keepalived
然后在节点上验证VIP是否正常绑定。
bash复制ip addr show | grep 192.168.10.100
此时VIP应该出现在主库节点192.168.10.11上。从任何一台服务器对一个VIP执行 ping 192.168.10.100 应该能通。
5. 故障演练:拔网线、杀容器、重启的完整切换测试
配置写完不演练等于白做。高可用方案必须实际验证过才敢上线,否则故障真的发生时,脚本的逻辑bug会让你在半夜手忙脚乱。下面分享我实际做过的三轮故障演练,每一轮都发现了问题。
5.1 模拟主库宕机的切换验证
第一轮演练最简单:直接在主库节点执行 docker stop postgres,模拟数据库进程崩溃。
观察切换过程:
bash复制# 在备库节点持续观察
watch -n 1 ip addr show | grep 192.168.10.100
正常情况下,几秒内VIP会出现在备库节点上。然后检查数据库角色是否成功切换:
bash复制docker exec postgres psql -U postgres -c "SELECT pg_is_in_recovery();"
# 预期返回 f,表示不再是只读模式
再用VIP做一次实际写入测试:
bash复制psql -h 192.168.10.100 -U postgres -c "CREATE TABLE test_switch(id int); INSERT INTO test_switch VALUES (1);"
如果这条命令执行成功,说明从库已经提升为可写的主库,应用层只要连接的是VIP,就能继续工作。
当时我遇到的第一个问题:从库提升成功,但VIP没有立即出现在备库。排查发现是健康检查脚本里 systemctl stop keepalived 动作导致的主库Keepalived进程退出不够快,VRRP的广播间隔是1秒,但脚本里 exit 1 和 systemctl stop 的组合有时会导致主库的Keepalived在降级前多坚持几次广播。解决办法是把 interval 从2秒调成1秒,fall 从2调成1,让故障感知更快。
5.2 旧主恢复后的角色逆转与重新加入
第二轮演练模拟主库机器恢复。这个环节最容易出事故。如果你直接把旧主库的Docker容器启动,它还会尝试以主库角色运行,但它持有的数据已经落后于新主库,如果两个库都接受写入,数据就分叉了。这在数据库术语中叫脑裂,比较危险。
正确的做法是:旧主库恢复后,把它重新变成新主库的从库。具体操作如下。
首先清理旧主库的数据目录。因为旧数据已经包含了旧时间点的数据,不能直接作为从库启动,重新从新主库拉一份基础备份。
bash复制# 在旧主库节点192.168.10.11上执行
docker stop postgres
sudo rm -rf /data/postgres/*
# 用pg_basebackup从新主库拉取基础备份
docker run --rm -it \
-v /data/postgres:/data \
postgres:16.2 \
pg_basebackup -h 192.168.10.12 -p 5432 -U replica \
-D /data/postgres -Fp -Xs -R -P
# 启动容器
docker compose up -d
启动后验证流复制状态。此时192.168.10.12是新主库,192.168.10.11重新变成从库。Keepalived的priority决定:192.168.10.11的priority原本是100,高于192.168.10.12的90,所以当它重新启动并检测到数据库健康后,会自动抢回VIP,完成回切。
但这里有个策略问题:旧主恢复后立即抢回VIP,会造成一次额外的业务闪断。如果业务高峰时段节点故障,恢复后立刻回切可能比保持现状更危险。我的建议是:生产环境给Keepalived加 nopreempt,让恢复后的原主库自动降为从库角色,始终保持当前主库不切换。等业务低峰期再手动做计划内切换。
5.3 常见坑:ARP缓存、参数不一致、容器自启
第三轮演练中暴露了不少细节问题,梳理几个高频的坑。
坑1:VIP漂移后ARP缓存不更新。 VIP从主库漂移到备库后,部分客户端和交换机还保留着旧IP到MAC地址的映射,导致VIP虽是同一个IP,但流量还是发往旧主库。Keepalived本身会在状态切换时发送免费ARP报文,但如果网络设备缓存时间比较长,仍然可能短暂不可达。解决办法是在notify_master脚本里主动发一次免费ARP:
bash复制arping -I ens33 -c 3 -s 192.168.10.100 192.168.10.1
坑2:主从配置参数不一致导致回放异常。 流复制过程中,从库的某些运行时参数如果与主库差异过大,可能导致WAL回放失败。常见问题包括 max_connections 从库比主库小、wal_level 不一致等。Docker compose里我通常把主从两套容器参数保持一致,避免这类问题。
坑3:容器状态与Keepalived状态脱节。 如果Docker服务没配置开机自启,或者容器没有 restart: always,节点重启后容器不会自动启动,而Keepalived可能还在正常运行,健康检查脚本发现数据库挂掉后会强制VIP漂移,导致两个节点都没有数据库可用。排查时看起来是VIP也在、服务也启动,但数据库就是连不上。这个坑非常隐蔽,建议把 restart: always 当作硬性要求写入部署规范。
6. 运维体检清单:日常巡检与监控建议
高可用方案上线后,不要以为万事大吉。定期巡检能让你提前发现隐患,避免故障发生时才暴露问题。
6.1 主动巡检命令组合
我每周抽几分钟跑一遍下面这些命令,全部通过定时任务或者脚本汇总输出,很节省时间。
bash复制# 1. 检查两节点VIP归属是否符合预期
ip addr show | grep 192.168.10.100
# 2. 检查两节点数据库角色
docker exec postgres psql -U postgres -c "SELECT pg_is_in_recovery();"
# 3. 检查主库侧复制连接数
docker exec postgres psql -U postgres -c "SELECT client_addr, state, sync_state FROM pg_stat_replication;"
# 4. 检查从库回放延迟
docker exec postgres psql -U postgres -tAc "SELECT now() - pg_last_xact_replay_timestamp();"
# 5. 检查Keepalived状态
systemctl status keepalived | grep Active
如果输出中 pg_is_in_recovery 返回t的节点恰好是持有VIP的节点,说明数据库处于只读但VIP却指向它,应用写请求会失败。这种场景通常发生在notify_master脚本执行失败时,巡检能第一时间发现。
6.2 应该关注的监控指标
除了巡检,监控告警也要覆盖三个层面:
数据库层面:流复制状态是否为streaming、WAL延迟大小、连接数使用率、慢查询数量。这些可以通过pg_stat_replication和pg_stat_activity视图采集。
系统层面:磁盘空间(特别是数据目录所在分区)、CPU负载、内存使用率。数据目录磁盘不足是一个常被忽视的隐患,主库会直接停机,从库可能进入Paused状态。
Keepalived层面:进程是否存活、VIP是否在预期节点上、notify脚本是否执行过。建议将notify_master、notify_backup的日志发送到统一的日志中心,方便事后分析切换链路。
提示:监控告警通道最好独立于数据库,否则数据库故障时告警也发不出去。我们用的告警方式是邮件加企业微信机器人,确保数据库不可用时告警仍然能送达。
6.3 后续扩展思路
如果你的业务增长,这套方案还可以在几个方向做扩展。
一是加一个从库节点做读写分离。Keepalived方案天然支持多从库,在备库节点上跑同样的Docker容器和Keepalived配置,把priority再调低一档即可。应用层可以把只读查询分发到VIP后面的从库节点上,减轻主库压力。
二是引入定时备份策略。高可用解决的是可用性问题,备份解决的是数据误删、灾难恢复的问题,两者不可互相替代。建议每天从从库节点做一次pg_dump或pg_basebackup,备份数据保留至少7天。
三是将Keepalived的notify脚本与告警平台打通。在notify_fault和notify_backup里加入webhook调用,任何角色变化都实时通知到运维群。切换是否成功、何时发生、由哪个节点接管,这些信息在故障复盘时非常关键。
关于这套方案,我最后再啰嗦几句
在整个部署和演练过程中,我最大的体会是:高可用方案的难度不在搭建,而在故障处理时的逻辑闭环。Keepalived只管VIP漂移,流复制只管数据同步,两者之间必须有清晰的衔接——数据库没挂时,VIP不漂移;数据库挂了,VIP必须漂移;VIP漂移到备库后,备库必须自动提升;旧库恢复后,必须降级为新主库的从库。任何一个环节断裂,整个高可用就成了摆设。
如果你正准备在生产环境落地这套方案,我的建议是先做三次完整的故障演练,每次都在旧主库恢复失败后再启动新主库测试,把所有恢复流程跑通再正式上线。另外,环境配置完成后,把keepalived.conf和两个脚本文件纳入版本管理,任何改动都要经过评审和演练,不要在生产环境临时改脚本。
这套方案我已经稳定运行了一年多,期间经历过两次真实的机器宕机,切换时间都在十秒以内,业务影响可控。希望这篇记录能帮到准备做PostgreSQL高可用的朋友。有任何部署细节问题,欢迎在评论区交流,我有空都会回复。
