Skip to content

Kubernetes 底座、Watch 与 ZooKeeper 对比

基于 etcd 3.5 / K8s 1.30 · 核于 2026-08

速查

  • etcd 是 K8s 唯一状态存储:K8s 把所有集群状态(Pod/Service/Deployment/ConfigMap/Secret)都存进 etcd,apiserver 是唯一读写入口,controller-manager/scheduler/kubelet 通过 Watch etcd 驱动控制循环。etcd 挂 = K8s 瘫(已有 Pod 继续跑但无法调度增删)。
  • 资源 = KV:每个 K8s 资源在 etcd 是一条 KV,key 用层级化路径(/registry/pods/<ns>/<name>/registry/services/<ns>/<name>),value 是 JSON 序列化的资源对象。
  • Watch 是控制循环根基:controller/scheduler/kubelet 调 apiserver 的 Watch 接口(底层是 etcd Watch),etcd 一变就推送,组件据此「看实际状态趋近期望状态」。Watch 基于 revision,断线重连从上次 revision 续看,不丢事件。
  • Revision + MVCC:每次写 etcd 全局 revision 递增。K8s 的 resourceVersion 就是 etcd revision——并发更新时 apiserver 用它做乐观锁(CAS),revision 不匹配则冲突重试。
  • 命名服务(业务层):用 etcd KV + Watch + Lease 拼注册中心——服务启动 put /services/order/instance-1(带 lease),调用方 get /services/order/(prefix)拿实例列表,服务挂了 lease 过期 key 自动删。CoreDNS、Registrator、gRPC 的 naming 就是这套。
  • 分布式锁etcdctl lock /my-lock(封装 Lease+txn)或 concurrency 包,跟 Consul 的 Session-Lock 同理。
  • etcd vs ZooKeeper:Raft(易理解)vs ZAB(不透明);Go 轻量 vs JVM 重型;扁平 KV vs 树形 znode;流式 Watch vs 一次性 Watch(3.6 前);K8s 主力 vs Hadoop/Kafka 主力。etcd 渐替代 ZK。
  • etcd vs Consul/Nacos:etcd 是纯 KV 底座(无业务服务发现语义,要自拼);Consul/Nacos 是业务服务发现开箱即用(注册/健康检查/DNS/配置中心)。业务微服务发现选 Consul/Nacos,做 K8s 底座或自研协调才用 etcd。

一、etcd 作为 K8s 状态存储

K8s 是声明式系统——用户声明「我要 3 个 nginx Pod」,K8s 持续工作直到实际状态匹配。所有状态都存 etcd:

   用户:kubectl create -f deployment.yaml (replicas: 3)


   apiserver(唯一读写入口)

              │  PUT /registry/deployments/default/nginx
              │     {"replicas":3, "template":...}

   etcd:  revision=N+1
          /registry/deployments/default/nginx = {...}

   ── Watch 驱动 ──
   controller-manager  Watch /registry/deployments/ → 发现 replicas=3
     → 创建 3 个 Pod(写 /registry/pods/...)
   scheduler  Watch /registry/pods/ (未调度) → 分配 node → 更新 pod.spec.nodeName
   kubelet  Watch /registry/pods/?nodeName=self → 调 containerd 拉起容器
  • 一切皆 KV:Deployment、Pod、Service、ConfigMap、Secret、Node 在 etcd 都是 /registry/<kind>/<ns>/<name> 的 KV,value 是资源 JSON。
  • apiserver 网关:所有组件只连 apiserver,apiserver 内部连 etcd。apiserver 做认证/鉴权/准入/序列化,etcd 只管存。
  • resourceVersion = revision:K8s 资源的 metadata.resourceVersion 就是 etcd 的 revision。并发更新时 apiserver 用它做乐观锁——A、B 都读到 revision=100 的 Pod,A 先改成功(revision 变 101),B 带 revision=100 改会冲突(OptimisticLockError),B 重试。
  • etcd 挂的后果:apiserver 无法读写 → kubectl 全部失败、controller 无法 reconcile、不能调度新 Pod。但已在运行的 Pod 不受影响(kubelet 本地缓存,短期不依赖 etcd)。所以 etcd 备份是 K8s 灾难恢复的核心。

二、Watch 机制:K8s 控制循环的根基

etcd 的 Watch 是 K8s 声明式控制循环的物理基础:

bash
# 监听单个 key
etcdctl watch /config/db/host

# 监听前缀(K8s 常用,监听一类资源)
etcdctl watch --prefix /registry/pods/default/

# 输出(key 变化时实时推送):
# PUT /registry/pods/default/nginx-xxx  {...}   revision=101
# DELETE /registry/pods/default/nginx-yyy        revision=102
  • 基于 revision:每个 Watch 事件带 revision。客户端记录「已处理到 revision=X」,断线重连时 etcdctl watch --rev=X+1,从断点续看,不丢事件、不重复。
  • 流式持续:etcd 3.x 的 Watch 是 gRPC 双向流(不像 ZK 3.6 前的一次性 Watch),持续推送直到客户端关闭。
  • 历史回看:可指定起始 revision,看「从某 revision 到现在」的所有变化——用于组件重启后追上进度(如 controller 重启后,先 list 全量再从 list 的 revision 开始 watch)。
  • K8s 控制循环:每个 controller 都 Watch 自己关心的资源前缀,etcd 一变就触发 reconcile(实际 vs 期望),形成「期望状态驱动」的自愈系统。

