专注,勤学,慎思。戒骄戒躁,谦虚谨慎

just do it

导航

Ubuntu 20 环境下 patroni 自动化安装,一分钟快速搭建 patroni 集群

 

1,前提是要对patroni的架构和工作原理有着非常清晰的认识,至少手动搭建过3次以上,自动化只是手段不是目的
2,该脚本在Ubuntu 20 下做过详尽的测试,pg17+ etcd v3.6.5+ patroni 4.1.3,可极大简化patroni 的安装过程,全程自动完成patroni 的搭建
3,欢迎试用,有建议或者问题请留言

 

0,环境 

1,先编译好PostgreSQL服务,然后停止相关实例,因为patroni初始化的时候会自动重新初始化数据
2,Ubuntu 20,PostgreSQL 17.6,etcd v3.6.5,patroni 4.1.3,patroni启动的是会创建数据目录,因此需要关闭各节点已有的PostgreSQL实例(如果有)。
3,先自动安装etcd集群,安装脚本依次再三台机器上执行,然后然后patroni集群,同样是安装脚本依次再三台机器上执行。


该脚本可以在一分钟之内可以搭建一个完整的patroni集群,展开数据库的下一步工作,而不是浪费大量时间在环境的搭建上。

root@ubuntu11:/usr/local/pg_install_package# cat /etc/hosts
127.0.0.1 localhost
127.0.1.1 ubuntu11

# The following lines are desirable for IPv6 capable hosts
::1     ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters

192.168.152.121 ubuntu11
192.168.152.122 ubuntu12
192.168.152.123 ubuntu13
192.168.152.124 ubuntu14

 

1,环境配置脚本:00_cluster_config.sh

该脚本不需要单独执行,需要修改各个路径等信息,比较简单不详述,改脚本会加载再后面两个脚本里

#!/bin/bash
# 00_cluster_config.sh - 集群全局配置文件
# 此文件在所有节点上内容相同

# ==================== 集群节点信息 ====================
# 节点1
export NODE1_NAME="ubuntu11"
export NODE1_IP="192.168.152.121"
# 节点2
export NODE2_NAME="ubuntu12"
export NODE2_IP="192.168.152.122"
# 节点3
export NODE3_NAME="ubuntu13"
export NODE3_IP="192.168.152.123"

# 当前节点名称(在每个节点上手动设置或自动检测)
# 方法1: 自动检测主机名
export CURRENT_NODE_NAME=$(hostname)
# 方法2: 手动设置(取消注释并修改)
# export CURRENT_NODE_NAME="ubuntu11"

# 根据当前节点名获取IP
case ${CURRENT_NODE_NAME} in
    ubuntu11)
        export CURRENT_NODE_IP="${NODE1_IP}"
        export ETCD_NAME="etcd01"
        ;;
    ubuntu12)
        export CURRENT_NODE_IP="${NODE2_IP}"
        export ETCD_NAME="etcd02"
        ;;
    ubuntu13)
        export CURRENT_NODE_IP="${NODE3_IP}"
        export ETCD_NAME="etcd03"
        ;;
    *)
        echo "Error: Unknown node name ${CURRENT_NODE_NAME}"
        exit 1
        ;;
esac

# ==================== 统一路径配置 ====================
# etcd相关目录
export ETCD_VERSION="v3.6.5"
export ETCD_INSTALL_DIR="/usr/local/etcd"
export ETCD_DATA_DIR="/usr/local/etcd/default"
export ETCD_CONF_DIR="/usr/local/etcd"

# PostgreSQL相关目录
export PG_BASE="/usr/local/pgsql17"
export PG_PORT="9000"
export PG_DATA_DIR="${PG_BASE}/pg9000/data"
export PG_BIN_DIR="${PG_BASE}/server/bin"
export PG_WAL_ARCH_DIR="/usr/local/pg9000_wal_arch"


# patroni相关目录
export PATRONI_DIR="${PG_BASE}/patroni"
export PATRONI_CONFIG_FILE="${PATRONI_DIR}/patroni.yml"
export SCOPE_NAME="pg_cluster_wy_prod"
export NAMESPACE="/service/"

