etcd,其实不是你以为的"数据库"——Kubernetes 大脑的六个反直觉真相

大部分人接触 etcd 的路径是:安装 Kubernetes → 看到控制面里有个 etcd → "哦,就是存 YAML 的地方"。然后把它当成一个"特殊一点的 Redis"或者"分布式版的 SQLite",存进去、读出来、定期备份,万事大吉。

但这种认知会在某个凌晨两点用极其惨烈的方式纠正你。

etcd 不是你熟悉的任何一类数据库。它不是关系型数据库(没有 SQL、没有 JOIN、没有事务 ACID 里剩下那三个),不是文档数据库(没有索引、没有查询语言),也不是缓存(没有 TTL、没有过期策略)。它本质上是一个共识引擎------存的不仅是数据,而是所有控制面组件对"集群现在是什么样子"的共识。

这个差异导致了一系列反直觉的行为。下面逐个拆开。


一、etcd 真正的核心不是"存",而是"共识"

先看一个最简单的场景:你执行 kubectl create deployment nginx --image=nginx,这条命令经过了 API Server → etcd。99% 的人理解的流程是"API Server 把 Deployment 对象存进了 etcd"。

实际上,真正发生的事情是:

yaml 复制代码
kubectl → API Server → etcd Leader 收到写请求 →

  Leader 把这条日志发给所有 Follower →

  超过半数(quorum)回复"收到" →

  Leader 把日志持久化,回复 API Server "写入成功"

这跟"写一条 SQL INSERT"有本质区别。SQL INSERT 只需要数据库实例自己确认就返回了。etcd 的写操作需要集群里大多数节点一致同意------这是 Raft 共识协议的要求。

这就是为什么 etcd 集群的节点数必须是奇数,而且通常选 3 或 5:

yaml 复制代码
3 节点:容忍 1 台挂掉(quorum = 2,挂 1 台还能达成共识)

4 节点:容忍 1 台挂掉(quorum = 3,跟 3 节点一样,还多花了一台机器的钱)

5 节点:容忍 2 台挂掉(quorum = 3)

4 节点是个陷阱。你多花了一台机器的成本,却没有提高容错能力------因为 quorum 从 2 提到了 3,跟 3 节点比,4 节点允许挂掉的节点数还是 1 台。没有任何理由用 4 节点的 etcd 集群。

更重要的一组数字:

yaml 复制代码
3 节点 etcd 集群:

  - 1 台挂了 → 正常读写 ✅(还有 2 台,满足 quorum)

  - 2 台挂了 → 完全不可用 ❌(只剩 1 台,不满足 quorum,连读都不行)

很多人都以为"3 台机器,挂了 2 台剩 1 台,至少有台机器还活着,是不是能读?"------不能。etcd 的读操作(线性一致性读)也需要 quorum。剩 1 台节点既不能写也不能读。整个 Kubernetes 集群的控制面跟着瘫痪------你没法创建 Pod、没法更新 Deployment、kubectl 基本废了。

这就是 etcd 跟普通数据库在心态上最大的区别:普通数据库你担心数据丢了,etcd 你担心集群达成不了共识。


二、etcd 不是"永远可用的 KV 存储"------它的容量上限会直接杀死你的集群

etcd 有一个默认参数:--quota-backend-bytes,默认值 2GB。

这不是建议值,是硬上限。一旦 etcd 的存储超过 2GB,etcd 进程不会报错、不会警告、不会自动清理------它直接拒绝所有写操作。所有的。包括你创建 Pod、更新 Deployment、kubectl apply 任何东西。

你的 Kubernetes 集群会变成只读状态。已有服务继续跑(Pod 在节点上运行不依赖 etcd),但你不能部署、不能扩缩容、不能更新配置------你的集群成了博物馆。

为什么会超过 2GB?三个最常见的原因:

第一,没开自动压缩(compaction)。 etcd 保留了每个 key 的所有历史版本(revision)。你改了一次 Deployment 的 replicas 从 3 到 5,etcd 里存了两个版本。改 1000 次就存 1000 个版本。这些历史版本在 compact 之前一直占着空间。

Kubernetes 自带了一个 compact 机制(--auto-compaction-mode=periodic --auto-compaction-retention=30m),但很多人用外部 etcd 部署时忘了配。结果是 etcd 空间缓慢增长,某天突然超 2GB,集群变砖。

第二,ConfigMap/Secret 当成"配置中心"用。 有些团队把大文件(几百 KB 甚至几 MB)塞进 ConfigMap,或者把 CI/CD 的日志输出存进去。etcd 不是对象存储。每个写入都要复制到 quorum 节点,一个 1MB 的 ConfigMap 在 3 节点集群里实际占用 3MB。多存 10 个就是 30MB。

第三,CRD 对象泛滥。 如果一个 Controller 有 bug,每秒钟创建一个 CRD 实例并且旧的不清理------etcd 被几十万个小对象撑爆的案例在我经历过的生产事故里排前五。

解药只有三个动作:compact(清理历史版本)→ defrag(回收磁盘空间)→ 调大 quota(如果你确实需要更多空间)

