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。它就是这个系统的根。

相关推荐
Hyyy1 小时前
Electron-Vite是什么,和Vite有什么区别
前端·面试
兰令水3 小时前
hot100【acm版】【2026.7.14打卡-java版本】
java·数据结构·算法·leetcode·面试
蜀道山老天师3 小时前
从零读懂 K8s:核心概念、集群架构与工作原理
容器·架构·kubernetes
SamDeepThinking5 小时前
Java面向对象在JVM里怎么实现
java·后端·面试
不简说7 小时前
JS 代码技巧 vol.1 — 10 个让代码少写 30% 的小套路
前端·javascript·面试
Mr_凌宇7 小时前
# AI 应用工程入门学习笔记 一
人工智能·面试·程序员
Mr_凌宇7 小时前
AI 应用工程进阶扩展学习笔记 二
前端·面试
Hyyy8 小时前
Git Worktree 完全讲解
前端·后端·面试
云技纵横8 小时前
Redis 延迟双删还是读到脏数据?一次缓存一致性事故
redis·后端·面试