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,这里无法提现
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需要至少一个其他节点安装后才能正常启动

Ubuntu12 视角
Ubuntu13视角略
浙公网安备 33010602011771号