土法炼钢 · 系统与基础设施

【etcd】生产全景:缺口、五轴坐标系与 16 篇路线

文章导航

分类入口
distributedkubernetes
标签入口
#etcd#raft#wal#mvcc#watch#lease#kubernetes#control-plane#overview#v3.5.33

目录

分布式系统百科 · etcd 深度解剖 已从 Watch/MVCC/Lease 讲过协调层全景;Raft 深度重写Raft 实现拆解 讲过 go.etcd.io/raft/v3 工程补丁;分布式 KV 对比 把 etcd 放在 TiKV、FoundationDB 选型海报里。中间仍缺一层:一次 Put/Txn 卡在 Raft propose 还是 MVCC apply、Raft lag 与 apply lag 如何分列、线性一致读与 follower 读各走哪条路径、Watch 从旧 revision 追赶为何触发 ErrCompacted、Lease KeepAlive 不经 Raft 时 Leader 切换如何影响 TTL,以及 K8s apiserver 耦合下 resourceVersion 与 Node Lease 落在哪一层。

本文只做三件事:钉缺口、定义后续 15 篇共用的五条坐标系,并给出阅读路线。难点不在「会不会 etcdctl put」,而在共识层与存储层交界上的可核对字段——Raft index 是否追上、committed 与 applied 是否分叉、MVCC revision 是否被 compact、Watch 客户端 rev 是否落后、Lease TTL 是否在 Leader 切换后漂移。

本篇在系列中的位置

篇目 核心内容
第 1 篇 · 生产全景 缺口、五轴坐标系、16 篇路线
第 2 篇 · 单 Raft 组与服务器角色 Leader/Follower/Learner;request 路由
系列目录 关键问题、阅读路径、全部价值点

版本锚定:etcd v3.5.33(源码 tag v3.5.33,2026-07-23 补丁线)。机制叙述对齐该 tag 下 go.etcd.io/etcd/server/v3 与内嵌 go.etcd.io/etcd/raft/v3;官方文档以 etcd.io/docs/v3.5/ 路径为准。etcd.io/docs/latest 是滚动文档,不以 /latest 冒充本版本。无真实 etcd 集群则不粘贴伪造 etcdctl endpoint status / etcdctl check perf 输出。 Kubernetes 控制面常见嵌入 3.5.x——正文机制钉 3.5.33;3.6/3.7 差异只在第 14 篇升级门列出。


一、相对站内已有内容,缺口在哪

下表列出各已有系列的视角,以及本系列补的格子。规则是:不重写已有内容,只填真正空着的格子。

已有内容 视角 本系列补什么
distributed/13 Raft 论文 → etcd/raft 工程补丁 不重写;只钉 etcdserver 如何嵌入 raft.Node
distributed/raft-etcd PreVote、流水线、ReadIndex 代码走读 收进 02–03、08;成系列生产路径
distributed/50 Watch/MVCC/Lease/K8s 百科单篇 成 16 篇可排障精度;五轴与失败落格
distributed/39 etcd/TiKV/FDB 横向选型 第 16 篇收束 etcd 叶;不重写 TiKV/FDB 全书
distributed/53 Jepsen etcd 3.4.3 第 11、16 篇引用;Lease 锁边界
tikv-htap/ Multi-Raft + Region 第 16 篇对照;不重写 Percolator
foundationdb/ Unbundled + OCC 第 16 篇对照;不重写 Sequencer/Resolver

结论:读者知道「K8s 靠 etcd Watch」,但不知道 proposal 卡在 Raft 还是 MVCC apply、follower 读为何可能 stale、Watch 从 historical rev 追赶为何 OOM、KeepAlive 不经 Raft 时 Leader 切换如何影响 TTL、defrag 与 compaction 能否同窗口、member replace 与 learner 升级的检查单差在哪;把 etcd 当通用数据库会在 quota 与 bbolt 单写者处撞墙。

与通识教程的差别也在这里:多数「在 K8s 上运维 etcd」文章停在 etcdctl endpoint health 与备份脚本;本系列追问 apiserver 超时之后「no leader / quota / ErrCompacted / Lease TTL 漂移」仍落在哪一层,以及 Raft lag 与 apply lag 为何不能混成一个指标。深度来自可证伪的阶段划分,而不是「etcd 很轻量」口号。

本系列刻意不写 Raft 论文全文、apiserver storage 实现全书、TiKV/FDB 内核重讲、Kine 兼容层全书、未实测「etcd vs TiKV QPS%」排行榜。若目标只是「K8s 靠 etcd Watch 的直觉」,distributed/50 已够;若目标是「Raft lag vs apply lag、Compaction SLO、apiserver 超时是否 etcd 轴」,则必须沿五轴下钻。

本系列反复回指的版本门(不当本篇深挖)