三、用 etcd 拼业务服务发现(命名服务)

etcd 本身没有「服务发现」语义,但用 KV + Watch + Lease 能拼出来(gRPC、CoreDNS、Registrator 都这么做):

bash
# 1. 服务启动:注册自己(带 lease,宕机自动注销)
LEASE_ID=$(etcdctl lease grant 30 | awk '{print $2}')
etcdctl put /services/order/instance-1 '{"ip":"10.0.0.5","port":8080}' --lease=$LEASE_ID
etcdctl lease keep-alive $LEASE_ID &  # 后台续约

# 2. 调用方:查实例列表(prefix 查询)
etcdctl get /services/order/ --prefix
# → /services/order/instance-1 {"ip":"10.0.0.5","port":8080}
# → /services/order/instance-2 {"ip":"10.0.0.6","port":8080}

# 3. 调用方:Watch 实例变化(实时感知扩缩容)
etcdctl watch --prefix /services/order/
# 实例上线/下线时推送,调用方更新本地实例列表

# 4. 服务宕机:lease 过期 → key 自动删除 → Watch 推送 DELETE → 调用方摘除
  • 这是「自拼注册中心」:etcd 只给原语,要自己写注册/查询/心跳/负载均衡逻辑。Consul/Nacos 把这些封装好了开箱即用,etcd 要团队自研或用 gRPC naming 这类库。
  • gRPC 默认服务发现:gRPC 的客户端负载均衡默认支持 etcd 做命名后端(grpc.NewServiceResolver),就是上面这套 KV+Watch+Lease。
  • CoreDNS + etcd:CoreDNS 有 etcd 插件,把 etcd KV 暴露成 DNS(myservice.default.svc.cluster.local → 实例 IP),是 K8s 服务发现的底层之一(不过 K8s 主要用 CoreDNS + apiserver,etcd 间接)。

四、分布式锁

bash
# 方式 1:etcdctl lock 命令(封装好了)
etcdctl lock /my-lock -- echo "I hold the lock"
# 拿到锁才执行 echo,释放后下一个等待者获取

# 方式 2:concurrency 包(编程式,Go 客户端)
#   mutex := concurrency.NewMutex(session, "/my-lock")
#   mutex.Lock(ctx)    // 内部 Lease + txn 抢占
#   defer mutex.Unlock(ctx)
  • 原理:跟领导选举一样,Lease + compare-and-swap。第一个抢到 /my-lock/lease-xxx 的持锁,释放(删 key 或 lease 过期)后其他等待者抢。
  • 公平性:etcd 的 concurrency 包支持公平锁(FIFO,按请求顺序排队),靠创建有序的 lease key 实现。

五、etcd vs ZooKeeper 详细对比

维度etcdZooKeeper
起源CoreOS 2013,对标 ZKYahoo 2008,Apache 顶级
共识协议Raft(2014,论文清晰)ZAB(2008,论文不透明)
实现语言Go(单二进制)Java(JVM,部署重)
数据模型扁平 KV + prefix树形 znode(路径树)
临时节点Lease(key 带 TTL)Ephemeral Node(连接断开删)
Watch流式持续(gRPC stream)3.6 前一次性(要重注册),3.6+ 持续
多版本MVCC(按 revision 读旧值)无(只能读当前)
事务TXN(if-then-else on keys)多操作原子(create/set check)
跨 DC需集群联邦观察者节点(Observer,手动配)
主要用户Kubernetes、CoreDNS、TiKV、RookHadoop、Kafka(旧版)、HBase、Spark
运维轻量,snapshot 备份简单重型,JVM 调优 + snapshot+log
现状CNCF 毕业,活跃,新项目首选成熟但渐被 etcd 替代,新项目少用
  • 为什么 etcd 赢了:①Raft 比 ZAB 易理解易实现;②Go 单二进制比 JVM 部署轻;③K8s 选了 etcd 带飞生态;④流式 Watch 比 ZK 一次性 Watch 好用。
  • ZK 的存量:Hadoop/YARN/HBase/Kafka(旧版,新版 Kafka 用 KRaft 去 ZK)仍用 ZK,这些是大数据生态的核心。新分布式项目(TiKV、CockroachDB、Rook)多选 etcd/Raft。

六、选型:etcd vs Consul vs Nacos

要做 K8s 底座 / 自研分布式协调服务? → etcd
业务微服务发现(要开箱即用)?
  → 中国 Spring Cloud → Nacos
  → 国际/多 DC/DNS → Consul
  → 已有 K8s 基础设施 → 用 K8s Service(底层 etcd)即可
要做 Hadoop/Kafka 协调(存量生态)? → ZooKeeper
  • 层级关键:etcd 是基础设施层底座(K8s 等系统构建在它之上);Consul/Nacos 是应用层注册中心(业务服务直接用它发现彼此)。两者不在同一层级,不是直接替代关系。
  • 业务微服务:除非你自研框架或有特殊定制需求,否则业务服务发现选 Consul/Nacos 更省事(开箱即用,不必自己拼 KV+Watch+Lease)。

下一步

理解了 K8s 底座与用法后,下一步看参考——Raft 术语速查、etcd API 清单、一致性级别表、易错点与命令速查,作为速查手册。