# ==================== PostgreSQL 统一密码 ====================
export SUPERUSER_PASSWORD="your_strong_postgres_pwd"
export REPL_PASSWORD="your_strong_repl_pwd"
export REWIND_PASSWORD="your_strong_rewind_pwd"

# ==================== etcd 集群配置 ====================
export ETCD_CLUSTER="etcd01=http://${NODE1_IP}:2380,etcd02=http://${NODE2_IP}:2380,etcd03=http://${NODE3_IP}:2380"
export ETCD_CLUSTER_TOKEN="etcd-cluster"

# ==================== 其他 ====================
export ETCDCTL_API=3

# ==================== 创建必要的目录 ====================
create_required_dirs() {
    local dirs_to_create=(
        "${ETCD_INSTALL_DIR}"
        "${ETCD_DATA_DIR}"
        "${ETCD_CONF_DIR}"
        "${PG_BASE}"
        "${PG_DATA_DIR}"
        "${PATRONI_DIR}"
        "${PG_BIN_DIR}"
        "${PG_WAL_ARCH_DIR}"
    )
    
    for dir in "${dirs_to_create[@]}"; do
        if [ ! -d "${dir}" ]; then
            echo "Creating directory: ${dir}"
            mkdir -p "${dir}"
        fi
    done
}

# 创建必要的目录
create_required_dirs


# ==================== 打印当前配置 ====================
echo "=== Current Node Configuration ==="
echo "Current Node Name: ${CURRENT_NODE_NAME}"
echo "Current Node IP: ${CURRENT_NODE_IP}"
echo "ETCD Name: ${ETCD_NAME}"
echo "ETCD Data Dir: ${ETCD_DATA_DIR}"
echo "PostgreSQL Base: ${PG_BASE}"
echo "Patroni Config: ${PATRONI_CONFIG_FILE}"
echo "PostgreSQL WAL ARCH Dir: ${PG_WAL_ARCH_DIR}"
echo "================================"

 

2,etcd集群安装脚本 01_install_etcd.sh

依次在 ubuntu11,ubuntu12,ubuntu13上执行,创建etcd集群

#!/bin/bash
set -euo pipefail

# 加载配置
source ./00_cluster_config.sh

echo "=== Installing etcd on ${CURRENT_NODE_NAME} ==="

# 1. 创建etcd工作目录并切换
echo "Step 1: Creating etcd working directory..."
mkdir -p /usr/local/etcd
cd /usr/local/etcd

# 2. 下载etcd
echo "Step 2: Downloading etcd..."
if [ ! -f "etcd-${ETCD_VERSION}-linux-amd64.tar.gz" ]; then
    wget -q https://github.com/etcd-io/etcd/releases/download/${ETCD_VERSION}/etcd-${ETCD_VERSION}-linux-amd64.tar.gz
fi

# 3. 解压etcd
echo "Step 3: Extracting etcd..."
# 如果解压目录已存在,先删除
if [ -d "etcd-${ETCD_VERSION}-linux-amd64" ]; then
    echo "Removing existing etcd directory..."
    rm -rf "etcd-${ETCD_VERSION}-linux-amd64"
fi
tar -xzf etcd-${ETCD_VERSION}-linux-amd64.tar.gz

# 4. 删除已存在的二进制文件或软链接
echo "Step 4: Removing existing etcd binaries..."
rm -f /usr/bin/etcd /usr/bin/etcdctl

# 5. 复制二进制文件到系统路径
echo "Step 5: Copying etcd binaries to /usr/bin..."
cp etcd-${ETCD_VERSION}-linux-amd64/etcd /usr/bin/etcd
cp etcd-${ETCD_VERSION}-linux-amd64/etcdctl /usr/bin/etcdctl
chmod +x /usr/bin/etcd /usr/bin/etcdctl

# 6. 创建数据目录
echo "Step 6: Creating data directory..."
mkdir -p ${ETCD_DATA_DIR}

# 7. 生成etcd配置文件
echo "Step 7: Generating etcd configuration..."
mkdir -p ${ETCD_CONF_DIR}

