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


启动步骤(按顺序在三台上执行)

  1. 在每台主机上分别把对应的 etcd.envpatroni.ymldocker-compose.yml 放入 /opt/timescaledb/config/opt/timescaledb(见上文)。
  2. 在每台主机创建数据目录并调整权限:
mkdir -p /opt/timescaledb/data/postgres /opt/timescaledb/data/etcd
chown -R 999:999 /opt/timescaledb/data/postgres || true

999 是容器中常见 postgres uid,若不确定可跳过 chown,或根据镜像实际 uid 设置)

  1. 先启动三台的 etcd(在每台运行):
cd /opt/timescaledb
docker compose up -d etcd
  1. 等 etcd 集群就绪(在任意一台执行):
# 如果 etcdctl 在容器内可用,可进入容器检查;简单 HTTP 检查:
curl http://192.168.1.169:2379/v2/members || true
# 或查看容器日志
docker logs -f etcd
  1. 启动 Patroni(在每台执行):
docker compose up -d pg-node1 #pg-node2 pg-node3
  1. 在 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 作为连接池。
posted @ 2025-10-10 15:00  蒲公英PGY  阅读(46)  评论(0)    收藏  举报