Article
1. etcd 基础概念
1.1 什么是 etcd
| 概念名称 | 说明 | 注意事项 |
|---|---|---|
| etcd | etcd 是一个高可用、强一致性的分布式键值存储系统,最初由 CoreOS 开发,现为 CNCF(云原生计算基金会)托管项目。它使用 Raft 共识算法保证数据一致性,常用于服务发现、配置共享和分布式协调。 | etcd 并非通用数据库,不支持复杂查询或 SQL;其设计目标是低延迟、高可靠的小规模元数据存储。 |
| 键值存储(Key-Value Store) | etcd 将数据以字节串形式的 key-value 对存储,key 有序(按字典序),支持范围查询。 | key 和 value 均为原始字节,但通常以 UTF-8 字符串形式使用;value 大小建议不超过 1MB。 |
| 分布式一致性 | etcd 通过 Raft 协议确保多个节点间的数据强一致性,即使部分节点故障仍可正常提供服务。 | 集群节点数应为奇数(如 3、5、7),以避免脑裂;多数节点存活才能写入成功。 |
1.2 etcd 的核心特性
| 特性名称 | 说明 | 注意事项 |
|---|---|---|
| 强一致性 | 所有读写操作均通过 Raft 日志达成共识,保证线性一致性(linearizable)。 | 默认读操作也是强一致的(需经过 Raft 日志),可通过 --consistency=serializable 提升读性能但牺牲一致性。 |
| 高可用 | 支持多节点集群部署,自动选主,容忍 (N-1)/2 个节点故障(N 为奇数)。 | 单节点仅用于开发测试;生产环境至少 3 节点。 |
| Watch 机制 | 客户端可监听 key 或 key 前缀的变化,服务端推送变更事件,实现高效通知。 | Watch 事件可能因网络中断丢失,需配合 revision 回溯;长时间 Watch 应处理 ErrCompacted 错误。 |
| Lease(租约) | 可为 key 绑定一个带 TTL 的租约,租约过期后自动删除关联 key,适用于临时状态管理。 | 租约需主动 keep-alive 续约,否则到期自动失效;未绑定 key 的租约会自动回收。 |
| 事务(Txn) | 支持基于条件的原子事务(Compare-And-Swap 风格),可组合多个操作。 | 事务中所有操作要么全部成功,要么全部失败;条件支持 version、value、create_revision 等比较。 |
| TLS 加密与认证 | 支持客户端/服务端双向 TLS 加密通信,并内置基于角色的访问控制(RBAC)。 | 启用 auth 后,所有操作需认证;首次启用需保留 root 用户凭证以防锁死。 |
1.3 etcd 的典型应用场景
| 场景名称 | 说明 | 注意事项 |
|---|---|---|
| Kubernetes 元数据存储 | Kubernetes 使用 etcd 存储集群所有状态信息(如 Pod、Service、Node 等),是其控制平面的核心依赖。 | Kubernetes 1.20+ 推荐使用独立 etcd 集群;需定期备份 snapshot 防止数据丢失。 |
| 服务发现 | 微服务启动时向 etcd 注册自身地址,消费者通过 Watch 或 Get 获取服务实例列表。 | 服务注册应绑定 Lease,避免实例宕机后残留无效记录。 |
| 分布式锁 | 利用 etcd 的事务和 Lease 机制实现公平、可重入的分布式锁。 | 锁 key 通常包含唯一标识(如 client ID);需处理租约续期失败导致的锁提前释放。 |
| 配置中心 | 应用从 etcd 读取动态配置,通过 Watch 实时感知配置变更并热更新。 | 敏感配置应启用 RBAC 和 TLS;避免频繁大 value 更新影响性能。 |
| 选主(Leader Election) | 多个候选者通过事务竞争写入同一个 key,成功者成为 leader,绑定 Lease 维持任期。 | Leader 需定期续约;其他节点 Watch 该 key 以感知 leadership 变更。 |
2. etcd 安装与启动
2.1 单节点安装(Linux/macOS/Windows)
| 步骤名称 | 操作细节 | 注意事项 |
|---|---|---|
| Linux 安装(二进制) |
| 确保系统架构匹配(amd64/arm64);生产环境建议使用 systemd 管理服务。 |
| macOS 安装(Homebrew) | 执行命令:brew install etcd验证: etcd --version | Homebrew 安装路径通常为 /opt/homebrew/bin/etcd(Apple Silicon)或 /usr/local/bin/etcd(Intel)。 |
| Windows 安装 |
| Windows 不支持 fork,仅用于开发测试;不建议用于生产集群。 |
| 启动单节点 | 在终端执行:etcd | 首次启动会生成默认数据目录 default.etcd;若端口被占用需指定 --listen-client-urls 等参数。 |
默认监听 http://localhost:2379(客户端)和 http://localhost:2380(peer) |
2.2 集群部署方式
| 部署方式 | 操作细节 | 注意事项 |
|---|---|---|
| 静态集群(Static) | 每个节点预先知道所有成员地址;启动命令示例(3 节点):etcd --name node1 --initial-advertise-peer-urls http://10.0.0.1:2380 --listen-peer-urls http://0.0.0.0:2380 --listen-client-urls http://0.0.0.0:2379 --advertise-client-urls http://10.0.0.1:2379 --initial-cluster node1=http://10.0.0.1:2380,node2=http://10.0.0.2:2380,node3=http://10.0.0.3:2380 --initial-cluster-state new | 所有节点必须使用相同的 --initial-cluster;--initial-cluster-state 首次为 new,恢复时用 existing。 |
| DNS 发现(DNS SRV) | 配置 DNS SRV 记录 _etcd-server._tcp.example.com;启动时指定:--discovery-srv example.com | 需 DNS 支持 SRV 记录;适用于云环境或已有 DNS 基础设施。 |
| etcd Discovery(已弃用) | 旧版通过公共发现服务(如 https://discovery.etcd.io)注册节点 | 自 v3.4 起官方不再维护公共 discovery 服务,不推荐使用。 |
| Kubernetes Operator | 使用 etcd-operator 或社区 Helm Chart 部署 | 适合在 K8s 内部运行 etcd;自动处理扩缩容、备份、TLS 等。 |
2.3 启动参数详解
| 参数名称 | 语法 | 用途 | 注意事项 |
|---|---|---|---|
--name | --name <string> | 当前节点在集群中的唯一标识名 | 必须在 --initial-cluster 中一致;默认为 default。 |
--data-dir | --data-dir <path> | 指定数据存储目录 | 默认为 default.etcd;应使用高性能 SSD 并定期备份。 |
--listen-client-urls | --listen-client-urls <url1,url2,...> | 监听客户端请求的 URL 列表 | 必须包含 http:// 或 https://;生产环境建议绑定内网 IP 而非 0.0.0.0。 |
--advertise-client-urls | --advertise-client-urls <url1,url2,...> | 向客户端和其他节点宣告的客户端 URL | 必须可被其他节点访问;通常与 --listen-client-urls 一致或为公网地址。 |
--listen-peer-urls | --listen-peer-urls <url1,url2,...> | 监听 peer 通信的 URL | 默认 http://localhost:2380;集群部署必须设为可跨节点访问地址。 |
--initial-cluster | --initial-cluster <name1=url1,name2=url2,...> | 初始集群成员列表 | 所有节点必须完全一致;格式为 <name>=<peer-url>。 |
--initial-cluster-state | --initial-cluster-state <new|existing> | 集群初始状态 | 首次创建用 new;从快照恢复或扩容用 existing。 |
--auto-compaction-retention | --auto-compaction-retention <duration> | 自动压缩历史版本(如 1h、72h) | 减少磁盘占用;单位支持 h(小时)、m(分钟);压缩后无法回溯旧 revision。 |
--quota-backend-bytes | --quota-backend-bytes <int> | 设置后端存储配额(字节) | 默认 2GB;建议不超过 8GB;超限后只读。 |
2.4 环境变量配置
| 环境变量 | 对应参数 | 说明 | 注意事项 |
|---|---|---|---|
ETCD_NAME | --name | 节点名称 | 优先级低于命令行参数。 |
ETCD_DATA_DIR | --data-dir | 数据目录路径 | 默认 default.etcd。 |
ETCD_LISTEN_CLIENT_URLS | --listen-client-urls | 客户端监听地址 | 多个 URL 用逗号分隔,无空格。 |
ETCD_ADVERTISE_CLIENT_URLS | --advertise-client-urls | 宣告的客户端地址 | 必须可被客户端访问。 |
ETCD_LISTEN_PEER_URLS | --listen-peer-urls | Peer 监听地址 | 用于节点间通信。 |
ETCD_INITIAL_CLUSTER | --initial-cluster | 初始集群成员 | 格式:node1=http://a:2380,node2=http://b:2380。 |
ETCD_INITIAL_CLUSTER_STATE | --initial-cluster-state | 初始集群状态 | new 或 existing。 |
ETCD_AUTO_COMPACTION_RETENTION | --auto-compaction-retention | 自动压缩保留时长 | 如 1h 表示保留最近 1 小时的历史。 |
ETCD_QUOTA_BACKEND_BYTES | --quota-backend-bytes | 存储配额(字节) | 如 8589934592 表示 8GB。 |
注意: 环境变量命名规则为将参数名中的
-替换为_并转为大写,前缀ETCD_。
3. etcdctl 命令行工具基础
3.1 etcdctl 安装与验证
| 步骤名称 | 操作细节 | 注意事项 |
|---|---|---|
| Linux 安装(二进制) |
| 确保 etcdctl 与 etcd 服务端版本兼容(建议主版本一致);v3 API 为默认。 |
| macOS 安装(Homebrew) | 执行:brew install etcd(已包含 etcdctl) | Homebrew 安装的 etcdctl 默认启用 v3 API。 |
验证:etcdctl version | ||
| Windows 安装 |
| 仅用于开发测试;部分功能(如 TLS)在 Windows 上可能受限。 |
| 验证连接 | 执行:etcdctl endpoint health | 若服务未运行或端口错误,会报 connection refused;确保 etcd 已启动。 |
预期输出:127.0.0.1:2379 is healthy |
3.2 连接 etcd 服务(端点、认证、TLS)
| 连接方式 | 操作细节 | 注意事项 |
|---|---|---|
| 指定端点(Endpoint) | 使用 --endpoints 参数:etcdctl --endpoints=http://10.0.0.1:2379,http://10.0.0.2:2379 put key value | 多个 endpoint 用逗号分隔,无空格;默认为 127.0.0.1:2379。 |
| 启用身份认证 |
| 首次启用 auth 后,必须使用 root 用户操作;忘记密码将无法恢复。 |
| TLS 加密通信(客户端) | 使用以下参数:--cacert=/path/ca.pem、--cert=/path/client.pem、--key=/path/client-key.pem示例: etcdctl --endpoints=https://10.0.0.1:2379 --cacert=ca.pem --cert=client.pem --key=client-key.pem get key | 服务端必须配置了 TLS;证书需由同一 CA 签发;路径必须为绝对路径或正确相对路径。 |
| 跳过 TLS 验证(仅测试) | 添加 --insecure-transport=false --insecure-skip-tls-verify | 禁止在生产环境使用;存在中间人攻击风险。 |
| 设置 API 版本 | 显式指定 v3:ETCDCTL_API=3 etcdctl ... | etcdctl v3.4+ 默认使用 v3 API;旧脚本可能依赖 v2,需显式设置 ETCDCTL_API=2(已弃用)。 |
3.3 常用全局选项说明
| 选项名称 | 语法 | 用途 | 注意事项 |
|---|---|---|---|
--endpoints | --endpoints=<url1,url2,...> | 指定 etcd 服务端地址列表 | 默认 127.0.0.1:2379;支持多个用于高可用。 |
--user | --user=<username:password> | 指定认证用户名和密码 | 启用 auth 后必需;密码含特殊字符需引号包裹。 |
--cacert | --cacert=<file> | 指定 CA 证书路径 | 用于验证服务端证书;必须与服务端 CA 一致。 |
--cert | --cert=<file> | 指定客户端证书路径 | 用于 mTLS 认证;需与 --key 配对使用。 |
--key | --key=<file> | 指定客户端私钥路径 | 私钥文件权限应设为 600(Linux/macOS)。 |
--insecure-skip-tls-verify | --insecure-skip-tls-verify | 跳过服务端证书验证 | 仅用于测试;生产环境必须关闭。 |
--insecure-transport | --insecure-transport=false | 禁用 TLS(强制明文) | 默认为 true(启用 TLS);若服务端未配 TLS,可设为 false。 |
--command-timeout | --command-timeout=<duration> | 设置命令超时时间(如 30s) | 防止长时间阻塞;默认通常为 30 秒。 |
--dial-timeout | --dial-timeout=<duration> | 设置连接超时时间 | 默认 2 秒;网络延迟高时可适当增加。 |
--debug | --debug | 启用调试日志 | 输出 gRPC 请求详情,便于排查连接问题。 |
提示: 所有全局选项必须放在子命令(如
put、get)之前,否则无效。
4. 键值操作(KV API)
4.1 写入键值(put)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| put | etcdctl put <key> <value> | 向 etcd 写入或更新一个键值对 | etcdctl put /config/db_host "192.168.1.10" | key 和 value 均为字符串;value 可包含空格(需引号包裹);默认覆盖已有值。 |
| put with lease | etcdctl put <key> <value> --lease=<lease_id> | 将 key 绑定到指定租约,实现自动过期 | etcdctl put session/123 "active" --lease=694d7a7e9b5c3f1a | 必须先通过 lease grant 获取有效 lease ID;租约过期后 key 自动删除。 |
| put with prev-kv | etcdctl put <key> <value> --prev-kv | 写入时返回旧值(用于调试或审计) | etcdctl put counter 5 --prev-kv | 仅在响应中显示旧值,不影响写入逻辑;性能略低。 |
4.2 读取键值(get)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| get | etcdctl get <key> | 获取指定 key 的值 | etcdctl get /config/db_host | 若 key 不存在,无输出且退出码为 0(非错误)。 |
| get with key-only | etcdctl get <key> --keys-only | 仅返回 key,不返回 value | etcdctl get / --prefix --keys-only | 适用于快速列出 key 结构。 |
| get with print-value-only | etcdctl get <key> --print-value-only | 仅输出 value,便于脚本解析 | etcdctl get /config/db_port --print-value-only | 输出无换行符,可直接赋值给 shell 变量。 |
| get with revision | etcdctl get <key> --rev=<revision> | 读取指定历史版本的值 | etcdctl get mykey --rev=10 | 需确保该 revision 未被 compaction 清除;否则报错 ErrCompacted。 |
| get all | etcdctl get "" --from-key 或 etcdctl get / --prefix | 获取所有 key-value | etcdctl get "" --from-key | "" --from-key 表示从空 key 开始到最大 key;等价于全表扫描。 |
4.3 删除键值(del)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| del | etcdctl del <key> | 删除单个 key | etcdctl del /config/temp_flag | 返回删除的 key 数量(整数);若 key 不存在,返回 0。 |
| del with prefix | etcdctl del <prefix> --prefix | 删除所有以 prefix 开头的 key | etcdctl del session/ --prefix | 危险操作!建议先用 get --prefix 确认范围。 |
| del with range | etcdctl del <start> <end> | 删除 [start, end) 范围内的 key | etcdctl del a z | 左闭右开区间;按字典序比较。 |
| del return-deleted | etcdctl del <key> --return-deleted | 删除并返回被删的 key-value | etcdctl del mykey --return-deleted | 用于审计或回滚场景;性能略低。 |
4.4 范围查询与前缀查询
| 查询方式 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 前缀查询 | etcdctl get <prefix> --prefix | 获取所有以 prefix 开头的 key | etcdctl get /services/web/ --prefix | 最常用范围查询方式;prefix 本身也作为 key 被包含。 |
| 范围查询(start-end) | etcdctl get <start> <end> | 获取 [start, end) 区间内所有 key | etcdctl get a d | 按字典序匹配;例如 a, aa, b 会被返回,但 d 不会。 |
| 从某 key 到末尾 | etcdctl get <start> --from-key | 获取从 start 到最大 key 的所有项 | etcdctl get /z --from-key | 等价于 get /z "\xFF"(因 \xFF 是最大字节)。 |
| 限制返回数量 | etcdctl get <key> --limit=<n> | 限制最多返回 n 个结果 | etcdctl get / --prefix --limit=10 | 与 --prefix 或范围查询组合使用。 |
| 排序输出 | etcdctl get <key> --order=<ascend|descend> | 按 key 升序或降序返回 | etcdctl get / --prefix --order=descend | 默认升序;降序可用于获取最新注册的服务。 |
4.5 键值过期与租约(lease)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| lease grant | etcdctl lease grant <ttl_in_seconds> | 创建一个 TTL 租约,返回 lease ID | etcdctl lease grant 60 → 输出 Lease 694d7a7e9b5c3f1a granted | TTL 单位为秒;最小为 1 秒;租约独立于 key 存在。 |
| lease keep-alive | etcdctl lease keep-alive <lease_id> | 持续续约租约(每秒一次) | etcdctl lease keep-alive 694d7a7e9b5c3f1a | 通常由程序后台执行;命令行模式会持续输出续约心跳。 |
| lease revoke | etcdctl lease revoke <lease_id> | 立即撤销租约,删除所有绑定 key | etcdctl lease revoke 694d7a7e9b5c3f1a | 撤销后无法恢复;关联 key 立即消失。 |
| lease timetolive | etcdctl lease timetolive <lease_id> [--keys] | 查看租约剩余时间及绑定的 key | etcdctl lease timetolive 694d7a7e9b5c3f1a --keys | --keys 显示所有绑定 key;若已过期,返回 -1。 |
| put with lease | etcdctl put <key> <value> --lease=<lease_id> | 将 key 绑定到租约 | etcdctl put lock/leader "node1" --lease=694d7a7e9b5c3f1a | 一个 key 只能绑定一个 lease;重新 put 会解除旧 lease 绑定。 |
4.6 原子性事务(txn)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| txn interactive | etcdctl txn | 交互式输入事务条件和操作 | 见下方多行示例 | 适合手动测试;脚本中应使用非交互式。 |
| txn non-interactive | etcdctl txn <<< $'compare\n<cmp>\nsuccess\n<op>\nfailure\n<op>' | 非交互式执行事务(shell heredoc) | etcdctl txn <<< $'compare\nmykey version = "1"\nsuccess\nput mykey "updated"\nfailure\nget mykey' | 条件(compare)、成功分支(success)、失败分支(failure)三部分必须完整。 |
| 支持的比较类型 | key version = "N" / key value = "val" / key create_revision = "N" / key mod_revision = "N" | 比较 key 的元数据或值 | mykey value = "old" | 所有比较值必须为字符串;数字也需加引号。 |
| 支持的操作 | get key / put key val / del key | 在 success/failure 分支中执行的操作 | put counter $(( $(etcdctl get counter --print-value-only) + 1)) | 操作数量不限;但整个事务大小受 gRPC 限制(默认 1.5MB)。 |
| 事务原子性 | 整个 txn 要么全部成功,要么全部失败 | 保证并发安全 | — | 事务执行期间其他客户端无法看到中间状态;适用于计数器、锁等场景。 |
事务示例说明: 上述非交互式示例含义:若
mykey的 version 为 1,则将其更新为"updated";否则读取当前值。
5. 监听与通知(Watch)
5.1 监听单个 key 变化
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| watch single key | etcdctl watch <key> | 持续监听指定 key 的所有变更事件(PUT/DELETE) | etcdctl watch /config/db_host | 默认阻塞运行,持续输出事件;可配合 --rev 回溯历史。 |
| watch with progress notify | etcdctl watch <key> --progress-notify | 定期接收进度通知(即使无事件) | etcdctl watch mykey --progress-notify | 用于检测连接活跃性;每 10 分钟发送一次 PROGRESS 事件。 |
| watch with prev-kv | etcdctl watch <key> --prev-kv | 事件中包含变更前的 key-value 值 | etcdctl watch counter --prev-kv | 便于计算差值(如计数器变化量);增加网络开销。 |
| watch once | etcdctl watch <key> --rev=<N> --exit-on-no-event | 仅监听一次事件后退出(需配合 revision) | etcdctl watch mykey --rev=10 --exit-on-no-event | 适用于脚本中等待特定事件;若无事件则立即退出。 |
5.2 监听前缀或范围变化
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| watch prefix | etcdctl watch <prefix> --prefix | 监听所有以 prefix 开头的 key 变更 | etcdctl watch /services/web/ --prefix | 最常用模式;适用于服务注册发现场景。 |
| watch range | etcdctl watch <start> <end> | 监听 [start, end) 范围内 key 的变更 | etcdctl watch a d | 左闭右开区间;按字典序匹配(如 a, aa, b 会被监听,d 不会)。 |
| watch from key to end | etcdctl watch <start> --from-key | 监听从 start 到最大 key 的所有变更 | etcdctl watch /z --from-key | 等价于 watch /z "\xFF";适用于监听”尾部”新增 key。 |
| watch with filters | etcdctl watch <key> --prefix --filter-delete --filter-put | 过滤事件类型(仅 PUT 或仅 DELETE) | etcdctl watch session/ --prefix --filter-delete | 可组合使用;默认监听所有事件类型。 |
5.3 Watch 的历史事件回溯
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| watch with revision | etcdctl watch <key> --rev=<revision> | 从指定 revision 开始回放历史事件 | etcdctl watch mykey --rev=5 | revision 必须 ≥ 当前 compacted revision;否则报 ErrCompacted。 |
| 获取当前 revision | etcdctl get <key> --rev=0 或 etcdctl endpoint status -w json | 查询当前集群或 key 的最新 revision | etcdctl get "" --prefix --limit=1(查看任意 key 的 mod_revision) | endpoint status 输出中 header.revision 为全局最新 revision。 |
| 处理 compacted error | 手动处理 etcdserver: mvcc: required revision has been compacted | 当指定 revision 被压缩时,需从最新状态开始监听 | 1. 获取当前 revision 2. 重新执行 get 获取当前值 3. 从新 revision 开始 watch | 应用需实现容错逻辑;定期 compaction 是正常运维行为。 |
| 结合 get + watch 实现可靠监听 | 先 get --rev=N 获取快照,再 watch --rev=N+1 监听后续变更 | 避免事件丢失,实现 exactly-once 语义 | current_rev=$(etcdctl get mykey --rev=0 --print-revision-only)etcdctl watch mykey --rev=$((current_rev+1)) | --print-revision-only 需 etcdctl v3.4+;旧版本需解析 JSON 输出。 |
关键概念说明:
- Revision:etcd 中每个写操作都会递增一个全局单调递增的 revision,用于标识数据版本。
- Compaction:为节省空间,etcd 可删除旧 revision 数据;通过
--auto-compaction-retention自动触发。- Watch 保证:从有效 revision 开始的 watch 可保证不丢事件(at-least-once),但应用需去重。
6. 租约(Lease)与 TTL
6.1 创建租约(lease grant)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| lease grant | etcdctl lease grant <ttl_in_seconds> | 创建一个具有指定 TTL(秒)的租约,返回唯一 Lease ID | etcdctl lease grant 30输出: Lease 694d7a7e9b5c3f1a granted | TTL 最小为 1 秒;租约独立存在,即使未绑定任何 key 也会在 TTL 后自动过期。 |
| lease grant with large TTL | etcdctl lease grant 86400 | 创建长生命周期租约(如 24 小时) | etcdctl lease grant 86400 | 适用于长期会话或 leader 选举;需确保客户端能持续续约。 |
| 解析 Lease ID | 从 lease grant 输出中提取十六进制 ID | 用于后续 put --lease 或 lease keep-alive | LEASE_ID=$(etcdctl lease grant 60 | awk '{print $2}') | — |
6.2 续约(lease keep-alive)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| lease keep-alive | etcdctl lease keep-alive <lease_id> | 向 etcd 发送心跳,延长租约 TTL | etcdctl lease keep-alive 694d7a7e9b5c3f1a | 默认每秒发送一次续约请求;命令行模式会持续输出响应(如 Lease 694d... keepalived, TTL=30)。 |
| lease keep-alive once | etcdctl lease keep-alive <lease_id> --once | 单次续约(不持续监听) | etcdctl lease keep-alive 694d7a7e9b5c3f1a --once | 适用于脚本中手动触发续约;成功返回新 TTL,失败返回错误。 |
| 自动续约(程序实现) | 应用程序定期调用 gRPC LeaseKeepAlive | 在生产服务中维持租约有效 | 需在代码中实现定时器和重连逻辑 | 命令行 keep-alive 不适合生产;应使用 SDK(如 Go client 的 KeepAlive)。 |
| 续约失败处理 | 当网络中断或 etcd 不可用时,续约失败 | 租约将在原 TTL 结束后过期 | 应用需监听租约状态并释放资源(如释放分布式锁) | 客户端应设置合理的 TTL(通常 ≥ 2× 心跳间隔)以容忍短暂网络抖动。 |
6.3 查询与撤销租约(lease list / revoke)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| lease list | etcdctl lease list | 列出当前所有活跃租约 ID | etcdctl lease list输出: 694d7a7e9b5c3f1a | 仅显示未过期且未被撤销的租约;已绑定 key 的租约也会列出。 |
| lease timetolive | etcdctl lease timetolive <lease_id> | 查询租约剩余 TTL(秒) | etcdctl lease timetolive 694d7a7e9b5c3f1a输出: Lease 694d... has TTL "25" | 若租约已过期,返回 TTL “-1”;若不存在,报错 lease not found。 |
| lease timetolive —keys | etcdctl lease timetolive <lease_id> --keys | 查询租约剩余 TTL 及其绑定的所有 key | etcdctl lease timetolive 694d7a7e9b5c3f1a --keys | 输出包含 key 列表;便于调试 key 生命周期。 |
| lease revoke | etcdctl lease revoke <lease_id> | 立即撤销租约,删除所有绑定 key | etcdctl lease revoke 694d7a7e9b5c3f1a | 操作不可逆;撤销后无法再续约;常用于主动释放资源。 |
| 批量撤销(脚本) | 结合 lease list 循环撤销 | 清理所有租约(谨慎使用) | etcdctl lease list | xargs -I {} etcdctl lease revoke {} | — |
6.4 租约与 key 绑定
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| put with lease | etcdctl put <key> <value> --lease=<lease_id> | 将 key-value 绑定到指定租约 | etcdctl put session/user123 "active" --lease=694d7a7e9b5c3f1a | 一个 key 只能绑定一个 lease;重新 put 会解除旧 lease 并绑定新 lease(或无 lease)。 |
| 多 key 绑定同一 lease | 对多个 key 执行 put --lease=<same_id> | 实现一组 key 的统一过期 | etcdctl put lock/leader "nodeA" --lease=$Letcdctl put lock/ts "1700000000" --lease=$L | 常用于分布式锁:主键 + 元数据共享同一租约。 |
| key 自动删除 | 租约过期或被撤销后,绑定 key 自动删除 | 无需手动清理临时状态 | 观察:etcdctl get session/user123(TTL 过后返回空) | 删除是原子的;Watch 会收到 DELETE 事件。 |
| 无 lease 的 key | 直接 put key value(不带 --lease) | 创建永久 key(除非手动删除) | etcdctl put config/version "v1.0" | 永久 key 不受租约机制影响;适用于静态配置。 |
| 替换 lease 绑定 | 先 put new_key new_val --lease=L2,再 del old_key | 安全迁移 key 到新租约 | 适用于租约即将过期但会话仍需保持的场景 | 避免直接覆盖,防止中间状态出现 key 无 lease。 |
7. 集群管理与运维
7.1 查看集群成员(member list)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| member list | etcdctl member list | 列出当前集群所有成员及其状态 | etcdctl member list输出示例: 8e9e05c52164694d, started, node1, http://10.0.0.1:2380, http://10.0.0.1:2379 | 输出字段依次为:Member ID、状态(started/unstarted)、name、peer URL、client URL。 |
| member list -w table | etcdctl member list -w table | 以表格格式美化输出 | etcdctl member list -w table | 更易读;适用于交互式查看。 |
| member list -w json | etcdctl member list -w json | 以 JSON 格式输出(便于脚本解析) | etcdctl member list -w json | 包含完整元数据,如 isLearner(是否为 learner 节点)。 |
| 查看单个成员详情 | 结合 grep 或 jq 过滤 | 定位特定节点信息 | etcdctl member list | grep node2 | — |
7.2 添加/移除集群节点(member add / remove)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| member add | etcdctl member add <name> --peer-urls=<url> | 向集群添加新成员(需在现有节点执行) | etcdctl member add node3 --peer-urls=http://10.0.0.3:2380 | 返回新成员的初始启动参数(含 --initial-cluster);新节点必须使用该配置启动。 |
| 新节点启动命令 | 使用 member add 返回的提示命令 | 启动待加入的 etcd 实例 | etcd --name=node3 --initial-advertise-peer-urls=http://10.0.0.3:2380 --listen-peer-urls=http://0.0.0.0:2380 --listen-client-urls=http://0.0.0.0:2379 --advertise-client-urls=http://10.0.0.3:2379 --initial-cluster=node1=http://10.0.0.1:2380,node2=http://10.0.0.2:2380,node3=http://10.0.0.3:2380 --initial-cluster-state=existing | --initial-cluster-state 必须为 existing;否则会新建集群。 |
| member remove | etcdctl member remove <member_id> | 从集群中移除指定成员 | etcdctl member remove 8e9e05c52164694d | 只能移除处于 unstarted 或故障状态的节点;若移除 leader,会触发重新选主。 |
| 移除后清理 | 手动删除被移除节点的数据目录 | 避免残留数据干扰 | rm -rf /var/lib/etcd(在被移除节点上执行) | 若节点未来要重新加入,必须清空 data-dir,否则可能因旧状态导致启动失败。 |
| 安全扩容原则 | 先 add,再启动新节点;先停止节点,再 remove | 避免集群不可用 | 扩容时确保多数节点在线;缩容前确认节点已下线 | 集群规模应保持奇数(3/5/7);偶数节点不提升容错能力。 |
7.3 集群健康检查(endpoint health)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| endpoint health | etcdctl endpoint health | 检查所有 endpoints 的健康状态 | etcdctl endpoint health输出: 127.0.0.1:2379 is healthy | 默认检查 --endpoints 中所有地址;返回非 0 退出码表示有节点不健康。 |
| 指定多个 endpoint | etcdctl --endpoints=a:2379,b:2379 endpoint health | 批量检查多节点 | etcdctl --endpoints=http://10.0.0.1:2379,http://10.0.0.2:2379 endpoint health | 用于验证整个集群可用性。 |
| endpoint status | etcdctl endpoint status -w table | 查看各节点详细状态(DB 大小、leader、revision 等) | etcdctl endpoint status -w table | 关键指标:DB SIZE(是否接近 quota)、IS LEADER、RAFT TERM。 |
| endpoint hashkv | etcdctl endpoint hashkv | 计算各节点数据哈希值,验证一致性 | etcdctl endpoint hashkv | 所有节点哈希值应相同;若不同,说明数据不一致(严重故障)。 |
| 健康检查原理 | 向 /health 发送 HTTP GET 请求 | etcd 内置健康端点 | curl http://127.0.0.1:2379/health | 返回 {"health":"true"} 表示正常;可用于 Prometheus 或 K8s liveness probe。 |
7.4 数据快照与恢复(snapshot save / restore)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| snapshot save | etcdctl snapshot save <file_path> | 保存当前集群数据快照到文件 | etcdctl snapshot save backup.db | 必须连接到 healthy 节点;快照包含完整 KV 数据,不含 raft 日志。 |
| snapshot status | etcdctl snapshot status <file_path> -w table | 查看快照元信息(revision、hash、size) | etcdctl snapshot status backup.db -w table | 用于验证快照完整性;TOTAL KEY 表示 key 总数。 |
| snapshot restore | etcdctl snapshot restore <file> --data-dir=<new_dir> --name=<name> --initial-cluster=<cfg> --initial-cluster-token=<token> | 从快照恢复数据到新数据目录 | etcdctl snapshot restore backup.db --data-dir=restored.etcd --name=node1 --initial-cluster=node1=http://10.0.0.1:2380 --initial-cluster-token=etcd-cluster-1 | 不会直接启动 etcd,仅生成新 data-dir;恢复后需用新目录启动新集群。 |
| 恢复为单节点集群 | 使用 --initial-cluster 仅含一个节点 | 用于灾难恢复或迁移 | etcdctl snapshot restore backup.db --data-dir=restored --name=newnode --initial-cluster=newnode=http://127.0.0.1:2380 | 恢复后的集群 Member ID 会改变,不能直接加入原集群。 |
| 自动备份策略 | 定时任务(cron)执行 snapshot save | 实现定期备份 | 0 2 * * * etcdctl --endpoints=localhost:2379 snapshot save /backups/etcd-$(date +\%Y\%m\%d).db | 快照文件可压缩归档;建议保留多个历史版本;备份期间不影响读写。 |
| 快照 vs WAL | 快照是内存 B+Tree 的持久化,WAL 是 Raft 日志 | 快照用于快速恢复,WAL 用于崩溃恢复 | 正常运行时,etcd 自动管理 WAL 和 snapshot | 用户只需关心 snapshot save 用于跨集群恢复或长期归档。 |
8. 安全与权限控制
8.1 启用身份认证(auth enable)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| auth enable | etcdctl auth enable | 启用 etcd 内置基于角色的访问控制(RBAC) | 先执行 etcdctl user add root再执行 etcdctl auth enable | 必须先创建至少一个用户(通常是 root),否则启用后无法操作;启用后所有请求需认证。 |
| auth disable | etcdctl --user=root:<pwd> auth disable | 禁用身份认证(需已认证用户执行) | etcdctl --user=root:secret auth disable | 仅用于紧急恢复;生产环境应保持启用。 |
| 验证认证状态 | etcdctl --user=root:<pwd> get /anykey | 测试是否强制认证 | 若未提供 --user 且 auth 已启用,返回 permission denied | 启用 auth 后,etcdctl 必须携带 --user 参数或设置 ETCDCTL_USER 环境变量。 |
| 默认 root 权限 | root 用户自动拥有 root 角色 | 可管理用户、角色和所有 key | etcdctl --user=root:pwd role list | root 角色默认拥有对所有 key 的读写权限(--prefix "")。 |
8.2 用户管理(user add / passwd / grant-role)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| user add | etcdctl user add <username> | 创建新用户(交互式输入密码) | etcdctl user add app_user | 密码输入时不回显;也可通过 --interactive=false 配合管道传入(不推荐明文)。 |
| user add non-interactive | echo "password" | etcdctl user add app_user --interactive=false | 脚本中非交互式创建用户 | printf "mypass\nmypass\n" | etcdctl user add app_user --interactive=false | — |
| user passwd | etcdctl user passwd <username> | 修改用户密码 | etcdctl user passwd app_user | 需已认证用户执行;root 可改任意用户密码。 |
| user grant-role | etcdctl user grant-role <username> <role> | 为用户分配角色 | etcdctl user grant-role app_user reader | 一个用户可拥有多个角色;权限取并集。 |
| user revoke-role | etcdctl user revoke-role <username> <role> | 撤销用户的角色 | etcdctl user revoke-role app_user writer | 撤销后立即生效;若用户无任何角色,则无任何数据访问权限。 |
| user list | etcdctl user list | 列出所有用户 | etcdctl user list | 输出用户名列表,不包含密码或角色信息。 |
| user delete | etcdctl user delete <username> | 删除用户 | etcdctl user delete app_user | 删除后该用户所有会话失效;无法删除当前认证用户自身。 |
8.3 角色与权限分配(role add / grant-permission)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| role add | etcdctl role add <role_name> | 创建新角色 | etcdctl role add config_reader | 角色初始无任何权限;需后续通过 grant-permission 授权。 |
| role grant-permission | etcdctl role grant-permission [read|write|readwrite] [--prefix] | 为角色授予 key 的访问权限 | etcdctl role grant-permission config_reader read /config/ --prefix | 权限类型:read(读)、write(写)、readwrite(读写);--prefix 表示前缀授权。 |
| 精确 key 权限 | 不使用 --prefix | 仅授权单个 key | etcdctl role grant-permission db_admin readwrite /db/password | 适用于敏感配置项的细粒度控制。 |
| 全局权限 | --prefix "" | 授予对所有 key 的权限 | etcdctl role grant-permission admin readwrite --prefix "" | 等同于 root 权限;谨慎分配。 |
| role revoke-permission | etcdctl role revoke-permission <role> <key> [--prefix] | 撤销角色对 key 的权限 | etcdctl role revoke-permission config_reader /config/debug --prefix | 撤销后立即生效;不影响其他 key 权限。 |
| role get | etcdctl role get <role_name> | 查看角色的权限详情 | etcdctl role get config_reader | 输出格式:KV range: [/config/, /config0) 表示前缀 /config/。 |
| role delete | etcdctl role delete <role_name> | 删除角色 | etcdctl role delete temp_role | 删除前应确保无用户绑定该角色;否则用户将失去对应权限。 |
8.4 访问控制示例
| 场景名称 | 操作细节 | 代码示例 | 注意事项 |
|---|---|---|---|
| 只读配置用户 | 创建仅能读取 /config/ 下所有 key 的用户 | etcdctl role add config_readeretcdctl role grant-permission config_reader read /config/ --prefixetcdctl user add readeretcdctl user grant-role reader config_reader | 应用程序使用 reader 账号连接,无法写入或读取其他路径。 |
| 服务注册专用账号 | 允许服务写入自身 key,但不能读写其他服务 | etcdctl role add service_registraretcdctl role grant-permission service_registrar write /services/web/ --prefixetcdctl user add web_svcetcdctl user grant-role web_svc service_registrar | 服务只能写入 /services/web/<instance_id>,无法读取(防止信息泄露)。 |
| 审计只读账号 | 创建可读全量数据但不可写的审计账号 | etcdctl role add auditoretcdctl role grant-permission auditor read --prefix ""etcdctl user add audit_useretcdctl user grant-role audit_user auditor | 适用于监控或备份工具;禁止写权限保障数据安全。 |
| 最小权限原则验证 | 使用非 root 账号测试权限边界 | etcdctl --user=reader:pwd put /config/new_key "test" → 应报 permission denied | 所有账号应遵循最小权限;避免直接使用 root 账号运行应用。 |
| TLS + RBAC 联合认证 | 同时启用客户端证书和用户名密码 | 1. 启用 TLS(服务端配置 --client-cert-auth)2. 启用 auth 3. 客户端同时提供证书和 --user | etcd 支持双重认证;但 RBAC 仍基于用户名,证书仅用于传输安全。 |
9. etcd 性能与调优
9.1 读写性能基准测试(benchmark)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| benchmark put | etcdctl benchmark put --total=<N> --key-size=<K> --val-size=<V> --endpoints=<url> | 测试写入吞吐与延迟 | etcdctl benchmark put --total=10000 --key-size=64 --val-size=256 --endpoints=http://127.0.0.1:2379 | 默认并发数为 1;可通过 --conns 和 --clients 调整连接与客户端数。 |
| benchmark get | etcdctl benchmark get --total=<N> --key-size=<K> --endpoints=<url> | 测试读取性能(需先写入数据) | etcdctl benchmark get --total=10000 --key-size=64 --endpoints=http://127.0.0.1:2379 | 读测试依赖已有 key;建议先用 put 预热数据。 |
| benchmark range | etcdctl benchmark range --total=<N> --prefix=/test/ --endpoints=<url> | 测试范围查询性能 | etcdctl benchmark range --total=1000 --prefix=/bench/ --endpoints=http://127.0.0.1:2379 | 模拟前缀查询场景;结果反映大规模 key 下的扫描效率。 |
| benchmark watch | etcdctl benchmark watch --total=<N> --key-size=<K> --endpoints=<url> | 测试 Watch 事件推送性能 | etcdctl benchmark watch --total=1000 --key-size=32 --endpoints=http://127.0.0.1:2379 | 启动后台 watcher 并统计事件接收延迟;适用于服务发现场景评估。 |
| 常用参数说明 | — | 控制压测负载特征 | --conns=10 --clients=50 --total=50000 | 建议逐步增加负载,观察 CPU、内存、磁盘 IO 变化;避免压垮测试节点。 |
压测参数详解:
--conns:连接数--clients:并发客户端数--total:总操作数--key-size/--val-size:key/value 大小(字节)
9.2 日志与快照调优参数
| 参数名称 | 语法(启动参数) | 用途 | 推荐值 | 注意事项 |
|---|---|---|---|---|
--auto-compaction-retention | --auto-compaction-retention=1h | 自动压缩历史版本,释放空间 | 1h(小时)或 72h(根据业务回溯需求) | 单位支持 h(小时)、m(分钟);压缩后无法通过旧 revision 读取历史数据。 |
--auto-compaction-mode | --auto-compaction-mode=revision 或 periodic | 压缩模式:按 revision 数或时间周期 | periodic(默认) | revision 模式需指定 revision 数量(如 --auto-compaction-retention=1000)。 |
--snapshot-count | --snapshot-count=100000 | 每执行 N 次 raft 提交后触发一次快照 | 100000(默认) | 值越小,快照越频繁,恢复更快但写放大增加;值过大则 WAL 文件膨胀。 |
--quota-backend-bytes | --quota-backend-bytes=8589934592 | 设置后端存储配额(字节) | 8GB(最大推荐值) | 默认 2GB;超过后集群变为只读;必须配合监控告警。 |
--heartbeat-interval | --heartbeat-interval=100 | Leader 向 Follower 发送心跳间隔(ms) | 100(默认) | 网络稳定可适当增大(如 200ms)以降低开销;不稳定网络应减小(如 50ms)。 |
--election-timeout | --election-timeout=1000 | Follower 超时未收到心跳后触发选举(ms) | 1000(默认) | 应为 heartbeat 的 5–10 倍;过小易误触发选举,过大导致故障恢复慢。 |
--max-txn-ops | --max-txn-ops=2048 | 单个事务允许的最大操作数 | 128(默认)或 2048(高吞吐场景) | 增大可提升批量操作效率,但占用更多内存;gRPC 消息大小也需同步调整。 |
--max-request-bytes | --max-request-bytes=1572864 | 单个请求最大字节数(默认 1.5MB) | 10485760(10MB,若需大 value) | 必须 ≤ gRPC server 的 MaxRecvMsgSize;同时调整客户端 --max-call-send-message-size。 |
9.3 网络与磁盘 I/O 优化建议
| 优化方向 | 操作细节 | 推荐配置 | 注意事项 |
|---|---|---|---|
| 磁盘选择 | 使用高性能 SSD(NVMe 优先) | IOPS ≥ 10,000;延迟 < 1ms | etcd 对 fsync 延迟极度敏感;HDD 或共享云盘会导致性能骤降。 |
| 独立磁盘 | 将 --data-dir 挂载到专用磁盘 | 不与系统盘、日志盘共用 | 避免其他进程 I/O 干扰;确保 ext4 或 xfs 文件系统并启用 noatime。 |
| 文件系统调优 | 挂载选项:noatime,nobarrier,data=ordered | mount -o noatime /dev/sdb1 /var/lib/etcd | nobarrier 需配合带电池缓存的 RAID;否则可能丢数据。 |
| 网络隔离 | peer 通信(2380)与 client 通信(2379)使用不同网卡或 VLAN | 内网万兆网络;MTU ≥ 1500 | 避免 client 流量影响 Raft 共识;跨 AZ 部署时注意网络延迟(建议 < 5ms)。 |
| 内核参数调优 | 调整 TCP 缓冲区和连接跟踪 | net.core.somaxconn=32768net.ipv4.tcp_max_syn_backlog=8192 | 高并发场景防止连接拒绝;需在所有 etcd 节点生效。 |
| 禁用 swap | 防止内存交换导致延迟毛刺 | swapoff -a + 注释 /etc/fstab 中 swap 行 | etcd 是延迟敏感型服务;swap 会引发秒级停顿。 |
| 资源限制 | 使用 systemd 或容器限制 CPU/内存 | Memory: ≥ 4GB;CPU: ≥ 2 核 | 避免 OOM killer 杀死 etcd;建议预留 50% 内存余量。 |
| 监控指标 | 关键 Prometheus 指标 | 告警阈值:fsync > 10ms;RTT > 50ms;DB size > 80% quota | 结合 Grafana dashboard 实时观测;提前扩容或调优。 |
关键 Prometheus 指标:
etcd_disk_wal_fsync_duration_seconds— fsync 延迟etcd_network_peer_round_trip_time_seconds— 网络 RTTetcd_mvcc_db_total_size_in_bytes— 数据库大小
10. 常见问题与故障排查
10.1 节点无法加入集群
| 问题现象 | 排查步骤 | 解决方案 | 注意事项 |
|---|---|---|---|
新节点启动后报 member unknown 或 cluster ID mismatch | 1. 检查 --initial-cluster 是否与现有集群完全一致2. 确认 --initial-cluster-state=existing3. 查看新节点 data-dir 是否为空 | 1. 使用 etcdctl member add 获取正确初始配置2. 清空新节点 data-dir 3. 使用返回的完整命令启动 | --initial-cluster 中的 name 和 peer URL 必须与 member add 时一致;旧 data-dir 会导致 ID 冲突。 |
节点反复重启,日志显示 raft: failed to process message | 1. 检查网络连通性(telnet 2380 端口)2. 确认防火墙未阻断 peer 通信 3. 检查主机名解析是否一致 | 1. 开放 TCP 2380 端口 2. 使用 IP 地址代替主机名(避免 DNS 不一致) 3. 统一所有节点 /etc/hosts | Raft 依赖稳定网络;跨云或跨 VPC 部署需确保双向通信。 |
加入后状态为 unstarted 且不参与选举 | 1. 检查新节点是否成功连接到 majority 2. 查看 leader 日志是否有 added member 记录 | 1. 确保新节点能访问至少 (N/2)+1 个现有节点 2. 手动执行 etcdctl member update <id> --peer-urls=<correct_url> 修正地址 | unstarted 表示未完成初始化;通常因网络隔离或配置错误导致。 |
| 集群已启用 auth,新节点无法同步 | 1. 检查新节点是否配置了正确的 client TLS 证书(若启用) 2. 确认新节点未尝试认证(etcd 节点间通信不走 RBAC) | 1. 节点间通信使用 peer URL(2380),不受 auth 影响 2. 确保 peer 证书 CN 或 SAN 匹配 | auth 仅作用于 client API(2379),不影响 peer 通信(2380)。 |
10.2 Watch 丢失事件
| 问题现象 | 排查步骤 | 解决方案 | 注意事项 |
|---|---|---|---|
| Watch 进程运行中,但部分 key 变更未收到通知 | 1. 检查 etcd 日志是否有 compacted 错误2. 确认 Watch 启动时的 revision 是否有效 3. 查看客户端是否处理了 ErrCompacted | 1. 启用 --auto-compaction-retention 时保留足够历史(如 72h)2. 实现可靠监听模式:先 get --rev=N 获取快照,再 watch --rev=N+1 | Watch 从指定 revision 开始;若该 revision 已被压缩,则丢失此前所有事件。 |
| 网络闪断后 Watch 未自动恢复 | 1. 检查客户端是否重连 2. 确认是否启用了 --progress-notify 监测连接活性 | 1. 使用 SDK(如 Go client)的自动重连 Watcher 2. 命令行 Watch 需配合 supervisord 重启 | etcdctl watch 命令行工具无自动重连;生产环境应使用编程 SDK。 |
| 大量并发 Watch 导致事件延迟或丢失 | 1. 检查 etcd CPU 和内存使用率 2. 查看 etcd_network_client_grpc_sent_bytes_total 指标 | 1. 增加 etcd 资源配额 2. 合并多个 Watch 为前缀 Watch(减少连接数) 3. 升级到更高版本(v3.5+ 性能优化) | 单 etcd 节点建议 Watch 连接数 ≤ 10,000;超限会导致 gRPC 流控或 OOM。 |
| Watch 返回重复事件 | 1. 检查是否多个客户端监听同一 key 2. 确认应用是否去重 | 1. 应用层基于 mod_revision 去重2. 避免在事务中多次修改同一 key | etcd 保证事件有序和至少一次投递,不保证不重复;去重是客户端责任。 |
10.3 Lease 不自动过期
| 问题现象 | 排查步骤 | 解决方案 | 注意事项 |
|---|---|---|---|
| 租约 TTL 已过,但绑定 key 仍存在 | 1. 检查租约是否被持续 keep-alive 2. 查看 lease timetolive <id> 输出3. 检查 etcd 时钟是否同步 | 1. 停止续约进程 2. 若 TTL 显示负值但仍存在,检查 etcd 是否卡住 3. 确保所有节点 NTP 同步 | etcd 依赖单调时钟;节点时间漂移会导致 lease 行为异常。 |
| 客户端崩溃后 key 未删除 | 1. 确认客户端是否调用了 keep-alive 2. 检查 lease TTL 设置是否过长 | 1. 使用较短 TTL(如 10–30 秒) 2. 客户端实现优雅退出(主动 revoke) | Lease 机制依赖客户端失联;若客户端僵死(未退出但也不续约),key 会残留至 TTL 结束。 |
| lease grant 成功但 put —lease 失败 | 1. 检查 lease ID 是否拼写错误 2. 确认 lease 是否已过期 | 1. 从 lease grant 输出准确复制 ID2. 在 put 前执行 lease timetolive 验证 | Lease ID 为十六进制字符串,大小写敏感;过期 lease 无法绑定新 key。 |
| 集群脑裂后 lease 状态不一致 | 1. 检查多数派是否存活 2. 查看各节点 endpoint status 的 revision | 1. 恢复网络,等待 Raft 重新收敛 2. 避免在 minority 分区写入 | Lease 由 leader 管理;minority 分区无法续约,但可能因本地缓存误判状态。 |
10.4 权限拒绝(permission denied)
| 问题现象 | 排查步骤 | 解决方案 | 注意事项 |
|---|---|---|---|
启用 auth 后所有操作报 permission denied | 1. 确认是否使用 --user 参数2. 检查用户名/密码是否正确 3. 验证 root 用户是否存在 | 1. 执行 etcdctl --user=root:<pwd> user list2. 若忘记密码,需临时禁用 auth(需直接访问数据目录) | 紧急恢复方法:1. 停止 etcd;2. 备份 data-dir;3. 从快照恢复未启用 auth 的状态 |
| 用户有角色但无权限访问特定 key | 1. 执行 etcdctl role get <role>2. 检查权限范围是否包含目标 key 3. 确认是否使用 --prefix | 1. 修正权限:etcdctl role grant-permission <role> readwrite /target/ --prefix2. 验证 key 路径是否匹配 | 权限匹配按字典序区间;/a 不包含 /aa,除非使用 --prefix。 |
| Watch 或 Txn 操作被拒绝 | 1. 检查角色是否对涉及的所有 key 有权限 2. 事务中的 compare 条件也需读权限 | 1. 为角色授予所需 key 的 read 权限 2. Watch 的 key 范围必须完全包含在权限内 | etcd 对事务和 Watch 中涉及的每个 key 单独鉴权;缺少任一 key 权限即失败。 |
| TLS 启用后认证失败 | 1. 检查客户端证书是否被服务端 CA 信任 2. 确认 --user 与 RBAC 用户匹配 | 1. 使用 openssl s_client -connect 测试 TLS 握手2. RBAC 用户名独立于证书 CN | TLS 用于加密传输,RBAC 用于授权;两者正交,需同时配置正确。 |