首页 / 资讯中心 / 文章详情

etcd v3.5.5 部署与排障:从单机到三节点集群

etcd v3.5.5 部署与排障:从单机到三节点集群 ★ FEATURED ARTICLE
简介《etcd实战及其原理分析.pdf》面向希望系统掌握分布式键值存储的开发者、运维与架构人员围绕 etcd 的部署实践与核心机制展开适合具备一定 Go 与分布式基础、需要落地服务发现、分布式锁、主备选举等场景的读者。资源包仅含 1 个 PDF 文件大小约 4.55MB正文按 etcd 介绍、实战操作、核心原理三部分组织。内容从单实例、单机多实例到多节点集群搭建结合客户端命令与权限管理说明常见操作原理部分覆盖读请求与写请求执行流程、Raft 选举与日志复制、MVCC 的 treeIndex 及增删改查机制并梳理 WAL、Snapshot、boltdb 等存储模块。已有 448 人学习可作为理解 etcd 架构与排错思路的参考。1. etcd v3.5 装之前先想清楚单机、伪集群还是真集群帮人看一个 etcd 起不来的现场现象很简单bind: address already in use端口 2380 被上一轮没退干净的进程占着而他把>cd /tmp # 下载 v3.5.5 的 linux-amd64 包包名里的架构要和机器对上 wget https://github.com/etcd-io/etcd/releases/download/v3.5.5/etcd-v3.5.5-linux-amd64.tar.gz # 解压后目录里同时带 etcd 和 etcdctl 两个可执行文件 tar -zxvf etcd-v3.5.5-linux-amd64.tar.gz cd etcd-v3.5.5-linux-amd64 # 装之前先确认二进制本身没问题 ./etcd --version ./etcdctl version--version这一步别省。曾经遇到过压缩包传输中断解压没报错但二进制是截断的直接执行会报cannot execute binary file早期发现能省掉后面一堆怀疑人生的时间。输出里应该能看到etcd Version: 3.5.5和API Version: 3.5两行如果etcdctl version显示的是 v2 API说明拿错了老包。# 复制到系统 PATH后续不用再带路径 cp etcd etcdctl /usr/local/bin/ # 建三个目录数据、日志、配置 mkdir -p /data/etcd /var/log/etcd /etc/etcd # 用独立用户跑别用 root 裸奔 useradd -r -s /sbin/nologin etcd chown -R etcd:etcd /data/etcd /var/log/etcd目录规划这件事看起来无关紧要但 etcd 的数据目录一旦有残留重启时行为会很不一样。/data/etcd放 member 的 WAL 和 boltdb 快照/var/log/etcd放日志/etc/etcd放配置文件三者分离后面换盘、清数据、做备份都不会互相牵连。2.2 用配置文件代替一长串命令行参数etcd 支持把命令行参数全部写进 YAML用--config-file加载。生产环境我一直用配置文件原因很实在命令行长到一定程度改错一个字符肉眼看不出来而 YAML 至少能对齐。# /etc/etcd/etcd.conf.yml name: etcd-1># /etc/systemd/system/etcd.service [Unit] Descriptionetcd v3.5.5 Afternetwork-online.target Wantsnetwork-online.target [Service] Typenotify Useretcd Groupetcd ExecStart/usr/local/bin/etcd --config-file/etc/etcd/etcd.conf.yml Restarton-failure RestartSec5 # etcd 每个客户端连接会占文件描述符限制太小会莫名拒绝连接 LimitNOFILE65536 [Install] WantedBymulti-user.targetTypenotify的意思是 etcd 启动完成会主动通知 systemdsystemctl start返回时服务已经真的可用了不会出现启动命令成功了但端口还没监听的假象。Restarton-failure配合RestartSec5避免进程崩溃后疯狂重启把日志刷爆。systemctl daemon-reload systemctl enable --now etcd # 看状态active (running) 才算起来 systemctl status etcd --no-pager journalctl -u etcd -n 50 --no-pager # 端到端验证健康检查 状态查询 etcdctl --endpointshttp://127.0.0.1:2379 endpoint health etcdctl --endpointshttp://127.0.0.1:2379 endpoint status -w table # 真正落一条数据 etcdctl --endpointshttp://127.0.0.1:2379 put username fox etcdctl --endpointshttp://127.0.0.1:2379 get usernameendpoint health返回is healthy: successfully committed proposal才算完整闭环这句话的含义是不仅进程在而且能成功提交一次提案比单纯ss -lntp看到 2379 端口有价值得多。到这一步单实例的地基就算打完了。3. 单机多实例与三节点集群goreman 和静态配置两条路单实例跑通之后下一步是集群。这里有个分岔本地开发验证选单机多实例三个进程三个端口预发和生产环境选多机三节点静态配置。两者参数结构一样只是地址和端口不同先把前者跑一遍能省掉大量在真机上反复试错的时间。3.1 用 goreman 在一台机器上拉起三个实例单机多实例的核心矛盾只有一个三个 etcd server 默认都想监听 2380端口必然冲突。解决办法是给每个实例分配一套独立的客户端端口和集群端口比如 12379/12380、22379/22380、32379/32380。goreman 是个轻量的多进程管理工具靠一个 Procfile 格式的文本文件批量起停进程需要 Go 环境。# 安装 goreman依赖本机已有 Go go install github.com/mattn/goremanlatest # 确认命令可用 goreman help下面这份local-cluster-profile就是三个实例的启动清单。注意每行是一条完整命令不能换行goreman 是按行解析的续行符会被当成命令的一部分。etcd1: etcd --name infra1 --listen-client-urls http://127.0.0.1:12379 --advertise-client-urls http://127.0.0.1:12379 --listen-peer-urls http://127.0.0.1:12380 --initial-advertise-peer-urls http://127.0.0.1:12380 --initial-cluster-token etcd-cluster-1 --initial-cluster infra1http://127.0.0.1:12380,infra2http://127.0.0.1:22380,infra3http://127.0.0.1:32380 --initial-cluster-state new --data-dir /tmp/etcd-data/infra1 etcd2: etcd --name infra2 --listen-client-urls http://127.0.0.1:22379 --advertise-client-urls http://127.0.0.1:22379 --listen-peer-urls http://127.0.0.1:22380 --initial-advertise-peer-urls http://127.0.0.1:22380 --initial-cluster-token etcd-cluster-1 --initial-cluster infra1http://127.0.0.1:12380,infra2http://127.0.0.1:22380,infra3http://127.0.0.1:32380 --initial-cluster-state new --data-dir /tmp/etcd-data/infra2 etcd3: etcd --name infra3 --listen-client-urls http://127.0.0.1:32379 --advertise-client-urls http://127.0.0.1:32379 --listen-peer-urls http://127.0.0.1:32380 --initial-advertise-peer-urls http://127.0.0.1:32380 --initial-cluster-token etcd-cluster-1 --initial-cluster infra1http://127.0.0.1:12380,infra2http://127.0.0.1:22380,infra3http://127.0.0.1:32380 --initial-cluster-state new --data-dir /tmp/etcd-data/infra3三个实例的--initial-cluster必须逐字一致包括成员名和端口。这个字符串是集群引导期的通讯录任何一个实例写的内容不同集群就会分裂成两个互不认识的阵营表现出来是各自都能起但member list只看到自己。# 前台启动CtrlC 全部停掉加 -f 指定 profile 文件 goreman -f ./local-cluster-profile start # 另开终端从任意一个实例查成员列表 etcdctl --endpointshttp://127.0.0.1:12379 member list -w table # 随便停一个观察集群是否仍然可写 goreman run stop etcd3 etcdctl --endpointshttp://127.0.0.1:12379 put user fox # 再把节点拉回来它会自动追平日志 goreman run restart etcd3member list的输出里ISLEADER列只有一个true其余是false如果全是false说明选主没完成别急着写数据先去看日志里的lost leader或者failed to reach之类的记录。停掉一个节点后仍然能写入是因为 3 个节点里 2 个达成多数派就满足 Quorum但停掉两个就彻底写不进去了这是 Raft 的固有约束不是配置问题。3.2 三节点静态配置把地址和成员名提前确定下来生产环境的三节点集群基本都用静态配置搭集群之前节点数量和 IP 都已知启动时通过参数把成员列表写死。准备三台机器假设内网地址分别是 10.0.0.11、10.0.0.12、10.0.0.13机器名对应 infra0、infra1、infra2。# 每个节点都要执行建目录、放二进制、准备配置文件目录 mkdir -p /data/etcd /var/log/etcd /etc/etcd cp etcd etcdctl /usr/local/bin/节点一的配置文件如下另外两台只改name和所有出现 IP 的地方initial-cluster保持完全相同。# /etc/etcd/etcd.conf.yml 10.0.0.11 上 name: infra0># 三台分别执行 systemctl enable --now etcd # 在任意一台验证成员数量应该是 3 个 etcdctl --endpointshttp://10.0.0.11:2379,http://10.0.0.12:2379,http://10.0.0.13:2379 member list -w table # 逐个体检 etcdctl --endpointshttp://10.0.0.11:2379,http://10.0.0.12:2379,http://10.0.0.13:2379 endpoint health --cluster--cluster参数会把 endpoints 列表里每个成员都检查一遍比单点检查更能暴露问题。常见的半成功状态是member list显示三个成员但 health 只有两个健康那个不健康的往往是 peer 端口被防火墙拦了日志里会出现failed to dial加目标地址。3.3 关键启动参数逐条对照参数不多但每一个都有明确语义混淆之后排查成本极高。下表是我自己整理的一份对照改配置的时候会对着看。参数作用常见误配--name本节点在集群中的唯一名字与initial-cluster里的成员名不一致导致握手失败--data-dir存放 WAL 和快照的目录多实例共用同一目录数据互相覆盖--listen-client-urls监听客户端请求2379只写 127.0.0.1跨机访问全部超时--advertise-client-urls告知客户端用什么地址访问自己写成 0.0.0.0客户端拿到不可达地址--listen-peer-urls监听集群内部通信2380与 2379 写反节点间永远连不上--initial-advertise-peer-urls告知其他成员用什么地址连自己多网卡机器写错网段--initial-cluster集群初始成员通讯录各节点内容不一致集群分裂--initial-cluster-token集群标识隔离同名集群同一网络里两套集群用同一 token--initial-cluster-statenew新建 /existing加入已有数据的目录用new重启直接拒绝启动--initial-cluster-token这个参数平时没人注意但它的作用不小同一批机器上如果同时跑着两套 etcd 集群token 相同会导致节点互相串门A 集群的节点跑去 B 集群注册。给它一个能区分环境的字符串比如etcd-prod-1、etcd-test-1成本几乎为零。--initial-cluster-state是另一个高频雷区。new只在集群第一次引导时用一旦># size 是集群节点数必须提前确定返回一个一次性 token URL curl https://discovery.etcd.io/new?size3 # 各节点用同一个 discovery URL 启动不再需要 initial-cluster etcd --name infra0 --data-dir /data/etcd \ --initial-advertise-peer-urls http://10.0.0.11:2380 \ --listen-peer-urls http://10.0.0.11:2380 \ --listen-client-urls http://10.0.0.11:2379,http://127.0.0.1:2379 \ --advertise-client-urls http://10.0.0.11:2379 \ --discovery https://discovery.etcd.io/返回的token这个 token 只能用于一次集群引导集群起来之后就不能再拿它做运行时扩缩容了。扩缩容走的是member add/member remove两套机制别混。另外--name必须唯一重名会让发现服务直接拒绝注册。4. etcdctl 与权限从 put/get 到 auth enable集群起来只是能存数据真正接入业务之前还有两件事把 etcdctl 的常用操作摸熟以及把认证打开。前者决定排查效率后者决定这套环境能不能上线。4.1 键操作与 revision 字段怎么读etcdctl 的键操作就那么几个但返回值里的字段值得单独说因为排查数据为什么读不到时全靠它们。export ETCDCTL_API3 # 3.4 以前必须显式指定3.5 默认已是 v3留着是为了兼容老脚本 EP--endpointshttp://10.0.0.11:2379 # 写 etcdctl $EP put /test/name fox # 读 etcdctl $EP get /test/name # 以 JSON 输出能看到完整元数据 etcdctl $EP get /test/name -w json # 按前缀批量读limit 控制返回条数 etcdctl $EP get --prefix /test --limit 3 # 按字典序从某个 key 开始读 etcdctl $EP get --from-key name2 # 读历史版本rev 是全局 revision etcdctl $EP get --rev8 /test/name # 删除并带回被删的值 etcdctl $EP del --prev-kv /test/name-w json的输出里有几个字段必须理解否则会误判数据状态revision集群全局版本号任何一次 put 或 delete 都会让它加 1所以它是集群级的不是单个 key 的。create_revision这个 key 第一次被创建时的全局 revision。mod_revision这个 key 最后一次被修改时的全局 revision和revision相等说明它就是最近一次变更。version这个 key 自己被改过几次从 1 开始删除后重建会重新从 1 计。raft_term当前 Leader 的任期号节点重启或重新选主后这个值会变。一个典型误用是拿version当全局版本号做乐观锁。version是 key 级别的两个不同的 key 各自从 1 开始跨 key 的并发控制必须看mod_revision。4.2 lease 与 watchTTL 和变更通知租约是 etcd 做服务发现的基础。键绑定到租约上租约到期键自动消失不需要业务自己写清理逻辑。# 申请一个 TTL 30 秒的租约返回租约 ID etcdctl $EP lease grant 30 # 写入时绑定租约 etcdctl $EP put --lease705b8474d1acd63b /test/name fox # 续期保持租约不过期 etcdctl $EP lease keep-alive 705b8474d1acd63b # 查剩余时间和绑定的 key etcdctl $EP lease timetolive --keys 705b8474d1acd63b # 主动撤销绑定的 key 一起删掉 etcdctl $EP lease revoke 705b8474d1acd63b # 监听单个 key 的变化另开终端 put 一次就能看到输出 etcdctl $EP watch /test/namelease keep-alive是长连接前台运行会一直挂着实际业务里由客户端库在后台续期。手工验证时如果看到 key 莫名其妙消失第一反应应该是租约到期而不是数据被误删lease timetolive一查就清楚。watch 相比轮询的价值在于事件驱动且 etcd 的 watch 是可持续的不会像某些实现那样触发一次就失效。做配置热更新时业务进程只需要 watch 一个前缀配置变更后立刻收到通知不用重启。4.3 用户、角色与开启认证的正确顺序权限这块的顺序错了会把自己锁在外面。正确流程是建 root 用户 → 建普通用户和角色 → 给角色授权 → 用户绑角色 → 最后才auth enable。EP3--endpointshttp://10.0.0.11:2379,http://10.0.0.12:2379,http://10.0.0.13:2379 # 第一步必须有 root 用户否则 auth enable 会失败 etcdctl $EP3 user add root # 第二步开启认证从此所有请求都要带账号 etcdctl $EP3 --userroot --password123456 auth enable # 第三步建普通用户和角色 etcdctl $EP3 --userroot --password123456 user add fox etcdctl $EP3 --userroot --password123456 role add test # 第四步给角色授权粒度是 key 范围加读写类型 etcdctl $EP3 --userroot --password123456 role grant-permission test readwrite /test # 第五步用户绑定角色 etcdctl $EP3 --userroot --password123456 user grant-role fox test # 验证普通用户只能操作被授权的范围 etcdctl $EP3 --userfox --passwordxxxxxx get --prefix /test etcdctl $EP3 --userfox --passwordxxxxxx get /other # 应该返回 permission deniedroot 用户不需要绑定任何角色天生拥有全部权限这也意味着 root 密码必须是能保管住的那种。auth enable之后如果所有客户端都返回 401先确认命令里有没有带--user和--password再确认账号密码没打错最后才怀疑是不是认证把集群搞坏了。注意auth enable之后忘记 root 密码是比较麻烦的事。恢复手段是停掉其他节点在单个节点上带--force-new-cluster启动重建集群然后重新配权限和数据。这属于重量级操作执行前一定确认没有别的路可走。角色授权的 key 范围支持前缀匹配/test会覆盖/test及其所有子 key。如果只想放行单个 key就写完整路径/test/name。粒度设计上建议按业务前缀划分避免出现一条 readwrite 覆盖整个键空间的情况。5. 排查与避坑五条从现场带回来的记录etcd 的报错大多集中在启动、选主、认证三个阶段且同一句报错可能对应好几个根因。下面五条按现象 → 原因 → 解决整理都是实际环境里反复出现的。5.1 启动与端口类问题现象一启动即退出日志里是bind: address already in use。原因通常不是端口真的被别人占了而是上一轮 etcd 没退干净——kill之后进程还在收尾或者用nohup起的实例被遗忘。etcdctl endpoint health此时还能通让人误以为服务正常新实例起不来是别的原因。 解决办法是先确认占用者ss -lntp | grep -E 2379|2380找到 PIDkill掉如果确认是老实例又不想留它的数据再清理>ss -lntp | grep -E 2379|2380 ps -ef | grep etcd | grep -v grep # 确认无用后清理注意这一步会丢数据 rm -rf /data/etcd/*现象二改了--name或--initial-cluster后重启失败报member ... has already been bootstrapped。原因是># 时间同步状态检查偏差应在毫秒级 timedatectl status chronyc tracking 2/dev/null || ntpq -p现象五auth enable之后业务侧全部请求返回 401包括原本正常的读。原因通常是三类客户端连接没有带账号密码密码里含特殊字符被 shell 提前解释开启了认证但调用方还用着开启前的长连接缓存。 解决上先去掉业务代码用 etcdctl 手工验证一次etcdctl --userroot --passwordxxx endpoint health。如果手工能通说明是客户端配置问题手工也不通再检查 root 口令是否正确。密码含$、!这类字符时用单引号包住别用双引号。提示生产环境开启 auth 前务必先在预发环境把整套授权关系跑一遍确认每个业务方用的账号和权限范围再在生产执行。认证一旦打开没配好权限的客户端会立刻开始报错。6. 装完怎么验证一份能照着打勾的验收清单环境装完不等于装对。我见过不少能起来、能读写但一压就散、一停就选不出主的集群问题都藏在没验证的角落里。下面这份清单按从轻到重的顺序排单机、集群都适用建议逐条跑一遍再交付。EP--endpointshttp://10.0.0.11:2379,http://10.0.0.12:2379,http://10.0.0.13:2379 # 1. 三个节点都健康且只有一个 leader etcdctl $EP endpoint health --cluster etcdctl $EP endpoint status -w table # 2. 成员数量与预期一致 etcdctl $EP member list -w table # 3. 跨节点数据可见往 A 写从 C 读 etcdctl --endpointshttp://10.0.0.11:2379 put /verify/key ok etcdctl --endpointshttp://10.0.0.13:2379 get /verify/key # 4. 无告警NOSPACE、CORRUPT 都应该是空的 etcdctl $EP alarm list # 5. 数据库大小与配额占用 etcdctl $EP endpoint status -w json | grep -o dbSize:[0-9]* # 6. 清理验证数据 etcdctl $EP del /verify/key第 3 条是整个验收里最关键的一步。单节点自读自写只能证明进程活着跨节点读写才能证明 Raft 日志确实复制到了多数派、状态机确实应用成功。第 4 条容易被忽略alarm list返回memberID:... alarm:NOSPACE说明后端配额已经打满此时 etcd 只接受读和删除所有写入都会失败而客户端看到的可能只是一句etcdserver: mvcc: database space exceeded不查 alarm 很难定位到配额上。故障切换也要实测一次这是唯一能证明高可用真的可用的方式。验收项操作期望结果健康检查endpoint health --cluster全部 healthy成员完整member list -w table数量正确ISLEADER 只有一个 true跨节点读A 写 C 读读到相同值告警alarm list输出为空停 Leader停掉 ISLEADER 为 true 的节点5 秒内剩余节点选出新 Leader写入不中断恢复节点启动刚停的节点自动追平日志重新出现在 member list 中认证开 auth 后带账号读写授权范围外返回 permission denied停 Leader 这一步有个前提别在只有两个节点存活的时候做。3 节点集群停 1 个还剩 2 个满足多数派如果先停了一个再停 Leader剩下 1 个节点凑不够 Quorum整个集群只读不写新手很容易在这里以为集群彻底坏了。恢复方法就是把节点拉起来等它追平日志。还有个可选动作etcdctl check perf可以做吞吐压测但它会在集群里写入大量测试数据只能在测试环境跑生产环境慎用。真要评估性能用endpoint status看dbSize和raftIndex的增长趋势比压测更安全。从那以后我每次搭完 etcd都会强制走一遍停 Leader 再拉回来的流程不管环境多小、交付多急。有次省了这一步上线第二天主节点所在机器重启集群选了 40 秒才选出新 Leader原因是从节点的磁盘 I/O 太慢日志落盘跟不上而这个问题在任何静态检查里都看不出来。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站