cat > ${ETCD_CONF_DIR}/etcd.conf <<EOF
ETCD_NAME="${ETCD_NAME}"
ETCD_DATA_DIR="${ETCD_DATA_DIR}"
ETCD_LISTEN_CLIENT_URLS="http://${CURRENT_NODE_IP}:2379,http://127.0.0.1:2379"
ETCD_ADVERTISE_CLIENT_URLS="http://${CURRENT_NODE_IP}:2379"
ETCD_LISTEN_PEER_URLS="http://${CURRENT_NODE_IP}:2380"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://${CURRENT_NODE_IP}:2380"
ETCD_INITIAL_CLUSTER="${ETCD_CLUSTER}"
ETCD_INITIAL_CLUSTER_TOKEN="${ETCD_CLUSTER_TOKEN}"
ETCD_INITIAL_CLUSTER_STATE="new"
EOF

# 8. 创建systemd服务
echo "Step 8: Creating systemd service..."
cat > /etc/systemd/system/etcd.service <<EOF
[Unit]
Description=Etcd Server
After=network.target network-online.target
Wants=network-online.target

[Service]
Type=notify
EnvironmentFile=${ETCD_CONF_DIR}/etcd.conf
WorkingDirectory=${ETCD_DATA_DIR}
ExecStart=/usr/bin/etcd
Restart=on-failure
RestartSec=5
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
EOF

# 9. 启动etcd服务
echo "Step 9: Starting etcd service..."
systemctl daemon-reload
systemctl enable etcd
systemctl start etcd &

# 10. 设置环境变量
echo "Step 10: Setting environment variables..."
if ! grep -q "ETCDCTL_API=3" /etc/profile; then
    echo 'export ETCDCTL_API=3' >> /etc/profile
fi


# 11. 验证安装
echo "Step 11: Verifying etcd installation..."
etcd --version
sleep 3
systemctl status etcd --no-pager

echo "=== etcd installation on ${CURRENT_NODE_NAME} completed ==="

 

3 ,patroni集群安装脚本:02_install_patroni.sh

依次在 ubuntu11,ubuntu12,ubuntu13上执行,创建patroni集群

V 1.0  初始化
V 1.1  增加  synchronous_mode: true, synchronous_node_count: 1 ,修改pg_hba的md5 为 scram-sha-56,增加本机免密
V 1.2  patroni  service文件增加Environment="TZ=Asia/Shanghai",否则其日志时间为UTC 0时区
V 1.3  监听这里listen: ${CURRENT_NODE_IP}修改为0.0.0.0
V 1.4  手动编辑patroni.yml配置文件中的tags标签,设置每个节点的failover_priority,这里无法提现
#!/bin/bash
set -euo pipefail

# 先加载配置
source ./00_cluster_config.sh

echo "=== Installing Patroni and PostgreSQL on ${CURRENT_NODE_NAME} ==="


# 0. 检查和清理现有数据,
echo "Step 0: Checking existing data directory..."
# 检查 Patroni 服务是否在运行
if systemctl is-active --quiet patroni 2>/dev/null; then
	echo "ERROR: Patroni service is currently running on this node."
	echo "Please stop Patroni service before reinstalling:"
	echo "  systemctl stop patroni"
	echo "  systemctl disable patroni"
	echo "Exiting installation to avoid conflicts."
	exit 1
fi

