etcd扩容这件事吧,说起来就是几步命令的事,但真上手的时候你会发现——坑一个接一个。我当初在生产环境扩容的时候,因为一个配置参数搞错,折腾了两个小时才恢复。今天就把我踩过的坑、总结的经验一次性给你讲清楚。

本文基于 etcd v3.5.x 实测,操作系统 CentOS 7+/Rocky Linux,Kubernetes v1.24+。

读完本文你将能:

  • 理解etcd扩容的核心原理(搞懂原理才能不慌)
  • 安全地从3节点扩容到5节点,保证集群不抖动
  • 遇到常见报错能快速定位并修复

一、什么时候需要扩容?先判断清楚

别动不动就扩容,先看看是不是真的有必要。以下是典型的扩容信号:

现象

判断阈值

leader CPU持续 >80%

需要分摊写压力

proposals pending持续高位

etcdctl endpoint status看raft term正常但pending高

db size > 30GB

单节点磁盘/内存压力大

需要跨AZ部署提升容错

从3节点扩到5节点,允许2台故障

如果只是读请求多,考虑加follower节点就够了;如果是写压力大,加节点其实反而会降低写性能——因为每次写入都需要多数派确认,节点越多网络开销越大。

(顺便提一嘴,etcd官方推荐的节点数是3、5、7,偶数节点虽然也能跑,但容错能力跟少一台的奇数集群一样,还多浪费一台机器,别干这种事。)

二、扩容前必须搞懂的几件事

2.1 动态成员变更是怎么工作的?

etcd用Raft协议做动态成员变更,你不需要停服。流程是这样的:

  1. 你在任意节点执行 member add
  2. Leader把新成员信息提交给所有现有成员
  3. 新成员以 learner模式加入(v3.5+默认),先同步snapshot和WAL日志
  4. 追平之后自动升级为voting member

关键点:数据同步时间取决于当前db大小和网络,几个GB的话几分钟,几十GB可能要几十分钟甚至几个小时。这段时间新节点不能投票,但集群正常服务。

2.2 前提条件(漏一个都会出问题)
  • etcd版本完全一致 —— 这个太重要了,版本不一致会导致协议不兼容,集群直接崩。建议用 etcdctl version 确认一下。
  • TLS证书用同一套CA签发 —— 新节点的证书必须由现有集群的CA签,别自己生成一套新的。
  • 机器间网络互通 —— 2379(客户端端口)和2380(集群内部通信端口)都要放通。
  • 新节点data-dir为空 —— /var/lib/etcd 目录必须是空的,有旧数据会残留之前的成员信息,导致加入失败。
  • 集群当前健康 —— 扩容前一定要检查。

三、实战演示:从3节点扩容到5节点

假设现有集群是这样的:

  • etcd01: 10.0.1.10
  • etcd02: 10.0.1.11
  • etcd03: 10.0.1.12

新增:

  • etcd04: 10.0.1.13
  • etcd05: 10.0.1.14
步骤1:备份!备份!备份!

别嫌我啰嗦,这是救命的:

export ETCDCTL_API=3
etcdctl snapshot save /backup/etcd-snapshot-$(date +%Y%m%d_%H%M%S).db \
  --endpoints=https://10.0.1.10:2379,https://10.0.1.11:2379,https://10.0.1.12:2379 \
  --cacert=/etc/etcd/ssl/ca.pem \
  --cert=/etc/etcd/ssl/etcd.pem \
  --key=/etc/etcd/ssl/etcd-key.pem

备份完验证一下快照是否完好:

etcdctl snapshot status /backup/etcd-snapshot-xxx.db --write-out=table
步骤2:检查集群状态(这是扩容的“体检”)
export ETCDCTL_API=3
export ETCDCTL_ENDPOINTS=https://10.0.1.10:2379,https://10.0.1.11:2379,https://10.0.1.12:2379
export ETCDCTL_CACERT=/etc/etcd/ssl/ca.pem
export ETCDCTL_CERT=/etc/etcd/ssl/etcd.pem
export ETCDCTL_KEY=/etc/etcd/ssl/etcd-key.pem

# 看所有成员
etcdctl member list --write-out=table

# 看每个端点的健康状态
etcdctl endpoint health

# 看详细状态,重点看"IS LEADER"和"RAFT TERM"
etcdctl endpoint status --write-out=table

预期输出中,所有成员应该是 healthy: true,RAFT TERM应该一致。

步骤3:添加etcd04(第一个新节点)

3.1 在现有集群的任意节点上添加成员:

etcdctl member add etcd04 --peer-urls=https://10.0.1.13:2380

输出会类似:

Member 8e9e05c5b3e4a1b7 added to cluster 3a2b1c4d5e6f7890

