pglookout 2.0.3 常用 CLI 命令与工具详解
把 pglookout 2.0.3 的运行期管理工具链一次讲全:主进程的启动/热加载/日志、主节点判定辅助命令 `pglookout_current_master`、HTTP 状态 API(`GET /state.json`、`POST /check`)、状态文件与三类告警文件、故障转移外部命令脚本,并附一次三节点集群完整故障转移演练实测(停主 → 120 秒保护期后备库自动 promote 接管 → 旧主脑裂告警 → `pg_basebackup` 重建旧主回备)。全部命令输出为真实回显,报错与退出码均来自真实触发,已在 Ubuntu 24.04 + PostgreSQL 16.15 三节点集群实测通过。
pglookout 2.0.3 常用 CLI 命令与工具详解
目标:把 pglookout 2.0.3 的运行期管理工具链一次讲全:主进程的启动/热加载/日志、主节点判定辅助命令
pglookout_current_master、HTTP 状态 API(GET /state.json、POST /check)、状态文件与三类告警文件、故障转移外部命令脚本,并附一次三节点集群完整故障转移演练实测(停主 → 120 秒保护期后备库自动 promote 接管 → 旧主脑裂告警 →pg_basebackup重建旧主回备)。全部命令输出为真实回显,报错与退出码均来自真实触发,已在 Ubuntu 24.04 + PostgreSQL 16.15 三节点集群实测通过。
一、环境信息
本文所有命令与回显均在以下环境实测(版本复核日期 2026-11-01,服务器时区 UTC,日志时间戳均为 UTC):
| 项目 | 值 |
|---|---|
| 操作系统 | Ubuntu 24.04 LTS(x86_64) |
| pglookout | 2.0.3(GitHub main 源码,venv 安装于 /opt/pglookout/venv,命令链接至 /usr/local/bin) |
| PostgreSQL | 16.15(Ubuntu 16.15-0ubuntu0.24.04.1) |
| 配置文件 | /etc/pglookout/pglookout.json(每节点一份,位置参数传入) |
| 状态文件 | /var/run/pglookout/pglookout_state.json(json_state_file_path) |
| 告警目录 | /var/run/pglookout(alert_file_dir) |
| 运行日志 | /var/log/postgresql/pglookout.log(进程管理器重定向) |
| HTTP 状态服务 | 每节点监听 15000 端口(http_port) |
| 集群拓扑 | 主 p1(10.89.60.11,主机名 pg-primary)/ 备 s1(10.89.60.12)/ 观察 obs1(10.89.60.13) |
| 监控账号 | pglookout(密码 q1w2e3r4,需 pg_monitor 角色 + pg_read_binary_file 授权) |
| 复制账号 | replicator(密码 q1w2e3r4,REPLICATION 属性) |
| 故障转移脚本 | /opt/pglookout/failover.sh(failover_command,内部执行 pg_ctl promote) |
集群部署过程见 《pglookout 2.0.3 Docker Compose 部署指南》;参数键的语义与取值边界见 《pglookout 2.0.3 配置参数详解》;组件定位与选型见 《pglookout 介绍与选型指南》。三篇分工一句话:选型看 A1、部署看 B2、参数看 C1、进程怎么管和故障怎么演看本文。
二、工具概览
pglookout 的 CLI 体系非常收敛——只有两个二进制、没有子命令,全部行为由配置文件驱动,运行期管理靠"文件 + HTTP"完成:
| 工具 | 角色 | 本文章节 |
|---|---|---|
pglookout | 主命令:启动监控/故障转移守护进程,唯一位置参数是配置文件,支持 SIGHUP 热加载 | 4.1 |
pglookout_current_master | 辅助命令:从状态文件读出当前主节点名,退出码可直接写进脚本 | 4.2 |
| HTTP 状态 API | 运行期查询接口:GET /state.json 查集群状态、POST /check 触发立即检查 | 4.3 |
| 状态文件 | /var/run/pglookout/pglookout_state.json:判定结果与辅助命令的数据源 | 4.4 |
| 告警文件 | failover_has_happened / multiple_master_warning / replication_delay_warning | 4.5 |
| 外部命令脚本 | failover_command / pg_start_command / pg_stop_command,真正执行切换动作的"手" | 4.6 |
💡 说明:pglookout 没有"打印当前生效配置"或"手动触发切换"的子命令。想让备库立即提升,唯一路径是让主库真的失联并等过
failover_timeout保护期(见 4.7 演练);想核对运行期视点,读状态文件或调 HTTP 接口(4.3/4.4)。
三、前置条件
- 三节点集群已按部署篇搭好、配置篇调好参数,三个节点上 pglookout 进程都在运行。确认方法(主节点
p1实测回显):
ps -eo pid,args | grep [p]glookout
1 /opt/pglookout/venv/bin/python3 /usr/local/bin/pglookout /etc/pglookout/pglookout.json
53 postgres: 16/main: pglookout postgres 10.89.60.11(41120) idle
59 postgres: 16/main: pglookout postgres 10.89.60.13(53820) idle
62 postgres: 16/main: pglookout postgres 10.89.60.12(32866) idle
第一行是 pglookout 主进程本身;后三行是它(以及备节点、观察节点的 pglookout)连进本库的监控会话——三个节点各有一条 pglookout 用户连接是正常形态。
- 节点上有
curl、python3、psql;操作用户可读/var/run/pglookout/与/var/log/postgresql/pglookout.log。 - 本文 4.7 演练会真的停掉主库并触发一次不可逆的故障转移(切换后旧主需重建),务必在测试环境执行,并提前确认
failover.sh内容(4.6)。
四、各工具详解
4.1 pglookout 主命令:启动、热加载与日志
用法(--help 实测回显):
usage: pglookout [-h] [--version] config
postgresql replication monitoring and failover daemon
positional arguments:
config configuration file
options:
-h, --help show this help message and exit
--version show program version
pglookout --version
2.0.3
启动:唯一位置参数就是配置文件路径,进程以前台方式运行,生产环境交给进程管理器托管(本验证环境无 systemd,重启路径以 supervisord 等等价执行):
pglookout /etc/pglookout/pglookout.json
启动日志(log_level: DEBUG,演练后重启 p1 节点 pglookout 的实测回显,节选):
2026-11-01 09:37:17,151 pglookout MainThread DEBUG Loading JSON config from: '/etc/pglookout/pglookout.json'
2026-11-01 09:37:17,151 ClusterMonitor MainThread DEBUG Initialized ClusterMonitor with: {}
2026-11-01 09:37:17,152 WebServer MainThread DEBUG WebServer initialized with address: '0.0.0.0' port: 15000
2026-11-01 09:37:17,152 pglookout MainThread INFO PGLookout initialized, local hostname: 'pg-primary', own_db: 'p1', cwd: '/'
2026-11-01 09:37:17,152 pglookout MainThread WARNING No cluster state: {}, probably still starting up, node_count: 0, configured node_count: 2
2026-11-01 09:37:17,170 pglookout MainThread INFO Immediate failover check completed
2026-11-01 09:37:17,170 pglookout MainThread INFO New master node detected: old: None new: 's1': {'fetch_time': '2026-11-01T09:37:17.091326Z', 'connection': True, 'db_time': '2026-11-01T09:37:17.091419Z', 'pg_is_in_recovery': False, 'pg_last_xact_replay_timestamp': None, 'pg_last_xlog_receive_location': None, 'pg_last_xlog_replay_location': '0/B000670', 'replication_time_lag': None, 'min_replication_time_lag': 0.039022, 'replication_slots': []}
三个要点:
- 日志格式为
时间戳\t模块\t线程\t级别\t消息,制表符分隔,可直接awk -F'\t' '$4=="WARNING"'过滤; - 进程启动后第一个决策周期就会完成一次 failover check(
Immediate failover check completed),并从状态里认出新主(本例old: None new: 's1',因为此时集群已完成切换); local hostname是socket.gethostname()的结果,own_db取自配置,核对这两个值是排障第一步——own_db填错会让节点认错自己。
热加载:改完配置发 SIGHUP 即可,无需重启:
kill -HUP $(pidof pglookout)
信号只置"待重载"标志,实际重读发生在下一个决策周期边界(replication_state_check_interval,默认 10 秒),日志时间戳比发信号晚 0~10 秒属正常;参数级行为详见 《pglookout 2.0.3 配置参数详解》 的热加载一节。
日志检索:
tail -f /var/log/postgresql/pglookout.log # 实时跟踪
grep -E 'failover|master' /var/log/postgresql/pglookout.log | tail -20 # 只看切换/主节点判定
4.2 pglookout_current_master:主节点判定辅助命令
用法(--help 实测回显):
usage: pglookout_current_master [-h] [--version] state
postgresql replication monitoring and failover daemon
positional arguments:
state pglookout state file
options:
-h, --help show this help message and exit
--version show program version
⚠️ 帮助文本有误导:位置参数虽叫 state,但实际要传的是配置文件路径——源码(pglookout/current_master.py)先把它当 JSON 读,取出 json_state_file_path 键,再去读真正的状态文件。传状态文件本身会因取不到 json_state_file_path 而走默认值 /tmp/pglookout_state.json,实测直接退出码 255(见六 Q3)。
正常输出(演练基线时在主节点 p1 实测):
pglookout_current_master /etc/pglookout/pglookout.json; echo $?
p1
0
故障转移完成后,在备节点(此时已是新主)与观察节点上实测,返回的都是新主名:
s1
exit=0
退出码语义(源码 + 实测):
| 退出码 | 含义 | 实测回显 |
|---|---|---|
0 | 正常,stdout 输出 current_master 值 | p1 / s1 |
1 | 传入的配置文件不存在 | pglookout_current_master: '/etc/pglookout/nope.json' doesn't exist |
255(即 -1) | 状态文件超过 60 秒未更新(pglookout 进程大概率已死),或任何解析失败 | 无输出,exit=255 |
脚本用法(应用侧路由/巡检脚本的标准写法):
MASTER=$(pglookout_current_master /etc/pglookout/pglookout.json) \
|| { echo "pglookout 视点异常(进程未运行或状态文件过期)"; exit 1; }
if [ "$(hostname -s)" = "$(echo "$MASTER" | tr -d \"'\")" ]; then
echo "本节点是主库"
fi
4.3 HTTP 状态 API:/state.json 与 /check
每个节点的 pglookout 都在 http_port(默认 15000)上提供一个简易 HTTP 服务,共两个可用端点。
GET /state.json——返回该节点视点的裸集群状态字典(键为节点名):
curl -s http://10.89.60.13:15000/state.json | python3 -c "import json,sys; d=json.load(sys.stdin); print('top keys:', sorted(d.keys()))"
top keys: ['p1', 's1']
演练终态实测(观察节点视点):
s1 conn= True recovery= False
p1 conn= True recovery= True
⚠️ 与磁盘状态文件结构不同:HTTP 端点直接吐出 cluster_state 字典(只有各 DB 节点的采集值),没有 current_master、observer_nodes 这些顶层键;顶层视点信息只在状态文件里(4.4)。写采集脚本别把两者当同一个结构。
POST /check——触发一次立即状态检查 + 故障转移检查,返回 204 No Content:
curl -s -o /dev/null -w '%{http_code}\n' -X POST http://10.89.60.13:15000/check
204
对应节点日志(实测回显):
2026-11-01 09:28:31,877 WebServer Thread-479 (process_request_thread) INFO Immediate status check requested
10.89.60.1 - - [01/Nov/2026 09:28:31] "POST /check HTTP/1.1" 204 -
2026-11-01 09:28:31,880 pglookout MainThread INFO Immediate failover check completed
GET /check 是一个必须避开的坑:健康检查用 GET 会挂——源码的 do_GET 只实现了 /state.json,其他路径回 404,且 404 分支不调用 end_headers,客户端会一直等响应体直到超时:
curl -s -o /dev/null -w '%{http_code}\n' --max-time 5 http://10.89.60.13:15000/check
000
(000 即 curl 未收到完整响应。)而节点访问日志里明明有 404 记录:
10.89.60.1 - - [01/Nov/2026 09:28:09] "GET /check HTTP/1.1" 404 -
结论:健康检查必须用 POST /check(204 即健康),不要用 GET。
4.4 状态文件:pglookout_state.json
路径由 json_state_file_path 决定,本环境为 /var/run/pglookout/pglookout_state.json。每个决策周期由主进程重写一次,结构为三层:db_nodes(各 DB 节点采集值)、observer_nodes(观察节点视点)、current_master(当前主节点名)。
实测回显(演练终态,观察节点,提取命令见下):
python3 -c "import json; d=json.load(open('/var/run/pglookout/pglookout_state.json')); print('current_master:', d['current_master']); print('db_nodes:', d.get('db_nodes')); print('observer_nodes:', d.get('observer_nodes'))"
current_master: s1
db_nodes: {'s1': {'fetch_time': '2026-11-01T09:42:07.189171Z', 'connection': True, 'db_time': '2026-11-01T09:42:07.189262Z', 'pg_is_in_recovery': False, 'pg_last_xact_replay_timestamp': None, 'pg_last_xlog_receive_location': None, 'pg_last_xlog_replay_location': '0/B0025A0', 'replication_time_lag': None, 'min_replication_time_lag': 0.039022, 'replication_slots': []}, 'p1': {'fetch_time': '2026-11-01T09:42:07.189040Z', 'connection': True, 'db_time': '2026-11-01T09:42:07.189155Z', 'pg_is_in_recovery': True, 'pg_last_xact_replay_timestamp': '2026-11-01T09:42:03.009041Z', 'pg_last_xlog_receive_location': '0/B0025A0', 'pg_last_xlog_replay_location': '0/B0025A0', 'replication_slots': [], 'replication_time_lag': 4.180114, 'min_replication_time_lag': 2.521524}}
observer_nodes: {}
读法要点:
current_master是全集群视点,pglookout_current_master命令读的就是这个键;- 每个节点的
connection表示"该视点采集时能否连上",pg_is_in_recovery为False即主库身份;终态里s1不在恢复模式(新主)、p1在恢复模式(已回备)且收发位点一致(0/B0025A0),说明复制健康; replication_time_lag只在备节点有值(本例 4.18 秒,为演练后回放追平的瞬时值);- 文件 mtime 超过 60 秒未更新 = pglookout 进程已死,
pglookout_current_master会据此返回 255(4.2)。
4.5 告警文件:三类"地砖"
alert_file_dir(本环境 /var/run/pglookout/)下的存在性文件是 pglookout 的原生告警通道,适合被任何监控采集器以"文件是否存在"判定:
| 告警文件 | 触发条件 | 自动消除 | 实测 |
|---|---|---|---|
replication_delay_warning | 备库回放延迟超过 warning_replication_time_lag | ✅ 延迟回落后自动删除 | 见配置篇实测 |
failover_has_happened | 本节点执行过 failover_command | ❌ 需人工确认集群健康后删除 | 4.7 步骤 5 |
multiple_master_warning | 视图中出现两个可达主库(脑裂) | ❌ 需人工处置后删除 | 4.7 步骤 6 |
源码事实:delete_alert_file 只对 replication_delay_warning 调用,后两类文件创建后永不自动删除,这是设计使然——切换/脑裂必须人工复核。
演练切换后备节点实测回显:
ls -l /var/run/pglookout/; cat /var/run/pglookout/failover_has_happened
total 20
-rw-r--r-- 1 root root 5 Nov 1 09:30 failover_has_happened
-rw-r--r-- 1 root root 2238 Nov 1 09:31 pglookout_state.json
alert
脑裂时旧主实测回显:
total 20
-rw-r--r-- 1 root root 5 Nov 1 09:32 multiple_master_warning
-rw-r--r-- 1 root root 2236 Nov 1 09:32 pglookout_state.json
$ cat /var/run/pglookout/multiple_master_warning
alert
内容固定为 5 字节的 alert,告警语义全在文件名上。人工确认集群健康后的标准处置:
rm /var/run/pglookout/failover_has_happened /var/run/pglookout/multiple_master_warning
4.6 外部命令脚本:failover_command 与 pg_start/stop_command
pglookout 自己不动数据库,所有"提升/重启/停止"动作都通过配置里的三个命令键委托给外部脚本。
failover_command(本环境配置为 /opt/pglookout/failover.sh,cat 实测回显):
#!/bin/bash
# pglookout failover_command: promote the local standby to primary
set -x
logger -t pglookout "failover_command invoked, promoting local PostgreSQL instance"
su postgres -c "/usr/lib/postgresql/16/bin/pg_ctl promote -D /var/lib/postgresql/16/main -t 60 -w"
# 生产环境建议在此实现 STONITH(fence 旧主),示例略
exit 0
切换时刻的实际执行日志(备节点 s1,4.7 演练中回显):
2026-11-01 09:30:42,580 pglookout MainThread WARNING Executing external command: ['/opt/pglookout/failover.sh']
+ logger -t pglookout 'failover_command invoked, promoting local PostgreSQL instance'
+ su postgres -c '/usr/lib/postgresql/16/bin/pg_ctl promote -D /var/lib/postgresql/16/main -t 60 -w'
waiting for server to promote.... done
server promoted
+ exit 0
2026-11-01 09:30:42,689 pglookout MainThread WARNING Executed external command: 0, output: 0
2026-11-01 09:30:42,689 pglookout MainThread WARNING Executed failover command: ['/opt/pglookout/failover.sh'], return_code: 0, took: 0.11s
三个要点:
set -x的跟踪输出(+ logger ...)会被 pglookout 原样写进自己的日志,脚本内logger另发 syslog(tagpglookout),双通道留痕;failover_command会在所有判定"该我上"的存活 DB 节点上执行,pglookout 没有互斥锁——脚本必须幂等(pg_ctl promote对已提升实例立即返回),生产环境应在脚本里实现 STONITH 隔离旧主;- 脚本返回码只记录不决策(
return_code: 0, took: 0.11s),非零不会回滚已做的提升。
pg_start_command / pg_stop_command:autofollow: true 时,pglookout 发现新主后用它俩重启本节点数据库完成"改嫁"。旧主实测回显(4.7 步骤 6):
2026-11-01 09:32:22,325 pglookout MainThread INFO Starting to follow new master 's1', modified recovery.conf and restarting PostgreSQL; pg_start_command ['pg_ctlcluster', '16', 'main', 'start']; pg_stop_command ['pg_ctlcluster', '16', 'main', 'stop', '-m', 'fast']
2026-11-01 09:32:22,325 pglookout MainThread WARNING Executing external command: ['pg_ctlcluster', '16', 'main', 'stop', '-m', 'fast']
Cluster is not running.
2026-11-01 09:32:22,347 pglookout MainThread ERROR Problem with executing: ['pg_ctlcluster', '16', 'main', 'stop', '-m', 'fast'], return_code: 2, output: None
2026-11-01 09:32:22,349 pglookout MainThread WARNING Executed external command: 2, output: ''
2026-11-01 09:32:22,349 pglookout MainThread WARNING Executing external command: ['pg_ctlcluster', '16', 'main', 'start']
2026-11-01 09:32:24,510 pglookout MainThread WARNING Executed external command: 0, output: 0
2026-11-01 09:32:24,510 pglookout MainThread INFO Started following new master 's1', took: 2.19s
stop 失败(旧主停机期间本就"Cluster is not running",pg_ctlcluster 返回 2)只打 ERROR 行,流程继续执行 start——这既是容错也是隐患:start 起来的实例角色完全取决于数据目录现状(下一步就踩了这个坑)。
4.7 故障转移演练实测:停主 → 接管 → 旧主回备
以下为完整演练记录。拓扑:p1 主 / s1 备 / obs1 观察;failover_timeout 120 秒;s1 与 p1 均 autofollow: true。
步骤 0:基线确认(见 4.2/4.3 实测:POST /check 返回 204、helper 返回 p1、p1 角色 f|1、s1 角色 t)。演练数据在 p1 预置:
CREATE TABLE IF NOT EXISTS failover_drill (id int PRIMARY KEY, note text, at timestamptz DEFAULT now());
INSERT INTO failover_drill (id, note) VALUES (1, 'before-failover');
步骤 1:停主(p1,09:28:39 UTC):
pg_ctlcluster 16 main stop
su postgres -c "psql -tAc 'SELECT 1'"
psql: error: connection to server on socket "/var/run/postgresql/.s.PGSQL.5432" failed: No such file or directory
Is the server running locally and accepting connections on that socket?
步骤 2:备节点第一时间感知(s1 日志)。主库连接先被管理员命令终止,随后拒绝连接:
2026-11-01 09:28:42,811 ClusterMonitor ThreadPoolExecutor-236_0 WARNING OperationalError (FATAL: terminating connection due to administrator command
SSL connection has been closed unexpectedly) querying status from p1
2026-11-01 09:28:47,813 ClusterMonitor Thread-1 WARNING OperationalError (Connection refused
Is the server running on that host and accepting TCP/IP connections?) connecting to p1 ('p1' (host=10.89.60.11 dbname=postgres user=pglookout; hidden password))
步骤 3:120 秒保护期(s1 日志,每个决策周期重复,节选):
2026-11-01 09:28:52,534 pglookout MainThread WARNING No known master node, disconnected masters: ['p1']
2026-11-01 09:28:52,534 pglookout MainThread WARNING No reachable master node in cluster, 1 standby nodes exist, 1182.37 seconds since last cluster config update, failover timeout set to 120.0 seconds, previous master: 'p1', master_node: {'fetch_time': '2026-11-01T09:28:46.884434Z', 'connection': False, 'db_time': '2026-11-01T09:28:36.880832Z', 'pg_is_in_recovery': False, 'pg_last_xact_replay_timestamp': None, 'pg_last_xlog_receive_location': None, 'pg_last_xlog_replay_location': '0/900FF48', 'replication_slots': [], 'replication_time_lag': None}
2026-11-01 09:28:52,537 pglookout MainThread WARNING Performing failover decision because master node is not reachable
2026-11-01 09:28:52,537 pglookout MainThread WARNING No connected master nodes, but last contact was still within failover timeout (120.0s), not failing over
最后这行就是"保护期":主库失联但未超过 failover_timeout,不切换——主库若在 120 秒内恢复,集群不会有任何动作。期间每 10 秒一轮 Connection refused + not failing over,直到 09:30:42。
步骤 4:决策与提升(s1 日志,09:30:42):
2026-11-01 09:30:42,580 pglookout MainThread WARNING Node that is furthest along is: 's1', all replication positions were: [151060560]
2026-11-01 09:30:42,580 pglookout MainThread WARNING We will now do a failover to ourselves since we were the instance furthest along
2026-11-01 09:30:42,580 pglookout MainThread WARNING Executing external command: ['/opt/pglookout/failover.sh']
(failover.sh 执行回显见 4.6,return_code: 0, took: 0.11s。)10 秒后的下一周期,新主身份被确认:
2026-11-01 09:30:52,690 pglookout MainThread WARNING Picked 's1' as master since {'p1': {...'connection': False...}} are in a disconnected state
2026-11-01 09:30:52,690 pglookout MainThread INFO New master node detected: old: 'p1' new: 's1': {'fetch_time': '2026-11-01T09:30:52.583684Z', 'connection': True, 'db_time': '2026-11-01T09:30:52.584235Z', 'pg_is_in_recovery': False, 'pg_last_xact_replay_timestamp': None, 'pg_last_xlog_receive_location': None, 'pg_last_xlog_replay_location': '0/9010208', 'replication_time_lag': None, 'replication_start_time': 24427.124697868, 'min_replication_time_lag': 0.192627, 'replication_slots': []}
从停主(09:28:39)到 promote(09:30:42)实测耗时约 123 秒,几乎全部花在 120 秒保护期上——这就是 pglookout 故障转移的 RTO 下限。
步骤 5:切换后验证:
su postgres -c "psql -tAc 'SELECT pg_is_in_recovery(), timeline_id FROM pg_control_checkpoint()'" # s1
f|2
时间线已推进到 2。/var/run/pglookout/failover_has_happened 出现(4.5 回显)。观察节点视点:HTTP /state.json 中 s1 conn=True recovery=False、p1 conn=False;状态文件与 helper 均给出 s1(4.2 回显)。新主写入验证:
INSERT INTO failover_drill (id, note) VALUES (2, 'after-failover-write');
INSERT 0 1
步骤 6:旧主自动改嫁——脑裂。p1 的 pglookout 进程一直活着,autofollow 生效,日志与 postgresql.auto.conf 被改写(4.6 已给重启回显)。auto.conf 实测内容:
# pglookout updated primary_conninfo for instance s1 at 2026-11-01T09:32:22.325419Z
# Do not edit this file manually!
# It will be overwritten by the ALTER SYSTEM command.
primary_conninfo = 'user=replicator password=q1w2e3r4 application_name=pglookout-primary sslmode=disable host=10.89.60.12'
recovery_target_timeline = 'latest'
但 pglookout 只改 auto.conf,不创建 standby.signal(PG 12+ 的备库标志文件)——旧主数据目录里没有它,于是 start 起来的 p1 以主库身份运行:
$ su postgres -c "psql -tAc 'SELECT pg_is_in_recovery()'" # p1
f
$ ls /var/lib/postgresql/16/main/standby.signal
ls: cannot access '/var/lib/postgresql/16/main/standby.signal': No such file or directory
两个"主库"同时可达,p1 的 pglookout 日志随即报脑裂:
2026-11-01 09:32:34,511 pglookout MainThread ERROR More than one master node connected_master_nodes: {'s1': {...'pg_is_in_recovery': False, ...'pg_last_xlog_replay_location': '0/9010D50'...}, 'p1': {...'pg_is_in_recovery': False, ...'pg_last_xlog_replay_location': '0/9010118'...}}, disconnected_master_nodes: {}
并落地 multiple_master_warning 告警文件(4.5 回显)。更糟的是 p1 以主身份又写了一点 WAL(checkpoint 已到 0/90104A8,而时间线 1 只到 0/9010050)——WAL 分叉。
步骤 7:手工降级失败——timeline 分叉实锤。补上标志文件重启:
touch /var/lib/postgresql/16/main/standby.signal
chown postgres:postgres /var/lib/postgresql/16/main/standby.signal
pg_ctlcluster 16 main restart
启动即 FATAL(实测回显):
2026-11-01 09:35:04.374 UTC [1047] LOG: entering standby mode
2026-11-01 09:35:04.374 UTC [1047] FATAL: requested timeline 2 is not a child of this server's history
2026-11-01 09:35:04.374 UTC [1047] DETAIL: Latest checkpoint is at 0/90104A8 on timeline 1, but in the history of the requested timeline, the server forked off from that timeline at 0/9010050.
pg_ctl: could not start server
Examine the log output.
步骤 8:pg_rewind 失败——前置条件缺失。标准修法是先试 pg_rewind(在新主给 postgres 角色设好密码后,旧主停机执行):
su postgres -c "pg_rewind --target-pgdata=/var/lib/postgresql/16/main --source-server='host=10.89.60.12 user=postgres password=q1w2e3r4 dbname=postgres'"
pg_rewind: connected to server
pg_rewind: error: target server needs to use either data checksums or "wal_log_hints = on"
exit=1
本集群 initdb 未开数据校验和、wal_log_hints 也是默认 off——pg_rewind 根本不可用(该前置必须在建库时定,见五-6)。中途还踩了一个次生坑:以主身份临时启动修配置时 ALTER SYSTEM 报 ERROR: could not open file "postgresql.auto.conf": Permission denied——auto.conf 已被 pglookout 以 rename 方式重写、属主变成 root(见五-5)。
步骤 9:pg_basebackup 重建旧主回备。停机、清空数据目录、从新主全量重建(-R 自动生成 standby.signal + primary_conninfo):
rm -rf /var/lib/postgresql/16/main/*
su postgres -c "pg_basebackup -h 10.89.60.12 -U replicator -D /var/lib/postgresql/16/main -R -X stream"
pg_basebackup: created temporary replication slot "pg_basebackup_1268"
23274/23274 kB (100%), 0/1 tablespace (...gresql/16/main/global/pg_control)
23274/23274 kB (100%), 1/1 tablespace
pg_basebackup: write-ahead log end point: 0/A000100
pg_basebackup: waiting for background process to finish streaming ...
pg_basebackup: syncing data to disk ...
pg_basebackup: renaming backup_manifest.tmp to backup_manifest
pg_basebackup: base backup completed
pg_ctlcluster 16 main start
start exit=0
步骤 10:收敛验证(终态实测回显):
# p1:角色 + 接收/回放位点
su postgres -c "psql -tAc \"SELECT pg_is_in_recovery(), pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn()\""
# p1:walreceiver 状态
su postgres -c "psql -tAc \"SELECT status, sender_host, received_tli, latest_end_lsn FROM pg_stat_wal_receiver\""
# s1:walsender 状态
su postgres -c "psql -tAc \"SELECT application_name, state, sync_state FROM pg_stat_replication\""
# p1:演练表数据
su postgres -c "psql -tAc 'SELECT * FROM failover_drill ORDER BY id'"
t|0/B0026C8|0/B0026C8
streaming|10.89.60.12|2|0/B0026C8
16/main|streaming|async
1|before-failover|2026-11-01 08:59:16.289383+00
2|after-failover-write|2026-11-01 09:33:14.767819+00
p1 已在新主(时间线 2)上流式回放,切换前后两行数据都在——数据零丢失(演练中主库是干净停机的;异常宕机时未归档 WAL 可能丢失,这是 pglookout 这类"无 fencing 自动切换"方案的固有代价)。
步骤 11:收尾。人工确认集群健康后清理告警文件(4.5),重启 p1 的 pglookout 加载回退后的配置(autofollow 关闭,避免下次演练再触发自动改嫁),终态全景:
current_master: s1 | p1 recovery: True | s1 recovery: False
helper 与 HTTP 接口视点一致(4.2/4.3 实测),集群回到"一主一备一观察"的健康形态,只是主备互换——旧主 p1 需择机用同样的 basebackup 流程反向重建回主,或用 planned switchover 思路手工回切(pglookout 本身不提供回切命令)。
五、注意事项
- RTO 下限 =
failover_timeout:实测停主到 promote 约 123 秒,其中 120 秒是保护期。调小该参数会增大误切风险(网络抖动即切换),权衡见配置篇。 failover_command无互斥、无 fencing:脚本在所有"该我上"的存活 DB 节点上都会执行,官方 README 明确警告脑裂风险。脚本必须幂等,生产应加 STONITH;演练中旧主脑裂(4.7 步骤 6)就是没有 fencing 的直接后果。autofollow: true的前提是节点本来就在备库模式:pglookout 只改写postgresql.auto.conf里的primary_conninfo/recovery_target_timeline,不会创建standby.signal。主库身份回归的节点没有该文件,改嫁后仍会以主身份启动造成脑裂。标准做法:部署备库时就touch standby.signal,并把它纳入配置管理。- 旧主回归不是自动的:切换后旧主要么
pg_rewind要么pg_basebackup重建,pglookout 只负责"让它连上新主",不负责数据层收敛。 - pglookout 会改
postgresql.auto.conf的文件属主:它以"写临时文件 + rename"方式重写该文件,属主变成 pglookout 的运行账号(本环境 root),postgres 随后执行ALTER SYSTEM会报Permission denied(实测,见 4.7 步骤 8)。修法:chown postgres:postgres /var/lib/postgresql/16/main/postgresql.auto.conf。 - pg_rewind 前置条件必须在建库时定:
wal_log_hints = on或 initdb 数据校验和二选一,事后无法用 rewind 补救已分叉的旧主(wal_log_hints改动还需完整重启 + checkpoint 才生效)。本演练两者都没开,只能走 basebackup。 - HTTP 端点与状态文件结构不同(4.3/4.4):
/state.json只有裸cluster_state,current_master只在状态文件里;采集脚本按各自结构取数。 - 健康检查必须
POST /check(4.3):GET 会因源码 404 分支不关闭响应头而挂起,负载均衡/存活探针用 GET 会全部超时。 - 本验证环境无 systemd:进程托管以 supervisord 等等价执行;
pg_ctlcluster是 Debian 系打包的集群控制命令,RPM 系发行版对应pg_ctl -D <datadir>。
六、常见问题
Q1:curl GET /check 一直挂起/超时,服务是不是挂了?
现象:curl 返回码 000,但节点访问日志明明打了 404。
原因:do_GET 未实现 /check 且 404 分支不调 end_headers,响应永远发不完(4.3 实测)。
解决:健康检查改用 POST /check,204 即健康。
Q2:停了主库,备库迟迟不切换?
现象:日志每 10 秒刷 No connected master nodes, but last contact was still within failover timeout (120.0s), not failing over。
原因:保护期未过,属正常行为(4.7 步骤 3)。
解决:等满 failover_timeout;若始终不切,依次检查 failover_on_disconnect 是否为 false、本节点是否在 never_promote_these_nodes 名单、own_db 是否配置(观察节点 own_db 为空永不提升)。
Q3:pglookout_current_master 返回 255 且无输出?
原因:状态文件超 60 秒未更新(pglookout 进程死了),或传错了参数——该命令要传配置文件而不是状态文件(4.2,帮助文本的 state 措辞有误导)。
解决:先 pidof pglookout 确认进程活着,再核对传参是 /etc/pglookout/pglookout.json。
Q4:切换后旧主上出现 multiple_master_warning,日志报 More than one master node?
原因:autofollow 改嫁时旧主数据目录缺 standby.signal,以主身份回归(4.7 步骤 6)。
解决:立即停旧主防止双写;按 Q5/Q6 把旧主降级回备;确认集群单主健康后 rm 告警文件。
Q5:旧主加 standby.signal 后起不来,报 FATAL: requested timeline 2 is not a child of this server's history?
原因:脑裂期间旧主以主身份写过 WAL,时间线 1 已分叉(DETAIL 里的 checkpoint 位点超过分叉点 0/9010050,4.7 步骤 7)。
解决:不能直接回放,必须 pg_rewind(有前置条件,见 Q6)或 pg_basebackup 重建(4.7 步骤 9)。
Q6:pg_rewind 报 target server needs to use either data checksums or "wal_log_hints = on"?
原因:建库时没开校验和、wal_log_hints 默认 off,旧主分叉点之后的 WAL 不可回放。
解决:小数据量直接 pg_basebackup -R 重建(本文实测路径);长期方案在建库时开 initdb -k 或 wal_log_hints = on。
Q7:ALTER SYSTEM 报 could not open file "postgresql.auto.conf": Permission denied?
原因:pglookout 改嫁时以 rename 方式重写了 auto.conf,属主变成 pglookout 运行账号(五-5)。
解决:chown postgres:postgres 修回属主再执行。
Q8:配置里删了 pg_data_directory 等键并 SIGHUP 后,日志每周期刷 TypeError: expected str, bytes or os.PathLike object, not NoneType,进程不死但刷屏?
原因:热加载路径的源码只判断 current_master != own_db 就尝试重写恢复配置、不检查 autofollow 开关(演练中已回退 autofollow=false 仍复现),取到 None 的数据目录每周期抛异常。
解决:重启 pglookout 进程即清除;改配置尽量"改值"而不是"删键",删键后走重启。
七、参考链接
- 《pglookout 介绍与选型指南》(本专栏 A1 篇)
- 《pglookout 2.0.3 Docker Compose 部署指南》(本专栏 B2 篇)
- 《pglookout 2.0.3 配置参数详解》(本专栏 C1 篇)
- 官方仓库与 README:https://github.com/pglookout/pglookout
- 源码:
pglookout/pglookout.py(告警文件、改嫁逻辑)、pglookout/current_master.py(helper 退出码)