Files
wiki/数据库/ETCD/etcd-v3.7-学习手册-03-运维运营篇.md
2026-10-08 15:05:32 +08:00

336 KiB
Raw Permalink Blame History

etcd v3.7 学习手册 · 03 运维运营篇

基于 https://etcd.io/docs/v3.7/ 整理 文档版本:v3.7.0 整理日期:2026-09-18 本册定位:运维运营篇(部署、配置、监控、故障、升级)。兄弟分册:《01-核心概念篇》《02-开发实战篇》。 范围声明:按用户指定「只做 v3.7 主线」,本手册未覆盖 v3.0–v3.5 的历史升级/降级步骤页与 v2.x benchmark 页(共 11 页),清单见《etcd-v3.7-抓取清单.md》。


目录


1. 本册导读

1.1 本册覆盖的官方页面

本册由 8 个素材批次整理而成,共覆盖 etcd v3.7 官方文档 50 个页面(不含索引页重复计数):

批次 主题 覆盖页面数 对应本册章节
batch-04 集群引导 / 配置项全表 / 硬件 / 支持平台 / feature gates / 调优 6 第 2、3、4 章
batch-05 运行时重配置 / 维护 / 灾难恢复 / 故障模式 / 数据损坏 5 第 5、6、7、8、9 章
batch-06 TLS 安全模型 / 认证 / RBAC / 版本策略 / 监控 / metrics 概览 6 第 10、11 章
batch-07 容器部署 / K8s StatefulSet / gateway / gRPC proxy / 命名与发现 / gRPC gateway / 嵌入 / modules 8 第 2、13 章
batch-09 运维任务(搭集群 / 查状态 / 增删成员 / 快照 / 选主)+ 两版 discovery 协议 8 第 2、5、6 章
batch-10 升级总纲 / 3.5→3.6 / 3.6→3.7 / 降级总纲 / 3.7→3.6 / 3.6→3.5 6 第 14 章
batch-11 生态工具与客户端库 / 日志约定 / 报 bug / issue 与 PR 分诊 / FAQ 7 第 16、17 章
batch-13 3 个 benchmark + metrics-latest 指标全表 4 第 12、15 章
合计 50

逐页映射见 附录 A;未纳入的 11 页见 附录 B。

1.2 读者对象

  • SRE / 运维工程师:负责 etcd 集群的部署、扩缩容、备份恢复、升级降级与值班处置。重点读第 2、5、6、7、8、14 章。
  • 平台 / 中间件工程师:负责把 etcd 作为底座提供给上层业务(如 Kubernetes)。重点读第 3、4、13 章。
  • 安全合规同学:重点读第 10 章(TLS、认证、RBAC)。
  • 可观测性 / 稳定性同学:重点读第 11、12 章(监控、指标全表)与第 15 章(容量推算)。
  • 正在排查线上问题的值班同学:直接跳到第 8 章「故障模式」与第 9 章「数据损坏」的「现象 → 原因 → 处置」三段式表格。

1.3 运维全景图

                          ┌──────────────────────────────────────────────┐
                          │              规划阶段 (Plan)                  │
                          │  硬件选型(Ch4) · 平台矩阵(Ch4) · 容量推演(Ch15) │
                          └───────────────────────┬──────────────────────┘
                                                  │
                          ┌───────────────────────▼──────────────────────┐
                          │              交付阶段 (Deploy)                │
                          │  static / etcd discovery / DNS discovery(Ch2) │
                          │  容器化 docker run(Ch2) · K8s StatefulSet(Ch2) │
                          │  配置全参 flag / env / yaml(Ch3)               │
                          └───────────────────────┬──────────────────────┘
                                                  │
      ┌───────────────────────────────────────────┼───────────────────────────────────────────┐
      │                                           │                                           │
┌─────▼──────────┐                    ┌───────────▼───────────┐                    ┌──────────▼─────────┐
│   安全 (Secure) │                    │     运行 (Operate)     │                    │   观测 (Observe)    │
│ TLS 双向(Ch10)  │                    │ 成员增删/learner(Ch5)  │                    │ /metrics /health(Ch11)│
│ auth enable     │                    │ 压缩/整理/配额(Ch6)    │                    │ 123 条指标(Ch12)    │
│ RBAC(Ch10)      │                    │ 快照备份(Ch6)          │                    │ Prometheus/Grafana   │
└─────┬──────────┘                    └───────────┬───────────┘                    └──────────┬─────────┘
      │                                           │                                           │
      │                              ┌────────────▼────────────┐                              │
      │                              │     故障 (Failure)       │◀─────────────────────────────┘
      │                              │ 少数派/多数派/分区(Ch8)   │
      │                              │ 数据损坏检测与修复(Ch9)   │
      │                              │ 灾难恢复 restore(Ch7)    │
      │                              └────────────┬────────────┘
      │                                           │
      └───────────────────────────────────────────┼───────────────────────────────────────────┘
                                                  │
                          ┌───────────────────────▼──────────────────────┐
                          │              演进 (Evolve)                    │
                          │  一个 minor 一跳(Ch14) · 滚动替换 · health 校验 │
                          │  downgrade validate / enable / cancel(Ch14)   │
                          │  代理层 gateway / gRPC proxy(Ch13)             │
                          └──────────────────────────────────────────────┘

1.4 使用约定

  • 中文正文叙述;命令、flag、环境变量、指标名、YAML 字段名一律保留英文原文。
  • 引用官方文档原文处标注 > 引自:[页面标题](URL);非官方推导、经验性补充标注 > 💡 补充:。
  • 所有数值(默认值、benchmark 数字、配额、超时)均原样保留,不做四舍五入或单位换算。
  • 素材中标注「源文未提供」的,本册如实说明,不编造。

2. 集群部署:三种引导方式与容器 / Kubernetes

引自:Clustering Guide、Run etcd clusters inside containers、Run etcd clusters as a Kubernetes StatefulSet

etcd 集群的**引导(bootstrap)**有三种机制:static、etcd discovery、DNS discovery。集群运行起来之后的成员增删走 runtime reconfiguration(第 5 章),不再依赖引导机制。

静态方式要求每个成员都事先知道其他成员;若成员 IP 事先未知(云环境、DHCP),可借助 discovery 服务引导。

2.1 示例集群(全章通用)

Name Address Hostname
infra0 10.0.1.10 infra0.example.com
infra1 10.0.1.11 infra1.example.com
infra2 10.0.1.12 infra2.example.com

2.2 方式一:Static(静态引导)

适用场景与参数说明

  • 适用场景:成员 IP / 主机名事先已知、节点数量固定的环境(自建机房、固定 IP 的云主机)。
  • 核心参数:--initial-cluster 提供离线引导配置,--initial-cluster-state=new 表示新建集群。

环境变量形式:

ETCD_INITIAL_CLUSTER="infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380"
ETCD_INITIAL_CLUSTER_STATE=new

等价命令行:

--initial-cluster infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380 \
--initial-cluster-state new

启动命令(infra0)

$ etcd --name infra0 --initial-advertise-peer-urls http://10.0.1.10:2380 \
  --listen-peer-urls http://10.0.1.10:2380 \
  --listen-client-urls http://10.0.1.10:2379,http://127.0.0.1:2379 \
  --advertise-client-urls http://10.0.1.10:2379 \
  --initial-cluster-token etcd-cluster-1 \
  --initial-cluster infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380 \
  --initial-cluster-state new

infra1(--name infra1,地址 10.0.1.11)、infra2(--name infra2,地址 10.0.1.12)命令同构,仅 --name、--initial-advertise-peer-urls、--listen-peer-urls、--listen-client-urls、--advertise-client-urls 中的 IP 替换为各自地址,其余 flag 与上面完全一致。

三个必知的坑

  1. initial-cluster 中的 URL 是 advertised peer URLs,必须与对应节点上的 --initial-advertise-peer-urls 一致。
  2. 强烈建议为测试用途反复创建的集群设置唯一的 --initial-cluster-token,以便 etcd 生成唯一的 cluster ID 与 member ID,避免跨集群交互导致数据损坏。
  3. etcd 在 --listen-client-urls 上接受客户端流量,通过 --advertise-client-urls 向其他成员、代理、客户端通告;常见错误是把它设为 localhost 或保留默认值,导致远程客户端无法访问。

补充:--initial-cluster 开头的命令行参数在 etcd 后续运行中会被忽略,引导完成后可移除这些环境变量/flag。后续变更成员请走 runtime configuration。

TLS:自签名证书启动示例

每个成员需要由共享集群 CA(ca.crt)签发的唯一密钥对(member.crt、member.key),peer 与 client 连接各一套。

$ etcd --name infra0 --initial-advertise-peer-urls https://10.0.1.10:2380 \
  --listen-peer-urls https://10.0.1.10:2380 \
  --listen-client-urls https://10.0.1.10:2379,https://127.0.0.1:2379 \
  --advertise-client-urls https://10.0.1.10:2379 \
  --initial-cluster-token etcd-cluster-1 \
  --initial-cluster infra0=https://10.0.1.10:2380,infra1=https://10.0.1.11:2380,infra2=https://10.0.1.12:2380 \
  --initial-cluster-state new \
  --client-cert-auth --trusted-ca-file=/path/to/ca-client.crt \
  --cert-file=/path/to/infra0-client.crt --key-file=/path/to/infra0-client.key \
  --peer-client-cert-auth --peer-trusted-ca-file=ca-peer.crt \
  --peer-cert-file=/path/to/infra0-peer.crt --peer-key-file=/path/to/infra0-peer.key

infra1 / infra2 将 infra0 与 10.0.1.10 分别替换为 infra1/10.0.1.11、infra2/10.0.1.12(含证书路径 infra1-client.crt、infra1-peer.crt 等)。

TLS:自动证书

只需加密、不需认证连接时,etcd 可自动生成密钥;初始化时每个成员基于自己的 advertised IP 与主机名生成密钥集。

$ etcd --name infra0 --initial-advertise-peer-urls https://10.0.1.10:2380 \
  --listen-peer-urls https://10.0.1.10:2380 \
  --listen-client-urls https://10.0.1.10:2379,https://127.0.0.1:2379 \
  --advertise-client-urls https://10.0.1.10:2379 \
  --initial-cluster-token etcd-cluster-1 \
  --initial-cluster infra0=https://10.0.1.10:2380,infra1=https://10.0.1.11:2380,infra2=https://10.0.1.12:2380 \
  --initial-cluster-state new \
  --auto-tls \
  --peer-auto-tls

常见错误(现象 → 原因 → 处置)

现象(日志 / 退出码) 原因 处置
etcd: infra1 not listed in the initial cluster config → exit 1 新主机未列入枚举节点列表 新集群必须把该 host 加入 --initial-cluster 成员列表
etcd: error setting up initial cluster: infra0 has different advertised URLs in the cluster and advertised peer URLs list → exit 1 节点的 advertised 地址与列表中枚举地址不一致(如 advertised http://127.0.0.1:2380 而列表为 http://10.0.1.10:2380) 让 --initial-advertise-peer-urls 与 --initial-cluster 中该成员的 URL 完全一致
etcd: conflicting cluster ID to the target cluster (c6ab534d07e8fcc4 != bc25ea2a74fb18b0). Exiting. → exit 1 使用不同配置参数加入集群导致 cluster ID 不匹配 清空 data-dir 重新引导,或修正 --initial-cluster-token / --initial-cluster-state

2.3 方式二:etcd Discovery

适用场景

用于 peer IP 事先未知的场景(云厂商、DHCP)。两种方式:etcd discovery service 与 DNS SRV records(后者见 2.4)。

discovery URL 生命周期:一个 discovery URL 标识唯一 etcd 集群,每个新集群都应使用新的 URL,不要复用;且 discovery URL 仅用于初始引导,集群运行后变更成员走 runtime reconfiguration。

自建 discovery 服务

$ curl -X PUT https://myetcd.local/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83/_config/size -d value=3

设置 size key 后即创建期望集群大小为 3 的 discovery URL,实际使用的 URL 为 https://myetcd.local/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83。

每个成员必须指定不同的 --name flag,Hostname 或 machine-id 是好选择,否则会因重名导致 discovery 失败。

$ etcd --name infra0 --initial-advertise-peer-urls http://10.0.1.10:2380 \
  --listen-peer-urls http://10.0.1.10:2380 \
  --listen-client-urls http://10.0.1.10:2379,http://127.0.0.1:2379 \
  --advertise-client-urls http://10.0.1.10:2379 \
  --discovery https://myetcd.local/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83

infra1、infra2 同构(替换 IP 与 name)。所有成员注册完成后集群即启动。

公共 discovery 服务

无现成集群时可用 discovery.etcd.io:

$ curl https://discovery.etcd.io/new?size=3
https://discovery.etcd.io/3e86b59982e49066c5d813af1c2e2579cbf573de

不指定 size 时默认为 3。对应环境变量 ETCD_DISCOVERY=https://discovery.etcd.io/3e86b59982e49066c5d813af1c2e2579cbf573de,命令行 --discovery https://discovery.etcd.io/3e86b59982e49066c5d813af1c2e2579cbf573de。各成员启动命令同上(把 --discovery 换成该 URL,name 各不相同)。可用环境变量 ETCD_DISCOVERY_PROXY 让 etcd 通过 HTTP 代理连接 discovery 服务。

错误与警告

类型 输出 说明
服务端错误 etcd: error: the cluster doesn’t have a size configuration value in https://discovery.etcd.io/3e86b59982e49066c5d813af1c2e2579cbf573de/_config → exit 1 discovery URL 未设置 size
无害警告 etcdserver: discovery token ignored since a cluster has already been initialized. Valid log found at /var/lib/etcd 本机已初始化过,discovery URL 被忽略

💡 补充:v3.7 中 v2 store 已被完全移除,「自建 discovery 服务」依赖 v2 keys API 的写法在 v3.7 集群上不再可用;v3.7 提供的 discovery 相关 flag 是另一套(--discovery-token / --discovery-endpoints 等,见第 5.8 节 dev-internal 版协议)。本页为官方 clustering 文档原文,按原文保留。

2.4 方式三:DNS Discovery

适用场景与查询顺序

--discovery-srv 指定 discovery SRV 记录所在的 DNS 域名。设置 --discovery-srv example.com 后按以下顺序查询:

  1. _etcd-server-ssl._tcp.example.com
  2. _etcd-server._tcp.example.com

若找到 _etcd-server-ssl._tcp.example.com,etcd 将尝试通过 TLS 引导。

帮助客户端发现集群的 SRV 记录查询顺序:

  1. _etcd-client._tcp.example.com
  2. _etcd-client-ssl._tcp.example.com

若找到 _etcd-client-ssl._tcp.example.com,客户端将用 SSL/TLS 通信。

TLS 注意事项(重要)

  • 使用 TLS 时,discovery SRV 记录域名(如 example.com)必须包含在证书的 DNS SAN 中(连同主机名),否则集群化失败,日志形如: [...] rejected connection from "10.0.1.11:53162" (error "remote error: tls: bad certificate", ServerName "example.com")
  • 若使用 TLS 而没有自定义 CA,discovery 域名(如 example.com)必须匹配 SRV 记录域名(如 infra1.example.com),以缓解伪造 SRV 记录指向他域的攻击。
  • --discovery-srv-name 可为查询的 SRV 名追加后缀,用于在同一域下区分多个 etcd 集群。例如 discovery-srv=example.com + -discovery-srv-name=foo 时查询:
    • _etcd-server-ssl-foo._tcp.example.com
    • _etcd-server-foo._tcp.example.com

创建 DNS SRV 记录

$ dig +noall +answer SRV _etcd-server._tcp.example.com
_etcd-server._tcp.example.com. 300 IN  SRV  0 0 2380 infra0.example.com.
_etcd-server._tcp.example.com. 300 IN  SRV  0 0 2380 infra1.example.com.
_etcd-server._tcp.example.com. 300 IN  SRV  0 0 2380 infra2.example.com.
$ dig +noall +answer SRV _etcd-client._tcp.example.com
_etcd-client._tcp.example.com. 300 IN SRV 0 0 2379 infra0.example.com.
_etcd-client._tcp.example.com. 300 IN SRV 0 0 2379 infra1.example.com.
_etcd-client._tcp.example.com. 300 IN SRV 0 0 2379 infra2.example.com.
$ dig +noall +answer infra0.example.com infra1.example.com infra2.example.com
infra0.example.com.  300  IN  A  10.0.1.10
infra1.example.com.  300  IN  A  10.0.1.11
infra2.example.com.  300  IN  A  10.0.1.12

用 DNS 引导集群(域名形式)

成员可通告域名或 IP,引导过程会解析 DNS A 记录。自 3.2 起(3.1 仅打印警告)--listen-peer-urls 与 --listen-client-urls 会拒绝接受域名作为网络接口绑定地址。--initial-advertise-peer-urls 解析后的地址必须匹配 SRV 目标解析出的地址之一。

$ etcd --name infra0 \
--discovery-srv example.com \
--initial-advertise-peer-urls http://infra0.example.com:2380 \
--initial-cluster-token etcd-cluster-1 \
--initial-cluster-state new \
--advertise-client-urls http://infra0.example.com:2379 \
--listen-client-urls http://0.0.0.0:2379 \
--listen-peer-urls http://0.0.0.0:2380

infra1、infra2 同构(域名分别换为 infra1.example.com、infra2.example.com)。

用 DNS 引导集群(IP 形式)

$ etcd --name infra0 \
--discovery-srv example.com \
--initial-advertise-peer-urls http://10.0.1.10:2380 \
--initial-cluster-token etcd-cluster-1 \
--initial-cluster-state new \
--advertise-client-urls http://10.0.1.10:2379 \
--listen-client-urls http://10.0.1.10:2379 \
--listen-peer-urls http://10.0.1.10:2380

infra1(10.0.1.11)、infra2(10.0.1.12)同构。

自 v3.1.0 起(v3.2.9 除外),当 etcd --discovery-srv=example.com 配置 TLS 时,服务端仅在提供的证书 SAN 字段包含根域 example.com 条目时才认证 peer/client。

三种引导方式对比

维度 Static etcd Discovery DNS Discovery
前置条件 成员 IP 事先已知 有可用的 etcd 集群或公共 discovery 服务 可控的 DNS SRV 记录
核心 flag --initial-cluster --discovery --discovery-srv
集群规模约定 写在 --initial-cluster 里 写在 discovery URL 的 _config/size 由 SRV 记录条数决定
典型适用 自建机房 / 固定 IP 云厂商 DHCP、临时集群 已有 DNS 基础设施、需自动化
主要坑 advertised 地址必须与列表一致 name 不能重复;URL 不能复用 证书 SAN 必须含 SRV 域名

2.5 容器部署(Docker)

引自:Run etcd clusters inside containers

本页演示用 Docker 走 static bootstrap 方式启动 etcd。

网络暴露前提:要把 etcd API 暴露给 Docker 主机之外的客户端,必须使用容器的 host IP 地址(可用 docker inspect 获取);也可以在 docker run 上加 --net=host 跳过容器独立网络栈。

镜像仓库与版本

镜像仓库有两处:quay.io/coreos/etcd;自 v3.2.5 起可用 gcr.io/etcd-development/etcd(脚本中后者覆盖前者)。版本变量 ETCD_VERSION 取当前文档版本(本册为 v3.7.0)。

单节点 etcd

export NODE1=192.168.1.21

docker volume create --name etcd-data
export DATA_DIR="etcd-data"

ETCD_VERSION=v3.7.0
REGISTRY=quay.io/coreos/etcd
# available from v3.2.5
REGISTRY=gcr.io/etcd-development/etcd

docker run \
  -p 2379:2379 \
  -p 2380:2380 \
  --volume=${DATA_DIR}:/etcd-data \
  --name etcd ${REGISTRY}:${ETCD_VERSION} \
  /usr/local/bin/etcd \
  --data-dir=/etcd-data --name node1 \
  --initial-advertise-peer-urls http://${NODE1}:2380 --listen-peer-urls http://0.0.0.0:2380 \
  --advertise-client-urls http://${NODE1}:2379 --listen-client-urls http://0.0.0.0:2379 \
  --initial-cluster node1=http://${NODE1}:2380

要素说明:

要素 值
端口映射 2379 客户端、2380 peer
容器内数据目录 /etcd-data
容器名 etcd
入口命令 /usr/local/bin/etcd
镜像 quay.io/coreos/etcd 或 gcr.io/etcd-development/etcd

查看成员:

etcdctl --endpoints=http://${NODE1}:2379 member list

3 节点 etcd 集群

REGISTRY=quay.io/coreos/etcd
# available from v3.2.5
REGISTRY=gcr.io/etcd-development/etcd

# For each machine
ETCD_VERSION=v3.7.0
TOKEN=my-etcd-token
CLUSTER_STATE=new
NAME_1=etcd-node-0
NAME_2=etcd-node-1
NAME_3=etcd-node-2
HOST_1=10.20.30.1
HOST_2=10.20.30.2
HOST_3=10.20.30.3
CLUSTER=${NAME_1}=http://${HOST_1}:2380,${NAME_2}=http://${HOST_2}:2380,${NAME_3}=http://${HOST_3}:2380
DATA_DIR=/var/lib/etcd

# For node 1
THIS_NAME=${NAME_1}
THIS_IP=${HOST_1}
docker run \
  -p 2379:2379 \
  -p 2380:2380 \
  --volume=${DATA_DIR}:/etcd-data \
  --name etcd ${REGISTRY}:${ETCD_VERSION} \
  /usr/local/bin/etcd \
  --data-dir=/etcd-data --name ${THIS_NAME} \
  --initial-advertise-peer-urls http://${THIS_IP}:2380 --listen-peer-urls http://0.0.0.0:2380 \
  --advertise-client-urls http://${THIS_IP}:2379 --listen-client-urls http://0.0.0.0:2379 \
  --initial-cluster ${CLUSTER} \
  --initial-cluster-state ${CLUSTER_STATE} --initial-cluster-token ${TOKEN}

node 2、node 3 结构完全相同,仅把 THIS_NAME=${NAME_2}/THIS_IP=${HOST_2}、THIS_NAME=${NAME_3}/THIS_IP=${HOST_3} 替换即可(docker run 与全部 flag 一字不差复用)。三节点共用同一个 CLUSTER 字符串与同一个 --initial-cluster-token,--initial-cluster-state=new。

使用 API version 3 的 etcdctl:

docker exec etcd /usr/local/bin/etcdctl put foo bar

Bare Metal

裸机 3 节点部署可参考 coreos-baremetal 仓库的 examples(https://github.com/coreos/coreos-baremetal/tree/master/examples)。

挂载证书 volume(重要坑)

etcd release 容器不含默认根证书。若要使用被根 CA 信任的 HTTPS(例如 discovery),需把证书目录挂进容器。

rkt 示例(--insecure-options=image,REGISTRY 写作 docker://gcr.io/etcd-development/etcd):

rkt run \
  --insecure-options=image \
  --volume etcd-ssl-certs-bundle,kind=host,source=/etc/ssl/certs/ca-certificates.crt \
  --mount volume=etcd-ssl-certs-bundle,target=/etc/ssl/certs/ca-certificates.crt \
  ${REGISTRY}:${ETCD_VERSION} -- --name my-name \
  --initial-advertise-peer-urls http://localhost:2380 --listen-peer-urls http://localhost:2380 \
  --advertise-client-urls http://localhost:2379 --listen-client-urls http://localhost:2379 \
  --discovery https://discovery.etcd.io/c11fbcdc16972e45253491a24fcf45e1

Docker 等价写法:

docker run \
  -p 2379:2379 \
  -p 2380:2380 \
  --volume=/etc/ssl/certs/ca-certificates.crt:/etc/ssl/certs/ca-certificates.crt \
  ${REGISTRY}:${ETCD_VERSION} \
  /usr/local/bin/etcd --name my-name \
  --initial-advertise-peer-urls http://localhost:2380 --listen-peer-urls http://localhost:2380 \
  --advertise-client-urls http://localhost:2379 --listen-client-urls http://localhost:2379 \
  --discovery https://discovery.etcd.io/86a9ff6c8cb8b4c4544c1a2f88f8b801

2.6 Kubernetes StatefulSet 部署

引自:Run etcd clusters as a Kubernetes StatefulSet

用 StatefulSet 完成 static bootstrap。

应用与验证

把清单存为 etcd.yaml 后:

$ kubectl apply --filename etcd.yaml
$ kubectl get pods
NAME     READY   STATUS    RESTARTS   AGE
etcd-0   1/1     Running   0          24m
etcd-1   1/1     Running   0          24m
etcd-2   1/1     Running   0          24m

示例镜像内含 etcdctl,可在 pod 内直接调用:

$ kubectl exec -it etcd-0 -- etcdctl member list -wtable

输出中 peer/client 地址为 http://etcd-0.etcd:2380 / http://etcd-0.etcd:2379(etcd-1、etcd-2 同理)。

Headless Service

apiVersion: v1
kind: Service
metadata:
  name: etcd
  namespace: default
spec:
  type: ClusterIP
  clusterIP: None
  selector:
    app: etcd
  publishNotReadyAddresses: true
  ports:
  - name: etcd-client
    port: 2379
  - name: etcd-server
    port: 2380
  - name: etcd-metrics
    port: 8080

要点:

  • clusterIP: None 即 headless service;
  • publishNotReadyAddresses: true 是为了让 etcd pod 在 Ready 之前就能互相解析(注释说明:理想情况应通过 SRV 记录做 peer discovery,但容器内缺少等待记录填充的逻辑,故以此规避);
  • 端口命名 etcd-client/etcd-server/etcd-metrics 与 etcd 用 SRV 做 discovery 时的命名方案一致。

StatefulSet 关键字段

apiVersion: apps/v1
kind: StatefulSet
metadata:
  namespace: default
  name: etcd
spec:
  serviceName: etcd
  replicas: 3
  podManagementPolicy: Parallel
  updateStrategy:
    type: RollingUpdate
  selector:
    matchLabels:
      app: etcd
  template:
    metadata:
      labels:
        app: etcd
      annotations:
        serviceName: etcd
字段 含义 / 注意
serviceName: etcd 用于以 headless 方式使用该 service
replicas: 3 若对已有集群扩容副本数,必须把 --initial-cluster-state 改为 existing
podManagementPolicy: Parallel 初始化时 etcd pod 必须在 Ready 之前彼此可见
updateStrategy.type: RollingUpdate 为保证可用性,任意时刻至少 51% 的 etcd 节点在线
pod 模板 annotation serviceName: etcd 会被容器配置引用为 DNS 名的一部分,必须与为 etcd 集群创建的 service 名一致(放 annotation 而非 env,是因为每个 etcd 集群只应有 1 个 service)
标签 app: etcd 只适用于「一个 namespace 一个 etcd 集群」;同 namespace 多集群需改成每集群唯一

反亲和(podAntiAffinity)

      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - etcd
            topologyKey: "kubernetes.io/hostname"

含义:若某节点上已有 app=etcd 的 pod,调度器不得再往该节点放一个,防止多个 etcd server 落在同一硬件上。

容器定义与探针

      containers:
      - name: etcd
        image: quay.io/coreos/etcd:v3.7.0
        imagePullPolicy: IfNotPresent
        ports:
        - name: etcd-client
          containerPort: 2379
        - name: etcd-server
          containerPort: 2380
        - name: etcd-metrics
          containerPort: 8080
        readinessProbe:
          httpGet:
            path: /readyz
            port: 8080
          initialDelaySeconds: 10
          periodSeconds: 5
          timeoutSeconds: 5
          successThreshold: 1
          failureThreshold: 30
        livenessProbe:
          httpGet:
            path: /livez
            port: 8080
          initialDelaySeconds: 15
          periodSeconds: 10
          timeoutSeconds: 5
          failureThreshold: 3

探针走 8080 端口而非 TLS 客户端端口(自签证书下走 TLS 会失败);/readyz 和 /livez 自 v3.5.12 起提供,此前需要在容器内额外装工具;探针参数仅为示例,生产需自行校验。

环境变量

        env:
        - name: K8S_NAMESPACE
          valueFrom:
            fieldRef:
             fieldPath: metadata.namespace
        - name: HOSTNAME
          valueFrom:
            fieldRef:
             fieldPath: metadata.name
        - name: SERVICE_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.annotations['serviceName']
        - name: ETCDCTL_ENDPOINTS
          value: $(HOSTNAME).$(SERVICE_NAME):2379
        - name: URI_SCHEME
          value: "http"

这些 env 由 Kubernetes 解析(不是在容器环境里解析)。ETCDCTL_ENDPOINTS 让容器内的 etcdctl 默认连自己这个节点,便于排障。TLS 场景下 URI_SCHEME 改为 "https",并启用 ETCDCTL_KEY=/etc/etcd/certs/client/tls.key、ETCDCTL_CERT=/etc/etcd/certs/client/tls.crt、ETCDCTL_CACERT=/etc/etcd/certs/client/ca.crt。

启动命令与参数

        command:
        - /usr/local/bin/etcd
        args:
        - --name=$(HOSTNAME)
        - --data-dir=/data
        - --wal-dir=/data/wal
        - --listen-peer-urls=$(URI_SCHEME)://0.0.0.0:2380
        - --listen-client-urls=$(URI_SCHEME)://0.0.0.0:2379
        - --advertise-client-urls=$(URI_SCHEME)://$(HOSTNAME).$(SERVICE_NAME):2379
        - --initial-cluster-state=new
        - --initial-cluster-token=etcd-$(K8S_NAMESPACE)
        - --initial-cluster=etcd-0=$(URI_SCHEME)://etcd-0.$(SERVICE_NAME):2380,etcd-1=$(URI_SCHEME)://etcd-1.$(SERVICE_NAME):2380,etcd-2=$(URI_SCHEME)://etcd-2.$(SERVICE_NAME):2380
        - --initial-advertise-peer-urls=$(URI_SCHEME)://$(HOSTNAME).$(SERVICE_NAME):2380
        - --listen-metrics-urls=http://0.0.0.0:8080

DNS 域名规则:$(HOSTNAME).$(SERVICE_NAME),展开后为 etcd-0.etcd、etcd-1.etcd、etcd-2.etcd;peer 与 client 均走该域名(2380 / 2379)。

参数注意事项:

参数 说明
--initial-cluster-state=new 必须为 new 才能完成初始 bootstrap;扩容副本时改为 existing,设置错误时 etcd 会尝试启动但安全失败
--initial-cluster-token=etcd-$(K8S_NAMESPACE) 建议每集群唯一;同 namespace 多集群需额外保证唯一性
--initial-cluster 必须与 replicas 数量匹配,单个 peer 的参数化形式为 etcd-0=$(URI_SCHEME)://etcd-0.$(SERVICE_NAME):2380
--wal-dir=/data/wal 把 WAL 与 data 目录放在一起,示例明确说明生产不理想,应单独挂卷
--listen-metrics-urls=http://0.0.0.0:8080 用于避免启用 TLS 后探针失败

可选 / 注释项:

  • 自动压缩:--auto-compaction-mode=periodic、--auto-compaction-retention=10m
  • 客户端 TLS:--client-cert-auth --trusted-ca-file=$(ETCDCTL_CACERT) --cert-file=$(ETCDCTL_CERT) --key-file=$(ETCDCTL_KEY)
  • peer TLS:--peer-client-cert-auth --peer-trusted-ca-file=/etc/etcd/certs/server/ca.crt --peer-cert-file=/etc/etcd/certs/server/tls.crt --peer-key-file=/etc/etcd/certs/server/tls.key

存储

容器挂载:volumeMounts: - name: etcd-data, mountPath: /data;TLS 时另挂 /etc/etcd/certs/client 与 /etc/etcd/certs/server(readOnly),对应 secret etcd-client-tls / etcd-server-tls。

  volumeClaimTemplates:
  - metadata:
      name: etcd-data
    spec:
      accessModes: ["ReadWriteOnce"]
      # storageClassName: ""
      resources:
        requests:
          storage: 1Gi

注意:由 volumeClaimTemplate 为每个副本创建的 PVC 后续不易扩容;部分集群需显式设置 storageClassName,本例使用默认 storage class。

证书生成(cert-manager)

$ helm upgrade --install --create-namespace --namespace cert-manager cert-manager cert-manager --repo https://charts.jetstack.io --set crds.enabled=true

ClusterIssuer(issuer.yaml):

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: selfsigned
spec:
  selfSigned: {}

Certificate(certificates.yaml)分别生成 etcd-server(secretName etcd-server-tls)与 etcd-client(secretName etcd-client-tls),均 issuerRef: {name: selfsigned, kind: ClusterIssuer}、commonName: etcd。

dnsNames 必须是证书所有合法主机名的穷举列表,含:

  • etcd、etcd.default、etcd.default.svc.cluster.local
  • etcd-0、etcd-0.etcd、etcd-0.etcd.default、etcd-0.etcd.default.svc、etcd-0.etcd.default.svc.cluster.local
  • (etcd-1、etcd-2 同构)

💡 补充:这份 dnsNames 清单是 K8s 场景下最容易漏配的一项——漏掉 etcd-0.etcd 这类短域名会直接导致 peer TLS 握手失败。上线前建议用 openssl x509 -in tls.crt -noout -text 核对 SAN 列表。

2.7 演示集群快速拉起(tasks/operator 版)

官方 tasks/operator/how-to-setup-cluster 页给了一套「3 节点演示集群」的最短路径,与 2.2/2.3 同源但变量命名更简洁,适合本地验证或给新人做第一次上手。

方式一:静态配置

先在每台 etcd 节点上定义集群成员变量:

TOKEN=token-01
CLUSTER_STATE=new
NAME_1=machine-1
NAME_2=machine-2
NAME_3=machine-3
HOST_1=10.240.0.17
HOST_2=10.240.0.18
HOST_3=10.240.0.19
CLUSTER=${NAME_1}=http://${HOST_1}:2380,${NAME_2}=http://${HOST_2}:2380,${NAME_3}=http://${HOST_3}:2380

然后在每台机器上执行(只改 THIS_NAME / THIS_IP,其余完全一致)。以 machine 1 为例:

# For machine 1
THIS_NAME=${NAME_1}
THIS_IP=${HOST_1}
etcd --data-dir=data.etcd --name ${THIS_NAME} \
	--initial-advertise-peer-urls http://${THIS_IP}:2380 --listen-peer-urls http://${THIS_IP}:2380 \
	--advertise-client-urls http://${THIS_IP}:2379 --listen-client-urls http://${THIS_IP}:2379 \
	--initial-cluster ${CLUSTER} \
	--initial-cluster-state ${CLUSTER_STATE} --initial-cluster-token ${TOKEN}

关键参数作用(与 2.2 一致,此处补齐逐项说明):

参数 作用
--data-dir=data.etcd 数据落盘目录,相对路径
--name ${THIS_NAME} 本节点成员名,必须与 --initial-cluster 列表中的名字一致
--initial-advertise-peer-urls http://${THIS_IP}:2380 告知集群内其他成员本节点被访问的 peer 地址(2380 为 peer 端口)
--listen-peer-urls http://${THIS_IP}:2380 本节点实际监听的 peer 地址
--advertise-client-urls http://${THIS_IP}:2379 对外公告的客户端地址(2379 为 client 端口)
--listen-client-urls http://${THIS_IP}:2379 本节点实际监听的客户端地址
--initial-cluster ${CLUSTER} 初始成员列表,格式 name=peerURL,逗号分隔,所有节点必须给完全相同的值
--initial-cluster-state ${CLUSTER_STATE} 取 new 表示新建集群
--initial-cluster-token ${TOKEN} 集群唯一标识 token(token-01),同一 token 下的成员才属同一集群,可防止误跨集群加入

方式二:使用公共发现服务(--discovery)

先申请一个 discovery URL:

curl https://discovery.etcd.io/new?size=3
https://discovery.etcd.io/a81b5818e67a6ea83e9d4daea5ecbc92

返回的 URL 即 discovery token(示例中为 a81b5818e67a6ea83e9d4daea5ecbc92)。把它赋给 DISCOVERY,启动命令把 --initial-cluster ${CLUSTER} 替换为 --discovery ${DISCOVERY}:

DISCOVERY=https://discovery.etcd.io/a81b5818e67a6ea83e9d4daea5ecbc92

THIS_NAME=${NAME_1}
THIS_IP=${HOST_1}
etcd --data-dir=data.etcd --name ${THIS_NAME} \
	--initial-advertise-peer-urls http://${THIS_IP}:2380 --listen-peer-urls http://${THIS_IP}:2380 \
	--advertise-client-urls http://${THIS_IP}:2379 --listen-client-urls http://${THIS_IP}:2379 \
	--discovery ${DISCOVERY} \
	--initial-cluster-state ${CLUSTER_STATE} --initial-cluster-token ${TOKEN}

machine 2、machine 3 同样只改 THIS_NAME / THIS_IP。注意:此时仍需保留 --initial-cluster-state 与 --initial-cluster-token。

验证连接:

export ETCDCTL_API=3
HOST_1=10.240.0.17
HOST_2=10.240.0.18
HOST_3=10.240.0.19
ENDPOINTS=$HOST_1:2379,$HOST_2:2379,$HOST_3:2379

etcdctl --endpoints=$ENDPOINTS member list

注意事项:

  • 必须先 export ETCDCTL_API=3 才能使用 v3 API 的 etcdctl;
  • --initial-cluster 在所有节点上必须逐字一致;
  • peer 端口 2380 与 client 端口 2379 不要混淆;
  • 发现服务方式下 --initial-cluster 与 --discovery 二选一,不可同时用作互斥的初始化来源。

3. 配置全参(flag / 环境变量 / 默认值)

etcd 有三种配置途径:命令行 flag、环境变量、配置文件。

3.1 配置途径与优先级(有坑)

  • 环境变量命名规则:flag 名前加 ETCD_ 前缀,全大写、snake_case。例如 --some-flag → ETCD_SOME_FLAG。
  • 优先级:
    • 命令行 flag 优先于环境变量;
    • 一旦提供配置文件,所有命令行 flag 与环境变量都会被忽略(互斥,不是叠加)。
  • 配置文件为 YAML,key 就是命令行 flag 名,通过 --config-file 或环境变量 ETCD_CONFIG_FILE 指定路径。
  • 持续时间字段的坑:命令行里可写人类可读串(如 10m、5s),但在配置文件中只能填纳秒整数(Go time.Duration 反序列化限制)。

受影响 flag 清单:--grpc-keepalive-min-time、--grpc-keepalive-interval、--grpc-keepalive-timeout、--backend-batch-interval、--corrupt-check-time、--compact-hash-check-time、--compaction-sleep-interval、--watch-progress-notify-interval、--warning-apply-duration、--warning-unary-request-duration、--downgrade-check-time。

# 正确写法(纳秒)
watch-progress-notify-interval: 600000000000
# 错误写法(配置文件里不认人类可读串)
# watch-progress-notify-interval: '10m'

💡 补充:这条限制是实战中最容易踩的坑之一——从命令行迁移到 --config-file 时,把 '10m' 原样搬进 YAML 会直接导致启动失败或值被解析为 0。建议迁移时把上述 11 个 flag 单独列一份换算表(1s = 1000000000)。

3.2 Member flags(24 条)

命令行 flag 环境变量 说明 默认值
--name ETCD_NAME 本成员的可读名称 'default'
--data-dir ETCD_DATA_DIR 数据目录路径 '${name}.etcd'
--wal-dir ETCD_WAL_DIR 专用 wal 目录路径 ''
--snapshot-count ETCD_SNAPSHOT_COUNT 触发落盘快照的已提交事务数 '10000'
--heartbeat-interval ETCD_HEARTBEAT_INTERVAL 心跳间隔,单位毫秒 '100'
--election-timeout ETCD_ELECTION_TIMEOUT 选举超时,单位毫秒 '1000'
--initial-election-tick-advance ETCD_INITIAL_ELECTION_TICK_ADVANCE 启动时快进初始选举 tick 以加速选举 'true'
--listen-peer-urls ETCD_LISTEN_PEER_URLS 监听 peer 流量的 URL 列表 'http://localhost:2380'
--listen-client-urls ETCD_LISTEN_CLIENT_URLS 监听客户端 grpc 流量的 URL 列表;若未指定 --listen-client-http-urls 则同时承载 http 'http://localhost:2379'
--listen-client-http-urls ETCD_LISTEN_CLIENT_HTTP_URLS 仅监听 http 客户端流量的 URL 列表;启用后会从 --listen-client-urls 移除 http 服务 ''
--max-snapshots ETCD_MAX_SNAPSHOTS 保留的快照文件最大数(0 为不限) '5'
--max-wals ETCD_MAX_WALS 保留的 wal 文件最大数(0 为不限) '5'
--memory-mlock ETCD_MEMORY_MLOCK 强制 etcd 页面(尤其 bbolt)常驻 RAM 无值(布尔开关)
--quota-backend-bytes ETCD_QUOTA_BACKEND_BYTES backend 大小超配额时告警(0 表示使用低空间配额) '0'
--backend-bbolt-freelist-type ETCD_BACKEND_BBOLT_FREELIST_TYPE boltdb backend 使用的 freelist 类型,支持 array 和 map 'map'
--backend-batch-interval ETCD_BACKEND_BATCH_INTERVAL 提交 backend 事务前的最长等待时间 ''
--backend-batch-limit ETCD_BACKEND_BATCH_LIMIT 提交 backend 事务前允许的最大操作数 '0'
--max-txn-ops ETCD_MAX_TXN_OPS 单个事务允许的最大操作数 '128'
--max-request-bytes ETCD_MAX_REQUEST_BYTES 服务端接受的最大客户端请求字节数 '1572864'
--grpc-keepalive-min-time ETCD_GRPC_KEEPALIVE_MIN_TIME 客户端 ping 服务端的最小间隔 '5s'
--grpc-keepalive-interval ETCD_GRPC_KEEPALIVE_INTERVAL 服务端到客户端 ping 的频率(0 关闭) '2h'
--grpc-keepalive-timeout ETCD_GRPC_KEEPALIVE_TIMEOUT 关闭无响应连接前的额外等待时长(0 关闭) '20s'
--socket-reuse-port ETCD_SOCKET_REUSE_PORT 在 listener 上启用 SO_REUSEPORT,允许重绑已占用端口 'false'
--socket-reuse-address ETCD_SOCKET_REUSE_ADDRESS 在 listener 上启用 SO_REUSEADDR,允许绑定 TIME_WAIT 状态的地址 'false'

💡 补充:--snapshot-count 在本册素材中存在两处不同记载:本表(Configuration options 页)为 '10000';而 batch-05 的 maintenance 文档称「自 v3.2 起默认值从 10,000 改为 100,000」。同时 upgrade_3_6 页把「--snapshot-count 从 100000 变为 10000」列为 v3.6 的默认值变更。三处并列呈现,请以实际版本的 etcd -h 输出为准,不要擅自统一。

3.3 Clustering flags(16 条)

命令行 flag 环境变量 说明 默认值
--initial-advertise-peer-urls ETCD_INITIAL_ADVERTISE_PEER_URLS 向集群其余成员通告的本成员 peer URL 列表 'http://localhost:2380'
--initial-cluster ETCD_INITIAL_CLUSTER 引导用的初始集群配置 'default=http://localhost:2380'
--initial-cluster-state ETCD_INITIAL_CLUSTER_STATE 初始集群状态(new 或 existing) 'new'
--initial-cluster-token ETCD_INITIAL_CLUSTER_TOKEN 引导期集群 token,防止多集群间意外交叉交互 'etcd-cluster'
--advertise-client-urls ETCD_ADVERTISE_CLIENT_URLS 向外部通告的客户端 URL 列表,客户端库据此连接集群 'http://localhost:2379'
--discovery ETCD_DISCOVERY 用于引导集群的 discovery URL ''
--discovery-fallback ETCD_DISCOVERY_FALLBACK discovery 服务失败时的行为(exit 或 proxy);proxy 仅支持 v2 API 'proxy'
--discovery-proxy ETCD_DISCOVERY_PROXY 访问 discovery 服务使用的 HTTP 代理 ''
--discovery-srv ETCD_DISCOVERY_SRV 用于引导集群的 DNS srv 域名 ''
--discovery-srv-name ETCD_DISCOVERY_SRV_NAME 引导时查询的 dns srv 名称后缀 ''
--strict-reconfig-check ETCD_STRICT_RECONFIG_CHECK 拒绝会导致丢 quorum 的重配置请求 'true'
--pre-vote ETCD_PRE_VOTE 启用 raft Pre-Vote 算法,防止被分区节点重新加入时造成扰动 'true'
--auto-compaction-retention ETCD_AUTO_COMPACTION_RETENTION 自动压缩保留时长,0 表示禁用自动压缩 '0'
--auto-compaction-mode ETCD_AUTO_COMPACTION_MODE 解释 --auto-compaction-retention 的方式:periodic(按时间,无单位时默认小时,如 '5m')或 revision(按 revision 数) 'periodic'
--enable-v2 ETCD_ENABLE_V2 接受 etcd V2 客户端请求。已废弃,计划在 v3.6 下线 'false'
--v2-deprecation ETCD_V2_DEPRECATION v2store 下线阶段,可 opt-in 更高兼容模式 'not-yet'

--v2-deprecation 取值说明:

取值 含义 备注
'not-yet' 若 v2store 有实质内容则告警 v3.5 默认
'write-only' 不允许自定义 v2 state v3.6 与 v3.7 默认
'write-only-skip-check' 不支持自定义 v2 state,存在则忽略 v3.5.32+、v3.6.13+、v3.7.0+ 可用,风险自负
'write-only-drop-data' 自定义 v2 state 会被删除 计划成为 v3.8 默认
'gone' v2store 不再维护 —

💡 补充:Configuration 表中 --enable-v2 与 --v2-deprecation 的默认值分别写作 'false' 与 'not-yet'(v3.5 口径),而 v3.7 中 v2 store 已被完全移除、--enable-v2 也不复存在(见第 14 章 v3.6→v3.7 的 Flags removed)。本表按配置页原文保留。

3.4 Security flags / TLS(21 条)

命令行 flag 环境变量 说明 默认值
--cert-file ETCD_CERT_FILE 客户端服务端 TLS 证书文件路径 ''
--key-file ETCD_KEY_FILE 客户端服务端 TLS 私钥文件路径 ''
--client-cert-auth ETCD_CLIENT_CERT_AUTH 启用客户端证书认证。官方建议开启以防未认证客户端攻击(如 CVE-2023-44487),尤其是 etcd 作为公共服务运行时 'false'
--client-crl-file ETCD_CLIENT_CRL_FILE 客户端证书吊销列表文件路径 ''
--client-cert-allowed-hostname ETCD_CLIENT_CERT_ALLOWED_HOSTNAME 客户端证书认证允许的 SAN 主机名,逗号分隔 ''
--trusted-ca-file ETCD_TRUSTED_CA_FILE 客户端服务端 TLS 受信 CA 证书路径。注意:设置此参数会自动启用客户端证书认证,无论 --client-cert-auth 为何值 ''
--auto-tls ETCD_AUTO_TLS 客户端 TLS 使用自动生成证书 'false'
--peer-cert-file ETCD_PEER_CERT_FILE peer 服务端 TLS 证书文件路径 ''
--peer-key-file ETCD_PEER_KEY_FILE peer 服务端 TLS 私钥文件路径 ''
--peer-client-cert-auth ETCD_PEER_CLIENT_CERT_AUTH 启用 peer 客户端证书认证。建议开启以防伪造 peer 攻击(如 CVE-2023-44487) 'false'
--peer-trusted-ca-file ETCD_PEER_TRUSTED_CA_FILE peer 服务端 TLS 受信 CA 文件路径 ''
--peer-cert-allowed-cn ETCD_PEER_CERT_ALLOWED_CN peer 间 TLS 认证允许的 CN,逗号分隔 ''
--peer-cert-allowed-hostname ETCD_PEER_CERT_ALLOWED_HOSTNAME peer 间 TLS 认证允许的 SAN 主机名,逗号分隔 ''
--peer-auto-tls ETCD_PEER_AUTO_TLS 未提供 --peer-key-file 与 --peer-cert-file 时,peer TLS 使用自生成证书 'false'
--self-signed-cert-validity ETCD_SELF_SIGNED_CERT_VALIDITY 指定 ClientAutoTLS / PeerAutoTLS 时自动生成证书的有效期,单位年 '1'
--peer-crl-file ETCD_PEER_CRL_FILE peer 证书吊销列表文件路径 ''
--cipher-suites ETCD_CIPHER_SUITES client/server 与 peer 间支持的 TLS 加密套件列表,逗号分隔(留空由 Go 自动填充) ''
--cors ETCD_CORS CORS 允许的源白名单,逗号分隔(空或 * 表示允许全部) '*'
--host-whitelist ETCD_HOST_WHITELIST 服务端非安全模式下可接受的 HTTP 客户端请求主机名(空或 * 表示允许全部) '*'
--tls-min-version ETCD_TLS_MIN_VERSION 支持的最低 TLS 版本 'TLS1.2'
--tls-max-version ETCD_TLS_MAX_VERSION 支持的最高 TLS 版本(留空由 Go 自动填充) ''

3.5 Auth flags(3 条)

命令行 flag 环境变量 说明 默认值
--auth-token ETCD_AUTH_TOKEN 指定 v3 认证 token 类型及选项(simple 或 jwt) 'simple'
--bcrypt-cost ETCD_BCRYPT_COST 认证密码 bcrypt 哈希的 cost/强度,合法取值 4–31 10
--auth-token-ttl ETCD_AUTH_TOKEN_TTL auth token TTL,单位秒 300

3.6 Profiling and monitoring flags(3 条)

命令行 flag 环境变量 说明 默认值
--enable-pprof ETCD_ENABLE_PPROF 通过 HTTP 启用运行时 profiling 数据,地址为 client URL + /debug/pprof/ 'false'
--metrics ETCD_METRICS 导出指标的详尽程度,指定 extensive 会包含服务端 grpc histogram 指标 'basic'
--listen-metrics-urls ETCD_LISTEN_METRICS_URLS metrics 与 health 端点监听的 URL 列表 ''

3.7 Logging flags(7 条)

命令行 flag 环境变量 说明 默认值
--logger ETCD_LOGGER 当前仅支持 zap 结构化日志 'zap'
--log-outputs ETCD_LOG_OUTPUTS 指定 stdout 或 stderr(即使在 systemd 下也跳过 journald),或逗号分隔的输出目标列表 'default'
--log-level ETCD_LOG_LEVEL 日志级别,仅支持 debug/info/warn/error/panic/fatal 'info'
--log-format ETCD_LOG_FORMAT 日志格式,仅支持 json、console 'json'
--enable-log-rotation ETCD_ENABLE_LOG_ROTATION 对单个 log-outputs 文件目标启用日志轮转 'false'
--log-rotation-config-json ETCD_LOG_ROTATION_CONFIG_JSON 启用日志轮转时的 JSON 配置:MaxSize(MB)、MaxAge(天,0 不限)、MaxBackups(0 不限)、LocalTime(使用本机时区)、Compress(gzip) '{"maxsize": 100, "maxage": 0, "maxbackups": 0, "localtime": false, "compress": false}'
--warning-unary-request-duration ETCD_WARNING_UNARY_REQUEST_DURATION 一元请求超过该时长则记录警告日志 '300ms'

3.8 Distributed tracing flags(5 条)

命令行 flag 环境变量 说明 默认值
--enable-distributed-tracing ETCD_ENABLE_DISTRIBUTED_TRACING 启用分布式追踪 'false'
--distributed-tracing-address ETCD_DISTRIBUTED_TRACING_ADDRESS 分布式追踪 collector 地址 'localhost:4317'
--distributed-tracing-service-name ETCD_DISTRIBUTED_TRACING_SERVICE_NAME 分布式追踪服务名,所有 etcd 实例必须一致 'etcd'
--distributed-tracing-instance-id ETCD_DISTRIBUTED_TRACING_INSTANCE_ID 分布式追踪实例 ID,每个 etcd 实例必须唯一 ''
--distributed-tracing-sampling-rate ETCD_DISTRIBUTED_TRACING_SAMPLING_RATE 每百万 span 采集的采样数 '0'

v3.7 提示:若干 --experimental-* flag 已在 v3.7 中转正或改名,需替换为稳定版本(映射见第 14.4 节)。

3.9 v2 Proxy flags(6 条,文档标注将在 v3.6 废弃)

命令行 flag 环境变量 说明 默认值
--proxy ETCD_PROXY 代理模式设置(off、readonly、on) 'off'
--proxy-failure-wait ETCD_PROXY_FAILURE_WAIT endpoint 保持失败状态的时长,毫秒 5000
--proxy-refresh-interval ETCD_PROXY_REFRESH_INTERVAL endpoint 刷新间隔,毫秒 30000
--proxy-dial-timeout ETCD_PROXY_DIAL_TIMEOUT 拨号超时,毫秒 1000
--proxy-write-timeout ETCD_PROXY_WRITE_TIMEOUT 写超时,毫秒 5000
--proxy-read-timeout ETCD_PROXY_READ_TIMEOUT 读超时,毫秒 0

💡 提示:这一组 flag 在 v3.6 中已被移除(见第 14.4 节 Flags removed 清单),v3.7 同样不存在。保留本表是为了让老配置迁移时有对照。

3.10 Features flags(11 条)

命令行 flag 环境变量 说明 默认值
--corrupt-check-time ETCD_CORRUPT_CHECK_TIME 集群 corruption 检查的时间间隔 '0s'
--compact-hash-check-time ETCD_COMPACT_HASH_CHECK_TIME leader 检查 follower compaction hash 的时间间隔 '1m'
--compaction-batch-limit ETCD_COMPACTION_BATCH_LIMIT 每个 compaction 批次删除的最大 revision 数 1000
--peer-skip-client-san-verification ETCD_PEER_SKIP_CLIENT_SAN_VERIFICATION peer 连接时跳过客户端证书 SAN 字段校验 'false'
--watch-progress-notify-interval ETCD_WATCH_PROGRESS_NOTIFY_INTERVAL 周期性 watch 进度通知的时间间隔 '10m'
--warning-apply-duration ETCD_WARNING_APPLY_DURATION 请求处理超过该时长则产生告警 '100ms'
--bootstrap-defrag-threshold-megabytes ETCD_BOOTSTRAP_DEFRAG_THRESHOLD_MEGABYTES 启动时若碎片整理能释放至少该阈值(MB)磁盘空间则执行;需非 0 值才生效 无默认值标注
--max-learners ETCD_MAX_LEARNERS 集群中允许的 learner 成员最大数 '1'
--compaction-sleep-interval ETCD_COMPACTION_SLEEP_INTERVAL 每个 compaction 批次之间的 sleep 间隔 无默认值标注
--downgrade-check-time ETCD_DOWNGRADE_CHECK_TIME 两次 downgrade 状态检查之间的时间间隔 无默认值标注
--snapshot-catchup-entries ETCD_SNAPSHOT_CATCHUP_ENTRIES follower 在 raft 存储条目压缩后追赶所需的条目数 无默认值标注

素材说明:--bootstrap-defrag-threshold-megabytes、--compaction-sleep-interval、--downgrade-check-time、--snapshot-catchup-entries 四个 flag 在官方配置表中未标注默认值,本册如实保留「无默认值标注」,不做推测。

3.11 Unsafe features flags(2 条)

警告:使用不安全特性会破坏共识协议给出的保证。

命令行 flag 环境变量 说明 默认值
--force-new-cluster ETCD_FORCE_NEW_CLUSTER 强制创建新的单成员集群 'false'
--unsafe-no-fsync ETCD_UNSAFE_NO_FSYNC 关闭 fsync,不安全,会导致数据丢失 'false'

3.12 Feature Gates

命令行写法(9 条)

命令行 flag 说明 默认值
--feature-gates=AllAlpha=true|false 启用/禁用所有 alpha 特性 false
--feature-gates=AllBeta=true|false 启用/禁用所有 beta 特性 false
--feature-gates=CompactHashCheck=true 启用 leader 周期性检查 follower compaction hashes 取代 --experimental-compact-hash-check-enabled
--feature-gates=InitialCorruptCheck=true 在提供 client/peer 流量前做 corruption 检查 取代 --experimental-initial-corrupt-check
--feature-gates=LeaseCheckpoint=true 启用 primary lessor 持久化 lease remainingTTL,防止长生命周期 lease 无限自动续期 取代 --experimental-enable-lease-checkpoint
--feature-gates=LeaseCheckpointPersist=true 持久化 remainingTTL 防止长生命周期 lease 无限自动续期。v3.6 中始终启用;用于从启用了该特性的 v3.5 集群平滑升级 取代 --experimental-enable-lease-checkpoint-persist
--feature-gates=SetMemberLocalAddr=true 允许设置成员的本地地址 —
--feature-gates=StopGRPCServiceOnDefrag=true 碎片整理期间让 gRPC 服务停止响应客户端请求 取代 --experimental-stop-grpc-service-on-defrag
--feature-gates=TxnModeWriteWithSharedBuffer=true 写事务在只读检查操作中使用共享 buffer 取代 --experimental-txn-mode-write-with-shared-buffer

命令行与 YAML 配置文件两种写法:

# 命令行
--feature-gates=...,StopGRPCServiceOnDefrag=true
# YAML 配置文件
feature-gates: ...,StopGRPCServiceOnDefrag=true

特性门清单(7 条)

引自:Feature Gates

Feature Default Stage Details
CompactHashCheck false Alpha 在提供任何 client/peer 流量前检查数据损坏
InitialCorruptCheck false Alpha 让 leader 周期性检查 follower 的 compaction hashes
LeaseCheckpoint false Alpha 让 leader 定期向其他成员发送 checkpoint,防止 leader 变更时 remaining TTL 被重置
LeaseCheckpointPersist false Alpha 持久化 remainingTTL,防止长生命周期 lease 无限自动续期
SetMemberLocalAddr false Alpha 使用 initial-advertise-peer-urls 中第一个指定且非 loopback 的本地地址作为与 peer 通信的本地地址
StopGRPCServiceOnDefrag false Alpha 碎片整理期间让 etcd gRPC 服务停止响应客户端请求
TxnModeWriteWithSharedBuffer true Beta 让写事务在其只读检查操作中使用共享 buffer

另可通过 AllAlpha=true|false(默认 false)与 AllBeta=true|false(默认 false)批量开关所有 alpha / beta 特性。用 etcd -h 可查看完整列表。

💡 补充:注意官方两处对 CompactHashCheck 与 InitialCorruptCheck 的 Details 描述在「Configuration options」页与「Feature Gates」页顺序相反(配置页把 InitialCorruptCheck 描述为「在提供流量前做检查」,Feature Gates 页把 CompactHashCheck 描述为「在提供流量前检查数据损坏」)。本册按各自页面原文保留,未做统一。

特性阶段说明

阶段 含义
Alpha 默认关闭;可能有 bug,开启可能暴露问题;支持可能随时无通知地移除;API 可能在后续版本中以不兼容方式变更;因 bug 风险高且缺乏长期支持,建议仅在短生命周期测试集群中使用
Beta 默认开启;经过充分测试,开启被认为是安全的;特性整体支持不会取消,但细节可能变更;因广泛采用可能暴露新的隐蔽 bug,建议仅用于非业务关键场景(官方鼓励试用 Beta 特性并反馈)
GA(stable) 特性始终启用,无法关闭;对应的 feature gate 不再需要;稳定特性会在后续多个版本中持续存在
Deprecated feature gate 不再使用;特性已升为 GA 或被移除

embed.EtcdServer 结构体变更(3.6)

3.6 中 embed.Config 新增字段 ServerFeatureGate(类型 featuregate.FeatureGate),用于取代下列已废弃的 experimental 字段(标注将于 v3.7 下线):

废弃字段(json 名) 替代 Feature Gate
ExperimentalCompactHashCheckEnabled(experimental-compact-hash-check-enabled) CompactHashCheck
ExperimentalInitialCorruptCheck(experimental-initial-corrupt-check) InitialCorruptCheck
ExperimentalTxnModeWriteWithSharedBuffer(experimental-txn-mode-write-with-shared-buffer) TxnModeWriteWithSharedBuffer
ExperimentalStopGRPCServiceOnDefrag(experimental-stop-grpc-service-on-defrag) StopGRPCServiceOnDefrag
ExperimentalEnableLeaseCheckpoint(experimental-enable-lease-checkpoint) LeaseCheckpoint
ExperimentalEnableLeaseCheckpointPersist(experimental-enable-lease-checkpoint-persist) LeaseCheckpointPersist

3.13 配置参数计数核对

分组 条数
Member flags 24
Clustering flags 16
Security flags(TLS) 21
Auth flags 3
Profiling and monitoring flags 3
Logging flags 7
Distributed tracing flags 5
v2 Proxy flags 6
Features flags 11
Feature Gates(命令行写法) 9
Unsafe features flags 2
命令行 flag 合计 107
Feature Gates 特性清单(Alpha/Beta 表) 7

💡 补充:107 条命令行 flag 中包含 9 条 --feature-gates=... 写法(其中 AllAlpha / AllBeta 为聚合开关,与 7 条具体特性不重复计数)。Feature Gates 特性清单 7 条与之对应。


4. 硬件、支持平台与系统调优

引自:Hardware recommendations、Supported platforms、Tuning

开发与测试环境下 etcd 资源需求很低(笔记本或廉价云机即可)。以下为生产环境建议,属起点而非硬性规则,上线前务必用模拟负载测试。

4.1 硬件建议

CPU

  • 大多数部署不需要很多 CPU:典型集群 2–4 核即可平稳运行。
  • 重负载部署(服务数千客户端、每秒数万请求)通常受 CPU 限制(etcd 可从内存直接响应),一般需要 8–16 个专用核。

内存

  • etcd 内存占用较小,但性能依赖充足内存:服务端会积极缓存 key-value 数据,其余内存主要用于跟踪 watcher。
  • 通常 8GB 足够;重负载(数千 watcher、数百万 key)按需分配 16GB 到 64GB。

磁盘(最关键因素)

  • 快速磁盘是 etcd 性能与稳定性的最关键因素。
  • 慢磁盘会拉高请求延迟并可能损害集群稳定性:多数派成员必须将每个请求落盘,etcd 还会增量 checkpoint 状态以截断日志;写入过慢会导致心跳超时并触发选举。
  • 可用 fio 等基准工具判断磁盘是否够快。
  • 顺序 IOPS:通常需要 50 sequential IOPS(如 7200 RPM 盘);重负载集群建议 500 sequential IOPS(如典型本地 SSD 或高性能虚拟化块设备)。
    • 注意多数云厂商公布的是并发 IOPS,可能是顺序 IOPS 的 10 倍;实测顺序 IOPS 请用 diskbench 或 fio。
  • 磁盘带宽:需求不高,但带宽越大故障成员追赶越快。通常 10MB/s 可在 15 秒内恢复 100MB 数据;大型集群建议 100MB/s 或更高,以便 15 秒内恢复 1GB 数据。
  • 尽量使用 SSD 作为 etcd 存储后端:写入延迟更低、抖动更小。若必须用机械盘则选最快规格(15,000 RPM)。RAID 0 对机械盘和 SSD 都是有效的提速手段。至少有 3 个成员时,RAID 的镜像/奇偶校验变体没有必要——etcd 的一致性复制已提供高可用。

网络

  • 多成员部署受益于快速可靠的网络;不可靠、频繁分区的网络会损害可用性。低延迟保证成员通信迅速,高带宽缩短故障成员恢复时间。
  • 常见部署 1GbE 足够;大型 etcd 集群用 10GbE 可降低平均恢复时间(MTTR)。
  • 尽量把成员部署在同一数据中心以避免延迟开销、降低分区概率;若需跨数据中心容灾,选择离现有机房更近的机房,并参阅 tuning 文档了解跨数据中心部署。

示例硬件配置(假设机器完全专用于 etcd)

混跑其他应用会造成资源争抢、导致集群不稳定。

Small cluster(少于 100 客户端、少于 200 请求/秒、数据不超过 100MB;示例负载:50 节点 Kubernetes 集群)

Provider Type vCPUs Memory (GB) Max concurrent IOPS Disk bandwidth (MB/s)
AWS m4.large 2 8 3600 56.25
GCE n1-standard-2 + 50GB PD SSD 2 7.5 1500 25

Medium cluster(少于 500 客户端、少于 1,000 请求/秒、数据不超过 500MB;示例负载:250 节点 Kubernetes 集群)

Provider Type vCPUs Memory (GB) Max concurrent IOPS Disk bandwidth (MB/s)
AWS m4.xlarge 4 16 6000 93.75
GCE n1-standard-4 + 150GB PD SSD 4 15 4500 75

Large cluster(少于 1,500 客户端、少于 10,000 请求/秒、数据不超过 1GB;示例负载:1,000 节点 Kubernetes 集群)

Provider Type vCPUs Memory (GB) Max concurrent IOPS Disk bandwidth (MB/s)
AWS m4.2xlarge 8 32 8000 125
GCE n1-standard-8 + 250GB PD SSD 8 30 7500 125

xLarge cluster(超过 1,500 客户端、超过 10,000 请求/秒、数据超过 1GB;示例负载:3,000 节点 Kubernetes 集群)

Provider Type vCPUs Memory (GB) Max concurrent IOPS Disk bandwidth (MB/s)
AWS m4.4xlarge 16 64 16,000 250
GCE n1-standard-16 + 500GB PD SSD 16 60 15,000 250

💡 补充:这张表把「客户端数 / QPS / 数据量」与「机型」直接挂钩,是做容量规划时最实用的一张表。Kubernetes 场景可直接按节点数对号入座(50 / 250 / 1000 / 3000 节点)。

4.2 支持的平台与架构

支持层级定义

层级 定义
Tier 1 由 etcd maintainers 完全支持;保证通过所有测试,包括 functional 与 robustness 测试
Tier 2 保证通过 integration 与 end-to-end 测试,但不保证 functional 或 robustness 测试
Tier 3 仅保证能构建,可能只做轻度测试(或完全不测),应视为不稳定

当前支持矩阵

Architecture Operating system Support tier Maintainers
AMD64 Linux 1 etcd maintainers
ARM64 Linux 1 etcd maintainers
AMD64 Darwin 3 —
ARM64 Darwin 3 —
AMD64 Windows 3 —
ppc64le Linux 3 —
s390x Linux 3 —

未列出的平台不受支持。

支持新平台的要求

成为新平台的「官方」maintainer,除承诺支持该平台外,还需搭建满足对应层级要求的 CI:

etcd continuous integration Tier 1 Tier 2 Tier 3
Build passes ✓ ✓ ✓
Unit tests pass ✓ ✓ —
Integration and end-to-end tests pass ✓ ✓ —
Robustness tests pass ✓ — —

ARM64 的 tier-2 CI 搭建示例见 etcd PR #12928。

不受支持平台的处理

为避免误在不受支持平台运行 etcd,etcd 会打印警告信息并立即退出,除非将环境变量 ETCD_UNSUPPORTED_ARCH 设为目标架构。

32 位系统警告:etcd 在 32 位系统上存在已知问题(源于 Go runtime bug),详见 Go issue #599 与 atomic package bug note。

💡 补充:生产环境请只用 AMD64 + Linux 或 ARM64 + Linux。Darwin / Windows 只应作为本地开发调试用途(Tier 3)。

4.3 系统调优(tuning)

默认设置适用于平均网络延迟较低的本地网络。跨多数据中心或高延迟网络时,心跳间隔与选举超时可能需要调优。注意网络不是延迟的唯一来源:leader 与 follower 上的慢磁盘也会影响每个请求/响应;这些超时代表从发出请求到对端成功响应的总时长。

4.3.1 时间参数:Heartbeat Interval 与 Election Timeout

分布式共识协议依赖两个时间参数:

参数 含义 默认值
Heartbeat Interval(--heartbeat-interval) leader 通知 follower 自己仍是 leader 的频率。最佳实践应设为成员间往返时间(RTT)附近 100ms
Election Timeout(--election-timeout) follower 多久收不到心跳就尝试自己成为 leader 1000ms

调优取权衡:

  • 心跳间隔建议约为成员间平均 RTT 的最大值,通常为 0.5–1.5 倍 RTT。过低会让 etcd 发送不必要的消息,增加 CPU 与网络资源消耗;过高会导致选举超时变长,进而延长 leader 故障的检测时间。测量 RTT 最简单的方式是用 PING 工具。
  • 选举超时应基于心跳间隔与成员间平均 RTT 设定,必须至少为 RTT 的 10 倍以应对网络波动。例如成员间 RTT 为 10ms,则选举超时至少应为 100ms。
  • 选举超时上限为 50000ms(50s),仅应在部署全球分布式 etcd 集群时使用。参考:美国大陆内 RTT 约 130ms,美日之间约 350–400ms;网络性能不均或有规律丢包延迟时可能需多次重试,因此 5s 是全球 RTT 的安全上限。由于选举超时应比广播时间大一个数量级,全球分布式集群在 ~5s 广播时间下,50 秒成为合理的最大值。
  • 心跳间隔与选举超时的值在同一集群的所有成员上必须一致,不同取值可能破坏集群稳定性。

覆盖默认值(单位为毫秒):

# Command line arguments:
$ etcd --heartbeat-interval=100 --election-timeout=500

# Environment variables:
$ ETCD_HEARTBEAT_INTERVAL=100 ETCD_ELECTION_TIMEOUT=500 etcd

💡 补充:推导逻辑一句话版——先测出成员间 RTT(ping 或 etcd_network_peer_round_trip_time_seconds,见第 12 章),heartbeat-interval ≈ 1 × RTT,election-timeout ≥ 10 × RTT 且 ≥ 5 × heartbeat-interval(后者来自 FAQ 对 failed to send out heartbeat on time 的处置建议,见第 17 章)。同集群所有 member 必须一致。

4.3.2 快照(Snapshots)与 --snapshot-count

etcd 把所有 key 变更追加到日志文件,该日志会无限增长、构成完整的线性变更历史。轻负载集群适合完整历史,重负载集群则会拖着巨大的日志。为避免日志过大,etcd 会周期性做快照:保存当前系统状态并删除旧日志,从而压缩日志。

快照调优:V2 backend 下创建快照代价较高,因此仅在 etcd 发生一定数量变更后才创建,默认每 10,000 次变更后做一次快照。若 etcd 的内存与磁盘占用过高,可下调快照阈值:

# Command line arguments:
$ etcd --snapshot-count=5000

# Environment variables:
$ ETCD_SNAPSHOT_COUNT=5000 etcd

💡 补充:--snapshot-count 越小 → 快照越频繁 → WAL 更短、恢复更快,但落盘开销更大;越大 → 快照更少、写放大更小,但 WAL 更长、重启恢复更慢。调整前最好结合 etcd_debugging_snap_save_total_duration_seconds(第 12 章)观察单次 save 耗时。

4.3.3 磁盘优先级

etcd 集群对磁盘延迟极为敏感。由于 etcd 必须把 proposal 持久化到日志,其他进程的磁盘活动可能造成很长的 fsync 延迟,导致 etcd 错过心跳、请求超时与临时性 leader 丢失。给 etcd 更高的磁盘优先级有时可以让它与这些进程稳定共存。

Linux 下用 ionice 配置磁盘优先级:

# best effort, highest priority
$ sudo ionice -c2 -n0 -p `pgrep etcd`

4.3.4 网络优先级(peer 流量高于 client 流量)

若 etcd leader 服务大量并发客户端请求,可能因网络拥塞延迟处理 follower 的 peer 请求,表现为 follower 节点上的发送缓冲区错误:

dropped MsgProp to 247ae21ff9436b2d since streamMsg's sending buffer is full
dropped MsgAppResp to 247ae21ff9436b2d since streamMsg's sending buffer is full

解决办法是把 etcd 的 peer 流量优先级调到客户端流量之上。Linux 下用流量控制机制(示例网卡 eth0):

tc qdisc add dev eth0 root handle 1: prio bands 3
tc filter add dev eth0 parent 1: protocol ip prio 1 u32 match ip sport 2380 0xffff flowid 1:1
tc filter add dev eth0 parent 1: protocol ip prio 1 u32 match ip dport 2380 0xffff flowid 1:1
tc filter add dev eth0 parent 1: protocol ip prio 2 u32 match ip sport 2379 0xffff flowid 1:1
tc filter add dev eth0 parent 1: protocol ip prio 2 u32 match ip dport 2379 0xffff flowid 1:1

peer 端口 2380 用 prio 1,客户端端口 2379 用 prio 2。取消 tc 配置:

tc qdisc del dev eth0 root

4.3.5 CPU governor

etcd 对延迟非常敏感,Linux 系统可把 CPU governor 设为 performance 或 conservative 模式进一步优化性能:

echo performance | tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor

4.3.6 调优项速查表

调优对象 手段 关键值 / 命令
心跳间隔 --heartbeat-interval 默认 100ms;取 0.5–1.5 倍 RTT
选举超时 --election-timeout 默认 1000ms;≥ 10 倍 RTT,上限 50000ms
快照频率 --snapshot-count 默认 10000;内存/磁盘占用高时下调(如 5000)
磁盘 IO 优先级 ionice sudo ionice -c2 -n0 -p \pgrep etcd``
网络优先级 tc qdisc peer 2380 → prio 1;client 2379 → prio 2
CPU scaling_governor performance 或 conservative
磁盘介质 SSD / 15,000 RPM / RAID 0 顺序 IOPS ≥ 50(重负载 ≥ 500)

💡 补充:关于「并发 fd / ulimit」的具体建议值,官方 tuning 页未提供;本册不在该条目上编造数值。相关可观测项可用 process_open_fds 与 process_max_fds / os_fd_used 与 os_fd_limit(第 12 章)自行建立水位告警。


5. 成员与运行时管理

本章回答运维最常问的四件事:集群现在什么状态、怎么加/减机器、成员变更做砸了怎么办、bootstrap 阶段成员是怎么互相找到的。

主要依据:

引自:Runtime reconfiguration 引自:How to check Cluster status 引自:How to Add and Remove Members 引自:How to conduct leader election in etcd cluster 引自:Discovery service protocol(v2 版) 引自:Discovery service protocol(v3 版)

5.1 查看集群状态:endpoint status / health / hashkv

etcdctl 提供三个 endpoint 子命令,分别看整体状态、健康度、KV 历史哈希。三者都支持 --endpoints 与 --cluster 两种取地址方式(二选一)。

5.1.1 命令形式

etcdctl endpoint status (--endpoints=$ENDPOINTS|--cluster)
etcdctl endpoint health (--endpoints=$ENDPOINTS|--cluster)
etcdctl endpoint hashkv (--endpoints=$ENDPOINTS|--cluster) [rev=$REV]

各命令的专有选项:

子命令 选项 默认值 含义
endpoint status --cluster false use all endpoints from the cluster member list,从集群成员列表自动取全部 endpoint
endpoint health --cluster false 同上
endpoint hashkv --cluster false 同上
endpoint hashkv --rev 0 maximum revision to hash(default: latest revision)

从父命令继承的全局选项:

--endpoints="127.0.0.1:2379": gRPC endpoints
-w, --write-out="simple": set the output format (fields, json, protobuf, simple, table)

💡 补充:--endpoints 默认是 127.0.0.1:2379,不指定就只查本地节点——这是巡检脚本里最常见的「我以为查了整个集群」事故来源。巡检务必显式写 --cluster 或完整 --endpoints。

5.1.2 endpoint status 输出字段全解

etcdctl --write-out=table --endpoints=$ENDPOINTS endpoint status

+------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
|    ENDPOINT      |        ID        | VERSION | DB SIZE | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX | ERRORS |
+------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
| 10.240.0.17:2379 | 4917a7ab173fabe7 |  3.5.0  |   45 kB |      true |      false |         4 |      16726 |              16726 |        |
| 10.240.0.18:2379 | 59796ba9cd1bcd72 |  3.5.0  |   45 kB |     false |      false |         4 |      16726 |              16726 |        |
| 10.240.0.19:2379 | 94df724b66343e6c |  3.5.0  |   45 kB |     false |      false |         4 |      16726 |              16726 |        |
+------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------|
字段 含义 巡检要点
ENDPOINT 被查询的节点 client 地址(示例 10.240.0.17:2379 等三个节点) 确认覆盖了全部节点
ID 该成员的十六进制 member ID(如 4917a7ab173fabe7),与 member list 中的 ID 对应 与 member list 交叉核对,发现「幽灵成员」
VERSION 节点 etcd 版本(示例为 3.5.0) 滚动升级中会出现版本不一致,属正常
DB SIZE 该节点后端数据库大小(示例均为 45 kB) 三节点应基本一致;偏差过大说明有节点落后或存在碎片
IS LEADER 是否为当前 raft leader(示例中仅第一个节点为 true) 正常集群应恰好一个为 true
IS LEARNER 是否为 learner(非投票成员),示例全部为 false learner 不参与投票,长期 true 说明 promote 忘了做
RAFT TERM 当前 raft 任期号(示例均为 4) 同集群各节点任期应一致;任期跳变说明发生过重新选举
RAFT INDEX raft 日志索引(示例均为 16726) —
RAFT APPLIED INDEX 已应用到状态机的日志索引(示例均为 16726) 与 RAFT INDEX 的差值反映应用滞后,正常应相等或极接近
ERRORS 节点级错误信息,示例为空即无异常 非空即为告警信号

⚠️ 源文说明:上表 VERSION 示例为 3.5.0(官方文档示例尚未更新到 v3.7),字段含义与列顺序按 v3.7 文档原文保留。

5.1.3 endpoint health

etcdctl --endpoints=$ENDPOINTS endpoint health

10.240.0.17:2379 is healthy: successfully committed proposal: took = 3.345431ms
10.240.0.19:2379 is healthy: successfully committed proposal: took = 3.767967ms
10.240.0.18:2379 is healthy: successfully committed proposal: took = 4.025451ms

要点:

  • 健康检查会向集群提交一个 proposal,成功提交即判定 healthy,并打印耗时 took。
  • 输出顺序不保证与 --endpoints 输入顺序一致(示例中 10.240.0.19 排在 10.240.0.18 之前)。
  • took 反映一次共识往返的时延,可用于判断网络/磁盘抖动。

5.1.4 endpoint hashkv

etcdctl --cluster endpoint hashkv  --write-out=table

+------------------+------------+---------------+
|     ENDPOINT     |    HASH    | HASH REVISION |
+------------------+------------+---------------+
| 10.240.0.17:2379 | 3892279174 |             3 |
| 10.240.0.18:2379 | 3892279174 |             3 |
| 10.240.0.19:2379 | 3892279174 |             3 |
+------------------+------------+---------------+
字段 含义
ENDPOINT 被查询的节点 client 地址
HASH 该 endpoint 在指定 revision 内的 KV 历史哈希(示例 3892279174)
HASH REVISION 哈希对应的 revision(示例 3)

各节点 HASH 与 HASH REVISION 一致说明数据一致;不一致则意味着节点间数据出现分叉或落后。

5.1.5 三个命令怎么选

场景 用哪个 理由
日常巡检看版本/DB 大小/leader endpoint status -w table 信息最全、字段可读
探活、判断能否写入、看共识时延 endpoint health 真的提交了一次 proposal,最接近真实可写性
校验各节点数据是否一致 endpoint hashkv --cluster 直接比较 KV 历史哈希
只想看某一历史 revision 的一致性 endpoint hashkv --rev=N --rev=0 默认哈希到最新 revision

5.2 运行时重配置:设计约束

引自:Runtime reconfiguration

  • etcd 支持 incremental runtime reconfiguration(增量运行时重配置),可在运行时更新集群成员关系。
  • 重配置请求只有在多数派成员正常工作时才能被处理。
  • 生产环境强烈建议集群规模大于 2。从 2 成员集群中移除成员是不安全的:2 成员集群的多数派同样是 2,移除过程中若发生故障,集群可能无法继续推进,需要从多数派故障中重新启动(restart from majority failure)。

5.2.1 四类典型场景

场景 官方做法要点
轮换/升级多台机器(计划性维护:硬件升级、网络中断) 一次只改动一个成员。移除 leader 是安全的,但选举期间会有短暂不可用。若集群持有超过 50MB 的 v2 数据,建议先做 member migration(迁移该成员的数据目录)
改变集群规模 扩容提升失败容忍度与读性能(客户端可从任意成员读,成员越多整体串行读吞吐越高);缩容提升写性能(写入需复制到多数派才算提交,多数派变小则提交更快),代价是韧性下降
替换故障机器 硬件故障、数据目录损坏等致命情况要尽快替换;已故障但未移除的成员会损害 quorum 并降低对额外故障的容忍度。步骤 = 先 remove 再 add。若集群超过 50MB 且故障成员数据目录仍可访问,建议迁移它
从多数派故障重启集群 多数派丢失或所有节点 IP 都变了时需人工介入:用旧数据创建新集群 → 强制单个成员充当 leader → 用运行时配置逐个 add 新成员
从少数派故障恢复 等同于「替换故障机器」

5.2.2 顺序约束(硬性)

  • 任何变更前必须有简单多数(quorum)可用,与任何写入 etcd 的要求一致。
  • 所有集群变更必须串行执行:
目标 需要的操作序列
更新单个成员 peerURLs 一次 update 操作
替换一个健康成员 先 remove 旧成员,再 add 新成员
3 扩到 5 两次 add
5 缩到 3 两次 remove
  • 除 etcdctl 外,也可用 v2 HTTP members API 或 v3 gRPC members API 变更成员。

5.3 更新成员:client URL 与 peer URL 的区别(易踩坑)

更新对象 做法 是否需要显式 member update
advertise client URLs 直接以更新后的客户端 URL 重启该成员即可,用 flag --advertise-client-urls 或环境变量 ETCD_ADVERTISE_CLIENT_URLS。重启后成员会自行发布新 URL 不需要
advertise peer URLs 必须先通过 member 命令显式更新,再重启成员 需要

原因:peer URL 变更属于集群级配置改动,会影响集群健康;而客户端 URL 配错不会影响集群健康。

先列出成员拿到 ID:

$ etcdctl member list
6e3bd23ae5f1eae0: name=node2 peerURLs=http://localhost:23802 clientURLs=http://127.0.0.1:23792
924e2e83e93f2560: name=node3 peerURLs=http://localhost:23803 clientURLs=http://127.0.0.1:23793
a8266ecf031671f3: name=node1 peerURLs=http://localhost:23801 clientURLs=http://127.0.0.1:23791

更新 ID 为 a8266ecf031671f3 的成员,把 peerURLs 改为 http://10.0.1.10:2380:

$ etcdctl member update a8266ecf031671f3 --peer-urls=http://10.0.1.10:2380
Updated member with ID a8266ecf031671f3 in cluster

5.4 移除成员

$ etcdctl member remove a8266ecf031671f3
Removed member a8266ecf031671f3 from cluster

目标成员会自行停止并在日志中打印:

etcd: this member has been permanently removed from the cluster. Exiting.

移除 leader 是安全的,但新 leader 选举期间集群处于 inactive 状态,时长通常为选举超时加上投票过程的时间。

5.5 添加新成员:完整命令序列(两步)

第一步:通过 HTTP members API、gRPC members API 或 etcdctl member add 把新成员加入集群。 第二步:用新的集群配置启动新成员(已有成员 + 新成员的完整列表)。

5.5.1 op-guide 版示例

$ etcdctl member add infra3 --peer-urls=http://10.0.1.13:2380
added member 9bf1b35fc7761a23 to cluster

ETCD_NAME="infra3"
ETCD_INITIAL_CLUSTER="infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380,infra3=http://10.0.1.13:2380"
ETCD_INITIAL_CLUSTER_STATE=existing

etcdctl 会告知集群新成员并打印启动所需的环境变量,然后据此启动:

$ export ETCD_NAME="infra3"
$ export ETCD_INITIAL_CLUSTER="infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380,infra3=http://10.0.1.13:2380"
$ export ETCD_INITIAL_CLUSTER_STATE=existing
$ etcd --listen-client-urls http://10.0.1.13:2379 --advertise-client-urls http://10.0.1.13:2379 --listen-peer-urls http://10.0.1.13:2380 --initial-advertise-peer-urls http://10.0.1.13:2380 --data-dir %data_dir%

新成员会立即开始追赶集群其余成员。

  • 最佳实践:一次只配置一个成员,确认启动正确后再加下一个。
  • 向 1 节点集群添加成员时,在新成员启动前集群无法推进(需要 2 个成员构成多数派达成一致)。该状态只存在于 etcdctl member add 通知集群之后、新成员成功建立连接之前这段时间。

5.5.2 tasks/operator 版完整序列(含替换成员)

第 0 步:静态方式拉起 3 节点集群。TOKEN=my-etcd-token-1、CLUSTER_STATE=new,成员 etcd-node-1/etcd-node-2/etcd-node-3,主机 10.240.0.13/10.240.0.14/10.240.0.15:

CLUSTER=${NAME_1}=http://${HOST_1}:2380,${NAME_2}=http://${HOST_2}:2380,${NAME_3}=http://${HOST_3}:2380

每个节点启动命令(以 node 1 为例,node 2/node 3 同构,只改 THIS_NAME/THIS_IP):

THIS_NAME=${NAME_1}
THIS_IP=${HOST_1}
etcd --data-dir=data.etcd --name ${THIS_NAME} \
	--initial-advertise-peer-urls http://${THIS_IP}:2380 \
	--listen-peer-urls http://${THIS_IP}:2380 \
	--advertise-client-urls http://${THIS_IP}:2379 \
	--listen-client-urls http://${THIS_IP}:2379 \
	--initial-cluster ${CLUSTER} \
	--initial-cluster-state ${CLUSTER_STATE} \
	--initial-cluster-token ${TOKEN}

第 1 步:取 member ID → 移除 → 加入新成员

# get member ID
export ETCDCTL_API=3
HOST_1=10.240.0.13
HOST_2=10.240.0.14
HOST_3=10.240.0.15
etcdctl --endpoints=${HOST_1}:2379,${HOST_2}:2379,${HOST_3}:2379 member list

# remove the member
MEMBER_ID=278c654c9a6dfd3b
etcdctl --endpoints=${HOST_1}:2379,${HOST_2}:2379,${HOST_3}:2379 \
	member remove ${MEMBER_ID}

# add a new member (node 4)
export ETCDCTL_API=3
NAME_1=etcd-node-1
NAME_2=etcd-node-2
NAME_4=etcd-node-4
HOST_1=10.240.0.13
HOST_2=10.240.0.14
HOST_4=10.240.0.16 # new member
etcdctl --endpoints=${HOST_1}:2379,${HOST_2}:2379 \
	member add ${NAME_4} \
	--peer-urls=http://${HOST_4}:2380

要点:先用 member list 取得待删成员的 member ID(示例 278c654c9a6dfd3b),再用 member remove ${MEMBER_ID} 移除;member add ${NAME_4} --peer-urls=http://${HOST_4}:2380 中 --peer-urls 指定新成员的 peer 地址(2380)。注意 member add 只写两个存活节点 HOST_1:2379,${HOST_2}:2379,不需要把还没启动的新成员列进去。

第 2 步:以 existing 状态启动新成员

# [WARNING] If the new member starts from the same disk space,
# make sure to remove the data directory of the old member
#
# restart with 'existing' flag
TOKEN=my-etcd-token-1
CLUSTER_STATE=existing
NAME_1=etcd-node-1
NAME_2=etcd-node-2
NAME_4=etcd-node-4
HOST_1=10.240.0.13
HOST_2=10.240.0.14
HOST_4=10.240.0.16 # new member
CLUSTER=${NAME_1}=http://${HOST_1}:2380,${NAME_2}=http://${HOST_2}:2380,${NAME_4}=http://${HOST_4}:2380

THIS_NAME=${NAME_4}
THIS_IP=${HOST_4}
etcd --data-dir=data.etcd --name ${THIS_NAME} \
	--initial-advertise-peer-urls http://${THIS_IP}:2380 \
	--listen-peer-urls http://${THIS_IP}:2380 \
	--advertise-client-urls http://${THIS_IP}:2379 \
	--listen-client-urls http://${THIS_IP}:2379 \
	--initial-cluster ${CLUSTER} \
	--initial-cluster-state ${CLUSTER_STATE} \
	--initial-cluster-token ${TOKEN}

关键注意事项(5 条):

  1. 新成员必须用 --initial-cluster-state existing(而非 new),表示加入一个已存在的集群。
  2. 源文 [WARNING]:新成员若复用同一块磁盘空间,务必先删除旧成员的 data directory(即 --data-dir=data.etcd 对应目录),否则旧数据会导致加入失败或集群损坏。
  3. --initial-cluster 列表要更新为新的成员组合(node-1、node-2、node-4),且 --initial-cluster-token 必须与现有集群一致(my-etcd-token-1),否则会被视为另一个集群。
  4. 每个 member 操作前都要 export ETCDCTL_API=3。
  5. 本页未出现 --learner 与 --member-id 两个 flag——--learner 只在 op-guide/runtime-configuration.md 与 learning/design-learner.md 中出现,不属于本页内容。

5.6 Learner:先追赶、再投票(自 v3.4 起)

推荐新成员先以 learner 加入、追赶完成后再提升,这样更安全并减少集群停机时间,共三步:

  1. 通过 gRPC members API 或 etcdctl member add --learner 添加 learner。
  2. 用新的集群配置启动新成员(与前述完全相同)。
  3. 通过 gRPC members API 或 etcdctl member promote 将 learner 提升为投票成员。

etcd server 会校验 promote 请求以确保操作安全性:只有 raft log 追上 leader 之后 learner 才能被提升;未追上时 promote 会失败,应等待后重试。

v3.4 中 etcd server 限制集群最多只能有 1 个 learner,主要考虑是限制 leader 向 learner 传播数据带来的额外负载。

$ etcdctl member add infra3 --peer-urls=http://10.0.1.13:2380 --learner
Member 9bf1b35fc7761a23 added to cluster a7ef944b95711739

ETCD_NAME="infra3"
ETCD_INITIAL_CLUSTER="infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380,infra3=http://10.0.1.13:2380"
ETCD_INITIAL_CLUSTER_STATE=existing
$ etcdctl member promote 9bf1b35fc7761a23
Member 9e29bbaa45d74461 promoted in cluster a7ef944b95711739

💡 补充:learner 的价值在于「加入集群的瞬间不会立刻改变 quorum 计算」。直接 add 一个投票成员,若它启动慢或 peer URL 配错,新成员会被计入 quorum 却无法真正参与,等于凭空把可用性门槛抬高——这正是 5.7 节 --strict-reconfig-check 试图兜底的问题,也是 5.6 节推荐用 learner 的原因。

5.7 --strict-reconfig-check:重配置的护栏

项目 内容
背景 新成员若配置不正确(例如 peer URLs 写错),即便该成员对现有成员不可达,仍会被计入 quorum,从而导致 quorum 丢失;连接问题或运维问题同样可能引发
开启后的行为 若重配置后已启动成员数将少于重配置集群的 quorum,etcd 会拒绝该重配置请求
默认值 默认启用(true)

💡 补充:除非你非常清楚自己在做什么(例如从多数派故障中人工重建集群),不要关掉它。关掉之后的「成功 add」很可能是一个已经失去 quorum 的集群。

5.8 成员变更失败场景处置(现象 → 原因 → 处置)

5.8.1 添加成员

现象(报错) 原因 处置
text<br>etcdserver: assign ids error: the member count is unequal<br>exit 1<br> 新主机未包含在枚举节点列表中;若是新集群,该节点必须加入 initial cluster 成员列表 把新成员补进 --initial-cluster 再启动

对应命令:

$ etcd --name infra3 \
  --initial-cluster infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380 \
  --initial-cluster-state existing
etcdserver: assign ids error: the member count is unequal
exit 1
现象(报错) 原因 处置
text<br>etcdserver: assign ids error: unmatched member while checking PeerURLs<br>exit 1<br> 使用了与加入集群时(10.0.1.13:2380)不同的地址(10.0.1.14:2380) 启动时的 peer URL 必须与 member add 时登记的完全一致;不一致就先 member remove 再重新 member add

对应命令:

$ etcd --name infra4 \
  --initial-cluster infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380,infra4=http://10.0.1.14:2380 \
  --initial-cluster-state existing
etcdserver: assign ids error: unmatched member while checking PeerURLs
exit 1
现象(报错) 原因 处置
text<br>etcd: this member has been permanently removed from the cluster. Exiting.<br>exit 1<br> etcd 使用了已被移除成员的数据目录启动,一旦连上集群中任何活跃成员就会自动退出 清空/删除该 --data-dir 后重新走 add 流程

对应命令:

$ etcd
etcd: this member has been permanently removed from the cluster. Exiting.
exit 1

5.8.2 添加 learner

现象(报错) 原因 处置
Error: etcdserver: too many learner members in cluster 集群已有 1 个 learner 时不能再添加(v3.4 限制最多 1 个 learner) 先把已有 learner promote 或 remove,再添加新的
$ etcdctl member add infra4 --peer-urls=http://10.0.1.14:2380 --learner
Error: etcdserver: too many learner members in cluster

5.8.3 promote learner

现象(报错) 原因 处置
Error: etcdserver: can only promote a learner member which is in sync with leader learner 未与 leader 同步(raft log 还没追上) 等待追赶完成后重试 promote
Error: etcdserver: can only promote a learner member 被提升的对象不是 learner(已是投票成员) 无需 promote
Error: etcdserver: member not found 被提升的成员在集群中不存在(ID 写错) 用 member list 重新确认 ID
$ etcdctl member promote 9bf1b35fc7761a23
Error: etcdserver: can only promote a learner member which is in sync with leader
$ etcdctl member promote 9bf1b35fc7761a23
Error: etcdserver: can only promote a learner member
$ etcdctl member promote 12345abcde
Error: etcdserver: member not found

5.9 选举:用 etcdctl 做一次 leader election

引自:How to conduct leader election in etcd cluster

文档明确说明:该命令保证同一时刻只有一个客户端能成为 leader。

etcdctl --endpoints=$ENDPOINTS elect <election-name> [proposal]
etcdctl --endpoints=$ENDPOINTS elect election-name p1
参数 说明
--endpoints : $ENDPOINTS etcd 集群各成员的地址(Address of each etcd cluster members)
election-name(string) 选举的标识符。所有参与同一领导竞争的参与者必须使用相同的选举名
leader-name(string,即位置参数 [proposal] 的文档描述名) 新 leader 的提议值(Proposal value of the new leader)
./etcdctl elect my-election proposal1
my-election/694d99fafea88404
proposal1

another election:
./etcdctl elect new-election proposal1
new-election/694d99fafea8840f
proposal1

输出解读:第一行 my-election/694d99fafea88404 是本次选举生成的 key(格式 <election-name>/<生成的编号>),第二行回显赢得选举的 proposal 值 proposal1。换一个选举名(new-election)会得到另一个独立 key(new-election/694d99fafea8840f),说明不同选举名之间是互相隔离的竞选域。

注意事项:

  • 示例中的 ./etcdctl elect 未带 --endpoints,走的是默认 127.0.0.1:2379。
  • elect 是阻塞式的——客户端成为 leader 后命令保持持有,退出即释放领导权。
  • ⚠️ 源文不一致:文档参数列表里写的是 leader-name,而命令示例里位置参数写作 p1 / proposal1。本册按原文并列保留,不擅自统一。

5.10 两种 discovery 服务协议(dev-guide 版 vs dev-internal 版)

etcd 官方文档里有两页同名的 Discovery service protocol,标题与 front matter(title、weight: 1500、description)完全相同,但内容不是同一份文档:一页讲 v2 discovery,另一页讲 v3 discovery。

5.10.1 共同约束(两版一致)

  1. Discovery service protocol 仅用于集群 bootstrap 阶段,不能用于运行时重配置(runtime reconfiguration)或集群监控(cluster monitoring)。
  2. 该协议用一个新的 discovery token 引导出一个唯一的 etcd 集群;一个 discovery token 只能代表一个 etcd 集群。
  3. 一旦基于某个 token 的 discovery 协议开始,即使中途失败,也绝不能再用该 token 去引导另一个 etcd 集群。

协议思路也相同:用一个内部 etcd 集群协调新集群的 bootstrap——先由所有新成员与 discovery service 交互、共同生成期望的成员列表,然后每个新成员用这个列表启动自己的 server,效果等同于 -initial-cluster flag。两版都采用 UUID=$(uuidgen) 生成 token,size 通常取 3、5 或 7(文档链到 optimal cluster size);注册数大于期望大小 N 时取最先注册的 N 个作为成员列表,自己在列表中则成功并拉取 peers,不在列表中则以「cluster has been full」失败;成员可能在注册自己之前就先检查集群状态,因此集群已满时可快速失败。

5.10.2 差异对比(9 个维度)

维度 dev-guide 版(v2) dev-internal 版(v3)
线上 URL https://etcd.io/docs/v3.7/dev-guide/discovery_protocol/ https://etcd.io/docs/v3.7/dev-internal/discovery_protocol/
源文件 dev-guide/discovery_protocol.md dev-internal/discovery_protocol.md
协议版本 v2 discovery(使用 /v2/keys/ HTTP 端点) 明确声明 only for v3 discovery
发现依据 共享的 discovery URL 共享的 discovery token + endpoint 列表
示例工具 curl etcdctl
key 前缀 _etcd/registry(无前导斜杠),完整 URL http://example.com/v2/keys/_etcd/registry /_etcd/registry(有前导斜杠)
注册路径 /_etcd/registry/${UUID}/${member_id},带 ?prevExist=false /_etcd/registry/${UUID}/members/${member_id},无 prevExist 参数
状态检查路径 GET /v2/keys/_etcd/registry/${UUID}(整棵子树) get /_etcd/registry/${UUID}/members(members 子前缀)
等待机制 v2 long polling:?wait=true&waitIndex=${current_etcd_index},链到 v2.3 API 文档 v3 watch:watch /_etcd/registry/${UUID}/members --prefix
启动 flag -discovery(单 flag,指向 URL) --discovery-token + --discovery-endpoints(两个 flag),并新增 TLS/认证/超时三组 flag
公共服务章节 有完整「Public discovery service」章节 无公共服务章节;改为「Note: only for v3 discovery」并回链 v2 文档
示例主机 http://example.com(v2 keys API,默认 2379 隐含在 /v2/keys) http://example.com:2379(显式端口)

5.10.3 v2 版(dev-guide)完整步骤

步骤 1:创建 discovery token

UUID=$(uuidgen)

步骤 2:指定期望的集群大小

curl -X PUT http://example.com/v2/keys/_etcd/registry/${UUID}/_config/size -d value=${cluster_size}

size 用于让 discovery service 判断何时已找齐所有初始成员,通常取 3、5 或 7。

步骤 3:拉起 etcd 进程

拿到 discovery URL 后,把它作为 -discovery flag 传给 etcd;给定 -discovery 后每个 etcd 进程会在内部自动执行后续几步。

步骤 4:注册自己

curl -X PUT http://example.com/v2/keys/_etcd/registry/${UUID}/${member_id}?prevExist=false -d value="${member_name}=${member_peer_url_1}&${member_name}=${member_peer_url_2}"

以 member ID 为 key 写入 discovery URL;?prevExist=false 保证该 key 此前不存在(避免重复注册覆盖);value 形如 member_name=peer_url_1&member_name=peer_url_2。

步骤 5:检查状态

curl -X GET http://example.com/v2/keys/_etcd/registry/${UUID}/_config/size
curl -X GET http://example.com/v2/keys/_etcd/registry/${UUID}

已注册成员不足则继续等待;若已注册成员数大于期望大小 N,取最先注册的 N 个作为成员列表:若自己在列表中,discovery 成功并通过该列表拉取所有 peers;若不在列表中,discovery 以「cluster has been full」失败结束。

步骤 6:等待所有成员

curl -X GET http://example.com/v2/keys/_etcd/registry/${UUID}?wait=true&waitIndex=${current_etcd_index}

基于 etcd v2 API 的 long polling(wait=true + waitIndex),持续等待直到找齐所有成员。

Public discovery service(CoreOS Inc. 托管于 https://discovery.etcd.io/):

  • Mask key prefix:公共服务会把 https://discovery.etcd.io/${UUID} 重定向到后端 etcd 集群中位于 /v2/keys/_etcd/registry 的 key,从而遮蔽注册 key 前缀,得到简短可读的 discovery URL。
  • Get new token:
GET /new

Sent query:
	size=${cluster_size}
Possible status codes:
	200 OK
	400 Bad Request
200 Body:
	generated discovery url

生成流程遵循前文「Creating a New Discovery Token」到「Specifying the Expected Cluster Size」两步。

适用范围说明:本文示例对应自托管的 discovery 集群;公共服务 discovery.etcd.io 工作方式相同,只是额外做了包装(隐藏丑陋 URL、自动生成 UUID、对过量请求提供一定保护),其核心仍是使用 etcd 集群作为数据存储。

5.10.4 v3 版(dev-internal)完整步骤

步骤 1:创建 token

UUID=$(uuidgen)

步骤 2:指定期望集群大小

etcdctl --endpoints=http://example.com:2379 put /_etcd/registry/${UUID}/_config/size ${cluster_size}

步骤 3:拉起 etcd 进程(v3 特有的一批 flag)

把 token ${UUID} 设到 --discovery-token,把承载 discovery service 的 etcd 集群 endpoints 设到 --discovery-endpoints,即可启用 v3 discovery 引导集群。给定这两个 flag 后每个 etcd 进程会执行后续内部步骤。

配套 flag 分三组:

场景 flag
discovery 服务启用客户端证书认证(用法与 etcdctl 访问集群完全一致) --discovery-insecure-transport、--discovery-insecure-skip-tls-verify、--discovery-cert、--discovery-key、--discovery-cacert
启用基于角色的认证 --discovery-user、--discovery-password
调整默认时间与超时值(用法同样与 etcdctl 一致) --discovery-dial-timeout、--discovery-request-timeout、--discovery-keepalive-time、--discovery-keepalive-timeout

步骤 4:注册自己

etcdctl --endpoints=http://example.com:2379 put /_etcd/registry/${UUID}/members/${member_id} ${member_name}=${member_peer_url_1}&${member_name}=${member_peer_url_2}

注意 v3 的 key 路径多了一层 /members/。

步骤 5:检查状态

etcdctl --endpoints=http://example.com:2379 get /_etcd/registry/${UUID}/_config/size
etcdctl --endpoints=http://example.com:2379 get /_etcd/registry/${UUID}/members

步骤 6:等待所有成员

v3 用 watch 而非 v2 的 long polling,持续 watch key 前缀 /_etcd/registry/${UUID}/members 直到找齐所有成员:

etcdctl --endpoints=http://example.com:2379 watch /_etcd/registry/${UUID}/members --prefix

⚠️ 源文说明:v3 版文档明确写着「Note that this document is only for v3 discovery」,并链回 v2 discovery 文档(源文链接写为 /docs/v3.5/dev-internal/discovery_protocol)查看细节——该链接指向 v3.5,疑为官方文档自身笔误,本册按原文保留。

5.11 成员管理速查卡

想做的事 命令
看成员列表与 ID etcdctl member list
看各节点状态(table) etcdctl -w table --cluster endpoint status
探活 etcdctl --cluster endpoint health
校验数据一致性 etcdctl -w table --cluster endpoint hashkv
改 peer URL etcdctl member update <ID> --peer-urls=<url> 然后重启该成员
改 client URL 直接以新 --advertise-client-urls 重启,无需 member update
移除成员 etcdctl member remove <ID>
加投票成员 etcdctl member add <name> --peer-urls=<url> → 以 existing 启动
加 learner etcdctl member add <name> --peer-urls=<url> --learner → 以 existing 启动
提升 learner etcdctl member promote <ID>(需已与 leader 同步)
客户端侧选举 etcdctl --endpoints=$ENDPOINTS elect <election-name> [proposal](阻塞式)

💡 补充:tasks/operator/_index.md 这一页仅含 front matter,无任何正文(共 5 行,字段为 title: Operator Tasks、weight: 95、description: Operational guides for deploying, configuring, and maintaining an etcd cluster.),是纯导航用的 list page,因此没有可提炼的内容;真正内容在本章引用的各子页中。


6. 日常维护:压缩、碎片整理、配额、快照

引自:Maintenance

etcd 集群需要周期性维护以保持可靠;通常可自动化、无需停机或显著性能下降即可完成。所有维护动作都在管理 keyspace 消耗的存储资源:

  • keyspace 大小失控 → 由 storage space quotas(空间配额)兜底;成员空间不足会触发集群级 alarm,使系统进入 limited-operation maintenance mode。
  • 为避免写不进去,必须做历史压缩(history compaction)。
  • 空间通过碎片整理(defragmentation)归还。
  • 周期性快照备份可恢复运维误操作导致的逻辑数据丢失或损坏。

6.1 Raft log retention 与 --snapshot-count

  • etcd --snapshot-count 配置在压缩前在内存中保留的已应用 Raft entry 数量。达到阈值后,server 先把快照数据落盘,再截断旧 entry。慢 follower 请求已被压缩索引之前的日志时,leader 会发送快照强制 follower 覆盖自身状态。
  • 更高的 --snapshot-count:内存中保留更多 Raft entry,导致周期性的更高内存占用;同时 leader 保留最新 entry 更久,慢 follower 有更多时间追赶。是「更高内存占用」与「慢 follower 更好可用性」之间的权衡。
  • 自 v3.2 起,默认值从 10,000 改为 100,000。
  • 性能上 --snapshot-count 大于 100,000 可能影响写吞吐:内存中对象数量多会拖慢 Go GC mark phase runtime.scanobject,内存回收不频繁也会让分配变慢。压缩过频繁影响集群可用性与写吞吐,过稀疏则给 Go GC 太大压力。

💡 补充:--snapshot-count 的取值存在三处不一致记载:配置表 '10000'、maintenance 页「自 v3.2 起改为 100,000」、upgrade_3_6 页「v3.6 默认值由 100000 改为 10000」。本册并列保留,请以实际版本 etcd -h 为准(见 3.2 节注)。

6.2 历史压缩(History Compaction)

etcd 保存 keyspace 的完整历史,必须定期压缩,否则性能退化并最终耗尽存储空间。压缩会丢弃给定 keyspace revision 之前被取代的 key 的全部信息,其占用空间变为可复用。

可自动压缩(--auto-compaction-mode + --auto-compaction-retention),也可用 etcdctl 手动压缩(细粒度控制):

# compact up to revision 3
$ etcdctl compact 3

压缩点之前的 revision 将不可访问:

$ etcdctl get --rev=2 somekey
Error:  rpc error: code = 11 desc = etcdserver: mvcc: required revision has been compacted

6.3 Auto Compaction(两种模式)

模式 含义 启用方式 检查频率
periodic(默认) 保留基于时间窗口的 keyspace 历史 --auto-compaction-mode=periodic --auto-compaction-retention=<时长> 保留期 > 1h:每小时一次;≤ 1h:按保留期间隔
revision 保留固定数量的 revision --auto-compaction-mode=revision --auto-compaction-retention=<条数> 每 5 分钟检查一次

Periodic compaction

# keep one hour of history
$ etcd --auto-compaction-retention=1h
  • retention 值指定保留多少历史;一条记录在创建后约该时长内不会被压缩,确保慢 watcher 能在窗口内追上。
  • 保留期 大于 1 小时:etcd 每小时压缩一次,同时维持完整保留窗口;保留期 ≤ 1 小时:按保留期间隔压缩。
  • 例:--auto-compaction-retention=10h,先等 10 小时做第一次压缩,之后每小时压缩一次:
0hr  (rev = 1)
1hr  (rev = 10)
...
8hr  (rev = 80)
9hr  (rev = 90)
10hr (rev = 100, Compact(1))
11hr (rev = 110, Compact(10))
...

推荐值:

场景 建议值
同一 key 频繁更新 1h 或 30m
更新不频繁 24h、48h、72h
通用默认 10h

Revision compaction

# keep 1000 revisions
$ etcd --auto-compaction-mode=revision --auto-compaction-retention=1000

etcd 每 5 分钟检查一次,压缩到 "latest revision" - 1000;例如最新 revision 为 30000 时压缩到 29000。

6.4 碎片整理(Defragmentation)

为什么需要 defrag

  • 压缩后后端数据库会产生内部碎片:这些空间对后端可用,但仍占用存储;压缩旧 revision 会在后端数据库中留下 gap。碎片空间对 etcd 可用,但对宿主机文件系统不可用——即删除应用数据并不会回收磁盘空间。
  • 碎片整理把该空间释放回文件系统,按成员逐个执行,以避免集群范围的延迟尖刺。

在线整理(etcdctl defrag)

$ etcdctl defrag
Finished defragmenting etcd member[127.0.0.1:2379]
  • 警告:对在线成员做碎片整理会在重建状态期间阻塞系统读写。
  • 警告:碎片整理请求不会在集群内复制,只作用于本地节点。需在 --endpoints 中指定所有成员,或用 --cluster 自动发现所有集群成员。
$ etcdctl defrag --cluster
Finished defragmenting etcd member[http://127.0.0.1:2379]
Finished defragmenting etcd member[http://127.0.0.1:22379]
Finished defragmenting etcd member[http://127.0.0.1:32379]

离线整理(etcdutl defrag)

在 etcd 未运行时直接整理数据目录:

etcdutl defrag --data-dir <path-to-etcd-data-dir>

何时需要 defrag(判据)

💡 补充:官方未给出「何时必须 defrag」的阈值,以下判据基于官方给出的两个监控指标推导。

判据 说明
etcd_mvcc_db_total_size_in_bytes(物理)远大于 etcd_mvcc_db_total_size_in_use_in_bytes(逻辑) 差值即碎片空间,差值占比高时应 defrag
两者都接近 --quota-backend-bytes 应先做历史压缩,再做 defrag
刚做过大批量删除 / 大版本压缩 压缩后必然产生碎片

6.5 空间配额(Space quota)

  • 没有配额时,keyspace 过大可能导致性能变差或耗尽存储,引发不可预测的集群行为。
  • 任一成员的 keyspace 后端数据库超过配额 → etcd 触发集群级 alarm,集群进入 maintenance mode,只接受 key 读和删除。只有「释放足够空间 + 碎片整理 + 清除配额 alarm」后才能恢复正常。
  • 默认已有适合大多数应用的保守配额,也可按字节在命令行配置:
# set a very small 16 MiB quota
$ etcd --quota-backend-bytes=$((16*1024*1024))

触发配额的现象与确认

# fill keyspace
$ while [ 1 ]; do dd if=/dev/urandom bs=1024 count=1024  | ETCDCTL_API=3 etcdctl put key  || break; done
...
Error:  rpc error: code = 8 desc = etcdserver: mvcc: database space exceeded

# confirm quota space is exceeded
$ ETCDCTL_API=3 etcdctl --write-out=table endpoint status
+----------------+------------------+-----------+---------+-----------+-----------+------------+
|    ENDPOINT    |        ID        |  VERSION  | DB SIZE | IS LEADER | RAFT TERM | RAFT INDEX |
+----------------+------------------+-----------+---------+-----------+-----------+------------+
| 127.0.0.1:2379 | bf9071f4639c75cc | 2.3.0+git | 18 MB   | true      |         2 |       3332 |
+----------------+------------------+-----------+---------+-----------+-----------+------------+

# confirm alarm is raised
$ ETCDCTL_API=3 etcdctl alarm list
memberID:13803658152347727308 alarm:NOSPACE

完整恢复序列(NOSPACE 处置五步)

# 1. get current revision
$ rev=$(ETCDCTL_API=3 etcdctl --endpoints=:2379 endpoint status --write-out="json" | egrep -o '"revision":[0-9]*' | egrep -o '[0-9].*')

# 2. compact away all old revisions
$ ETCDCTL_API=3 etcdctl compact $rev
compacted revision 1516

# 3. defragment away excessive space
$ ETCDCTL_API=3 etcdctl defrag
Finished defragmenting etcd member[127.0.0.1:2379]

# 4. disarm alarm
$ ETCDCTL_API=3 etcdctl alarm disarm
memberID:13803658152347727308 alarm:NOSPACE

# 5. test puts are allowed again
$ ETCDCTL_API=3 etcdctl put newkey 123
OK

配额相关监控指标

指标 含义
etcd_mvcc_db_total_size_in_use_in_bytes 历史压缩后的实际数据库用量
etcd_debugging_mvcc_db_total_size_in_bytes 包含待碎片整理空闲空间的数据库大小。该值仅在前者接近它时才增长,因此两者都接近配额时就需要做历史压缩以避免触发配额
etcd_mvcc_db_total_size_in_bytes 自 v3.4 起由 etcd_debugging_mvcc_db_total_size_in_bytes 更名而来
etcd_server_quota_backend_bytes 当前 backend 存储配额大小(字节)

警告:Put/Txn/LeaseGrant 请求可能返回 ErrGRPCNoSpace 错误,但写入在后端仍然成功——因为 etcd 在 API 层和内部 Apply 层都检查配额,而 Apply 层只会触发 NOSPACE alarm,不会阻断事务继续。

💡 补充:正因为「报错但写入成功了」这一行为,处理 NOSPACE 时不要假设失败请求可以重放——应先确认数据实际状态再决定是否重试,否则可能产生重复写入。

6.6 快照备份(Snapshot backup)

定期快照是 etcd keyspace 的持久化备份,可把集群恢复到某个已知良好状态的时间点。

$ etcdctl snapshot save backup.db

$ etcdutl --write-out=table snapshot status backup.db
+----------+----------+------------+------------+
|   HASH   | REVISION | TOTAL KEYS | TOTAL SIZE |
+----------+----------+------------+------------+
| fe01cf57 |       10 |          7 | 2.1 MB     |
+----------+----------+------------+------------+

文件命名:官方示例使用 backup.db(也有 snapshot.db 的写法)。文件名本身无强制约定,建议带上集群名 + 日期 + revision,例如 <cluster>-<yyyymmdd>-r<revision>.db,便于回溯。

💡 补充:这一条命名建议是本册补充,官方页面只给了 backup.db / snapshot.db 两种示例名。

6.7 维护动作速查

动作 命令 是否阻塞 是否复制到集群
手动压缩 etcdctl compact <rev> 否 是(集群级 revision)
在线碎片整理 etcdctl defrag / etcdctl defrag --cluster 是(阻塞读写) 否(只作用于本地节点)
离线碎片整理 etcdutl defrag --data-dir <dir> etcd 未运行 否
查配额告警 etcdctl alarm list 否 —
清除配额告警 etcdctl alarm disarm 否 是
快照备份 etcdctl snapshot save <file> 否 —
查快照状态 etcdutl snapshot status <file> -w table 否 —

7. 灾难恢复

引自:Disaster recovery

7.1 前提与设计

  • etcd 集群能自动从临时故障(如机器重启)中恢复,N 成员集群可容忍最多 (N-1)/2 个永久性故障。
  • 成员永久故障(硬件故障或磁盘损坏)会失去集群访问权;永久丢失超过 (N-1)/2 个成员则灾难性失败,不可逆转地丢失 quorum,集群无法达成共识、不能再接受更新。
  • 恢复手段:etcd v3 提供 snapshot and restore 设施,可在不丢失 v3 key 数据的前提下重建集群。恢复 v2 key 需参考 v2 admin guide(v3.7 中 v2 已移除)。

7.2 快照 keyspace(snapshot save)

$ ETCDCTL_API=3 etcdctl --endpoints $ENDPOINT snapshot save snapshot.db

快照来源有两种:

来源 说明 风险
对活跃成员执行 etcdctl snapshot save 推荐方式 无
从 etcd 数据目录复制 member/snap/db 文件 离线拷贝 可能丢失尚未写入、但已包含在 wal(write-ahead-log)目录中的数据

7.3 查看快照状态(snapshot status)

$ etcdutl snapshot status snapshot.db -w table
+---------+----------+------------+------------+
|  HASH   | REVISION | TOTAL KEYS | TOTAL SIZE |
+---------+----------+------------+------------+
| 7ef846e |   485261 |      11642 |      94 MB |
+---------+----------+------------+------------+

7.4 Revision Difference(revision 回退问题)

  • 恢复集群时,现有客户端可能感知到 revision 回退数百或数千——因为快照只包含截止到快照时刻的数据沿革,而当前状态可能已经超前。
  • 运行 Kubernetes 时这尤其成问题:controller 和 operator 使用 informers(本地缓存,通过 watch 接收更新),恢复到旧 revision 可能无法正确刷新缓存,导致 controller 行为不可预测且不一致。
  • 只要存在 watch API 的已知消费者、etcd 数据的本地缓存副本,或使用 Kubernetes,强烈建议使用 revision bumps 方式恢复。

7.5 从快照恢复(snapshot restore)

  • 恢复集群只需要一个快照 "db" 文件;etcdutl snapshot restore 会创建新的 etcd 数据目录,所有成员都应使用同一个快照恢复。
  • 恢复会覆写部分快照元数据(具体是 member ID 和 cluster ID),成员失去原有身份,防止其无意中加入已有集群。因此从快照启动集群必须启动一个新的逻辑集群。
$ etcdutl snapshot restore snapshot.db --data-dir output-dir

7.6 完整性检查(Integrity Checks)

  • 恢复时可选择性校验快照完整性。
  • 用 etcdctl snapshot save 生成的快照带有 integrity hash,etcdutl snapshot restore 会校验它。
  • 从数据目录复制出来的快照没有 integrity hash,只能加 --skip-hash-check 才能恢复。

7.7 带 revision bump 的恢复

  • --bump-revision 接受一个 64 位整数,表示在当前快照 revision 上增加多少 revision,确保恢复后 revision 永不回退。
  • 由于每次写入 revision +1,假设 etcd 每秒写入少于 1500 次,则 bump 1'000'000'000 足以覆盖一周前的快照。
  • 在 Kubernetes controller 场景下,还应用 --mark-compacted 把所有 revision(含 bump 部分)标记为已压缩,确保所有 watch 被终止、etcd 不响应快照之后 revision 的请求,从而有效作废 informer 缓存。
$ etcdutl snapshot restore snapshot.db --bump-revision 1000000000 --mark-compacted --data-dir output-dir

7.8 带更新成员关系的恢复

  • 成员关系存储在 etcd 自身中并通过 raft 共识维护。quorum 完全丢失时,可以重新规划新集群的构成(例如全新的一套成员)。
  • 恢复时可直接把新的成员关系写入 datastore:
$ etcdutl snapshot restore snapshot.db \
  --name m1 \
  --data-dir m1.etcd \
  --initial-cluster m1=http://host1:2380,m2=http://host2:2380,m3=http://host3:2380 \
  --initial-cluster-token etcd-cluster-1 \
  --initial-advertise-peer-urls http://host1:2380

这确保新建集群只会与给定 token 的其他已恢复成员连接,而不会连到可能仍存活并尝试连接的旧成员。

  • 替代方案:启动 etcd 时用 --force-new-cluster 覆写成员关系并保留现有应用数据。文档明确强烈不建议这样做,因为若旧集群的其他成员仍存活会 panic。务必周期性保存快照。

7.9 逐节点恢复完整流程(End-2-End Example)

Step 1:从活跃集群取快照

$ etcdctl snapshot save snapshot.db

Step 2:为三成员集群创建新的数据目录

每个成员用同一快照、不同 --name / --data-dir / --initial-advertise-peer-urls:

$ etcdutl snapshot restore snapshot.db \
  --name m1 \
  --data-dir m1_data_dir.etcd \
  --initial-cluster m1=http://host1:2380,m2=http://host2:2380,m3=http://host3:2380 \
  --initial-cluster-token etcd-cluster-1 \
  --initial-advertise-peer-urls http://host1:2380

$ etcdutl snapshot restore snapshot.db \
  --name m2 \
  --data-dir m2_data_dir.etcd \
  --initial-cluster m1=http://host1:2380,m2=http://host2:2380,m3=http://host3:2380 \
  --initial-cluster-token etcd-cluster-1 \
  --initial-advertise-peer-urls http://host2:2380

$ etcdutl snapshot restore snapshot.db \
  --name m3 \
  --data-dir m3_data_dir.etcd \
  --initial-cluster m1=http://host1:2380,m2=http://host2:2380,m3=http://host3:2380 \
  --initial-cluster-token etcd-cluster-1 \
  --initial-advertise-peer-urls http://host3:2380

Step 3:用新数据目录启动 etcd

注意 --name 与 --data-dir 一一对应,--listen-client-urls / --advertise-client-urls 用 2379,--listen-peer-urls 用 2380:

$ etcd \
  --name m1 \
  --data-dir m1_data_dir.etcd \
  --listen-client-urls http://host1:2379 \
  --advertise-client-urls http://host1:2379 \
  --listen-peer-urls http://host1:2380 &

$ etcd \
  --name m2 \
  --data-dir m2_data_dir.etcd \
  --listen-client-urls http://host2:2379 \
  --advertise-client-urls http://host2:2379 \
  --listen-peer-urls http://host2:2380 &

$ etcd \
  --name m3 \
  --data-dir m3_data_dir.etcd \
  --listen-client-urls http://host3:2379 \
  --advertise-client-urls http://host3:2379 \
  --listen-peer-urls http://host3:2380 &

Step 4:恢复后的 etcd 集群即可对外提供快照中的 keyspace。

7.10 恢复后校验

💡 补充:官方 recovery 页未列出「恢复后校验」清单,以下为本册基于本页与维护页信息整理的校验动作。

校验项 命令 期望
集群健康 etcdctl endpoint health --endpoints=... 全部 is healthy
成员关系 etcdctl member list -w table 三个成员、peerURLs / clientURLs 正确
版本 curl http://host:2379/version etcdcluster 与 storage 均为目标版本
数据完整性 etcdctl get "" --prefix --keys-only | wc -l 与快照 TOTAL KEYS 一致
revision 单调 etcdctl endpoint status -w table RAFT INDEX / revision 不小于恢复前(若用了 --bump-revision)
告警状态 etcdctl alarm list 无输出(无 NOSPACE / CORRUPT)

7.11 注意事项汇总

项 说明
工具分工 自 etcd v3.6 起:只能用 etcdctl 做快照(snapshot save),必须用 etcdutl 从快照恢复(snapshot restore)
--data-dir 默认值 若未指定,默认值为 <name>.etcd(<name> 取自 --name)。例如成员名为 m1、m2、m3 且未指定 --data-dir,则数据目录为 m1.etcd、m2.etcd、m3.etcd
必须用同一快照 所有成员都应使用同一个快照恢复
新逻辑集群 恢复会覆写 member ID 与 cluster ID,必须启动一个新的逻辑集群
--force-new-cluster 强烈不建议;若旧集群其他成员仍存活会 panic
integrity hash 从数据目录复制的快照没有 hash,需 --skip-hash-check
revision 回退 Kubernetes / 有 watch 消费者的场景必须用 --bump-revision + --mark-compacted

8. 故障模式

引自:Failure modes

大规模部署中机器故障很常见:单台因软硬件故障失效;多台因断电或网络问题同时失效;多种故障也可能并发,几乎不可能穷举所有情况。本节对故障种类做编目并说明 etcd 的设计容忍方式。大多数用户都能把具体故障映射到其中一类。为应对罕见的不可恢复故障,务必备份 etcd 集群。

8.1 故障模式总表(现象 → 原因 → 处置)

# 故障模式 现象 原因 影响面 处置 / 恢复路径
1 Minor followers failure(少数派 follower 故障) 少于半数的 follower 失效;客户端与故障成员的连接断开 单台机器软硬件故障 集群仍可接受请求并正常推进,无重大中断(5 成员集群中 2 个 follower 故障不影响运行) 客户端库应对读请求自动重连到其他成员;运维应预期其他成员因重连导致系统负载上升
2 Leader failure(leader 故障) 集群自动选举新 leader,但不是瞬时完成 leader 节点失效;故障检测基于超时模型 约需一个 election timeout 才能选出新 leader;选举期间不能处理任何写,写请求被排队;已发给旧 leader 但未提交的写可能丢失(已提交的写永不丢失) 等待自动选举完成;新 leader 会自动延长所有 lease 的超时,确保 lease 不会在授予的 TTL 之前过期
3 Majority failure(多数派故障) 集群多数成员失效 断电、机房故障等导致多台同时失效 集群失败,不能接受更多写入 只有多数成员重新可用后集群才能恢复;多数恢复后自动选举新 leader 并回到健康状态,新 leader 同样自动延长所有 lease 超时;若多数成员无法重新上线,必须执行 disaster recovery(第 7 章)
4 Network partition(网络分区) 集群被切成多数派一侧与少数派一侧 网络设备 / 链路故障 多数派一侧成为可用集群,少数派一侧不可用。etcd 不存在 split-brain 分区恢复后,少数派一侧会自动识别多数派一侧的 leader 并恢复自身状态。leader 在多数派侧=视作少数派 follower 故障;leader 在少数派侧=视作 leader 故障,该 leader 主动退位(step down),多数派侧选出新 leader
5 Failure during bootstrapping(启动引导期故障) 集群 bootstrap 未成功 只要有必需成员未成功启动,bootstrap 就不算成功 集群不可用 删除所有成员上的数据目录,用新的 cluster-token 或新的 discovery token 重新 bootstrap(几乎总比恢复更快更省资源)

8.2 各类故障详解

8.2.1 少数派 follower 故障

  • 现象:少于半数的 follower 失效。
  • 影响:集群仍可接受请求并正常推进,无重大中断。例如 5 成员集群中 2 个 follower 故障不影响运行。但客户端会失去与故障成员的连接。
  • 恢复/应对:客户端库应对读请求自动重连到其他成员,对用户隐藏中断;运维方应预期其他成员因重连导致系统负载上升。

8.2.2 Leader 故障

  • 现象:leader 失效。
  • 影响:集群自动选举新 leader,但不是瞬时完成的——故障检测基于超时模型,因此大约需要一个 election timeout 才能选出新 leader。
  • 选举期间集群不能处理任何写;此期间发来的写请求会被排队,直到新 leader 选出后再处理。
  • 已发给旧 leader 但尚未提交的写可能丢失;新 leader 有权重写旧 leader 的任何未提交 entry。从用户视角看,部分写请求可能在新 leader 选举后超时,但已提交的写永远不会丢失。
  • 恢复/应对:新 leader 会自动延长所有 lease 的超时,确保 lease 不会在授予的 TTL 之前过期(即使该 lease 由旧 leader 授予)。

8.2.3 多数派故障

  • 现象:集群多数成员失效。
  • 影响:集群失败,不能接受更多写入。
  • 恢复:只有多数成员重新可用后集群才能恢复;若多数成员无法重新上线,运维必须执行 disaster recovery 来恢复集群。一旦多数成员恢复工作,集群会自动选举新 leader 并回到健康状态;新 leader 同样会自动延长所有 lease 超时,确保没有 lease 因服务端不可用而过期。

8.2.4 网络分区

  • 现象:与少数派 follower 故障或 leader 故障类似,网络分区把集群切成两半:多数派一侧与少数派一侧。
  • 影响:多数派一侧成为可用集群,少数派一侧不可用。etcd 不存在 "split-brain",因为集群成员的增删都是显式的,且每次变更都由当前多数派成员批准。
    • leader 在多数派一侧 → 从多数派视角看就是少数派 follower 故障。
    • leader 在少数派一侧 → 属于 leader 故障,少数派侧的 leader 会主动退位(step down),多数派一侧选出新 leader。
  • 恢复:分区恢复后,少数派一侧会自动识别多数派一侧的 leader 并恢复自身状态。

8.2.5 启动引导期故障

  • 现象:集群 bootstrap 只有在所有必需成员都成功启动时才算成功。
  • 影响/恢复:引导期发生任何故障,应删除所有成员上的数据目录,并用新的 cluster-token 或新的 discovery token 重新 bootstrap。
  • 说明:也可以像恢复运行中集群那样去恢复一个 bootstrap 失败的集群,但由于没有数据要恢复,几乎总是比重新 bootstrap 更耗时耗资源。

8.3 与故障相关的补充场景(映射到上述五类)

💡 补充:官方 failures 页只编目了上述 5 类。实践中常见的两个场景可映射如下(映射关系为本册推导):

实践场景 归入类别 处置要点
磁盘慢(fsync 延迟高导致心跳超时、频繁切主) 归入「Leader failure」的诱因 用 etcd_disk_wal_fsync_duration_seconds 确认;ionice 提优先级;必要时换 SSD(见第 4.3 节)
Quorum 丢失(永久丢失超过 (N-1)/2 成员) 归入「Majority failure」的终局 必须走第 7 章 disaster recovery,不能靠重启自愈

8.4 容错能力速查

集群规模 N 可容忍永久故障数 (N-1)/2 写入所需多数派
1 0 1
3 1 2
5 2 3
7 3 4

💡 补充:上表由 failures 页给出的公式 (N-1)/2 推导,具体 quorum 表见第 17 章 FAQ。


9. 数据损坏

引自:Data Corruption

etcd 内置了自动化数据损坏检测,用于防止成员状态发生偏离(diverging)。

9.1 检测方式与启用 flag

检测方式分两类:

类别 检测对象 启用 flag
Initial check(初始检查) 成员 bootstrap 期间的持久化状态 --experimental-initial-corrupt-check
Periodic check(周期性检查)· Compacted revision hash 已压缩 revision 的 hash --experimental-compact-hash-check-enabled
Periodic check(周期性检查)· Latest revision hash 最新 revision 的 hash --experimental-corrupt-check-time

💡 补充:这三个 flag 均带 --experimental- 前缀,在 v3.7 中已被移除并改为 feature gate / 非 experimental flag(见第 3.12 与 14.4 节):

旧写法(data_corruption 页) v3.7 等价写法
--experimental-initial-corrupt-check --feature-gates=InitialCorruptCheck=true
--experimental-compact-hash-check-enabled --feature-gates=CompactHashCheck=true
--experimental-compact-hash-check-time --compact-hash-check-time
--experimental-corrupt-check-time --corrupt-check-time

9.2 两类检查的行为差异

维度 Initial check Periodic check
执行时机 etcd 成员 bootstrap 期间 已运行集群中,由集群 leader 执行
执行者 该成员自己 集群 leader
比对对象 自己的持久化状态 vs 其他成员 自己的持久化状态 vs 其他成员
不匹配后果 退出(exit) 触发 CORRUPT ALARM

两种周期检查目的相同,但建议都启用,以在性能开销与检测时延之间取得平衡:

周期检查 性能开销 能否处理慢 follower 前置要求
Compacted revision hash check 极小(不造成额外数据库扫描) 能 要求集群有定期 compaction
Latest revision hash check 高(需在给定 revision 上扫描整个 etcd 内容计算 checksum) 不能(也不适用于频繁 compaction) 无

9.3 Compacted revision hash check

  • 用 --experimental-compact-hash-check-enabled 启用后,每分钟执行一次。
  • 可用 --experimental-compact-hash-check-time 调整周期,格式为 1m(每分钟)、1h(每小时)。
  • 该检查扩展了 compaction 流程,额外计算可供成员间比对的 checksum;不会造成额外的数据库扫描,因此非常廉价,但要求集群有定期 compaction。

9.4 Latest revision hash check

  • 用 --experimental-corrupt-check-time 启用,需要提供执行周期,格式同为 1m / 1h。
  • 由于性能开销高,推荐周期为数小时(a couple of hours)。
  • 执行一次检查需要在给定 revision 上扫描整个 etcd 内容来计算 checksum。

9.5 数据损坏成因

💡 补充:官方 data_corruption 页没有单独列出「成因」小节,仅说明检测机制的目的是「防止成员状态发生偏离(diverging)」。本册不编造成因清单。可归入「成员状态偏离」的实际诱因(如非正常关机导致 WAL 未落盘、跨集群误配置、磁盘静默错误等)请以官方 CHANGELOG 与 issue 为准。

9.6 修复损坏的成员(三种方式)

修复完成后,CORRUPT ALARM 可以被移除。三种方式:

方式 适用场景 数据目录是否需要保留
Purge member persistent state 只想让该成员重新从 leader 拉取最新快照 保留(移走 snap 子目录)
Replace member 该成员本地状态已不可信 删除
Restore whole cluster 损坏范围广,或多个成员受损 全部重建

方式一:Purge member persistent state

步骤:

  1. 停止 etcd 实例。
  2. 备份 etcd 数据目录。
  3. 把 etcd 数据目录中的 snap 子目录移出去。
  4. 以 --initial-cluster-state=existing 启动 etcd,并在 --initial-cluster 中列出集群成员。

预期结果:该 etcd 成员会从 leader 下载最新的快照。

注:原文步骤编号为 1、2、3、6,本册按顺序整理为 1–4。

方式二:Replace member

步骤:

  1. 停止 etcd 实例。
  2. 备份 etcd 数据目录。
  3. 删除数据目录。
  4. 执行 etcdctl member remove 把该成员从集群中移除。
  5. 执行 etcdctl member add 把它重新加回。
  6. 以 --initial-cluster-state=existing 启动 etcd,并在 --initial-cluster 中列出集群成员。

方式三:Restore whole cluster

  • 通过从当前 leader 保存快照并恢复到所有成员来恢复整个集群。
  • 对 leader 执行 etcdctl snapshot save,然后按 restoring a cluster 流程操作(即第 7 章的恢复流程)。

9.7 三种修复方式对比

💡 补充:下表对比为本册基于三种方式的步骤推导。

维度 Purge Replace Restore whole cluster
影响的成员数 1 1 全部
是否需要 remove/add 否 是 是(重建新逻辑集群)
数据来源 从 leader 下载最新快照 从 leader 追平 指定快照文件
是否改变 member ID 否 是(remove+add 后 ID 变化) 是(restore 覆写 member/cluster ID)
停机范围 单成员 单成员 全集群
典型场景 快照文件损坏但 WAL/成员身份可用 单成员数据目录整体损坏 quorum 丢失或多成员不一致

10. 安全:TLS、认证与 RBAC

本章讲三件事:链路上怎么加密(TLS)、身份怎么认证(auth)、权限怎么划分(RBAC),最后给出版本策略与兼容范围。

引自:Transport security model 引自:Authentication 引自:Role-based access control 引自:Versioning

10.1 基本前提与默认风险(必须先读)

etcd 同时支持自动 TLS 与客户端证书认证,覆盖 client-to-server 与 peer(server-to-server / 集群)两种通信。

重要默认行为:etcd 默认不启用基于 RBAC 的认证,也不启用传输层认证,目的是降低新用户上手摩擦;且该默认自 2013 年确立,改动属于 breaking change。因此未启用安全特性的 etcd 集群会把数据暴露给任意客户端。

起步要求:

  • 先准备一个 CA 证书(ca.crt)和为该成员签名的密钥对;
  • 建议为集群中每个成员单独创建并签名新的密钥对;
  • 文档推荐 cfssl 工具(示例见 hack/tls-setup),也提供自签名密钥对生成指南。

⚠️ 源文提示:下方 flag 列表可能因开发变更而过期,最新 flag 以 etcd --help 为准。

10.2 client-to-server(客户端到服务器)TLS flag

flag 说明
--cert-file=<path> 用于到 etcd 的 SSL/TLS 连接的证书。设置后 --advertise-client-urls 可使用 HTTPS schema
--key-file=<path> 证书对应的私钥,必须未加密
--client-cert-auth 设置后 etcd 检查所有入站 HTTPS 请求是否携带由受信任 CA 签名的客户端证书,未提供有效证书的请求失败。若同时启用了 authentication,证书提供凭据,用户名取证书 Common Name 字段
--trusted-ca-file=<path> 受信任的证书颁发机构
--auto-tls 使用自动生成自签名证书与客户端建立 TLS

10.3 Peer(server-to-server / 集群)TLS flag

flag 说明
--peer-cert-file=<path> peer 间 SSL/TLS 证书,既用于监听 peer 地址,也用于向其他 peer 发请求
--peer-key-file=<path> 私钥,必须未加密
--peer-client-cert-auth 检查来自集群的入站 peer 请求是否持有由给定 CA 签发的有效客户端证书
--peer-trusted-ca-file=<path> peer 侧受信任 CA
--peer-auto-tls peer 间使用自动生成自签名证书

规则:只要提供了 client-to-server 或 peer 任一证书,就必须同时设置对应的 key。 全部配置项均可用环境变量表达,如 ETCD_CA_FILE、ETCD_PEER_CA_FILE 等。

10.4 通用 TLS 选项

flag 说明
--cipher-suites server/client 与 peer 之间支持的 TLS cipher suite 逗号分隔列表;留空则由 Go 自动填充
--tls-min-version=<version> 设置 etcd 支持的最低 TLS 版本
--tls-max-version=<version> 设置 etcd 支持的最高 TLS 版本;未设置则使用 Go 支持的最高版本

10.5 证书 keyUsage 与 extendedKeyUsage(必配)

etcd 依赖 Go 的 crypto/tls 与 crypto/x509 做证书校验,握手期间会强制这些用途。推荐配置:

证书角色 keyUsage extendedKeyUsage
Server(client-to-server) digitalSignature, keyEncipherment serverAuth
Client digitalSignature, keyEncipherment clientAuth
Peer(server-to-server) digitalSignature, keyEncipherment serverAuth, clientAuth

补充:启用 --peer-cert-allowed-cn 之外的 --peer-client-cert-auth 时,peer 证书用于成员间 mTLS,必须同时含 serverAuth 与 clientAuth;配合 --client-cert-auth 的客户端证书应含 clientAuth。

10.6 四个标准示例

10.6.1 Example 1:client-to-server HTTPS 传输安全

$ etcd --name infra0 --data-dir infra0 \
  --cert-file=/path/to/server.crt --key-file=/path/to/server.key \
  --advertise-client-urls=https://127.0.0.1:2379 --listen-client-urls=https://127.0.0.1:2379

验证(自签名 CA 必须用 --cacert 传给 curl;也可把 CA 加入系统受信目录 /etc/pki/tls/certs 或 /etc/ssl/certs):

$ curl --cacert /path/to/ca.crt https://127.0.0.1:2379/v2/keys/foo -XPUT -d value=bar -v

OSX 10.9+ 注意:该版本 curl 7.30.0 不理解命令行传入的证书,需把 ca.crt 导入 keychain,或加 -k 忽略错误;可运行 open ./tests/fixtures/ca/ca.crt 后按提示操作,测试完请删除该证书。

10.6.2 Example 2:HTTPS 客户端证书认证

$ etcd --name infra0 --data-dir infra0 \
  --client-cert-auth --trusted-ca-file=/path/to/ca.crt --cert-file=/path/to/server.crt --key-file=/path/to/server.key \
  --advertise-client-urls https://127.0.0.1:2379 --listen-client-urls https://127.0.0.1:2379

此时未带客户端证书的请求被服务端拒绝:routines:SSL3_READ_BYTES:sslv3 alert bad certificate。携带证书后成功(握手输出含 SSLv3, TLS handshake, CERT verify (15) 与 TLS handshake, Finished (20),返回 {"action": "set", "node": {...}}):

$ curl --cacert /path/to/ca.crt --cert /path/to/client.crt --key /path/to/client.key \
  -L https://127.0.0.1:2379/v2/keys/foo -XPUT -d value=bar -v

指定 cipher suites 屏蔽弱套件(参考 issue#8320),握手时 client hello 若使用非法套件会失败:

$ etcd \
  --cert-file ./server.crt \
  --key-file ./server.key \
  --trusted-ca-file ./ca.crt \
  --cipher-suites TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
# 合法套件,请求成功,返回 etcd_server_version{server_version="3.2.22"} 1
$ curl --cacert /path/to/ca.crt --cert /path/to/client.crt --key /path/to/client.key \
  -L [CLIENT-URL]/metrics --ciphers ECDHE-RSA-AES128-GCM-SHA256

# 非法套件,失败:(35) error:14094410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure
$ curl --cacert /path/to/ca.crt --cert /path/to/client.crt --key /path/to/client.key \
  -L [CLIENT-URL]/metrics --ciphers ECDHE-RSA-DES-CBC3-SHA

10.6.3 Example 3:集群内传输安全 + 客户端证书(peer 双向 TLS)

DISCOVERY_URL=... # from https://discovery.etcd.io/new

# member1
$ etcd --name infra1 --data-dir infra1 \
  --peer-client-cert-auth --peer-trusted-ca-file=/path/to/ca.crt --peer-cert-file=/path/to/member1.crt --peer-key-file=/path/to/member1.key \
  --initial-advertise-peer-urls=https://10.0.1.10:2380 --listen-peer-urls=https://10.0.1.10:2380 \
  --discovery ${DISCOVERY_URL}

# member2
$ etcd --name infra2 --data-dir infra2 \
  --peer-client-cert-auth --peer-trusted-ca-file=/path/to/ca.crt --peer-cert-file=/path/to/member2.crt --peer-key-file=/path/to/member2.key \
  --initial-advertise-peer-urls=https://10.0.1.11:2380 --listen-peer-urls=https://10.0.1.11:2380 \
  --discovery ${DISCOVERY_URL}

成员组成集群后,成员间通信全部加密并用客户端证书认证,日志中连接地址显示为 HTTPS。

10.6.4 Example 4:自动自签名传输安全

仅需加密、不需认证时用 --auto-tls 与 --peer-auto-tls:

# member1
$ etcd --name infra1 --data-dir infra1 \
  --auto-tls --peer-auto-tls \
  --initial-advertise-peer-urls=https://10.0.1.10:2380 --listen-peer-urls=https://10.0.1.10:2380 \
  --discovery ${DISCOVERY_URL}
# member2 同构,IP 为 10.0.1.11

限制一:自签名证书不认证身份,curl 报 curl: (60) SSL certificate problem: Invalid certificate chain,需用 -k:

curl -k https://127.0.0.1:2379/v2/keys/foo -Xput -d value=bar -v

限制二:指定 ClientAutoTLS 与 PeerAutoTLS 时,etcd 自动生成的 client / peer 证书有效期仅 1 年;可用 --self-signed-cert-validity 以「年」为单位设置有效期。

10.7 DNS SRV 注意事项

自 v3.1.0 起(v3.2.9 除外),discovery SRV 引导会用 --discovery-srv 的根域名认证 ServerName,以防御中间人证书攻击:要求证书的 Subject Alternative Name(SAN) 含匹配的根域名。

例如 etcd --discovery-srv=etcd.local 只接受 SAN 中含 etcd.local 的证书。

10.8 etcd proxy 注意事项

proxy 终止来自其客户端的 TLS,并使用自身 --peer-key-file / --peer-cert-file 指定的 key/cert 与 etcd 成员通信。proxy 通过成员的 --advertise-client-urls 和 --advertise-peer-urls 两者通信:

用途 走哪个 URL
转发客户端请求 advertise client urls
同步初始集群配置 advertise peer urls

管理员必须确保:

  • 成员启用 client 认证时,proxy 的 --peer-cert-file 证书对该认证有效;
  • 启用 peer 认证时该证书也必须对 peer 认证有效。

10.9 TLS 认证的版本演进细节(逐条)

版本 变更 细节
v3.2.0 TLS 证书在每次客户端连接时重载(PR#7829) 可在不停机情况下直接用新证书覆盖旧证书完成轮换;每连接刷新开销不大,未来可加缓存层优化
v3.2.0 服务端拒绝 IP SAN 错误的入站 peer 证书(PR#7687) 若 peer 证书 SAN 含 IP 地址,只有远端 IP 命中其中之一才通过。例:CSR hosts 含 10.138.0.27 而实际 IP 为 10.138.0.2 时被拒,错误 x509: certificate is valid for 10.138.0.27, not 10.138.0.2
v3.2.0 服务端在检查 SAN 时解析 TLS DNSNames(PR#7767) 若证书 SAN 只有 DNS 名无 IP,则对该 DNS 名做正向解析(如 dig b.com),解析结果含远端 IP 才通过,否则报错 tls: 10.138.0.2 does not match any of DNSNames ["b.com"]
v3.2.2 若 IP 匹配则直接接受,不再检查 DNS 条目(PR#8223) 例:hosts 为 ["invalid.domain", "10.138.0.2"],远端 IP 为 10.138.0.2 时即使 invalid.domain 无效也认证通过(issue#8206)
v3.2.5 支持通配符 DNS SAN 的反向查找(PR#8281) 先对远端 IP 做反向解析(nslookup IPADDR)得到主机名列表,与证书 DNS 名做精确或通配匹配;都不匹配时再对每个 DNS 条目做正向解析(条目 *.example.default.svc 则查 example.default.svc),命中远端 IP 才接受;否则返回 "tls: "10.138.0.2" does not match any of DNSNames ["*.example.default.svc","*.example.default.svc.cluster.local"](issue#8268)
v3.3.0 新增 etcd --peer-cert-allowed-cn(PR#8616) 支持基于 CN 的 inter-peer 连接认证(issue#8262)
v3.2.19 与 v3.3.4 修复「证书 SAN 只含 IP、不含域名」时的 TLS reload(issue#9541) 见下方展开说明

10.9.1 --peer-cert-allowed-cn 的工作方式

Kubernetes TLS bootstrapping 会为 etcd 成员与 API server、kubelet 等动态生成证书,为每个组件维护独立 CA 虽更严格但繁琐;指定该 flag 后,即使共享 CA,也只有 CN 匹配的节点才能加入。

  • CN 匹配是精确字符串比较,不支持通配符或前缀匹配。
  • 基于主机名的过滤 --peer-cert-allowed-hostname / --client-cert-allowed-hostname 使用 Go 的 x509.Certificate.VerifyHostname(),支持精确主机名与通配符条目(如 *.example.com)。

三节点示例:CSR 均为 "CN": "etcd.local",hosts 分别为 m1.etcd.local / m2.etcd.local / m3.etcd.local 加 127.0.0.1、localhost;--peer-cert-allowed-cn etcd.local 时仅 CN 匹配者可加入,否则日志显示:

$ etcd --peer-cert-allowed-cn m1.etcd.local

I | embed: rejected connection from "127.0.0.1:48044" (error "CommonName authentication failed", ServerName "m1.etcd.local")
I | embed: rejected connection from "127.0.0.1:55702" (error "remote error: tls: bad certificate", ServerName "m3.etcd.local")

正确配置时每个进程以 etcd --peer-cert-allowed-cn etcd.local 启动,日志显示:

pkg/netutil: resolving m3.etcd.local:32380 to 127.0.0.1:32380
etcdserver: published {Name:m1 ClientURLs:[https://m1.etcd.local:2379]} to cluster 9db03f09b20de32b
embed: ready to serve client requests

10.9.2 SAN 只含 IP 时的 TLS reload 修复(v3.2.19 / v3.3.4)

Go 中仅当 (*tls.Config).Certificates 非空,或 (*tls.ClientHelloInfo).ServerName 非空且 SNI 有效时,服务端才调用 (*tls.Config).GetCertificate 重载证书。

  • 修复前:etcd 在初始握手时总把 Certificates 填为非空,导致客户端必须提供匹配 SNI 才能通过校验并触发重载;而 SAN 仅含 IP 的证书会产生空 ServerName,无法触发重载,过期证书在线替换因此失败。
  • 修复后:初始握手时 Certificates 创建为空以触发 GetCertificate,随后在每个新 TLS 连接上填充其余证书,即使客户端 SNI 为空(证书只含 IP)也能重载。

10.10 Host Whitelist(防御 DNS Rebinding)

etcd --host-whitelist 指定 HTTP 客户端请求可接受的主机名,用于防御 "DNS Rebinding" 攻击(参考 CVE-2018-5702):任意网站可创建授权 DNS 名并指向 "localhost",从而使监听 localhost 的 etcd HTTP 端点全部暴露。

规则:

  1. 客户端连接为 HTTPS 安全连接时,允许任意主机名;
  2. 连接不安全且 "HostWhitelist" 非空时,只允许 Host 字段在白名单内的请求。无论是否启用认证,该策略都强制生效。

默认值与坑:

  • 默认 etcd --host-whitelist 与 embed.Config.HostWhitelist 为空(允许所有主机名);
  • 指定主机名时不会自动加入 loopback 地址,需手动添加 "localhost"、"127.0.0.1" 等。

10.11 TLS 常见问题(FAQ)

Q1:TLS 客户端认证出现 SSLv3 alert handshake failure

Go 的 crypto/tls 使用证书公钥前会检查 key usage;做 client auth 必须在创建证书公钥时把 clientAuth 加入 Extended Key Usage。在 openssl.cnf 增加段并签名时引用:

[ ssl_client ]
...
  extendedKeyUsage = clientAuth
...
$ openssl ca -config openssl.cnf -policy policy_anything -extensions ssl_client -out certs/machine.crt -infiles machine.csr

Q2:peer 证书认证报 "certificate is valid for 127.0.0.1, not $MY_IP"

必须用成员公网 IP 作为 Subject Name 签发证书;etcd-ca 的 new-cert 提供 --ip= 选项。证书还需为成员的 FQDN 签名,用 Subject Alternative Names(简称 IP SANs) 补充 IP 地址;etcd-ca 提供 --domain= 选项,openssl 也可用 subjectAltName。

Q3:etcd 是否加密磁盘上的数据?

不加密。 若需加密:让客户端应用自行加解密,或使用底层存储系统的加密能力(如 [dm-crypt])。

Q4:日志告警 "directory X exist without recommended permission -rwx------"

etcd 新建某些目录时把权限设为 700 以防越权访问;若用户已按自己的偏好创建该目录,etcd 沿用现有目录并在权限非 700 时打印该告警。

10.12 开启认证:完整步骤与命令

源文件说明:authentication.md 仅 1254 字节,正文只有一段 shell 示例加一句 "This is just a stub which needs to be filled and updated with more information on authentication. The text above is just a code example."。同目录 _index.md 只有 Hugo front matter(title: Authentication Guides / weight: 4000 / description),无任何正文内容。因此本册仅能输出源文实际存在的命令序列,权限模型细节见 10.13 节。

源文完整命令序列(原样保留):

export ETCDCTL_API=3
ENDPOINTS=localhost:2379

etcdctl --endpoints=${ENDPOINTS} role add root
etcdctl --endpoints=${ENDPOINTS} role get root

etcdctl --endpoints=${ENDPOINTS} user add root
etcdctl --endpoints=${ENDPOINTS} user grant-role root root
etcdctl --endpoints=${ENDPOINTS} user get root

etcdctl --endpoints=${ENDPOINTS} role add role0
etcdctl --endpoints=${ENDPOINTS} role grant-permission role0 readwrite foo
etcdctl --endpoints=${ENDPOINTS} user add user0
etcdctl --endpoints=${ENDPOINTS} user grant-role user0 role0

etcdctl --endpoints=${ENDPOINTS} auth enable
# now all client requests go through auth

etcdctl --endpoints=${ENDPOINTS} --user=user0:123 put foo bar
etcdctl --endpoints=${ENDPOINTS} get foo
# permission denied, user name is empty because the request does not issue an authentication request
etcdctl --endpoints=${ENDPOINTS} --user=user0:123 get foo
# user0 can read the key foo
etcdctl --endpoints=${ENDPOINTS} --user=user0:123 get foo1

要点解读(严格基于上述命令的注释):

  1. 第一步固定为 export ETCDCTL_API=3,随后所有操作通过 etcdctl --endpoints=${ENDPOINTS} 发起。
  2. 建 root 角色 → role get root 查看 → 建 root 用户 → user grant-role root root(把 root 角色授予 root 用户)→ user get root 查看。
  3. 再建普通角色 role0,用 role grant-permission role0 readwrite foo 授予对 key foo 的 READWRITE 权限;建普通用户 user0 并用 user grant-role user0 role0 授予角色。
  4. auth enable 之后所有客户端请求都走认证。
  5. 未带 --user 的 get foo 被拒绝(permission denied),源文注释明确原因:请求未发起认证请求,因此 user name 为空。
  6. --user=user0:123 get foo 成功(user0 可读 foo);get foo1 则不在授权范围内。

最小启用步骤(管理员可在启用认证之前或之后配置用户与角色,取决于偏好):

$ etcdctl user add root
Password of root:
$ etcdctl auth enable

禁用认证(需要 root 身份):

$ etcdctl --user root:rootpw auth disable

10.13 RBAC 权限模型

认证功能自 etcd 2.1 加入;etcd v3 API 略微修改了认证特性的 API 与用户界面以适配新数据模型。

10.13.1 特殊用户与特殊角色

存在一个特殊用户 root 与一个特殊角色 root。

主体 说明
User root 拥有对 etcd 的完全访问权,必须在激活认证之前创建。设计用途是管理目的——管理角色与普通用户。root 用户必须拥有 root 角色,并被允许修改 etcd 内任何内容
Role root 除 root 用户外,也可授予任何其他用户。持有 root 角色的用户拥有全局读写访问权以及更新集群认证配置的权限。此外 root 角色还授予通用集群维护特权,包括修改集群成员、对 store 做碎片整理(defragment)、以及打快照

⚠️ 源文说明:源文只列出 root 用户与 root 角色两个特殊主体,未提及 guest 角色。(任务书中提到的「默认角色 root/guest」,guest 一项 v3.7 源文未提供。)

10.13.2 用户管理(etcdctl user 子命令)

$ etcdctl user list                      # 列出用户
$ etcdctl user add myusername            # 创建用户,会提示输入新密码
$ etcdctl user add myusername --no-password   # 创建不能用密码认证的用户,只能通过 TLS Common Name 认证
$ etcdctl user grant-role myusername foo # 授予角色
$ etcdctl user revoke-role myusername bar# 撤销角色
$ etcdctl user get myusername            # 查看用户设置
$ etcdctl user passwd myusername         # 修改密码,会再次提示输入
$ etcdctl user delete myusername         # 删除账号
  • 创建/改密时可用 --interactive=false 从标准输入提供密码;也可用 --new-user-password 提供密码。
  • 空密码限制:etcd 不支持用空密码认证(形如 --user username:)。例如以空密码创建的用户 etcdctl user add anonymous:'' 无法通过用户名/密码请求认证,etcdctl --user anonymous: get foo 会失败并报 user name is empty。
  • 角色本身没有密码,仅定义一组访问权限。

10.13.3 角色管理(etcdctl role 子命令)

$ etcdctl role list                      # 列出角色
$ etcdctl role add myrolename            # 新建角色
$ etcdctl role get myrolename            # 查看已授予的权限
$ etcdctl role delete myrolename         # 删除角色

10.13.4 权限(permission)模型

角色被授予对单个 key 或一段 key range 的访问权。range 以区间 [start-key, end-key) 表示,要求 start-key 在字典序上小于 end-key。

权限类型三种:read(READ)、write(WRITE)、readwrite(READWRITE)。

# 授予对 key /foo 的读权限
$ etcdctl role grant-permission myrolename read /foo

# 授予对前缀 /foo/ 下 key 的读权限;前缀等价于区间 [/foo/, /foo0)
$ etcdctl role grant-permission myrolename --prefix=true read /foo/

# 授予 /foo/bar 的只写权限
$ etcdctl role grant-permission myrolename write /foo/bar

# 授予区间 [key1, key5) 的完全权限
$ etcdctl role grant-permission myrolename readwrite key1 key5

# 授予前缀 /pub/ 下 key 的完全权限
$ etcdctl role grant-permission myrolename --prefix=true readwrite /pub/

撤销权限与授权逻辑对称:

$ etcdctl role revoke-permission myrolename /foo/bar

10.14 认证的安全作用域(重要边界)

用 etcdctl auth enable 启用认证后,它保护 V3 gRPC API 操作(get、put、delete、watch 等)。

/metrics 与 /health HTTP 端点运行在独立的 handler 上,不受 V3 RBAC 认证保护。

该设计让 Prometheus 与负载均衡器无需 gRPC 认证即可抓取指标,同时仍然保护 key-value 数据。要保护这些可观测性端点,可选方案:

方案 具体做法
启用 mTLS --cert-file、--key-file、--client-cert-auth
绑定内网 用 --listen-metrics-urls 把 metrics 绑定到私有网络接口
网络层限制 使用网络策略 / 防火墙规则限制访问

10.15 etcdctl 认证用法

$ etcdctl --user user:password get foo        # 类似 curl 的认证 flag
$ etcdctl --user user get foo                 # 密码从提示符读取
$ etcdctl --user user --password password get foo   # 密码由 --password 命令行 flag 提供

其余 etcdctl 命令保持不变;用户与角色仍可创建和修改,但必须由具有 root 角色的用户通过认证后执行。

10.16 使用 TLS Common Name 做认证

自 v3.2 起,若 etcd server 以 --client-cert-auth=true 启动,客户端 TLS 证书的 Common Name(CN)字段会被当作 etcd 用户名;此时 CN 即完成用户认证,客户端不需要密码。

优先级:若同时满足 1)传入 --client-cert-auth=true 且客户端提供了 CN,2)客户端提供了用户名和密码,则优先使用基于用户名与密码的认证。

限制:该特性不能与 gRPC-proxy 和 gRPC-gateway 一起使用。

  • gRPC-proxy 会终止来自其客户端的 TLS,所有客户端共享 proxy 的证书;
  • gRPC-gateway 内部也用 TLS 连接把 HTTP 请求转换为 gRPC 请求,限制相同——客户端无法正确向服务端提供自己的 CN;
  • 若给定证书的 CN 非空,gRPC-proxy 会报错并停止,返回的错误表明客户端证书含非空 CN。

10.17 密码强度说明

etcdctl 与 etcd API 在创建用户或更新用户密码时不强制任何密码长度要求,是否强制由管理员负责。为规避密码强度相关的安全风险,可使用 TLS Common Name 认证以及用 --no-password 创建的用户。

10.18 版本策略与兼容范围

引自:Versioning

10.18.1 版本号语义

etcd 版本表示为 x.y.z,其中 x = major(主版本),y = minor(次版本),z = patch(补丁版本),遵循 Semantic Versioning 术语。新的次版本可能向 API 添加额外特性。

10.18.2 支持的发布分支

  • etcd 项目为当前版本与上一个发布版本维护 release branch。
  • 例如:v3.5 为当前版本时,v3.4 受支持;当 v3.6 发布时,v3.4 不再受支持。
  • 适用的修复(包括安全修复)可能被 backport 到这两个发布分支,取决于严重性与可行性;需要时从这些分支切出 patch 版本。该决策由项目 Maintainers 负责。

💡 补充:按上述规则推演,v3.7 为当前版本时,受支持的分支是 v3.7 与 v3.6;v3.5 及更早分支不再受支持(源文未明示 v3.7 场景,此处为规则推演,非原文)。

10.18.3 查看运行中集群版本

etcdctl --endpoints=127.0.0.1:2379 endpoint status

10.18.4 API 版本化

v3 API 的响应自 3.0.0 发布起不应发生变化,但新特性会随时间增加。

⚠️ 源文说明:源文未给出 API 版本兼容矩阵或跨版本升级/降级的具体兼容范围表格,本页不含此类内容。实际升级/降级的版本要求见第 14 章。

10.19 安全配置检查清单

项 建议 依据章节
client-to-server 加密 --cert-file + --key-file(key 必须未加密) 10.2
client 强制证书认证 --client-cert-auth + --trusted-ca-file 10.2
peer 双向 TLS --peer-client-cert-auth + --peer-trusted-ca-file + --peer-cert-file/--peer-key-file 10.3
peer 证书 EKU 必须同时含 serverAuth 与 clientAuth 10.5
自动 TLS 有效期 仅 1 年,用 --self-signed-cert-validity 调整 10.6.4
DNS SRV 引导 证书 SAN 需含 --discovery-srv 根域名 10.7
DNS Rebinding 防护 --host-whitelist 手动补 localhost/127.0.0.1 10.10
启用认证 先建 root 用户与 root 角色,再 auth enable 10.12
可观测端点 /metrics、/health 不受 RBAC 保护,需 mTLS 或内网绑定 10.14
磁盘加密 etcd 不加密磁盘数据,需应用层或 dm-crypt 10.11 Q3

11. 监控与告警

本章回答:etcd 暴露了哪些可观测端点、Prometheus 怎么配、哪些指标必须告警。指标全量 123 条见第 12 章。

引自:Monitoring etcd 引自:Metrics

每个 etcd server 通过 HTTP 端点在其 client port 上提供本地监控信息,数据可用于系统健康检查与集群调试。

11.1 可观测端点全景

端点 路径 启用条件 / 版本 用途
Debug /debug/pprof、/debug/requests 需 --log-level=debug Go runtime profiling、gRPC 调用轨迹
Metrics /metrics 默认在 client port;也可用 --listen-metrics-urls 指定 Prometheus 指标
Health /health v3.3.0 起,--listen-metrics-urls 指定的位置也会响应 健康检查(供 LB / 监控用)
Liveness /livez v3.4 起 进程是否存活、是否需要重启
Readiness /readyz v3.4 起 进程是否就绪可对外提供服务

11.2 Debug 端点(有代价)

设置 --log-level=debug 时,etcd server 在 client port 的 /debug 路径下导出调试信息。注意该设置会导致性能下降与日志冗长。

11.2.1 /debug/pprof

标准 Go runtime profiling 端点,可用于 CPU、heap、mutex、goroutine 使用率分析。

$ go tool pprof http://localhost:2379/debug/pprof/profile
Fetching profile from http://localhost:2379/debug/pprof/profile
Please wait... (30s)
Saved profile in /home/etcd/pprof/pprof.etcd.localhost:2379.samples.cpu.001.pb.gz
Entering interactive mode (type "help" for commands)
(pprof) top10
310ms of 480ms total (64.58%)
Showing top 10 nodes out of 157 (cum >= 10ms)
    flat  flat%   sum%        cum   cum%
   130ms 27.08% 27.08%      130ms 27.08%  runtime.futex
    70ms 14.58% 41.67%       70ms 14.58%  syscall.Syscall
    20ms  4.17% 45.83%       20ms  4.17%  github.com/coreos/etcd/vendor/golang.org/x/net/http2/hpack.huffmanDecode
    20ms  4.17% 50.00%       30ms  6.25%  runtime.pcvalue
    20ms  4.17% 54.17%       50ms 10.42%  runtime.schedule
    10ms  2.08% 56.25%       10ms  2.08%  github.com/coreos/etcd/vendor/github.com/coreos/etcd/etcdserver.(*EtcdServer).AuthInfoFromCtx
    10ms  2.08% 58.33%       10ms  2.08%  github.com/coreos/etcd/vendor/github.com/coreos/etcd/etcdserver.(*EtcdServer).Lead
    10ms  2.08% 60.42%       10ms  2.08%  github.com/coreos/etcd/vendor/github.com/coreos/etcd/pkg/wait.(*timeList).Trigger
    10ms  2.08% 62.50%       10ms  2.08%  github.com/coreos/etcd/vendor/github.com/prometheus/client_golang/prometheus.(*MetricVec).hashLabelValues
    10ms  2.08% 64.58%       10ms  2.08%  github.com/coreos/etcd/vendor/golang.org/x/net/http2.(*Framer).WriteHeaders

11.2.2 /debug/requests

通过浏览器给出 gRPC 调用轨迹与性能统计。示例(对 key abc 的 Range 请求):

When	Elapsed (s)
2017/08/18 17:34:51.999317 	0.000244 	/etcdserverpb.KV/Range
17:34:51.999382 	 .    65 	... RPC: from 127.0.0.1:47204 deadline:4.999377747s
17:34:51.999395 	 .    13 	... recv: key:"abc"
17:34:51.999499 	 .   104 	... OK
17:34:51.999535 	 .    36 	... sent: header:<cluster_id:14841639068965178418 member_id:10276657743932975437 revision:15 raft_term:17 > kvs:<key:"abc" create_revision:6 mod_revision:14 version:9 value:"asda" > count:1

11.3 Metrics 端点

每个 etcd server 在 client port 的 /metrics 路径导出指标,也可选地在 --listen-metrics-urls 指定的位置导出。用 curl 抓取(源文示例用 grep -v debugging 过滤不稳定的 debugging 指标):

$ curl -L http://localhost:2379/metrics | grep -v debugging # ignore unstable debugging metrics

# HELP etcd_disk_backend_commit_duration_seconds The latency distributions of commit called by backend.
# TYPE etcd_disk_backend_commit_duration_seconds histogram
etcd_disk_backend_commit_duration_seconds_bucket{le="0.002"} 72756
etcd_disk_backend_commit_duration_seconds_bucket{le="0.004"} 401587
etcd_disk_backend_commit_duration_seconds_bucket{le="0.008"} 405979
etcd_disk_backend_commit_duration_seconds_bucket{le="0.016"} 406464
...

11.4 健康检查端点

端点 引入版本 语义
/health v3.3.0 起,--listen-metrics-urls 指定的位置也会响应 标准健康检查。当标准端点配置了双向(客户端)TLS 认证,而负载均衡器或监控服务仍需访问健康检查时,这一设计很有用
/livez v3.4 起 反映进程是否存活、或是否需要重启
/readyz v3.4 起 反映进程是否就绪可对外提供服务

设计细节记录在 Kubernetes KEP(sig-etcd/4331-livez-readyz)中。

每个端点包含若干独立的健康检查项,可用 verbose 参数打印各检查项及其状态:

curl -k http://localhost:2379/readyz?verbose

响应类似:

[+]data_corruption ok
[+]serializable_read ok
[+]linearizable_read ok
ok

HTTP API 也支持排除特定检查项:

curl -k http://localhost:2379/readyz?exclude=data_corruption

源文中出现的检查项名称共三个:

检查项 含义(按名称)
data_corruption 数据损坏检查
serializable_read 可串行化读检查
linearizable_read 线性一致读检查

💡 补充:把 /livez 与 /readyz 分开用——存活探针用 /livez(避免 etcd 短暂不可读时被 K8s 误杀重启),就绪探针用 /readyz。排障时先打 ?verbose 看是哪一项不通过,比反复看日志快得多。

11.5 Prometheus 抓取配置

运行 Prometheus 是摄取与记录 etcd 指标的最简单方式。安装(源文示例版本 2.0.0):

PROMETHEUS_VERSION="2.0.0"
wget https://github.com/prometheus/prometheus/releases/download/v$PROMETHEUS_VERSION/prometheus-$PROMETHEUS_VERSION.linux-amd64.tar.gz -O /tmp/prometheus-$PROMETHEUS_VERSION.linux-amd64.tar.gz
tar -xvzf /tmp/prometheus-$PROMETHEUS_VERSION.linux-amd64.tar.gz --directory /tmp/ --strip-components=1
/tmp/prometheus -version

scrape 配置(scrape_interval: 10s):

global:
  scrape_interval: 10s
scrape_configs:
  - job_name: test-etcd
    static_configs:
    - targets: ['10.240.0.32:2379','10.240.0.33:2379','10.240.0.34:2379']

写入配置文件并启动:

cat > /tmp/test-etcd.yaml <<EOF
global:
  scrape_interval: 10s
scrape_configs:
  - job_name: test-etcd
    static_configs:
    - targets: ['10.240.0.32:2379','10.240.0.33:2379','10.240.0.34:2379']
EOF
cat /tmp/test-etcd.yaml
nohup /tmp/prometheus \
    -config.file /tmp/test-etcd.yaml \
    -web.listen-address ":9090" \
    -storage.local.path "test-etcd.data" >> /tmp/test-etcd.log  2>&1 &

配置完成后 Prometheus 每 10 秒抓取一次 etcd 指标。

⚠️ 源文说明:以上为源文示例命令,Prometheus 2.0.0 版本较旧,实际生产请使用当前版本并参考其官方文档的 flag 命名(如 --config.file)。本册不对源文命令做改写。

11.6 Alerting(告警)

etcd v3 集群有一组 default alerts(Prometheus 用)。

注意:job label 可能需要按实际需求调整;这些规则是针对单一集群编写的,因此建议选择集群唯一的 label。

⚠️ 源文说明:本页未在正文中给出任何具体 PromQL 告警表达式与阈值,仅链接到 contrib/mixin 的外部规则集,故本册不列举表达式。

11.7 Grafana

Grafana 内置 Prometheus 支持,只需添加 Prometheus data source:

Name:   test-etcd
Type:   Prometheus
Url:    http://localhost:9090
Access: proxy

然后导入默认的 etcd dashboard 模板(../grafana.json)并按需定制。例如若 Prometheus data source 名为 my-etcd,则 JSON 中的 datasource 字段值也需改为 my-etcd。页面含一张示例 dashboard 截图 ../etcd-sample-grafana.png。

11.8 分布式追踪(Distributed tracing)

v3.5 起 etcd 支持使用 OpenTelemetry 做分布式追踪。该特性仍为实验性,可能随时变更。

启用方式:给 etcd server 传 --experimental-enable-distributed-tracing=true,并用 --experimental-distributed-tracing-sampling-rate=<number> 选择每百万 span 采集多少样本,默认采样率为 0。

flag 可选性 默认值 说明
--experimental-distributed-tracing-address (可选) "localhost:4317" tracing collector 地址
--experimental-distributed-tracing-service-name (可选) "etcd" 分布式追踪服务名,所有 etcd 实例必须相同
--experimental-distributed-tracing-instance-id (可选) — 实例 ID,虽可选但强烈建议设置,且每个 etcd 实例必须唯一

启用前请确保已有 OpenTelemetry 端点;若地址与默认值不同,用 --experimental-distributed-tracing-address 覆盖。

开销提示:任何可观测性信号都有资源开销,据初始测量,该特性的 CPU 开销约在 2% - 4% 之间。

11.9 关键指标清单(来自 metrics/_index.md)

引自:Metrics

11.9.1 总览与命名空间规则

etcd 使用 Prometheus 上报指标,指标可用于实时监控与调试。etcd 不持久化其指标:成员重启后指标会被重置。

命名规则:遵循 Prometheus 建议的最佳实践。指标名以 etcd 或 etcd_debugging 前缀作为命名空间(namespace),后跟子系统(subsystem)前缀(例如 wal、etcdserver)。

  • etcd 命名空间下的指标用于监控与告警,是稳定的高层指标;若这些指标发生任何变更,会包含在 release notes 中。
  • etcd2 相关指标另见 v2 metrics guide。

11.9.2 Server 指标(前缀 etcd_server_)

描述 etcd server 状态。为了发现故障或排障,每个生产 etcd 集群都应密切监控这些 server 指标。

Name Description Type
has_leader Whether or not a leader exists. 1 is existence, 0 is not. Gauge
leader_changes_seen_total The number of leader changes seen. Counter
proposals_committed_total The total number of consensus proposals committed. Gauge
proposals_applied_total The total number of consensus proposals applied. Gauge
proposals_pending The current number of pending proposals. Gauge
proposals_failed_total The total number of failed proposals seen. Counter

逐条含义:

指标 含义与告警解读
etcd_server_has_leader 指示该成员是否有 leader。某成员没有 leader 则完全不可用;若集群所有成员都没有 leader,则整个集群完全不可用。
etcd_server_leader_changes_seen_total 自启动以来该成员看到的 leader 变更次数。频繁的 leader 变更会显著影响 etcd 性能,也说明 leader 不稳定,可能源于网络连通性问题或集群负载过重
etcd_server_proposals_committed_total 已提交的共识提案总数。集群健康时该 gauge 应随时间增长。健康的多个成员在同一时刻的总提交数可能不同,原因可能是启动后从 peer 恢复、落后于 leader,或本身是 leader 因而提交最多。必须跨所有成员监控此指标;若单个成员与 leader 持续存在较大滞后,说明该成员缓慢或不健康。
etcd_server_proposals_applied_total 已应用的共识提案总数。etcd server 异步应用每个已提交提案。proposals_committed_total 与 proposals_applied_total 的差值通常应很小(即使高负载下也在几千以内)。若差值持续增大,说明 etcd server 过载,可能发生在执行昂贵查询(如大范围 range 查询或大型 txn 操作)时
etcd_server_proposals_pending 排队等待提交的提案数。pending 上升说明客户端负载高,或该成员无法提交提案。
etcd_server_proposals_failed_total 通常与两类问题相关:与 leader 选举相关的临时性失败,或因集群丧失 quorum 造成的较长停机

💡 补充:「committed 与 applied 差值即使高负载下也在几千以内」是源文给出的经验量级,可作为告警阈值的起点——超过这个量级就要怀疑 server 过载或存在大 range/txn 查询。

11.9.3 Disk 指标(前缀 etcd_disk_)

Name Description Type
wal_fsync_duration_seconds The latency distributions of fsync called by wal Histogram
backend_commit_duration_seconds The latency distributions of commit called by backend. Histogram
  • etcd_disk_wal_fsync_duration_seconds:etcd 在应用日志条目之前将其持久化到磁盘时调用 wal_fsync。
  • etcd_disk_backend_commit_duration_seconds:etcd 把最近变更的增量快照提交到磁盘时调用 backend_commit。
  • 高磁盘操作延迟(上述任一指标)通常指示磁盘问题,可能导致高请求延迟或使集群不稳定。

11.9.4 Network 指标(前缀 etcd_network_)

Name Description Type
peer_sent_bytes_total The total number of bytes sent to the peer with ID To. Counter(To)
peer_received_bytes_total The total number of bytes received from the peer with ID From. Counter(From)
peer_sent_failures_total The total number of send failures from the peer with ID To. Counter(To)
peer_received_failures_total The total number of receive failures from the peer with ID From. Counter(From)
peer_round_trip_time_seconds Round-Trip-Time histogram between peers. Histogram(To)
client_grpc_sent_bytes_total The total number of bytes sent to grpc clients. Counter
client_grpc_received_bytes_total The total number of bytes received to grpc clients. Counter
  • etcd_network_peer_sent_bytes_total:发往特定 peer 的总字节数。通常 leader 成员发送的数据多于其他成员,因为它负责传输复制数据。
  • etcd_network_peer_received_bytes_total:从特定 peer 接收的总字节数。通常 follower 只从 leader 成员接收数据。

11.9.5 gRPC requests

这类指标通过 go-grpc-prometheus 暴露。

⚠️ 源文说明:源文在该小节下未列出具体指标名称与表格。 实际可观测的 gRPC 指标(如 grpc_server_* 四条)见第 12 章指标全表。

11.9.6 etcd_debugging 命名空间指标

etcd_debugging 前缀下的指标用于调试,高度依赖具体实现且易变,可能在新 etcd 版本中不做任何警告地被修改或移除。其中部分指标在变得更稳定后可能被移到 etcd 前缀下。

Snapshot 类:

Name Description Type
snapshot_save_total_duration_seconds The total latency distributions of save called by snapshot Histogram

即 etcd_debugging_snapshot_save_total_duration_seconds:异常偏高的快照耗时指示磁盘问题,并可能导致集群不稳定。

11.9.7 Prometheus 供给的指标(Prometheus supplied metrics)

Prometheus client library 在 go 与 process 命名空间下提供若干指标,其中两项尤受关注:

Name Description Type
process_open_fds Number of open file descriptors. Gauge
process_max_fds Maximum number of open file descriptors. Gauge

注意:process_open_fds、process_max_fds 这类 process 指标目前在 Darwin(macOS)系统上不受支持。

文件描述符(process_open_fds)占用过高(即接近进程的 fd 上限 process_max_fds)意味着存在 fd 耗尽风险;一旦 fd 耗尽,etcd 可能因无法创建新的 WAL 文件而 panic。

11.9.8 指标生成清单

源文末尾仅有 "Generated list of metrics" 小节标题,正文未提供生成的指标清单内容(同目录下存在 etcd-metrics-latest.txt)。

💡 补充:本册第 12 章正是补全了这一缺口——按 etcd-metrics-latest 页面给出全部 123 条指标。

11.10 监控落地建议(值班视角)

优先级 监控对象 指标 说明
P0 是否有 leader etcd_server_has_leader 任何一个成员为 0 即需立即处理
P0 磁盘 fsync/commit 延迟 etcd_disk_wal_fsync_duration_seconds、etcd_disk_backend_commit_duration_seconds 延迟高 = 磁盘问题,会引起请求延迟与集群不稳
P0 fd 水位 process_open_fds / process_max_fds 接近上限会导致无法创建 WAL 而 panic
P1 提交/应用落差 etcd_server_proposals_committed_total - etcd_server_proposals_applied_total 差值持续增大说明 server 过载
P1 leader 抖动 etcd_server_leader_changes_seen_total 频繁变更显著影响性能
P1 pending 堆积 etcd_server_proposals_pending 客户端负载高或成员无法提交
P2 peer 往返时延 etcd_network_peer_round_trip_time_seconds 跨机房/网络质量问题定位
P2 快照耗时 etcd_debugging_snapshot_save_total_duration_seconds 偏高指示磁盘问题

💡 补充:以上优先级与阈值口径为本册基于源文「含义描述」整理的建议,源文 monitoring 页未给出具体 PromQL 表达式与阈值(见 11.6)。落地时请以 contrib/mixin 的默认告警规则为准。

11.11 日志约定(zap 级别)

etcd 使用 zap 库(https://github.com/uber-go/zap)输出应用日志,日志按 levels(级别)分类。日志级别决定你在告警里能看到什么,因此放进监控章节:

级别 语义 源文示例 告警含义
DebugLevel 日志量通常很大,生产环境一般禁用 向远端 peer 发送一条普通消息;向磁盘写一条日志条目 只在排障时临时开启(--log-level=debug 会导致性能下降与日志冗长)
InfoLevel 默认的日志优先级 启动配置;开始做快照;向集群加入新节点;向 auth 子系统加入新用户 正常信息,无需告警
WarnLevel 比 Info 更重要,但不需要人工逐条介入 review 向远端 peer 发送 Raft 消息失败;在配置的 election timeout 内未收到心跳消息 值得告警:往往是抖动/网络问题的前兆
ErrorLevel 高优先级。若应用运行平稳,不应该产生任何 error 级日志 为 WAL 分配磁盘空间失败 必须告警
PanicLevel 记录一条消息后 panic Raft 消息编码失败 必须告警(进程将退出)
FatalLevel 记录一条消息后调用 os.Exit(1) Raft 快照保存失败 必须告警(进程将退出)

💡 补充:把告警阈值定在 Warn 及以上,并把 Error/Panic/Fatal 设为立即告警——源文对 Error 的描述是「应用运行平稳时不应该产生任何 error 级日志」,这本身就是最好的告警基线。完整约定见 16.2 节。


12. 指标全表(123 条)

引自:etcd Metrics(latest)(线上生成页,源文件名 metrics-latest.txt)

本章按命名空间分组,完整收录 123 条指标。列含义:

  • 指标名:Prometheus 暴露的原始名称,原样保留;
  • 类型:gauge / counter / histogram / summary;
  • 中文说明:官方描述的中译;
  • 标签:维度标签,- 表示该指标无维度标签;标签值沿用页面给出的示例值。

12.0 分组计数汇总(自检用)

命名空间 条数
etcd_debugging_* 35
go_* 28
etcd_server_* 21
etcd_mvcc_* 9
process_* 7
etcd_network_* 6
etcd_disk_* 5
grpc_server_* 4
etcd_snap_* 3
os_* 2
promhttp_* 2
etcd_cluster_* 1
合计 123

💡 补充(三点使用提醒):

  1. 任务描述称 metrics-latest.txt 为「124 行指标表」,但实际有效指标行为 123 行,本册按实际内容全部收录,未补写任何不存在的指标。
  2. 不存在 etcd_raft_* 命名空间——v3.7 latest 指标表中没有该组,raft 相关状态请用 etcd_server_has_leader、etcd_server_leader_changes_seen_total、etcd_server_proposals_* 等代替。
  3. etcd_debugging_* 占比最大(35/123),且与 etcd_mvcc_*、etcd_disk_*、etcd_snap_* 存在语义重叠(如 etcd_debugging_mvcc_db_total_size_in_bytes 与 etcd_mvcc_db_total_size_in_bytes)。做告警时优先用非 debugging 命名空间,debugging 组留给排障下钻。

12.1 etcd_cluster_*(1 条)

指标名 类型 中文说明 标签
etcd_cluster_version gauge 正在运行的版本。对带有当前集群版本的 cluster_version 标签取值为 1 cluster_version="3.5"

12.2 etcd_debugging_*(35 条)

指标名 类型 中文说明 标签
etcd_debugging_auth_revision gauge auth store 当前的 revision -
etcd_debugging_disk_backend_commit_rebalance_duration_seconds histogram bboltdb backend 调用 commit.rebalance 的延迟分布 le="0.001"
etcd_debugging_disk_backend_commit_spill_duration_seconds histogram bboltdb backend 调用 commit.spill 的延迟分布 le="0.001"
etcd_debugging_disk_backend_commit_write_duration_seconds histogram bboltdb backend 调用 commit.write 的延迟分布 le="0.001"
etcd_debugging_lease_granted_total counter 已授予的 lease 总数 -
etcd_debugging_lease_renewed_total counter leader 观测到的续约 lease 数量 -
etcd_debugging_lease_revoked_total counter 已撤销的 lease 总数 -
etcd_debugging_lease_ttl_total histogram lease TTL 的分桶直方图 le="1"
etcd_debugging_mvcc_compact_revision gauge store 中最近一次压缩的 revision -
etcd_debugging_mvcc_current_revision gauge store 当前的 revision -
etcd_debugging_mvcc_db_compaction_keys_total counter 已压缩的 db key 总数 -
etcd_debugging_mvcc_db_compaction_last gauge 上一次 db 压缩的 unix 时间,启动时重置为 0 -
etcd_debugging_mvcc_db_compaction_pause_duration_milliseconds histogram db 压缩暂停时长的分桶直方图 le="1"
etcd_debugging_mvcc_db_compaction_total_duration_milliseconds histogram db 压缩总时长的分桶直方图 le="100"
etcd_debugging_mvcc_db_total_size_in_bytes gauge 底层数据库物理分配的总大小(字节) -
etcd_debugging_mvcc_delete_total counter 本成员观测到的 delete 总数 -
etcd_debugging_mvcc_events_total counter 本成员发送的事件总数 -
etcd_debugging_mvcc_index_compaction_pause_duration_milliseconds histogram 索引压缩暂停时长的分桶直方图 le="0.5"
etcd_debugging_mvcc_keys_total gauge key 总数 -
etcd_debugging_mvcc_pending_events_total gauge 待发送的事件总数 -
etcd_debugging_mvcc_put_total counter 本成员观测到的 put 总数 -
etcd_debugging_mvcc_range_total counter 本成员观测到的 range 总数 -
etcd_debugging_mvcc_slow_watcher_total gauge 未同步的慢 watcher 总数 -
etcd_debugging_mvcc_total_put_size_in_bytes gauge 本成员观测到的 put kv 对总大小 -
etcd_debugging_mvcc_txn_total counter 本成员观测到的 txn 总数 -
etcd_debugging_mvcc_watch_stream_total gauge watch stream 总数 -
etcd_debugging_mvcc_watcher_total gauge watcher 总数 -
etcd_debugging_server_lease_expired_total counter 已过期的 lease 总数 -
etcd_debugging_snap_save_marshalling_duration_seconds histogram snapshot 调用 save 时的 marshalling 开销分布 le="0.001"
etcd_debugging_snap_save_total_duration_seconds histogram snapshot 调用 save 的总延迟分布 le="0.001"
etcd_debugging_store_expires_total counter 过期的 key 总数 -
etcd_debugging_store_reads_total counter 本成员本地读操作(get/getRecursive)总数,local to this member action="getRecursive"
etcd_debugging_store_watch_requests_total counter 进入的 watch 请求总数(新建或重连) -
etcd_debugging_store_watchers gauge 当前活跃的 watcher 数 -
etcd_debugging_store_writes_total counter 本成员观测到的写操作(如 set/compareAndDelete)总数 action="create"

12.3 etcd_disk_*(5 条)

指标名 类型 中文说明 标签
etcd_disk_backend_commit_duration_seconds histogram backend 调用 commit 的延迟分布 le="0.001"
etcd_disk_backend_defrag_duration_seconds histogram backend 碎片整理(defragmentation)的延迟分布 le="0.1"
etcd_disk_backend_snapshot_duration_seconds histogram backend 快照的延迟分布 le="0.01"
etcd_disk_wal_fsync_duration_seconds histogram WAL 调用 fsync 的延迟分布 le="0.001"
etcd_disk_wal_write_bytes_total gauge 写入 WAL 的总字节数 -

💡 补充:这一组是排查「慢磁盘」类故障的第一现场——etcd_disk_wal_fsync_duration_seconds 的 p99 是 FAQ 中明确给出的阈值对象(< 10ms,见第 17 章)。

12.4 etcd_mvcc_*(9 条)

指标名 类型 中文说明 标签
etcd_mvcc_db_open_read_transactions gauge 当前打开的读事务数 -
etcd_mvcc_db_total_size_in_bytes gauge 底层数据库物理分配的总大小(字节) -
etcd_mvcc_db_total_size_in_use_in_bytes gauge 底层数据库逻辑上正在使用的总大小(字节) -
etcd_mvcc_delete_total counter 本成员观测到的 delete 总数 -
etcd_mvcc_hash_duration_seconds histogram storage hash 操作的延迟分布 le="0.01"
etcd_mvcc_hash_rev_duration_seconds histogram storage 按 revision 做 hash 操作的延迟分布 le="0.01"
etcd_mvcc_put_total counter 本成员观测到的 put 总数 -
etcd_mvcc_range_total counter 本成员观测到的 range 总数 -
etcd_mvcc_txn_total counter 本成员观测到的 txn 总数 -

12.5 etcd_network_*(6 条)

指标名 类型 中文说明 标签
etcd_network_active_peers gauge 当前活跃的 peer 连接数 Local="LOCAL_NODE_ID",Remote="REMOTE_PEER_NODE_ID"
etcd_network_client_grpc_received_bytes_total counter 从 grpc 客户端接收的总字节数 -
etcd_network_client_grpc_sent_bytes_total counter 发送给 grpc 客户端的总字节数 -
etcd_network_peer_received_bytes_total counter 从 peer 接收的总字节数 From="REMOTE_PEER_NODE_ID"
etcd_network_peer_round_trip_time_seconds histogram peer 之间的往返时间(RTT)直方图 To="REMOTE_PEER_NODE_ID",le="+Inf"
etcd_network_peer_sent_bytes_total counter 发送给 peer 的总字节数 To="REMOTE_PEER_NODE_ID"

12.6 etcd_server_*(21 条)

指标名 类型 中文说明 标签
etcd_server_client_requests_total counter 按客户端版本统计的客户端请求总数 client_api_version="3.5",type="stream"
etcd_server_go_version gauge 服务端运行所用的 Go 版本。对带当前版本的 server_go_version 标签取值为 1 server_go_version="go1.14.3"
etcd_server_has_leader gauge 是否存在 leader。1 表示存在,0 表示不存在 -
etcd_server_health_failures counter 健康检查失败的总次数 -
etcd_server_health_success counter 健康检查成功的总次数 -
etcd_server_heartbeat_send_failures_total counter leader 心跳发送失败总次数(通常是慢磁盘导致过载) -
etcd_server_id gauge 服务/成员 ID(十六进制格式)。对带当前 ID 的 server_id 标签取值为 1 server_id="451af08594378132"
etcd_server_is_leader gauge 本成员是否为 leader。1 是,0 否 -
etcd_server_is_learner gauge 本成员是否为 learner。1 是,0 否 -
etcd_server_leader_changes_seen_total counter 观测到的 leader 变更次数 -
etcd_server_learner_promote_successes counter 本成员为 leader 时,learner 提升成功的总次数 -
etcd_server_proposals_applied_total gauge 已应用的共识提案总数 -
etcd_server_proposals_committed_total gauge 已提交的共识提案总数 -
etcd_server_proposals_failed_total counter 观测到的失败提案总数 -
etcd_server_proposals_pending gauge 当前待提交的提案数 -
etcd_server_quota_backend_bytes gauge 当前 backend 存储配额大小(字节) -
etcd_server_read_indexes_failed_total counter 失败的 read index 总数 -
etcd_server_slow_apply_total counter 慢 apply 请求总数(通常是慢磁盘导致过载) -
etcd_server_slow_read_indexes_total counter 与 leader 不同步或已超时的 pending read index 请求总数 -
etcd_server_snapshot_apply_in_progress_total gauge 服务端正在应用 incoming snapshot 时为 1,否则为 0 -
etcd_server_version gauge 正在运行的版本。对带当前版本的 server_version 标签取值为 1 server_version="3.5.0-pre"

12.7 etcd_snap_*(3 条)

指标名 类型 中文说明 标签
etcd_snap_db_fsync_duration_seconds histogram fsync .snap.db 文件的延迟分布 le="0.001"
etcd_snap_db_save_total_duration_seconds histogram v3 snapshot save 的总延迟分布 le="0.1"
etcd_snap_fsync_duration_seconds histogram snap 调用 fsync 的延迟分布 le="0.001"

12.8 grpc_server_*(4 条)

指标名 类型 中文说明 标签
grpc_server_handled_total counter 服务端已完成的 RPC 总数,无论成功或失败 grpc_code="CODE",grpc_method="Alarm",grpc_service="etcdserverpb.Maintenance",grpc_type="unary"
grpc_server_msg_received_total counter 服务端接收的 RPC 流消息总数 grpc_method="Alarm",grpc_service="etcdserverpb.Maintenance",grpc_type="unary"
grpc_server_msg_sent_total counter 服务端发送的 gRPC 流消息总数 grpc_method="Alarm",grpc_service="etcdserverpb.Maintenance",grpc_type="unary"
grpc_server_started_total counter 服务端启动的 RPC 总数 grpc_method="Alarm",grpc_service="etcdserverpb.Maintenance",grpc_type="unary"

12.9 go_*(28 条)

指标名 类型 中文说明 标签
go_gc_duration_seconds summary GC 调用耗时的摘要 quantile="0"
go_goroutines gauge 当前存在的 goroutine 数 -
go_info gauge Go 环境信息 version="go1.14.3"
go_memstats_alloc_bytes gauge 已分配且仍在使用的字节数 -
go_memstats_alloc_bytes_total counter 已分配的字节总数,即使已被释放 -
go_memstats_buck_hash_sys_bytes gauge profiling bucket hash 表占用的字节数 -
go_memstats_frees_total counter free 总次数 -
go_memstats_gc_cpu_fraction gauge 程序启动以来 GC 占用可用 CPU 时间的比例 -
go_memstats_gc_sys_bytes gauge GC 系统元数据占用的字节数 -
go_memstats_heap_alloc_bytes gauge 已分配且仍在使用的堆字节数 -
go_memstats_heap_idle_bytes gauge 等待被使用的堆字节数 -
go_memstats_heap_inuse_bytes gauge 正在使用的堆字节数 -
go_memstats_heap_objects gauge 已分配的对象数 -
go_memstats_heap_released_bytes gauge 已归还给操作系统的堆字节数 -
go_memstats_heap_sys_bytes gauge 从系统获取的堆字节数 -
go_memstats_last_gc_time_seconds gauge 上次 GC 距 1970 年的秒数 -
go_memstats_lookups_total counter 指针查找总次数 -
go_memstats_mallocs_total counter malloc 总次数 -
go_memstats_mcache_inuse_bytes gauge mcache 结构正在使用的字节数 -
go_memstats_mcache_sys_bytes gauge 从系统获取的用于 mcache 结构的字节数 -
go_memstats_mspan_inuse_bytes gauge mspan 结构正在使用的字节数 -
go_memstats_mspan_sys_bytes gauge 从系统获取的用于 mspan 结构的字节数 -
go_memstats_next_gc_bytes gauge 下次 GC 触发时的堆字节数 -
go_memstats_other_sys_bytes gauge 其他系统分配占用的字节数 -
go_memstats_stack_inuse_bytes gauge 栈分配器正在使用的字节数 -
go_memstats_stack_sys_bytes gauge 从系统获取的用于栈分配器的字节数 -
go_memstats_sys_bytes gauge 从系统获取的字节数 -
go_threads gauge 已创建的 OS 线程数 -

12.10 os_*(2 条)

指标名 类型 中文说明 标签
os_fd_limit gauge 文件描述符上限 -
os_fd_used gauge 已使用的文件描述符数 -

12.11 process_*(7 条)

指标名 类型 中文说明 标签
process_cpu_seconds_total counter 用户态与内核态 CPU 耗时总秒数 -
process_max_fds gauge 最大可打开文件描述符数 -
process_open_fds gauge 已打开的文件描述符数 -
process_resident_memory_bytes gauge 常驻内存大小(字节) -
process_start_time_seconds gauge 进程启动时间,unix epoch 秒 -
process_virtual_memory_bytes gauge 虚拟内存大小(字节) -
process_virtual_memory_max_bytes gauge 可用虚拟内存上限(字节) -

12.12 promhttp_*(2 条)

指标名 类型 中文说明 标签
promhttp_metric_handler_requests_in_flight gauge 当前正在处理的 scrape 数 -
promhttp_metric_handler_requests_total counter 按 HTTP 状态码统计的 scrape 总数 code="200"

12.13 值班常用指标速查(非官方,基于上表的组合建议)

💡 补充:以下组合为本册基于 123 条指标给出的排障入口,非官方文档原文。

症状 优先看的指标
集群没有 leader / 频繁切主 etcd_server_has_leader、etcd_server_leader_changes_seen_total、etcd_server_is_leader
写入变慢 etcd_disk_wal_fsync_duration_seconds、etcd_disk_backend_commit_duration_seconds、etcd_server_slow_apply_total
心跳超时告警 etcd_server_heartbeat_send_failures_total、etcd_network_peer_round_trip_time_seconds
空间将满 etcd_mvcc_db_total_size_in_bytes vs etcd_server_quota_backend_bytes
碎片严重 etcd_mvcc_db_total_size_in_bytes(物理)与 etcd_mvcc_db_total_size_in_use_in_bytes(逻辑)差值
watcher 积压 etcd_debugging_mvcc_slow_watcher_total、etcd_debugging_mvcc_pending_events_total
压缩是否推进 etcd_debugging_mvcc_compact_revision vs etcd_debugging_mvcc_current_revision
fd 是否将耗尽 os_fd_used / os_fd_limit、process_open_fds / process_max_fds
版本盘点 etcd_server_version、etcd_cluster_version、etcd_server_go_version
learner 状态 etcd_server_is_learner、etcd_server_learner_promote_successes

13. 代理层:etcd gateway 与 gRPC proxy

引自:etcd gateway、gRPC proxy

13.1 定位差异总览

维度 etcd gateway gRPC proxy
工作层次 TCP 层 gRPC 层(L7)
角色 简单的 TCP 代理,把网络数据转发到 etcd 集群 无状态 etcd 反向代理
状态 无状态且透明:不检查客户端请求、不干预集群响应 无状态,但会理解并改写请求语义
TLS 不终止 TLS、不代客户端做 TLS 握手、不校验连接是否安全 可终止 TLS,对外提供未加密本地 endpoint
endpoint 选择 支持多个 endpoint,简单轮询(round-robin) 启动时随机挑选一个 endpoint,故障时才切换
故障处理 只路由到可用 endpoint,对客户端隐藏故障 检测到 endpoint 故障则切到其他可用 endpoint
缓存 / 合并 无缓存、无 watch 合并、无批处理 合并 watch 与 lease、缓存 key range 请求
主要目的 提供稳定的本地 endpoint,让集群 endpoint 变化自动传播 降低核心集群负载、提升可扩展性
未来演进 可能支持加权轮询等重试策略 可能支持加权轮询等策略

etcd gateway 的「何时用 / 何时不用」

何时使用:每个访问 etcd 的应用都必须先知道 etcd 集群的 client endpoint 地址;同机多个应用访问同一集群时都要各自维护该列表,集群重新配置后还要逐一更新,既繁琐又易错。gateway 作为稳定的本地 endpoint 解决这个问题:典型配置是每台机器跑一个 gateway 监听本地地址,本机所有 etcd 应用连本地 gateway,集群变更时只需更新 gateway 的 endpoints。

总结:为了让集群 endpoint 变化自动传播,应在每台运行多个访问同一 etcd 集群应用的机器上部署 etcd gateway。

何时不要用:

  • 提升性能:gateway 不是为提升集群性能设计的,不提供缓存、watch 合并(coalescing)或批处理;etcd 团队另有面向集群可扩展性的缓存代理(即 gRPC proxy)在开发中。
  • 运行在集群管理系统上:Kubernetes 这类高级集群管理系统原生支持服务发现,应用可用 DNS 名或系统托管的虚拟 IP 访问 etcd;kube-proxy 就等价于 etcd gateway。

13.2 etcd gateway

启动命令(静态 endpoint)

集群静态 endpoint 示例:infra0 = 10.0.1.10 / infra0.example.com:2379,infra1 = 10.0.1.11 / infra1.example.com:2379,infra2 = 10.0.1.12 / infra2.example.com:2379。

$ etcd gateway start --endpoints=infra0.example.com:2379,infra1.example.com:2379,infra2.example.com:2379
2016-08-16 11:21:18.867350 I | tcpproxy: ready to proxy client requests to [...]

启动命令(DNS SRV 发现)

用 DNS SRV 做服务发现时,先有 SRV 记录:

$ dig +noall +answer SRV _etcd-client._tcp.example.com
_etcd-client._tcp.example.com. 300 IN SRV 0 0 2379 infra0.example.com.
_etcd-client._tcp.example.com. 300 IN SRV 0 0 2379 infra1.example.com.
_etcd-client._tcp.example.com. 300 IN SRV 0 0 2379 infra2.example.com.

$ dig +noall +answer infra0.example.com infra1.example.com infra2.example.com
infra0.example.com.  300  IN  A  10.0.1.10
infra1.example.com.  300  IN  A  10.0.1.11
infra2.example.com.  300  IN  A  10.0.1.12

再从 SRV 记录拉取 endpoints:

$ etcd gateway start --discovery-srv=example.com
2016-08-16 11:21:18.867350 I | tcpproxy: ready to proxy client requests to [...]

全部 flag

etcd cluster 类

flag 说明 默认值 / 注意
--endpoints 转发客户端连接的 etcd server 目标,逗号分隔列表 默认 127.0.0.1:2379;必须带端口;非法示例 https://127.0.0.1:2379(gateway 不终止 TLS)。gateway 不校验 HTTP scheme 也不检查请求,只做转发
--discovery-srv 通过 SRV 记录引导集群 endpoint 的 DNS 域名 默认未设置

Network 类

flag 说明 默认值 / 注意
--listen-addr 接受客户端请求所绑定的接口与端口 默认 127.0.0.1:23790
--retry-delay 重连失败 endpoint 前的延迟时长 默认 1m0s;非法示例 "123"(需要带时间单位)

Security 类

flag 说明 默认值 / 注意
--insecure-discovery 接受不安全或易受中间人攻击的 SRV 记录 默认 false
--trusted-ca-file 客户端 TLS CA 文件路径,用于校验 SRV 发现返回的 endpoint。仅用于认证被发现的 endpoint,不用于创建数据传输连接;gateway 永不终止 TLS,也不代客户端建立 TLS 连接 默认未设置

13.3 gRPC proxy

概述与 endpoint 选择

gRPC proxy 是运行在 gRPC 层(L7)的无状态 etcd 反向代理,用于降低核心 etcd 集群的总处理负载:为水平扩展合并 watch 与 lease API 请求;为防止滥用客户端拖垮集群,缓存 key range 请求。

支持多个 etcd server endpoint:启动时随机挑选一个 endpoint,该 endpoint 服务所有请求,直到 proxy 检测到 endpoint 故障;此时若还有其他 endpoint 可用则切换过去,对客户端隐藏故障。

可扩展 watch API(watch 聚合)

proxy 把同一 key 或 range 上的多个客户端 watcher(c-watcher)合并为连接到 etcd server 的单个 watcher(s-watcher),再把 s-watcher 的所有事件广播给各个 c-watcher。

N 个客户端 watch 同一 key 时,一个 gRPC proxy 可把 etcd server 上的 watch 负载从 N 降到 1;可部署多个 proxy 进一步分摊。

限制(一致性注意):为合并 watcher,proxy 会尽可能把新 c-watcher 并入已有的 s-watcher,而这个 s-watcher 可能因网络延迟或未投递的缓冲事件而与 etcd server 不同步。当 watch revision 未指定时,proxy 不保证 c-watcher 从最新的 store revision 开始 watch:例如客户端直连 etcd server 从 revision 1000 开始,而经 proxy 可能从 revision 990 开始。取消操作有类似限制:watcher 被取消时,etcd server 的 revision 可能大于取消响应的 revision。文档指出这两点对多数场景无碍,将来可能提供让 watcher 绕过 proxy 以获得更精确 revision 的选项。

可扩展 lease API(lease 合并)

客户端要维持 lease 必须至少建立一条到 etcd server 的 gRPC 流用于周期性心跳;大量客户端的重度 lease 活动会造成过多流与 CPU 消耗。proxy 支持 lease stream 合并:N 个客户端更新 lease 时,单个 proxy 把 server 上的流负载从 N 降到 1,可部署多个 proxy 进一步分摊。proxy 把各 c-stream 的客户端 lease 心跳转发到 s-stream,再把响应回给对应的 c-stream。

防滥用客户端

proxy 会在不破坏一致性要求的前提下缓存请求响应,从而保护 etcd server 免受紧循环(tight for loop)客户端的冲击。

启动与基本使用

静态 endpoint 示例:infra0 = 10.0.1.10 / infra0.example.com,infra1 = 10.0.1.11 / infra1.example.com,infra2 = 10.0.1.12 / infra2.example.com。

$ etcd grpc-proxy start --endpoints=infra0.example.com,infra1.example.com,infra2.example.com --listen-addr=127.0.0.1:2379

proxy 启动后监听 2379,把请求转发到上述三个 endpoint 之一。

$ ETCDCTL_API=3 etcdctl --endpoints=127.0.0.1:2379 put foo bar
OK
$ ETCDCTL_API=3 etcdctl --endpoints=127.0.0.1:2379 get foo
foo
bar

Client endpoint 同步与名字解析

proxy 支持把自己的 endpoint 注册到用户定义的 endpoint 上供发现,用途有二:一是让客户端在一组 proxy endpoint 之间同步自己的 endpoint 以实现高可用;二是作为 etcd gRPC naming 的 endpoint provider。

$ etcd grpc-proxy start --endpoints=localhost:2379 \
  --listen-addr=127.0.0.1:23790 \
  --advertise-client-url=127.0.0.1:23790 \
  --resolver-prefix="___grpc_proxy_endpoint" \
  --resolver-ttl=60

$ etcd grpc-proxy start --endpoints=localhost:2379 \
  --listen-addr=127.0.0.1:23791 \
  --advertise-client-url=127.0.0.1:23791 \
  --resolver-prefix="___grpc_proxy_endpoint" \
  --resolver-ttl=60

带 resolver prefix 时,member list 会列出所有成员(示例中两条记录的 CLIENT ADDRS 分别为 127.0.0.1:23791 与 127.0.0.1:23790,PEER ADDRS 为空,ID 均为 0):

ETCDCTL_API=3 etcdctl --endpoints=http://localhost:23790 member list --write-out table

客户端可通过 Sync 自动发现 proxy endpoint:

cli, err := clientv3.New(clientv3.Config{
    Endpoints: []string{"http://localhost:23790"},
})
if err != nil {
    log.Fatal(err)
}
defer cli.Close()

// fetch registered grpc-proxy endpoints
if err := cli.Sync(context.Background()); err != nil {
    log.Fatal(err)
}

若启动时不带 resolver prefix:

$ etcd grpc-proxy start --endpoints=localhost:2379 \
  --listen-addr=127.0.0.1:23792 \
  --advertise-client-url=127.0.0.1:23792

则对 grpc-proxy 调用 member list API 只返回它自己的 advertise-client-url(示例为 127.0.0.1:23792)。

Namespacing(keyspace 隔离)

当应用期望对整个 key space 拥有完全控制权、而 etcd 集群又被其他应用共享时,可用 --namespace 让 proxy 对 keyspace 分区,使客户端看起来仍拥有完整 keyspace:所有进入 proxy 的客户端请求被翻译成带用户定义前缀的 key,访问 etcd 集群时挂上该前缀,proxy 返回响应时剥掉前缀,客户端感知不到前缀。

$ etcd grpc-proxy start --endpoints=localhost:2379 \
  --listen-addr=127.0.0.1:23790 \
  --namespace=my-prefix/
$ ETCDCTL_API=3 etcdctl --endpoints=localhost:23790 put my-key abc
# OK
$ ETCDCTL_API=3 etcdctl --endpoints=localhost:23790 get my-key
# my-key
# abc
$ ETCDCTL_API=3 etcdctl --endpoints=localhost:2379 get my-prefix/my-key
# my-prefix/my-key
# abc

💡 补充:namespace 是多租户共享同一 etcd 集群时成本最低的隔离手段,但它只做 key 前缀重写,不提供鉴权隔离——能直连后端集群的客户端仍可读到 my-prefix/my-key。真正的访问控制要靠 RBAC(第 10 章)。

TLS 终止

可由 gRPC proxy 终止来自安全 etcd 集群的 TLS,对外提供未加密的本地 endpoint。先启动一个 https 的单成员集群:

$ etcd --listen-client-urls https://localhost:2379 --advertise-client-urls https://localhost:2379 --cert-file=peer.crt --key-file=peer.key --trusted-ca-file=ca.crt --client-cert-auth

确认客户端端口是 https:

# fails
$ ETCDCTL_API=3 etcdctl --endpoints=http://localhost:2379 endpoint status
# works
$ ETCDCTL_API=3 etcdctl --endpoints=https://localhost:2379 --cert=client.crt --key=client.key --cacert=ca.crt endpoint status

用客户端证书连接 https://localhost:2379,在 localhost:12379 启动 proxy:

$ etcd grpc-proxy start --endpoints=https://localhost:2379 --listen-addr localhost:12379 --cert client.crt --key client.key --cacert=ca.crt --insecure-skip-tls-verify &

测试 TLS 终止(经 http 写入 proxy):

$ ETCDCTL_API=3 etcdctl --endpoints=http://localhost:12379 put abc def
# OK

Metrics 与健康检查

gRPC proxy 为 --endpoints 定义的 etcd 成员暴露 /health 与 Prometheus /metrics。也可以用 --metrics-addr 额外定义一个同时响应 /metrics 与 /health 的 URL:

$ etcd grpc-proxy start \
  --endpoints https://localhost:2379 \
  --metrics-addr https://0.0.0.0:4443 \
  --listen-addr 127.0.0.1:23790 \
  --key client.key \
  --key-file proxy-server.key \
  --cert client.crt \
  --cert-file proxy-server.crt \
  --cacert ca.pem \
  --trusted-ca-file proxy-ca.pem

已知问题:proxy 主接口同时服务 HTTP2 与 HTTP/1.1。若按上例配置了 TLS,用 cURL 之类的客户端访问主接口取 /metrics 或 /health 时,必须显式指定 HTTP/1.1;使用 --metrics-addr 的次要接口则无此要求:

 $ curl --cacert proxy-ca.pem --key proxy-client.key --cert proxy-client.crt https://127.0.0.1:23790/metrics --http1.1

gRPC proxy 全部 flag 汇总

--endpoints、--listen-addr、--advertise-client-url、--resolver-prefix、--resolver-ttl、--namespace、--metrics-addr、--cert、--cert-file、--key、--key-file、--cacert、--trusted-ca-file、--insecure-skip-tls-verify。

flag 作用
--endpoints 后端 etcd 集群 endpoint 列表
--listen-addr proxy 监听地址
--advertise-client-url 对外通告的 proxy 地址(用于注册与发现)
--resolver-prefix 把 proxy endpoint 注册到该前缀下,供 gRPC naming 发现
--resolver-ttl 注册记录的 TTL(秒)
--namespace keyspace 前缀隔离
--metrics-addr 独立的 /metrics 与 /health 监听地址
--cert / --key / --cacert 作为客户端连接后端 etcd 的证书
--cert-file / --key-file / --trusted-ca-file 作为服务端对外提供 TLS 的证书
--insecure-skip-tls-verify 跳过后端证书校验

13.4 选型建议

💡 补充(非官方原文,基于两页定位推导):

场景 建议
同机多应用访问同一 etcd,想少维护 endpoint 列表 etcd gateway(本机 127.0.0.1:23790 + 轮询)
已在 Kubernetes 内 两者都不需要,用 Service DNS 即可(kube-proxy 已等价 gateway)
watcher / lease 客户端极多,核心集群压力大 gRPC proxy(watch N→1、lease stream N→1)
需要给多租户做 keyspace 隔离 gRPC proxy + --namespace(注意不替代 RBAC)
需要把 https 集群暴露成明文本地端口 gRPC proxy(TLS 终止);gateway 不终止 TLS
对 watch 起始 revision 有强一致要求 慎用 gRPC proxy,或显式指定 watch revision;gateway 是透明转发不受影响

14. 升级与降级

引自:Upgrading etcd clusters and applications、Upgrade etcd from v3.5 to v3.6、Upgrade etcd from v3.6 to v3.7、Downgrading etcd clusters and applications、Downgrade etcd from v3.7 to v3.6、Downgrade etcd from v3.6 to v3.5

14.1 升级总纲与硬性规则

etcd 只支持以下两种升级场景:

场景 说明 示例
Patch upgrade(补丁升级) 同一 minor 版本内的小版本升级 3.7.0 → 3.7.1
Minor upgrade(小版本升级) 一次只升一个 minor 版本;跳 minor 版本的升级不被支持,且很可能失败 3.6 → 3.7

在升到下一个 minor 版本之前,必须先更新到当前 minor 版本的最新 patch 版本。

官方升级文档链接清单(v3.7 文档站):

页面 链接
Upgrade etcd from 3.0 to 3.1 ../upgrade_3_1/
Upgrade etcd from 3.1 to 3.2 ../upgrade_3_2/
Upgrade etcd from 3.2 to 3.3 ../upgrade_3_3/
Upgrade etcd from 3.3 to 3.4 ../upgrade_3_4/
Upgrade etcd from 3.4 to 3.5 ../upgrade_3_5/
Upgrade etcd from 3.5 to v3.6 ../upgrade_3_6/
Upgrade etcd from v3.6 to v3.7 ../upgrade_3_7/
Upgrade a v2.3 cluster to v3.0 ../upgrade_3_0/

范围声明:上表中 3.0–3.5 的五页属于历史版本,本册按用户指定「只做 v3.7 主线」不展开,见附录 B。

14.2 通用的服务端升级检查清单(适用于 3.5→3.6 与 3.6→3.7)

检查项 要求
Upgrade requirements 集群必须是健康的,升级前用 etcdctl endpoint health 检查;且需满足目标版本要求的最低版本(3.6 要求 ≥ v3.5;3.7 要求 ≥ v3.6.11)。etcd 只支持一次升一个 minor 版本
Preparation 先在 staging 环境测试依赖 etcd 的服务,再推向生产;开始前下载 snapshot 备份。注意 snapshot 命令只备份 v3 数据
Mixed versions 升级过程中支持 member 混合版本,并以最低公共版本的协议运行;只有全部 member 都升到新版本时集群才算完成升级;member 之间会互相协商确定整体集群版本,该版本决定对外报告的版本与可用特性
Rollback(回滚原则) 见下方要点

Rollback 要点(升级场景):

  1. 升级前先创建并下载 snapshot 备份,可用于把集群恢复到升级前状态。
  2. 若升级中出问题,应先定位并解决根因。
  3. 若集群仍处于混合版本状态(至少还有一个 member 是旧版本),可以把二进制/镜像换回旧版本,或直接用 snapshot 恢复集群。此时集群仍按旧版本集群运行,可以回滚而无需走正式的降级流程。
  4. 一旦所有 member 都升到新版本,集群视为完全升级,用二进制回滚已不再可能。此时唯一恢复手段是用升级前拍的 snapshot 恢复;若仍想回到原版本,应走官方降级指南以保证一致性、避免数据损坏。

14.3 v3.5 → v3.6 完整步骤

总体说明

一般情况下,从 etcd v3.5 升到 v3.6 是一次零停机滚动升级(zero-downtime, rolling upgrade):

  • 逐个停止 etcd v3.5 进程,替换为 etcd v3.6 进程;
  • 全部替换为 v3.6 进程后,v3.6 的新特性才对集群可用。

升级前检查清单

1)Update 3.5(重要 / warning)

升级到 3.6 之前,必须确保所有 3.5 member 都已升级到 3.5.32 或更高版本。patch 版本 3.5.24 到 3.5.26 修复了若干潜在的升级阻塞问题;3.5.32 新增了 --v2-deprecation=write-only-skip-check,并把 etcdutl check v2store 扩展为可同时检查 WAL 记录和 v2 snapshot。

2)V2 Store(info)

  • 若未配置 --enable-v2 或该 flag 为 false,无需任何额外操作。
  • 若已配置 --enable-v2,运行命令 etcdutl check v2store,确认 v2store 中是否含有非 membership 的自定义数据。没有自定义数据则可以安全移除该 flag;否则参考 v2 migration guide。

升级流程(3 member v3.5 集群示例)

Step 1:check upgrade requirements

确认集群健康且运行 v3.5.x:

etcdctl --endpoints=localhost:2379,localhost:22379,localhost:32379 endpoint health
<<COMMENT
localhost:2379 is healthy: successfully committed proposal: took = 2.555774ms
localhost:32379 is healthy: successfully committed proposal: took = 2.631133ms
localhost:22379 is healthy: successfully committed proposal: took = 3.020958ms
COMMENT

curl http://localhost:2379/version
<<COMMENT
{"etcdserver":"3.5.18","etcdcluster":"3.5.0"}
COMMENT

(对 localhost:22379、localhost:32379 各执行一次 curl http://localhost:PORT/version,预期均返回 {"etcdserver":"3.5.18","etcdcluster":"3.5.0"}。)

Step 2:download snapshot backup from leader

etcd leader 保证拥有最新的应用数据,因此从 leader 取快照。先用 metrics 判断谁是 leader(etcd_server_is_leader 为 1 者):

curl -sL http://localhost:2379/metrics | grep etcd_server_is_leader
<<COMMENT
# HELP etcd_server_is_leader Whether or not this member is a leader. 1 if is, 0 otherwise.
# TYPE etcd_server_is_leader gauge
etcd_server_is_leader 1
COMMENT

etcdctl --endpoints=localhost:2379 snapshot save backup.db
<<COMMENT
{"level":"info","ts":"2025-03-01T04:34:10.336768+0530","caller":"snapshot/v3_snapshot.go:65","msg":"created temporary db file","path":"backup.db.part"}
...
{"level":"info","ts":"2025-03-01T04:34:10.348933+0530","caller":"snapshot/v3_snapshot.go:97","msg":"saved","path":"backup.db"}
Snapshot saved at backup.db
COMMENT

Step 3:stop one existing etcd server

每停掉一个 etcd 进程,其他 member 会记录预期内的错误日志,这是正常的(member 连接被临时断开)。典型日志包含:

{"level":"info","ts":"2025-03-01T04:35:01.443045+0530","caller":"osutil/interrupt_unix.go:64","msg":"received signal; shutting down","signal":"interrupt"}
{"level":"info","ts":"2025-03-01T04:35:01.443417+0530","caller":"etcdserver/server.go:1503","msg":"leadership transfer starting","local-member-id":"bf9071f4639c75cc","current-leader-member-id":"bf9071f4639c75cc","transferee-member-id":"91bc3c398fb3c146"}
{"level":"info","ts":"2025-03-01T04:35:01.544062+0530","caller":"etcdserver/server.go:1520","msg":"leadership transfer finished","local-member-id":"bf9071f4639c75cc","old-leader-member-id":"bf9071f4639c75cc","new-leader-member-id":"91bc3c398fb3c146","took":"100.640374ms"}
{"level":"warn","ts":"2025-03-01T04:35:01.545156+0530","caller":"rafthttp/peer_status.go:66","msg":"peer became inactive (message send to peer failed)","peer-id":"91bc3c398fb3c146","error":"failed to read 91bc3c398fb3c146 on stream MsgApp v2 (context canceled)"}
{"level":"warn","ts":"2025-03-01T04:35:01.545669+0530","caller":"rafthttp/http.go:413","msg":"failed to find remote peer in cluster","local-member-id":"bf9071f4639c75cc","remote-peer-id-stream-handler":"bf9071f4639c75cc","remote-peer-id-from":"91bc3c398fb3c146","cluster-id":"59a05384c9b79ee"}
{"level":"info","ts":"2025-03-01T04:35:02.550561+0530","caller":"embed/etcd.go:410","msg":"closed etcd server","name":"node1","data-dir":"/tmp/etcd-node1","advertise-peer-urls":["http://127.0.0.1:2380"],"advertise-client-urls":["http://127.0.0.1:2379"]}

Step 4:restart the etcd server with same configuration

用相同配置、换成新的 etcd 二进制重启:

-etcd-old --name ${name} \
+etcd-new --name ${name} \
  --data-dir /path/to/${name}.etcd \
  --listen-client-urls http://localhost:2379 \
  --advertise-client-urls http://localhost:2379 \
  --listen-peer-urls http://localhost:2380 \
  --initial-advertise-peer-urls http://localhost:2380 \
  --initial-cluster s1=http://localhost:2380,s2=http://localhost:22380,s3=http://localhost:32380 \
  --initial-cluster-token tkn \
  --initial-cluster-state new

新的 v3.6 etcd 会把自己的信息发布到集群。此时集群仍以 v3.5 协议运行(最低公共版本),对应日志:

{"level":"info","ts":"2025-03-01T04:40:36.828+0530","caller":"api/capability.go:76","msg":"enabled capabilities for version","cluster-version":"3.5"}
{"level":"info","ts":"2025-03-01T04:40:36.889+0530","caller":"membership/cluster.go:539","msg":"updated cluster version","cluster-id":"59a05384c9b79ee","local-member-id":"bf9071f4639c75cc","from":"3.0","to":"3.5"}
{"level":"info","ts":"2025-03-01T04:40:36.894+0530","caller":"etcdserver/server.go:1686","msg":"published local member to cluster through raft","local-member-id":"bf9071f4639c75cc","local-member-attributes":"{Name:node1 ClientURLs:[http://127.0.0.1:2379]}","cluster-id":"59a05384c9b79ee","publish-timeout":"7s"}

验证每个 member、进而整个集群在新 v3.6 二进制下健康:

etcdctl endpoint health --endpoints=localhost:2379,localhost:22379,localhost:32379
<<COMMENT
localhost:2379 is healthy: successfully committed proposal: took = 1.704998ms
localhost:22379 is healthy: successfully committed proposal: took = 2.331754ms
localhost:32379 is healthy: successfully committed proposal: took = 2.490705ms
COMMENT

未升级的 member 会持续打印如下警告,直到整个集群升级完成(这是预期的,全部升到 v3.6 后即消失):

{"level":"warn","ts":"2025-03-01T04:40:37.545960+0530","caller":"etcdserver/cluster_util.go:189","msg":"leader found higher-versioned member","local-member-version":"3.5.18","remote-member-id":"bf9071f4639c75cc","remote-member-version":"3.6.0-alpha.0"}

Step 5:repeat step 3 and step 4 for rest of the members

全部 member 升级完成后,集群报告升级到 v3.6 成功:

Member 1:

{"level":"info","ts":"2025-03-01T04:58:32.375+0530","caller":"etcdserver/server.go:2149","msg":"updating cluster version using v3 API","from":"3.5","to":"3.6"}
{"level":"info","ts":"2025-03-01T04:58:32.377+0530","caller":"etcdserver/server.go:2164","msg":"cluster version is updated","cluster-version":"3.6"}

Member 2:

{"level":"info","ts":"2025-03-01T04:58:32.377+0530","caller":"membership/cluster.go:539","msg":"updated cluster version","cluster-id":"59a05384c9b79ee","local-member-id":"91bc3c398fb3c146","from":"3.5","to":"3.6"}

Member 3:

{"level":"info","ts":"2025-03-01T04:58:32.377+0530","caller":"membership/cluster.go:539","msg":"updated cluster version","cluster-id":"59a05384c9b79ee","local-member-id":"fd422379fda50e48","from":"3.5","to":"3.6"}

最终验证健康与版本(文档中此处命令写作 endpoint health --endpoints=...,疑漏 etcdctl 前缀,本册按原文保留):

endpoint health --endpoints=localhost:2379,localhost:22379,localhost:32379
<<COMMENT
localhost:2379 is healthy: successfully committed proposal: took = 492.834µs
localhost:22379 is healthy: successfully committed proposal: took = 1.015025ms
localhost:32379 is healthy: successfully committed proposal: took = 1.853077ms
COMMENT

curl http://localhost:2379/version
<<COMMENT
{"etcdserver":"3.6.0-alpha.0","etcdcluster":"3.6.0"}
COMMENT

(三个端口均返回 {"etcdserver":"3.6.0-alpha.0","etcdcluster":"3.6.0}" 形态,即 etcdcluster 变为 3.6.0。)

14.4 v3.6 → v3.7 完整步骤

总体说明

一般情况下,从 etcd v3.6 升到 v3.7 是一次零停机滚动升级:

  • 逐个停止 etcd v3.6 进程,替换为 etcd v3.7 进程;
  • 全部运行 v3.7 进程后,v3.7 的新特性才对集群可用。

升级前检查清单

1)Update 3.6(Important / warning)

升级到 3.7 之前,必须确保所有 3.6 member 都已升级到 3.6.11 或更高版本。更早的 3.6 patch 版本可能与滚动升级到 3.7 不兼容。

2)V2 store

v2 store 在 v3.7 中已被完全移除:v2 HTTP API(--enable-v2)、v2-on-v3 模拟(--experimental-enable-v2v3)、v2 discovery service、client/v2 包以及 v2 snapshot 文件的加载功能全部消失。详见 CHANGELOG-3.7 的 breaking-change 引用。

  • 若从 3.6 集群升级,这些 flag 本来就不存在,无需操作。
  • 若从更旧版本且带有自定义 v2 数据升级,升级前需按 v2 migration guide 处理。

3)Go refactoring(内部重构,影响自定义集成)

v3.7 包含大量内部重构,正常升级者不受影响,但自定义集成需注意:

  • 从 gogo/protobuf 迁移到标准 google.golang.org/protobuf(issue #14533);
  • 把已废弃的 go-grpc-middleware v1 logging / tags 库迁移到 v2 interceptors(PR #20420);
  • OpenTelemetry gRPC interceptors 升级到 otelgrpc v0.61.0,用 NewServerHandler 取代已废弃的 UnaryServerInterceptor 和 StreamServerInterceptor(PR #20017)。

若把 etcd 作为库嵌入、基于 clientv3 API 构建、或依赖内部包,升级前应先查阅 CHANGELOG-3.7。

Flags removed(破坏性变更,重点)

v3.7 移除了所有已废弃的 --experimental-* flag(PR #19959)。 在 v3.6 中,这些 flag 各自已有对应的非 experimental 同名 flag 或 --feature-gates 条目。若仍保留任何 --experimental-* 设置,必须在滚动到 v3.7 之前替换为 v3.6 等价写法,否则 v3.7 进程将无法启动。

-etcd --experimental-bootstrap-defrag-threshold-megabytes
-etcd --experimental-compact-hash-check-enabled
-etcd --experimental-compact-hash-check-time
-etcd --experimental-compaction-batch-limit
-etcd --experimental-compaction-sleep-interval
-etcd --experimental-corrupt-check-time
-etcd --experimental-distributed-tracing-address
-etcd --experimental-distributed-tracing-instance-id
-etcd --experimental-distributed-tracing-sampling-rate
-etcd --experimental-distributed-tracing-service-name
-etcd --experimental-downgrade-check-time
-etcd --experimental-enable-distributed-tracing
-etcd --experimental-enable-lease-checkpoint
-etcd --experimental-enable-lease-checkpoint-persist
-etcd --experimental-initial-corrupt-check
-etcd --experimental-memory-mlock
-etcd --experimental-peer-skip-client-san-verification
-etcd --experimental-snapshot-catchup-entries
-etcd --experimental-stop-grpc-service-on-defrag
-etcd --experimental-txn-mode-write-with-shared-buffer
-etcd --experimental-warning-apply-duration
-etcd --experimental-warning-unary-request-duration
-etcd --experimental-watch-progress-notify-interval
  • Flags added:None.(v3.7 未新增 flag)
  • Flags with new defaults:None.(v3.7 无默认值变更)

升级流程(3 member v3.6 集群;输出取自 etcd v3.6.12 与 etcd v3.7.0-rc.0 的真实运行,单机三个 loopback 端口)

Step 1:check upgrade requirements

集群是否健康且运行 v3.6.11 或更高:

etcdctl --endpoints=localhost:2379,localhost:22379,localhost:32379 endpoint health
<<COMMENT
localhost:2379 is healthy: successfully committed proposal: took = 7.681459ms
localhost:22379 is healthy: successfully committed proposal: took = 7.691750ms
localhost:32379 is healthy: successfully committed proposal: took = 7.698000ms
COMMENT

curl http://localhost:2379/version
<<COMMENT
{"etcdserver":"3.6.12","etcdcluster":"3.6.0","storage":"3.6.0"}
COMMENT

Step 2:download snapshot backup from leader

etcd leader 保证拥有最新应用数据,因此从 leader 取快照(循环探测三个端口):

for p in 2379 22379 32379; do
  echo -n "localhost:$p leader="
  curl -sL http://localhost:$p/metrics | grep "^etcd_server_is_leader " | awk '{print $2}'
done
<<COMMENT
localhost:2379 leader=1
localhost:22379 leader=0
localhost:32379 leader=0
COMMENT

etcdctl --endpoints=localhost:2379 snapshot save backup.db
<<COMMENT
{"level":"info","ts":"2026-06-02T07:01:41.863225+0300","caller":"snapshot/v3_snapshot.go:83","msg":"created temporary db file","path":"backup.db.part"}
{"level":"info","ts":"2026-06-02T07:01:41.877203+0300","caller":"snapshot/v3_snapshot.go:111","msg":"fetched snapshot","endpoint":"localhost:2379","size":"98 kB","took":"13.822583ms","etcd-version":"3.6.0"}
{"level":"info","ts":"2026-06-02T07:01:41.877303+0300","caller":"snapshot/v3_snapshot.go:121","msg":"saved","path":"backup.db"}
Snapshot saved at backup.db
Server version 3.6.0
COMMENT

Step 3:stop one existing etcd server

停止每个 etcd 进程时,其他 member 会记录预期内的错误日志(member 连接被临时断开)。leader 会在退出前转移 leadership:

{"level":"info","ts":"2026-06-02T07:01:54.949299+0300","caller":"etcdserver/server.go:1274","msg":"leadership transfer finished","local-member-id":"7339c4e5e833c029","old-leader-member-id":"7339c4e5e833c029","new-leader-member-id":"b548c2511513015","took":"101.052625ms"}
{"level":"info","ts":"2026-06-02T07:01:54.949369+0300","caller":"etcdserver/server.go:2349","msg":"server has stopped; stopping cluster version's monitor"}
{"level":"info","ts":"2026-06-02T07:01:55.503219+0300","caller":"embed/etcd.go:626","msg":"stopped serving peer traffic","address":"127.0.0.1:2380"}

Step 4:restart the etcd server with same configuration

相同配置 + 新的 etcd 二进制:

-etcd-old --name ${name} \
+etcd-new --name ${name} \
  --data-dir /path/to/${name}.etcd \
  --listen-client-urls http://localhost:2379 \
  --advertise-client-urls http://localhost:2379 \
  --listen-peer-urls http://localhost:2380 \
  --initial-advertise-peer-urls http://localhost:2380 \
  --initial-cluster s1=http://localhost:2380,s2=http://localhost:22380,s3=http://localhost:32380 \
  --initial-cluster-token tkn \
  --initial-cluster-state new

新的 v3.7 etcd 会向集群发布自身信息;此时集群仍以 v3.6 协议运行(最低公共版本):

{"level":"info","ts":"2026-06-02T07:01:58.920780+0300","caller":"membership/cluster.go:296","msg":"set cluster version from store","cluster-version":"3.6"}
{"level":"info","ts":"2026-06-02T07:01:58.979186+0300","caller":"etcdserver/server.go:1828","msg":"published local member to cluster through raft","local-member-id":"7339c4e5e833c029","local-member-attributes":"{Name:s1 ClientURLs:[http://localhost:2379]}","cluster-id":"7dee9ba76d59ed53","publish-timeout":"7s"}

验证每个 member 及整个集群在新 v3.7 二进制下健康(注意此处用 endpoint status -w table,可看到 STORAGE VERSION 仍为 3.6.0):

etcdctl --endpoints=localhost:2379,localhost:22379,localhost:32379 endpoint status -w table
<<COMMENT
+-----------------+------------------+------------+-----------------+---------+--------+-----------+
|    ENDPOINT     |        ID        |  VERSION   | STORAGE VERSION | DB SIZE | LEADER | RAFT TERM |
+-----------------+------------------+------------+-----------------+---------+--------+-----------+
|  localhost:2379 | 7339c4e5e833c029 | 3.7.0-rc.0 |           3.6.0 |   98 kB |  false |         3 |
| localhost:22379 | 729934363faa4a24 |     3.6.12 |           3.6.0 |   98 kB |  false |         3 |
| localhost:32379 |  b548c2511513015 |     3.6.12 |           3.6.0 |   98 kB |   true |         3 |
+-----------------+------------------+------------+-----------------+---------+--------+-----------+
COMMENT

未升级 member 与已升级 member 都会记录混合版本状态相关日志,全部升到 v3.7 后消失(预期行为)。

Step 5:repeat step 3 and step 4 for rest of the members

全部 member 升级后,集群报告升级到 v3.7 成功:

{"level":"info","ts":"2026-06-02T07:02:36.054783+0300","caller":"etcdserver/server.go:2311","msg":"updating cluster version using v3 API","from":"3.6","to":"3.7"}
{"level":"info","ts":"2026-06-02T07:02:36.059345+0300","caller":"membership/cluster.go:593","msg":"updated cluster version","cluster-id":"7dee9ba76d59ed53","local-member-id":"7339c4e5e833c029","from":"3.6","to":"3.7"}
{"level":"info","ts":"2026-06-02T07:02:36.059409+0300","caller":"etcdserver/server.go:2326","msg":"cluster version is updated","cluster-version":"3.7"}
etcdctl --endpoints=localhost:2379,localhost:22379,localhost:32379 endpoint health
<<COMMENT
localhost:2379 is healthy: successfully committed proposal: took = 550.833µs
localhost:32379 is healthy: successfully committed proposal: took = 733.458µs
localhost:22379 is healthy: successfully committed proposal: took = 714.416µs
COMMENT

curl http://localhost:2379/version
<<COMMENT
{"etcdserver":"3.7.0-rc.0","etcdcluster":"3.7.0","storage":"3.7.0"}
COMMENT

(三个端口均返回 {"etcdserver":"3.7.0-rc.0","etcdcluster":"3.7.0","storage":"3.7.0"},storage 版本也随之变为 3.7.0。)

14.5 降级总纲

本页是降级文档的索引页,正文只有一句说明与链接列表,不含具体命令。

说明:本节包含针对 etcd 集群与应用降级的专门文档。

降级路径 链接
Downgrade etcd from 3.5 to 3.4 ../downgrade_3_5/
Downgrade etcd from 3.6 to 3.5 ../downgrade_3_6/
Downgrade etcd from 3.7 to 3.6 ../downgrade_3_7/

要点:

  • 可降级路径按文档仅列出这三条,均为相邻 minor 版本之间的降级。
  • 具体前提条件(一次只降一个 minor、需先 etcdctl downgrade validate、再 etcdctl downgrade enable、逐台替换二进制、混合版本状态可用 etcdctl downgrade cancel 撤销)写在 downgrade_3_7.md / downgrade_3_6.md 中。

14.6 v3.7 → v3.6 降级步骤

总体说明

一般情况下,从 etcd v3.7 降到 v3.6 是一次零停机滚动降级:

  • 逐个停止 etcd v3.7 进程,替换为 etcd v3.6 进程;
  • 开启降级(enabling the downgrade)之后,v3.7 的新特性就不再对集群可用。

降级检查清单:v3.7 与 v3.6 的显著差异

Difference in flags

v3.7 没有引入任何新 flag,因此 v3.6 进程接受 v3.7 配置中的每一个 flag,降级时无需修改配置。

Note:以下 diff 基于 v3.7.0-rc.0 与 v3.6.13。实际差异取决于你的 patch 版本,请先用 diff <(etcd-3.7/bin/etcd -h | grep \-\-) <(etcd-3.6/bin/etcd -h | grep \-\-) 检查。

在 v3.7 中被移除的已废弃 --experimental-* flag 在 v3.6 中仍然存在,但降级后不要再加回来;应使用它们的非 experimental 等价 flag 或 --feature-gates 条目,这些写法在两个版本上都可用。

Difference in Prometheus metrics(v3.6 中不存在的指标)

# metrics not available in v3.6
-etcd_server_request_duration_seconds
-etcd_debugging_server_watch_send_loop_control_stream_duration_seconds
-etcd_debugging_server_watch_send_loop_progress_duration_seconds
-etcd_debugging_server_watch_send_loop_watch_stream_duration_seconds
-etcd_debugging_server_watch_send_loop_watch_stream_duration_per_event_seconds

服务端降级检查清单

检查项 要求
Downgrade requirements 运行中的集群必须健康,降级前用 etcdctl endpoint health 检查
Preparation 先在 staging 环境测试依赖 etcd 的服务;开始前下载 snapshot 备份;并下载最新的 etcd v3.6 发布版
Mixed versions 降级期间支持混合版本,按最低公共版本协议运行;一旦通过 etcdctl downgrade enable 3.6 开启降级,集群即视为已降级。内部把整体集群版本设为降级目标版本,该版本决定报告版本与可用特性
Rollback 见下方要点

Rollback 要点(降级场景):

  1. 降级前创建并下载 snapshot 备份,可恢复到降级前状态;出问题先定位并解决根因。
  2. 若已执行 etcdctl downgrade enable、集群仍处混合版本状态(至少一个 member 还是 v3.7),可以运行 etcdctl downgrade cancel 取消进行中的降级,并用原来的 v3.7 二进制重启所有已降级的 member。
  3. 一旦所有 member 都降到 v3.6,集群视为完全降级;此时若要回到原版本,应按官方 upgrade guide 走升级流程,以保证一致性、避免数据损坏。

降级流程(3 member v3.7 集群;输出取自 etcd v3.7.0-rc.0 与 etcd v3.6.13 的真实运行)

Step 1:check downgrade requirements

etcdctl --endpoints=localhost:2379,localhost:22379,localhost:32379 endpoint health
<<COMMENT
localhost:2379 is healthy: successfully committed proposal: took = 1.052416ms
localhost:32379 is healthy: successfully committed proposal: took = 1.11625ms
localhost:22379 is healthy: successfully committed proposal: took = 1.114291ms
COMMENT

curl http://localhost:2379/version
<<COMMENT
{"etcdserver":"3.7.0-rc.0","etcdcluster":"3.7.0","storage":"3.7.0"}
COMMENT

并用 endpoint status 确认 DOWNGRADE 相关列为空/关闭:

etcdctl --endpoints=localhost:2379,localhost:22379,localhost:32379 endpoint status -w=table
<<COMMENT
|  localhost:2379 | 7339c4e5e833c029 | 3.7.0-rc.0 |           3.7.0 |   98 kB |  98 kB |                    0% | 2.1 GB |      true |      false |         5 |         20 |                 20 |        |                          |             false |
| localhost:22379 | 729934363faa4a24 | 3.7.0-rc.0 |           3.7.0 |   98 kB |  98 kB |                    0% | 2.1 GB |     false |      false |         5 |         20 |                 20 |        |                          |             false |
| localhost:32379 |  b548c2511513015 | 3.7.0-rc.0 |           3.7.0 |   98 kB |  98 kB |                    0% | 2.1 GB |     false |      false |         5 |         20 |                 20 |        |                          |             false |
COMMENT

(DOWNGRADE TARGET VERSION 为空、DOWNGRADE ENABLED 为 false;STORAGE VERSION 为 3.7.0。)

Step 2:download snapshot backup from leader

etcdctl --endpoints=localhost:2379 snapshot save backup.db
<<COMMENT
{"level":"info","ts":"2026-07-02T06:48:11.091982+0300","caller":"snapshot/v3_snapshot.go:83","msg":"created temporary db file","path":"backup.db.part"}
{"level":"info","ts":"2026-07-02T06:48:11.103116+0300","caller":"snapshot/v3_snapshot.go:111","msg":"fetched snapshot","endpoint":"localhost:2379","size":"98 kB","took":"10.9815ms","etcd-version":"3.7.0"}
{"level":"info","ts":"2026-07-02T06:48:11.103296+0300","caller":"snapshot/v3_snapshot.go:121","msg":"saved","path":"backup.db"}
Snapshot saved at backup.db
Server version 3.7.0
COMMENT

Step 3:validate downgrade target version

开启降级前先校验降级目标版本:

  • 一次只支持降一个 minor 版本,例如从 v3.7 直接降到 v3.5 是不允许的;
  • 校验成功之前,请不要进入下一步。
etcdctl downgrade validate 3.6
<<COMMENT
Downgrade validate success, cluster version 3.7
COMMENT

Step 4:enable downgrade

etcdctl downgrade enable 3.6
<<COMMENT
Downgrade enable success, cluster version 3.7
COMMENT

开启降级后,集群开始以 v3.6 协议(降级目标版本)运行;此外 etcd 会自动把 schema 迁移到降级目标版本,通常很快。进入下一步前,用 endpoint status 确认所有 server 的 storage version 已迁移到 v3.6:

etcdctl --endpoints=localhost:2379,localhost:22379,localhost:32379 endpoint status -w=table
<<COMMENT
|  localhost:2379 | 7339c4e5e833c029 | 3.7.0-rc.0 |           3.6.0 |   98 kB | ... |      true |      false |         5 |         22 |                 22 |        |                    3.6.0 |              true |
| localhost:22379 | 729934363faa4a24 | 3.7.0-rc.0 |           3.6.0 |   98 kB | ... |     false |      false |         5 |         22 |                 22 |        |                    3.6.0 |              true |
| localhost:32379 |  b548c2511513015 | 3.7.0-rc.0 |           3.6.0 |   98 kB | ... |     false |      false |         5 |         22 |                 22 |        |                    3.6.0 |              true |
COMMENT

Note:一旦开启降级,即使所有 server 仍运行 v3.7 二进制,集群也会保持以 v3.6 协议运行,除非用 etcdctl downgrade cancel 取消降级。

Step 5:stop one existing etcd server

停止 server 前先检查它是否是 leader,建议最后再降级 leader。若要停止的 server 是 leader,可以在停止前用 move-leader 把 leadership 移走,避免一些停机时间:

etcdctl --endpoints=localhost:2379,localhost:22379,localhost:32379 move-leader 729934363faa4a24
<<COMMENT
Leadership transferred from 7339c4e5e833c029 to 729934363faa4a24
COMMENT

停止进程时其他 member 会记录预期内的错误日志(连接被临时断开),例如:

{"level":"warn","ts":"2026-07-02T06:48:14.518460+0300","caller":"rafthttp/stream.go:227","msg":"lost TCP streaming connection with remote peer","stream-writer-type":"stream Message","local-member-id":"7339c4e5e833c029","remote-peer-id":"729934363faa4a24"}
{"level":"warn","ts":"2026-07-02T06:48:15.913169+0300","caller":"etcdserver/cluster_util.go:261","msg":"failed to reach the peer URL","address":"http://localhost:22380/version","remote-member-id":"729934363faa4a24","error":"Get \"http://localhost:22380/version\": dial tcp [::1]:22380: connect: connection refused"}
{"level":"warn","ts":"2026-07-02T06:48:15.913364+0300","caller":"etcdserver/cluster_util.go:162","msg":"failed to get version","remote-member-id":"729934363faa4a24","error":"Get \"http://localhost:22380/version\": dial tcp [::1]:22380: connect: connection refused"}
{"level":"warn","ts":"2026-07-02T06:48:16.856521+0300","caller":"version/monitor.go:212","msg":"remotes server has mismatching etcd version","remote-member-id":"b548c2511513015","current-server-version":"3.7.0","target-version":"3.6.0"}

Step 6:restart the etcd server with same configuration

用相同配置、换成 v3.6 的 etcd 二进制重启:

-etcd-3.7/bin/etcd --name s2 \
+etcd-3.6/bin/etcd --name s2 \
  --data-dir /tmp/etcd/s2 \
  --listen-client-urls http://localhost:22379 \
  --advertise-client-urls http://localhost:22379 \
  --listen-peer-urls http://localhost:22380 \
  --initial-advertise-peer-urls http://localhost:22380 \
  --initial-cluster s1=http://localhost:2380,s2=http://localhost:22380,s3=http://localhost:32380 \
  --initial-cluster-token tkn \
  --initial-cluster-state existing

验证每个 member、进而整个集群在 v3.6 二进制下健康(注意 localhost:22379 的 VERSION 已变为 3.6.13):

etcdctl --endpoints=localhost:2379,localhost:22379,localhost:32379 endpoint status -w=table
<<COMMENT
|  localhost:2379 | 7339c4e5e833c029 | 3.7.0-rc.0 |           3.6.0 | ... |      true | ... |                    3.6.0 |              true |
| localhost:22379 | 729934363faa4a24 |     3.6.13 |           3.6.0 | ... |     false | ... |                    3.6.0 |              true |
| localhost:32379 |  b548c2511513015 | 3.7.0-rc.0 |           3.6.0 | ... |     false | ... |                    3.6.0 |              true |
COMMENT

etcdctl endpoint health --endpoints=localhost:2379,localhost:22379,localhost:32379
<<COMMENT
localhost:2379 is healthy: successfully committed proposal: took = 939.625µs
localhost:32379 is healthy: successfully committed proposal: took = 981.459µs
localhost:22379 is healthy: successfully committed proposal: took = 1.11075ms
COMMENT

Note:与 v3.5 不同,v3.6 的 status endpoint 会报告降级信息,因此已降级的 member 会继续显示 DOWNGRADE ENABLED 为 true,并保留其 storage version,直到降级完成。

Step 7:repeat step 5 and step 6 for rest of the members

全部 member 降级后,降级自动完成,DOWNGRADE ENABLED 复位为 false。检查集群健康与状态,确认所有 member 的 minor 版本与 storage version 均为 v3.6:

etcdctl --endpoints=localhost:2379,localhost:22379,localhost:32379 endpoint status -w=table
<<COMMENT
|  localhost:2379 | 7339c4e5e833c029 |  3.6.13 |           3.6.0 | ... |     false |      false |         6 |         30 |                 30 |        |                          |             false |
| localhost:22379 | 729934363faa4a24 |  3.6.13 |           3.6.0 | ... |      true |      false |         6 |         30 |                 30 |        |                          |             false |
| localhost:32379 |  b548c2511513015 |  3.6.13 |           3.6.0 | ... |     false |      false |         6 |         30 |                 30 |        |                          |             false |
COMMENT

etcdctl endpoint health --endpoints=localhost:2379,localhost:22379,localhost:32379
<<COMMENT
localhost:22379 is healthy: successfully committed proposal: took = 5.176958ms
localhost:32379 is healthy: successfully committed proposal: took = 5.177875ms
localhost:2379 is healthy: successfully committed proposal: took = 5.191625ms
COMMENT

curl http://localhost:2379/version
<<COMMENT
{"etcdserver":"3.6.13","etcdcluster":"3.6.0","storage":"3.6.0"}
COMMENT

在 leader 的日志中应能看到类似消息:

{"level":"info","ts":"2026-07-02T06:48:32.312205+0300","caller":"version/monitor.go:143","msg":"the cluster has been downgraded","cluster-version":"3.6.0"}

14.7 v3.6 → v3.5 降级步骤

总体说明

一般情况下,从 etcd v3.6 降到 v3.5 是一次零停机滚动降级:

  • 逐个停止 etcd v3.6 进程,替换为 etcd v3.5 进程;
  • 开启降级之后,v3.6 的新特性不再对集群可用。

降级检查清单:v3.6 → v3.5 的破坏性变更

Difference in flags

若 v3.6 配置中使用了下列 flag,降级到 v3.5 时必须移除、改名、或改回默认值。

Note:diff 基于 v3.6.0 与 v3.5.18;实际差异取决于 patch 版本,请先用 diff <(etcd-3.6/bin/etcd -h | grep \-\-) <(etcd-3.5/bin/etcd -h | grep \-\-) 检查。

(1)v3.5 中不存在的 flag(降级前必须删除):

# flags not available in v3.5
-etcd --discovery-token ''
-etcd --discovery-endpoints ''
-etcd --discovery-dial-timeout '2s'
-etcd --discovery-request-timeout '5s'
-etcd --discovery-keepalive-time '2s'
-etcd --discovery-keepalive-timeout '6s'
-etcd --discovery-insecure-transport 'true'
-etcd --discovery-insecure-skip-tls-verify 'false'
-etcd --discovery-cert ''
-etcd --discovery-key ''
-etcd --discovery-cacert ''
-etcd --discovery-user ''
-etcd --discovery-password ''
-etcd --feature-gates
-etcd --log-format

(2)同名但改名的 flag(改回 --experimental- 前缀):

-etcd --bootstrap-defrag-threshold-megabytes          +etcd --experimental-bootstrap-defrag-threshold-megabytes
-etcd --compaction-batch-limit                        +etcd --experimental-compaction-batch-limit
-etcd --compact-hash-check-time                       +etcd --experimental-compact-hash-check-time
-etcd --compaction-sleep-interval                     +etcd --experimental-compaction-sleep-interval
-etcd --corrupt-check-time                            +etcd --experimental-corrupt-check-time
-etcd --enable-distributed-tracing                    +etcd --experimental-enable-distributed-tracing
-etcd --distributed-tracing-address                   +etcd --experimental-distributed-tracing-address
-etcd --distributed-tracing-instance-id               +etcd --experimental-distributed-tracing-instance-id
-etcd --distributed-tracing-sampling-rate             +etcd --experimental-distributed-tracing-sampling-rate
-etcd --distributed-tracing-service-name              +etcd --experimental-distributed-tracing-service-name
-etcd --downgrade-check-time                          +etcd --experimental-downgrade-check-time
-etcd --max-learners                                  +etcd --experimental-max-learners
-etcd --memory-mlock                                  +etcd --experimental-memory-mlock
-etcd --peer-skip-client-san-verification             +etcd --experimental-peer-skip-client-san-verification
-etcd --snapshot-catchup-entries                      +etcd --experimental-snapshot-catchup-entries
-etcd --warning-apply-duration                        +etcd --experimental-warning-apply-duration
-etcd --warning-unary-request-duration                +etcd --experimental-warning-unary-request-duration
-etcd --watch-progress-notify-interval                +etcd --experimental-watch-progress-notify-interval

(3)v3.6 feature gates 的等价 flag(改回 v3.5 写法):

-etcd --feature-gates=CompactHashCheck=true           +etcd --experimental-compact-hash-check-enabled=true
-etcd --feature-gates=InitialCorruptCheck=true        +etcd --experimental-enable-initial-corrupt-check=true
-etcd --feature-gates=LeaseCheckpoint=true            +etcd --experimental-enable-lease-checkpoint=true
-etcd --feature-gates=LeaseCheckpointPersist=true     +etcd --experimental-enable-lease-checkpoint-persist=true
-etcd --feature-gates=StopGRPCServiceOnDefrag=true    +etcd --experimental-stop-grpc-service-on-defrag=true
-etcd --feature-gates=TxnModeWriteWithSharedBuffer=false  +etcd --experimental-txn-mode-write-with-shared-buffer=false

(4)同一 flag 默认值不同(需改回 v3.5 默认值):

-etcd --snapshot-count=10000        +etcd --snapshot-count=100000
-etcd --v2-deprecation='write-only' +etcd --v2-deprecation='not-yet'
-etcd --discovery-fallback='exit'   +etcd --discovery-fallback='proxy'

Difference in Prometheus metrics

# metrics not available in v3.5
-etcd_network_known_peers
-etcd_server_feature_enabled

服务端降级检查清单

检查项 要求
Downgrade requirements 运行中的集群必须健康,降级前用 etcdctl endpoint health 检查
Preparation 先在 staging 测试依赖 etcd 的服务;开始前下载 snapshot 备份以便出问题时回滚;并下载最新的 etcd v3.5 发布版
Mixed versions 降级期间支持混合版本,按最低公共版本协议运行;通过 etcdctl downgrade enable 3.5 开启降级后,集群即视为已降级;内部把整体集群版本设为降级目标版本,决定报告版本与可用特性
Rollback 降级前创建并下载 snapshot 备份;出问题先定位并解决根因。若已执行 etcdctl downgrade enable、集群仍处混合版本状态(至少一个 member 还是 v3.6),可运行 etcdctl downgrade cancel 取消进行中的降级,并用原来的 v3.6 二进制重启所有已降级 member。一旦所有 member 降到 v3.5,集群视为完全降级;此时若要回到原版本,应按官方 upgrade guide 走升级流程,保证一致性、避免数据损坏

降级流程(3 member v3.6 集群)

Step 1:check downgrade requirements

etcdctl --endpoints=localhost:2379,localhost:22379,localhost:32379 endpoint health
<<COMMENT
localhost:2379 is healthy: successfully committed proposal: took = 2.118638ms
localhost:22379 is healthy: successfully committed proposal: took = 3.631388ms
localhost:32379 is healthy: successfully committed proposal: took = 2.157051ms
COMMENT

curl http://localhost:2379/version
<<COMMENT
{"etcdserver":"3.6.0-alpha.0","etcdcluster":"3.6.0","storage":"3.6.0"}
COMMENT

etcdctl --endpoints=localhost:2379,localhost:22379,localhost:32379 endpoint status -w=table
<<COMMENT
|  localhost:2379 | 8211f1d0f64f3269 | 3.6.0-alpha.0 |           3.6.0 |   20 kB |  16 kB |                   20% |   0 B |      true |      false |         2 |         10 |                 10 |        |                          |             false |
| localhost:22379 | 91bc3c398fb3c146 | 3.6.0-alpha.0 |           3.6.0 |   20 kB |  16 kB |                   20% |   0 B |     false |      false |         2 |         10 |                 10 |        |                          |             false |
| localhost:32379 | fd422379fda50e48 | 3.6.0-alpha.0 |           3.6.0 |   20 kB |  16 kB |                   20% |   0 B |     false |      false |         2 |         10 |                 10 |        |                          |             false |
COMMENT

Step 2:download snapshot backup from leader

下载 snapshot 备份以便出问题时有降级/恢复路径。(本页该步骤未给出具体命令,仅有说明文字。)

Step 3:validate downgrade target version

  • 一次只支持降一个 minor 版本,例如从 v3.6 降到 v3.4 是不允许的;
  • 校验成功之前不要进入下一步。
etcdctl downgrade validate 3.5
<<COMMENT
Downgrade validate success, cluster version 3.6
COMMENT

Step 4:enable downgrade

etcdctl downgrade enable 3.5
<<COMMENT
Downgrade enable success, cluster version 3.6
COMMENT

开启降级后,集群开始以 v3.5 协议运行;etcd 会自动把 schema 迁回降级目标版本,通常很快。进入下一步前用 endpoint status 确认所有 server 的 storage version 已迁移到 v3.5:

etcdctl --endpoints=localhost:2379,localhost:22379,localhost:32379 endpoint status -w=table
<<COMMENT
|  localhost:2379 | 8211f1d0f64f3269 | 3.6.0-alpha.0 |           3.5.0 |   20 kB | ... |      true |      false |         2 |         12 |                 12 |        |                    3.5.0 |              true |
| localhost:22379 | 91bc3c398fb3c146 | 3.6.0-alpha.0 |           3.5.0 |   20 kB | ... |     false |      false |         2 |         12 |                 12 |        |                    3.5.0 |              true |
| localhost:32379 | fd422379fda50e48 | 3.6.0-alpha.0 |           3.5.0 |   20 kB | ... |     false |      false |         2 |         12 |                 12 |        |                    3.5.0 |              true |
COMMENT

Note:一旦开启降级,即使所有 server 仍运行 v3.6 二进制,集群也会保持以 v3.5 协议运行,除非用 etcdctl downgrade cancel 取消。

Step 5:stop one existing etcd server

停止 server 前先检查它是否是 leader,建议最后再降级 leader。先用 endpoint status -w=table 看 IS LEADER 列;若要停止的 server 是 leader,可先 move-leader:

etcdctl --endpoints=localhost:2379,localhost:22379,localhost:32379 move-leader 91bc3c398fb3c146

etcdctl --endpoints=localhost:2379,localhost:22379,localhost:32379 endpoint status -w=table
<<COMMENT
|  localhost:2379 | 8211f1d0f64f3269 | 3.6.0-alpha.0 |           3.5.0 | ... |     false |      false |         3 |         13 |                 13 |        |                    3.5.0 |              true |
| localhost:22379 | 91bc3c398fb3c146 | 3.6.0-alpha.0 |           3.5.0 | ... |      true |      false |         3 |         13 |                 13 |        |                    3.5.0 |              true |
| localhost:32379 | fd422379fda50e48 | 3.6.0-alpha.0 |           3.5.0 | ... |     false |      false |         3 |         13 |                 13 |        |                    3.5.0 |              true |
COMMENT

停止进程时其他 member 会记录预期内错误(连接被临时断开):

{"level":"warn","ts":"2025-02-28T17:35:43.795069Z","caller":"etcdserver/cluster_util.go:259","msg":"failed to reach the peer URL","address":"http://127.0.0.1:12380/version","remote-member-id":"8211f1d0f64f3269","error":"Get \"http://127.0.0.1:12380/version\": dial tcp 127.0.0.1:12380: connect: connection refused"}
{"level":"warn","ts":"2025-02-28T17:35:43.795149Z","caller":"etcdserver/cluster_util.go:160","msg":"failed to get version","remote-member-id":"8211f1d0f64f3269","error":"Get \"http://127.0.0.1:12380/version\": dial tcp 127.0.0.1:12380: connect: connection refused"}
{"level":"warn","ts":"2025-02-28T17:35:44.368651Z","caller":"rafthttp/probing_status.go:68","msg":"prober detected unhealthy status","round-tripper-name":"ROUND_TRIPPER_SNAPSHOT","remote-peer-id":"8211f1d0f64f3269","rtt":"483.01µs","error":"dial tcp 127.0.0.1:12380: connect: connection refused"}
{"level":"warn","ts":"2025-02-28T17:35:44.368726Z","caller":"rafthttp/probing_status.go:68","msg":"prober detected unhealthy status","round-tripper-name":"ROUND_TRIPPER_RAFT_MESSAGE","remote-peer-id":"8211f1d0f64f3269","rtt":"735.659µs","error":"dial tcp 127.0.0.1:12380: connect: connection refused"}

Step 6:restart the etcd server with same configuration (minus the flags that are removed or replaced in v3.5)

用相同配置(去掉在 v3.5 中被移除或替换的 flag)+ v3.5 二进制重启:

-etcd-3.6/bin --name s1 \
+etcd-3.5/bin --name s1 \
  --data-dir /tmp/etcd/s1 \
  --listen-client-urls http://localhost:2379 \
  --advertise-client-urls http://localhost:2379 \
  --listen-peer-urls http://localhost:2380 \
  --initial-advertise-peer-urls http://localhost:2380 \
  --initial-cluster s1=http://localhost:2380,s2=http://localhost:22380,s3=http://localhost:32380 \
  --initial-cluster-token tkn \
  --initial-cluster-state existing

素材疑点:downgrade_3_6.md Step 6 的 diff 中旧行写作 -etcd-3.6/bin --name s1 \,疑漏 /etcd(应为 etcd-3.6/bin/etcd)。本册按原文保留。

验证 member 与集群在 v3.5 二进制下健康:

etcdctl --endpoints=localhost:2379,localhost:22379,localhost:32379 endpoint status -w=table
<<COMMENT
|  localhost:2379 | 8211f1d0f64f3269 |        3.5.18 |                 |   20 kB |  16 kB |                   20% |   0 B |     false |      false |         3 |         14 |                 14 |        |                          |             false |
| localhost:22379 | 91bc3c398fb3c146 | 3.6.0-alpha.0 |           3.5.0 |   20 kB |  16 kB |                   20% |   0 B |      true |      false |         3 |         14 |                 14 |        |                    3.5.0 |              true |
| localhost:32379 | fd422379fda50e48 | 3.6.0-alpha.0 |           3.5.0 |   20 kB |  16 kB |                   20% |   0 B |     false |      false |         3 |         14 |                 14 |        |                    3.5.0 |              true |
COMMENT

etcdctl endpoint health --endpoints=localhost:2379,localhost:22379,localhost:32379
<<COMMENT
localhost:22379 is healthy: successfully committed proposal: took = 4.650967ms
localhost:2379 is healthy: successfully committed proposal: took = 4.634377ms
localhost:32379 is healthy: successfully committed proposal: took = 5.047777ms
COMMENT

Note:你会看到 v3.5 server 的 DOWNGRADE ENABLED 为 false,因为 v3.5 的 status endpoint 未实现降级信息;此时集群层面的降级仍然是开启状态。

Step 7:repeat step 5 and step 6 for rest of the members

全部 member 降级后,检查健康与状态,确认所有 member 的 minor 版本为 v3.5,且 storage version 为空:

etcdctl --endpoints=localhost:2379,localhost:22379,localhost:32379 endpoint status -w=table
<<COMMENT
|  localhost:2379 | 8211f1d0f64f3269 |  3.5.18 |                 |   20 kB |  16 kB |                   20% |   0 B |     false |      false |         3 |         26 |                 26 |        |                          |             false |
| localhost:22379 | 91bc3c398fb3c146 |  3.5.18 |                 |   20 kB |  16 kB |                   20% |   0 B |      true |      false |         3 |         26 |                 26 |        |                          |             false |
| localhost:32379 | fd422379fda50e48 |  3.5.18 |                 |   20 kB |  16 kB |                   20% |   0 B |     false |      false |         3 |         26 |                 26 |        |                          |             false |
COMMENT

etcdctl endpoint health --endpoints=localhost:2379,localhost:22379,localhost:32379
<<COMMENT
localhost:22379 is healthy: successfully committed proposal: took = 4.650967ms
localhost:2379 is healthy: successfully committed proposal: took = 4.634377ms
localhost:32379 is healthy: successfully committed proposal: took = 5.047777ms
COMMENT

curl http://localhost:2379/version
<<COMMENT
{"etcdserver":"3.5.18","etcdcluster":"3.5.0"}
COMMENT

在 leader 日志中应能看到:

{"level":"info","ts":"2025-02-28T17:59:50.019862Z","caller":"etcdserver/server.go:2749","msg":"the cluster has been downgraded","cluster-version":"3.5.0"}

14.8 破坏性变更与废弃 flag 映射总表

v3.6 相对 v3.5 的变化

新增 flag(Flags added)

+etcd --discovery-token ''
+etcd --discovery-endpoints ''
+etcd --discovery-dial-timeout '2s'
+etcd --discovery-request-timeout '5s'
+etcd --discovery-keepalive-time '2s'
+etcd --discovery-keepalive-timeout '6s'
+etcd --discovery-insecure-transport 'true'
+etcd --discovery-insecure-skip-tls-verify 'false'
+etcd --discovery-cert ''
+etcd --discovery-key ''
+etcd --discovery-cacert ''
+etcd --discovery-user ''
+etcd --discovery-password ''
+etcd --feature-gates
+etcd --log-format

被移除的 flag(Flags removed)

-etcd --enable-v2
-etcd --experimental-enable-v2v3
-etcd --proxy
-etcd --proxy-failure-wait
-etcd --proxy-refresh-interval
-etcd --proxy-dial-timeout
-etcd --proxy-write-timeout
-etcd --proxy-read-timeout

被废弃的 flag(Flags deprecated,16 个,均去掉 --experimental- 前缀)

废弃写法 替代写法
--experimental-bootstrap-defrag-threshold-megabytes --bootstrap-defrag-threshold-megabytes
--experimental-compaction-batch-limit --compaction-batch-limit
--experimental-compact-hash-check-time --compact-hash-check-time
--experimental-compaction-sleep-interval --compaction-sleep-interval
--experimental-corrupt-check-time --corrupt-check-time
--experimental-enable-distributed-tracing --enable-distributed-tracing
--experimental-distributed-tracing-address --distributed-tracing-address
--experimental-distributed-tracing-instance-id --distributed-tracing-instance-id
--experimental-distributed-tracing-sampling-rate --distributed-tracing-sampling-rate
--experimental-distributed-tracing-service-name --distributed-tracing-service-name
--experimental-downgrade-check-time --downgrade-check-time
--experimental-max-learners --max-learners
--experimental-memory-mlock --memory-mlock
--experimental-peer-skip-client-san-verification --peer-skip-client-san-verification
--experimental-snapshot-catchup-entries --snapshot-catchup-entries
--experimental-warning-apply-duration --warning-apply-duration
--experimental-warning-unary-request-duration --warning-unary-request-duration
--experimental-watch-progress-notify-interval --watch-progress-notify-interval

素材注明「共 16 个」,但表格实际列出 18 行。本册按表格原文全部保留,未删行。

v3.5 feature gate 的等价 flag 映射

v3.5 写法 v3.6 等价写法
--experimental-compact-hash-check-enabled=true --feature-gates=CompactHashCheck=true
--experimental-initial-corrupt-check=true --feature-gates=InitialCorruptCheck=true
--experimental-enable-lease-checkpoint=true --feature-gates=LeaseCheckpoint=true
--experimental-enable-lease-checkpoint-persist=true --feature-gates=LeaseCheckpointPersist=true
--experimental-stop-grpc-service-on-defrag=true --feature-gates=StopGRPCServiceOnDefrag=true
--experimental-txn-mode-write-with-shared-buffer=false --feature-gates=TxnModeWriteWithSharedBuffer=false

默认值发生变化的 flag(v3.5 → v3.6)

原默认值 v3.6 新默认值
--snapshot-count=100000 --snapshot-count=10000
--v2-deprecation='not-yet' --v2-deprecation='write-only'
--discovery-fallback='proxy' --discovery-fallback='exit'

Prometheus metrics 差异(v3.6 新增)

# metrics added in v3.6
+etcd_network_known_peers
+etcd_server_feature_enabled

v3.7 相对 v3.6 的变化

类别 内容
Flags removed 所有 23 个已废弃 --experimental-* flag(见 14.4 节 diff 清单,PR #19959)
Flags added None
Flags with new defaults None
破坏性变更 v2 store 完全移除(v2 HTTP API、v2-on-v3 模拟、v2 discovery service、client/v2 包、v2 snapshot 加载)
Go 重构 gogo/protobuf → google.golang.org/protobuf(#14533);go-grpc-middleware v1 → v2 interceptors(#20420);otelgrpc v0.61.0 用 NewServerHandler 取代废弃 interceptor(#20017)

降级方向的指标差异汇总

降级方向 目标版本不存在的指标
v3.7 → v3.6 etcd_server_request_duration_seconds、etcd_debugging_server_watch_send_loop_control_stream_duration_seconds、etcd_debugging_server_watch_send_loop_progress_duration_seconds、etcd_debugging_server_watch_send_loop_watch_stream_duration_seconds、etcd_debugging_server_watch_send_loop_watch_stream_duration_per_event_seconds
v3.6 → v3.5 etcd_network_known_peers、etcd_server_feature_enabled

💡 补充:降级前若 Grafana 面板或告警规则引用了上表中的指标,降级后这些面板会出现「No data」。建议降级前先把相关面板/规则标注或临时下线。

14.9 升级 / 降级操作要点速查

项目 升级 降级
版本跨度 一次一个 minor,先到最新 patch 一次一个 minor
最低前置版本 3.6 要求 ≥ v3.5;3.7 要求 ≥ v3.6.11 —
健康校验 etcdctl endpoint health etcdctl endpoint health
版本校验 curl http://host:2379/version curl http://host:2379/version + endpoint status -w=table
前置动作 从 leader 拍 snapshot 备份 从 leader 拍 snapshot 备份 + 下载目标版本发布包
关键命令 逐台替换二进制 etcdctl downgrade validate <ver> → etcdctl downgrade enable <ver> → 逐台替换 → 完成
中途反悔 混合版本状态下可换回旧二进制 混合版本状态下 etcdctl downgrade cancel + 用原二进制重启已降级 member
完成后反悔 只能用 snapshot 恢复,或走正式降级流程 只能走正式升级流程
leader 处理 leader 会在退出前自动转移 leadership 建议最后降级 leader,用 etcdctl move-leader 先移走
配置是否需要改 相同配置即可 v3.7→v3.6 无需改配置;v3.6→v3.5 必须删除/改名/改默认值

14.10 文档内部疑点(素材已注明,本册未做修正)

  1. upgrade_3_6.md Step 5 的命令写作 endpoint health --endpoints=...,疑似漏写 etcdctl 前缀(Step 4 中为 etcdctl endpoint health --endpoints=...)。
  2. upgrade_3_6.md 的 "Upgrade requirements" 要求「低于 v3.5 请先升级到 v3.5」,但链接指向 ../upgrade_3_4/(3.4→3.5 指南),文字与链接版本存在表述不一致。
  3. upgrade_3_6.md 要求 3.5 member 至少为 3.5.32 或更高,但同页示例输出中版本为 3.5.18,示例与要求不自洽。
  4. upgrade_3_6.md 与 downgrade_3_6.md 中 InitialCorruptCheck 的旧写法不一致:升级页为 --experimental-initial-corrupt-check=true,降级页为 --experimental-enable-initial-corrupt-check=true。
  5. downgrade_3_6.md 的 Step 2 仅有说明文字,未给出 snapshot 备份命令(对比 downgrade_3_7.md Step 2 给出了 etcdctl --endpoints=localhost:2379 snapshot save backup.db)。
  6. downgrade_3_6.md Step 6 的 diff 中旧行写作 -etcd-3.6/bin --name s1 \,疑漏 /etcd。
  7. 「先 learner 升级」「只有所有 member 都运行 v3.x 才能降级」「降级后 revision 变化」等说法,在这 6 个源文件中均未出现;升级流程未规定 learner 顺序,降级前提条件仅写明「一次只降一个 minor 版本 + etcdctl downgrade validate 校验通过 + 集群健康」。

15. 性能与容量:三个 benchmark

本章原样保留三个官方 benchmark 页的全部数字,不做换算、不补数据。三个页面在素材中均未给出具体的命令行调用与参数,本册如实说明。

15.1 Benchmarking etcd v3(读性能)

引自:Benchmarking etcd v3

测试目的

衡量 etcd v3(demo 模式)在单成员部署下的读性能基线,分别观测「空 store 上读单个 key」与「先执行一次 put 之后再读单个 key」两种场景,并以「空 server handler」的基准作为对照,判断读路径的性能损耗到底来自 gRPC/服务端框架,还是来自 storage 包。

环境 / 硬件配置

物理机:GCE n1-highcpu-2 machine type

  • 1x 专用本地 SSD,挂载于 /var/lib/etcd
  • 1x 专用慢速磁盘用于操作系统
  • 1.8 GB 内存
  • 2x CPUs
  • etcd version 2.2.0

etcd 集群:1 个 etcd member,以 v3 demo mode 运行。

命令

  • 使用 etcd v3 benchmark tool(工具名为 benchmark,位于 tools/benchmark)。
  • 本页未给出具体的命令行调用与参数(如 benchmark put / range 的具体 flags),仅指向上述工具仓库路径。

结果数据表

1)reading one single key(读单个 key)

key size in bytes number of clients read QPS 90th Percentile Latency (ms)
256 1 2716 0.4
256 64 16623 6.1
256 256 16622 21.7

2)reading one single key after putting(put 之后读单个 key)

key size in bytes number of clients read QPS 90th Percentile Latency (ms)
256 1 2269 0.5
256 64 13582 8.6
256 256 13262 47.5

结论

  • 纯读场景下的性能与「空 server handler」几乎一致,说明 gRPC/服务端框架本身不是瓶颈。
  • 客户端数从 1 提升到 64 时,read QPS 从 2716 提升到 16623,90th 延迟从 0.4 ms 上升到 6.1 ms;继续提升到 256 客户端时 QPS 基本不再增长(16622),而 90th 延迟进一步恶化到 21.7 ms,说明此时已达到饱和。
  • put 之后读的场景整体下降:1/64/256 客户端下 QPS 分别为 2269 / 13582 / 13262,90th 延迟分别为 0.5 / 8.6 / 47.5 ms。
  • 由于「空 server handler」的性能不受单次 put 影响,因此 put 后读的性能下降应归因于 storage 包,而非服务端框架。

💡 补充(容量规划可推出的结论):

  1. 读的并发饱和点约在 64 客户端:从 64 → 256 客户端,QPS 不再增长(16623 → 16622),但 p90 延迟从 6.1ms 恶化到 21.7ms(约 3.6 倍)。据此,客户端连接池不是开得越大越好,超过饱和点只会换延迟。
  2. 单次 put 之后读吞吐下降约 16%–20%(16623 → 13582;16622 → 13262),且高并发下延迟恶化更明显(21.7ms → 47.5ms,约 2.2 倍)。写多读少的集群要按「put 后读」这一档做容量基线。
  3. 该页环境标注为 etcd version 2.2.0 而标题为 v3,疑为文档沿用了早期版本的环境描述(素材已注明),因此这组数字宜作为量级参考,不宜作为 v3.7 的 SLA 基线。

15.2 Watch Memory Usage Benchmark

引自:Watch Memory Usage Benchmark

测试目的

量化 etcd watch 功能的物理内存占用,找到 watch 内存消耗模型中三个系数 c1(每个连接)、c2(每个 stream)、c3(每个 watching)的经验值,从而估算单台 etcd server 能承载多少 watcher。

etcd 的设计目标:支持 O(10k) 个客户端、O(100K) 条 watch stream(每客户端 O(10) 条 stream)、O(10M) 个总 watching(每 stream O(100) 个 watching)。

内存消耗模型

etcd watch 有三个消耗物理内存的组件:

  1. grpc.Conn:维护真实的 TCP 连接与 gRPC 连接状态,每个消耗 O(10kb),其上可挂载多条 watch stream。
  2. watch stream:独立的 HTTP2 连接,每个再消耗 O(10kb),多条 watching 可共享同一条 stream。
  3. watching:真正跟踪 key-value store 变更的结构体,每个应仅消耗 < O(1kb)。

理论估算公式:

memory = c1 * number_of_conn + c2 * avg_number_of_stream_per_conn + c3 * avg_number_of_watch_stream

结构关系示意:conn → 多条 stream,每条 stream → 多个 watch(如 watch foo、watch bar)。

环境 / 硬件配置

命令 / 测量方法

  • 总体内存占用以 RSS 衡量,结果可能有约 10% 的波动,但足以反映大致用量与分配模式。
  • 本页未给出 benchmark、etcdctl check perf 或 go test -bench 等具体命令行,仅说明通过实际压测得到上表后反推系数。

结果数据表(Overall memory usage)

clients streams per client watchings per stream total watching memory usage
1k 1 1 1k 50MB
2k 1 1 2k 90MB
5k 1 1 5k 200MB
1k 10 1 10k 217MB
2k 10 1 20k 417MB
5k 10 1 50k 980MB
1k 50 1 50k 1001MB
2k 50 1 100k 1960MB
5k 50 1 250k 4700MB
1k 50 10 500k 1171MB
2k 50 10 1M 2371MB
5k 50 10 2.5M 5710MB
1k 50 100 5M 2380MB
2k 50 100 10M 4672MB
5k 50 100 25M OOM

结论

  • 由基准结果反推:c1 = 17kb、c2 = 18kb、c3 = 350bytes。即每增加一个客户端连接约消耗 17kb 内存,每增加一条 stream 约消耗 18kb 内存,而每增加一个 watching 仅约 350bytes。
  • 单个 watching 的成本远低于连接和 stream,因此优化重点在于连接与 stream 的复用。
  • 正常情况下单台 etcd server 可用几 GB 内存维持数百万(millions)个 watching。
  • 数据也显示瓶颈主要来自连接数与 stream 数:5k clients × 50 streams × 1 watching 就已到 4700MB,而 5k × 50 × 10(总 2.5M watching)也只有 5710MB,说明 watching 数量本身对内存影响相对小。
  • 极端规模(5k clients × 50 streams × 100 watchings,共 25M watching)在 7.5 GB 内存机器上会触发 OOM。
  • 页面提示:watch 特性仍在活跃开发中,内存占用可能随之变化,但预期不会显著超过上表数字。

💡 补充(容量规划可直接套用的估算式): 用反推系数做粗算:内存(字节) ≈ 17KB × 连接数 + 18KB × (连接数 × 每连接 stream 数) + 350B × (总 watching 数)。 例:2k 客户端 × 10 stream/客户端 × 每 stream 100 watching(总 2M watching)≈ 34MB + 360MB + 700MB ≈ 1.09GB,与表中 2k×50×10(2.37GB,stream 数更多)量级一致。 优化优先级由此非常明确:① 减少 gRPC 连接数(复用 client);② 减少每连接的 stream 数(合并 watch);③ watching 数量本身反而是最便宜的。 表中 7.5GB 机器在 25M watching 时 OOM,因此单 member 的 watching 上限建议控制在 10M 量级以内并留出余量。

15.3 Storage Memory Usage Benchmark

引自:Storage Memory Usage Benchmark

测试目的

量化 etcd storage 的内存占用,分为两部分:

  1. In-memory index(进程内 B-tree 索引)的内存开销,目标是求出公式中的 c1(key 元数据开销)与 c2(版本元数据开销),并理解 store 的内存硬上限。
  2. Overall memory usage(RSS 总体占用),验证 value size 对总体内存的影响是否显著。

etcd storage 有两个消耗物理内存的组件:etcd 进程分配的 in-memory index(加速 key 查找),以及由操作系统管理的 page cache(缓存最近访问的磁盘数据)。in-memory index 在 B-tree 中保存全部 key 及指向磁盘 value 的指针,每个 key 可能有多个指向不同版本的指针。

理论公式:

N * (c1 + avg_key_size) + N * (avg_versions_of_key) * (c2 + size_of_pointer)

其中 c1 为 key 元数据开销,c2 为版本元数据开销。Page cache 由 OS 管理,本文档不展开。

环境 / 硬件配置

命令 / 测量方法

  • 通过 Go runtime.ReadMemStats 计算内存用量:取创建索引前与创建索引后的 total allocated bytes 差值。
  • 该方法不能完美反映 in-memory index 自身的精确占用,但能显示大致的消耗模式。
  • 本页未出现 benchmark、etcdctl check perf、go test -bench 等具体命令行。

结果数据表

1)In-memory index memory usage

N versions key size memory usage
100K 1 64bytes 22MB
100K 5 64bytes 39MB
1M 1 64bytes 218MB
1M 5 64bytes 432MB
100K 1 256bytes 41MB
100K 5 256bytes 65MB
1M 1 256bytes 409MB
1M 5 256bytes 506MB

2)Overall memory usage(RSS,含 value size 变量)

N versions key size value size memory usage
100K 1 64bytes 256bytes 40MB
100K 5 64bytes 256bytes 89MB
1M 1 64bytes 256bytes 470MB
1M 5 64bytes 256bytes 880MB
100K 1 64bytes 1KB 102MB
100K 5 64bytes 1KB 164MB
1M 1 64bytes 1KB 587MB
1M 5 64bytes 1KB 836MB

结论

  • 由结果反推 c1 = 120bytes、c2 = 30bytes。由于 c1、c2 是公式中仅有的未知量,只需两组数据即可求解,表中的 c1/c2 是 4 组计算结果的平均值。
  • 对小 key-value 对而言,key 元数据开销仍相对较高(约 50%),但相比旧 store 至少 1000% 的开销已是显著改进。
  • key size 从 64bytes 增大到 256bytes 会明显推高索引内存:1M × 1 version 从 218MB 增至 409MB。
  • 版本数从 1 增至 5 同样显著推高内存:1M × 64bytes 从 218MB 增至 432MB;1M × 256bytes 从 409MB 增至 506MB。
  • value size 对总体内存消耗影响不显著,因为 value 保存在磁盘上,仅热数据由 OS page cache 留在内存中;表中有小幅上升(如 100K × 1 version 从 40MB 到 102MB)主要是 page cache 中驻留数据增多所致。
  • Page cache 由操作系统管理,本文不做详细分析。

💡 补充(容量规划可推出的结论):

  1. 索引内存 ≈ N × (120B + key_size) + N × 版本数 × (30B + 指针大小)。以 1M key、64B key、5 个版本估算:1M × 184B + 1M × 5 × 38B ≈ 184MB + 190MB ≈ 374MB,与实测 432MB 同量级——可用此式做上机前的内存预估。
  2. key 的长度比 value 的长度更「贵」:key 从 64B → 256B(+192B)使 1M×1version 索引内存从 218MB → 409MB(+191MB,几乎 1:1 线性);而 value 从 256B → 1KB 主要只推高 page cache,进程内影响小。设计 key 命名时应尽量短。
  3. 历史版本数是隐形内存杀手:版本数 1 → 5 让 1M×64B 从 218MB 涨到 432MB(约 2 倍)。这正是要配好 auto-compaction(第 6 章)的原因——压缩后旧版本被回收,索引内存随之下降。
  4. 综合 watch 与 storage 两组数据:内存规划应同时算「连接/stream 开销」与「key 数 × 版本数 开销」两项,只算一项会低估。

15.4 三个 benchmark 的环境与来源对照

页面 机型 CPU / 内存 etcd 版本 测量方式 是否给出命令行
Benchmarking etcd v3 GCE n1-highcpu-2 + 本地 SSD 2x CPUs / 1.8 GB 2.2.0(页面标注,疑沿用旧描述) benchmark 工具(QPS / p90 延迟) 否(仅给工具链接)
Watch Memory Usage Benchmark GCE n1-standard-2 2x CPUs / 7.5 GB git head 185097ffaa627b909007e772c175e8fefac17af3 RSS 否
Storage Memory Usage Benchmark GCE n1-standard-2 2x CPUs / 7.5 GB git head 776e9fb7be7eee5e6b58ab977c8887b4fe4d48db Go runtime.ReadMemStats 否

💡 补充:三页均为单机 2 核小机型下的测量,且都未给出可复现的命令行。因此它们适合用于「量级判断与系数推导」,不适合直接当作生产 SLA。真要做容量验收,建议用 tools/benchmark 在自己的机型上跑一遍(工具仓库:https://github.com/etcd-io/etcd/tree/main/tools/benchmark)。


16. 生态与社区

本章介绍 etcd 周边的工具与客户端库、日志约定、报 bug 的模板要素,以及社区 issue / PR 的分诊规则与时效。

引自:Libraries and tools 引自:Logging conventions 引自:Reporting bugs 引自:Issue triage guidelines 引自:PR management

16.1 Libraries and tools

开篇声明:下文提到的第三方库与工具(非托管在 https://github.com/etcd-io 的)etcd 团队既不测试也不维护。使用前建议用户自行阅读与调研。

16.1.1 Tools(共 19 个)

# 名称 链接 定位
1 etcdctl https://github.com/etcd-io/etcd/tree/main/etcdctl etcd 命令行客户端(官方)
2 etcd-dump https://npmjs.org/package/etcd-dump 命令行工具,用于 dump / 恢复 etcd
3 etcd-fs https://github.com/xetorthio/etcd-fs 面向 etcd 的 FUSE 文件系统
4 etcddir https://github.com/rekby/etcddir 实时同步 etcd 与本地目录,支持 Windows 与 Linux
5 etcd-browser https://github.com/henszey/etcd-browser 基于 AngularJS 的 Web 版 etcd key/value 编辑器
6 etcd-lock https://github.com/datawisesystems/etcd-lock 基于 etcd 的 master 选举与分布式读写锁实现,支持 v2
7 etcd-console https://github.com/matishsiao/etcd-console 基于 PHP 的 Web 版 etcd key/value 编辑器
8 etcd-viewer https://github.com/nikfoundas/etcd-viewer Java 编写的 etcd KV 编辑器 / 查看器
9 etcdtool https://github.com/mickep76/etcdtool 以 JSON/YAML/TOML 导出/导入/编辑 etcd 目录,并用 JSON schema 校验
10 etcdloadtest https://github.com/sinsharat/etcdloadtest 命令行压测客户端,适用于 etcd 3.0 及以上
11 etcd-tui https://github.com/olamilekan000/etcd-tui 现代化终端 UI(TUI),可在终端内浏览 key、查看 value、过滤数据、管理集群
12 etcdfinder https://etcdfinder.github.io 极速现代 Web UI,带即时搜索,同时支持 v2 与 v3
13 lucas https://github.com/ringtail/lucas 面向 Kubernetes etcd3.0+ 集群的 Web 版 KV 查看器
14 etcd-manager https://etcdmanager.io 现代化、高效、跨平台且免费的 etcd 3.x GUI 与客户端工具,支持 Windows/Linux/Mac
15 etcd-backup-restore https://github.com/gardener/etcd-backup-restore 周期性、增量式备份与恢复 etcd 的实用工具
16 etcd-druid https://github.com/gardener/etcd-druid Kubernetes operator,用于部署 etcd 集群并管理 day-2 运维
17 etcdadm https://github.com/kubernetes-sigs/etcdadm 操作 etcd 集群的命令行工具
18 etcd-defrag https://github.com/ahrtr/etcd-defrag 更易用、更智能的 etcd 碎片整理工具
19 etcdhelper https://github.com/tsonglew/intellij-etcdhelper IntelliJ 平台的 etcd 插件

💡 补充:运维视角可优先关注 4 类——备份恢复(etcd-backup-restore)、K8s 编排(etcd-druid)、集群操作 CLI(etcdadm)、碎片整理(etcd-defrag)。其余多为 KV 浏览/编辑类 GUI,排障时好用但不建议作为日常运维入口。

16.1.2 Libraries(按语言分类)

Go

库 链接 说明
etcd/client/v3 https://github.com/etcd-io/etcd/tree/main/client/v3 v3 官方维护的 Go 客户端
go-etcd https://github.com/coreos/go-etcd 已废弃的官方客户端,对更老的(<2.0.0)etcd 版本可能有用
encWrapper https://github.com/lumjjb/etcd/tree/enc_wrapper/clientwrap/encwrapper 针对 etcd client Keys API/KV 的加密封装

Java

库 链接 说明
coreos/jetcd https://github.com/etcd-io/jetcd 支持 v3
justinsb/jetcd https://github.com/justinsb/jetcd —
cdancy/etcd-rest https://github.com/cdancy/etcd-rest 基于 jclouds 提供完整的 v2 API 实现
IBM/etcd-java https://github.com/IBM/etcd-java —

Scala

库 链接 说明
maciej/etcd-client https://github.com/maciej/etcd-client 支持 v2,基于 Akka HTTP 的全异步客户端
eiipii/etcdhttpclient https://bitbucket.org/eiipii/etcdhttpclient 支持 v2,基于 Netty 与 Scala Futures 的异步 HTTP 客户端
mingchuno/etcd4s https://github.com/mingchuno/etcd4s 支持 v3,使用 gRPC,可选 Akka Stream 支持

Perl

库 链接 说明
hexfusion/perl-net-etcd https://github.com/hexfusion/perl-net-etcd 支持 v3 grpc gateway HTTP API
robn/p5-etcd https://github.com/robn/p5-etcd 支持 v2

Python

库 链接 说明
kragniz/python-etcd3 https://github.com/kragniz/python-etcd3 v3 客户端
jplana/python-etcd https://github.com/jplana/python-etcd 支持 v2
russellhaering/txetcd https://github.com/russellhaering/txetcd 一个 Twisted Python 库
cholcombe973/autodock https://github.com/cholcombe973/autodock Docker 部署自动化工具
lisael/aioetcd https://github.com/lisael/aioetcd (Python 3.4+)asyncio 协程客户端,支持 v2
txaio-etcd https://github.com/crossbario/txaio-etcd 异步 etcd v3-only 客户端库,当前支持 Twisted,未来支持 asyncio
dims/etcd3-gateway https://github.com/dims/etcd3-gateway 使用 HTTP grpc gateway 的 etcd v3 API 库
aioetcd3 https://github.com/gaopeiliang/aioetcd3 (Python 3.6+)面向 asyncio 的 etcd v3 API
Revolution1/etcd3-py https://github.com/Revolution1/etcd3-py (python2.7 与 python3.5+)etcd v3 Python 客户端,使用 gRPC-JSON-Gateway

Node

库 链接 说明
mixer/etcd3 https://github.com/mixer/etcd3 支持 v3
stianeikeland/node-etcd https://github.com/stianeikeland/node-etcd 支持 v2(含 Coffeescript)
lavagetto/nodejs-etcd https://github.com/lavagetto/nodejs-etcd 支持 v2
deedubs/node-etcd-config https://github.com/deedubs/node-etcd-config 支持 v2

Ruby

库 链接 说明
iconara/etcd-rb https://github.com/iconara/etcd-rb —
jpfuentes2/etcd-ruby https://github.com/jpfuentes2/etcd-ruby —
ranjib/etcd-ruby https://github.com/ranjib/etcd-ruby 支持 v2
davissp14/etcdv3-ruby https://github.com/davissp14/etcdv3-ruby 支持 v3

C

库 链接 说明
apache/celix/etcdlib https://github.com/apache/celix/tree/master/libs/etcdlib 支持 v2
jdarcy/etcd-api https://github.com/jdarcy/etcd-api 支持 v2
shafreeck/cetcd https://github.com/shafreeck/cetcd 支持 v2

C++

库 链接 说明
edwardcapriolo/etcdcpp https://github.com/edwardcapriolo/etcdcpp 支持 v2
suryanathan/etcdcpp https://github.com/suryanathan/etcdcpp 支持 v2(带 waits)
nokia/etcd-cpp-api https://github.com/nokia/etcd-cpp-api 支持 v2
etcd-cpp-apiv3/etcd-cpp-apiv3 https://github.com/etcd-cpp-apiv3/etcd-cpp-apiv3 支持 v3

Clojure

库 链接 说明
aterreno/etcd-clojure https://github.com/aterreno/etcd-clojure —
dwwoelfel/cetcd https://github.com/dwwoelfel/cetcd 支持 v2
rthomas/clj-etcd https://github.com/rthomas/clj-etcd 支持 v2

Erlang

库 链接 说明
marshall-lee/etcd.erl https://github.com/marshall-lee/etcd.erl 支持 v2
zhongwencool/eetcd https://github.com/zhongwencool/eetcd 支持 v3+(仅 GRPC)

Elixir

库 链接 说明
team-telnyx/etcdex https://github.com/team-telnyx/etcdex 支持 v3+(仅 GRPC)

.NET

库 链接 说明
wangjia184/etcdnet https://github.com/wangjia184/etcdnet 支持 v2
drusellers/etcetera https://github.com/drusellers/etcetera —
shubhamranjan/dotnet-etcd https://github.com/shubhamranjan/dotnet-etcd 支持 v3+(仅 GRPC)
SimplifyNet/Etcd.Microsoft.Extensions.Configuration https://github.com/SimplifyNet/Etcd.Microsoft.Extensions.Configuration —

PHP

库 链接 说明
linkorb/etcd-php https://github.com/linkorb/etcd-php —
activecollab/etcd https://github.com/activecollab/etcd —
ouqiang/etcd-php https://github.com/ouqiang/etcd-php v3 gRPC gateway 客户端

其他语言

语言 库 链接 说明
Haskell wereHamster/etcd-hs https://github.com/wereHamster/etcd-hs —
R ropensci/etseed https://github.com/ropensci/etseed —
Nim etcd_client https://github.com/FedericoCeratto/nim-etcd-client —
Tcl efrecon/etcd-tcl https://github.com/efrecon/etcd-tcl 支持 v2,但不支持 wait
Rust jimmycuadra/rust-etcd https://github.com/jimmycuadra/rust-etcd 支持 v2
Gradle gradle-etcd-rest-plugin https://github.com/cdancy/gradle-etcd-rest-plugin 支持 v2
Lua api7/lua-resty-etcd https://github.com/api7/lua-resty-etcd 支持 v2 与 v3(grpc gateway HTTP API)

16.1.3 Deployment tools

类别 项目 链接
Chef integrations coderanger/etcd-chef https://github.com/coderanger/etcd-chef
Chef cookbooks spheromak/etcd-cookbook https://github.com/spheromak/etcd-cookbook
BOSH releases cloudfoundry-community/etcd-boshrelease https://github.com/cloudfoundry-community/etcd-boshrelease
BOSH releases cloudfoundry/cf-release(jobs/etcd) https://github.com/cloudfoundry/cf-release/tree/master/jobs/etcd

16.1.4 Projects using etcd(使用 etcd 的项目)

项目 链接 说明
etcd Raft users https://github.com/etcd-io/raft/blob/main/README.md#notable-users 使用 etcd raft 库实现的项目列表
Apache APISIX https://github.com/apache/apisix 使用 etcd 作为配置存储的 API 网关
apache/celix https://github.com/apache/celix 适配 C/C++ 的 OSGi 规范实现
binocarlos/yoda https://github.com/binocarlos/yoda etcd + ZeroMQ
blox/blox https://github.com/blox/blox 面向 AWS ECS 的容器管理与编排开源项目集合
calavera/active-proxy https://github.com/calavera/active-proxy 用 etcd 配置的 HTTP 代理
chain/chain https://github.com/chain/chain 用于运维与连接高可扩展许可链网络的软件
derekchiang/etcdplus https://github.com/derekchiang/etcdplus 基于 etcd 构建的一组分布式同步原语
go-discover https://github.com/flynn/go-discover Go 编写的服务发现
gleicon/goreman https://github.com/gleicon/goreman/tree/etcd Go 版 Foreman 克隆的分支,支持 etcd
garethr/hiera-etcd https://github.com/garethr/hiera-etcd 使用 etcd 的 Puppet hiera 后端
mattn/etcd-vim https://github.com/mattn/etcd-vim 在 vim 内 SET/GET key
mattn/etcdenv https://github.com/mattn/etcdenv 集成 etcd 的 "env" shebang
kelseyhightower/confd https://github.com/kelseyhightower/confd 用模板 + etcd 数据管理本地应用配置文件
configdb https://git.autistici.org/ai/configdb/tree/master 构建在任意数据库后端之上的 REST 关系型抽象,用于存储配置与资产清单
kubernetes/kubernetes https://github.com/kubernetes/kubernetes Google 发起的容器集群管理器
mailgun/vulcand https://github.com/mailgun/vulcand 以 etcd 为配置后端的 HTTP 代理
duedil-ltd/discodns https://github.com/duedil-ltd/discodns 使用 etcd 作为名称与记录数据库的简易 DNS 服务器
skynetservices/skydns https://github.com/skynetservices/skydns 符合 RFC 的 DNS 服务器
xordataexchange/crypt https://github.com/xordataexchange/crypt 用 GPG 加密把值安全存入 etcd
spf13/viper https://github.com/spf13/viper Go 配置库,可从 ENV、pflags、文件与 etcd 读取(可选加密)
lytics/metafora https://github.com/lytics/metafora Go 分布式任务库
ryandoyle/nss-etcd https://github.com/ryandoyle/nss-etcd 用于从 etcd 解析名称的 GNU libc NSS 模块
Gru https://github.com/dnaeon/gru 用 Go 实现的编排工具
Vitess http://vitess.io/ 用于 MySQL 水平扩展的数据库集群系统
lclarkmichalek/etcdhcp https://github.com/lclarkmichalek/etcdhcp 使用 etcd 做持久化与协调的 DHCP 服务器
openstack/networking-vpp https://github.com/openstack/networking-vpp 编程 FD.io VPP 数据面以提供 OpenStack 云虚拟网络的网络驱动
OpenStack https://github.com/openstack/governance/blob/master/reference/base-services.rst OpenStack 服务可将 etcd 作为基础服务依赖
CoreDNS https://github.com/coredns/coredns/tree/master/plugin/etcd 链式插件的 DNS 服务器,CNCF 与 Kubernetes 生态项目
Uber M3 https://github.com/m3db/m3 Uber 开源的、面向 Prometheus 的大规模指标平台
Rook https://github.com/rook/rook Kubernetes 的存储编排
Patroni https://github.com/zalando/patroni 使用 ZooKeeper、etcd 或 Consul 的 PostgreSQL 高可用模板
Trillian https://github.com/google/trillian 实现 Merkle tree,内容由数据存储层提供以支撑超大规模树
purpleidea/mgmt https://github.com/purpleidea/mgmt 下一代分布式、事件驱动、并行配置管理
Portworx/kvdb https://docs.portworx.com/concepts/internal-kvdb/ 存储 Portworx 集群配置的内部 kvdb
Apache Pulsar https://pulsar.apache.org/ 为云构建的开源分布式消息与流平台

16.2 日志约定(Logging conventions)

etcd 使用 zap 库(https://github.com/uber-go/zap)输出应用日志,日志按 levels(级别)分类。日志级别按以下约定判定:

级别 语义 源文示例
DebugLevel 日志量通常很大,生产环境一般禁用 向远端 peer 发送一条普通消息(Send a normal message to a remote peer);向磁盘写一条日志条目(Write a log entry to disk)
InfoLevel 默认的日志优先级 启动配置(Startup configuration);开始做快照(Start to do snapshot);向集群加入新节点(Add a new node into the cluster);向 auth 子系统加入新用户(Add a new user into auth subsystem)
WarnLevel 比 Info 更重要,但不需要人工逐条介入 review 向远端 peer 发送 Raft 消息失败(Failure to send Raft message to a remote peer);在配置的 election timeout 内未收到心跳消息(Failure to receive heartbeat message within the configured election timeout)
ErrorLevel 高优先级。若应用运行平稳,不应该产生任何 error 级日志 为 WAL 分配磁盘空间失败(Failure to allocate disk space for WAL)
PanicLevel 记录一条消息后 panic Raft 消息编码失败(Failure to encode Raft messages)
FatalLevel 记录一条消息后调用 os.Exit(1) Raft 快照保存失败(Failure to save Raft snapshot)

⚠️ 源文说明(如实记录):该页面原文**仅覆盖「日志级别分类约定」**这一部分内容,未包含结构化日志字段规范(如 level、caller、msg、stacktrace 等字段名约定)、全局 logger 的获取/注入用法(如 zap.L()、GetLogger())以及代码位置约定等章节——本批抓取的 v3.7 源文件中不存在这些内容,故不展开,避免编造。

16.3 报 bug(Reporting bugs)

提交渠道:etcd 项目任何部分存在 bug 或文档错误,都请通过 opening an issue 告知。etcd 团队严肃对待 bug 与错误,认为没有问题是小到不值得提的(no issue is too small)。创建报告前,请先确认是否已经存在报告同一问题的 issue。

16.3.1 好的 bug 报告应满足的 5 条标准(模板要素)

# 标准 要点
1 Specific(具体) 尽可能包含细节——哪个版本、什么环境、什么配置等。若 bug 与运行 etcd server 相关,请附上 etcd 日志(带 etcd 配置的启动日志尤其重要)
2 Reproducible(可复现) 包含复现步骤;某些问题难以复现时,请给出可能导致该问题的步骤。若可能,请附上受影响的 etcd data dir 与 stack strace
3 Isolated(隔离) 尽量用最小依赖隔离并复现该 bug;报告中牵涉过多依赖会显著拖慢修复速度。调试依赖 etcd 的外部系统不在范围内,但很乐意提供方向指引或帮助使用 etcd 本身
4 Unique(唯一) 不要重复已有的 bug 报告
5 Scoped(范围限定) 一个报告只报一个 bug,不要在一份报告里追报另一个 bug

⚠️ 源文笔误保留:第 2 条中的 "stack strace" 为源文原文写法(应为 stack trace),本册不擅自修改。

其他约定:建议先阅读 Elika Etemad 关于如何提交好 bug 报告的文章(http://fantasai.inkedblade.net/style/talks/filing-good-bugs/)。团队可能会要求补充信息以定位 bug;重复的 bug 报告将被关闭。

16.3.2 三条取信息的命令

  • 如何获取 stack trace:
$ kill -QUIT $PID
  • 如何获取 etcd 版本:
$ etcd --version
  • 当 etcd 以 systemd service etcd2.service 运行时,如何获取配置与日志:
$ sudo systemctl cat etcd2
$ sudo journalctl -u etcd2

注意:由于上游 systemd 的 bug,journald 可能在进程退出时丢失最后几行日志。若 journalctl 显示 etcd 已停止却没有 fatal 或 panic 消息,请改用 sudo journalctl -f -t etcd2 获取完整日志。

16.4 Triage 章节索引页

triage/_index.md 仅包含 Hugo front matter,无任何正文内容。front matter 为:title: Triage、weight: 8000、description: Managing changes in etcd。它是 triage 章节(含 issues.md、PRs.md 两个子页面)的索引/导航页,本身不含实质性流程说明。

16.5 Issue 分诊:标签与时效规则

Purpose(目的):加速 issue 管理(Speed up issue management)。

标签体系:etcd 的 issue 列在 https://github.com/etcd-io/etcd/issues,通过 label 标识。例如被确认为 bug 的 issue 最终会被打上标签 area/bug。新建 issue 起初没有任何 label,通常由 etcd maintainer 与活跃贡献者根据判断补充。完整标签清单位于 https://github.com/kubernetes/kubernetes/labels。

预定检索入口(方便分诊):

入口 链接
Bugs https://github.com/etcd-io/etcd/labels/area%2Fbug
Help Wanted https://github.com/etcd-io/etcd/labels/Help%20Wanted
Longest untriaged issues https://github.com/etcd-io/etcd/issues?utf8=%E2%9C%93&q=is%3Aopen+sort%3Aupdated-asc
Issue Triage Meeting https://etcd.io/community/#community-meetings

Scope(适用范围):本文档是 etcd issue 分诊的主要文档。欢迎所有人参与管理 issue 与 PR,但文中所述工作与责任是以 etcd maintainer 与活跃贡献者为对象编写的。

处理流程与时限约定:

# 环节 规则 时效
1 Validate if an issue is a bug 确认 issue 确实是 bug。若不是,附上发现并关闭 trivial 的 issue;对非 trivial 的 issue,等待报告者回应、看是否有异议 报告者 30 天内未回复 → 关闭
2 Inactive issues 缺乏报告者足够信息的 issue 报告者 60 天内未提供信息 → 应关闭
3 Duplicate issues 若是重复问题,留言说明并引用原始 issue,然后关闭 —
4 Issues that don't belong to etcd 有些 issue 实际属于 etcd 所依赖的其他项目(例如 grpc 或 golang)。应要求报告者到对应的其他项目开 issue 除 maintainer 与报告者认为有必要保留以做跟踪外,一律关闭
5 Verify important labels are in place 确保 issue 带有其所属 area 的标签、已添加合适的 assignees、已确定 milestone;缺失则补上。若因权限不足无法打标签或无法确定正确标签,也无需强求,必要时联系 maintainer —
6 Poke issue owner if needed 若某 issue 已被某开发者认领但未创建 PR 30 天内没有创建 PR → 联系 owner,要求其提交 PR 或(如需)释放认领

16.6 PR 管理:标签与时效规则

Purpose(目的):加速 PR 管理(Speed up PR management)。

PR 列表与标签:etcd 的 PR 列在 https://github.com/etcd-io/etcd/pulls。一个 PR 可以带有各种 label、milestone、reviewer 等。完整标签清单位于 https://github.com/kubernetes/kubernetes/labels。

示例检索:

入口 链接
Open PRs for milestone etcd-v3.4 https://github.com/etcd-io/etcd/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aopen+milestone%3Aetcd-v3.4
PRs under investigation https://github.com/etcd-io/etcd/labels/Investigating

生命周期与时限约定:

# 环节 规则 时效
1 Handle inactive PRs review 意见未被处理 → 催 PR owner;PR owner 未回复 → 尽可能用一个新的 commit 更新该 PR;若做不到则关闭 review 意见 15 天内未处理 → 催;owner 90 天内未回复 → 自行更新;不活跃 PR 180 天后关闭
2 Poke reviewer if needed reviewer 一般会及时响应,但大家都很忙,请求 review 后若未快速响应请给些时间 10 天内没有回应 → 可通过在 PR 中留言、发邮件或在 Slack 上发消息联系
3 Verify important labels are in place 确保 PR 已添加合适的 reviewer、已确定 milestone;若缺失请补充。若无法确定正确的标签,留言让 maintainer 来处理 —

16.7 时效规则速查

对象 动作 天数
Issue 报告者未回复 → 关闭 30
Issue 报告者未提供信息 → 关闭(inactive) 60
Issue 已认领但未出 PR → 催 owner 30
PR review 意见未处理 → 催 owner 15
PR owner 未回复 → 尝试自行更新 90
PR 不活跃 → 关闭 180
PR reviewer 无回应 → 可主动联系 10

17. FAQ 全量

引自:FAQ(源文件 faq.md,约 18KB)

本章逐条收录官方 FAQ 全部问答。英文问题原文保留,中文为答案要点;涉及数字、命令、默认值的均按原文保留。

17.1 etcd, general


Q: What is etcd?

etcd 是一个一致的分布式键值存储。主要用作分布式系统中独立的协调服务;被设计用来存放可以完全放入内存的小量数据。


Q: How do you pronounce etcd?

读作 /ˈɛtsiːdiː/,含义是 "distributed etc directory"(分布式的 etc 目录)。


Q: Do clients have to send requests to the etcd leader?

不需要。[Raft][raft] 是基于 leader 的:leader 处理所有需要集群共识的客户端请求。但客户端不需要知道哪个节点是 leader——任何需要共识的请求若发给 follower,会被自动转发给 leader。不需要共识的请求(例如序列化读,serialized reads)可以由任意集群成员处理。


17.2 Configuration


Q: What is the difference between listen-<client,peer>-urls, advertise-client-urls or initial-advertise-peer-urls?

  • listen-client-urls 与 listen-peer-urls:指定 etcd server 本地绑定并接受 incoming 连接的地址。要在某端口上监听所有网卡,把监听 IP 指定为 0.0.0.0。
  • advertise-client-urls 与 initial-advertise-peer-urls:指定 etcd 客户端或其他 etcd 成员联系该 server 时应使用的地址。advertise 地址必须能被远端机器访问到。生产环境不要 advertise localhost 或 0.0.0.0 这类地址,因为它们从远端机器不可达。

Q: Why doesn't changing --listen-peer-urls or --initial-advertise-peer-urls update the advertised peer URLs in etcdctl member list?

成员的 advertised peer URLs 源自集群初次启动时的 --initial-advertise-peer-urls。启动后再改监听 peer URLs 或初始 advertise peers 不会影响已导出的 advertise peer URLs,因为这类变更必须经过 quorum,以避免成员配置出现 split brain。要更新成员的 peer URLs,请使用 etcdctl member update。


17.3 Deployment


Q: System requirements

  • 由于 etcd 会写盘,性能强烈依赖磁盘性能,因此强烈推荐 SSD。评估磁盘是否够快可用 [fio][fio] 等压测工具(示例见 [fio-blog-post])。
  • 为防止性能下降或无意中压垮 KV 存储,etcd 强制一个可配置的存储配额,默认 2GB。为避免 swapping 或内存耗尽,机器至少应有能覆盖该配额的 RAM。
  • 8GB 是常规环境建议的最大配额;配置值超过它时 etcd 会在启动时告警。
  • 在 CoreOS,etcd 集群通常部署在专用 CoreOS Container Linux 机器上,配置为双核处理器、2GB RAM、80GB SSD(至少)。
  • 注意:性能本质上取决于工作负载,生产部署前请务必测试。更多建议见 [hardware][hardware-setup]。
  • 最稳定的生产环境是 Linux 操作系统 + amd64 架构,详见 [supported platform][supported-platform]。

Q: Why an odd number of cluster members?

etcd 集群需要多数派(quorum)才能就集群状态更新达成一致。n 个成员的集群,quorum = (n/2)+1。对任意奇数规模集群,加一个节点总会增加达成 quorum 所需的节点数。虽然往奇数规模集群加节点看起来机器更多了,但容错性反而变差:能失败而不丢 quorum 的节点数完全相同,却多出了更多可能失败的节点。若集群已处于无法再容忍任何故障的状态,先加后删节点是危险的——因为一旦新节点注册进集群失败(例如地址配置错误),quorum 将永久丢失。


Q: What is maximum cluster size?

理论上没有硬限制。但 etcd 集群大概不应超过 7 个节点。与 etcd 类似、在 Google 内部广泛部署多年的 [Google Chubby lock service][chubby] 建议运行 5 个节点。一个 5 成员的 etcd 集群可容忍 2 个成员故障,多数场景已足够。更大的集群容错更好,但写性能会受损,因为数据必须复制到更多机器上。


Q: What is failure tolerance?

只要能形成成员 quorum,etcd 集群就能运行。

  • 瞬时网络故障(如分区)导致 quorum 丢失:网络恢复、quorum 重建后,etcd 会自动且安全地恢复;Raft 强制集群一致性。
  • 掉电:etcd 将 Raft log 持久化到磁盘,etcd 会重放日志到故障点并恢复参与集群。
  • 永久性硬件故障:可通过 [runtime reconfiguration][runtime reconfiguration] 将该节点移出集群。

建议集群成员数为奇数。奇数规模集群与偶数规模集群容忍的故障数相同但节点更少。对照表(Cluster Size / Majority / Failure Tolerance):

Cluster Size Majority Failure Tolerance
1 1 0
2 2 0
3 2 1
4 3 1
5 3 2
6 4 2
7 4 3
8 5 3
9 5 4

把成员数加到偶数不会带来额外容错;同理,网络分区期间,奇数成员能保证始终存在一个多数派分区可以继续运行,并在分区结束后成为事实来源(source of truth)。


Q: Does etcd work in cross-region or cross data center deployments?

跨区域部署提升容错性(成员处于不同的故障域),代价是跨数据中心边界带来更高的共识请求延迟。由于 etcd 依赖成员 quorum 达成共识,且至少多数派成员必须响应共识请求,跨数据中心的延迟会比较明显;此外集群数据必须复制到所有 peer,因此还有带宽成本。延迟变长时,默认 etcd 配置可能导致频繁的选举或心跳超时,高延迟部署请参考 [tuning] 调整超时参数。


17.4 Operation


Q: How to backup a etcd cluster?

etcdctl 提供 snapshot 命令用于创建备份,详见 [backup][backup]。


Q: Should I add a member before removing an unhealthy member?

替换 etcd 节点时,务必先移除成员、再添加其替代者。

  • etcd 采用基于 quorum 模型的分布式共识:(n/2)+1 个成员(多数派)同意后提案才能提交到集群。提案既包括键值更新,也包括成员变更。该模型完全避免了 split brain 不一致的可能,缺点是永久性 quorum 丢失是灾难性的。
  • 对成员变更而言:3 成员集群挂了 1 个,quorum 为 2、仍有 2 个存活,可继续推进。但往 3 成员集群加一个新成员会把 quorum 提升到 3(4 个成员需要 3 票才算多数)。由于 quorum 上升,这个额外成员在容错上什么都没换来——集群距离不可恢复仍然只差一个节点故障。
  • 此外新成员本身有风险:它可能配置错误或无法加入。那种情况下无法恢复 quorum——集群 2 个成员宕机、2 个成员存活,但需要 3 票才能变更成员来撤销这次失败的添加。etcd 默认会拒绝可能导致集群以这种方式宕掉的 member add 尝试。
  • 反过来,先从成员列表移除宕掉的节点:成员数变为 2,quorum 保持 2;随后添加新成员,quorum 仍稳定在 2。这样即使新节点起不来,也仍可通过剩余存活成员上的 quorum 把新成员移除。

Q: Why won't etcd accept my membership changes?

etcd 设置了 strict-reconfig-check,用于拒绝会导致 quorum 丢失的重配置请求。放弃 quorum 风险极高(尤其在集群已经不健康时)。虽然在已丢 quorum 的情况下很想关掉这项检查来加新成员,但这可能导致彻底的集群不一致;对很多应用来说问题会更糟("disk geometry corruption" 是最可怕的候选之一)。


Q: Why does etcd lose its leader from disk latency spikes?

这是有意设计的:磁盘延迟是 leader 存活(liveness)的一部分。假设 leader 花了一分钟才把一条 raft log 更新 fsync 到磁盘,而集群的 election timeout 是 1 秒:即使 leader 能在选举间隔内处理网络消息(如发送心跳),它实际上是不可用的,因为它无法提交任何新提案——它在等慢磁盘。若集群因磁盘延迟频繁丢 leader,尝试 [tuning][tuning] 磁盘设置或 etcd 时间参数。


Q: What does the etcd warning "request ignored (cluster ID mismatch)" mean?

每个新 etcd 集群都会基于初始集群配置和用户提供的唯一 initial-cluster-token 值生成新的 cluster ID。通过唯一的 cluster ID,etcd 得以免受跨集群交互的破坏(这种交互可能损坏集群)。该告警通常发生在:拆掉旧集群后,新集群复用了部分 peer 地址。若旧集群仍有 etcd 进程在运行,它会尝试联系新集群;新集群识别出 cluster ID 不匹配,于是忽略该请求并输出此告警。通常确保不同集群之间的 peer 地址互不相交即可消除该告警。


Q: What does "mvcc: database space exceeded" mean and how do I fix it?

etcd 的 [multi-version concurrency control][api-mvcc](MVCC)数据模型会保留 keyspace 的完整历史。若不周期性地压缩该历史(例如设置 --auto-compaction),etcd 最终会耗尽存储空间。空间不足时,etcd 会触发一个 space quota alarm 以保护集群不再接受写入;只要告警处于触发状态,etcd 就会以错误 mvcc: database space exceeded 响应写请求。

恢复步骤:

  1. [Compact][maintenance-compact] 压缩 etcd 的历史;
  2. [Defragment][maintenance-defragment] 对每一个 etcd endpoint 做碎片整理;
  3. [Disarm][maintenance-disarm] 解除告警。

💡 补充:这与第 6.5 节的「NOSPACE 恢复五步」是同一件事的两种表述——本页为三步版(compact → defrag → disarm),maintenance 页给出含取 revision 与验证的五步版。生产执行建议按第 6.5 节的五步走。


Q: What does the etcd warning "etcdserver/api/v3rpc: transport: http2Server.HandleStreams failed to read frame: read tcp 127.0.0.1:2379->127.0.0.1:43020: read: connection reset by peer" mean?

这是 gRPC 侧的告警:服务端在客户端流被提前关闭的情况下收到了 TCP RST 标志。例如客户端关闭了连接,而 gRPC server 还没处理完 TCP 队列中的所有 HTTP/2 帧。服务端侧可能丢了一些数据,但只要客户端连接已经关闭就没问题。只有 old versions of gRPC 会记录该日志;etcd >=v3.2.13 默认以 DEBUG 级别记录,因此只有开启 --log-level=debug 标志时可见。


17.5 Performance


Q: How should I benchmark etcd?

使用 [benchmark] 工具(https://github.com/etcd-io/etcd/tree/main/tools/benchmark)。当前的 [benchmark results][benchmark-result] 可供对比。


Q: What does the etcd warning "apply entries took too long" mean?

多数派成员同意提交请求后,每个 etcd server 会把请求 apply 到自己的数据存储并把结果落盘。即便使用慢速机械盘或虚拟化网络盘(如 Amazon 的 EBS、Google 的 PD),apply 一个请求通常也应少于 50 毫秒。若平均 apply 耗时超过 100 毫秒,etcd 就会告警 entries 应用过慢。

常见原因与排查:

  • 最常见是慢磁盘。磁盘可能与 etcd 之外的其他应用争用,或磁盘本身太慢(如共享的虚拟化磁盘)。为排除慢盘,监控 [backend_commit_duration_seconds][backend_commit_metrics](p99 应小于 25ms)以确认磁盘够快。磁盘太慢时,为 etcd 分配独占磁盘或换更快的盘通常可解决。
  • 第二常见是 CPU 饥饿。若机器 CPU 使用率监控显示负载很高,可能没有足够的算力给 etcd。把 etcd 迁到专用机器、用 cgroups 增强进程资源隔离、或把 etcd server 进程 renice 到更高优先级,通常可解决。
  • 昂贵的用户请求(访问过多 key,例如拉取整个 keyspace)也会导致长 apply 延迟。但每次请求访问少于几百个 key 应当始终是高性能的。
  • 若上述建议都不能消除告警,请 [open an issue][new_issue] 并附上详细的日志、监控、指标以及可选的负载信息。

Q: What does the etcd warning "failed to send out heartbeat on time" mean?

etcd 使用基于 leader 的共识协议做一致的数据复制与日志执行。集群成员选举出唯一 leader,其余为 follower。被选出的 leader 必须周期性向 follower 发送心跳以维持领导权;follower 若在一个选举间隔内没收到心跳就推断 leader 失效并触发选举。若 leader 没按时发出心跳但仍在运行,这次选举就是假(spurious)选举,很可能由资源不足引起。为捕捉这类软故障,leader 跳过两个心跳间隔时,etcd 会告警 failed to send a heartbeat on time。

常见原因与排查:

  • 最常见是慢磁盘。leader 在发送附带元数据的心跳前,可能需要先把元数据持久化到磁盘。同样可能是磁盘争用或磁盘本身太慢(如共享虚拟化磁盘)。监控 [wal_fsync_duration_seconds][wal_fsync_duration_seconds](p99 应小于 10ms)确认磁盘是否够快。磁盘太慢时,分配独占磁盘或换更快的盘通常可解决。也可用 [fio][fio] 判断磁盘是否够快(示例见 [fio-blog-post])。
  • 第二常见是 CPU 饥饿。处理方式同上:迁到专用机器、用 cgroups 增强进程资源隔离、或把 etcd server 进程 renice 到更高优先级。
  • 慢网络也会导致该问题。若 etcd 机器间的网络指标显示长延迟或高丢包率,可能网络容量不足,把 etcd 成员迁到不那么拥塞的网络通常可解决。但若 etcd 集群跨数据中心部署,成员间长延迟是预期内的:此时应把 heartbeat-interval 调整为大致匹配机器间的往返时间(RTT),并把 election-timeout 配置为至少 5 * heartbeat-interval。详见 [tuning documentation][tuning]。
  • 若上述建议都不能消除告警,请 [open an issue][new_issue] 并附上详细的日志、监控、指标以及可选的负载信息。

Q: What does the etcd warning "snapshotting is taking more than x seconds to finish ..." mean?

etcd 会发送其完整键值存储的快照,用于刷新慢 follower以及用于 [backups][backup]。快照传输过慢会拉高 MTTR;若集群正在以高吞吐摄入数据,慢 follower 可能因「还没收完一个快照就又需要新快照」而发生活锁(livelock)。为捕捉快照性能问题,当发送快照耗时超过 30 秒(thirty seconds)且超过 1Gbps 连接的预期传输时间时,etcd 会告警。


17.6 本页引用链接(原样保留)

[api-mvcc]: ../learning/api/#revisions
[backend_commit_metrics]: ../metrics/#disk
[backup]: ../op-guide/recovery/#snapshotting-the-keyspace
[benchmark]: https://github.com/etcd-io/etcd/tree/main/tools/benchmark
[benchmark-result]: ../op-guide/performance/
[chubby]: http://static.googleusercontent.com/media/research.google.com/en//archive/chubby-osdi06.pdf
[fio]: https://github.com/axboe/fio
[fio-blog-post]: https://web.archive.org/web/20240726111518/https://prog.world/is-storage-speed-suitable-for-etcd-ask-fio/
[hardware-setup]: ../op-guide/hardware/
[maintenance-compact]:  ../op-guide/maintenance/#history-compaction-v3-api-key-value-database
[maintenance-defragment]: ../op-guide/maintenance/#defragmentation
[maintenance-disarm]: https://github.com/etcd-io/etcd/blob/main/etcdctl/README.md#alarm-disarm
[new_issue]: https://github.com/etcd-io/etcd/issues/new
[raft]: https://raft.github.io/raft.pdf
[runtime reconfiguration]: ../op-guide/runtime-configuration/
[supported-platform]: ../op-guide/supported-platform/
[tuning]: ../tuning/
[wal_fsync_duration_seconds]: ../metrics/#disk

17.7 FAQ 关键数字速查

数字 含义 出处
2GB 默认存储配额(--quota-backend-bytes) System requirements
8GB 常规环境建议的最大配额,超过会在启动时告警 System requirements
双核 / 2GB RAM / 80GB SSD(至少) CoreOS 上常见的 etcd 专用机配置 System requirements
(n/2)+1 quorum 计算公式 Why an odd number of cluster members
7 集群规模的经验上限(「大概不应超过 7 个节点」) What is maximum cluster size
5 Chubby 建议的节点数;5 成员可容忍 2 个故障 What is maximum cluster size
50 毫秒 apply 一个请求的通常耗时上限(含慢盘场景) apply entries took too long
100 毫秒 平均 apply 耗时超过该值即告警 apply entries took too long
25ms backend_commit_duration_seconds 的 p99 建议上限 apply entries took too long
10ms wal_fsync_duration_seconds 的 p99 建议上限 failed to send out heartbeat on time
5 * heartbeat-interval election-timeout 的最小建议值 failed to send out heartbeat on time
30 秒 / 1Gbps 快照耗时告警的两个触发条件 snapshotting is taking more than x seconds

18. 附录 A:官方页面 → 本册章节映射表

本附录把《etcd-v3.7-抓取清单.md》纳入范围的 79 个官方页面逐条映射到本册章节,便于按图索骥回查原文。

状态说明:

状态 含义
已覆盖 该页内容已被本册对应章节采用(并标注了引用来源)
未覆盖 · 兄弟分册 该页属《01-核心概念篇》或《02-开发实战篇》范围,本册按定位不展开
未覆盖 · 本册范围外 该页属开发/内部主题,与运维运营无关
未覆盖 · 素材未提供 该页所在批次未纳入本册素材,本册未获取其内容

18.1 映射表(79 页)

# 源文件 页面标题 分类 批次 本册状态
1 _index.md v3.7 docs 概览/入门 batch-01 未覆盖 · 素材未提供(文档站导航页,属《01-核心概念篇》)
2 install.md Install 概览/入门 batch-01 未覆盖 · 素材未提供(安装页,建议并入《01-核心概念篇》)
3 quickstart.md Quickstart 概览/入门 batch-01 未覆盖 · 素材未提供(属《01-核心概念篇》)
4 demo.md Demo 概览/入门 batch-01 未覆盖 · 素材未提供(属《01-核心概念篇》)
5 dev-guide/local_cluster.md Set up a local cluster 开发指南 batch-01 未覆盖 · 素材未提供(本地集群搭建;本册第 2 章以静态/discovery/DNS 三种引导方式覆盖同类需求)
6 learning/why.md etcd versus other key-value stores 核心概念/设计 batch-01 未覆盖 · 兄弟分册(《01-核心概念篇》)
7 learning/glossary.md Glossary 核心概念/设计 batch-01 未覆盖 · 兄弟分册(《01-核心概念篇》)
8 learning/data_model.md Data model 核心概念/设计 batch-02 未覆盖 · 兄弟分册(《01-核心概念篇》)
9 learning/api.md etcd API 核心概念/设计 batch-02 未覆盖 · 兄弟分册(《01-核心概念篇》/《02-开发实战篇》)
10 learning/api_guarantees.md etcd API guarantees 核心概念/设计 batch-02 未覆盖 · 兄弟分册(《01-核心概念篇》)
11 learning/persistent-storage-files.md etcd persistent storage files 核心概念/设计 batch-02 未覆盖 · 兄弟分册(《01-核心概念篇》;本册第 6、7 章涉及 WAL/snap/db 文件但未展开该页)
12 dev-guide/limit.md System limits 开发指南 batch-02 未覆盖 · 素材未提供(系统限制,可与第 4 章硬件建议互为补充)
13 dev-guide/features.md etcd features 开发指南 batch-02 未覆盖 · 素材未提供
14 learning/design-client.md etcd client design 核心概念/设计 batch-03 未覆盖 · 兄弟分册(《01-核心概念篇》)
15 learning/design-learner.md etcd learner design 核心概念/设计 batch-03 未覆盖 · 素材未提供(learner 设计;本册第 5.6 节给出 learner 的运维操作)
16 learning/design-auth-v3.md learning/design-auth-v3.md 核心概念/设计 batch-03 未覆盖 · 兄弟分册(v3 认证设计;本册第 10 章覆盖认证与 RBAC 的使用层)
17 op-guide/runtime-reconf-design.md Design of runtime reconfiguration 运维 batch-03 未覆盖 · 素材未提供(重配置设计原理;本册第 5 章覆盖操作层)
18 dev-guide/interacting_v3.md Interacting with etcd 开发指南 batch-03 未覆盖 · 兄弟分册(《02-开发实战篇》)
19 op-guide/clustering.md Clustering Guide 运维 batch-04 已覆盖 · 第 2.2 / 2.3 / 2.4 节
20 op-guide/configuration.md Configuration options 运维 batch-04 已覆盖 · 第 3 章(107 条命令行 flag 全表)
21 op-guide/hardware.md Hardware recommendations 运维 batch-04 已覆盖 · 第 4.1 节
22 op-guide/supported-platform.md Supported platforms 运维 batch-04 已覆盖 · 第 4.2 节
23 feature-gates/index.md Feature Gates 概览/入门 batch-04 已覆盖 · 第 3.12 节
24 tuning.md Tuning 概览/入门 batch-04 已覆盖 · 第 4.3 节
25 op-guide/runtime-configuration.md Runtime reconfiguration 运维 batch-05 已覆盖 · 第 5.2 – 5.8 节
26 op-guide/maintenance.md Maintenance 运维 batch-05 已覆盖 · 第 6 章
27 op-guide/recovery.md Disaster recovery 运维 batch-05 已覆盖 · 第 7 章
28 op-guide/failures.md Failure modes 运维 batch-05 已覆盖 · 第 8 章
29 op-guide/data_corruption.md Data Corruption 运维 batch-05 已覆盖 · 第 9 章
30 op-guide/security.md Transport security model 运维 batch-06 已覆盖 · 第 10.1 – 10.11 节
31 op-guide/authentication/authentication.md Authentication 运维 batch-06 已覆盖 · 第 10.12 节(源文为 stub,已如实说明)
32 op-guide/authentication/rbac.md Role-based access control 运维 batch-06 已覆盖 · 第 10.13 – 10.17 节
33 op-guide/versioning.md Versioning 运维 batch-06 已覆盖 · 第 10.18 节
34 op-guide/monitoring.md Monitoring etcd 运维 batch-06 已覆盖 · 第 11.1 – 11.8 节
35 metrics/_index.md Metrics 概览/入门 batch-06 已覆盖 · 第 11.9 节
36 op-guide/container.md Run etcd clusters inside containers 运维 batch-07 已覆盖 · 第 2.5 节
37 op-guide/kubernetes.md Run etcd clusters as a Kubernetes StatefulSet 运维 batch-07 已覆盖 · 第 2.6 节
38 op-guide/gateway.md etcd gateway 运维 batch-07 已覆盖 · 第 13 章
39 op-guide/grpc_proxy.md gRPC proxy 运维 batch-07 已覆盖 · 第 13 章
40 dev-guide/grpc_naming.md gRPC naming and discovery 开发指南 batch-07 未覆盖 · 本册范围外(客户端命名与发现,属《02-开发实战篇》)
41 dev-guide/api_grpc_gateway.md Why gRPC gateway 开发指南 batch-07 未覆盖 · 本册范围外(本册第 10.16 节仅引用其「不能与 TLS CN 认证共用」的限制)
42 dev-guide/golang_embed_pkg.md Embedding etcd in a Go Application 开发指南 batch-07 未覆盖 · 本册范围外(Go 嵌入式用法,属《02-开发实战篇》)
43 dev-internal/modules.md Golang modules 开发者内部 batch-07 未覆盖 · 本册范围外(模块依赖管理)
44 tasks/developer/_index.md tasks/developer/_index.md 任务 batch-08 未覆盖 · 兄弟分册(《02-开发实战篇》导航页)
45 tasks/developer/reading-from-etcd.md Reading from etcd 任务 batch-08 未覆盖 · 兄弟分册(《02-开发实战篇》)
46 tasks/developer/writing-to-etcd.md Writing to etcd 任务 batch-08 未覆盖 · 兄弟分册(《02-开发实战篇》)
47 tasks/developer/how-to-get-key-by-prefix.md How to get keys by prefix 任务 batch-08 未覆盖 · 兄弟分册(《02-开发实战篇》)
48 tasks/developer/how-to-delete-keys.md How to delete keys 任务 batch-08 未覆盖 · 兄弟分册(《02-开发实战篇》)
49 tasks/developer/how-to-transactional-write.md How to make multiple writes in a transaction 任务 batch-08 未覆盖 · 兄弟分册(《02-开发实战篇》)
50 tasks/developer/how-to-watch-keys.md How to watch keys 任务 batch-08 未覆盖 · 兄弟分册(《02-开发实战篇》)
51 tasks/developer/how-to-create-lease.md How to create lease 任务 batch-08 未覆盖 · 兄弟分册(《02-开发实战篇》)
52 tasks/developer/how-to-create-locks.md How to create locks 任务 batch-08 未覆盖 · 兄弟分册(《02-开发实战篇》)
53 tasks/operator/_index.md tasks/operator/_index.md 任务 batch-09 已覆盖 · 第 5.11 节(源文仅 front matter,已如实说明)
54 tasks/operator/how-to-setup-cluster.md How to Set Up a Demo etcd Cluster 任务 batch-09 已覆盖 · 第 2.7 节
55 tasks/operator/how-to-check-cluster-status.md How to check Cluster status 任务 batch-09 已覆盖 · 第 5.1 节
56 tasks/operator/how-to-deal-with-membership.md How to Add and Remove Members 任务 batch-09 已覆盖 · 第 5.5.2 节
57 tasks/operator/how-to-save-database.md How to save the database 任务 batch-09 已覆盖 · 第 6.6 节(--endpoints 只能含一个 endpoint 的约束)
58 tasks/operator/how-to-conduct-elections.md How to conduct leader election in etcd cluster 任务 batch-09 已覆盖 · 第 5.9 节
59 dev-guide/discovery_protocol.md Discovery service protocol 开发指南 batch-09 已覆盖 · 第 5.10.3 节(v2 版)
60 dev-internal/discovery_protocol.md Discovery service protocol 开发者内部 batch-09 已覆盖 · 第 5.10.4 节(v3 版)
61 upgrades/upgrading-etcd.md Upgrading etcd clusters and applications 升级 batch-10 已覆盖 · 第 14.1 – 14.3 节
62 upgrades/upgrade_3_6.md Upgrade etcd from v3.5 to v3.6 升级 batch-10 已覆盖 · 第 14.5 节
63 upgrades/upgrade_3_7.md Upgrade etcd from v3.6 to v3.7 升级 batch-10 已覆盖 · 第 14.4 节
64 downgrades/downgrading-etcd.md Downgrading etcd clusters and applications 降级 batch-10 已覆盖 · 第 14.6 节
65 downgrades/downgrade_3_7.md Downgrade etcd from v3.7 to v3.6 降级 batch-10 已覆盖 · 第 14.7 节
66 downgrades/downgrade_3_6.md Downgrade etcd from v3.6 to v3.5 降级 batch-10 已覆盖 · 第 14.8 节
67 integrations.md Libraries and tools 概览/入门 batch-11 已覆盖 · 第 16.1 节
68 dev-internal/logging.md Logging conventions 开发者内部 batch-11 已覆盖 · 第 11.11 / 16.2 节
69 reporting_bugs.md Reporting bugs 概览/入门 batch-11 已覆盖 · 第 16.3 节
70 triage/_index.md Triage 社区流程 batch-11 已覆盖 · 第 16.4 节(源文仅 front matter,已如实说明)
71 triage/issues.md Issue triage guidelines 社区流程 batch-11 已覆盖 · 第 16.5 节
72 triage/PRs.md PR management 社区流程 batch-11 已覆盖 · 第 16.6 节
73 faq.md FAQ 概览/入门 batch-11 已覆盖 · 第 17 章(全量问答)
74 dev-guide/api_reference_v3.md API reference 开发指南 batch-12 未覆盖 · 兄弟分册(《02-开发实战篇》)
75 dev-guide/api_concurrency_reference_v3.md API reference: concurrency 开发指南 batch-12 未覆盖 · 兄弟分册(《02-开发实战篇》)
76 benchmarks/etcd-3-demo-benchmarks.md Benchmarking etcd v3 基准测试 batch-13 已覆盖 · 第 15.1 节
77 benchmarks/etcd-3-watch-memory-benchmark.md Watch Memory Usage Benchmark 基准测试 batch-13 已覆盖 · 第 15.2 节
78 benchmarks/etcd-storage-memory-benchmark.md Storage Memory Usage Benchmark 基准测试 batch-13 已覆盖 · 第 15.3 节
79 __metrics_latest__ etcd Metrics (latest) 监控 batch-13 已覆盖 · 第 12 章(123 条指标全表)

18.2 覆盖统计

统计项 数量 页号
纳入范围页面总数 79 1–79
本册已覆盖 46 19–39、53–73、76–79
未覆盖 · 兄弟分册(核心概念篇 / 开发实战篇) 20 6–11、14、16、18、44–52、74、75
未覆盖 · 本册范围外(开发/内部主题) 4 40–43
未覆盖 · 素材未提供(batch-01/02/03 未纳入本册素材) 9 1–5、12、13、15、17
合计校验 46 + 20 + 4 + 9 = 79 ✓

已覆盖的 47 页清单(按章节聚合):

章节 覆盖页号
第 2 章(部署) 19、36、37、54
第 3 章(配置全参) 20、23
第 4 章(硬件与平台) 21、22、24
第 5 章(成员与运行时管理) 25、53、55、56、58、59、60
第 6 章(日常维护) 26、57
第 7 章(灾难恢复) 27
第 8 章(故障模式) 28
第 9 章(数据损坏) 29
第 10 章(安全) 30、31、32、33
第 11 章(监控与告警) 34、35、68
第 12 章(指标全表) 79
第 13 章(代理层) 38、39
第 14 章(升级与降级) 61、62、63、64、65、66
第 15 章(性能与容量) 76、77、78
第 16 章(生态与社区) 67、68、69、70、71、72
第 17 章(FAQ) 73

说明:第 68 页(Logging conventions)同时服务于第 11.11 节与第 16.2 节,故在上表两个章节中均列出,但页数统计只计一次。


19. 附录 B:本次未覆盖页面清单

本附录说明按用户指定「只做 v3.7 主线」而未纳入的 11 个官方页面,以及本册内部未展开的页面类别。

19.1 范围声明(复述)

按用户指定「只做 v3.7 主线」,本手册未覆盖 v3.0–v3.5 的历史升级/降级步骤页与 v2.x benchmark 页(共 11 页),清单见《etcd-v3.7-抓取清单.md》。

19.2 v2.x benchmark 页(4 页)

# 源文件 页面标题 未覆盖原因
1 benchmarks/etcd-2-1-0-alpha-benchmarks.md Benchmarking etcd v2.1.0 历史版本内容,非 v3.7 主线;v2 存储引擎已于 v3.x 时代弃用,对 v3.7 容量规划无参考价值
2 benchmarks/etcd-2-2-0-benchmarks.md Benchmarking etcd v2.2.0 同上
3 benchmarks/etcd-2-2-0-rc-benchmarks.md Benchmarking etcd v2.2.0-rc 同上
4 benchmarks/etcd-2-2-0-rc-memory-benchmarks.md Benchmarking etcd v2.2.0-rc-memory 同上

💡 补充:本册第 15 章覆盖的是 v3 的三份 benchmark(读吞吐、watch 内存、storage 内存)。若确需 v2 时代的对照数据,请回官方站查阅上述 4 页——本册不转述、不估算其数字。

19.3 v3.0–v3.5 升级/降级步骤页(7 页)

# 源文件 页面标题 未覆盖原因
5 upgrades/upgrade_3_0.md Upgrade etcd from 2.3 to 3.0 历史版本内容,非 v3.7 主线;v3.7 集群无法直接执行该路径的升级
6 upgrades/upgrade_3_1.md Upgrade etcd from 3.0 to 3.1 同上
7 upgrades/upgrade_3_2.md Upgrade etcd from 3.1 to 3.2 同上
8 upgrades/upgrade_3_3.md Upgrade etcd from 3.2 to 3.3 同上
9 upgrades/upgrade_3_4.md Upgrade etcd from 3.3 to 3.4 同上
10 upgrades/upgrade_3_5.md Upgrade etcd from 3.4 to 3.5 同上;本册第 14 章只保留 v3.5 → v3.6 与 v3.6 → v3.7 两步
11 downgrades/downgrade_3_5.md Downgrade etcd from 3.5 to 3.4 同上;本册第 14 章只保留 v3.7 → v3.6 与 v3.6 → v3.5 两步

⚠️ 提示:若现网仍在运行 v3.4 或更早版本,不能按本册第 14 章直接跳到 v3.7。按第 14.1 节的总纲原则——一次只跨一个 minor,且先升到该 minor 的最新 patch——需先依次完成 upgrade_3_5、upgrade_3_6,再执行本册第 14.4 节的 v3.6 → v3.7。这些中间步骤请回官方站查阅上表对应页面。

19.4 本册内部「未展开」的三类页面

除上述 11 页外,纳入 79 页清单中还有 33 页未被本册采用,原因已在附录 A 逐条标注,此处汇总:

类别 数量 说明
属兄弟分册 20 《01-核心概念篇》(数据模型、API、设计、glossary 等)与《02-开发实战篇》(读写删 watch lease lock、API reference 等)
本册范围外(开发/内部主题) 4 grpc_naming、api_grpc_gateway、golang_embed_pkg、modules——均为客户端/嵌入式开发主题
素材未提供 9 batch-01/02/03 未纳入本册素材(install、quickstart、demo、local_cluster、limit、features、design-learner、runtime-reconf-design 等)

19.5 源文本身「内容缺失」的页面(如实记录)

以下页面已抓取,但源文本身几乎没有正文或有明显未完成的痕迹,本册已在对应章节如实标注,未做任何补全或推测:

页面 情况 本册处理位置
tasks/operator/_index.md 仅 front matter(5 行),无正文,纯导航 list page 第 5.11 节
triage/_index.md 仅 front matter,无正文 第 16.4 节
op-guide/authentication/authentication.md 仅 1254 字节,源文自称 "This is just a stub which needs to be filled…" 第 10.12 节
op-guide/authentication/_index.md 仅 front matter,无正文 第 10.12 节
metrics/_index.md 末尾 仅有 "Generated list of metrics" 标题,正文未提供指标清单 第 11.9.8 节(并由第 12 章补全 123 条)
dev-internal/logging.md 只覆盖日志级别分类,无结构化字段规范 / logger 用法 / 代码位置约定 第 11.11 / 16.2 节

19.6 全文数据一致性声明

本册所有数字(benchmark 结果、flag 默认值、配额、时间阈值、指标条数)均原样取自素材,未做任何换算或估算。素材内部存在冲突或笔误处,本册采用「并列保留 + 标注」而非擅自统一,共 6 处:

# 位置 冲突内容 本册处理
1 第 3.2 / 6.1 / 14 章 --snapshot-count 默认值:配置表 10000 vs maintenance「自 v3.2 改为 100000」vs upgrade_3_6「v3.6 由 100000 改为 10000」 并列呈现,注明以实际版本 etcd -h 为准
2 第 3.12 节 CompactHashCheck 与 InitialCorruptCheck 在 Configuration 页与 Feature Gates 页 Details 顺序相反 按各自页面原文保留
3 第 14 章 upgrade_3_6.md Step 5 漏 etcdctl 前缀 按原文保留并标注
4 第 14 章 downgrade_3_6.md Step 6 diff 疑漏 /etcd 按原文保留并标注
5 第 14 章 deprecated flag 称「共 16 个」但表列 18 行 按原文保留并标注
6 第 5.9 节 elect 参数 leader-name 与示例 [proposal] 命名不一致 并列保留,不擅自统一