etcd 集群部署与维护实战:K8s 背后那个数据库的日常运维

只要用 Kubernetes,etcd 就是绕不开的组件:集群的全部状态——节点、Pod、Service、配置——都存在这个键值数据库里,它选不出主,整个集群就只剩只读甚至不可用。本文不走 kubeadm 托管方式,手工部署一套三节点 etcd,把证书、成员管理、快照恢复与容量维护走全;理解了独立部署,托管环境出问题时也知道查什么。环境口径:Rocky Linux 9、etcd 3.5,节点 etcd-01(192.0.2.31)、etcd-02(192.0.2.32)、etcd-03(192.0.2.33)。

前置条件

  • 三台主机时间同步,偏差控制在秒级内。etcd 的选主与租约对时钟偏差敏感,时间不准是很多“莫名其妙”故障的源头。
  • 防火墙放行 2379(客户端口)与 2380(节点间 peer 口);2380 只允许集群内部互访,不要对业务网段开放。
  • 准备一套内部 CA。服务端与 peer 通信共用一组证书,证书的 SAN 必须覆盖各节点的主机名与 IP。
  • 数据目录使用独立磁盘或分区。etcd 对磁盘写入延迟极其敏感,不要和日志量大、IO 高的业务共盘。

先定位:etcd 在 K8s 中的角色与数据模型

etcd 是一个强一致的键值存储:所有写入都要经过 Raft 多数派确认,每次修改生成一个全局递增的 revision,历史版本在被压缩(compaction)之前一直保留——这正是它能实现 watch 与按版本读取的基础。Kubernetes 的 apiserver 只与 etcd 交互,集群里任何对象的“当前状态”都以 etcd 里的数据为准,所以 etcd 数据的备份优先级高于任何单个业务。

容量上有两个概念必须分清。一是后端配额 quota-backend-bytes,默认只有 2 GiB,超限会触发 NOSPACE 告警并拒绝写入,这是逻辑上限;二是后端 db 文件在磁盘上的实际大小,它靠碎片整理(defrag)回收,是物理占用。排障时把两者混为一谈,就会在“明明盘还空着却写不进去”和“明明删了数据文件却不缩小”这两个坑里来回跳。

第一步:签发证书

用内部 CA 签发一张三节点通用的证书(逐台签发也行,但轮换时要操作三份)。SAN 写全三个节点的主机名与 IP,并同时带 serverAuth 与 clientAuth 用途,因为这组证书 peer 与 client 两侧都要用:

sudo mkdir -p /etc/etcd/pki /var/lib/etcd /backup/etcd
cd /etc/etcd/pki

# 1) 自签 CA(已有内部 CA 时跳过这一步)
openssl genrsa -out ca.key 4096
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -out ca.crt -subj "/CN=etcd-ca"

# 2) 节点证书:SAN 覆盖全部节点名、IP 以及 127.0.0.1
openssl genrsa -out etcd.key 2048
openssl req -new -key etcd.key -out etcd.csr -subj "/CN=etcd"
printf "subjectAltName=DNS:etcd-01,DNS:etcd-02,DNS:etcd-03,IP:192.0.2.31,IP:192.0.2.32,IP:192.0.2.33,IP:127.0.0.1\nextendedKeyUsage=serverAuth,clientAuth\n" > ext.cnf
openssl x509 -req -in etcd.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out etcd.crt -days 1825 -extfile ext.cnf

把 ca.crt、etcd.crt、etcd.key 分发到三台的相同路径,私钥权限收紧为 600。证书有效期不要贪长,并把到期日写进巡检——etcd 证书过期是能让整个 K8s 集群停摆的事故类型。

第二步:静态部署三节点

三台使用同一份配置模板,只改 name 与本机 IP 两处。initial-cluster 必须完整列出三台的 peer 地址,首次启动时 initial-cluster-state 为 new:

# /etc/etcd/etcd.yaml(以 etcd-01 为例,其余节点改 name 与本机地址)
name: etcd-01
data-dir: /var/lib/etcd
listen-peer-urls: https://192.0.2.31:2380
listen-client-urls: https://192.0.2.31:2379,https://127.0.0.1:2379
advertise-client-urls: https://192.0.2.31:2379
initial-advertise-peer-urls: https://192.0.2.31:2380
initial-cluster: etcd-01=https://192.0.2.31:2380,etcd-02=https://192.0.2.32:2380,etcd-03=https://192.0.2.33:2380
initial-cluster-state: new
initial-cluster-token: etcd-cluster-01
quota-backend-bytes: 8589934592
client-transport-security:
  cert-file: /etc/etcd/pki/etcd.crt
  key-file: /etc/etcd/pki/etcd.key
  trusted-ca-file: /etc/etcd/pki/ca.crt
peer-transport-security:
  cert-file: /etc/etcd/pki/etcd.crt
  key-file: /etc/etcd/pki/etcd.key
  trusted-ca-file: /etc/etcd/pki/ca.crt

这里顺手把配额从默认 2 GiB 提到 8 GiB 给 K8s 场景留余量,但配额只是上限,不是越大越安全。systemd 单元保持简单,关键是 Type=notify 让 systemd 正确感知 etcd 的就绪状态:

# /etc/systemd/system/etcd.service
[Unit]
Description=etcd key-value store
After=network-online.target
Wants=network-online.target

[Service]
Type=notify
User=etcd
ExecStart=/usr/local/bin/etcd --config-file /etc/etcd/etcd.yaml
Restart=on-failure
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target

三台尽量在短时间内先后拉起,多数派(任意 2 台)起来后即可选主。随后统一约定 etcdctl 的环境变量,后面所有命令都带证书执行:

export ETCDCTL_API=3
export ETCDCTL_ENDPOINTS=https://192.0.2.31:2379,https://192.0.2.32:2379,https://192.0.2.33:2379
export ETCDCTL_CACERT=/etc/etcd/pki/ca.crt
export ETCDCTL_CERT=/etc/etcd/pki/etcd.crt
export ETCDCTL_KEY=/etc/etcd/pki/etcd.key

etcdctl endpoint health --cluster
etcdctl endpoint status --write-out=table

第三步:成员管理

扩容与换机都走成员管理接口,顺序不能乱。增加成员分两步:先在现有集群里执行 member add,把新成员写进集群配置——命令会回显新节点启动所需的完整 initial-cluster 字符串;再到新机器上用这串配置、以 initial-cluster-state=existing 启动,新成员先从 Leader 同步数据,追平后才对外提供服务。移除则相反:先 member remove,再处理那台机器。多数派不健康时不要做任何成员变更——那等于在选民名单不稳时改名单。

etcdctl member list
# 增加成员:先在老集群登记,再拿着回显的配置去启动新节点
etcdctl member add etcd-04 --peer-urls=https://192.0.2.34:2380
# 移除成员:ID 从 member list 里取
etcdctl member remove <MEMBER_ID>

第四步:容量维护——compact 与 defrag

日常维护就三件事:定期 compact 清掉历史 revision、逐台 defrag 回收 db 文件空间、配额告警时按固定顺序处置。顺序很重要:只 compact 不 defrag,文件不会变小;跳过 compact 直接 defrag,回收有限。正确顺序是先 compact 到当前 revision,再逐台 defrag,最后解除告警:

etcdctl alarm list
# 取当前 revision 并压缩历史版本
REV=$(etcdctl endpoint status --write-out=json | python3 -c "import json,sys; print(json.load(sys.stdin)[0]['Status']['header']['revision'])")
etcdctl compact $REV
# 逐台整理碎片(--cluster 会依次对每个成员执行)
etcdctl defrag --cluster
# 确认空间回收后再解除告警
etcdctl alarm disarm

NOSPACE 的治本不在 etcd 这一侧。K8s 里最常见的写入大户是事件(Event)对象与被频繁更新的大对象,先用 endpoint status 看 db 涨势,再去 apiserver 侧审计谁在狂写;单纯把 quota-backend-bytes 一路调大只是把告警往后推,还会让单次恢复越来越慢。

