PGSQL + patroni + timescaledb 集群搭建
三台主机(192.168.1.169 / 192.168.1.188 / 192.168.1.189) 的全部配置文件都生成好了,基于你指定的镜像 timescale/timescaledb:2.15.3-pg16
,每个容器都使用 network_mode: host
并挂载 /etc/localtime:/etc/localtime:ro
。
下面直接给出每台主机需要的文件内容与启动步骤(你可以直接 scp
或复制到相应主机对应路径并执行)。
安全提示:示例中使用的数据库密码
superpass
/ replication 密码replpass
为演示用,请在部署前改为你自己的强密码并避免把明文提交到版本库。
目录与前置操作(每台主机都做)
在三台主机上统一创建目录并设置权限(以 root 或有 sudo 的用户执行):
mkdir -p /opt/timescaledb/{data,config}
chown -R $(id -u):$(id -g) /opt/timescaledb
我们将在 /opt/timescaledb/config
放配置文件,数据目录放 /opt/timescaledb/data
。
构建基础镜像
pgsql官方镜像没有patroni与timescaledb,需要重新构建进行集成
dockerfile如下:
# 基础镜像
FROM postgres:16LABEL maintainer="Lang <dev@example.com>" \description="PostgreSQL 16 + TimescaleDB + Patroni"USER root# 安装构建依赖、TimescaleDB 编译依赖、Python 和 Patroni
RUN apt-get update && apt-get install -y \build-essential cmake git libssl-dev libperl-dev libreadline-dev \python3 python3-pip python3-venv libpq-dev curl wget gcc postgresql-server-dev-16 \&& rm -rf /var/lib/apt/lists/*
# TimescaleDB 版本
ENV TIMESCALEDB_VERSION=2.15.3# 下载并编译 TimescaleDB
COPY timescaledb /tmp/timescaledb
RUN cd /tmp/timescaledb \&& cd /tmp/timescaledb \&& ./bootstrap \&& cd build && make install \&& rm -rf /tmp/timescaledb# 安装 Patroni
#RUN pip3 install --no-cache-dir --break-system-packages "patroni[etcd3]==3.3.0" psycopg>=3.1
RUN python3 -m venv /opt/venv \&& /opt/venv/bin/pip install --upgrade pip \&& /opt/venv/bin/pip install --no-cache-dir "patroni[etcd3]==3.3.0" psycopg>=3.1# 更新 PATH
ENV PATH="/opt/venv/bin:$PATH"# TimescaleDB 自动加载
RUN echo "shared_preload_libraries = 'timescaledb'" >> /usr/share/postgresql/postgresql.conf.sample# 暴露端口
EXPOSE 5432 8008# 切换回 postgres 用户
USER postgres# 启动 Patroni
ENTRYPOINT ["patroni"]
CMD ["/config/patroni.yml"]
clone timescaledb
git clone --branch 2.15.3 https://github.com/timescale/timescaledb.git
构建镜像
docker build --no-cache -t timescaledb-patroni:16 .
1) node1 — 主机 192.168.1.169
/opt/timescaledb/config/etcd.env
(node1)
由于本文中的patroni 连接etcd使用的api接口为V2,etcd3默认关闭v2接口,需要单独开启
ETCD_NAME=node1
ETCD_INITIAL_ADVERTISE_PEER_URLS=http://192.168.1.169:2380
ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380
ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379
ETCD_ADVERTISE_CLIENT_URLS=http://192.168.1.169:2379
ETCD_INITIAL_CLUSTER=node1=http://192.168.1.169:2380,node2=http://192.168.1.188:2380,node3=http://192.168.1.189:2380
ETCD_INITIAL_CLUSTER_STATE=new
ETCD_INITIAL_CLUSTER_TOKEN=tsdb-cluster
ALLOW_NONE_AUTHENTICATION=yes
ETCD_ENABLE_V2=true # 开启v2接口
/opt/timescaledb/config/patroni.yml
(node1)
scope: tsdb
namespace: /service/
name: node1restapi:listen: 0.0.0.0:8008connect_address: 192.168.1.169:8008etcd:hosts:- 192.168.1.169:2379- 192.168.1.188:2379- 192.168.1.189:2379bootstrap:dcs:ttl: 30loop_wait: 10retry_timeout: 10maximum_lag_on_failover: 1048576postgresql:use_pg_rewind: truepg_hba:# 本地访问- local all all trust- host all all 127.0.0.1/32 trust- host all all ::1/128 trust# 允许本地 replication 连接(非常关键)- host replication replicator 127.0.0.1/32 trust# 集群内复制 (根据你集群 IP 段修改)- host replication replicator 192.168.1.0/24 md5- host all root 192.168.1.0/24 md5- host all all 192.168.1.0/24 md5parameters:shared_buffers: "256MB"max_connections: 200wal_level: replicahot_standby: "on"logging_collector: "on"log_directory: "/var/lib/postgresql/data/log"log_filename: "postgresql.log"initdb:- encoding: UTF8- data-checksumsusers:admin:password: superpassoptions:- createrole- createdbpostgresql:listen: 0.0.0.0:5432connect_address: 192.168.1.169:5432data_dir: /var/lib/postgresql/databin_dir: /usr/lib/postgresql/16/binauthentication:superuser:username: rootpassword: superpassreplication:username: replicatorpassword: replpass
/opt/timescaledb/config/haproxy.cfg
(放 node1)
globalmaxconn 200defaultslog globalmode tcpretries 2timeout connect 5stimeout client 1mtimeout server 1moption tcp-checklisten statsbind *:7000mode httpstats enablestats uri /frontend write_frontbind *:5000default_backend write_backbackend write_backoption httpchk GET /masterhttp-check expect status 200server node1 192.168.1.169:5432 check port 8008server node2 192.168.1.188:5432 check port 8008server node3 192.168.1.189:5432 check port 8008frontend read_frontbind *:5001default_backend read_backbackend read_backoption httpchk GET /replicahttp-check expect status 200server node1 192.168.1.169:5432 check port 8008server node2 192.168.1.188:5432 check port 8008server node3 192.168.1.189:5432 check port 8008
/opt/timescaledb/docker-compose.yml
(node1)
version: '3.8'services:etcd:image: quay.io/coreos/etcd:v3.5.12env_file: ./config/etcd.envvolumes:- ./data/etcd:/etcd-data- /etc/localtime:/etc/localtimenetwork_mode: hostrestart: alwayspg-node1:build: .image: timescaledb-patroni:16container_name: pg-node1volumes:- ./data/postgres:/var/lib/postgresql/data- ./config/patroni.yml:/config/patroni.yml:ro- /etc/localtime:/etc/localtimenetwork_mode: hostrestart: alwaysenvironment:PATRONI_LOG_LEVEL: INFOdepends_on:- etcdhaproxy:image: haproxy:2.8volumes:- ./config/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro- /etc/localtime:/etc/localtimenetwork_mode: hostrestart: always
2) node2 — 主机 192.168.1.188
/opt/timescaledb/config/etcd.env
(node2)
ETCD_NAME=node2
ETCD_INITIAL_ADVERTISE_PEER_URLS=http://192.168.1.188:2380
ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380
ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379
ETCD_ADVERTISE_CLIENT_URLS=http://192.168.1.188:2379
ETCD_INITIAL_CLUSTER=node1=http://192.168.1.169:2380,node2=http://192.168.1.188:2380,node3=http://192.168.1.189:2380
ETCD_INITIAL_CLUSTER_STATE=new
ETCD_INITIAL_CLUSTER_TOKEN=tsdb-cluster
ALLOW_NONE_AUTHENTICATION=yes
ETCD_ENABLE_V2=true
/opt/timescaledb/config/patroni.yml
(node2)
scope: tsdb
namespace: /service/
name: node2restapi:listen: 0.0.0.0:8008connect_address: 192.168.1.188:8008etcd:hosts:- 192.168.1.169:2379- 192.168.1.188:2379- 192.168.1.189:2379bootstrap:dcs:ttl: 30loop_wait: 10retry_timeout: 10maximum_lag_on_failover: 1048576postgresql:use_pg_rewind: truepg_hba:# 本地访问- local all all trust- host all all 127.0.0.1/32 trust- host all all ::1/128 trust# 允许本地 replication 连接(非常关键)- host replication replicator 127.0.0.1/32 trust# 集群内复制 (根据你集群 IP 段修改)- host replication replicator 192.168.1.0/24 md5- host all root 192.168.1.0/24 md5- host all all 192.168.1.0/24 md5parameters:shared_buffers: "256MB"max_connections: 200wal_level: replicahot_standby: "on"logging_collector: "on"log_directory: "/var/lib/postgresql/data/log"log_filename: "postgresql.log"initdb:- encoding: UTF8- data-checksumsusers:admin:password: jiuqi@2020options:- createrole- createdbpostgresql:listen: 0.0.0.0:5432connect_address: 192.168.1.188:5432data_dir: /var/lib/postgresql/databin_dir: /usr/lib/postgresql/16/binauthentication:superuser:username: rootpassword: jiuqi@2020replication:username: replicatorpassword: replpass
/opt/timescaledb/docker-compose.yml
(node2)
version: '3.8'services:etcd:image: quay.io/coreos/etcd:v3.5.12env_file: ./config/etcd.envvolumes:- /etc/localtime:/etc/localtime- ./data/etcd:/etcd-datanetwork_mode: hostrestart: alwayspg-node2: image: timescaledb-patroni:16container_name: pg-node2 # node03 改为 pg-node3volumes:- ./data/postgres:/var/lib/postgresql/data- ./config/patroni.yml:/config/patroni.yml:ro- /etc/localtime:/etc/localtimenetwork_mode: hostrestart: alwaysenvironment:PATRONI_LOG_LEVEL: INFOdepends_on:- etcd
3) node3 — 主机 192.168.1.189
/opt/timescaledb/config/etcd.env
(node3)
ETCD_NAME=node3
ETCD_INITIAL_ADVERTISE_PEER_URLS=http://192.168.1.189:2380
ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380
ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379
ETCD_ADVERTISE_CLIENT_URLS=http://192.168.1.189:2379
ETCD_INITIAL_CLUSTER=node1=http://192.168.1.169:2380,node2=http://192.168.1.188:2380,node3=http://192.168.1.189:2380
ETCD_INITIAL_CLUSTER_STATE=new
ETCD_INITIAL_CLUSTER_TOKEN=tsdb-cluster
ALLOW_NONE_AUTHENTICATION=yes
ETCD_ENABLE_V2=true
/opt/timescaledb/config/patroni.yml
(node3)
scope: tsdb
namespace: /service/
name: node3restapi:listen: 0.0.0.0:8008connect_address: 192.168.1.189:8008etcd:hosts:- 192.168.1.169:2379- 192.168.1.188:2379- 192.168.1.189:2379bootstrap:dcs:ttl: 30loop_wait: 10retry_timeout: 10maximum_lag_on_failover: 1048576postgresql:use_pg_rewind: truepg_hba:# 本地访问- local all all trust- host all all 127.0.0.1/32 trust- host all all ::1/128 trust# 允许本地 replication 连接(非常关键)- host replication replicator 127.0.0.1/32 trust# 集群内复制 (根据你集群 IP 段修改)- host replication replicator 192.168.1.0/24 md5- host all root 192.168.1.0/24 md5- host all all 192.168.1.0/24 md5parameters:shared_buffers: "256MB"max_connections: 200wal_level: replicahot_standby: "on"logging_collector: "on"log_directory: "/var/lib/postgresql/data/log"log_filename: "postgresql.log"initdb:- encoding: UTF8- data-checksumsusers:admin:password: jiuqi@2020options:- createrole- createdbpostgresql:listen: 0.0.0.0:5432connect_address: 192.168.1.189:5432data_dir: /var/lib/postgresql/databin_dir: /usr/lib/postgresql/16/binauthentication:superuser:username: rootpassword: jiuqi@2020replication:username: replicatorpassword: replpass
/opt/timescaledb/docker-compose.yml
(node3)
version: '3.8'services:etcd:image: quay.io/coreos/etcd:v3.5.12env_file: ./config/etcd.envvolumes:- ./data/etcd:/etcd-data- /etc/localtime:/etc/localtimenetwork_mode: hostrestart: alwayspg-node3: # node03 改为 pg-node3image: timescaledb-patroni:16container_name: pg-node3 # node03 改为 pg-node3volumes:- /etc/localtime:/etc/localtime- ./data/postgres:/var/lib/postgresql/data- ./config/patroni.yml:/config/patroni.yml:ronetwork_mode: hostrestart: alwaysenvironment:PATRONI_LOG_LEVEL: INFOdepends_on:- etcd
启动步骤(按顺序在三台上执行)
- 在每台主机上分别把对应的
etcd.env
、patroni.yml
、docker-compose.yml
放入/opt/timescaledb/config
与/opt/timescaledb
(见上文)。 - 在每台主机创建数据目录并调整权限:
mkdir -p /opt/timescaledb/data/postgres /opt/timescaledb/data/etcd
chown -R 999:999 /opt/timescaledb/data/postgres || true
(999
是容器中常见 postgres uid,若不确定可跳过 chown,或根据镜像实际 uid 设置)
- 先启动三台的 etcd(在每台运行):
cd /opt/timescaledb
docker compose up -d etcd
- 等 etcd 集群就绪(在任意一台执行):
# 如果 etcdctl 在容器内可用,可进入容器检查;简单 HTTP 检查:
curl http://192.168.1.169:2379/v2/members || true
# 或查看容器日志
docker logs -f etcd
- 启动 Patroni(在每台执行):
docker compose up -d pg-node1 #pg-node2 pg-node3
- 在 node1 启动 HAProxy(可只在 node1):
docker compose up -d haproxy
验证 & 常用命令
- 查看 Patroni REST 状态(每台):
curl http://192.168.1.169:8008
curl http://192.168.1.188:8008
curl http://192.168.1.189:8008
# 返回 JSON 中的 "role" 字段显示 leader/replica
- 在 leader 上启用 TimescaleDB 扩展(通过 HAProxy 写端口或直接 leader host):
psql -h 192.168.1.169 -p 5000 -U postgres -d postgres
# 然后:
CREATE EXTENSION IF NOT EXISTS timescaledb;
- 用 HAProxy 写端口连接(会路由到当前 leader):
psql -h 192.168.1.169 -p 5000 -U postgres -d postgres
- 强制切换测试(在当前 leader 主机执行):
# find which node is leader via curl -> then stop patroni on that node:
docker stop patroni
# 等待另一台成为 leader(检查 REST role)
- 查看复制状态(在 leader psql):
SELECT client_addr, state, sync_state, sent_lsn, write_lsn, flush_lsn, replay_lsn FROM pg_stat_replication;
注意事项与建议(简要)
- 更换默认密码:把
superpass
/replpass
改为强密码,或使用 secrets 管理。 - 防火墙:确保三台允许互通端口:2379、2380、5432、8008、5000、5001、7000(若 haproxy 在别处则开放相应端口)。
- 时间同步:宿主机启用 chrony/ntpd,已挂载
/etc/localtime
保证容器时区一致。 - etcd TLS(可选):生产建议为 etcd 开启 TLS,避免明文传输。
- 备份:制定 WAL + basebackup 备份方案(pg_basebackup / pgBackRest)。
- 连接池:在高并发场景下,建议在 HAProxy 前放置 PgBouncer 作为连接池。