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)。

pglookoutpglookoutPostgreSQL监控2026/11/01

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)
Docker29.9.2(build a82e528)
Docker Composev5.6.1
pglookout2.0.3(GitHub main 分支源码,Makefile short_ver = 2.0.3)
PostgreSQL16.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 sdist1.4.0仅 ≤3.6(async 关键字、imp 模块)现代系统不可用,弃用
GitHub main2.0.3CPython 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(Makefile short_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

三处关键设计:

  1. primary 的 4 条 GRANT EXECUTE ON FUNCTION pg_read_binary_file(...):pglookout 读取逻辑复制槽状态时调用 pg_read_binary_file,即使查询命中 0 行也会做函数 ACL 检查,不授权会导致主节点状态轮询整体 InsufficientPrivilege 失败(见 11.5)。
  2. standby 的 until ... psql -U replicator 等待循环:备库初始化必须等主库 entrypoint 把 replicator 角色建好,否则 pg_basebackup 报 password authentication failed(见 11.4)。
  3. 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_ROLEprimary / standby / observerobserver决定 entrypoint 初始化路径与是否建集群/角色/备库

七、配置参数说明

7.1 pglookout JSON 核心键

三节点共用的 /etc/pglookout/pglookout.json 关键参数(默认值取自官方 README):

参数本文值默认说明
own_dbp1 / s1 / ""—本节点在 remote_conns 中的实例名;空串即纯观察节点
remote_connsp1/s1 连接串{}集群全部 DB 节点(含自己)的 libpq 连接串
observersobs1 URL{}观察节点 HTTP 地址,db 节点互相通过它交叉验证视点
http_address / http_port0.0.0.0 / 15000localhost / 15000状态 API 监听;实验环境需对外暴露故绑 0.0.0.0
db_poll_interval5.05.0轮询各 DB 节点状态的间隔(秒)
replication_state_check_interval10.010.0故障转移决策周期(秒)
warning_replication_time_lag30.030.0复制延迟超此值写 replication_delay_warning 告警文件
max_failover_replication_time_lag120.0120.0延迟超此值才允许自动故障转移
failover_command/opt/pglookout/failover.sh""判定主库失联后在「将成新主」的节点上执行的提主脚本
failover_on_disconnecttruetrue主库断连是否可触发故障转移(关闭则仅延迟超时触发)
autofollow备 true / 主 falsefalse备节点是否由 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_levelreplica打开 WAL 日志传送(默认 replica,显式声明)
max_wal_senders10复制发送进程上限(默认 10,显式声明)
max_replication_slots10复制槽上限
hot_standbyon备节点可查询

八、配置文件挂载

宿主文件容器路径模式适用服务
conf/pglookout-primary.json/etc/pglookout/pglookout.jsonropg-primary
conf/pglookout-standby.json/etc/pglookout/pglookout.jsonropg-standby
conf/pglookout-observer.json/etc/pglookout/pglookout.jsonropg-observer
conf/failover.sh/opt/pglookout/failover.shroprimary/standby
conf/postgresql-pglookout.conf/opt/pglookout/postgresql-pglookout.confroprimary/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 2.0.3 Docker Compose 部署指南 - Quzuo