pglookout 2.0.3 Docker Compose 部署指南
用 Docker Compose 在一台 Docker 主机上部署 pglookout 2.0.3 三节点监控故障转移实验环境(PostgreSQL 16.15 主节点 + 备节点 + 独立观察节点),打通流复制、HTTP 状态 API 与 autofollow 自动跟随,已在 Ubuntu 26.04.1 LTS(Docker 29.9.2 / Compose v5.6.1)实测通过。关于 pglookout 的定位、机制与选型对比,见 [《pglookout 介绍与选型指南》](https://www.quzuo.net/document/UGpJ1qZQEKAw)。
pglookout 2.0.3 Docker Compose 部署指南
目标:用 Docker Compose 在一台 Docker 主机上部署 pglookout 2.0.3 三节点监控故障转移实验环境(PostgreSQL 16.15 主节点 + 备节点 + 独立观察节点),打通流复制、HTTP 状态 API 与 autofollow 自动跟随,已在 Ubuntu 26.04.1 LTS(Docker 29.9.2 / Compose v5.6.1)实测通过。关于 pglookout 的定位、机制与选型对比,见 《pglookout 介绍与选型指南》。
一、环境信息
| 项目 | 值 |
|---|---|
| 宿主操作系统 | Ubuntu 26.04.1 LTS(x86_64) |
| Docker | 29.9.2(build a82e528) |
| Docker Compose | v5.6.1 |
| pglookout | 2.0.3(GitHub main 分支源码,Makefile short_ver = 2.0.3) |
| PostgreSQL | 16.15(Ubuntu 16.15-0ubuntu0.24.04.1) |
| 基础镜像 | ubuntu:24.04(Python 3.12.3) |
| 自建镜像 | pglookout-node:2.0.3(磁盘占用 1.16GB / 内容 282MB) |
| 集群网络 | 10.89.60.0/24(user-defined bridge,显式 subnet) |
| 节点拓扑 | pg-primary 10.89.60.11 / pg-standby 10.89.60.12 / pg-observer 10.89.60.13 |
| 宿主映射端口 | 8140 → observer 15000(HTTP 状态 API) |
| 数据持久化 | ./data/primary、./data/standby 两个 bind mount |
| 演示账号 | pglookout 监控角色 / replicator 复制角色,密码统一 q1w2e3r4 |
二、镜像选型说明
2.1 无官方镜像,必须自建
pglookout 官方仓库(aiven/pglookout)不提供 Docker 镜像,Docker Hub / ghcr 上也没有官方发布;README 给出的安装形态是 Debian make deb、Fedora make rpm 与 Python 包三种。本实验统一走「自建镜像」路线:以 ubuntu:24.04 为基础,apt 安装 PostgreSQL 16 服务端与客户端,Python venv 安装 pglookout,一个镜像同时承载 db 节点与 observer 节点,靠环境变量区分角色。
💡 技术拓展:pglookout 的部署形态不止自建镜像一种——
形态 做法 适用场景 deb/rpm 包 源码根目录 make deb/make rpm后 dpkg/dnf 安装传统发行版服务器,systemd 托管 pip/venv 安装 源码包装入 venv,裸进程或 Supervisor 托管 无打包习惯的 Python 运维栈 自建容器镜像 本方案:apt PG + venv pglookout 合一镜像 实验、伪集群、容器化平台 本文因需在一台主机上快速拉起三节点并保证可复现,采用「自建容器镜像 + Compose」形态;生产物理机部署优先 deb/rpm + systemd。
2.2 版本锚定:2.0.3,而不是 PyPI 的 1.4.0
版本实查结果:PyPI 最新发行版是 1.4.0(2017 年发布),GitHub main 分支 Makefile 中 short_ver = 2.0.3。1.4.0 发行包在任何现代 Python 上都无法运行(async=True 关键字、import imp 两处硬伤,详见「十一、常见问题」11.1/11.2),因此部署版本锚定为 GitHub main 源码包 2.0.3。README 明确 2.0.x 支持 CPython 3.9+ 与 PostgreSQL 10+,与 ubuntu:24.04 自带 Python 3.12.3、PG 16.15 完全兼容。
| 版本来源 | 版本号 | Python 兼容性 | 结论 |
|---|---|---|---|
| PyPI sdist | 1.4.0 | 仅 ≤3.6(async 关键字、imp 模块) | 现代系统不可用,弃用 |
| GitHub main | 2.0.3 | CPython 3.9+(async_ 形参、importlib) | 本文采用 |
⚠️ 注意:2.0.3 的 setup.py 漏声明 packaging 依赖(代码 from packaging.version import parse),pip 安装时必须手工追加 packaging,否则启动即 ModuleNotFoundError(见 11.3)。
2.3 基础镜像与 PostgreSQL 版本
基础镜像选 ubuntu:24.04:apt 仓库直接提供 postgresql-16(实测 16.15)与 python3(3.12.3),一条 apt-get install 同时解决 PG 与 Python,避免混用 PGDG 源。pglookout 监控 PostgreSQL 10 及以上版本,16.x 在支持范围内。
三、前置条件
3.1 确认 Docker 与 Compose
docker --version && docker compose version
实测回显:
Docker version 29.9.2, build a82e528
Docker Compose version v5.6.1
3.2 复核网络槽位与宿主端口
集群网段使用 10.89.60.0/24,部署前确认该网段未被既有网络占用、宿主 8140 端口空闲:
docker network ls | grep pglookout # 首次部署应无输出
ss -ltn | grep ':8140' || echo "8140 free" # 应输出 8140 free
3.3 下载 pglookout 源码包
PyPI 1.4.0 不可用,从 GitHub main 分支取源码包并校验:
curl -L -o pglookout-main.tar.gz https://codeload.github.com/aiven/pglookout/tar.gz/refs/heads/main
sha256sum pglookout-main.tar.gz
实测校验和(2026-11-01 抓取):
ad5502636d7af1acc2855d7243b83fc3e2db6bcf8d6c2e93f3d88436e49521df pglookout-main.tar.gz
💡 说明:main 分支是滚动源码,无固定 release tag;复现本文时若校验和变化,以
make -n version(Makefileshort_ver)确认仍为 2.0.3 即可。
四、部署步骤
4.1 目录布局
pglookout-stack/
├── compose.yaml
├── Dockerfile
├── entrypoint.sh
├── pglookout-main.tar.gz
├── conf/
│ ├── pglookout-primary.json
│ ├── pglookout-standby.json
│ ├── pglookout-observer.json
│ ├── postgresql-pglookout.conf
│ └── failover.sh
└── data/
├── primary/ # 主节点 PGDATA 持久化
└── standby/ # 备节点 PGDATA 持久化
4.2 Dockerfile
一个镜像承载三种角色,PG 与 pglookout 装在同一环境:
FROM ubuntu:24.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
postgresql-16 postgresql-client-16 \
python3 python3-venv python3-dev python3-pip \
build-essential libpq-dev \
ca-certificates curl iproute2 procps vim less \
&& rm -rf /var/lib/apt/lists/*
# PyPI 的 1.4.0 发行包在 Python 3.7+ 上不可用(async 关键字/imp 模块问题),
# 从 GitHub main 分支源码包安装(Makefile short_ver = 2.0.3)
COPY pglookout-main.tar.gz /tmp/
RUN tar xzf /tmp/pglookout-main.tar.gz -C /tmp \
&& python3 -m venv /opt/pglookout/venv \
&& /opt/pglookout/venv/bin/pip install --no-cache-dir /tmp/pglookout-main packaging \
&& rm -rf /tmp/pglookout-main /tmp/pglookout-main.tar.gz
RUN ln -s /opt/pglookout/venv/bin/pglookout /usr/local/bin/pglookout \
&& ln -s /opt/pglookout/venv/bin/pglookout_current_master /usr/local/bin/pglookout_current_master
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
要点:pip install 行末尾的 packaging 是手工补上的缺失依赖(见 2.2 注意事项);libpq-dev + build-essential 供 psycopg2 源码编译。
4.3 entrypoint.sh(按角色初始化)
镜像入口按环境变量 PGLOOKOUT_ROLE 分三条路径:observer 直接起 pglookout;primary 负责建集群、建角色、授权后起 pglookout;standby 负责等待主库、pg_basebackup -R 拉取基准备份后起 pglookout。
#!/bin/bash
set -e
ROLE="${PGLOOKOUT_ROLE:-observer}"
PGDATA=/var/lib/postgresql/16/main
mkdir -p /var/run/pglookout /var/log/pglookout
if [ "$ROLE" = "observer" ]; then
exec pglookout /etc/pglookout/pglookout.json
fi
# ensure a PostgreSQL 16 cluster exists (data dir persisted under /var/lib/postgresql)
if [ ! -s "$PGDATA/PG_VERSION" ]; then
pg_dropcluster --stop 16 main 2>/dev/null || true
pg_createcluster 16 main
fi
# replication / monitoring settings via conf.d include
cp /opt/pglookout/postgresql-pglookout.conf /etc/postgresql/16/main/conf.d/pglookout.conf
# allow pglookout + replication connections from the cluster network
if ! grep -q 'pglookout-net' /etc/postgresql/16/main/pg_hba.conf; then
cat >> /etc/postgresql/16/main/pg_hba.conf <<'EOF'
# pglookout monitoring & streaming replication (pglookout-net)
host all all 10.89.60.0/24 scram-sha-256
host replication replicator 10.89.60.0/24 scram-sha-256
EOF
fi
if [ "$ROLE" = "primary" ]; then
pg_ctlcluster 16 main start || true
su postgres -c "psql -c \"CREATE ROLE pglookout LOGIN PASSWORD 'q1w2e3r4'\"" || true
su postgres -c "psql -c \"CREATE ROLE replicator LOGIN REPLICATION PASSWORD 'q1w2e3r4'\"" || true
su postgres -c "psql -c \"GRANT pg_monitor TO pglookout\"" || true
su postgres -c "psql -c \"GRANT EXECUTE ON FUNCTION pg_read_binary_file(text) TO pglookout\"" || true
su postgres -c "psql -c \"GRANT EXECUTE ON FUNCTION pg_read_binary_file(text, bigint, bigint) TO pglookout\"" || true
su postgres -c "psql -c \"GRANT EXECUTE ON FUNCTION pg_read_binary_file(text, bigint, bigint, boolean) TO pglookout\"" || true
su postgres -c "psql -c \"GRANT EXECUTE ON FUNCTION pg_read_binary_file(text, boolean) TO pglookout\"" || true
exec pglookout /etc/pglookout/pglookout.json
fi
if [ "$ROLE" = "standby" ]; then
echo '10.89.60.11:5432:*:replicator:q1w2e3r4' > /var/lib/postgresql/.pgpass
chown postgres:postgres /var/lib/postgresql/.pgpass
chmod 600 /var/lib/postgresql/.pgpass
# 等待主库就绪且 replicator 角色可用(角色由主库 entrypoint 创建)
until su postgres -c "psql -h 10.89.60.11 -U replicator -d postgres -tAc 'SELECT 1'" >/dev/null 2>&1; do sleep 2; done
rm -rf "${PGDATA:?}"/*
su postgres -c "pg_basebackup -h 10.89.60.11 -U replicator -D $PGDATA -Fp -Xs -R -P"
pg_ctlcluster 16 main start || true
exec pglookout /etc/pglookout/pglookout.json
fi
echo "unknown PGLOOKOUT_ROLE: $ROLE" >&2
exit 1
三处关键设计:
- primary 的 4 条
GRANT EXECUTE ON FUNCTION pg_read_binary_file(...):pglookout 读取逻辑复制槽状态时调用pg_read_binary_file,即使查询命中 0 行也会做函数 ACL 检查,不授权会导致主节点状态轮询整体InsufficientPrivilege失败(见 11.5)。 - standby 的
until ... psql -U replicator等待循环:备库初始化必须等主库 entrypoint 把replicator角色建好,否则pg_basebackup报password authentication failed(见 11.4)。 pg_ctlcluster start || true:set -e下重复启动已在线的集群会返回非零,加|| true保证幂等。
4.4 compose.yaml
name: pglookout-2.0.3-stack
networks:
pglookout-net:
driver: bridge
ipam:
config:
- subnet: 10.89.60.0/24
services:
pg-primary:
build:
context: .
image: pglookout-node:2.0.3
container_name: pglookout-2.0.3-primary
hostname: pg-primary
environment:
PGLOOKOUT_ROLE: primary
volumes:
- ./data/primary:/var/lib/postgresql
- ./conf/pglookout-primary.json:/etc/pglookout/pglookout.json:ro
- ./conf/failover.sh:/opt/pglookout/failover.sh:ro
- ./conf/postgresql-pglookout.conf:/opt/pglookout/postgresql-pglookout.conf:ro
networks:
pglookout-net:
ipv4_address: 10.89.60.11
pg-standby:
image: pglookout-node:2.0.3
container_name: pglookout-2.0.3-standby
hostname: pg-standby
environment:
PGLOOKOUT_ROLE: standby
volumes:
- ./data/standby:/var/lib/postgresql
- ./conf/pglookout-standby.json:/etc/pglookout/pglookout.json:ro
- ./conf/failover.sh:/opt/pglookout/failover.sh:ro
- ./conf/postgresql-pglookout.conf:/opt/pglookout/postgresql-pglookout.conf:ro
networks:
pglookout-net:
ipv4_address: 10.89.60.12
depends_on:
- pg-primary
pg-observer:
image: pglookout-node:2.0.3
container_name: pglookout-2.0.3-observer
hostname: pg-observer
environment:
PGLOOKOUT_ROLE: observer
volumes:
- ./conf/pglookout-observer.json:/etc/pglookout/pglookout.json:ro
networks:
pglookout-net:
ipv4_address: 10.89.60.13
ports:
- "8140:15000"
depends_on:
- pg-primary
要点:顶层 name: 固定项目名,保证网络名、卷归属可预期;ipam 显式 subnet 让三节点 IP 稳定为 .11/.12/.13,pglookout 配置里的连接串才能写死地址;只有 observer 发布宿主端口 8140。
⚠️ 注意:Compose 生成网络名时会剥掉项目名中的点号——项目 pglookout-2.0.3-stack 的实际网络名是 pglookout-203-stack_pglookout-net:
docker network ls | grep pglookout
0fac1f976fb4 pglookout-203-stack_pglookout-net bridge local
4.5 PostgreSQL 增量配置与 failover 脚本
conf/postgresql-pglookout.conf(经 conf.d 包含进主配置,主备通用):
# pglookout 集群所需 PostgreSQL 设置(经 conf.d 包含进主配置)
listen_addresses = '*'
wal_level = replica
max_wal_senders = 10
max_replication_slots = 10
hot_standby = on
conf/failover.sh(failover_command 指向的脚本,被提升节点本地执行):
#!/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
4.6 三节点 pglookout 配置
pglookout 的配置是单个 JSON 文件(容器内路径 /etc/pglookout/pglookout.json,只读挂载),三类节点各一份。参数逐项详解见「七、配置参数说明」。
主节点 conf/pglookout-primary.json(own_db: "p1",autofollow 关闭):
{
"own_db": "p1",
"remote_conns": {
"p1": "host=10.89.60.11 dbname=postgres user=pglookout password=q1w2e3r4",
"s1": "host=10.89.60.12 dbname=postgres user=pglookout password=q1w2e3r4"
},
"observers": {
"obs1": "http://10.89.60.13:15000"
},
"http_address": "0.0.0.0",
"http_port": 15000,
"db_poll_interval": 5.0,
"replication_state_check_interval": 10.0,
"warning_replication_time_lag": 30.0,
"max_failover_replication_time_lag": 120.0,
"failover_command": "/opt/pglookout/failover.sh",
"failover_on_disconnect": true,
"known_gone_nodes": [],
"never_promote_these_nodes": [],
"maintenance_mode_file": "/tmp/pglookout_maintenance_mode_file",
"json_state_file_path": "/var/run/pglookout/pglookout_state.json",
"alert_file_dir": "/var/run/pglookout",
"log_level": "INFO",
"syslog": false,
"autofollow": false
}
备节点 conf/pglookout-standby.json(own_db: "s1",autofollow 开启,附 PG 启停与 conninfo 模板):
{
"own_db": "s1",
"remote_conns": {
"p1": "host=10.89.60.11 dbname=postgres user=pglookout password=q1w2e3r4",
"s1": "host=10.89.60.12 dbname=postgres user=pglookout password=q1w2e3r4"
},
"observers": {
"obs1": "http://10.89.60.13:15000"
},
"http_address": "0.0.0.0",
"http_port": 15000,
"db_poll_interval": 5.0,
"replication_state_check_interval": 10.0,
"warning_replication_time_lag": 30.0,
"max_failover_replication_time_lag": 120.0,
"failover_command": "/opt/pglookout/failover.sh",
"failover_on_disconnect": true,
"known_gone_nodes": [],
"never_promote_these_nodes": [],
"maintenance_mode_file": "/tmp/pglookout_maintenance_mode_file",
"json_state_file_path": "/var/run/pglookout/pglookout_state.json",
"alert_file_dir": "/var/run/pglookout",
"log_level": "INFO",
"syslog": false,
"autofollow": true,
"pg_data_directory": "/var/lib/postgresql/16/main",
"pg_start_command": "pg_ctlcluster 16 main start",
"pg_stop_command": "pg_ctlcluster 16 main stop -m fast",
"primary_conninfo_template": "user=replicator password=q1w2e3r4 application_name=pglookout-standby sslmode=disable"
}
观察节点 conf/pglookout-observer.json(own_db 为空串即纯观察者,无 failover 职责):
{
"own_db": "",
"remote_conns": {
"p1": "host=10.89.60.11 dbname=postgres user=pglookout password=q1w2e3r4",
"s1": "host=10.89.60.12 dbname=postgres user=pglookout password=q1w2e3r4"
},
"observers": {},
"http_address": "0.0.0.0",
"http_port": 15000,
"db_poll_interval": 5.0,
"json_state_file_path": "/var/run/pglookout/pglookout_state.json",
"alert_file_dir": "/var/run/pglookout",
"log_level": "INFO",
"syslog": false
}
💡 说明:
remote_conns的连接串用host=直写 IP 而非主机名,是因为 pglookout 的primary_conninfo_template只带账号与选项、host/port由 pglookout 在 autofollow 时按当前主节点注入;配置键统一用 IP 可避免 DNS 解析差异带来的误判。
4.7 构建镜像
cd pglookout-stack && docker compose build
构建输出节选(实测首尾):
#8 [4/7] RUN tar xzf /tmp/pglookout-main.tar.gz -C /tmp && python3 -m venv /opt/pglookout/venv && /opt/pglookout/venv/bin/pip install --no-cache-dir /tmp/pglookout-main packaging && rm -rf /tmp/pglookout-main /tmp/pglookout-main.tar.gz
#8 23.78 Successfully installed certifi-2026.10.23 charset_normalizer-3.5.2 idna-3.20 packaging-26.3 pglookout-2.0.3 psycopg2-2.9.13 requests-2.34.2 urllib3-2.8.0
...
#12 naming to docker.io/library/pglookout-node:2.0.3 done
#12 DONE 0.1s
Successfully installed 行确认 pglookout-2.0.3 与手工追加的 packaging-26.3 均已装入 venv。验证版本:
docker run --rm pglookout-node:2.0.3 pglookout --version
2.0.3
4.8 启动
docker compose up -d --build
实测输出:
Image pglookout-node:2.0.3 Built
Network pglookout-203-stack_pglookout-net Creating
Network pglookout-203-stack_pglookout-net Created
Container pglookout-2.0.3-primary Creating
Container pglookout-2.0.3-primary Created
Container pglookout-2.0.3-standby Creating
Container pglookout-2.0.3-observer Creating
Container pglookout-2.0.3-standby Created
Container pglookout-2.0.3-observer Created
Container pglookout-2.0.3-primary Starting
Container pglookout-2.0.3-primary Started
Container pglookout-2.0.3-observer Starting
Container pglookout-2.0.3-standby Starting
Container pglookout-2.0.3-observer Started
Container pglookout-2.0.3-standby Started
首次启动时 standby 会执行 initdb + pg_basebackup 拉取主库基础备份(约 23MB,秒级完成),实测备节点初始化日志节选:
Creating new PostgreSQL cluster 16/main ...
/usr/lib/postgresql/16/bin/initdb -D /var/lib/postgresql/16/main --auth-local peer --auth-host scram-sha-256 --no-instructions
...
Ver Cluster Port Status Owner Data directory Log file
16 main 5432 down postgres /var/lib/postgresql/16/main /var/log/postgresql/postgresql-16-main.log
waiting for checkpoint
23158/23158 kB (100%), 0/1 tablespace
23158/23158 kB (100%), 1/1 tablespace
4.9 验证:容器与 PostgreSQL 集群状态
docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
pglookout-2.0.3-observer pglookout-node:2.0.3 "/usr/local/bin/entr…" pg-observer 27 seconds ago Up 27 seconds 0.0.0.0:8140->15000/tcp, [::]:8140->15000/tcp
pglookout-2.0.3-primary pglookout-node:2.0.3 "/usr/local/bin/entr…" pg-primary 28 seconds ago Up 27 seconds
pglookout-2.0.3-standby pglookout-node:2.0.3 "/usr/local/bin/entr…" pg-standby 27 seconds ago Up 27 seconds
docker exec pglookout-2.0.3-primary pg_lsclusters
Ver Cluster Port Status Owner Data directory Log file
16 main 5432 online postgres /var/lib/postgresql/16/main /var/log/postgresql/postgresql-16-main.log
主节点查看流复制(应看到 standby 以 pglookout-standby 应用名 streaming):
docker exec pglookout-2.0.3-primary su postgres -c "psql -tAc \"SELECT application_name, client_addr, state FROM pg_stat_replication\""
pglookout-standby|10.89.60.12|streaming
备节点确认处于恢复态且收发位点追平:
docker exec pglookout-2.0.3-standby su postgres -c "psql -tAc \"SELECT pg_is_in_recovery(), pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn()\""
t|0/50043C8|0/50043C8
💡 说明:主节点 entrypoint 重复执行时日志会出现
ERROR: role "pglookout" already exists与NOTICE: ... has already been granted membership——这是幂等重建的正常提示(首次初始化时为CREATE ROLE/GRANT成功回显),|| true保证不中断启动。
4.10 验证:pglookout 集群视图与状态 API
主节点日志确认 pglookout 完成首轮检查并认定主库:
docker compose logs pg-primary | head -25
节选(实测首尾):
pglookout-2.0.3-primary | 2026-11-01 08:58:06,143 pglookout MainThread INFO PGLookout initialized, local hostname: 'pg-primary', own_db: 'p1', cwd: '/'
pglookout-2.0.3-primary | 2026-11-01 08:58:06,143 ClusterMonitor Thread-1 INFO Connecting to 'p1' (host=10.89.60.11 dbname=postgres user=pglookout; hidden password)
pglookout-2.0.3-primary | 2026-11-01 08:58:06,150 ClusterMonitor Thread-1 INFO Connecting to 's1' (host=10.89.60.12 dbname=postgres user=pglookout; hidden password)
pglookout-2.0.3-primary | 2026-11-01 08:58:06,164 pglookout MainThread INFO Immediate failover check completed
pglookout-2.0.3-primary | 2026-11-01 08:58:06,164 pglookout MainThread INFO New master node detected: old: None new: 'p1': {...'pg_is_in_recovery': False, ...}
观察节点 HTTP 状态 API(宿主 8140):
curl -s http://127.0.0.1:8140/state.json
实测回显(格式化节选):
{
"s1": {
"fetch_time": "2026-11-01T09:07:29.254246Z",
"connection": true,
"pg_is_in_recovery": true,
"pg_last_xlog_receive_location": "0/5004668",
"pg_last_xlog_replay_location": "0/5004668",
"replication_time_lag": 1.786
},
"p1": {
"fetch_time": "2026-11-01T09:07:29.254014Z",
"connection": true,
"pg_is_in_recovery": false,
"pg_last_xlog_replay_location": "0/5004668",
"replication_slots": [],
"replication_time_lag": null
}
}
健康检查端点与辅助命令:
curl -s -o /dev/null -w '%{http_code}\n' -X POST http://127.0.0.1:8140/check
docker exec pglookout-2.0.3-primary pglookout_current_master /etc/pglookout/pglookout.json; echo "exit=$?"
204
p1
exit=0
备节点确认 autofollow 已接管 primary_conninfo(pglookout 自动重写 postgresql.auto.conf):
docker exec pglookout-2.0.3-standby cat /var/lib/postgresql/16/main/postgresql.auto.conf
# pglookout updated primary_conninfo for instance p1 at 2026-11-01T09:06:40.264477Z
# Do not edit this file manually!
# It will be overwritten by the ALTER SYSTEM command.
primary_conninfo = 'user=replicator password=q1w2e3r4 application_name=pglookout-standby sslmode=disable host=10.89.60.11'
recovery_target_timeline = 'latest'
告警目录应只有状态文件、无告警标记(有告警时会新增 authentication_error 等空文件):
docker exec pglookout-2.0.3-primary ls -la /var/run/pglookout/
total 16
drwxr-xr-x 2 root root 4096 Nov 1 09:07 .
drwxr-xr-x 1 root root 4096 Nov 1 09:06 ..
-rw-r--r-- 1 root root 2228 Nov 1 09:07 pglookout_state.json
五、数据持久化说明
| 挂载 | 容器内路径 | 内容 |
|---|---|---|
./data/primary | /var/lib/postgresql | 主节点 PGDATA(16/main)、pglookout 无关 |
./data/standby | /var/lib/postgresql | 备节点 PGDATA(16/main) |
- 持久化根放在
/var/lib/postgresql整目录(而非 PGDATA 本身),Ubuntu PG 集群的pg_hba.conf、conf.d等配置在/etc/postgresql/16/main,不在持久化范围内;容器重建后 entrypoint 会重新拷贝 conf.d 增量并追加 pg_hba 规则(grep -q判重,幂等)。 - 观察节点无数据目录,纯无状态,可随时重建。
- 彻底重置实验:
docker compose down后删除data/两目录,下次up将重新 initdb + basebackup。
六、环境变量表
本方案镜像自身只消费一个环境变量(PostgreSQL 与 pglookout 的参数都走配置文件,不走环境变量):
| 变量 | 可选值 | 默认 | 作用 |
|---|---|---|---|
PGLOOKOUT_ROLE | primary / standby / observer | observer | 决定 entrypoint 初始化路径与是否建集群/角色/备库 |
七、配置参数说明
7.1 pglookout JSON 核心键
三节点共用的 /etc/pglookout/pglookout.json 关键参数(默认值取自官方 README):
| 参数 | 本文值 | 默认 | 说明 |
|---|---|---|---|
own_db | p1 / s1 / "" | — | 本节点在 remote_conns 中的实例名;空串即纯观察节点 |
remote_conns | p1/s1 连接串 | {} | 集群全部 DB 节点(含自己)的 libpq 连接串 |
observers | obs1 URL | {} | 观察节点 HTTP 地址,db 节点互相通过它交叉验证视点 |
http_address / http_port | 0.0.0.0 / 15000 | localhost / 15000 | 状态 API 监听;实验环境需对外暴露故绑 0.0.0.0 |
db_poll_interval | 5.0 | 5.0 | 轮询各 DB 节点状态的间隔(秒) |
replication_state_check_interval | 10.0 | 10.0 | 故障转移决策周期(秒) |
warning_replication_time_lag | 30.0 | 30.0 | 复制延迟超此值写 replication_delay_warning 告警文件 |
max_failover_replication_time_lag | 120.0 | 120.0 | 延迟超此值才允许自动故障转移 |
failover_command | /opt/pglookout/failover.sh | "" | 判定主库失联后在「将成新主」的节点上执行的提主脚本 |
failover_on_disconnect | true | true | 主库断连是否可触发故障转移(关闭则仅延迟超时触发) |
autofollow | 备 true / 主 false | false | 备节点是否由 pglookout 自动维护 primary_conninfo 并跟随新主 |
primary_conninfo_template | 备节点模板串 | — | autofollow 用,host/port 由 pglookout 注入 |
pg_data_directory / pg_start_command / pg_stop_command | 见 4.6 | — | autofollow 重写恢复配置后重启本地 PG 所需 |
json_state_file_path | /var/run/pglookout/pglookout_state.json | /tmp/pglookout_state.json | 状态文件,供 pglookout_current_master 读取 |
alert_file_dir | /var/run/pglookout | /tmp | 告警文件目录(外部监控盯这里) |
maintenance_mode_file | /tmp/pglookout_maintenance_mode_file | /tmp/pglookout_maintenance_mode_file | 文件存在时暂停故障转移决策,计划内维护闸门 |
known_gone_nodes / never_promote_these_nodes | [] | [] | 已知下线节点白名单 / 永不提升名单 |
全量参数逐项详解、取值边界与生产模板见 《pglookout 2.0.3 配置参数详解》。
7.2 PostgreSQL conf.d 增量参数
conf/postgresql-pglookout.conf 拷贝到 /etc/postgresql/16/main/conf.d/pglookout.conf:
| 参数 | 值 | 说明 |
|---|---|---|
listen_addresses | '*' | 允许跨主机连接(默认仅 localhost) |
wal_level | replica | 打开 WAL 日志传送(默认 replica,显式声明) |
max_wal_senders | 10 | 复制发送进程上限(默认 10,显式声明) |
max_replication_slots | 10 | 复制槽上限 |
hot_standby | on | 备节点可查询 |
八、配置文件挂载
| 宿主文件 | 容器路径 | 模式 | 适用服务 |
|---|---|---|---|
conf/pglookout-primary.json | /etc/pglookout/pglookout.json | ro | pg-primary |
conf/pglookout-standby.json | /etc/pglookout/pglookout.json | ro | pg-standby |
conf/pglookout-observer.json | /etc/pglookout/pglookout.json | ro | pg-observer |
conf/failover.sh | /opt/pglookout/failover.sh | ro | primary/standby |
conf/postgresql-pglookout.conf | /opt/pglookout/postgresql-pglookout.conf | ro | primary/standby |
⚠️ 注意:
pglookout.json以ro挂载,pglookout 收到 SIGHUP 会重读该文件(配置热加载,无需重启进程);但ro挂载下进程无法回写配置,pglookout 本身不回写,无影响。修改配置后执行docker compose kill -s HUP pg-primary pg-standby即可生效。
九、等价 docker run
不用 Compose、手工用 docker run 拉起同一环境的等价命令(已实测,容器名、网络、挂载与 Compose 版一致):
cd pglookout-stack
docker network create --driver bridge --subnet 10.89.60.0/24 pglookout-net
docker run -d --name pglookout-2.0.3-primary --hostname pg-primary \
--network pglookout-net --ip 10.89.60.11 \
-e PGLOOKOUT_ROLE=primary \
-v "$PWD/data/primary:/var/lib/postgresql" \
-v "$PWD/conf/pglookout-primary.json:/etc/pglookout/pglookout.json:ro" \
-v "$PWD/conf/failover.sh:/opt/pglookout/failover.sh:ro" \
-v "$PWD/conf/postgresql-pglookout.conf:/opt/pglookout/postgresql-pglookout.conf:ro" \
pglookout-node:2.0.3
docker run -d --name pglookout-2.0.3-observer --hostname pg-observer \
--network pglookout-net --ip 10.89.60.13 \
-e PGLOOKOUT_ROLE=observer \
-p 8140:15000 \
-v "$PWD/conf/pglookout-observer.json:/etc/pglookout/pglookout.json:ro" \
pglookout-node:2.0.3
docker run -d --name pglookout-2.0.3-standby --hostname pg-standby \
--network pglookout-net --ip 10.89.60.12 \
-e PGLOOKOUT_ROLE=standby \
-v "$PWD/data/standby:/var/lib/postgresql" \
-v "$PWD/conf/pglookout-standby.json:/etc/pglookout/pglookout.json:ro" \
-v "$PWD/conf/failover.sh:/opt/pglookout/failover.sh:ro" \
-v "$PWD/conf/postgresql-pglookout.conf:/opt/pglookout/postgresql-pglookout.conf:ro" \
pglookout-node:2.0.3
三条 docker run 分别返回容器 ID(实测节选):
2d93a4365abbd35a4f4c488187e17134267fc038a3f317a9a9d5cbad49de17bd
47aad9af847907ef376b09a370314c416d236848a616dc59fd7b1a48fa2b02ef
a5e60999c8323e52dd9c254a692f445180fd32869df144152603431f6f9df265
约 30 秒后验证与 Compose 版一致:
docker exec pglookout-2.0.3-primary su postgres -c "psql -tAc \"SELECT application_name, state FROM pg_stat_replication\""
curl -s http://127.0.0.1:8140/state.json | python3 -c 'import json,sys; d=json.load(sys.stdin); print("p1 in recovery:", d["p1"]["pg_is_in_recovery"], "| s1 in recovery:", d["s1"]["pg_is_in_recovery"])'
pglookout-standby|streaming
p1 in recovery: False | s1 in recovery: True
💡 说明:docker run 方式没有
depends_on,standby 的 entrypoint 内置「等主库 replicator 角色就绪」循环,因此三条命令按 primary → observer → standby 顺序执行即可(entrypoint 竞态兜底见 11.4)。
十、常用运维命令
# 三节点状态总览
docker compose ps
# 跟踪某节点 pglookout 日志(INFO 级决策日志)
docker compose logs -f pg-standby
# 主库 psql(容器内以 postgres 系统用户执行)
docker exec -it pglookout-2.0.3-primary su postgres -c psql
# 查看当前认定的主节点实例名(读状态文件,状态文件超 60 秒未更新则退出码 -1)
docker exec pglookout-2.0.3-primary pglookout_current_master /etc/pglookout/pglookout.json
# 观察节点状态 API(宿主 8140)
curl -s http://127.0.0.1:8140/state.json
# 配置热加载(SIGHUP)
docker compose kill -s HUP pg-primary pg-standby
# 只重启单个节点
docker compose restart pg-observer
# 停止整个实验环境(数据保留)
docker compose down
十一、常见问题(排障实录)
11.1 PyPI 1.4.0 构建期即失败:No module named 'imp'
现象:pip install pglookout==1.4.0 在 Python 3.12 环境构建 sdist 时报:
ModuleNotFoundError: No module named 'imp'
定位:1.4.0 发行包的 version.py 使用 import imp 读取版本,imp 模块在 Python 3.12 已被移除。
解决:换 GitHub main 分支源码(2.0.3,已改用 importlib)。不要试图给 3.12 补 imp 垫片。
11.2 降级 Python 也救不了 1.4.0:async 关键字 SyntaxError
现象:改用 Python 3.11/3.12 安装 1.4.0 后运行报(实测 3.12 复现回显):
File "<test>", line 1
conn = psycopg2.connect(dsn=dsn, async=True)
^^^^^
SyntaxError: invalid syntax
定位:async 自 Python 3.7 起是保留关键字,1.4.0 源码从未适配;PyPI 无 3.7+ 可用的更新发行包。
解决:同 11.1,锚定 main 分支 2.0.3(源码已改为 async_=True 形参)。
11.3 2.0.3 启动报 No module named 'packaging'
现象:main 分支源码 pip 安装成功,启动即崩:
ModuleNotFoundError: No module named 'packaging'
定位:pglookout.py 顶部 from packaging.version import parse,但 setup.py 的 install_requires 只声明了 psycopg2 与 requests,漏了 packaging。
解决:安装命令显式追加依赖(Dockerfile 已内置):
pip install --no-cache-dir /tmp/pglookout-main packaging
11.4 standby 启动即退:password authentication failed for user "replicator"
现象:首次 up 后备节点日志:
pg_basebackup: error: FATAL: password authentication failed for user "replicator"
定位:主备 entrypoint 竞态——备库发起 basebackup 时,主库还没执行完 CREATE ROLE replicator。
解决:standby 分支在 basebackup 前加等待循环,以 replicator 身份 psql SELECT 1 成功才继续(见 4.3)。
11.5 主节点状态轮询持续 InsufficientPrivilege
现象:primary 日志每轮出现:
InsufficientPrivilege (permission denied for function pg_read_binary_file)
且 state.json 里 p1 节点 connection: false。
定位:pglookout 采集逻辑复制槽状态时执行 pg_read_binary_file('pg_replslot/.../state'),即使当前 0 个逻辑槽,查询计划仍做函数 ACL 检查;pg_monitor 角色不含该授权。
解决:对 4 个函数重载签名逐一 GRANT EXECUTE(见 4.3),修复后 grep -c InsufficientPrivilege 为 0。
11.6 curl 未知路径返回 000(挂起)
现象:curl http://127.0.0.1:8140/anything 不返回而是超时,-w '%{http_code}' 得 000。
定位:pglookout 内置 WebServer 对非 /state.json、/check 路径调用 send_response(404) 后未调用 end_headers(),响应不完整,curl 等待连接关闭。
解决:属上游实现 quirk,非部署故障;监控探活只使用 GET /state.json 与 POST /check 两个端点。
十二、参考链接
- 《pglookout 介绍与选型指南》(本专栏 A1 篇:定位、机制、同类对比)
- 《pglookout 2.0.3 配置参数详解》(本专栏 C1 篇:配置键逐项详解)
- 《pglookout 2.0.3 常用 CLI 命令与工具详解》(本专栏 D1 篇:运行期管理与故障转移演练实测)
- pglookout GitHub 仓库:https://github.com/aiven/pglookout
- pglookout README(配置键与默认值权威来源):https://github.com/aiven/pglookout/blob/main/README.rst
- PyPI pglookout 页面(1.4.0,注意其不可用性见 11.1/11.2):https://pypi.org/project/pglookout/
- PostgreSQL 16 流复制文档:https://www.postgresql.org/docs/16/warm-standby.html