336 KiB
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. 本册导读
- 2. 集群部署:三种引导方式与容器 / Kubernetes
- 3. 配置全参(flag / 环境变量 / 默认值)
- 4. 硬件、支持平台与系统调优
- 5. 成员与运行时管理
- 6. 日常维护:压缩、碎片整理、配额、快照
- 7. 灾难恢复
- 8. 故障模式
- 9. 数据损坏
- 10. 安全:TLS、认证与 RBAC
- 11. 监控与告警
- 12. 指标全表(123 条)
- 13. 代理层:etcd gateway 与 gRPC proxy
- 14. 升级与降级
- 15. 性能与容量:三个 benchmark
- 16. 生态与社区
- 17. FAQ 全量
- 18. 附录 A:官方页面 → 本册章节映射表
- 19. 附录 B:本次未覆盖页面清单
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 |
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 与上面完全一致。
三个必知的坑
initial-cluster中的 URL 是 advertised peer URLs,必须与对应节点上的--initial-advertise-peer-urls一致。- 强烈建议为测试用途反复创建的集群设置唯一的
--initial-cluster-token,以便 etcd 生成唯一的 cluster ID 与 member ID,避免跨集群交互导致数据损坏。 - 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 后按以下顺序查询:
_etcd-server-ssl._tcp.example.com_etcd-server._tcp.example.com
若找到 _etcd-server-ssl._tcp.example.com,etcd 将尝试通过 TLS 引导。
帮助客户端发现集群的 SRV 记录查询顺序:
_etcd-client._tcp.example.com_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)
本页演示用 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 部署
用 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.localetcd-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),但在配置文件中只能填纳秒整数(Gotime.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 | 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. 硬件、支持平台与系统调优
开发与测试环境下 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 或高性能虚拟化块设备)。
- 磁盘带宽:需求不高,但带宽越大故障成员追赶越快。通常 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 运行时重配置:设计约束
- 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 条):
- 新成员必须用
--initial-cluster-state existing(而非new),表示加入一个已存在的集群。 - 源文
[WARNING]:新成员若复用同一块磁盘空间,务必先删除旧成员的 data directory(即--data-dir=data.etcd对应目录),否则旧数据会导致加入失败或集群损坏。 --initial-cluster列表要更新为新的成员组合(node-1、node-2、node-4),且--initial-cluster-token必须与现有集群一致(my-etcd-token-1),否则会被视为另一个集群。- 每个 member 操作前都要
export ETCDCTL_API=3。 - 本页未出现
--learner与--member-id两个 flag——--learner只在op-guide/runtime-configuration.md与learning/design-learner.md中出现,不属于本页内容。
5.6 Learner:先追赶、再投票(自 v3.4 起)
推荐新成员先以 learner 加入、追赶完成后再提升,这样更安全并减少集群停机时间,共三步:
- 通过 gRPC members API 或
etcdctl member add --learner添加 learner。 - 用新的集群配置启动新成员(与前述完全相同)。
- 通过 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
文档明确说明:该命令保证同一时刻只有一个客户端能成为 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 共同约束(两版一致)
- Discovery service protocol 仅用于集群 bootstrap 阶段,不能用于运行时重配置(runtime reconfiguration)或集群监控(cluster monitoring)。
- 该协议用一个新的 discovery token 引导出一个唯一的 etcd 集群;一个 discovery token 只能代表一个 etcd 集群。
- 一旦基于某个 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」两步。
- Check discovery status:
GET /${UUID},请求该 UUID 的值即可查看该 token 的注册状态,包括已注册的机器。 - 开源仓库:https://github.com/coreos/discovery.etcd.io,可用于自建 discovery service。
适用范围说明:本文示例对应自托管的 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 phaseruntime.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. 灾难恢复
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. 故障模式
大规模部署中机器故障很常见:单台因软硬件故障失效;多台因断电或网络问题同时失效;多种故障也可能并发,几乎不可能穷举所有情况。本节对故障种类做编目并说明 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. 数据损坏
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
步骤:
- 停止 etcd 实例。
- 备份 etcd 数据目录。
- 把 etcd 数据目录中的
snap子目录移出去。 - 以
--initial-cluster-state=existing启动etcd,并在--initial-cluster中列出集群成员。
预期结果:该 etcd 成员会从 leader 下载最新的快照。
注:原文步骤编号为 1、2、3、6,本册按顺序整理为 1–4。
方式二:Replace member
步骤:
- 停止 etcd 实例。
- 备份 etcd 数据目录。
- 删除数据目录。
- 执行
etcdctl member remove把该成员从集群中移除。 - 执行
etcdctl member add把它重新加回。 - 以
--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 端点全部暴露。
规则:
- 客户端连接为 HTTPS 安全连接时,允许任意主机名;
- 连接不安全且
"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
要点解读(严格基于上述命令的注释):
- 第一步固定为
export ETCDCTL_API=3,随后所有操作通过etcdctl --endpoints=${ENDPOINTS}发起。 - 建
root角色 →role get root查看 → 建root用户 →user grant-role root root(把 root 角色授予 root 用户)→user get root查看。 - 再建普通角色
role0,用role grant-permission role0 readwrite foo授予对 keyfoo的 READWRITE 权限;建普通用户user0并用user grant-role user0 role0授予角色。 auth enable之后所有客户端请求都走认证。- 未带
--user的get foo被拒绝(permission denied),源文注释明确原因:请求未发起认证请求,因此 user name 为空。 --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与/healthHTTP 端点运行在独立的 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 |
💡 补充(三点使用提醒):
- 任务描述称
metrics-latest.txt为「124 行指标表」,但实际有效指标行为 123 行,本册按实际内容全部收录,未补写任何不存在的指标。- 不存在
etcd_raft_*命名空间——v3.7 latest 指标表中没有该组,raft 相关状态请用etcd_server_has_leader、etcd_server_leader_changes_seen_total、etcd_server_proposals_*等代替。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
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 要点(升级场景):
- 升级前先创建并下载 snapshot 备份,可用于把集群恢复到升级前状态。
- 若升级中出问题,应先定位并解决根因。
- 若集群仍处于混合版本状态(至少还有一个 member 是旧版本),可以把二进制/镜像换回旧版本,或直接用 snapshot 恢复集群。此时集群仍按旧版本集群运行,可以回滚而无需走正式的降级流程。
- 一旦所有 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-middlewarev1 logging / tags 库迁移到 v2 interceptors(PR #20420); - OpenTelemetry gRPC interceptors 升级到
otelgrpcv0.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 要点(降级场景):
- 降级前创建并下载 snapshot 备份,可恢复到降级前状态;出问题先定位并解决根因。
- 若已执行
etcdctl downgrade enable、集群仍处混合版本状态(至少一个 member 还是 v3.7),可以运行etcdctl downgrade cancel取消进行中的降级,并用原来的 v3.7 二进制重启所有已降级的 member。 - 一旦所有 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.mdStep 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 文档内部疑点(素材已注明,本册未做修正)
upgrade_3_6.mdStep 5 的命令写作endpoint health --endpoints=...,疑似漏写etcdctl前缀(Step 4 中为etcdctl endpoint health --endpoints=...)。upgrade_3_6.md的 "Upgrade requirements" 要求「低于 v3.5 请先升级到 v3.5」,但链接指向../upgrade_3_4/(3.4→3.5 指南),文字与链接版本存在表述不一致。upgrade_3_6.md要求 3.5 member 至少为3.5.32或更高,但同页示例输出中版本为3.5.18,示例与要求不自洽。upgrade_3_6.md与downgrade_3_6.md中 InitialCorruptCheck 的旧写法不一致:升级页为--experimental-initial-corrupt-check=true,降级页为--experimental-enable-initial-corrupt-check=true。downgrade_3_6.md的 Step 2 仅有说明文字,未给出 snapshot 备份命令(对比downgrade_3_7.mdStep 2 给出了etcdctl --endpoints=localhost:2379 snapshot save backup.db)。downgrade_3_6.mdStep 6 的 diff 中旧行写作-etcd-3.6/bin --name s1 \,疑漏/etcd。- 「先 learner 升级」「只有所有 member 都运行 v3.x 才能降级」「降级后 revision 变化」等说法,在这 6 个源文件中均未出现;升级流程未规定 learner 顺序,降级前提条件仅写明「一次只降一个 minor 版本 +
etcdctl downgrade validate校验通过 + 集群健康」。
15. 性能与容量:三个 benchmark
本章原样保留三个官方 benchmark 页的全部数字,不做换算、不补数据。三个页面在素材中均未给出具体的命令行调用与参数,本册如实说明。
15.1 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 包,而非服务端框架。
💡 补充(容量规划可推出的结论):
- 读的并发饱和点约在 64 客户端:从 64 → 256 客户端,QPS 不再增长(16623 → 16622),但 p90 延迟从 6.1ms 恶化到 21.7ms(约 3.6 倍)。据此,客户端连接池不是开得越大越好,超过饱和点只会换延迟。
- 单次 put 之后读吞吐下降约 16%–20%(16623 → 13582;16622 → 13262),且高并发下延迟恶化更明显(21.7ms → 47.5ms,约 2.2 倍)。写多读少的集群要按「put 后读」这一档做容量基线。
- 该页环境标注为
etcd version 2.2.0而标题为 v3,疑为文档沿用了早期版本的环境描述(素材已注明),因此这组数字宜作为量级参考,不宜作为 v3.7 的 SLA 基线。
15.2 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 有三个消耗物理内存的组件:
grpc.Conn:维护真实的 TCP 连接与 gRPC 连接状态,每个消耗 O(10kb),其上可挂载多条 watch stream。- watch stream:独立的 HTTP2 连接,每个再消耗 O(10kb),多条 watching 可共享同一条 stream。
- 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)。
环境 / 硬件配置
- etcd version:git head https://github.com/etcd-io/etcd/commit/185097ffaa627b909007e772c175e8fefac17af3
- GCE
n1-standard-2machine type- 7.5 GB 内存
- 2x CPUs
命令 / 测量方法
- 总体内存占用以 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
测试目的
量化 etcd storage 的内存占用,分为两部分:
- In-memory index(进程内 B-tree 索引)的内存开销,目标是求出公式中的
c1(key 元数据开销)与c2(版本元数据开销),并理解 store 的内存硬上限。 - 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 管理,本文档不展开。
环境 / 硬件配置
- etcd version:git head https://github.com/etcd-io/etcd/commit/776e9fb7be7eee5e6b58ab977c8887b4fe4d48db
- GCE
n1-standard-2machine type- 7.5 GB 内存
- 2x CPUs
命令 / 测量方法
- 通过 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 由操作系统管理,本文不做详细分析。
💡 补充(容量规划可推出的结论):
- 索引内存 ≈ N × (120B + key_size) + N × 版本数 × (30B + 指针大小)。以 1M key、64B key、5 个版本估算:1M × 184B + 1M × 5 × 38B ≈ 184MB + 190MB ≈ 374MB,与实测 432MB 同量级——可用此式做上机前的内存预估。
- key 的长度比 value 的长度更「贵」:key 从 64B → 256B(+192B)使 1M×1version 索引内存从 218MB → 409MB(+191MB,几乎 1:1 线性);而 value 从 256B → 1KB 主要只推高 page cache,进程内影响小。设计 key 命名时应尽量短。
- 历史版本数是隐形内存杀手:版本数 1 → 5 让 1M×64B 从 218MB 涨到 432MB(约 2 倍)。这正是要配好 auto-compaction(第 6 章)的原因——压缩后旧版本被回收,索引内存随之下降。
- 综合 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 地址必须能被远端机器访问到。生产环境不要 advertiselocalhost或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 响应写请求。
恢复步骤:
- [Compact][maintenance-compact] 压缩 etcd 的历史;
- [Defragment][maintenance-defragment] 对每一个 etcd endpoint 做碎片整理;
- [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] 命名不一致 |
并列保留,不擅自统一 |