etcd 是一个强一致、高可用的分布式键值存储,使用 Raft 协议复制日志,常用于配置管理、服务发现、分布式锁、Leader 选举和 Kubernetes 集群状态存储。
本文基于 etcd 3.7.2(2026-09)和官方 latest 文档重写,覆盖架构、数据模型、etcdctl、集群、TLS/RBAC、compaction/defrag、备份恢复、监控调优、Kubernetes 实践,以及 Raft/MVCC/Watch/Lease 的深入机制。
名字来源:etc 代表
/etc配置目录,d 代表 distributed。etcd 适合存储控制面元数据,不适合当对象存储、日志库或大 Value 数据库。
- etcd 是什么
- 核心架构
- 数据模型
- 一致性语义
- 安装与启动
- etcdctl 常用命令
- 集群部署
- 安全
- 运维与备份
- 监控与调优
- Kubernetes 中的 etcd
- 典型应用场景
- etcd 与 ZooKeeper Consul
- 深入 Raft 与一致性
- 深入 MVCC 与存储
- 深入 Watch 与 Lease
- 深入集群变更与故障
- 深入性能与容量
- 深入安全模型
- 客户端开发实践
- 运维深水区
- Kubernetes etcd 深水区
- 故障案例
- 常见问题
- 参考资料
etcd 是什么
定位
etcd 是一个分布式、强一致的 key-value 存储:
- 强一致:写入通过 Raft 复制,线性一致读默认从 Leader 获取最新数据。
- 高可用:多节点组成集群,少数节点故障不影响 quorum。
- Watch:客户端可以监听 key/prefix 的变化,支持历史 revision。
- Lease:TTL 租约,适合服务注册、心跳、分布式锁。
- 事务:Compare-And-Swap、If/Then/Else 原子操作。
- MVCC:多版本并发控制,支持按 revision 读取历史数据(在 compaction 之前)。
- gRPC/HTTP API:官方客户端和 grpc-gateway 提供 HTTP/JSON 接口。
适合与不适合
适合:
- Kubernetes 集群状态、配置、元数据。
- 服务注册与发现、健康心跳。
- 分布式锁、Leader 选举、选主。
- 配置中心、动态配置下发。
- 小规模、强一致的协调数据。
不适合:
- 大 Value、图片、日志、文件存储。
- 高吞吐写入、海量 key。
- 需要复杂查询、二级索引、SQL 的业务数据。
- 作为消息队列或分析数据库。
etcd 官方建议:存储的数据应较小,写入频繁但数据量可控。默认单次请求上限约 1.5 MiB,生产不建议存大对象。
版本
- 当前官方 latest 文档为 etcd 3.7。
- v3 API 是主流;v2 API 已弃用,不要在新系统使用。
- Kubernetes 对 etcd 版本有兼容矩阵,升级前查看对应 Kubernetes 版本文档。
核心架构
Client (etcdctl / clientv3 / HTTP) |
| 组件 | 作用 |
|---|---|
| Raft | 一致性算法,负责 Leader 选举、日志复制、quorum |
| WAL | 预写日志,持久化 Raft entry,崩溃恢复 |
| Snapshot | Raft 快照,避免 WAL 无限增长 |
| MVCC | 多版本 key-value,revision、历史读 |
| treeIndex | 内存中的 key 索引,映射到 revision |
| bbolt | 嵌入式 KV 后端,存储 key-value 和元数据 |
| Lease | TTL 租约,key 到期自动删除 |
| Watch | 监听 key/prefix 变化 |
| Auth | RBAC 用户/角色/权限 |
| grpc-gateway | 把 gRPC 暴露为 HTTP/JSON |
写路径
- 客户端向 Leader 发起 Put/Txn/Delete。
- Leader 把请求转成 Raft log entry,写入本地 WAL。
- Leader 复制 entry 到 Follower。
- 多数节点持久化后,entry 被 commit。
- 各节点 apply 到 MVCC/backend。
- 返回客户端。
读路径
- 线性一致读(默认):通过 ReadIndex/Leader 确认,保证读到已提交的最新数据。
- 串行化读(
serializable=true):从本地节点读,可能读到旧数据,但延迟更低、可用性更好。 - Watch 事件按 revision 有序,客户端可以从指定 revision 开始 watch。
数据模型
KeyValue
etcd v3 的 key 是字节数组,逻辑上是扁平的 key 空间:
message KeyValue { |
| 字段 | 说明 |
|---|---|
key |
key,不能为空 |
value |
value,字节数组 |
create_revision |
该 key 最后一次创建时的全局 revision |
mod_revision |
最后一次修改的 revision |
version |
key 的修改次数;删除后归零 |
lease |
绑定的 lease ID,0 表示无租约 |
Revision
- 每次写事务都会推进全局 revision。
- 一个事务内的多个修改共享同一个 revision。
get --rev=N可以读取历史版本(compaction 之前)。- compaction 会删除旧 revision,之后读取会返回
ErrCompacted。
Range
Range 用于读取 key 或 key 范围:
key+range_end:范围查询。limit:限制返回数量,0 表示不限制。revision:按历史 revision 读。sort_order/sort_target:排序。serializable:本地读,可能 stale。keys_only/count_only:只返回 key / 只返回数量。min_mod_revision/max_mod_revision等:按 revision 过滤。
etcdctl get /app/config --prefix |
Put / Delete
Put:写入 key;支持prev_kv、ignore_value、ignore_lease、lease。Delete:删除 key 或范围;支持prev_kv。- 删除会生成 tombstone 事件,watcher 能收到 DELETE 事件。
etcdctl put /app/config '{"timeout":10}' |
Transaction
事务是原子的 If/Then/Else:
if (compare) { |
- 所有 compare 必须同时为真才执行 success。
- 一个事务内不能对同一个 key 多次修改。
- 事务内的修改只推进一次 revision。
- 适合 CAS、分布式锁、配置版本控制。
etcdctl txn --interactive |
Watch
Watch 是长连接 gRPC stream:
- 监听单个 key 或范围。
start_revision:从指定 revision 开始。progress_notify:无事件时周期性发送进度。filters:NOPUT、NODELETE。prev_kv:返回修改前的值。- 如果请求的 revision 已被 compaction,watch 会被取消并返回 compact revision。
etcdctl watch /app/config |
Lease
Lease 是 TTL 租约:
LeaseGrant创建租约。- 客户端定期
LeaseKeepAlive续约。 - 租约过期或被 revoke 时,所有绑定 key 自动删除。
- 每个 key 最多绑定一个 lease。
etcdctl lease grant 60 |
一致性语义
Raft 与 quorum
- 集群通常 3、5、7 个成员。
- 3 节点容忍 1 个故障;5 节点容忍 2 个故障。
- 写必须获得多数派确认。
- 偶数节点不会提高容错能力,反而增加成本和延迟,通常不推荐。
- Leader 负责处理写请求;Follower 可以转发或提供串行化读。
线性一致读 vs 串行化读
| 读模式 | 语义 | 特点 |
|---|---|---|
| 线性一致读(默认) | 读到最新已提交数据 | 需要 Leader/quorum 确认,延迟略高 |
| 串行化读 | 本地读,可能 stale | 性能好,适合可容忍旧数据的场景 |
etcdctl get /app/config --consistency=s |
--consistency=s 表示 serializable;默认是 linearizable。
Watch 保证
- 事件按 revision 顺序发送。
- 同一个 key 的修改不会乱序。
- 断线重连后可以从上次 revision 继续 watch。
- 如果 revision 已被 compaction,需要全量重新同步。
事务隔离
- etcd 事务是串行化的,所有 compare 原子执行。
- 事务内的修改共享一个 revision。
- 不支持跨事务的交互式长事务。
安装与启动
下载
从官方 release 下载对应平台的 etcd、etcdctl、etcdutl:
ETCD_VER=v3.7.2 |
版本号以官方最新 release 为准;生产固定版本并校验 checksum。
单机启动
etcd \ |
常用端口:
| 端口 | 用途 |
|---|---|
| 2379 | 客户端 gRPC/HTTP |
| 2380 | 成员之间 peer 通信 |
健康检查
etcdctl endpoint health |
systemd 示例
[Unit] |
生产建议:
data-dir使用独立 SSD,避免和业务日志/数据库抢 IO。--wal-dir可以单独放 WAL(需要独立磁盘时)。- 设置
LimitNOFILE,避免文件描述符耗尽。 - 开启 TLS 和认证,不要暴露公网。
- 不要随意删除
data-dir或member/snap。
etcdctl 常用命令
etcdctl 默认使用 v3 API。旧版本需要 ETCDCTL_API=3,v3.4+ 默认即 v3,通常不再需要。
连接参数
export ENDPOINTS=https://10.0.0.10:2379,https://10.0.0.11:2379,https://10.0.0.12:2379 |
或每次指定:
etcdctl --endpoints="$ENDPOINTS" \ |
KV
etcdctl put /app/config '{"timeout":10}' |
Watch
etcdctl watch /app/config |
Lease
etcdctl lease grant 60 |
事务
etcdctl txn --interactive |
交互式输入 compare、success、failure 操作;脚本中建议使用客户端库构造 Txn。
集群与状态
etcdctl member list --write-out=table |
维护
etcdctl compact <revision> |
备份
etcdctl snapshot save backup.db |
etcdctl snapshot save 用于在线备份;etcdutl 用于离线查看/恢复/defrag。
认证
etcdctl user add root |
认证开启后,所有请求都要带 --user/--password 或客户端证书。
集群部署
静态集群
三节点静态配置,每个节点使用相同的 initial-cluster:
etcd \ |
另外两个节点替换 --name、IP、data-dir 即可。
关键参数:
| 参数 | 说明 |
|---|---|
--initial-cluster |
初始集群所有成员的 peer URL |
--initial-cluster-token |
集群唯一标识,避免误加入其他集群 |
--initial-cluster-state |
new 新建,existing 加入已有集群 |
--initial-advertise-peer-urls |
其他成员访问本节点的 peer URL |
--advertise-client-urls |
客户端访问本节点的 URL |
动态发现
云环境/DHCP 场景可用:
- etcd discovery service。
- DNS SRV 记录。
- Kubernetes 等编排系统生成配置。
DNS SRV 示例:
_etcd-server._tcp.example.com. 300 IN SRV 0 0 2380 infra0.example.com. |
运行时增删成员
# 添加成员 |
注意:
- 一次只变更一个成员,避免 quorum 丢失。
- 删除成员前确认新集群仍有 quorum。
- 成员 ID 用
member list查看。 - 不要同时添加多个成员。
Learner
Learner 是非投票成员,先同步数据,再提升为正式成员:
etcdctl member add infra3 --peer-urls=http://10.0.1.13:2380 --learner |
适合大集群扩容,减少新节点加入对 quorum 的影响。
迁移 leader
etcdctl move-leader <member-id> |
用于维护、升级或平衡负载。
安全
TLS
etcd 支持客户端到服务端、成员到成员的 TLS:
etcd \ |
| 参数 | 作用 |
|---|---|
--cert-file / --key-file |
服务端证书/私钥 |
--trusted-ca-file |
客户端证书 CA |
--client-cert-auth |
强制客户端证书 |
--peer-cert-file / --peer-key-file |
成员间 TLS |
--peer-trusted-ca-file |
peer CA |
--peer-client-cert-auth |
强制成员间双向 TLS |
证书要求:
- 服务端证书:
serverAuth。 - 客户端证书:
clientAuth。 - peer 证书:同时包含
serverAuth和clientAuth。 - SAN 必须包含实际访问的 IP/DNS。
etcdctl --endpoints=https://10.0.0.10:2379 \ |
RBAC
etcd v3 认证基于用户、角色、权限:
- 先创建 root 用户,授予 root 角色。
- 开启 auth 后,所有请求需要认证。
- 角色权限按 key/prefix 的 read/write/readwrite 授予。
- 支持按 revision 控制权限(高级用法)。
etcdctl user add root |
注意:
- 开启 auth 前务必确认 root 用户可用,否则可能锁死集群。
- 生产建议同时启用 TLS + client cert auth + RBAC。
- etcd 不加密磁盘上的 key/value;需要磁盘加密时使用 dm-crypt 或应用层加密。
- 不要把 etcd 暴露到公网,使用内网、防火墙、安全组。
运维与备份
Compaction
etcd 保留历史 revision,长期不清理会导致存储膨胀:
etcdctl compact <revision> |
自动压缩:
etcd --auto-compaction-mode=periodic --auto-compaction-retention=10h |
建议:
- 频繁更新同一批 key:
1h/30m。 - 更新不频繁:
24h/48h/72h。 - 通用默认:
10h。 - revision 模式适合明确知道 revision 增长速率的场景。
Defrag
Compaction 只是逻辑删除,磁盘空间不会立即释放;需要 defrag:
etcdctl defrag |
注意:
- defrag 会阻塞该成员的读写,逐个成员执行,避免集群抖动。
- defrag 不会复制到其他成员,只作用于本地。
- 离线 defrag:
etcdutl defrag --data-dir <path>(etcd 停止时)。
空间配额与 NOSPACE
--quota-backend-bytes 控制 backend 配额。超过配额会触发集群级 NOSPACE alarm,集群进入只读/只删模式。
处理步骤:
# 1. 删除不必要的数据 |
生产建议:
- 显式设置配额,不要无限增长。
- 监控
etcd_mvcc_db_total_size_in_bytes、etcd_mvcc_db_total_size_in_use_in_bytes。 - 配额通常控制在 8 GiB 以内,结合 compaction/defrag。
- 不要用 etcd 存大对象。
快照备份
etcdctl snapshot save /backup/etcd-$(date +%F-%H%M).db |
建议:
- 每个 etcd 成员都可以备份,选择 Leader 或任一健康成员。
- 备份文件包含 key-value、revision 和集群元数据。
- 同时备份证书、配置、启动参数。
- 定期做恢复演练。
- 备份文件要加密、异地保存、设置保留策略。
恢复
把快照恢复到一个新的 data-dir,再启动新集群:
etcdutl snapshot restore /backup/etcd.db \ |
然后:
- 启动恢复后的节点。
- 用
member add加入其他节点,或按官方灾难恢复流程重建集群。 - 验证 key、revision、endpoint health。
- 更新客户端 endpoint/证书。
注意:
- 不要把快照恢复到正在运行的 data-dir。
- 恢复后是一个新集群,不要直接混入旧集群。
- 恢复前确认 etcd 版本兼容。
- 生产恢复要按官方 disaster recovery 文档执行。
升级
- 3.6 → 3.7 按官方 upgrade 文档滚动升级。
- 升级前备份快照。
- 一次升级一个成员,确认健康后再升级下一个。
- 检查 API/客户端兼容性、弃用参数、监控指标。
- 需要回滚时使用
etcdctl downgrade enable <version>,并按官方流程操作。
监控与调优
关键指标
| 指标 | 说明 |
|---|---|
etcd_server_has_leader |
是否有 Leader |
etcd_server_leader_changes_seen_total |
Leader 切换次数 |
etcd_disk_wal_fsync_duration_seconds |
WAL fsync 延迟 |
etcd_disk_backend_commit_duration_seconds |
backend commit 延迟 |
etcd_mvcc_db_total_size_in_bytes |
DB 总大小(含碎片) |
etcd_mvcc_db_total_size_in_use_in_bytes |
实际使用大小 |
etcd_server_slow_apply_total |
慢 apply 次数 |
etcd_server_slow_read_indexes_total |
慢 ReadIndex 次数 |
etcd_server_proposals_failed_total |
提案失败 |
etcd_network_peer_round_trip_time_seconds |
peer RTT |
etcd_server_quota_backend_bytes |
配额 |
etcd_alarm |
alarm 状态 |
告警重点:
- 无 Leader 持续 > 1 分钟。
- Leader 频繁切换。
- WAL fsync/backend commit 延迟超过阈值(如 p99 > 100ms)。
- DB 大小接近配额。
- NOSPACE/CORRUPT alarm。
- peer RTT 抖动、提案失败。
硬件建议
- SSD/NVMe,低 fsync 延迟。
- 专用磁盘,避免和日志/业务抢 IO。
- 2-4 核 CPU,8 GiB 内存起步;大规模按压测调整。
- 低延迟网络,成员尽量同机房/同区域。
- 不要用 NFS 作为 data-dir。
- 关注磁盘 IOPS、fsync 延迟、网络 RTT。
调优参数
| 参数 | 说明 |
|---|---|
--heartbeat-interval |
Leader 心跳间隔,默认 100ms |
--election-timeout |
选举超时,默认 1000ms |
--snapshot-count |
触发快照的已提交事务数 |
--max-request-bytes |
单请求最大字节,默认约 1.5 MiB |
--max-txn-ops |
单事务最大操作数 |
--quota-backend-bytes |
backend 配额 |
--auto-compaction-* |
自动 compaction |
--grpc-keepalive-* |
gRPC keepalive |
调优原则:
- 高延迟网络可适当增大 heartbeat/election timeout,但会增加故障检测时间。
- 不要盲目调大
max-request-bytes,大 Value 会放大复制和内存压力。 - 写吞吐受 fsync 限制,优先提升磁盘性能。
- 读多写少可以增加 serializable read,但要注意 stale 风险。
Kubernetes 中的 etcd
Kubernetes 使用 etcd 存储集群状态:
kube-apiserver是 etcd 的主要客户端。- etcd 通常以 static pod 或外部集群方式部署。
- 控制面节点需要低延迟、可靠磁盘。
- etcd 故障会导致 API Server 无法读写集群状态。
实践:
- 使用独立磁盘,避免和容器日志/镜像存储争 IO。
- 3 或 5 个控制面节点,etcd 成员与控制面同机部署时注意资源隔离。
- 开启 TLS 和 client cert auth。
- 定期快照备份,并验证恢复。
- 监控 WAL fsync、backend commit、DB size、Leader 变化。
- 升级 Kubernetes 前确认 etcd 版本兼容矩阵。
- 不要把业务数据写入 etcd。
备份示例(kubeadm 环境通常有证书路径):
ETCDCTL_API=3 etcdctl \ |
典型应用场景
服务注册与发现
- 服务启动时写入
/services/<name>/<instance>,并绑定 lease。 - 定期 keepalive 续约。
- 客户端 watch 前缀,实时感知实例上下线。
- 实例故障后 lease 过期,key 自动删除。
配置中心
- 配置写入
/config/<app>/<env>。 - 客户端 watch 配置变化。
- 利用 revision 做配置版本和回滚。
- 大配置不要放 etcd,放大对象存储并只存引用。
分布式锁
- 利用 lease + txn 实现互斥锁。
- 利用 create_revision 实现公平锁/队列。
- 官方 clientv3 提供
concurrency包。
Leader 选举
- 候选人创建带 lease 的 key,比较 create_revision,最小者成为 Leader。
- Leader 定期续约;失败后 lease 过期,重新选举。
- Kubernetes controller、Operator 常用类似模式。
分布式队列
- 用有序 key + watch 实现 FIFO 队列。
- 注意 etcd 不是高吞吐消息队列,适合控制面任务,不适合业务消息流。
etcd 与 ZooKeeper Consul
| 维度 | etcd | ZooKeeper | Consul |
|---|---|---|---|
| 一致性 | Raft | ZAB | Raft |
| API | gRPC/HTTP/JSON | 自定义协议 | HTTP/DNS |
| 数据模型 | 扁平 KV + revision | ZNode 树 | KV + 服务目录 |
| Watch | 原生 watch | Watch | blocking query |
| 租约 | Lease | Ephemeral node | Session/health check |
| 服务发现 | 需自行实现 | 需自行实现 | 内置 |
| 典型场景 | Kubernetes、配置、选主 | Hadoop/HBase、ZK 生态 | 多数据中心服务发现 |
| 运维复杂度 | 较低 | 中等 | 中等 |
选型建议:
- Kubernetes 生态、Go 技术栈、需要简单强一致 KV:etcd。
- 已有 ZooKeeper 生态、需要 ZNode 语义:ZooKeeper。
- 需要内置服务发现、健康检查、多数据中心:Consul。
深入 Raft 与一致性
Raft 状态机
每个 etcd member 内部都有一个 Raft 状态机:
| 状态 | 说明 |
|---|---|
| Follower | 被动接收 Leader 日志和心跳 |
| PreCandidate | Pre-Vote 阶段,先试探能否获得多数派支持 |
| Candidate | 发起正式选举 |
| Leader | 处理写请求、复制日志、发送心跳 |
关键变量:
- term:任期,单调递增;每个 term 最多一个 Leader。
- index:日志条目编号,单调递增。
- commitIndex:已知被多数派持久化的最大 index。
- appliedIndex:已经 apply 到状态机的最大 index。
- lastLogIndex / lastLogTerm:用于选举和日志一致性检查。
Raft 的安全性不依赖时钟同步。时钟只影响选举超时、心跳和 lease 过期,因此 NTP 仍然重要,但时钟跳变不会让 Raft 选出两个 Leader。
Leader 选举
- Follower 在 election timeout 内没收到 Leader 心跳,转为 Candidate。
- term + 1,投票给自己,向其他成员发送 RequestVote。
- 获得多数派投票的 Candidate 成为 Leader。
- Leader 定期发送心跳(AppendEntries)维持权威。
- 其他成员收到合法 Leader 心跳后重置 election timeout。
etcd 默认开启 Pre-Vote:分区恢复的节点先进行预投票,避免带着更高 term 强行打断健康集群。Leader 还会通过 check-quorum 检查是否还能联系多数派,失去多数派时主动 step down,减少脑裂窗口。
时间参数:
--heartbeat-interval:Leader 心跳间隔,默认 100ms。--election-timeout:选举超时,默认 1000ms。- 选举超时会加入随机抖动,降低同时竞选的概率。
- 高延迟网络可以适当调大,但会增加故障检测和切换时间。
日志复制
写请求的复制过程:
Client -> Leader |
关键点:
- Leader 必须先持久化自己的日志,再向 Follower 复制。
- Follower 日志冲突时,会截断冲突后缀,以 Leader 为准。
- commit 需要多数派持久化,不是多数派“收到”。
- apply 是确定性的状态机操作;etcd 的 apply 主要是写 backend、更新 treeIndex、通知 watcher。
- 慢 Follower 可能落后很多;Leader 会发送 snapshot 让它追上。
--snapshot-count控制触发快照的已提交 entry 数;太小会频繁快照,太大则 WAL/内存增长。--max-wals、--max-snapshots控制 WAL/快照保留数量。
ReadIndex 与线性一致读
etcd 默认的线性一致读不是简单“读 Leader 内存”,而是:
- 客户端请求到达 Leader(或 Follower 转发)。
- Leader 通过心跳/ReadIndex 确认自己仍是多数派认可的 Leader。
- 等待 appliedIndex 追上 read index。
- 从 MVCC 读取该 revision 的数据。
这样可以避免读到尚未 apply 的旧状态。代价是需要 quorum 确认,延迟高于本地读。
serializable=true 则直接从本地 member 读取,不经过 quorum:
- 延迟低、可用性好。
- 可能读到旧数据。
- 适合配置展示、监控、可容忍 stale 的读。
成员变更与 quorum
etcd 的成员变更一次只允许一个:
- 先
member add,新成员以 Learner 或普通成员加入。 - 新成员追日志/快照。
- 确认健康后,再移除旧成员。
- 同时变更多个成员可能破坏 quorum。
Learner:
- 不参与投票,不增加 quorum。
- 先同步数据,避免新成员加入时拖慢集群。
- 追上后
member promote转为正式成员。
失败语义
| 场景 | 行为 |
|---|---|
| Leader 故障 | 剩余多数派在 election timeout 后选出新 Leader;客户端短暂失败后重试 |
| 少数派分区 | 少数派无法 commit 写;多数派继续服务 |
| 多数派丢失 | 集群不可写;线性一致读也失败;需要恢复 quorum 或从快照恢复 |
| 慢 Follower | Leader 保留日志/发送 snapshot;可能增加网络和磁盘压力 |
| 旧 Leader 恢复 | check-quorum/Pre-Vote 使其 step down,避免双 Leader |
| 时钟跳变 | 不影响 Raft 安全性,但可能影响 lease/keepalive 过期判断 |
深入 MVCC 与存储
bbolt 与 treeIndex
etcd 的持久化后端是 bbolt(B+ 树、单写者、mmap):
keybucket:保存 key 的历史 revision 和 value。metabucket:保存 term、revision、cluster 元数据等。leasebucket:lease 元数据。authbucket:用户、角色、权限。alarmbucket:alarm 状态。members/cluster等:集群成员信息。
内存中的 treeIndex 把用户 key 映射到 revision 列表:
/app/config -> keyIndex |
create_revision、mod_revision、version都来自这里。- Range 查询先在 treeIndex 找到目标 revision,再读 backend。
- 重启时 treeIndex 会从 backend 重建。
写路径
gRPC Put |
注意:
- WAL fsync 是写延迟的主要来源。
- backend 写入是批量的,但仍然是单写者。
- apply 是串行的;慢 apply 会拖慢整个节点。
- 大 value 会放大 WAL、网络、backend 和 snapshot 成本。
读路径
gRPC Range |
- 历史读
--rev=N走 treeIndex + backend。 - 如果 revision 已被 compaction,返回
ErrCompacted。 serializable跳过 quorum 确认,直接本地读。keys_only/count_only减少数据传输。
Compaction
MVCC 保留历史 revision,写入越多,历史越多:
compact(rev)删除 <= rev 的历史版本。- compaction 后,旧 revision 不可读,旧 watcher 会被取消。
- 只做 compaction 不会立即释放磁盘空间,只会释放 backend 内部空间。
- 自动 compaction:
- periodic:按时间保留,如
--auto-compaction-retention=10h。 - revision:按 revision 数保留,如
--auto-compaction-retention=1000。
- periodic:按时间保留,如
compaction 的影响:
- watcher 的
start_revision小于 compact revision 时会收到CompactRevision,需要全量重新同步。 - 备份/恢复后 revision 仍然保留,便于客户端继续 watch(在保留窗口内)。
Defrag
bbolt 删除数据后会产生空闲页,但文件不会自动缩小:
etcdctl defrag重建 backend,释放磁盘空间。- defrag 会阻塞该成员的读写,必须逐个成员执行。
- defrag 不会复制到其他成员,只作用于本地。
- etcd 停止时可以用
etcdutl defrag --data-dir <path>。 - 建议先 compaction,再 defrag。
选择 defrag 时机:
- 低峰期。
- 一次一个成员,优先 Follower,最后 Leader。
- 确认其他成员健康、quorum 稳定。
- 预留足够磁盘空间(重建期间可能临时占用更多空间)。
Quota 与 NOSPACE
--quota-backend-bytes 限制 backend 大小。超过配额:
- 触发集群级 NOSPACE alarm。
- 集群进入维护模式,只接受读和删除。
- 写入返回
etcdserver: mvcc: database space exceeded。
处理顺序必须是:
删除数据 -> compaction -> defrag -> alarm disarm |
只删除数据不 compact/defrag,空间不会真正释放。
关键指标:
etcd_mvcc_db_total_size_in_bytes:backend 总大小(含空闲页)。etcd_mvcc_db_total_size_in_use_in_bytes:实际使用大小。- 两者差距大,说明需要 defrag。
- 接近 quota 时提前告警。
Snapshot 与 WAL
- WAL 保存未快照的 Raft entry;重启时 replay。
- Snapshot 保存某一 index 的完整状态,减少 WAL replay 时间。
- 默认保留多个 WAL/snapshot,供慢 Follower 追赶。
etcdctl snapshot save通过 backend 一致性读生成快照。etcdutl snapshot status查看 revision、key 数、大小、hash。- Snapshot 是恢复的基线,不是实时备份的替代;需要定期执行。
数据损坏
可能出现:
CORRUPTalarm。- backend 文件损坏。
- WAL 损坏。
- 磁盘满导致写入失败。
处理:
- 停止写入,保留现场。
- 检查
alarm list、日志、磁盘。 - 从最近的 snapshot 恢复到一个新集群。
- 如果只是单个 Follower 损坏,可以删除该成员数据目录并重新加入(先确认 quorum 安全)。
- 不要手工修改 bbolt/WAL 文件。
深入 Watch 与 Lease
Watch 的内部模型
etcd 的 watchable store 维护:
- synced watcher:已经追到当前 revision,事件从内存事件队列发送。
- unsynced watcher:落后于当前 revision,需要从 backend 读取历史事件追赶。
- minRev:watch 需要的最小 revision。
- 慢 watcher 可能被取消,避免拖垮服务端。
Watch 事件包含:
type:PUT/DELETE。kv:KeyValue。prev_kv:如果请求了prev_kv。
progress_notify 用于长时间没有事件时发送进度,帮助客户端确认 watch 仍然健康。
Watch 断线重连
客户端必须处理:
- 连接断开。
wresp.Canceled。wresp.CompactRevision非 0。- revision 落后于 compaction。
推荐逻辑:
lastRev = 上次处理完的 revision |
不要只依赖 watch 的实时性;关键状态要支持全量重建。
Lease 的内部模型
Lease 由 lessor 管理:
- Leader 负责 lease grant/keepalive/revoke。
- lease 有 TTL 和到期队列。
- 客户端定期 keepalive,重置 TTL。
- lease 过期或 revoke 时,所有绑定 key 被删除,并产生 DELETE 事件。
- lease 的 checkpoint 会持久化,避免 Leader 切换后丢失全部 lease。
实践:
- keepalive 间隔应小于 TTL,通常 TTL/3。
- keepalive channel 关闭时,重新 grant + 重新绑定 key。
- 不要为每个请求创建 lease。
- 服务注册使用 lease + prefix watch。
- 分布式锁使用 lease + txn,防止锁持有者崩溃后死锁。
etcdctl lease grant 30 |
深入集群变更与故障
成员变更原则
- 一次只加/删一个成员。
- 变更前确认集群健康、quorum 稳定。
- 新成员优先用 Learner 追数据,再 promote。
- 删除成员前确认剩余成员仍满足 quorum。
- 变更期间监控 Leader、WAL、RTT、apply 延迟。
- 不要手工修改成员列表或
initial-cluster。
成员替换
# 1. 添加新成员/learner |
网络分区
- 多数派一侧继续选举和写入。
- 少数派一侧无法 commit;线性一致读失败,serializable 读可能返回旧数据。
- 分区恢复后,旧 Leader/Follower 通过 term/日志一致性回到集群。
- 不要因为少数派“还能读”就认为集群健康。
Quorum 丢失
现象:
- 写入超时/失败。
- 线性一致读失败。
endpoint health可能显示不健康。- Leader 频繁变化或没有 Leader。
处理:
- 确认是网络、磁盘还是进程故障。
- 尽量恢复多数派成员。
- 如果无法恢复,使用最新 snapshot 恢复新集群。
- 不要强行让少数派继续写入,否则可能造成数据分叉。
灾难恢复
流程:
- 找到最新、可用的 snapshot。
etcdutl snapshot status校验 revision/key 数。- 恢复到新的 data-dir。
- 用新的
initial-cluster-token启动新集群。 - 逐个加入其他成员。
- 验证 key、revision、健康状态。
- 切换客户端/DNS。
- 隔离旧集群,避免双写。
注意:
- 恢复出来的集群 revision 可能落后,客户端需要重新同步。
--skip-hash-check只在明确知道快照完整、但 hash 校验失败时使用。- 恢复前备份当前现场,保留日志。
升级与降级
升级:
- 备份 snapshot。
- 阅读 3.6 → 3.7 升级文档。
- 一次升级一个成员,先 Follower 后 Leader。
- 检查 API、客户端、监控、弃用参数。
- 观察 Leader 稳定、WAL/apply 延迟、错误率。
降级:
etcdctl downgrade enable 3.6 |
降级窗口和限制以官方文档为准,不要长期停留在降级状态。
深入性能与容量
瓶颈在哪里
| 资源 | 影响 |
|---|---|
| 磁盘 fsync | WAL 写入延迟,直接影响写吞吐和 Leader 稳定性 |
| 磁盘 IOPS | backend commit、compaction、defrag |
| 网络 RTT | Raft 复制、ReadIndex、Leader 选举 |
| CPU | TLS、序列化、watch、压缩 |
| 内存 | treeIndex、bbolt cache、watch 队列、lease |
| 磁盘容量 | WAL、snapshot、backend、quota |
etcd 对磁盘延迟非常敏感。WAL fsync 从 1ms 上升到 100ms,写性能和 Leader 稳定性都会明显下降。
容量估算
粗略考虑:
backend 大小 ≈ key/value 总量 + 历史 revision + 索引 + 空闲页 |
影响因素:
- key 数量。
- value 大小。
- 写入频率和 revision 增长速度。
- compaction 保留窗口。
- watcher 数量。
- lease 数量。
- 集群成员数和复制流量。
实践:
- 单 value 控制在 KiB 级,不要存 MB 级对象。
- 单集群 backend 建议控制在 8 GiB 以内。
- 高频更新 key 要缩短 compaction 窗口。
- 大量 watch 会增加内存和 CPU。
- 用
etcdctl check datascale估算。 - 用
etcdctl check perf做基础性能检查。
调优
etcd \ |
原则:
- 先解决磁盘,再调参数。
- 不要把
max-request-bytes调得过大。 - 不要盲目增加成员数;成员越多,写放大和 RTT 影响越大。
- 读多写少可以用 serializable 读,但要接受 stale。
- 监控 p99 而非平均值。
深入安全模型
TLS 分层
etcd 有两种 TLS:
- client-to-server:客户端到 etcd 2379。
- peer-to-peer:成员之间 2380。
推荐全部启用,并开启双向认证:
--cert-file=server.crt --key-file=server.key \ |
证书要求:
| 角色 | keyUsage | extendedKeyUsage |
|---|---|---|
| 服务端 | digitalSignature, keyEncipherment | serverAuth |
| 客户端 | digitalSignature, keyEncipherment | clientAuth |
| peer | digitalSignature, keyEncipherment | serverAuth + clientAuth |
- SAN 必须包含实际访问的 IP/DNS。
- 客户端证书用于认证,不是可选的“加密开关”。
- 证书轮换要提前规划,避免过期导致集群失联。
RBAC 内部模型
- 认证信息存储在 auth store,带有 auth revision。
- 用户、角色、权限变更会推进 auth revision。
- root 用户拥有全部权限。
- 角色权限可以按 key/range 授予 read/write/readwrite。
- 权限检查在请求进入 Raft 之前完成。
开启顺序:
etcdctl user add root |
常见错误:
- 没有先创建 root 用户就 enable auth,导致无法管理。
- 只给应用 read 权限,却让它 watch 并写入状态 key。
- 证书 CN/SAN 不匹配,客户端报
x509: certificate is valid for ...。 - peer 证书缺少 clientAuth,开启 peer-client-cert-auth 后成员无法通信。
威胁模型
- 能访问 etcd 的客户端可以读取其权限范围内的所有 key。
- etcd 不加密磁盘数据;磁盘泄露等于数据泄露。
- etcd 没有内置细粒度审计/限流;需要网关、网络策略、日志。
- Docker/Kubernetes 中挂载 etcd 证书的 Pod 需要严格 RBAC。
- 不要把 etcd 暴露到公网,不要用自签名证书跳过校验。
客户端开发实践
初始化 clientv3
import ( |
原则:
- 客户端复用,不要每个请求新建。
- Endpoints 配置多个成员,clientv3 会自动负载均衡/重连。
- 设置 DialTimeout 和请求 Context 超时。
- 生产必须配置 TLS;不要让
InsecureSkipVerify进入生产。
KV 与 Watch
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) |
Lease
lease, err := cli.Grant(ctx, 30) |
事务与分布式锁
CAS:
txn, err := cli.Txn(ctx). |
使用官方 concurrency 包:
session, _ := concurrency.NewSession(cli, concurrency.WithTTL(10)) |
Leader 选举:
election := concurrency.NewElection(session, "/election") |
错误与重试
常见错误:
| 错误 | 含义 | 处理 |
|---|---|---|
ErrCompacted |
revision 已被压缩 | 全量重新同步,不要盲目重试 |
ErrFutureRev |
请求 revision 大于当前 | 等待/重试 |
ErrNoLeader |
没有 Leader | 退避重试 |
ErrTimeout |
请求超时 | 退避重试 |
ErrLeaderChanged |
Leader 变化 | 重试 |
ErrPermissionDenied |
RBAC 拒绝 | 修正权限 |
ErrRequestTooLarge |
请求过大 | 减小 value/拆分 |
ErrTooManyOps |
事务操作过多 | 拆分事务 |
if errors.Is(err, rpctypes.ErrCompacted) { |
读一致性选择
// 默认线性一致读 |
运维深水区
Compaction 与 Defrag 的标准流程
# 1. 查当前 revision |
注意:
- defrag 会阻塞当前成员读写。
- 不要同时 defrag 所有成员。
- 先 Follower,最后 Leader。
- 预留磁盘空间。
NOSPACE 处理
etcdctl alarm list |
如果 alarm disarm 后仍然报错,检查是否所有成员都完成了 compaction/defrag。
Snapshot 与 HashKV
etcdctl snapshot save /backup/etcd.db |
hashkv 比较各成员的 KV 历史 hash,用于发现数据不一致。hash 不一致时:
- 检查是否有成员落后。
- 检查是否刚刚 compaction/defrag。
- 必要时从健康成员重建问题成员。
成员替换与磁盘故障
单成员数据损坏:
- 确认其他成员仍有 quorum。
- 停止问题成员。
- 清理其 data-dir(确认不再需要)。
- 用
member add重新加入,或用已有成员 snapshot 恢复。 - 等待追上,检查 health/hashkv。
多数派损坏:按灾难恢复流程从 snapshot 重建新集群。
监控告警
建议告警:
etcd_server_has_leader == 0持续 1 分钟。- Leader 切换次数异常增长。
- WAL fsync p99 超过 100ms。
- backend commit p99 超过 100ms。
- DB 大小超过 quota 的 80%。
- NOSPACE/CORRUPT alarm。
- peer RTT 异常。
- 提案失败/慢 apply 持续增长。
Kubernetes etcd 深水区
静态 Pod 结构
kubeadm 集群中,etcd 通常是 static pod:
/etc/kubernetes/manifests/etcd.yaml |
kube-apiserver 通过 --etcd-servers=https://127.0.0.1:2379 访问本地 etcd,并使用证书认证。
备份
ETCDCTL_API=3 etcdctl \ |
同时备份:
- etcd 证书。
- kube-apiserver 配置。
- kubeadm-config。
- 集群版本信息。
恢复注意
- 恢复 etcd 会影响整个集群,必须按 Kubernetes 官方灾难恢复流程。
- 恢复后 API Server 需要重新连接。
- 不要在生产直接覆盖正在运行的 etcd data-dir。
- 恢复前隔离旧集群,避免双写。
- 恢复后检查 Node、Pod、CRD、RBAC 是否完整。
性能与磁盘
- etcd 的 WAL fsync 延迟会直接放大到 kube-apiserver 请求延迟。
- 控制面节点使用低延迟 SSD,避免和容器日志/镜像存储共享磁盘。
- 监控
etcd_disk_wal_fsync_duration_seconds、etcd_disk_backend_commit_duration_seconds。 - 控制面节点数量 3 或 5,etcd 成员数与控制面节点数一致时要注意资源隔离。
- 大规模集群关注 apiserver QPS、watch 数量、etcd DB 大小。
- 升级 Kubernetes 前检查 etcd 版本兼容矩阵。
故障案例
案例一:Leader 频繁切换
现象:
etcd_server_leader_changes_seen_total持续增长。- 客户端间歇性
leader changed。 - apiserver 偶发 5xx。
排查:
- WAL fsync 是否抖动。
- 网络 RTT 是否抖动/丢包。
- CPU 是否被其他进程抢占。
- 是否有成员频繁重启。
- 是否跨机房部署导致 RTT 过高。
处理:
- 换本地 SSD。
- 调整 heartbeat/election timeout。
- 修复网络。
- 隔离高负载进程。
案例二:NOSPACE
现象:
- 写入报
database space exceeded。 etcdctl alarm list显示 NOSPACE。
处理:
etcdctl del /old/prefix --prefix |
复盘:
- 是否缺少自动 compaction。
- 是否存了大 value/高频 revision。
- 是否没有 quota/空间告警。
案例三:慢磁盘导致 apply 落后
现象:
etcd_server_slow_apply_total增长。- 写入延迟高。
- Follower 落后,Leader 频繁发 snapshot。
处理:
- 检查磁盘 IOPS/fsync 延迟。
- 迁移到独立 SSD。
- 降低写入频率/大 value。
- 调整 snapshot/WAL 保留参数。
案例四:Quorum 丢失
现象:
- 无 Leader。
- 写入和线性一致读失败。
- 少数派节点仍能启动,但无法形成多数派。
处理:
- 恢复多数派节点。
- 无法恢复时,用最新 snapshot 重建新集群。
- 不要强行让少数派继续写入。
案例五:Kubernetes API 超时
现象:
kubectl超时、apiserver 5xx。- etcd 健康检查失败或延迟高。
- 控制面日志出现 etcd timeout。
排查:
- etcd WAL fsync/backend commit。
- 控制面磁盘是否被打满。
- etcd DB 是否接近 quota。
- 是否有大量 watch/大对象。
- 网络/证书是否正常。
处理:
- 恢复磁盘性能。
- compaction/defrag。
- 扩容控制面/etcd。
- 修复异常客户端。
常见问题
Q:etcd 集群为什么推荐奇数节点?
A:quorum 需要多数派。3 节点容忍 1 个故障,4 节点也只能容忍 1 个;偶数节点增加成本但不增加容错,还增加复制延迟。
Q:etcd 能存多大的数据?
A:不适合大 Value。默认单请求约 1.5 MiB,生产建议控制 key/value 大小,总数据量控制在几 GiB 以内,并配合 compaction/defrag。大对象放对象存储。
Q:compaction 和 defrag 有什么区别?
A:compaction 删除历史 revision,逻辑释放;defrag 重写 backend,物理回收磁盘空间。先 compaction,再 defrag。
Q:为什么删除 key 后磁盘没变小?
A:删除只是写 tombstone,compaction 后才释放逻辑历史,defrag 才回收物理空间。
Q:NOSPACE alarm 怎么处理?
A:删除数据 → compaction → defrag → alarm disarm。不要只删数据不 compact/defrag。
Q:etcd 会加密磁盘数据吗?
A:不会。TLS 只保护传输;磁盘加密需要 dm-crypt、云盘加密或应用层加密。
Q:可以只备份一个节点吗?
A:可以,etcd 快照包含完整 keyspace。选择健康成员备份,并定期验证恢复。生产建议多副本备份、异地保存。
Q:恢复快照后能直接加入旧集群吗?
A:不能。恢复出来的是一个新集群,必须按灾难恢复流程重建,避免 revision/成员信息冲突。
Q:Kubernetes 的 etcd 怎么备份?
A:使用控制面证书调用 etcdctl snapshot save,备份文件 + 证书 + 配置,定期做恢复演练。
Q:v2 API 还能用吗?
A:v2 已弃用,新系统使用 v3 API。etcdctl 默认 v3;不要在新项目依赖 v2。