v3.5.33 事实 主篇
v3 MVCC + Watch + Lease 04–11
ReadIndex 线性一致读 08
Learner / non-voting member 02、14
Lease checkpoint 10
auto-compaction / quota / alarm 12
bbolt 后端 05–07
v3.6/v3.7 独有变更 相对 3.5.33 正文不展开 14 升级门
live docs 后版本能力 禁止写入正文当 v3.5.33 事实

二、五条坐标系(后续章节回指)

后面每一篇都落到下面某一条轴上。排障口诀:先点名轴,再下钻组件——不要一上来 defrag 或换盘。Raft lag 与 apply lag 在轴 1–2 分列证据包

Li et al.(;login: 2017)把 etcd 描述为 Raft 共识 + 内存索引 + 持久化后端 的三层栈;v3.5.33 在此基础上增加了 Watchable MVCC、Lease lessor 与 alarm/quota 门。五轴是把这条管线按值班可核对的失败落格切开。

flowchart LR
  client["gRPC client"] --> api["etcdserver API"]
  api --> raft["Raft module"]
  raft --> wal["WAL"]
  raft -->|"commit"| apply["Apply pipeline"]
  apply --> mvcc["MVCC store"]
  mvcc --> bbolt["bbolt backend"]
  mvcc --> watch["watchableStore"]
  api --> lease["Lease lessor"]
  lease --> mvcc
可核对锚点 失败表象 主篇
Raft 共识与成员 Leader 是否存在;Raft index/term;learner 是否可投票;quorum no leader、选举风暴、复制 lag、member 不可达 02、03、14、15
WAL / 持久化与恢复 WAL 段;snapshot;disk fsync;applied vs committed 重启慢、corrupt WAL、snapshot 失败、NOSPC 03、14、15
MVCC / 存储与容量 Revision;treeIndex;bbolt;quota;compaction;defrag database space exceeded、读旧 rev、写 stall、defrag 窗口 04–07、12、15
Watch / 客户端同步 start rev;synced/unsynced;victims;ErrCompacted Watch 断流、事件风暴、追赶 OOM、客户端 rev 落后 09、13、15
Lease / TTL 与语义 grant/KeepAlive/checkpoint;Txn 边界 锁提前释放、Node NotReady 漂移、TTL 雪崩 10–11、13、15

Raft 共识与成员轴

定义:整个集群是否由单个 Raft 组管理;Leader 是否存活;写请求是否经 Leader propose;Learner 是否参与投票。

etcd 与 TiKV 的分叉点在这里:etcd 是 Single Raft Group——所有 key 共享同一份日志;TiKV 是 Multi-Raft,按 Region 切分。单组带来运维简单与全局 Revision 全序,也带来写吞吐与磁盘容量的硬顶。

etcdserver: no leader」或「member add 后长期 catch-up」优先落本轴。展开见 第 2 篇第 3 篇

WAL / 持久化与恢复轴

定义:Raft 条目如何经 WAL 段持久化;snapshot 何时截断日志;重启后 recovery 顺序;committed indexapplied index 是否分叉。

Ongaro & Ousterhout(USENIX ATC 2014)的 Raft 论文定义了日志复制与快照压缩;etcd 的 server/wal 包把 HardState、Entry、Snapshot 记录成 CRC 保护的段文件(默认段大小 64MB,见 SegmentSizeBytes)。

「节点重启后长时间 not ready」或「WAL corrupt / snapshot mismatch」优先落本轴。展开见 第 3 篇

MVCC / 存储与容量轴

定义:Revision (main, sub) 如何分配;treeIndex/keyIndex 如何映射到 bbolt;quota 与 alarm 如何拦截写入;compaction/defrag 如何回收空间。

与 ZooKeeper 的一次性 Watch 不同,etcd v3 的 MVCC 保留历史版本直到 compaction——这是 Watch 从任意 revision 重放的前提,也是磁盘膨胀的来源。Hunt et al.(ATC 2010)的 ZooKeeper 论文提供了协调服务对照基线。

mvcc: database space exceeded」或「读到的 mod revision 明显落后」优先落本轴。展开见 第 4 篇、第 05–07、12 篇。

Watch / 客户端同步轴

定义:watchableStore 如何把 apply 后的变更推给 synced/unsynced watcher;historical revision 追赶路径;channel 背压与 victims;ErrCompacted 何时强制全量重同步。

K8s apiserver 与 controller 的长生命周期 Watch 都依赖这一轴。客户端 rev 落后 + compaction 窗口配置不当,会把「看似 etcd 健康」变成「controller 大面积 resync」。

「Watch 断流但 Put 仍成功」或「unsynced watcher 堆积导致 OOM」优先落本轴。展开见第 09、13 篇。

Lease / TTL 与语义轴

定义:Lease grant/revoke/KeepAlive 哪些路径不经 Raft;checkpoint 如何把 TTL 持久化;与 Txn 分布式锁、Jepsen 结论如何共存。

K8s Node Lease(/leases/ 前缀)把节点心跳写进 etcd;Leader 切换时 lessor 的时钟语义与 apiserver 的 Node NotReady 判定耦合——这是协调层特有的失败面,不是通用 KV 文档会展开的内容。

