部署与高可用
集群规划
单节点 etcd 挂了就全挂,生产必须做集群。集群规模、磁盘、网络这三件事在部署前就要定下来,后面再改成本很高。
节点数量
Raft 靠多数派工作,所以节点数必须是奇数:
| 规模 | 多数派 | 可挂节点 | 适用场景 |
|---|---|---|---|
| 3 | 2 | 1 | 绝大多数生产集群,推荐 |
| 5 | 3 | 2 | 规模大、跨可用区、容错要求高 |
| 7 | 4 | 3 | 很少见,复制成本高,收益递减 |
3 节点是性价比最高的选择:能扛一台故障,写延迟可控,运维复杂度也低。5 节点适合跨三个可用区部署(每个区至少一个,再多一个冗余),但要接受更长的写路径。7 节点以上基本没必要,每次写要等 4 个成员落盘,延迟和带宽都不划算。
2 节点多数派是 2,挂一台就写不进去;4 节点多数派是 3,和 3 节点一样只能挂一台,却多了一台的开销。偶数节点除了浪费机器,没有任何好处。
硬件与网络
etcd 对 CPU 和内存要求不高,但对磁盘 fsync 延迟极其敏感:
- 磁盘:必须 SSD 或 NVMe,最好独占一块盘,不要和日志、镜像、业务数据混用。WAL 的每次写都要 fsync,机械盘或云盘 IOPS 不够会直接拖垮整个控制面。
- CPU / 内存:2 到 4 核、8 GB 内存起步。db 大小控制在 8 GB 以内,内存至少要能放下索引和热数据。
- 网络:成员之间 RTT 最好在 10ms 以内,至少同区域。跨地域部署会让选举和日志复制变得很不稳定。
- 时间同步:所有节点开 NTP/chrony。Raft 的选举超时和证书校验都依赖时钟大致准确。
生产环境建议把 etcd 部署在独立的三台机器上,和业务负载隔离。K8s 的 kubeadm 集群里 etcd 和 control plane 混部是常态,但至少保证控制面节点不跑重 IO 业务。
端口与目录
| 端口 | 用途 | 访问范围 |
|---|---|---|
| 2379 | 客户端 API | apiserver、etcdctl、运维工具 |
| 2380 | 成员间 peer 通信 | 仅集群成员之间 |
| 2381 | metrics(kubeadm 默认) | 监控系统 |
目录规划建议:
/etc/etcd/ # 配置和证书
/var/lib/etcd/ # 数据目录,独立磁盘挂载点
/var/backups/etcd/ # 快照备份
三节点部署
下面以 10.0.0.11、10.0.0.12、10.0.0.13 三台 Ubuntu 22.04 为例,成员名分别是 etcd-01、etcd-02、etcd-03。
配置文件
先在三台机器上装好二进制(步骤见上一篇),然后写 /etc/etcd/etcd.conf.yml。以 etcd-01 为例:
name: etcd-01
data-dir: /var/lib/etcd
# 客户端监听:本机 + 内网地址,方便 apiserver 和 etcdctl 访问
listen-client-urls: http://10.0.0.11:2379,http://127.0.0.1:2379
advertise-client-urls: http://10.0.0.11:2379
# 集群内部通信
listen-peer-urls: http://10.0.0.11:2380
initial-advertise-peer-urls: http://10.0.0.11:2380
# 初始集群,三台必须完全一致
initial-cluster: etcd-01=http://10.0.0.11:2380,etcd-02=http://10.0.0.12:2380,etcd-03=http://10.0.0.13:2380
initial-cluster-state: new
initial-cluster-token: etcd-cluster-prod
# 生产建议打开自动压缩,细节见运维篇
auto-compaction-mode: periodic
auto-compaction-retention: 1h
quota-backend-bytes: 8589934592
logger: zap
log-level: info
etcd-02 和 etcd-03 只需要改三处:
| 配置项 | etcd-02 | etcd-03 |
|---|---|---|
name | etcd-02 | etcd-03 |
listen-client-urls / advertise-client-urls | 10.0.0.12 | 10.0.0.13 |
listen-peer-urls / initial-advertise-peer-urls | 10.0.0.12 | 10.0.0.13 |
initial-cluster 三台保持完全一致,这是最容易写错的地方。任何一台的列表里少了成员、或者 IP 写错,都会出现「各起各的集群」或者「连不上初始集群」。
启动与验证
三台依次启动:
sudo systemctl enable --now etcd
sudo systemctl status etcd --no-pager
第一台启动时会一直重试,因为另外两台还没起来,拿到 2 票才能选出 leader。三台都起来后,日志里会看到 leader 选举完成。用 etcdctl 验证:
export ETCDCTL_ENDPOINTS=http://10.0.0.11:2379,http://10.0.0.12:2379,http://10.0.0.13:2379
# 三个成员都在
etcdctl member list -w table
# 三个端点都健康
etcdctl endpoint health --cluster
# 看谁是 leader、db 大小、raft 进度
etcdctl endpoint status -w table
member list 应该看到三行,IS LEADER 只有一行为 true。endpoint status 里三台的 RAFT TERM 应该一致,RAFT INDEX 相差不大。
三节点集群首次启动时,第一台会卡在 no leader 等另外两台,这是正常的。不要因为看到这个日志就反复重启,也不要用 --force-new-cluster 去「修复」它。
扩缩容
增加成员用 member add,新成员以 existing 状态加入:
# 在现有集群上预添加成员,拿到新成员 ID
etcdctl member add etcd-04 --peer-urls=http://10.0.0.14:2380
# 新节点配置 initial-cluster-state: existing,initial-cluster 用返回的完整列表
# 启动新节点后检查
etcdctl member list -w table
移除成员:
etcdctl member remove <member-id>
扩容要一台一台加,加完确认新成员追平(RAFT INDEX 接近 leader),再加下一台。缩容同理,一次移除一个,确认剩余成员保持多数派健康。不要一次性对多个成员做变更,中间态可能丢多数派。
TLS
生产环境必须开 TLS。etcd 的 TLS 分两套:
- 客户端 TLS:apiserver、etcdctl、监控系统访问 2379 时使用。
- Peer TLS:成员之间 2380 通信使用。两套可以共用 CA,但证书最好分开,便于权限控制和轮换。
证书要求
| 证书 | 用途 | SAN 必须包含 |
|---|---|---|
| CA | 签发和校验 | 无 |
| server | 服务端 2379 | 本机 IP、127.0.0.1、localhost、客户端使用的域名 |
| client | 客户端认证 | 无特殊要求,CN 可用于标识身份 |
| peer | 成员间 2380 | 本机 IP、127.0.0.1、localhost |
服务端证书的 SAN 一定要覆盖客户端实际连接的地址。apiserver 连的是 https://10.0.0.11:2379,证书里就得有 10.0.0.11;只写 localhost 会报 x509: certificate is valid for localhost, not 10.0.0.11。
生成证书
用 OpenSSL 生成一套最小可用的证书(生产建议用 cfssl 或公司 PKI):
mkdir -p /etc/etcd/certs && cd /etc/etcd/certs
# 1. CA
openssl genrsa -out ca.key 2048
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 \
-subj "/CN=etcd-ca" -out ca.crt
# 2. 服务端证书,SAN 按节点实际地址改
openssl genrsa -out server.key 2048
openssl req -new -key server.key -subj "/CN=etcd-01" -out server.csr
cat > server.ext <<'EOF'
subjectAltName=IP:10.0.0.11,IP:127.0.0.1,DNS:localhost
EOF
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-days 825 -sha256 -extfile server.ext -out server.crt
# 3. 客户端证书
openssl genrsa -out client.key 2048
openssl req -new -key client.key -subj "/CN=etcd-client" -out client.csr
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-days 825 -sha256 -out client.crt
# 4. peer 证书
openssl genrsa -out peer.key 2048
openssl req -new -key peer.key -subj "/CN=etcd-01-peer" -out peer.csr
cat > peer.ext <<'EOF'
subjectAltName=IP:10.0.0.11,IP:127.0.0.1,DNS:localhost
EOF
openssl x509 -req -in peer.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-days 825 -sha256 -extfile peer.ext -out peer.crt
每个节点都要用自己的 SAN 签一张 server 和 peer 证书。客户端证书可以一套通用,也可以给 apiserver、监控、运维各签一张,方便审计和吊销。
服务端参数
把监听地址和 advertise 地址全部改成 https://,再加 TLS 参数:
listen-client-urls: https://10.0.0.11:2379,https://127.0.0.1:2379
advertise-client-urls: https://10.0.0.11:2379
listen-peer-urls: https://10.0.0.11:2380
initial-advertise-peer-urls: https://10.0.0.11:2380
cert-file: /etc/etcd/certs/server.crt
key-file: /etc/etcd/certs/server.key
client-cert-auth: true
trusted-ca-file: /etc/etcd/certs/ca.crt
peer-cert-file: /etc/etcd/certs/peer.crt
peer-key-file: /etc/etcd/certs/peer.key
peer-client-cert-auth: true
peer-trusted-ca-file: /etc/etcd/certs/ca.crt
参数含义:
| 参数 | 作用 |
|---|---|
--cert-file / --key-file | 客户端流量的服务端证书和私钥 |
--trusted-ca-file | 校验客户端证书的 CA |
--client-cert-auth | 强制客户端出示证书,不开等于只加密不认证 |
--peer-cert-file / --peer-key-file | 成员间通信的证书 |
--peer-trusted-ca-file | 校验其他成员证书的 CA |
--peer-client-cert-auth | 强制成员间双向认证 |
--client-cert-auth 和 --peer-client-cert-auth 建议都打开。只开 TLS 不开认证,任何能连上 2379 的客户端都能读写,安全性约等于没有。
客户端访问
etcdctl \
--endpoints=https://10.0.0.11:2379,https://10.0.0.12:2379,https://10.0.0.13:2379 \
--cacert=/etc/etcd/certs/ca.crt \
--cert=/etc/etcd/certs/client.crt \
--key=/etc/etcd/certs/client.key \
endpoint health --cluster
也可以写进环境变量:
export ETCDCTL_ENDPOINTS=https://10.0.0.11:2379,https://10.0.0.12:2379,https://10.0.0.13:2379
export ETCDCTL_CACERT=/etc/etcd/certs/ca.crt
export ETCDCTL_CERT=/etc/etcd/certs/client.crt
export ETCDCTL_KEY=/etc/etcd/certs/client.key
etcd 证书过期后,成员之间握手失败,日志里是 transport: authentication handshake failed 或 x509: certificate has expired。监控里加一条证书过期告警(比如剩余 30 天),比事后救火便宜得多。kubeadm 集群的 etcd 证书默认一年有效期,记得定期 kubeadm certs renew。
快照备份与恢复
etcd 的数据是集群状态的全部,备份是最后一道防线。没有验证过恢复的备份等于没有备份,这一节一定要在测试环境完整演练一遍。
做快照
snapshot save 可以在线执行,不影响集群正常读写:
mkdir -p /var/backups/etcd
etcdctl \
--endpoints=https://10.0.0.11:2379 \
--cacert=/etc/etcd/certs/ca.crt \
--cert=/etc/etcd/certs/client.crt \
--key=/etc/etcd/certs/client.key \
snapshot save /var/backups/etcd/etcd-$(date +%F-%H%M).db
# 检查快照完整性、revision、hash
etcdctl snapshot status /var/backups/etcd/etcd-2026-10-25-1200.db -w table
快照里包含当前所有 key、revision 和成员信息,不含 WAL 里未提交的部分。备份策略建议:
- 每小时一次全量快照,保留 7 天;每天再留一份到对象存储或异地。
- 快照文件别和数据目录放同一块盘,磁盘坏了两个一起没。
- 定期(比如每季度)做一次恢复演练,确认快照能真的恢复出集群。
- 记录快照对应的 revision,恢复后方便和业务侧对账。
恢复演练
恢复的核心是:用快照重建一个新集群,而不是把快照文件塞回原数据目录。snapshot restore 的目标目录必须是空目录或不存在的目录,已经有数据的目录会直接报错。
假设三个节点全挂了,手上有一份 /var/backups/etcd/etcd-2026-10-25-1200.db。在三台机器上分别执行恢复,注意 --name、--data-dir、peer URL 每台不同:
# 在 etcd-01 上执行
sudo etcdutl snapshot restore /var/backups/etcd/etcd-2026-10-25-1200.db \
--name etcd-01 \
--data-dir /var/lib/etcd-restore \
--initial-cluster etcd-01=https://10.0.0.11:2380,etcd-02=https://10.0.0.12:2380,etcd-03=https://10.0.0.13:2380 \
--initial-advertise-peer-urls https://10.0.0.11:2380 \
--initial-cluster-token etcd-cluster-restored
etcd-02、etcd-03 用同一份快照、同样的 --initial-cluster 和 token,只改 --name、--data-dir 和 peer URL。然后把三台的配置指向新的数据目录、token 改成 etcd-cluster-restored,启动即可。
验证:
etcdctl --endpoints=https://10.0.0.11:2379 \
--cacert=/etc/etcd/certs/ca.crt \
--cert=/etc/etcd/certs/client.crt \
--key=/etc/etcd/certs/client.key \
endpoint health --cluster
etcdctl --endpoints=https://10.0.0.11:2379 \
--cacert=/etc/etcd/certs/ca.crt \
--cert=/etc/etcd/certs/client.crt \
--key=/etc/etcd/certs/client.key \
member list -w table
3.5 里 etcdctl snapshot restore 还能用,但会提示迁移到 etcdutl。3.6 起快照相关操作统一走 etcdutl,命令除了前缀基本一样。新环境直接记 etcdutl snapshot restore。
只恢复一个成员
如果另外两个成员的数据还在,只是有一个成员磁盘坏了,不需要全量恢复:
- 把坏掉的成员从集群里
member remove。 - 用
member add把它加回来,新节点以空数据目录、existing状态启动,自动从 leader 同步。 - 同步完成后检查
RAFT INDEX和 db 大小。
只有多数派数据全部丢失时,才需要走上面的全量快照恢复。如果只剩一个成员的数据,也可以用它加 --force-new-cluster 单节点拉起,再重新加成员,但要注意它的数据可能落后于最后一次快照。
K8s 场景
kubeadm 部署的集群里,etcd 是 kubelet 管理的静态 Pod,不是 Deployment:
- 清单在
/etc/kubernetes/manifests/etcd.yaml,改文件 kubelet 会自动重建 Pod。 - 数据目录在
/var/lib/etcd,证书在/etc/kubernetes/pki/etcd/。 - 只监听
127.0.0.1:2379和节点 IP,外部访问要走 SSH 隧道或上机执行。 - 三个控制面节点各有一个 etcd 成员,kubelet 挂了 etcd 容器不一定马上停,但失去自愈能力。
备份直接在控制面节点上执行:
sudo mkdir -p /var/backups
sudo etcdctl \
--endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt \
--key=/etc/kubernetes/pki/etcd/healthcheck-client.key \
snapshot save /var/backups/etcd-$(date +%F-%H%M).db
恢复的大致流程(务必在维护窗口、确认备份可用后操作):
- 停 kube-apiserver:把
/etc/kubernetes/manifests/kube-apiserver.yaml移出目录,等 Pod 消失。 - 停 etcd:把
/etc/kubernetes/manifests/etcd.yaml移出目录。 - 用
etcdutl snapshot restore恢复到/var/lib/etcd-restore,三个控制面节点用同一份快照和 token。 - 把恢复目录权限设好,修改
etcd.yaml的hostPath指向新目录(或把旧目录挪走、新目录改名成/var/lib/etcd)。 - 移回
etcd.yaml,确认 etcd 健康后,再移回kube-apiserver.yaml。 - 检查
kubectl get nodes、kubectl get pods -A,确认集群状态。
K8s 的 etcd 和 apiserver 是强依赖:etcd 不可用时 apiserver 会大量报错,但已经运行的 Pod 不受影响。恢复期间控制面不可用,业务 Pod 继续跑,这是 K8s 设计上的一点安慰。K8s 控制面的整体关系可以回看 Kubernetes 核心概念与架构。
EKS、ACK、GKE 这类托管 K8s,控制面和 etcd 由云厂商管理,你既没有 SSH 也没有 etcd 证书。备份和恢复走厂商的管控面备份功能,别想着连 etcd 端口,连不上也修不了。
小结
- 集群规模选 3 或 5,奇数、跨故障域、独立 SSD;网络 RTT 控制在 10ms 内,时钟要同步。
- 三节点部署的关键是
initial-cluster三台完全一致,首次启动用new,加成员用existing,一次只动一个成员。 - TLS 分客户端和 peer 两套,SAN 要覆盖实际访问地址,
client-cert-auth和peer-client-cert-auth都要开。 - 备份用
snapshot save在线做,恢复用etcdutl snapshot restore重建集群;恢复演练比备份本身更重要。 - kubeadm 集群的 etcd 是 kubelet 管的静态 Pod,恢复要先停 apiserver 和 etcd,改完 hostPath 再按顺序拉起。
集群跑起来只是及格线。下一篇进入日常运维:盯哪些指标、db 多大算危险、怎么接 Prometheus 告警。