# 检查数据目录是否存在且非空
if [ -d "${PG_DATA_DIR}" ] && [ "$(ls -A ${PG_DATA_DIR} 2>/dev/null)" ]; then
	echo "WARNING: Data directory ${PG_DATA_DIR} is not empty but Patroni is not running."
	read -p "Do you want to clear this directory and continue installation? (yes/no): " choice
	if [ "$choice" = "yes" ] || [ "$choice" = "y" ]; then
		echo "Cleaning up data directory contents..."
		rm -rf ${PG_DATA_DIR}/*
		rm -rf ${PG_DATA_DIR}/.[!.]* 2>/dev/null || true
		echo "Data directory contents cleared successfully."
	else
		echo "Exiting installation."
		exit 1
	fi
else
	echo "Data directory is empty or does not exist. No cleanup needed."
fi



# 1. 安装依赖包
echo "Step 1: Installing dependencies..."
apt update
apt install -y python3 python3-pip libpq-dev gcc

# 2. 先升级必要的Python包
echo "Step 2: Upgrading Python packages..."
pip3 install --upgrade pip setuptools wheel importlib_metadata

# 3. 安装Patroni
echo "Step 3: Installing Patroni..."
pip3 install psycopg2-binary
pip3 install patroni[etcd]

# 4. 创建目录
echo "Step 4: Creating directories..."
mkdir -p ${PATRONI_DIR}
mkdir -p ${PG_WAL_ARCH_DIR}
chown -R postgres:postgres ${PG_BASE}
chmod 700 ${PATRONI_DIR}
# 授权数据目录,测试过过程中发现只有一台实例patroni无法拉起PostgreSQL进程,授权之后就可以了,奇怪,所以这里再次授权
chmod 0700 ${PG_DATA_DIR}
chown -R postgres:postgres ${PG_DATA_DIR}
chmod 0700 ${PG_WAL_ARCH_DIR}
chown -R postgres:postgres ${PG_WAL_ARCH_DIR}


# 5. 生成Patroni配置文件
echo "Step 5: Generating Patroni configuration..."
cat > ${PATRONI_CONFIG_FILE} <<'EOF'
scope: pg_cluster_wy_prod
namespace: /service/
name: ${CURRENT_NODE_NAME}

restapi:
  listen: ${CURRENT_NODE_IP}:8008
  connect_address: ${CURRENT_NODE_IP}:8008

etcd3:
  host: ${CURRENT_NODE_IP}:2379

log:
  level: INFO
  traceback_level: ERROR
  dir: ${PATRONI_DIR}
  file_num: 10
  file_size: 104857600

bootstrap:
  dcs:
    ttl: 30
    loop_wait: 10
    retry_timeout: 10
    maximum_lag_on_failover: 1048576
    max_timelines_history: 0
    master_start_timeout: 300
    synchronous_mode: true
    synchronous_node_count: 1 
    postgresql:
      use_pg_rewind: true
      use_slots: true
      parameters:
        # basic parameter
        wal_level: hot_standby
        hot_standby: "on"
        max_connections: 100
        max_worker_processes: 8
        
        # wal parameter
        wal_keep_size: 128MB
        max_wal_senders: 10
        max_replication_slots: 10
        wal_log_hints: "on"
        
        # archive
        archive_mode: "on"
        archive_timeout: 1800
        archive_command: 'test ! -f ${PG_WAL_ARCH_DIR}/%f && cp %p ${PG_WAL_ARCH_DIR}/%f'
        
        # log
        logging_collector: "on"
        log_directory: "log"
        log_filename: "postgresql-%Y-%m-%d_%H%M%S.log"
        log_file_mode: "0600"
        log_rotation_age: "1d"
        log_rotation_size: "100MB"
        log_truncate_on_rotation: "on"
        
        # log 
        log_destination: "stderr"
        log_min_duration_statement: 1000
        log_autovacuum_min_duration: 0
        log_checkpoints: "on"
        log_connections: "on"
        log_disconnections: "on"
        log_hostname: "on"
        log_line_prefix: "%m [%p] user:%u,db:%d,app:%a,host:%h "
        log_lock_waits: "on"
        log_recovery_conflict_waits: "on"
        log_statement: "ddl"
        log_replication_commands: "on"
        log_temp_files: 1024
        log_timezone: "Asia/Shanghai"
        
        # buffer
        shared_buffers: 128MB
        effective_cache_size: 4GB
        work_mem: 4MB
        maintenance_work_mem: 64MB

      pg_hba:
        - "host all postgres 127.0.0.1/32 trust"
        - "host replication repl_user 0.0.0.0/0 scram-sha-256"
        - "host all all 0.0.0.0/0 scram-sha-256"
  initdb:
    - encoding: UTF8
    - data-checksums

postgresql:
  listen: 0.0.0.0:${PG_PORT}
  connect_address: ${CURRENT_NODE_IP}:${PG_PORT}
  data_dir: ${PG_DATA_DIR}
  bin_dir: ${PG_BIN_DIR}
  pgpass: /home/postgres/.pgpass
  authentication:
    replication:
      username: repl_user
      password: ${REPL_PASSWORD}
    superuser:
      username: postgres
      password: ${SUPERUSER_PASSWORD}
    rewind:
      username: rewind_user
      password: ${REWIND_PASSWORD}
  parameters:
    unix_socket_directories: '..'

  
tags:
    noloadbalance: false
    clonefrom: false
    nostream: false
EOF

# 注意:需要先进行变量替换
sed -i "s/\${CURRENT_NODE_NAME}/${CURRENT_NODE_NAME}/g" ${PATRONI_CONFIG_FILE}
sed -i "s/\${CURRENT_NODE_IP}/${CURRENT_NODE_IP}/g" ${PATRONI_CONFIG_FILE}
sed -i "s/\${PATRONI_DIR}/${PATRONI_DIR//\//\\/}/g" ${PATRONI_CONFIG_FILE}
sed -i "s/\${PG_BASE}/${PG_BASE//\//\\/}/g" ${PATRONI_CONFIG_FILE}
sed -i "s/\${PG_DATA_DIR}/${PG_DATA_DIR//\//\\/}/g" ${PATRONI_CONFIG_FILE}
sed -i "s/\${PG_PORT}/${PG_PORT}/g" ${PATRONI_CONFIG_FILE}
sed -i "s/\${PG_BIN_DIR}/${PG_BIN_DIR//\//\\/}/g" ${PATRONI_CONFIG_FILE}
sed -i "s/\${PG_WAL_ARCH_DIR}/${PG_WAL_ARCH_DIR//\//\\/}/g" ${PATRONI_CONFIG_FILE}
sed -i "s/\${REPL_PASSWORD}/${REPL_PASSWORD}/g" ${PATRONI_CONFIG_FILE}
sed -i "s/\${SUPERUSER_PASSWORD}/${SUPERUSER_PASSWORD}/g" ${PATRONI_CONFIG_FILE}
sed -i "s/\${REWIND_PASSWORD}/${REWIND_PASSWORD}/g" ${PATRONI_CONFIG_FILE}


chown postgres:postgres ${PATRONI_CONFIG_FILE}

# 6. 创建systemd服务文件
echo "Step 6: Creating systemd service..."
cat > /etc/systemd/system/patroni.service <<EOF
[Unit]
Description=Patroni
After=network.target etcd.service
Wants=etcd.service

[Service]
Type=simple
User=postgres
Group=postgres
Environment="TZ=Asia/Shanghai"
Environment="PYTHONUNBUFFERED=1"

ExecStart=/usr/local/bin/patroni ${PATRONI_CONFIG_FILE}
ExecReload=/bin/kill -HUP \$MAINPID
ExecStop=/bin/kill -TERM \$MAINPID

Restart=on-failure
RestartSec=10
TimeoutStartSec=120
TimeoutStopSec=60

LimitNOFILE=65536

StandardOutput=null
StandardError=journal
SyslogIdentifier=patroni

[Install]
WantedBy=multi-user.target
EOF

# 7. 启动Patroni服务
echo "Step 7: Starting Patroni service..."
systemctl daemon-reload
systemctl enable patroni
systemctl start patroni

# 8. 等待并检查状态
echo "Step 8: Waiting for Patroni to initialize (15 seconds)..."
sleep 15

echo "=== Patroni service status ==="
systemctl status patroni --no-pager

# 检查数据目录是否已创建
if [ -d "${PG_DATA_DIR}/base" ]; then
    echo "? PostgreSQL data directory initialized successfully"
else
    echo "? PostgreSQL data directory not initialized, check logs:"
    echo "  tail -f ${PATRONI_DIR}/patroni.log"
fi

echo "=== Installation on ${CURRENT_NODE_NAME} completed ==="
echo "To check cluster status, run: patronictl -c ${PATRONI_CONFIG_FILE} list"

 

4,安装示例

主节点上安装的日志以及示例,其他节点类似:需要注意的是,etcd需要至少一个其他节点安装后才能正常启动

image

 

Ubuntu12 视角
image

Ubuntu13视角略

 

posted on 2026-05-19 11:29  MSSQL123  阅读(66)  评论(0)    收藏  举报