基于Docker Compose的阿里云Linux 3环境PostgreSQL高可用集群部署实践
摘要
针对阿里云Linux 3.2104 LTS操作系统环境下PostgreSQL高可用部署需求,本文提出一套基于Docker Compose+Patroni+etcd架构的轻量化部署方案。该方案以容器化方式构建1主1从PostgreSQL伪集群,完整实现主从复制、自动/手动故障切换核心能力,具备环境隔离、一键运维、无残留清理等特性。方案既适用于测试环境快速验证PostgreSQL高可用能力,也可为生产级部署提供标准化参考范式。
关键词
PostgreSQL;高可用(HA);Patroni;etcd;Docker Compose;阿里云Linux 3
一、方案背景与核心优势
1.1 部署背景
PostgreSQL作为开源关系型数据库标杆产品,高可用部署是生产环境的核心诉求。传统原生部署方式存在环境依赖复杂、配置繁琐、清理困难等痛点,而容器化部署可有效规避此类问题,尤其适配测试场景的快速验证需求。阿里云Linux 3作为企业级操作系统,在云环境中广泛应用,亟需适配性强的PostgreSQL高可用部署方案。
1.2 核心优势
- 容器化隔离:端口、数据、配置与宿主机完全隔离,避免环境冲突;
- 一键化运维:通过Docker Compose实现集群启停、重启、销毁全生命周期管理;
- 无残留清理:测试完成后可一键删除容器、数据卷,快速还原宿主机状态;
- 网络自解析:Docker桥接网络支持容器名直接互访,无需手动配置hosts;
- 数据持久化:宿主机挂载数据卷,容器销毁后核心数据不丢失;
- 功能完整性:保留Patroni核心能力,与原生部署的高可用逻辑一致。
二、部署规划与基础要求
2.1 基础环境要求
- 操作系统:阿里云Linux 3.2104 LTS 64位;
- 硬件资源:内存≥2G(推荐4G),磁盘可用空间≥10G;
- 网络:外网通畅,可访问Docker镜像仓库及Python包源;
- 权限:全程使用root用户操作(测试环境便捷性优先)。
三、基础环境部署(Docker + Docker Compose)
3.1 卸载旧版本Docker(如有)
arduino
yum remove -y docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine
3.2 配置阿里云Docker YUM源
bash
# 安装基础依赖
yum install -y yum-utils device-mapper-persistent-data lvm2
# 删除旧版Docker源(避免冲突)
rm -rf /etc/yum.repos.d/docker-ce.repo
# 添加阿里云Docker源
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
# 适配阿里云Linux 3(兼容CentOS 8源)
sed -i 's/$releasever/8/g' /etc/yum.repos.d/docker-ce.repo
3.3 安装Docker与Docker Compose
python
# 清理YUM缓存并生效新源
yum clean all && yum makecache
# 安装Docker核心组件
yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# 验证安装
docker --version
docker compose version
3.4 配置阿里云镜像加速(关键)
bash
# 创建Docker守护进程配置文件
mkdir -p /etc/docker
cat > /etc/docker/daemon.json << EOF
{
"registry-mirrors": [
"https://docker.mirrors.aliyun.com",
"https://hub-mirror.c.163.com"
]
}
EOF
# 重启Docker并设置开机自启
systemctl daemon-reload
systemctl start docker && systemctl enable docker
# 验证加速配置
docker info | grep -i mirror
专属加速地址获取:登录阿里云控制台→容器镜像服务→镜像工具→镜像加速器。
3.5 验证Docker运行
arduino
docker run --rm hello-world
输出Hello from Docker!即表示Docker部署成功。
四、集群配置文件编写
4.1 创建项目目录结构
bash
# 创建主目录及子目录
mkdir -p /pg-ha-docker/{conf/pg01,conf/pg02,data/etcd,data/pg01,data/pg02}
# 进入项目目录(后续操作均在此目录执行)
cd /pg-ha-docker
# 宽松授权(避免容器内权限不足)
chmod -R 777 /pg-ha-docker
4.2 编写Patroni配置文件
4.2.1 主库配置(conf/pg01/patroni.yml)
bash
version: '3.8'
# 公共配置锚点:Patroni节点通用配置(使用已构建镜像)
x-patroni-common: &patroni-common
# 改动1:替换为已构建的自定义镜像(请替换为你的镜像地址/名称)
image: crpi-aew0ru2n2oswkxy7.cn-shenzhen.personal.cr.aliyuncs.com/shen_wang/shen_wang:15-bullseye-custom # 示例:harbor.example.com/pg-ha/patroni:v1.0
restart: always
user: root
environment:
- TZ=Asia/Shanghai
- PGTZ=Asia/Shanghai
- PIP_DEFAULT_TIMEOUT=300
- DEBIAN_FRONTEND=noninteractive
- TERM=xterm-256color
- ETCDCTL_API=2
- APT::Acquire::Queue-Mode=host
- APT::Acquire::Parallel=10
depends_on:
pg-etcd:
condition: service_healthy
networks:
- pg-ha-network
mem_limit: 2G
cpus: 1.0
healthcheck:
test: ["CMD", "bash", "-c", "netstat -tulpn 2>/dev/null | grep -q ':5432' && curl -f http://localhost:8008/patroni 2>/dev/null || exit 1"]
interval: 10s
timeout: 5s
retries: 10
start_period: 240s
# 改动2:简化command(仅保留容器启动必需逻辑,依赖/源配置已在镜像构建时完成)
x-patroni-command: &patroni-command
- /bin/bash
- -c
- |
set -euo pipefail
# 新增:创建缺失的pgdata子目录
echo '===== 创建PG数据目录 ====='
mkdir -p /var/lib/postgresql/data/pgdata
# 仅保留:数据目录权限修复(容器启动时动态调整)
echo '===== 修复数据目录权限 ====='
chown -R postgres:postgres /var/lib/postgresql/data
chmod 700 /var/lib/postgresql/data
chmod 700 /var/lib/postgresql/data/pgdata # 显式设置pgdata权限
# 仅保留:启动Patroni(镜像已预装patroni及依赖)
echo '===== 启动Patroni ====='
exec gosu postgres /usr/local/bin/patroni /etc/patroni/patroni.yml
sleep infinity
services:
# etcd服务(无改动,保持原有配置)
pg-etcd:
image: quay.io/coreos/etcd:v3.4.27
container_name: pg-etcd
restart: always
ports:
- "2379:2379"
- "2380:2380"
volumes:
- ./data/etcd:/data/etcd
networks:
- pg-ha-network
command: >
etcd
--name etcd-node
--data-dir /data/etcd
--listen-client-urls http://0.0.0.0:2379
--advertise-client-urls http://pg-etcd:2379
--listen-peer-urls http://0.0.0.0:2380
--initial-advertise-peer-urls http://pg-etcd:2380
--initial-cluster etcd-node=http://pg-etcd:2380
--initial-cluster-token etcd-cluster
--initial-cluster-state new
--enable-v2=true
healthcheck:
test: ["CMD", "etcdctl", "--endpoints=http://localhost:2379", "endpoint", "health"]
interval: 5s
timeout: 3s
retries: 5
start_period: 15s
# Patroni-PG主节点(使用已构建镜像+简化命令)
patroni-pg01:
<<: *patroni-common
container_name: patroni-pg01
ports:
- "5432:5432"
- "8008:8008"
volumes:
- ./conf/pg01:/etc/patroni
- ./data/pg01:/var/lib/postgresql/data
- ./logs/pg01:/var/log/postgresql
command: *patroni-command
# Patroni-PG从节点(使用已构建镜像+简化命令)
patroni-pg02:
<<: *patroni-common
container_name: patroni-pg02
ports:
- "5433:5432"
- "8009:8008"
volumes:
- ./conf/pg02:/etc/patroni
- ./data/pg02:/var/lib/postgresql/data
- ./logs/pg02:/var/log/postgresql
command: *patroni-command
networks:
pg-ha-network:
driver: bridge
name: pg-ha-network
4.2.2 从库配置(conf/pg02/patroni.yml)
仅需修改name和advertise字段为patroni-pg02,其余与主库一致:
yaml
log:
level: DEBUG
format: '%(asctime)s %(levelname)s: %(message)s'
scope: pg-ha-docker
namespace: /patroni/
name: patroni-pg02 # 确认节点名称唯一
restapi:
listen: 0.0.0.0:8009
advertise: patroni-pg02:8009
connect_address: patroni-pg02:8009
etcd:
hosts: ["pg-etcd:2379"]
postgresql:
listen: 0.0.0.0:5432
advertise: patroni-pg02:5432 # 保留advertise即可,无需重复配置connect_address
connect_address: patroni-pg02:5432
data_dir: /var/lib/postgresql/data/pgdata
config_dir: /var/lib/postgresql/data/pgdata
bin_dir: /usr/lib/postgresql/15/bin
pgpass: /tmp/pgpass
authentication:
superuser:
username: postgres
password: Pg@123456
replication:
username: repl # 统一复制用户,和主节点保持一致
password: Repl@123456
rewind: # 新增:rewind用户认证(复用repl用户即可)
username: repl
password: Repl@123456
parameters:
wal_level: replica
max_wal_senders: 5
wal_keep_size: 512MB
hot_standby: on
max_connections: 500
shared_buffers: 128MB
listen_addresses: '*'
port: 5432
priority: 50 # 从节点优先级低于主节点(pg01设为100)
bootstrap:
# 从节点通过pg_basebackup同步主节点数据,仅保留这个method即可
method: pg_basebackup
pg_basebackup:
host: patroni-pg01 # 主节点地址
port: 5432
username: repl # 匹配authentication.replication的用户
password: Repl@123456 # 匹配authentication.replication的密码
options: '-c fast -X stream' # 新增:优化备份参数,确保同步成功
restore_command: cp /var/lib/postgresql/data/pg_wal/%f %p
pg_hba:
- host all all 0.0.0.0/0 md5
- host replication repl 0.0.0.0/0 md5
dcs:
ttl: 30
loop_wait: 10
retry_timeout: 10
maximum_lag_on_failover: 1048576
postgresql: # use_pg_rewind/use_slots归到dcs.postgresql下(正确层级)
use_pg_rewind: true
use_slots: true
parameters:
wal_level: replica
max_wal_senders: 10
wal_keep_size: 1GB
hot_standby: on
postgresql:
force: true # 移除冗余的method: postgres
failover:
automatic: true
allowed_failover_times:
- 00:00-24:00
failover_timeout: 60
4.3 构建镜像
ini
FROM postgres:15-bullseye
LABEL maintainer="custom-patroni <your-email@xxx.com>"
# 环境变量
ENV TZ=Asia/Shanghai \
PGTZ=Asia/Shanghai \
DEBIAN_FRONTEND=noninteractive \
ETCDCTL_API=2
# 核心优化:保留官方pgdg源 + 添加清华源(解决依赖冲突,printf写入)
RUN set -eux; \
# 不删除pgdg源(关键:保留PostgreSQL官方依赖),仅清理旧缓存
rm -rf /var/lib/apt/lists/*; \
# 添加清华源到sources.list(补充源,不覆盖官方pgdg源)
printf "deb http://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye main contrib non-free\ndeb http://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye-updates main contrib non-free\ndeb http://mirrors.tuna.tsinghua.edu.cn/debian-security/ bullseye-security main contrib non-free\n" >> /etc/apt/sources.list; \
# 更新源(同时读取官方pgdg源+清华源)
apt-get update; \
# 安装依赖(官方源提供匹配的libpq5,清华源加速其他包)
apt-get install -y --no-install-recommends --fix-missing --fix-broken \
ca-certificates apt-transport-https curl gosu net-tools \
python3 python3-pip python3-dev gcc libpq-dev; \
# 清理缓存
apt-get clean; \
rm -rf /var/lib/apt/lists/*; \
update-ca-certificates
# 二进制安装 etcd 3.5.9(修复:使用GitHub官方原始地址,彻底解决404)
RUN set -eux; \
ETCD_VERSION="3.5.9"; \
ARCH="amd64"; \
curl -fsSL https://github.com/etcd-io/etcd/releases/download/v${ETCD_VERSION}/etcd-v${ETCD_VERSION}-linux-${ARCH}.tar.gz -o etcd.tar.gz; \
tar -zxvf etcd.tar.gz; \
mv etcd-v${ETCD_VERSION}-linux-${ARCH}/etcd /usr/local/bin/; \
4.4 编写Docker Compose编排文件
在/pg-ha-docker目录下创建docker-compose.yml:
bash
version: '3.8'
# 公共配置锚点:Patroni节点通用配置(使用已构建镜像)
x-patroni-common: &patroni-common
# 改动1:替换为已构建的自定义镜像(请替换为你的镜像地址/名称)
image: patroni-pg01:15-bullseye-custom # 示例:harbor.example.com/pg-ha/patroni:v1.0
restart: always
user: root
environment:
- TZ=Asia/Shanghai
- PGTZ=Asia/Shanghai
- PIP_DEFAULT_TIMEOUT=300
- DEBIAN_FRONTEND=noninteractive
- TERM=xterm-256color
- ETCDCTL_API=2
- APT::Acquire::Queue-Mode=host
- APT::Acquire::Parallel=10
depends_on:
pg-etcd:
condition: service_healthy
networks:
- pg-ha-network
mem_limit: 2G
cpus: 1.0
healthcheck:
test: ["CMD", "bash", "-c", "netstat -tulpn 2>/dev/null | grep -q ':5432' && curl -f http://localhost:8008/patroni 2>/dev/null || exit 1"]
interval: 10s
timeout: 5s
retries: 10
start_period: 240s
# 改动2:简化command(仅保留容器启动必需逻辑,依赖/源配置已在镜像构建时完成)
x-patroni-command: &patroni-command
- /bin/bash
- -c
- |
set -euo pipefail
# 新增:创建缺失的pgdata子目录
echo '===== 创建PG数据目录 ====='
mkdir -p /var/lib/postgresql/data/pgdata
# 仅保留:数据目录权限修复(容器启动时动态调整)
echo '===== 修复数据目录权限 ====='
chown -R postgres:postgres /var/lib/postgresql/data
chmod 700 /var/lib/postgresql/data
chmod 700 /var/lib/postgresql/data/pgdata # 显式设置pgdata权限
# 仅保留:启动Patroni(镜像已预装patroni及依赖)
echo '===== 启动Patroni ====='
exec gosu postgres /usr/local/bin/patroni /etc/patroni/patroni.yml
sleep infinity
services:
# etcd服务(无改动,保持原有配置)
pg-etcd:
image: quay.io/coreos/etcd:v3.4.27
container_name: pg-etcd
restart: always
ports:
- "2379:2379"
- "2380:2380"
volumes:
- ./data/etcd:/data/etcd
networks:
- pg-ha-network
command: >
etcd
--name etcd-node
--data-dir /data/etcd
--listen-client-urls http://0.0.0.0:2379
--advertise-client-urls http://pg-etcd:2379
--listen-peer-urls http://0.0.0.0:2380
--initial-advertise-peer-urls http://pg-etcd:2380
--initial-cluster etcd-node=http://pg-etcd:2380
--initial-cluster-token etcd-cluster
--initial-cluster-state new
--enable-v2=true
healthcheck:
test: ["CMD", "etcdctl", "--endpoints=http://localhost:2379", "endpoint", "health"]
interval: 5s
timeout: 3s
retries: 5
start_period: 15s
# Patroni-PG主节点(使用已构建镜像+简化命令)
patroni-pg01:
<<: *patroni-common
container_name: patroni-pg01
ports:
- "5432:5432"
- "8008:8008"
volumes:
- ./conf/pg01:/etc/patroni
- ./data/pg01:/var/lib/postgresql/data
- ./logs/pg01:/var/log/postgresql
command: *patroni-command
# Patroni-PG从节点(使用已构建镜像+简化命令)
patroni-pg02:
<<: *patroni-common
container_name: patroni-pg02
ports:
- "5433:5432"
- "8009:8008"
volumes:
- ./conf/pg02:/etc/patroni
- ./data/pg02:/var/lib/postgresql/data
- ./logs/pg02:/var/log/postgresql
command: *patroni-command
networks:
pg-ha-network:
driver: bridge
name: pg-ha-network
五、集群启动与基础验证
5.1 启动集群
bash
cd /pg-ha-docker
docker compose up -d
首次启动需拉取镜像并安装依赖,耗时1-3分钟(取决于网络带宽)。
5.2 验证容器状态
bash
# 查看容器运行状态
docker compose ps
# 或通用Docker命令
docker ps -a
成功标志:3个容器(pg-etcd、patroni-pg01、patroni-pg02)STATUS均为Up。
5.3 查看容器日志(排查故障)
bash
# 实时查看patroni-pg01日志
docker logs -f patroni-pg01
# 查看patroni-pg02日志
docker logs -f patroni-pg02
# 查看etcd日志
docker logs -f pg-etcd
无connection refused、permission denied、no such host等报错即表示基础环境正常。
5.4 验证Patroni集群状态
bash
# 进入patroni-pg01容器
docker exec -it patroni-pg01 /bin/bash
# 查看集群状态
patronictl -c /etc/patroni/patroni.yml list
# 第二种方式
curl http://localhost:8008/patroni/cluster
成功输出示例:
sql
+ Cluster: pg-ha-docker (1234567890123456789) -------+----+-----------+
| Member | Host | Role | State | TL | Lag in MB |
+----------------+---------------+---------+---------+----+-----------+
| patroni-pg01 | patroni-pg01 | Leader | running | 1 | |
| patroni-pg02 | patroni-pg02 | Replica | running | 1 | 0 |
+----------------+---------------+---------+---------+----+-----------+
六、核心功能验证
6.1 主从流复制验证
6.1.1 主库写入数据
sql
# 连接主库执行SQL
docker exec -it patroni-pg01 psql -U postgres -d postgres
# 创建测试库和表并插入数据
CREATE DATABASE test_ha;
\c test_ha;
CREATE TABLE t1 (id INT PRIMARY KEY, name VARCHAR(20), create_time TIMESTAMP DEFAULT now());
INSERT INTO t1 (id, name) VALUES (1, 'docker-pg'), (2, 'patroni-ha');
SELECT * FROM t1;
\q
6.1.2 从库验证同步
bash
# 连接从库查询数据
docker exec -it patroni-pg02 psql -U postgres -d postgres
\c test_ha;
SELECT * FROM t1; # 与主库数据一致即同步成功
\q
6.1.3 验证复制进程
bash
# 主库查看复制连接
docker exec -it patroni-pg01 psql -U postgres -c "SELECT usename, client_addr, state FROM pg_stat_replication;"
输出repl用户且state=streaming即表示主从复制正常。
6.2 自动故障切换验证
6.2.1 模拟主库宕机
arduino
# 停止主库容器
docker stop patroni-pg01
6.2.2 监控切换过程
bash
# 进入从库容器监控集群状态
docker exec -it patroni-pg02 /bin/bash
watch -n 2 patronictl -c /etc/patroni/patroni.yml list
切换完成后,patroni-pg02变为Leader,patroni-pg01状态为unreachable。
6.2.3 验证新主库可读写
sql
# 连接新主库插入数据
docker exec -it patroni-pg02 psql -U postgres -d test_ha
INSERT INTO t1 (id, name) VALUES (3, 'docker-auto-failover');
INSERT INTO t1 (id, name) VALUES (4, 'docker-auto-failover');
SELECT * FROM t1;
\q
6.2.4 恢复原主库(成为原从库子节点)
bash
# 重启原主库容器
docker start patroni-pg01
原主库(patroni-pg01)重启后,会自动加入集群并同步新主库(原从库patroni-pg02)的数据,最终成为原从库(新主库)的子节点,以从库身份跟随新主库保持数据同步,维持集群正常高可用架构。
6.3 手动故障切换验证
bash
# 进入patroni-pg01容器
docker exec -it patroni-pg01 /bin/bash
# 执行手动切换 这个版本里没有这个,看下日志就能看到
patronictl -c /etc/patroni/patroni.yml switchover
按交互提示选择目标主库(patroni-pg01)并确认,切换完成后验证patroni-pg01恢复为Leader,数据同步正常。
七、常用运维命令
7.1 集群整体管理
bash
# 启动集群
docker compose up -d
# 停止集群(保留容器/数据)
docker compose stop
# 重启集群
docker compose restart
# 停止并删除容器(保留数据卷)
docker compose down
# 彻底清理(容器+数据卷+网络)
docker compose down -v
7.2 容器单独操作
bash
# 启动单个容器
docker start patroni-pg01
# 停止单个容器
docker stop patroni-pg01
# 进入容器终端
docker exec -it patroni-pg01 /bin/bash
7.3 Patroni核心命令(容器内)
bash
# 查看集群状态
patronictl -c /etc/patroni/patroni.yml list
# 手动切换主库
patronictl -c /etc/patroni/patroni.yml switchover
# 强制故障切换
patronictl -c /etc/patroni/patroni.yml failover
八、常见故障排查
8.1 容器启动报permission denied
原因:宿主机目录权限不足,容器无法读写挂载的卷。
解决 :执行chmod -R 777 /pg-ha-docker授权,重启容器。
8.2 Patroni无法连接etcd
原因:etcd未启动或容器网络不通。
解决 :先启动etcd容器docker start pg-etcd,验证网络连通性后重启Patroni容器。
8.3 apt锁占用(Waiting for cache lock)
bash
# 进入容器终止占用进程
docker exec -it patroni-pg01 /bin/bash
killall -9 apt apt-get
rm -rf /var/lib/dpkg/lock-frontend /var/lib/apt/lists/lock
dpkg --configure -a
8.4 镜像拉取超时
原因:Docker镜像加速配置未生效,或外网网络不稳定。
解决:重新配置daemon.json并重启Docker + containerd,手动拉取镜像:
bash
docker pull bitnami/etcd:3.5.10
docker pull postgres:15-bullseye
九、环境清理
测试完成后,执行以下命令彻底清理环境,无残留:
bash
cd /pg-ha-docker
docker compose down -v
rm -rf /pg-ha-docker
# 可选:停止Docker服务
systemctl stop docker
十、生产环境扩展建议
本文方案为测试环境伪集群,生产环境落地需做以下调整:
- etcd部署3节点集群,提升配置存储层的高可用;
- PostgreSQL实例部署到多台物理/云主机,规避单节点故障风险;
- 增加HAProxy/Keepalived实现VIP漂移,统一数据库访问入口;
- 配置Prometheus + Grafana监控告警,实时监控集群状态和数据同步延迟;
- 增加数据备份策略(如pg_basebackup + WAL归档),保障数据可恢复。。