PGSQL + patroni + timescaledb 集群搭建
PGSQL + patroni + timescaledb 集群搭建
三台主机(192.168.1.169 / 192.168.1.188 / 192.168.1.189) 的全部配置文件都生成好了,基于你指定的镜像 postgres:16,每个容器都使用 network_mode: host 并挂载 /etc/localtime:/etc/localtime:ro。
下面直接给出每台主机需要的文件内容与启动步骤(你可以直接 scp 或复制到相应主机对应路径并执行)。
安全提示:示例中使用的数据库密码
rootpass/ 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:16
LABEL 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: node1
restapi:
listen: 0.0.0.0:8008
connect_address: 192.168.1.169:8008
etcd:
hosts:
- 192.168.1.169:2379
- 192.168.1.188:2379
- 192.168.1.189:2379
bootstrap:
dcs:
ttl: 30
loop_wait: 10
retry_timeout: 10
maximum_lag_on_failover: 1048576
postgresql:
use_pg_rewind: true
pg_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 md5
parameters:
shared_buffers: "256MB"
max_connections: 200
wal_level: replica
hot_standby: "on"
logging_collector: "on"
log_directory: "/var/lib/postgresql/data/log"
log_filename: "postgresql.log"
initdb:
- encoding: UTF8
- data-checksums
users:
admin:
password: rootpass
options:
- createrole
- createdb
postgresql:
listen: 0.0.0.0:5432
connect_address: 192.168.1.169:5432
data_dir: /var/lib/postgresql/data
bin_dir: /usr/lib/postgresql/16/bin
authentication:
superuser:
username: root
password: rootpass
replication:
username: replicator
password: replpass
/opt/timescaledb/config/haproxy.cfg (放 node1)
global
maxconn 200
defaults
log global
mode tcp
retries 2
timeout connect 5s
timeout client 1m
timeout server 1m
option tcp-check
listen stats
bind *:7000
mode http
stats enable
stats uri /
frontend write_front
bind *:5000
default_backend write_back
backend write_back
option httpchk GET /master
http-check expect status 200
server node1 192.168.1.169:5432 check port 8008
server node2 192.168.1.188:5432 check port 8008
server node3 192.168.1.189:5432 check port 8008
frontend read_front
bind *:5001
default_backend read_back
backend read_back
option httpchk GET /replica
http-check expect status 200
server node1 192.168.1.169:5432 check port 8008
server node2 192.168.1.188:5432 check port 8008
server 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.12
env_file: ./config/etcd.env
volumes:
- ./data/etcd:/etcd-data
- /etc/localtime:/etc/localtime
network_mode: host
restart: always
pg-node1:
build: .
image: timescaledb-patroni:16
container_name: pg-node1
volumes:
- ./data/postgres:/var/lib/postgresql/data
- ./config/patroni.yml:/config/patroni.yml:ro
- /etc/localtime:/etc/localtime
network_mode: host
restart: always
environment:
PATRONI_LOG_LEVEL: INFO
depends_on:
- etcd
haproxy:
image: haproxy:2.8
volumes:
- ./config/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
- /etc/localtime:/etc/localtime
network_mode: host
restart: 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: node2
restapi:
listen: 0.0.0.0:8008
connect_address: 192.168.1.188:8008
etcd:
hosts:
- 192.168.1.169:2379
- 192.168.1.188:2379
- 192.168.1.189:2379
bootstrap:
dcs:
ttl: 30
loop_wait: 10
retry_timeout: 10
maximum_lag_on_failover: 1048576
postgresql:
use_pg_rewind: true
pg_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 md5
parameters:
shared_buffers: "256MB"
max_connections: 200
wal_level: replica
hot_standby: "on"
logging_collector: "on"
log_directory: "/var/lib/postgresql/data/log"
log_filename: "postgresql.log"
initdb:
- encoding: UTF8
- data-checksums
users:
admin:
password: rootpass
options:
- createrole
- createdb
postgresql:
listen: 0.0.0.0:5432
connect_address: 192.168.1.188:5432
data_dir: /var/lib/postgresql/data
bin_dir: /usr/lib/postgresql/16/bin
authentication:
superuser:
username: root
password: rootpass
replication:
username: replicator
password: replpass
/opt/timescaledb/docker-compose.yml (node2)
version: '3.8'
services:
etcd:
image: quay.io/coreos/etcd:v3.5.12
env_file: ./config/etcd.env
volumes:
- /etc/localtime:/etc/localtime
- ./data/etcd:/etcd-data
network_mode: host
restart: always
pg-node2:
image: timescaledb-patroni:16
container_name: pg-node2 # node03 改为 pg-node3
volumes:
- ./data/postgres:/var/lib/postgresql/data
- ./config/patroni.yml:/config/patroni.yml:ro
- /etc/localtime:/etc/localtime
network_mode: host
restart: always
environment:
PATRONI_LOG_LEVEL: INFO
depends_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: node3
restapi:
listen: 0.0.0.0:8008
connect_address: 192.168.1.189:8008
etcd:
hosts:
- 192.168.1.169:2379
- 192.168.1.188:2379
- 192.168.1.189:2379
bootstrap:
dcs:
ttl: 30
loop_wait: 10
retry_timeout: 10
maximum_lag_on_failover: 1048576
postgresql:
use_pg_rewind: true
pg_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 md5
parameters:
shared_buffers: "256MB"
max_connections: 200
wal_level: replica
hot_standby: "on"
logging_collector: "on"
log_directory: "/var/lib/postgresql/data/log"
log_filename: "postgresql.log"
initdb:
- encoding: UTF8
- data-checksums
users:
admin:
password: rootpass
options:
- createrole
- createdb
postgresql:
listen: 0.0.0.0:5432
connect_address: 192.168.1.189:5432
data_dir: /var/lib/postgresql/data
bin_dir: /usr/lib/postgresql/16/bin
authentication:
superuser:
username: root
password: rootpass
replication:
username: replicator
password: replpass
/opt/timescaledb/docker-compose.yml (node3)
version: '3.8'
services:
etcd:
image: quay.io/coreos/etcd:v3.5.12
env_file: ./config/etcd.env
volumes:
- ./data/etcd:/etcd-data
- /etc/localtime:/etc/localtime
network_mode: host
restart: always
pg-node3: # node03 改为 pg-node3
image: timescaledb-patroni:16
container_name: pg-node3 # node03 改为 pg-node3
volumes:
- /etc/localtime:/etc/localtime
- ./data/postgres:/var/lib/postgresql/data
- ./config/patroni.yml:/config/patroni.yml:ro
network_mode: host
restart: always
environment:
PATRONI_LOG_LEVEL: INFO
depends_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;
注意事项与建议(简要)
- 更换默认密码:把
rootpass/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 作为连接池。

浙公网安备 33010602011771号