bash 复制代码
# 1. 查看当前存储使用量

ETCDCTL_API=3 etcdctl --endpoints=https://127.0.0.1:2379 \

  --cacert=/etc/kubernetes/pki/etcd/ca.crt \

  --cert=/etc/kubernetes/pki/etcd/server.crt \

  --key=/etc/kubernetes/pki/etcd/server.key \

  endpoint status --write-out=table

# 2. 压缩旧版本(保留最近 1 小时)

ETCDCTL_API=3 etcdctl compact $(etcdctl endpoint status --write-out="json" | jq .[0].Status.header.revision -r)

# 3. 碎片整理(回收磁盘空间,每个节点单独执行)

ETCDCTL_API=3 etcdctl defrag --endpoints=https://127.0.0.1:2379 ...

defrag 期间该节点不服务请求,所以在 3 节点集群里逐个执行------每台做完等它重新加入集群再做下一台。


三、etcd 备份不是"dump 一个文件",还原也不是"把文件拷回去"

这件事在 etcd 生产事故中占了最大的比例------备份存在但还原不了。

etcd 有两个你必须要知道的东西:

WAL(Write-Ahead Log): 每个写操作先记录到 WAL,再应用到数据文件。如果进程崩了,重启后从 WAL 重放丢失的写入。WAL 是连续写入的,文件会不断增长,etcd 会自动切割。

Snapshot(快照): 某个时间点的全量数据快照。用 etcdctl snapshot save 创建。

关键问题出在这:WAL 和数据文件是绑定在一起的。 你不能只拷数据文件到另一台机器上指望它能启动。正确的备份是打快照:

bash 复制代码
ETCDCTL_API=3 etcdctl snapshot save /backup/etcd-snapshot-$(date +%Y%m%d%H%M).db

这个 .db 文件就是你的完整备份,包含了创建快照那一刻的所有数据。

还原时最大翻车点:etcd 版本不兼容。 用 etcd 3.4 创建的快照,在 etcd 3.5 的集群上还原时你会发现------还原不了。etcd 的存储格式在不同大版本间不保证兼容。快照文件名、扩展名可以随便叫,但版本号决定了它是可读的还是废的。

还原的正确姿势:

bash 复制代码
# 先把快照还原到一个新的数据目录

ETCDCTL_API=3 etcdctl snapshot restore /backup/etcd-snapshot-20240715.db \

  --data-dir=/var/lib/etcd-restored \

  --name=etcd-0 \

  --initial-cluster=etcd-0=https://127.0.0.1:2380 \

  --initial-advertise-peer-urls=https://127.0.0.1:2380


# 然后修改 etcd 的 data-dir 指向还原后的目录并重启

注意:snapshot restore 会生成一个新的 etcd 数据目录,你要用这个新目录替换现有 etcd 的数据目录,再启动 etcd。不是把快照文件放到数据目录里就完事------那个文件只会被原封不动地无视。

另一个致命的认知偏差:你的 etcd 备份里只有 Kubernetes 资源对象,没有持久卷里的数据。 备份了 etcd ≠ 备份了数据库。如果一个 StatefulSet 里的 MySQL Pod 管理的 PVC 在 Ceph RBD 上,你的 etcd 快照只存了 "有个 StatefulSet 叫 mysql,它关联的 PVC 叫 mysql-data-0"------不存数据本身。数据库的备份你还要用 Velero、VolumeSnapshot 或应用层 mysqldump 单独做。


四、太多对象 = etcd 变慢 = 全集群变慢

Kubernetes 的几乎每一次操作都要经过 etcd。kubectl get pods 时,API Server 从 etcd 读取 Pod 列表。Scheduler 调度 Pod 时,从 etcd 读节点列表。Controller 做 reconcile 时,从 etcd 读它管理的对象。

这就是为什么"对象多了 etcd 就慢"这句话的真正意思是"全集群一起慢"。

etcd 并不擅长处理"全量扫描"。如果你有 10 万个 Pod、5 万个 ConfigMap、3 万个 Secret------那是一个很大的 etcd。当某个控制器想做一次全量 List 时,它需要 etcd 把所有匹配前缀的 key-value 打包返回,这个过程随着对象数呈线性增长。

这就是 Informer + Watch 机制存在的核心原因。Informer 在启动时做一次全量 List 建立本地缓存,之后通过 Watch 只接收增量更新------不需要反复全量扫描 etcd。但 Informer 只解决"反复 List"的问题,不解决"etc 里东西太多"的问题。

生产上的几条硬经验:

  • 不要往 K8s 里塞大量小对象。 如果你的业务需要管理百万级配置项,用专门的配置中心,不要用 ConfigMap 堆。

  • CRD 控制器的调和周期要合理。 如果每个 CRD 实例每 10 秒触发一次全量 List,你的集群在有 1000 个实例时很快就会感觉"不对劲"。

  • 定期审查集群里有多少资源对象。 很多集群跑了一两年,ConfigMap 累积了上千个、Secret 累积了几百个------很多是废弃的 Deployment 留下的垃圾。kubectl get all --all-namespaces 并不能"get all",它不列出 ConfigMap 和 Secret。


