声明:本文档记录的方案已不再是作者当前在生产环境中使用的方案。本文仅作历史演进参考。
在已有三节点 ZeroTier 高可用集群(K3s control-plane + Patroni PostgreSQL)的基础上,新增一台 WSL2 主机作为第四节点:部署 K3s Agent 工作节点与 Patroni 管理的 PostgreSQL 副本,通过 ZeroTier 网络
88c5b1f3396eaea0组网并固定 IP 为10.0.0.20。
一、背景与集群拓扑
现有集群由三台 Debian 主机组成,通过 ZeroTier 组成 10.0.0.0/24 二层内网:
| 主机名 | 内网 IP | 角色 |
|---|---|---|
| hyperbola-server | 10.0.0.10 | Patroni Leader、K3s control-plane |
| hyperbola-txy | 10.0.0.30 | Patroni Replica、K3s control-plane |
| hyperbola-aly | 10.0.0.40 | Patroni Replica、K3s control-plane |
本次新增一台 WSL2 主机(Windows 11 + Debian 13),作为第四节点:
| 主机名 | 内网 IP | 角色 |
|---|---|---|
| HyQAQ-WSL | 10.0.0.20 | K3s Agent(工作节点)、Patroni Replica |
备份节点定位(不参与 HA):
10.0.0.20不安装 Keepalived / HAProxy,不参与 VRRP 竞选,永远不会持有 VIP;- Patroni 配置
nofailover: true,永不成为数据库 Leader; - 三台主机的 HAProxy / Keepalived 配置与后端均无需改动,
10.0.0.20不加入任何 HA 后端。
目标:
- WSL2 通过 ZeroTier 加入内网,固定地址
10.0.0.20; - 部署 K3s Agent 加入现有集群,作为工作节点承载业务负载;
- 部署 PostgreSQL + Patroni 加入现有 HA 集群,作为普通副本(Replica),通过现有 etcd(端口
3379)协调。
二、环境信息
- 操作系统:Windows 11 + WSL2 Debian 13 (trixie)
- WSL 网络:桥接模式静态 IP
192.168.86.22(宿主局域网)+ ZeroTier 内网 IP10.0.0.20 - PostgreSQL 版本:17
- Patroni 版本:
apt install patroni python3-etcd3 python3-etcd - ZeroTier 网络 ID:
88c5b1f3396eaea0,接口名一般为ztpp6n6xmz
WSL2 桥接模式、静态 IP、systemd 等前置配置参考《WSL2 桥接模式实战:配置静态 IP、开启 IPv6 与 Systemd 避坑指南》。
三、WSL 环境准备
3.1 启用 systemd(关键)
WSL2 默认不使用 systemd 作为 init 系统,而 K3s 和 Patroni 都依赖 systemd 管理服务。编辑 /etc/wsl.conf:
sudo vim /etc/wsl.conf[boot]
systemd=true
[network]
hostname = HyQAQ-WSL
generateHosts = false
generateResolvConf = false在 PowerShell 中执行 wsl --shutdown,重新进入后验证:
ps -p 1 -o comm=
# 输出 systemd 即为成功3.2 确认静态 IP 与 DNS
ip a show eth0
# 应看到 192.168.86.22/24 静态地址
cat /etc/resolv.conf
# nameserver 223.5.5.5
# nameserver 114.114.114.114四、加入 ZeroTier 网络
4.1 安装 ZeroTier
curl -s https://install.zerotier.com | sudo bash
sudo systemctl enable --now zerotier-one4.2 加入网络并固定 IP
sudo zerotier-cli join 88c5b1f***6eaea0在 ZeroTier Central 管理后台:
- 找到网络
88c5b1f***6eaea0,在 Members 列表中授权新节点; - 在 Managed Addresses 中将该节点固定为
10.0.0.20; - 确认网络已有
10.0.0.0/24的路由段。
查看接口与 IP:
sudo zerotier-cli listnetworks
# ztpp6n6xmz 88c5b1f3396eaea0 ... 10.0.0.20/24 ...
ip addr show ztpp6n6xmz4.3 验证与集群互通
ping -c 4 10.0.0.10
ping -c 4 10.0.0.30
ping -c 4 10.0.0.40全部通顺则内网就绪。
五、部署 K3s Agent
5.1 获取集群 Token
在任一 Server 节点(如 10.0.0.10)执行:
sudo cat /var/lib/rancher/k3s/server/node-token记录输出的 token 值(形如 K10xxx::server:xxx)。
5.2 安装 Agent(环境变量方式)
curl -sfL https://get.k3s.io | K3S_URL=https://10.0.0.10:6443 K3S_TOKEN="K10xxx::server:xxx" K3S_NODE_IP=10.0.0.20 K3S_NODE_EXTERNAL_IP=10.0.0.20 K3S_FLANNEL_IFACE=ztpp6n6xmz sh -参数说明:
K3S_URL:指向现有集群任一 Server 的 API 地址;K3S_TOKEN:与集群一致的认证令牌(从 5.1 获取);K3S_NODE_IP/K3S_NODE_EXTERNAL_IP:使用 ZeroTier 内网 IP;K3S_FLANNEL_IFACE:必须与集群其他节点保持一致(集群使用host-gw后端,绑定ztpp6n6xmz),否则 Pod 跨节点通信异常。
安装脚本会读取环境变量创建 k3s-agent.service,自动以 agent 模式运行(设置 K3S_URL 后默认即为 agent,无需指定命令)。
5.3 验证节点加入
在任意 Server 节点执行:
sudo kubectl get nodes应看到新节点处于 Ready 状态,角色为 <none>(工作节点):
NAME STATUS ROLES AGE VERSION
hyperbola-server Ready control-plane,master ...
hyperbola-txy Ready control-plane,master ...
hyperbola-aly Ready control-plane,master ...
hyqaq-wsl Ready <none> 1m v1.xx.x六、部署 etcd、PostgreSQL 与 Patroni
6.1 部署 etcd(第 4 个成员)
Patroni 依赖 etcd 作为 DCS 存储集群状态,20 节点需要本地运行 etcd 并作为第 4 个成员加入现有集群。
安装:
sudo apt update
sudo apt install -y etcd-server etcd-client在现有 etcd 集群任一节点(如 10.0.0.10)注册新成员:
etcdctl --endpoints=http://10.0.0.10:3379 member add etcd-20 --peer-urls=http://10.0.0.20:3380编辑 /etc/default/etcd(以 10.0.0.20 为例,与现有 10.0.0.10 配置对齐:peer 端口 3380、token pg-cluster-token、启用 v2 API):
ETCD_NAME="etcd-20"
ETCD_INITIAL_CLUSTER="etcd-10=http://10.0.0.10:3380,etcd-30=http://10.0.0.30:3380,etcd-40=http://10.0.0.40:3380,etcd-20=http://10.0.0.20:3380"
ETCD_LISTEN_CLIENT_URLS="http://10.0.0.20:3379,http://127.0.0.1:3379"
ETCD_LISTEN_PEER_URLS="http://10.0.0.20:3380"
ETCD_ADVERTISE_CLIENT_URLS="http://10.0.0.20:3379"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://10.0.0.20:3380"
ETCD_ENABLE_V2="true"
ETCD_DATA_DIR="/var/lib/etcd/default"
ETCD_INITIAL_CLUSTER_STATE="existing"
ETCD_INITIAL_CLUSTER_TOKEN="pg-cluster-token"启动并验证:
sudo systemctl enable --now etcd
etcdctl --endpoints=http://10.0.0.20:3379 endpoint health
etcdctl --endpoints=http://10.0.0.10:3379,http://10.0.0.20:3379 member list其他 3 台主机的配合配置
20 作为第 4 个 etcd 成员加入后,10.0.0.10、10.0.0.30、10.0.0.40 三台主机的 etcd 与 Patroni 配置也需要更新:
1. etcd ETCD_INITIAL_CLUSTER 追加 etcd-20
member add 后现有成员虽然能通过 etcd API 实时感知新成员,但为了重启后静态配置与当前成员一致,建议在三台主机的 /etc/default/etcd 中将 etcd-20 加入 ETCD_INITIAL_CLUSTER:
ETCD_INITIAL_CLUSTER="etcd-10=http://10.0.0.10:3380,etcd-30=http://10.0.0.30:3380,etcd-40=http://10.0.0.40:3380,etcd-20=http://10.0.0.20:3380"2. Patroni etcd3.hosts 追加 10.0.0.20:3379
在三台主机的 /etc/patroni/config.yml 中,将 etcd3.hosts 更新为(注意后端是 etcd3:,不是 etcd:):
etcd3:
hosts: 10.0.0.10:3379,10.0.0.30:3379,10.0.0.40:3379,10.0.0.20:3379然后重载 Patroni(无需重启,改配置后 patronictl reload 或 systemctl reload patroni):
sudo systemctl reload patroni3. HAProxy 与 Keepalived
见第七节——三台主机的 HAProxy / Keepalived 无需任何改动,10.0.0.20 不加入任何后端。
6.2 安装 PostgreSQL 17
sudo apt install -y postgresql-176.3 安装 Patroni
直接使用 Debian 13 的 APT 包即可,patroni 加上 python3-etcd3、python3-etcd 依赖足够:
sudo apt install -y patroni python3-etcd3 python3-etcd验证 etcd 驱动可用:
python3 -c "from patroni.dcs.etcd import Etcd; print('etcd driver OK')"6.4 配置 Patroni
编辑 /etc/patroni/config.yml:
---
scope: pg-cluster
namespace: /db/
name: pg-20
restapi:
listen: 10.0.0.20:8008
connect_address: 10.0.0.20:8008
etcd3:
hosts: 10.0.0.10:3379,10.0.0.30:3379,10.0.0.40:3379,10.0.0.20:3379
bootstrap:
dcs:
ttl: 30
loop_wait: 10
retry_timeout: 10
maximum_lag_on_failover: 1048576
master_start_timeout: 300
synchronous_mode: true
synchronous_mode_strict: false
postgresql:
use_pg_rewind: true
parameters:
max_connections: 200
shared_buffers: 256MB
wal_level: replica
wal_keep_size: 128
max_wal_senders: 10
max_replication_slots: 10
synchronous_commit: "remote_apply"
synchronous_standby_names: "*"
pg_hba:
- local all all trust
- host all all 0.0.0.0/0 md5
- host replication all 0.0.0.0/0 md5
postgresql:
listen: 10.0.0.20:5432
connect_address: 10.0.0.20:5432
data_dir: /var/lib/postgresql/17/main
conf_dir: /etc/postgresql/17/main
bin_dir: /usr/lib/postgresql/17/bin
pgpass: /tmp/pgpass
authentication:
replication:
username: replicator
password: '@512710&Sql'
superuser:
username: postgres
password: '@512710&Sql'
parameters:
unix_socket_directories: '/var/run/postgresql'
tags:
nofailover: true # 备份节点,永不参与选主,不能成为 Leader
clonefrom: false
failover-priority: 10配置要点:
etcd3:后端(关键,不要写成etcd:):与现有集群节点完全一致,Patroni 通过 etcd v3 API 读取 DCS。若误用etcd:(v2 API),会读不到leader/config/status等键,导致 Patroni 认为集群未初始化、一直卡在waiting for leader to bootstrap;etcd3.hosts包含现有三节点 etcd 与本机 etcd(10.0.0.20:3379),本地有 etcd 后 Patroni 优先连本机,故障时可切换到其他节点;bootstrap.dcs需与集群其他节点逐字段一致(含retry_timeout),这是 Patroni DCS 初始化必需的;加入已有集群时该段用于对齐配置而非重新初始化;postgresql.conf_dir指向/etc/postgresql/17/main(Debian 包路径),缺失会导致 PG 启动异常;name必须唯一,这里是pg-20;restapi/postgresql的监听地址均使用 ZeroTier 内网 IP10.0.0.20,确保其他节点可达;nofailover: true:标记为永不参与选主,即使其他节点全部宕机,10.0.0.20也不会被提升为 Leader;failover-priority: 10设为较低值,正常情况下它只是普通副本,不会争夺 Leader。
6.5 启动 Patroni
务必清空数据目录,加入集群时会自动从 Leader 流复制:
sudo systemctl stop patroni postgresql 2>/dev/null
sudo pkill -9 -u postgres
sudo rm -rf /var/lib/postgresql/17/main
sudo -u postgres mkdir -p /var/lib/postgresql/17/main
sudo chmod 700 /var/lib/postgresql/17/main # PG 要求 0700/0750,否则启动失败
sudo systemctl daemon-reload
sudo systemctl enable --now patroni七、接入 HA:三台主机无需任何调整
10.0.0.20 的定位是纯备份节点:不承接任何业务流量,不能成为数据库 Leader,也不能成为 VIP 节点。因此:
- HA/Keepalived 仍由原有三台主机(
10.0.0.10、10.0.0.30、10.0.0.40)承担,10.0.0.20不安装 Keepalived 与 HAProxy,不参与 VRRP 竞选; - 三台主机的 HAProxy 后端完全不动,
pg_write、pg_read、k3s_servers均不加入10.0.0.20; - 三台主机的 Keepalived 配置完全不动,VIP
10.0.0.100只会在三台之间漂移,永远不会到10.0.0.20。
因此本步骤三台主机无需任何改动,保持原有 HA 配置即可。
八、验证集群状态
8.1 PostgreSQL 集群
在任意节点执行:
sudo patronictl -c /etc/patroni/config.yml list输出应包含新节点:
+ Cluster: pg-cluster (...) ----+----+-----------+------------------------+
| Member | Host | Role | State | TL | Lag in MB | Tags |
+--------+-----------+-----------+-----------+----+-----------+------------+
| pg-10 | 10.0.0.10 | Leader | running | 1 | | prio: 100 |
| pg-30 | 10.0.0.30 | Replica | streaming | 1 | 0 | prio: 50 |
| pg-40 | 10.0.0.40 | Replica | streaming | 1 | 0 | prio: 0 |
| pg-20 | 10.0.0.20 | Replica | streaming | 1 | 0 | prio: 10 |
+--------+-----------+-----------+-----------+----+-----------+------------+pg-20 状态为 streaming、延迟 0 MB 即为成功加入。
8.2 K3s 集群
sudo kubectl get nodes
sudo kubectl get pods -A -o wide确认 hyqaq-wsl 节点 Ready,并可调度工作负载。
8.3 复制验证
在主库写入数据,副本应实时同步:
# 在 10.0.0.10 主库
psql -h 10.0.0.10 -U postgres -c "CREATE TABLE IF NOT EXISTS test(id int); INSERT INTO test VALUES (1);"
# 在 10.0.0.20 副本
psql -h 10.0.0.20 -U postgres -c "SELECT * FROM test;"九、踩坑记录
-
WSL 必须启用 systemd
若不设置systemd=true,K3s 与 Patroni 都无法以 systemd 服务方式运行,systemctl会报System has not been booted with systemd as init system。 -
Patroni 依赖 ZeroTier 接口的启动顺序
与集群中 etcd 的问题类似,若 Patroni/PostgreSQL 绑定10.0.0.20而 ZeroTier 尚未分配该 IP,会报cannot assign requested address。在服务文件中添加依赖:After=zerotier-one.service network-online.target Requires=zerotier-one.service -
Flannel 接口必须与集群一致
K3s Agent 的flannel-iface若与现有节点不同(集群使用host-gw后端),Pod 间通信异常。 -
WSL 存在双 IP
桥接静态 IP(192.168.86.22)与 ZeroTier IP(10.0.0.20)并存。集群内一律使用10.0.0.20,Patroni 的connect_address不能写成192.168.86.22,否则其他节点无法回连。 -
PostgreSQL 使用 0.0.0.0 监听
若需同时接受桥接网段与 ZeroTier 网段的连接,可让 PostgreSQL 监听0.0.0.0,但 Patroni 的connect_address仍必须指向10.0.0.20。 -
etcd 对等端口配置错误导致 Cluster ID Mismatch
etcd-20加入集群时报错,member list中该节点一直处于unstarted。故障原因有两层:- 对等端口配置错误:集群初始元数据中记录的
etcd-20节点对等通信端口为默认的2380,而实际配置文件使用的是3380,导致节点无法与其他成员建立内部通信。 - 集群 ID 不匹配(Cluster ID Mismatch):由于上述网络不通,
etcd-20启动时未能成功加入现有集群,而是基于本地残留的旧数据,自行初始化了一个全新的单节点集群。这导致它与其他三个节点拥有完全不同的集群 ID,从而被集群拒绝。
恢复步骤:
# 1. 在健康节点上移除错误成员 etcdctl --endpoints=http://10.0.0.10:3379 member remove <错误memberID> # 2. 重新添加成员,指定正确的对等地址(端口 3380) etcdctl --endpoints=http://10.0.0.10:3379 member add etcd-20 --peer-urls=http://10.0.0.20:3380 # 3. 在故障节点(10.0.0.20)上停止服务并清理旧数据 sudo systemctl stop etcd sudo rm -rf /var/lib/etcd/default # 4. 确认配置中 ETCD_INITIAL_CLUSTER_STATE="existing",重启并验证 sudo systemctl start etcd etcdctl --endpoints=http://10.0.0.20:3379 endpoint health etcdctl --endpoints=http://10.0.0.10:3379,http://10.0.0.20:3379 member listmember list中该节点状态应变为started,集群恢复健康。 - 对等端口配置错误:集群初始元数据中记录的
-
二次踩坑:AI 重构步骤时”掐头去尾”,遗漏关键配置导致 Cluster ID Mismatch 复现
明明旧文档踩坑记录里已经写过 Cluster ID Mismatch,实际部署时还是再次踩坑,且根因不止一个。原因是:用 AI 重构部署步骤时,将配置”掐头去尾”了——只保留了看起来”核心”的部分,遗漏了与现有集群保持一致的关键项。本次复现的实际故障链(三层):
-
etcd 旧数据残留(Cluster ID Mismatch):20 的 etcd 数据目录
/var/lib/etcd/default里残留了之前以错误端口初始化的单节点集群数据(cluster-id16f1bc0c8e86d48e),与主集群 cluster-id(7667324251917980557)不符。etcd 启动后恢复本地旧数据,被主集群以rejected Raft message to mismatch member拒绝,随后正常退出(日志是Deactivated successfully,非崩溃,极具迷惑性)。 -
Patroni 误用
etcd:后端而非etcd3::这是最隐蔽的一层。集群其他节点(如 txy)的配置用的是etcd3:(Patroni 走 etcd v3 API),而重构后的 20 配置用的是etcd:(Patroni 默认走 etcd v2 API)。主集群的 etcd 虽开启ETCD_ENABLE_V2="true",但 v2 API 下/db/pg-cluster/只有members和initialize,读不到leader、config、status。于是 20 的 Patroni 能连上 etcd、却认为集群从未初始化,一直卡在waiting for leader to bootstrap(Lock owner: None),甚至报出与主集群不同的 cluster ID。 -
数据目录权限(附带):重建
/var/lib/postgresql/17/main时默认权限755,PostgreSQL 要求0700/0750,导致 PG 启动FATAL: data directory has invalid permissions。
排查过程关键命令:
# 1. 先看 etcd 成员状态 —— 发现 20 是 unstarted etcdctl --endpoints=http://10.0.0.10:3379,http://10.0.0.30:3379,http://10.0.0.40:3379 member list # → db828958631541e5, unstarted, , http://10.0.0.20:3380 # 2. 看 20 的 etcd 启动日志 —— 发现本地旧数据 + mismatch journalctl -u etcd -n 30 --no-pager # → server has been already initialized / cluster-id 16f1bc0c... # → rejected Raft message to mismatch member / Deactivated successfully # 3. 对比正常节点(txy)的 Patroni 配置 —— 发现 etcd3: vs etcd: sudo cat /etc/patroni/config.yml # 正常节点用 etcd3: # 4. v2 vs v3 视角对比 etcd 数据 —— 确认 v2 读不到 leader/config/status etcdctl --endpoints=http://10.0.0.20:3379 get /db --prefix --keys-only # v3,数据完整 ETCDCTL_API=2 etcdctl --endpoints=http://10.0.0.20:3379 ls /db/pg-cluster/ # v2,只有 members/initialize修复步骤(按顺序):
# 1. 停服务 sudo systemctl stop patroni sudo systemctl stop etcd # 2. 清空 etcd 旧数据(Cluster ID Mismatch 根因) sudo mv /var/lib/etcd/default /var/lib/etcd/default.bak sudo mkdir -p /var/lib/etcd/default && sudo chown etcd:etcd /var/lib/etcd/default # 3. 启动 etcd,确认成员 started sudo systemctl start etcd etcdctl --endpoints=http://10.0.0.20:3379 endpoint health # 4. 修正 Patroni 配置:etcd: → etcd3:,补齐 bootstrap.dcs / conf_dir / retry_timeout sudo tee /etc/patroni/config.yml << 'EOF' # ... 与正常节点完全对齐:etcd3: + bootstrap.dcs + postgresql.conf_dir 等 EOF # 5. 修正数据目录权限(PG 要求 0700/0750) sudo chmod 700 /var/lib/postgresql/17/main sudo chown -R postgres:postgres /var/lib/postgresql/17/main # 6. 启动 patroni,从 Leader 自动 pg_basebackup 加入 sudo systemctl start patroni sudo patronictl -c /etc/patroni/config.yml list # → pg-20 Replica running, TL 24, Lag 0 MB -
-
集群故障转移后副本 timeline 冲突:
requested timeline is not a child of this server's history
现象:主集群发生故障转移(原 Leader pg-10 宕机,Leader 漂移到 pg-40)后,备份节点pg-20的 Patroni 反复start failed,日志报:FATAL: requested timeline 25 is not a child of this server's history DETAIL: Latest checkpoint is at 0/15000028 on timeline 24, but in the history of the requested timeline, the server forked off from that timeline at 0/140002B0.原因:集群在 failover 时发生了 timeline 分支(新 Leader 在旧 timeline 上分叉)。
pg-20本地残留旧 timeline 的历史记录,与新的复制时间线不匹配,无法直接从旧位置继续流复制。解决:清空数据目录,让 Patroni 从当前 Leader 重新
pg_basebackup全量同步:sudo systemctl stop patroni sudo rm -rf /var/lib/postgresql/17/main sudo -u postgres mkdir -p /var/lib/postgresql/17/main sudo chmod 700 /var/lib/postgresql/17/main sudo systemctl start patroni # 日志应出现:trying to bootstrap from leader 'pg-XX' → in progress sudo patronictl -c /etc/patroni/config.yml list # → pg-20 Replica streaming, 0 lag
十、总结
通过 ZeroTier,WSL2 主机顺利加入了既有高可用集群:
- K3s Agent 作为工作节点,接入现有控制面,可承载业务负载;
- Patroni + PostgreSQL 作为 Replica,通过现有 etcd 协调并实时从 Leader 流复制,
nofailover: true+failover-priority低位确保其永不成为 Leader; - 接入 HA:三台主机的 HAProxy / Keepalived 完全不变,
10.0.0.20不加入任何后端、不安装 HA/Keepalived,永不持有 VIP。
至此,集群从三节点扩展为四节点。10.0.0.20 作为纯备份节点定位明确:不承接任何业务流量,不参与数据库选主,也不参与 VIP 竞选,仅在主链路整体故障时手动拉起恢复,故障时不影响主链路高可用。
分享文章
生成精美分享图或复制链接,与更多人分享本文。
继续阅读
最后更新于 ,距今已过 84 天
部分内容可能已过时