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号
浙公网安备 33010602011771号