五、etcd 的选举超时,网络一抖就脑裂

etcd 用 Raft 协议在集群里选出一个 Leader。Leader 处理所有写请求,Follower 从 Leader 同步日志。选举超时(election timeout)默认是 1000ms,心跳间隔(heartbeat interval)默认是 100ms。

这套参数在稳定的网络环境里工作得完美。但在网络偶尔抖动的环境下------比如云环境的随机网络延迟峰值------会发生什么:

yaml 复制代码
1. Follower 在 election timeout 内没收到 Leader 的心跳

2. Follower 认为 Leader 挂了,自己发起选举

3. 原 Leader 其实没挂,只是网络卡了一下

4. 集群进入一段时间的双 Leader 混乱状态 → 实际上两个都不能提供线性一致性保证

Kubernetes 控制面是 etcd 的客户端。当 etcd 内部在频繁选举时,API Server 看到的是一系列的 "etcdserver: request timed out" 或者 "context deadline exceeded"。结果是你的 kubectl 命令时快时慢、创建 Pod 偶尔超时------但节点上的 Pod 还是正常跑的。

这就是"Kubernetes 控制面卡了但业务没问题"的经典场景------控制面卡是 etcd 慢了,不是你的服务出了问题。

生产环境里,如果你的集群规模大(几千个 Pod、几百个节点),或者 etcd 磁盘是慢速卷(云厂商某些默认存储类型 IOPS 极低),election timeout 和 heartbeat 值得改成更宽松的值:

bash 复制代码
# etcd 启动参数

--heartbeat-interval=250        # 从 100ms 调大到 250ms

--election-timeout=3000         # 从 1000ms 调大到 3000ms

这不是"越高越好"------太高了 Leader 真挂掉时恢复时间更长。这是在容忍网络抖动和加速故障恢复之间做取舍。


六、生产环境等保命的五条规则

如果只能记五条,就记这些:

1. 3 节点就够了,别用 4 节点,更别用单节点。

单节点 etcd 挂了 = 集群控制面完全不可用。3 节点是生产最低配置,不是推荐配置。

2. 配好自动压缩,别让它撑到 2GB。

如果你用的是 kubeadm 部署的 Kubernetes,--auto-compaction-mode=periodic --auto-compaction-retention=30m 一般已经配好了。如果是外部 etcd,一定确认这个参数存在。

3. 定期打快照,并确认快照能还原。

备份不验证就是没备份。每个月至少做一次还原演练------在一台隔离的机器上把快照还原到一个新的 etcd 进程,确认它能启动、能读写。

4. etcd 的磁盘不能用慢速卷。

云厂商的默认 HDD 或低 IOPS 卷是 etcd 的天敌。使用 SSD,至少 3000 IOPS。etcd 对磁盘延迟极其敏感------fsync 延迟高意味着每个写操作都慢,写操作多了集群就堆成队。

5. 定期做 defrag,别等到空间告警才动手。

defrag 可以做成 CronJob,在凌晨低峰期逐个节点跑。不要等到 etcd_mvcc_db_total_size_in_bytes 指标到了 1.8GB 才想起来。


etcd 是那种"平时你感受不到它的存在,一旦出事就是全集群级别事故"的组件。它不像你的业务服务那样天天触发告警,但每次出现在告警里------都是大事。理解它的共识机制、容量模型和备份还原的正确方式,不是"顺便学一下"的事,是运行 Kubernetes 集群的基本功。

一个集群你可以不用 Scheduler(静态 Pod),你可以不用 Controller Manager(手动管理资源),你可以关掉 kube-proxy 换 eBPF------但没有 etcd,Kubernetes 就不是 Kubernetes。它就是这个系统的根。

相关推荐
找方案5 小时前
AI+人力资源:AI招聘面试的兴起与争议
人工智能·面试·职场和发展
CC数分12 小时前
2026年HRBP岗位硬性要求和加分项
面试·职场和发展·数据分析
2601_9626802212 小时前
数据分析面试常考的SQL题和业务题分别是哪几类?
sql·面试·数据分析
黄敬峰13 小时前
一文搞懂 Agentic RAG:用 LangGraph 打造会思考的智能检索架构
面试
晚安日记wanna13 小时前
SSR 为什么不适合登录态从水合冲突到缓存串号
前端·react.js·面试
晚安日记wanna13 小时前
压测 TPS 卡在 800CPU 只跑四成六步定位法
运维·面试·测试
晚安日记wanna14 小时前
批量请求失败只弹一个 Toast面试官想听五层
前端·面试·架构
晚安日记wanna14 小时前
TS const 类型参数一道面试题的四层追问
前端·面试·typescript
晚安日记wanna14 小时前
大表 DDL 面试翻车现场Online DDL 为什么还会锁死业务
数据库·面试·架构
晚安日记wanna14 小时前
MySQL 主从延迟别只答并行复制
数据库·面试·架构