ETCD_NAME="etcd04"
ETCD_INITIAL_CLUSTER="etcd01=https://10.0.1.10:2380,etcd02=https://10.0.1.11:2380,etcd03=https://10.0.1.12:2380,etcd04=https://10.0.1.13:2380"
ETCD_INITIAL_ADVERTISE_PEER_URLS="https://10.0.1.13:2380"
ETCD_INITIAL_CLUSTER_STATE="existing"

输出里的这几行配置别直接复制用,实际配置要根据你的环境改

3.2 在etcd04节点上启动etcd服务:

小心,这里容易搞错。关键是 ETCD_INITIAL_CLUSTER_STATE 要设为 existing,要是设成 new 它会尝试创建新集群,会跟现有集群ID冲突。

配置示例(放到 /etc/etcd/etcd.conf 或 systemd service文件里):

ETCD_NAME=etcd04
ETCD_DATA_DIR=/var/lib/etcd
ETCD_LISTEN_PEER_URLS=https://0.0.0.0:2380
ETCD_LISTEN_CLIENT_URLS=https://0.0.0.0:2379
ETCD_INITIAL_ADVERTISE_PEER_URLS=https://10.0.1.13:2380
ETCD_ADVERTISE_CLIENT_URLS=https://10.0.1.13:2379
ETCD_INITIAL_CLUSTER="etcd01=https://10.0.1.10:2380,etcd02=https://10.0.1.11:2380,etcd03=https://10.0.1.12:2380,etcd04=https://10.0.1.13:2380"
ETCD_INITIAL_CLUSTER_STATE=existing
ETCD_INITIAL_CLUSTER_TOKEN=your-cluster-token

启动并检查:

systemctl start etcd
systemctl status etcd

3.3 验证etcd04是否成功加入:

etcdctl member list

应该能看到etcd04,状态从unstarted慢慢变成healthy。

步骤4:等数据同步完成

加入learner后,新节点开始同步数据。可以用 etcdctl endpoint status 看db大小,或者看 member list 里节点从 unstarted 变成 started。通常几分钟到半小时不等,耐心等待。

步骤5:重复步骤3-4,添加etcd05

一样的流程,把etcd04的信息加到initial-cluster里。

步骤6:最终验证
# 所有成员列出
etcdctl member list --write-out=table

# 逐个endpoint检查健康
etcdctl endpoint health --cluster

# 检查选主情况
etcdctl endpoint status --write-out=table --cluster

5个节点都healthy,RAFT TERM一致,就恭喜你扩成功了。

四、扩容失败怎么办?常见问题的诊断与修复

坑1:request cluster ID mismatch 报错

这就是我遇到的那个大坑。集群ID不匹配,通常是initial-cluster-state设成了new。日志里会看到:

rafthttp: request cluster ID mismatch

解决方法:

  1. 从集群中移除节点:etcdctl member remove <member_id>
  2. 清空 /var/lib/etcd/member 目录
  3. 把配置里的 initial-cluster-state 改成 existing,重新启动
坑2:failed to find member 或启动后不加入集群

新节点的数据目录里有旧的成员信息。根本原因是etcd启动时会优先读本地的成员列表,而不是用你配的initial-cluster。

解决方法:

  • 停止etcd服务,删除 /var/lib/etcd/member 目录,清空data-dir,重启
  • 确保配置的initial-cluster里包含所有现有成员
坑3:新节点一直unhealthy,无法同步数据

检查网络端口(2379/2380),以及证书是否正确。常见错误:

  • 证书过期:openssl x509 -in /etc/etcd/ssl/etcd.pem -noout -dates
  • 防火墙挡了2380端口:telnet 10.0.1.13 2380 不通
坑4:操作完才发现版本不一致

这个太致命了。不同版本之间的内部协议可能不兼容(protobuf schema变化)。扩容前务必核对

etcdctl version | head -1

所有节点的etcd version必须一致。

五、彩蛋:两个你可能没听过但很好用的技巧

  1. learner模式(v3.5+) 可以查询新节点是否还在同步中:
etcdctl member list | grep learner

learner状态下它还没投票权,但已经在拉数据了,不怕影响quorum。

  1. 如果你想控制learner升级为voting的时机,可以手动触发:
etcdctl member promote <member-id>

不过正常情况下集群会自动处理,不用你操心中。

  1. 关于Kubespray环境:如果是用Kubespray部署的K8s集群,不建议直接etcdctl操作,最好按官方指南跑 scale.yml 来加节点,否则下次重新部署会被覆盖掉。

六、要不要扩容?我的经验小结

  • 读多写少:加follower有用
  • 写多:加节点反而慢,不如拆分集群或优化写入量
  • 容错:3→5才能多容忍1台故障,5→7也是一样的道理,够用就好
  • 禁止:扩容期间做缩容或集群元数据变更

你有没有在etcd扩容时踩过什么我没提到的坑?评论区聊聊,说不定能帮到更多人。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