「锁提前释放」或「Node 抖动但 etcd 健康」优先落本轴。展开见第 10–11、13 篇。


三、坐标系背后的三个不变量

  1. 写路径必须经过 Raft,读路径可以分叉:Put/Txn/Compaction 等 mutating 操作走 propose → commit → apply;Serializable 读可绕过 ReadIndex,线性一致读走 ReadIndex 或 Lease read。代价是 follower 读与 Leader 读证据包不同。
  2. committed 不等于 applied:Raft 多数派 ACK 只意味着日志可提交;MVCC/bbolt 应用与 Watch 通知发生在 apply 阶段。代价是 committedIndex - appliedIndex 可独立膨胀——不能把 Raft metrics 当成存储健康。
  3. Revision 全序与单 Raft 组绑定:全局 (main, sub) 序是 Watch 与 K8s resourceVersion 的语义基础;换 Multi-Raft(TiKV)意味着另一套版本模型。代价是 etcd 水平扩展有硬顶,不能靠加节点线性提升写吞吐。

排障时先问「哪条不变量被触碰」,再落到对应轴。例如:Raft index 正常但 Watch 延迟高 → 不变量 2;客户端从 rev=1 Watch 全库 → 不变量 3 + Watch 轴。


四、设计谱系:Chubby → Raft → etcd v3 MVCC

Lamport Paxos(1998)→ Ongaro Raft(2014):可理解的复制日志
  → CoreOS etcd(2013–):K8s 协调层选型
  → Li et al. ;login: 2017:Raft + 内存索引 + BoltDB 栈
  → etcd v3:Revision MVCC + 持久 Watch + Lease API
  → 仍争:单 Raft 组 vs Multi-Raft 扩展性;bbolt 单写者 vs LSM 写放大
维度 Raft 论文 / distributed/13 etcd v3.5.33 生产路径 开放分叉
拓扑 抽象复制状态机 单 Raft 组 + Learner Multi-Raft(TiKV)
持久化 日志 + 快照 WAL 段 + bbolt snapshot 云盘 fsync 延迟
版本模型 日志 index Revision (main, sub) + keyIndex ZK 多版本对照
协调语义 未定义 Watch 持久 Watch + Lease Kine 类兼容层语义差
对照 distributed/13 本系列 distributed/39

不在第 1 篇宣布胜负;选型留给 第 16 篇 排除树。


五、16 篇路线与阅读建议

flowchart TD
  overview["01 Overview"] --> raft["02 Single Raft"]
  raft --> wal["03 WAL Snapshot"]
  wal --> mvcc["04 MVCC Model"]
  mvcc --> bbolt["05 bbolt Backend"]
  bbolt --> apply["06 Apply Pipeline"]
  apply --> write["07 Write Path"]
  write --> read["08 Read Consistency"]
  read --> watch["09 Watch"]
  watch --> lease["10 Lease"]
  lease --> txn["11 Txn Semantics"]
  txn --> cap["12 Compaction Quota"]
  cap --> k8s["13 K8s Coupling"]
  k8s --> ops["14 Ops Upgrade"]
  ops --> trouble["15 Troubleshoot"]
  trouble --> select["16 Selection"]
  overview --> select
路径 篇目 适合
必读核心 1 → 3 → 6 → 8 → 15 快速建立五轴
K8s 控制面 1 → 9 → 10 → 13 → 15 apiserver 超时归因
容量与运维 4 → 7 → 12 → 14 quota/defrag/升级
对照选型 1 → 16 协调层路径选型
完整通读 1 → … → 16 系统掌握

读法:先读本篇;若症状是 no leader,优先 02→03→15;若是 ErrCompacted,优先 04→09→13→15;若是 Node Lease 漂移,优先 10→13→15。Raft 论文与 PreVote 细节回 distributed/13;Multi-Raft 回 tikv-htap/

Kubernetes 控制面指针

本系列第 13 篇专写 apiserver ↔︎ etcd 耦合;本篇只登记链路上的位置:

kubectl / controller / scheduler
  → kube-apiserver(storage 接口 + watch cache)
    → etcd gRPC(Put/Txn/Watch/Lease)
      → 单 Raft 组 → WAL → MVCC → bbolt

apiserver 把 etcd Revision 暴露为 resourceVersion;controller 的 resync 与 Watch 断连重连都依赖 MVCC 轴与 Watch 轴。控制面 etcd 故障的第一症状常在 apiserver 超时或 503,而不是 Pod 网络——排障时须能把症状映射回五轴,而不是默认 CNI 问题。


六、本篇不写什么


参考资料

规范 / 官方文档 / 源码(A)

论文(A)

站内对照(A/B)

实验台账


上一篇系列目录

下一篇单 Raft 组与服务器角色

读完这篇,下一步读什么

优先读同系列或同问题的下一篇,把单篇消费变成主题集群。


By .