kubectl 突然超时、Pod 调度卡住、API Server 连不上——遇到这种场景,绝大多数人的第一反应是去看 kubelet、看网络、查负载均衡,但很少有人会第一时间想到 etcd。我自己也是栽过几次跟头之后才明白:K8s 集群里最不能出问题的不是业务 Pod,而是躲在控制面底下那三台看起来"没什么动静"的 etcd 节点。这篇文章不聊 K8s 的完整原理,专门围绕 etcd 的日常操作来写:健康检查怎么做、定时备份怎么落地、成员节点加错了怎么处理、备份怎么恢复,以及哪些操作属于"一失手成千古恨"的高危动作。适合正在维护生产集群的运维、刚接手 K8s 平台的 SRE,以及那些学完 CKA 却没有真正操作过 etcd 的同学。
1. 先理清边界:etcd 在 K8s 里到底管什么,挂掉之后会怎样
很多人会把 etcd 理解成"一个数据库",这个说法没错,但如果停留在"数据库"三个字上,后面做操作一定会出偏差。etcd 在 K8s 体系里保存的是整个集群最核心的"真相":所有资源对象(Pod、Service、Deployment、ConfigMap、Secret)的期望状态和实际状态,都以 KV 形式存放在这里。换句话说,API Server 的每次读取,本质上都是在问 etcd"现在世界应该长什么样";调度器调度完一个 Pod 之后,也要把结果写回 etcd。
1.1 它存了什么,又没存什么
etcd 里的数据大致分三类:
- 对象定义本身:Deployment 的期望副本数、Service 的 selector、CRD 里声明的自定义资源。
- 状态回写:Pod 被调度到哪个节点、Pod 的 IP、Node 的可用状态、事件记录等。
- 运行时元数据:比如 Lease 对象,节点心跳用的是 lease 机制而不是直接改 Node 对象的字段。
不存的东西同样重要:容器的文件系统变化、日志内容、镜像层数据,etcd 一概不碰。真要恢复一个业务应用,靠的仍然是镜像仓库和持久卷,etcd 只给你提供"把集群恢复到出事前的控制面状态"的能力。理解这一点,你才会清楚:备份 etcd 不是"备份业务数据",而是"备份集群的神经系统"。
1.2 三种故障形态,影响天差地别
我习惯把 etcd 故障分成三个级别,方便在告警响应的第一时间定位严重程度:
| 故障形态 | K8s 控制面表现 | 紧急程度 |
|---|---|---|
| 单节点故障 | 集群整体往往无感,少数请求出现瞬时超时 | 低,但需要尽快修复 |
| 领导者节点失联 | API Server 的写请求出现周期性的超时重试,读操作基本可用 | 中 |
| 多数派节点丢失 | 整个控制面进入只读甚至完全不可用状态,Pod 无法调度,kubectl 卡死 | 极高 |
最关键的点在于:etcd 的可用性取决于多数派,不是取决于"还剩几个节点"。三节点集群挂一个,仍然有法定多数,可以继续对外服务;挂两个,就只剩单节点,这时候无论你手动调 API 还是切流量,都不可能恢复写入能力,K8s 控制面会陷入"假死"。这个机制很多新手想不明白,总觉得还剩一个节点就能扛一下,实际上 Raft 协议为了保证一致性,宁可完全不可用也不允许出现两个脑裂的"领导"。
2. 健康诊断:别等节点宕了才开始看日志
我见过的不少同学做体检的方式是"上去看服务有没有挂"——systemctl status etcd一下没问题,就觉得 ok。这远远不够。etcd 的故障往往是渐进式的:磁盘变慢、心跳超时、commit 延迟上升——这些在服务进程还活着、端口还能连上时就已经开始发生。等日志里刷出failed to send out heartbeat、dropped MsgApp之类的字样,说明集群已经徘徊在危险边缘了。
2.1 三组核心指标与合理阈值
生产环境里,我更推荐直接用 Prometheus 抓指标,因为 etcd 本身已经暴露了标准的 metrics 接口。重点盯三组:
第一组:Raft 协议层
etcd_server_leader_changes_seen_total:多少秒内出现了 leader 切换。正常情况下这个值长期不变,短期内连续增加意味着节点间网络不稳定或 IO 抖动过大。etcd_server_is_leader:确认当前节点角色。etcd_network_peer_round_trip_time_seconds(对 K8s 自建 etcd 常用grpc_proxy或 peer 间 RTT 指标辅助判断):节点间的网络延迟。
第二组:存储提交延迟
etcd_disk_wal_fsync_duration_seconds**:按 etcd 官方建议,P99 应该长期低于 10ms,超过这个值说明磁盘响应速度已经跟不上 Raft 的刷盘节奏。etcd_disk_backend_commit_duration_seconds:P99 建议低于 25ms。etcd_server_apply_duration_seconds:状态机应用日志的耗时,过大说明机器整体负载已失衡。
第三组:容量与写入分担
etcd_mvcc_db_total_size_in_bytes:数据库文件大小。集群规模扩大之后上涨是正常的,但要注意历史版本堆积导致的膨胀。etcd_mvcc_put_request_total和etcd_mvcc_delete_request_total:看到异常高的 DELETE 量,要先排查是否有组件在疯狂清理对象,比如事件堆积。
2.2 值班场景下的"两分钟体检"
如果你暂时没有完整的监控体系,用命令行一样能完成快速体检。什么事情都先跑一遍,心里就有底:
export ETCDCTL_API=3 export ETCDCTL_CACERT=/etc/kubernetes/pki/etcd/ca.crt export ETCDCTL_CERT=/etc/kubernetes/pki/etcd/server.crt export ETCDCTL_KEY=/etc/kubernetes/pki/etcd/server.key # 批量检查三个成员的健康状态 etcdctl --endpoints=https://10.0.0.11:2379,https://10.0.0.12:2379,https://10.0.0.13:2379 endpoint health -w table # 查看每个成员的 raft 角色和版本信息 etcdctl --endpoints=https://10.0.0.11:2379,https://10.0.0.12:2379,https://10.0.0.13:2379 endpoint status -w table执行完之后,你期望看到的状态是:三个 endpoint 的MEMBER ID各不相同,RAFT TERM一致,RAFT INDEX非常接近。如果某一个节点的 RAFT INDEX 落后其他节点几千甚至几万,这就是典型的同步落后状态,轻则需要观察一段时间,重则说明这个成员应该被移除后重新加入,而不是硬撑着留在集群里继续拖慢写入。
3. 定时备份与快照校验:成本最低的保险,却有一半人做错
如果说 etcd 健康检查是日常保健,那备份就是生命保险。但我在实际接触过的项目里发现,至少一半团队的备份方案做得不对:有人直接用cp -r拷贝数据目录,有人只在手工变更前临时备份一次,有人定时备份做了却从没验证过备份能不能恢复。等到真正要恢复的时候,才发现备份文件是坏的,或者版本不兼容。
3.1 为什么不能用 cp 直接拷贝数据目录
etcd 的数据目录里包含 WAL 日志和 snap 文件,直接拷贝必须保证文件系统层面的一致性。除非你先把 etcd 停掉(生产环境等于提前宣布故障),否则拷贝出来的目录很可能处于中间状态,用于恢复时轻则数据缺失,重则完全无法启动。官方支持的姿势是etcdctl snapshot save,因为它在调用的时候会通过 Raft 拿到一致的快照点,把数据库文件和必要的元数据打成一个相对完整的包。
正确做法是走 etcdctl:
etcdctl --endpoints=https://10.0.0.11:2379 snapshot save /data/etcd-backup/etcd-snapshot-$(date +%Y%m%d-%H%M%S).db3.2 带上校验的定时备份实战
单条命令谁都会敲,真正要做的是把它变成一套可巡检、可恢复的流程。下面是我在工作环境里实际用的脚本骨架:
#!/usr/bin/env bash set -euo pipefail BACKUP_DIR="/data/etcd-backup" KEEP_DAYS=7 ENDPOINTS="https://10.0.0.11:2379,https://10.0.0.12:2379,https://10.0.0.13:2379" CACERT="/etc/kubernetes/pki/etcd/ca.crt" CERT="/etc/kubernetes/pki/etcd/server.crt" KEY="/etc/kubernetes/pki/etcd/server.key" export ETCDCTL_API=3 export ETCDCTL_CACERT=$CACERT export ETCDCTL_CERT=$CERT export ETCDCTL_KEY=$KEY mkdir -p "$BACKUP_DIR" SNAPSHOT_FILE="$BACKUP_DIR/etcd-snapshot-$(date +%Y%m%d-%H%M%S).db" # 备份 etcdctl --endpoints="$ENDPOINTS" snapshot save "$SNAPSHOT_FILE" # 校验 etcdctl snapshot status "$SNAPSHOT_FILE" -w table # 清理旧备份,保留最近7天 find "$BACKUP_DIR" -name "etcd-snapshot-*.db" -mtime +${KEEP_DAYS} -delete要注意两点。第一,校验不是可选项。snapshot save有没有返回成功,都不代表文件一定能用于恢复,所以脚本里必须接着跑snapshot status。第二,三节点集群虽然通常只对 leader 执行备份就够了,但我更推荐随机选节点或者多写一个 endpoint 列表里的次要节点,避免备份操作本身加大 leader 的压力。
定时任务可以这样挂:
0 2 * * * /usr/local/bin/etcd-backup.sh >> /var/log/etcd-backup.log 2>&1除了脚本,我还要额外保存两样东西:etcd 的版本号(比如etcd --version的输出)、证书文件本身。恢复的时候如果 K8s 控制面已经完全起不来,你还是要把旧的证书拿回来的,否则即便有快照文件,跟新初始化的 etcd 也对不上互信关系。
4. 成员节点扩容失败处理:一次真实操作记录
相比备份,成员管理是更"刺激"的操作。我印象最深的一次,是在某个模拟生产环境中给三节点 etcd 集群加第四个节点,结果加完才发现集群异常。那次的排查链路值得完整记录下来,因为大部分人第一次加 etcd 节点都会踩类似的坑。
4.1 现象:新节点一直在 Learner 状态徘徊
K8s 自建集群通常用 3 个静态 Pod 形式的 etcd。当时我用 etcdctl 添加新成员:
# 在三节点集群中,先获取现有集群成员列表 etcdctl member list -w table # 添加一个新成员,返回一个用于新节点启动的 token etcdctl member add etcd-4 --peer-urls=https://10.0.0.14:2380然后在新节点上配置好证书和启动参数,启动 etcd。正常情况下,新节点会以learner角色加入集群,短暂同步数据后自动晋升为voter。但那次member list一直显示etcd-4 ... learner,新节点的数据同步速率极慢,集群写入也开始出现轻微抖动。
4.2 排查链路:从指标到日志再到网络
我没有急着去"踢"节点,先顺着这几个方向查:
- 看 etcd 自身的
endpoint status,确认新节点 Raft Index 是否在增长。如果一直不动,说明它根本没追上主集群的进度。 - 看新节点的日志。如果持续刷
took too long to apply、failed to send snapshot,多半是数据拷贝环节出了问题。 - 检查新节点与旧节点之间的 2380 端口连通性。etcd 成员之间用的是 peer 端口,不走 2379,很多防火墙规则只放行了 2379,结果成员加进去根本收不到 raft 消息。
最后定位到的问题是新节点所在主机的/etc/hosts里没有配置其他 etcd 节点的解析,peer 通信全面失败。这类问题在日志里不容易一眼看出,因为 etcd 不会每次都报 error,更多是重试和超时。
4.3 处理动作与先后顺序
确认是准入节点自身配置问题后,我按这个顺序处理:
- 先用
etcdctl member remove <member-id>移除 learner 状态的新节点。注意 remove 的是 learner 成员,不会影响当前多数派。 - 修复 hosts 和防火墙规则。
- 重新执行
member add,拿到新的启动 token 后再启动 etcd。
一个容易被忽视的要点是:member 的 ID 一旦变化,旧的节点文件不能再带过去,否则 Raft 状态会冲突。我习惯在新节点上把数据目录整个清空,重新用 token 引导启动,这样最干净。
5. 备份回放与单节点恢复:从零复原的可行路径
备份做了一百遍,如果从没演练过恢复,等于白做。恢复 etcd 的场景通常有两种:一是个别节点损坏,想把它重新拉回集群;二是整个集群炸了,需要用一个快照文件重建集群。第二种最复杂,也是这篇文章里最应该反复练习的部分。
5.1 恢复前的三样必备
启动恢复之前,先确认以下几点:
- 手头有没有一个可用的 snapshot 文件?
etcdctl snapshot status能不能正常输出? - 有没有原始证书?
/etc/kubernetes/pki/etcd里的 ca.crt、server.crt、server.key 是否完好。 - 是否确定当前集群已经无法通过多数派方式恢复?如果两节点还活着,优先尝试摘掉故障节点,保留两节点也不要轻率地全部重建。
5.2 用 snapshot 重建一个新集群
我以一个三节点全部损坏、用最近快照重来的简化版本为例。先在单机上初始化数据:
etcdctl snapshot restore /data/etcd-backup/etcd-snapshot-20250101-020000.db \ --name=etcd-1 \ --initial-cluster=etcd-1=https://10.0.0.11:2380,etcd-2=https://10.0.0.12:2380,etcd-3=https://10.0.0.13:2380 \ --initial-advertise-peer-urls=https://10.0.0.11:2380 \ --data-dir=/var/lib/etcd-restore这个命令会基于快照生成一套全新的 Raft 成员记录,成员 ID 和老集群完全不同。这也是为什么恢复之后,K8s 的 API Server 通过原本的 etcd endpoints 去访问是自然衔接的——endpoint 是 IP:端口,成员 ID 的变化不影响连接;但证书的 IP SAN 要确保覆盖这些地址。
第一台恢复完成后,其余节点用相同快照恢复,但--name和--initial-advertise-peer-urls改成各自对应的值,最终能组成一个全新的三节点集群。再把原来的数据目录备份好,替换启动参数中的># 压缩到当前最新版本前 1000 个版本 etcdctl compact $(etcdctl endpoint status -w json | jq -r '.[0].Status.header.revision') # 逐个节点执行整理碎片 etcdctl defrag --endpoints=https://10.0.0.11:2379 etcdctl defrag --endpoints=https://10.0.0.12:2379 etcdctl defrag --endpoints=https://10.0.0.13:2379
这里最典型的错误是只对集群端点执行一次defrag。defrag 必须逐个 endpoint 执行,因为每个节点的数据文件是独立存储的。如果在生产集群里用默认的集群端点一次性执行,可能会导致部分节点没有被整理。另一个更隐蔽的坑是:在 leader 上执行 defrag 会引起瞬时 leader 阻塞,某些版本还会导致 election timeout。所以业界更推荐的做法是直接对 follower 逐个执行,最后一台再处理热闹。或者将 etcd 虽然节点少,但操作习惯上要靠这个顺序来避免抖动。
6.2 把 etcd 当业务键值库写数据
工作中见过有同事为了让一个标志位在集群内全局同步,直接向 K8s 的 etcd 写入业务数据。这个做法短期也能工作,但隐患非常多:etcd 的数据是 K8s API Server 和各类 controller 共享的,任何人用 etcdctl 直接写 key,API Server 完全不知道这些变更,可能绕过准入控制、校验逻辑,也谈不上 resourceVersion 的一致性管理。一旦这个 key 被某次 snapshot restore 覆盖或者因为版本清理被 compact 掉,业务侧会出现极其诡异的现象。
正确做法是,把这类需求放到原生的 ConfigMap、自定义 CRD 里,或者干脆引入专门配置中心。etcd 是控制面的神经系统,不是通用的 Redis,这个边界要守住。
6.3 没有演练就敢在生产环境操作成员变动
最后想强调一个很多人忽略的软性风险:成员变动、leader 切换、defrag 这类操作,如果从来没有在测试环境里完整演练过,就不要想着生产环境"小心一点"。etcd 的三节点集群相比业务应用要敏感得多,它内部 Raft 对超时、网络分区、磁盘抖动都有严格容忍。我在测试环境里做过一次"让某个节点暂时宕机 30 秒"的实验,结果发现只要心跳超时设置得当,K8s 确实无感;但同样场景放在 I/O 已经吃紧的机器上,情况可能完全不同。所以,任何和 etcd 相关的变更,都应该先找一个同等配置的环境跑一遍,再把同样的步骤搬到生产。
回到文章标题说的"K8s etcd 相关操作",我的个人体会是:所有操作归根到底都是围绕"尽量让多数派健康、让每个成员离多数派不太远、让数据随时可恢复"这三件事展开。日常巡检盯指标,变更前做备份,恢复前做校验,把这四步养成肌肉记忆,哪怕真遇到集群假死,你也能在 10 分钟内拿出一个可以执行的恢复路径,而不是对着屏幕等日志。