简介:这份围绕 etcd 的 PDF 文档面向分布式系统开发、运维及中间件学习者,系统梳理 etcd 的定位、实战操作与核心原理,可帮助读者理解强一致键值存储在服务发现、分布式锁、分布式队列、主备选举等场景中的落地方式。资源包共 1 个 PDF 文件,大小约 4.55MB,内容按介绍、实战、原理分析三部分组织:介绍部分说明 etcd 的适用场景、与 ZooKeeper 的差异及 Client 层到存储层的分层架构;实战部分覆盖单实例、单机多实例和多节点集群部署,并给出 etcdctl 常用命令、租约与权限管理示例;原理部分则深入读请求与写请求执行流程,拆解 Raft 的 Leader 选举、日志复制与安全性保证,以及 MVCC 的 treeIndex、更新、查询和删除 key 机制。已有 450 人学习,文档以概念词汇表串联 raft、Lease、MVCC 等术语,适合希望从部署使用进阶到核心原理理解、并需要系统查阅 etcd 知识的读者。
1. 版本选错,后面全白搭:v3.5 环境一次性说清
上周帮人看一个本地起不来的 etcd,现象很典型:etcdctl put报context deadline exceeded,换成--endpoints=127.0.0.1:2379还是连不上。翻到启动日志才发现,二进制是 v3.4 的,客户端是 v3.5 的,配置文件里还写着ETCDCTL_API=2。三样东西互相对不上,谁也不认谁。这类问题在 etcd 上出现频率高得离谱,因为它不像 Redis 那样装完基本就能用,它有一套自己的版本语义、端口约定和集群初始化规则,任何一环对不上,表现出来都是"连不上""读不到""写不进去"。
etcd 是 Go 写的分布式一致性 KV 存储,被大量中间件当作元数据底座,服务发现、分布式锁、主备选举、配置下发都会用到它。这份材料锁定的是v3.5这一条线,下面所有安装步骤、参数写法、排错路径都围绕它展开。适合两类人:一是要在本机或测试机上快速跑起一套可用的 etcd 做联调;二是要按生产标准部署三节点集群、并且想知道每一步参数到底在干什么。如果你只是听说过 etcd 想试试水,也一样能照着走完——先把单机版跑通,再往集群上推。
2. 单机起步:v3.5 二进制的下载与运行
在动任何集群之前,先把一台机器上的 etcd 跑起来。这一步的价值不只是"能用",而是让你在最小变量下看清它的端口、数据目录和客户端行为。很多集群问题拿到单机上复现,五分钟就能定位。
2.1 选二进制包而不是包管理器
etcd 社区提供编译好的二进制包,直接下、直接解、直接跑,不依赖系统的包管理策略,也不会有发行版自己打补丁带来的版本漂移。开发测试环境我一般都用这个方式,路径清晰,想删干净就删目录。
# 下载 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 # 解压并进入目录 tar -zxvf etcd-v3.5.5-linux-amd64.tar.gz cd etcd-v3.5.5-linux-amd64/ # 核对版本,确认 server 和 ctl 都是 v3.5.x ./etcd --version ./etcdctl version三步之后要确认的是版本号一致。etcd --version输出的是服务端版本,etcdctl version输出的是客户端版本,两者必须都在 v3.5 这一条线上。包名里的linux-amd64是平台标识,ARM 机器要换成对应的linux-arm64,否则解压出来也跑不动。tar -zxvf里的z是走 gzip 解压,x是解包,v打印文件列表方便你确认内容是否完整。
etcdctl 还有一个容易忽略的点:v3 的 API 需要通过环境变量显式声明。不设置的话,某些老版本客户端默认走 v2 协议,put命令的行为和 v3 完全不同。
# 显式指定使用 v3 API,建议写进 ~/.bashrc 持久化 export ETCDCTL_API=3 # 写一条数据再读回来,验证读写链路 etcdctl --endpoints=localhost:2379 put username fox etcdctl --endpoints=localhost:2379 get username--endpoints指定要连哪个服务端,写全http://localhost:2379和简写localhost:2379在多数情况下等价,但涉及 TLS 的集群必须写完整的 scheme。put写入后返回OK,get返回username和fox两行,说明服务端、客户端、API 版本三者已经打通。这一步不通,后面集群一定不通,别急着往下走。
2.2 前台跑和后台跑,日志位置决定你能不能排错
第一次启动建议前台跑,日志直接打在屏幕上,任何端口占用、数据目录权限问题一眼可见。
# 前台启动,Ctrl+C 结束 ./etcd # 确认无误后改为后台启动,日志落到 /tmp/etcd.log nohup ./etcd >/tmp/etcd.log 2>&1 &前台启动时重点看三行输出:name是节点名,默认default;listen-client-urls决定客户端连哪个地址,默认http://localhost:2379;listen-peer-urls是节点之间通信用的,默认http://localhost:2380。这两个端口的分工要记牢——2379 面向客户端,2380 面向集群内部,单机上 2380 空着不用是正常的。
后台启动那行命令里,>/tmp/etcd.log把标准输出重定向到文件,2>&1把标准错误也并进去,&让进程进入后台。不加2>&1的话,报错信息会丢失,进程悄悄退出你都不知道为什么——这是最常见的"etcd 启动没反应"来源之一。
# 把两个可执行文件放进 PATH,后续任意目录都能直接调用 cp etcd etcdctl /usr/local/bin复制到/usr/local/bin是为了免去每次写./。这一步做完,etcdctl在任何目录都能调起。注意别把旧版本的同名文件留在 PATH 的其他位置,which etcdctl确认一下指向的是哪一个。
2.3 单机多实例:用 goreman 模拟三节点
单实例只能验证读写,验证不了一致性。想在本地观察 Leader 选举、节点下线、日志复制,需要三个实例。但一台机器上 2380 端口只有一个,直接起必然冲突,解决办法是给每个实例分配不同端口段。
这里用 goreman 做进程管理,它是 Procfile 风格的进程管理工具,一个文件描述所有进程,一条命令全拉起。
# 安装 goreman,需要本机已有 Go 环境 go install github.com/mattn/goreman@latest然后写一个local-cluster-profile,三个实例的客户端端口分别用 12379、22379、32379,内部通信端口用 12380、22380、32380:
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 'infra1=http://127.0.0.1:12380,infra2=http://127.0.0.1:22380,infra3=http://127.0.0.1:32380' \ --initial-cluster-state new 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 'infra1=http://127.0.0.1:12380,infra2=http://127.0.0.1:22380,infra3=http://127.0.0.1:32380' \ --initial-cluster-state new三个实例的--initial-cluster内容必须完全一致,--name必须各不相同。这是初始化阶段的硬约束:集群成员靠名字 + peer 地址互相认亲,任何一个名字重复或者列表不一致,都会导致启动时无法组成 quorum。
启动与常用操作:
# 拉起整个集群 goreman -f ./local-cluster-profile start # 查看成员列表,确认三个节点都在 etcdctl --endpoints=localhost:12379 member list # 停掉某一个节点、再重启 goreman run stop etcd2 goreman run restart etcd2 # 向非第一个节点写数据,验证请求转发 etcdctl --endpoints=http://localhost:22379 put user foxmember list的输出里,每个节点都有isLearner字段,正常成员都是false。停掉 etcd2 之后再写数据仍然成功,说明剩下两个节点满足(3+1)/2 = 2的法定人数——这就是 etcd 的仲裁机制,超过半数即可提交,因此集群节点数推荐用 3、5、7,偶数节点并不能提升容错能力。
3. 生产集群:三节点部署的三种引导方式
本地三实例解决的是"看得见",生产走的才是"扛得住"。集群部署要先定节点信息,这里用一组示意地址:infra0、infra1、infra2 三个节点分别监听各自的 2379 和 2380。引导方式有三种,选哪种取决于你在启动之前是否已经知道全部节点地址。
3.1 静态配置:地址已知时的首选
静态配置要求集群节点数量和每个节点的地址在启动前就已确定,适合线下环境、自建机房、IP 固定的场景。三个节点的参数照下面写,其他节点把 IP 和 name 换掉即可,--initial-cluster列表三台机器必须一字不差地相同:
# 在 infra1 上执行,--name 与成员列表里的键名必须对应 etcd --name infra1 \ --listen-client-urls http://192.168.65.206:2379 \ --advertise-client-urls http://192.168.65.206:2379 \ --listen-peer-urls http://192.168.65.206:2380 \ --initial-advertise-peer-urls http://192.168.65.206:2380 \ --initial-cluster-token etcd-cluster-1 \ --initial-cluster 'infra1=http://192.168.65.206:2380,infra2=http://192.168.65.209:2380,infra3=http://192.168.65.210:2380' \ --initial-cluster-state new参数逐个对一遍:--name是本节点在集群中的唯一标识,和列表里对应项的键名必须一致;--listen-client-urls是真正绑定的地址,防火墙要放通;--advertise-client-urls是告诉客户端"来连我这个地址",多网卡机器上这两个值经常不一样,写错会导致客户端拿到一个连不通的地址;--listen-peer-urls与--initial-advertise-peer-urls的关系同理,作用于节点间通信;--initial-cluster-token是集群的隔离标识,同一台机器上跑多套集群时用它区分,别不同集群用同一个 token;--initial-cluster-state new表示这是新建集群,加入已有集群要换成existing。
三台都起来后验证:
etcdctl --endpoints=192.168.65.206:2379,192.168.65.209:2379,192.168.65.210:2379 member listmember list会输出三行,每行带started状态。同时列三个 endpoint 的意义在于:client 会用轮询的方式选择 endpoint,某个节点故障后自动切到其他节点,业务侧不需要自己实现故障转移。
3.2 动态发现:地址事先不确定时用
有时代码还没申请下来,IP 无法预知,静态配置就走不通了。etcd 提供 discovery 协议,先申请一个发现令牌,所有新成员拿着同一个令牌去注册,由发现服务汇总出成员列表。
# 申请一个 size=3 的发现 URL,返回的令牌只能用于一次集群引导 curl 'https://discovery.etcd.io/new?size=3'size是集群规模,不写默认为 3。这个返回的 URL 是整次引导的核心,三台机器都用它。令牌只能代表一个集群,协议一旦启动就不能复用于引导另一个集群,这是它的设计边界。
启动前先把数据目录建好,别和数据盘上的其他服务混用:
mkdir -p /var/log/etcd/ mkdir -p /data/etcd然后三台分别启动,差异只在--name、IP 和 peer 地址:
# 每个节点的 --name 必须唯一,重名会导致发现失败 etcd --name etcd1 --data-dir /data/etcd \ --initial-advertise-peer-urls http://192.168.65.206:2380 \ --listen-peer-urls http://192.168.65.206:2380 \ --listen-client-urls http://192.168.65.206:2379,http://127.0.0.1:2379 \ --advertise-client-urls http://192.168.65.206:2379 \ --discovery 'https://discovery.etcd.io/ef89922e093e9d5ff5ae5c8ec481f4e3'--data-dir显式指定数据目录,生产环境一定要从默认的相对路径挪到独立磁盘路径上,否则日志和快照会把系统盘撑满。--listen-client-urls这里同时写了内网 IP 和127.0.0.1,好处是本机上的其他组件走回环地址访问,省掉一次网络绕行。--discovery就是前面拿到的 URL,注意它只替代了--initial-cluster的作用,其他参数照常要给。
需要注意 discovery 协议只服务于引导阶段,运行期增删节点、监控集群不归它管,不要指望用它做后续的成员管理。
3.3 客户端常用操作与租约、权限
集群起来之后,真正天天打交道的还是 etcdctl。键操作的基本面要弄清半开区间和前缀匹配:
# 基础增删改查 etcdctl put /test/name fox etcdctl get /test/name # 范围查询,左闭右开,所以 /test/name3 会被包含进来 etcdctl get /test/name /test/name3 # 前缀匹配,--limit 可以限制返回条数 etcdctl get --prefix /test --limit 3 # 删除并返回删除前的值,排错时很有用 etcdctl del --prev-kv /test/name--limit 3在 key 很多的目录下能避免一次拉回全部数据。--prev-kv是排查"谁把配置删了"的关键参数,它会把删除前的键值一起打出来。另外get --rev=8可以读取指定版本的 key,靠的是 MVCC 的多版本能力,这在你需要回溯某个配置改成了什么时非常直接。
租约(Lease)是 etcd 区别于普通 KV 的核心能力之一,服务发现靠它做存活检测:
# 申请一个 30 秒 TTL 的租约,返回租约 ID etcdctl lease grant 30 # 把 key 绑定到租约上,TTL 到期后 key 自动消失 etcdctl put --lease=705b8474d1acd63b name fox # 续约、查询剩余时间、撤销 etcdctl lease keep-alive 705b8474d1acd63b etcdctl lease timetolive --keys 705b8474d1acd63b etcdctl lease revoke 705b8474d1acd63blease grant 30的 30 是秒。keep-alive是长驻进程,客户端要在租约到期前持续续约,否则 key 会被清掉——这正是服务发现里"节点下线自动摘除"的实现原理。revoke会连带删除所有绑定在这个租约上的 key,比逐个del干净得多。
权限部分有一条硬规则:必须先创建 root 用户,才能开启鉴权。
# 建 root 用户,自带全部权限 etcdctl user add root etcdctl auth enable # 之后所有操作都要带凭据 etcdctl --user='root' --password='123456' put name fox # 普通用户 + 角色 + 授权,最小权限原则 etcdctl --user='root' --password='123456' user add fox etcdctl --user='root' --password='123456' role add test etcdctl --user='root' --password='123456' user grant-role fox test etcdctl --user='root' --password='123456' role grant-permission test readwrite /nameauth enable之后如果不带凭据,所有命令都会报etcdserver: user name is empty。这是新手最容易慌的一步,以为集群崩了,其实只是鉴权开了。role grant-permission的权限有三档:read、write、readwrite,后面的路径是 key 前缀,别再往里塞通配符。权限粒度就到这里,它不做字段级别的隔离。
4. 环境搭完别急着写业务:四条踩坑记录
环境本身不出错,出错的是参数和状态的组合。以下四条是我自己踩过、也见过别人反复踩的。
4.1 启动卡住并报context deadline exceeded
现象:执行etcdctl put返回context deadline exceeded,member list也超时,但进程明明在跑。
原因:客户端 endpoint 写的是默认的localhost:2379,而服务端起在了别的端口或绑定了其他网卡地址;另一种情况是上一轮启动的残留进程还占着端口,新进程静默退出。
解决:先ss -lntp | grep 2379看端口到底被谁占了,杀掉残留进程再起;然后用--endpoints显式写全地址。排查顺序固定成"进程在不在、端口通不通、endpoint 对不对"三段,能砍掉大部分误判。
4.2 数据目录残留导致集群身份错乱
现象:重启后member list里出现重复成员,或者新节点死活加不进集群,报member ... has already been bootstrapped。
原因:--data-dir下面保存了该节点上次的集群元数据。如果重新初始化了一个新集群但沿用了旧目录,etcd 会认为它仍然属于旧集群,拒绝以新的身份启动。
解决:重建集群前务必清空数据目录,这是后悔药的正确用法——先把旧目录改名备份(比如mv /data/etcd /data/etcd.bak.$(date +%s)),再建空目录启动。别直接rm -rf,出事了还能捞回来。另外--initial-cluster-state existing不能用于全新引导,新集群只能是new。
4.3 偶数节点集群的容错假象
现象:部署了 4 个节点,认为比 3 个节点更安全,结果一次宕机两个就整体不可写。
原因:Quorum 是(n+1)/2的向上取整,4 节点需要 3 个才能提交,容错能力是 1 个,和 3 节点一模一样,但多花了一台机器的资源,还增加了日志复制的网络开销。
解决:集群规模只用奇数,3、5、7。5 节点可以容忍 2 台故障,7 节点容忍 3 台,但节点越多单次写要广播的目标越多,写性能随之下降。生产上 3 节点是性价比最高的起点,跨机房容灾再考虑 5 节点。
4.4 鉴权开启后自己把自己锁在外面
现象:执行auth enable之后所有命令都报user name is empty,想关掉鉴权也关不掉。
原因:鉴权一旦开启,任何操作都要带--user和--password,包括关闭鉴权的命令本身。root 用户必须是在auth enable之前创建的,否则开启后无人有权限。
解决:确认 root 用户已创建再执行auth enable;已经锁住的话,带上 root 凭据执行etcdctl auth disable回退。密码不要写在命令行历史里,用ETCDCTL_PASSWORD环境变量或者配置文件传入更稳妥。上线前先在测试环境完整走一遍"开鉴权、建用户、验权限"的流程。
5. 装完怎么验证:三条链路各自跑通才算完成
很多人的验证只做到put成功就结束了,这只能证明单机能写。一套真正可交付的 etcd 环境,要分别验证客户端链路、集群一致性、数据持久化三条线,缺一条都可能在业务压测时暴露。
第一条是客户端链路。用member list和endpoint status一起看:
# 成员与健康状态,注意 started 字段 etcdctl --endpoints=http://192.168.65.206:2379,http://192.168.65.209:2379,http://192.168.65.210:2379 member list # 逐节点看健康、版本、dbSize、raftIndex etcdctl --endpoints=http://192.168.65.206:2379 endpoint status -w table etcdctl --endpoints=http://192.168.65.206:2379 endpoint healthendpoint status -w table的表格里重点看三列:DB SIZE是否在稳定增长、RAFT INDEX各节点是否接近、IS LEADER是否只有一个true。如果两个节点同时显示 leader,说明集群已经脑裂,立刻停掉其中一个排查网络。RAFT INDEX差值持续拉大,通常是某个节点磁盘 I/O 跟不上,apply 追不上 leader 的提交。
第二条是集群一致性。停掉 leader,再写数据:
# 先找到 leader etcdctl --endpoints=http://192.168.65.206:2379 endpoint status -w table # 停掉 leader 所在节点后,向存活节点写数据 etcdctl --endpoints=http://192.168.65.209:2379 put failover ok etcdctl --endpoints=http://192.168.65.210:2379 get failover写入成功、并且在另一个节点上能读到,说明选举和日志复制都正常。这个过程会有几秒的不可写窗口,属于 Leader 选举的正常代价,业务侧要做重试。如果一直写不进去,检查剩余节点是否还满足 Quorum。
第三条是持久化。重启一个节点,看数据还在不在:
# 重启节点后读取之前的 key etcdctl --endpoints=http://192.168.65.206:2379 get failover能读回来,说明 WAL 和快照恢复生效。这一步容易被跳过,但恰恰是 WAL 模块的价值所在——进程崩溃后数据不丢靠的就是它。
| 验证项 | 命令 | 通过标准 |
|---|---|---|
| 客户端连通 | endpoint health | 全部返回 healthy |
| 成员完整性 | member list | 节点数与规划一致,均 started |
| 单 Leader | endpoint status -w table | 只有一个 IS LEADER 为 true |
| 故障转移 | 停 leader 后写入 | 存活节点写入并读回成功 |
| 持久化恢复 | 重启后 get | 数据仍可读 |
这三条链路走完,一套 etcd 才算真正交付。从那以后我每次搭完 etcd,不管多急,endpoint status -w table和一次停 leader 的故障演练都强制走一遍,宁可多花十分钟,也不想在半夜被"读不到配置"叫醒。希望这套流程能帮到你,少走几个我走过的弯路。
本文还有配套的精品资源,点击获取