第五步:快照备份与恢复

备份用 snapshot save 在线执行,不用停服,建议每天至少一份、多保留几份并异地再存一份。恢复则是破坏性操作:必须停掉全部成员,逐台用快照生成新数据目录,再按原拓扑重新拉起;任何一台用错 cluster token 或成员列表,都会自成一个新集群,数据就此分叉。特别提醒:etcd 3.5 起 snapshot restore 已从 etcdctl 中移除,统一使用 etcdutl,不要照着老文档敲错命令。

# 在线备份(任意能连通集群的机器上执行)
etcdctl snapshot save /backup/etcd/snapshot-$(date +%Y%m%d-%H%M).db
etcdutl snapshot status /backup/etcd/snapshot-20261108-0200.db --write-out=table

# 恢复:以 etcd-01 为例,三台都要做,--name 与 --initial-advertise-peer-urls 按本机修改
sudo systemctl stop etcd
sudo mv /var/lib/etcd /var/lib/etcd.broken
sudo etcdutl snapshot restore /backup/etcd/snapshot-20261108-0200.db \
  --name etcd-01 \
  --initial-cluster etcd-01=https://192.0.2.31:2380,etcd-02=https://192.0.2.32:2380,etcd-03=https://192.0.2.33:2380 \
  --initial-cluster-token etcd-cluster-01 \
  --initial-advertise-peer-urls https://192.0.2.31:2380 \
  --data-dir /var/lib/etcd
sudo chown -R etcd:etcd /var/lib/etcd
sudo systemctl start etcd

验证

  • endpoint health --cluster 显示三台全部 healthy;endpoint status 里各成员的 DB SIZE、RAFT INDEX 数值接近,没有哪台明显掉队。
  • 写入一个测试键,再从三个端点分别读取,值一致;随后删除该键,确认删除同样生效。
  • member list 的成员数量、名字与 peer 地址和规划一致,没有残留的幽灵成员。
  • 用最新快照在隔离环境做一次恢复演练并记录耗时——没演练过的备份等于没有备份,这在 etcd 上尤其成立。

常见问题

1. 选主失败,日志反复刷选举超时

先查三件事:时钟偏差、2380 端口连通性、数据盘写入延迟(慢盘会让 Raft 心跳超时,表现与网络故障几乎一样)。用 endpoint health --cluster 定位哪台掉队再针对性处理,不要一上来全重启——多数派脆弱时全重启只会拉长恢复窗口。

2. NOSPACE 告警后集群拒绝写入

按第四步的固定顺序处置:compact 到当前 revision、逐台 defrag、最后 alarm disarm。临时急救可先小幅调高 quota-backend-bytes 让写入恢复,但只是买时间,事后必须回溯写入来源。defrag 时该成员会短暂抖动,逐台做,不要并行。

3. 证书过期,apiserver 连不上 etcd

症状通常是整个集群 API 不可用,容易误判成 etcd 本身挂了。先用 openssl x509 查 etcd 节点证书与 apiserver 侧客户端证书的到期日。轮换的标准做法:先逐台替换 etcd 节点证书并滚动重启,再换客户端侧,最后把全部证书到期日纳入巡检与告警,别等过期了才第一次操作轮换流程。

4. 单个成员数据目录损坏起不来

不要试图修数据文件本身。其余成员健康、多数派成立时,先 member remove 摘掉损坏成员,清空其数据目录,再按第三步的 existing 方式重新加入,让它从 Leader 全量同步一份干净数据。这比任何文件级修复都快、也更可靠。

小结

etcd 运维的核心认知就两条:它是 Raft 多数派系统,任何操作前先看多数派是否健康;它装的是整个集群的事实,快照备份与恢复演练的优先级高于一切日常优化。再把 endpoint 健康、db 大小、revision 增速接入监控,这套集群基本不会给你意外的惊喜。

发表回复

后才能评论