先说现象。一套三节点的 Kubernetes 集群,跑了几个月一直没出过事。某天开始,运维收到反馈:kubectl apply 偶尔会卡住,卡的时间不长,三四秒到五六秒,卡完自己就好了,命令也确实执行成功了。同一时间 kubectl get pod 也会跟着慢半拍,像是整条控制面被什么东西"捏"了一下,然后松开。
上去看监控,第一个反应是"这不科学"。三个 etcd 节点全部处于健康状态,进程没有重启,没有 core dump,没有 OOM。CPU 使用率长期在两成以下,内存用了不到四成,网络带宽更是空得可怜,只有几百 Kbps 的波动。磁盘容量这一项最离谱:etcd 的数据目录总共分了 100G,实际用了不到 8G。所有能想到的资源指标,没有一项接近告警线。
真正有价值的线索在日志里,而且两份日志是能对上的。kube-apiserver 侧每隔一段时间刷出一批 etcdserver: request timed out;etcd 侧的日志里,几乎同一秒出现 leader changed to 这一行,前面还跟着若干条 apply request took too long 与 waiting for ReadIndex response took too long。把两个时间戳对齐之后,结论非常直接:kubectl 卡住的那几秒,就是 etcd 在重新选主的那几秒。
这里要分清一件事,很多人的第一反应是"控制面慢了",这个判断不准确。集群里的业务 Pod 全程正常,节点没有掉线,Service 的转发也没有中断,容器网络和业务流量完全没受影响。真正发生的是集群状态的可写性短暂消失了——这段时间里不能创建 Pod、不能滚动更新、不能扩缩容,调度器拿不到最新的集群状态,HPA 的扩容决策也会延后。Raft 在选出新 leader 之前不接受写入,这是设计使然,不是故障。所以对外表现为"控制面卡了几秒",实质是"集群状态变更的入口被关掉了几秒"。
顺着"谁触发了选主"往下查,事情就清楚了。三个节点中有一个的数据目录挂在一块共享存储上,那块盘跑 fio 的时候随机写能到几万 IOPS,平均延迟看着很漂亮,但把测试时间拉长到二十分钟、用小块同步写去测,p99 会周期性冲到几百毫秒。冲高的时间点和 etcd 的快照落盘时间点高度吻合。也就是说:故障的根因不在资源不够,而在某一次 fsync 返回得太晚,晚到超过了 Raft 的时间预算,follower 判定 leader 失联,发起了选举。
这个故事里最值得记住的一点:所有"使用率"类的指标都不会提前告诉你这件事。CPU、内存、容量、带宽全是健康的,唯一不健康的是磁盘的尾延迟,而尾延迟在常规监控里往往压根没有采集。等到你从 leader 变更日志里发现它的时候,抖动已经发生过很多次了。
要判断磁盘够不够,得先知道 etcd 的一次写入到底在磁盘上做了什么。etcd 用 Raft 保证一致性,一次写请求的完整路径是这样的:客户端把请求发给 leader;leader 把这条提案追加到自己的 WAL(Write Ahead Log,预写日志);同时 leader 把这条提案复制给所有 follower;follower 收到后也追加到自己的 WAL;每个节点各自对 WAL 执行 fsync;fsync 成功之后节点才向 leader 返回确认;leader 收集到多数派(三节点集群是两个)的确认之后,这条提案才算 commit;commit 之后应用到状态机(boltdb),最后向客户端返回成功。
这条路径上有两个约束决定了磁盘的性能画像。
第一个约束是 fsync 不能延后。WAL 写完后数据还在操作系统的页缓存里,掉电就丢。Raft 的语义是"多数派确认过的东西不能丢",所以 fsync 必须发生在"返回确认"之前,而不是之后。这一步没有任何异步优化的余地——你不能先把确认发出去再慢慢刷盘,那等于放弃了持久性。fsync 的耗时直接、完整地计入了一次写请求的延迟。
第二个约束是 WAL 是顺序追加写。每条提案追加在文件末尾,没有随机寻址,没有原地更新。这意味着 etcd 对磁盘的随机 IOPS 能力几乎没有要求。一块标称"随机读写几十万 IOPS"的高端盘,在 etcd 场景下能发挥出来的部分远小于在数据库场景下的发挥。反过来,一块随机 IOPS 平平、但顺序同步写延迟稳定在低位且尾巴很短的盘,跑 etcd 反而更稳。
理解了这两点,就能得出一个很实用的推论:一次 etcd 写请求的耗时下界,约等于"多数派节点中 fsync 最慢的那个节点的 fsync 耗时"加上"网络往返时间"。注意是"最慢",不是"平均"。三节点集群的多数派是两个节点,也就是说三个节点里只要有任意两个里的那个慢的慢了,整体就慢了。这个"取最慢"的性质,是 etcd 磁盘选型一切反直觉结论的源头:加节点不能改善延迟,只会让"最慢"的样本空间变大。
还有一个常见的误解要在这里澄清:etcd 的写入是串行化的。Raft 的日志必须按序提交,不能靠并发 IO 把延迟掩盖掉。数据库可以靠提高队列深度、把延迟藏在并行里,etcd 不行。这也解释了为什么 etcd 的写吞吐天然上不去——它本来就没打算做高吞吐。控制面的写请求频率通常只有几十到几百 QPS 的量级(取决于集群规模与控制器活跃度),etcd 需要的从来不是"每秒能写多少",而是"每一次写多久、最慢的那次多久"。
把平均值当成判断依据,是这个场景里最普遍的错。一块盘 p50 是 1 毫秒、p99 是 150 毫秒,一万次里有一百次超过 100 毫秒,那么它的平均延迟大概也就两毫秒出头。平均值看起来完全正常,p99 却足以让 Raft 的计时器超时。Raft 不关心平均值,它只关心"这一次有没有超时"。
抖动从哪儿来?按来源分四类,这四类在实际环境里经常同时存在。
SSD 内部行为。闪存不能原地覆盖写,要先擦除再写入,于是盘内部有垃圾回收(GC)与磨损均衡。消费级 TLC/QLC 盘普遍用一段 SLC 缓存来吸收突发写入,缓存撑得住的时候速度很快,一旦写满、或者后台 GC 开始搬数据,写入延迟会出现数量级的抬升。这就是为什么短时间的 fio 测试永远测不出问题——三十秒的测试连 SLC 缓存都没写满。
云盘与虚拟化层。云盘的 fsync 要穿过虚拟化层到达后端存储,后端通常还有多副本同步。这一层的变量有:是否为本地盘、是否有突发配额(burst credit)、配额耗尽后降速到什么水平、同宿主机上其他租户的 IO 压力有多大。突发配额这一条最容易被忽略:很多云盘的标称 IOPS 是"突发值",配额用完之后会掉到一个低得多的基线值,而这个下降过程在使用方看来就是"莫名奇妙的周期性抖动"。
共享存储。NAS、SAN、网络块存储这类方案,每次 fsync 都要走到存储控制器再返回,延迟里包含了网络往返与控制器排队。它的延迟基数本来就比本地盘高,抖动的幅度也更不可控。这类存储做数据归档、做共享文件完全没问题,放在 etcd 的 WAL 目录下就是灾难。
同盘竞争。系统日志、容器运行时、镜像层、监控 agent 的本地缓存、备份任务,全都往同一块盘上写。平时相安无事,一旦有大块写入(拉镜像、日志滚动、备份),IO 队列被占满,排在后面的 WAL fsync 就得等着。这个"等"就是尾延迟。
现在把抖动和选主连起来。Raft 靠心跳维持 leader 的权威:leader 周期性向所有 follower 发心跳,follower 在选举超时(election timeout)之内没有收到心跳,就认为 leader 挂了,自己转成 candidate 发起新选举。选举超时是一个计时器,任何一端被卡住超过这个计时器,就会触发选举。leader 侧 fsync 卡住,心跳就发得晚;follower 侧 fsync 卡住,它处理心跳与日志复制的响应就慢,同时它自己的选举计时器也走完了。
选举期间会发生两件事:一是集群不再接受写入,二是新 leader 需要追平日志才能开始服务。于是那一次 150 毫秒的 fsync 抖动,最终表现为"控制面卡了几秒"。放大倍数来自这里:磁盘抖动的量级是毫秒级,控制面不可用的量级是秒级,中间那几十倍的差距,是选举流程本身带来的,不是磁盘带来的。
还有一层放大器经常被漏掉:etcd 慢了之后,客户端(kube-apiserver)的请求会超时并重试。重试意味着同一批写请求被重复提交,队列更长,延迟更高,触发更多超时。这是一个正反馈。所以真实的故障曲线往往不是"抖一下、恢复、再抖一下",而是"抖一下、雪崩、过十几秒才慢慢缓过来"。排查的时候如果只盯着那一次最初的 fsync 抖动,会低估问题的严重性。
把 etcd 的磁盘 IO 当成一件事来规划,是第二个普遍的错误。它实际上有三处完全不同的敏感点,对 IO 的要求各不相同,出问题时表现也不一样。
WAL 的写入形态是小块、顺序、每次都要 fsync。它对延迟极端敏感,对吞吐几乎不敏感。规划上最重要的一条:WAL 目录要独立。etcd 提供了把 WAL 放到单独目录的参数(常见写法是 --wal-dir,参数名以所用版本官方文档为准),在无法给整块高性能盘的场景下,至少把 WAL 挪到低延迟的盘上,数据目录留在普通盘,收益非常明显。
WAL 还有预分配行为,会预先占用文件空间以减少运行期的元数据开销,在某些文件系统上首启阶段会有一次性的写入放大。这个行为的具体参数与影响,以所用版本官方文档为准。
WAL 不能无限增长,所以 etcd 会在日志条目累积到一定数量后做一次快照:把当前状态机的完整数据序列化写到磁盘,然后截断之前的 WAL。快照的触发阈值与相关参数以所用版本官方文档为准。
快照的写入形态和 WAL 完全不同——它是大块、连续、量与数据库大小同级的顺序写。一个 4G 的 etcd 数据库,一次快照就是 4G 级别的写入。它吃的是吞吐,理论上不会推高 WAL 的延迟。但现实里这两件事发生在同一块盘上:快照的大块写占住了 IO 队列,同一时刻排队的 WAL fsync 就得等它,等待时间直接计入 fsync 的尾延迟。
这就解释了那个非常典型的现象:平时一切正常,每隔一段时间准时抖一次。抖动周期与快照触发周期一致,是最强的判断证据。验证方法也很直接——把 etcd 的快照相关指标(指标名以所用版本官方文档为准)与 wal_fsync_duration_seconds 的分位数画在同一张图上,看两个曲线的高点是否对齐。
etcd 底层用 boltdb,数据的删除与更新会在文件里留下空洞,表现为 db 文件的实际大小大于有效数据量。回收这些空间需要执行碎片整理,也就是 etcdctl defrag。这一处有三个特性必须提前知道。
第一,defrag 期间会阻塞请求处理。它不是后台悄悄跑的整理任务,而是重建整个数据库文件。执行期间这个节点不能正常服务读写。所以绝不能三个节点同时 defrag——那样等于把整个控制面停掉。正确做法是逐节点滚动执行,且每一步之前确认剩余节点健康、集群仍能构成多数派。
第二,defrag 需要额外的临时磁盘空间。重建数据库意味着在一段时间内新旧两份文件同时存在,所需空间与 db 文件大小在同一个量级。这就是"容量从来不是 etcd 的问题"这句话的例外:一个只用了 8G 的 etcd,放在 20G 的分区里,完全可能因为 defrag 的临时空间不足而失败。规划容量时要按"db 实际大小 × 2 加上余量"来算,而不是按"db 实际大小加一点"来算。
第三,自动 defrag 是一把双刃剑。etcd 支持自动碎片整理的相关参数(是否默认开启、触发条件以所用版本官方文档为准)。开启之后省事,代价是 defrag 的发生时机不可控——它可能在业务高峰执行,然后你就得到一个"每天下午三点莫名抖一下"的玄学故障。更可控的做法是关掉自动,改成在低峰期由运维显式执行,执行前后记录 db 大小与耗时,把这件事纳入变更流程而不是交给运气。
长期不做 defrag 也不行。db 文件持续膨胀会同时抬高三个成本:每次快照落盘的数据量、故障恢复时加载状态机的时间、以及内存里需要驻留的工作集。defrag 的频率没有通用答案,盯住 db 大小的增长曲线,在它明显偏离实际数据量时处理,比按固定周期执行更合理。
这一节全是机制推演,具体默认数值一律以所用版本官方文档为准,本文不给出确定的毫秒数。
etcd 的心跳与选举超时存在明确的联动关系:心跳间隔(heartbeat interval)应当贴近成员之间的往返时间,选举超时应当是心跳间隔的若干倍。这个倍数关系的存在是必要的——心跳在网络上可能丢一次,如果选举超时只是心跳间隔的两三倍,一次丢包就会引发误判选举。倍数带来的冗余,正是用来吸收"偶发的一次没收到"。
把 RTT 换成跨城或跨地域的量级,这条关系就开始产生连锁反应。同城三机房的成员间 RTT 通常在亚毫秒到一两毫秒量级;跨城三节点的 RTT 会到几十毫秒量级(预估,实际取决于链路质量与路由)。RTT 抬升之后,心跳间隔必须跟着抬升,否则心跳还在路上、follower 的计时器就走完了,会产生大量无意义的选举。心跳间隔抬升,选举超时按倍数关系跟着抬升,于是整个 Raft 的时间预算被网络 RTT 撑大了。
乍一看这是好事:预算变大了,磁盘抖动更不容易触发选举。但跨地域部署放大问题的地方不在这里,而在写路径。
回到前面那条结论:一次写的耗时等于"多数派里最慢的那个节点的 fsync"加上"网络往返"。三节点跨地域部署时,多数派是两个节点,而两地三节点的分布方式(比如两个节点在 A 地、一个在 B 地,或者三地各一个)决定了多数派里必然至少包含一个远端节点。于是每一次写的关键路径上都挂着一次跨城 RTT,而且这个 RTT 之上还要叠加一次远端磁盘的 fsync。本地 fsync 抖动 100 毫秒,在同城部署里可能刚好被预算吸收;同样的 100 毫秒放在跨城部署里,和几十毫秒的 RTT 叠在一起,就越线了。
还有一个更隐蔽的放大机制。抖动来源的相关性变了。同城三机房的三个节点,往往采购自同一批机器、同一型号同一固件的盘、同一套存储后端,它们的 IO 抖动高度相关——一个慢的时候另外两个也慢,样本虽多但信息冗余。跨地域部署用的是不同机房、不同批次、不同宿主机,三个节点的抖动是相互独立的。独立性听着是好事,但在"取最慢"的语义下它是坏事:多数派里三个节点各自独立抖动,意味着任一时刻出现"某一个节点正在抖"的概率,从"三个一起抖"变成了"三个里至少一个抖",后者要高得多。你需要的是所有节点都不抖,任何一个抖了,这次写就慢了。
第三个放大点来自运维现实:跨地域部署为了控制成本,常常把第三个节点放在配置较低的机器或较便宜的存储上。这个节点于是成为永久的短板——它不只是"偶尔慢",而是"每次写都慢"。前面说过,一次写的耗时由最慢的节点决定,所以给第三个节点配一块差盘,等价于给整个控制面配了一块差盘,另外两块好盘的投入全部白费。
把这一节压缩成一条可执行的原则:跨地域部署对磁盘尾延迟的要求不是更低,而是更高,因为网络 RTT 已经吃掉了预算的一部分,剩下的余量更少。如果必须在跨城三节点上跑控制面,优先保证三个节点的磁盘规格完全一致,并且用实测而不是型号来判断。
| 参数 | 影响面 | 不达标的表现 | 判断方式 | 什么情况下可以放宽 |
|---|---|---|---|---|
| WAL fsync 尾延迟(p99 / p999) | 决定 leader 心跳与提案提交是否被推迟,直接关联是否触发选举 | leader 变更次数异常增多,apiserver 出现 request timed out,控制面周期性卡数秒 | 长时间跑小块同步顺序写的 fio,取 p99/p999;同时看 wal_fsync_duration_seconds 的分位数 | 几乎没有放宽空间;只在测试集群、可接受控制面不可用的场景能忍 |
| 磁盘是否独占、是否本地 | 决定尾延迟是否可控,以及抖动是否与其他负载相关 | 抖动时间点与宿主机上的其他任务(拉镜像、备份、日志滚动)高度吻合 | 确认是否本地盘、是否有突发配额、配额耗尽后的基线值、同宿主机是否共享 IO | 数据目录可以放宽到共享盘;WAL 目录不建议放宽 |
| 内存容量相对 db 大小 | etcd 通过 mmap 访问数据文件,内存不足会让读变成随机磁盘读 | 读延迟随 db 增长而抬升,快照与恢复时间变长,页缓存命中率下降 | 对比 db 文件大小与节点可用内存,观察 page cache 命中与换页情况 | 内存超过 db 大小并留出充足余量后,继续加内存的边际收益接近零 |
| 成员间 RTT 的稳定性与丢包率 | 决定心跳间隔的下限,进而决定选举超时的量级 | 无谓选举、ReadIndex 超时、follower 反复脱离又重新加入 | 看 peer RTT 的分位数而非平均值,同时看重传率与丢包 | 同城三机房可以放宽监控频率;跨城三节点必须持续盯 |
| 数据目录剩余空间(db 大小 × 2 + 余量) | 决定快照能否落盘、defrag 能否执行成功 | defrag 失败、快照写入失败、空间告警后 etcd 进入只读或拒绝写入状态 | 盯 db 文件大小趋势,按两倍大小核对剩余空间 | 若已规划独立的临时目录且 defrag 在低峰手动执行,可略降低冗余 |
| 快照与备份期间的队列占用 | 大块顺序写挤占队列,抬高同一时刻的 WAL fsync 延迟 | 抖动呈周期性,与快照触发周期或备份任务时间点对齐 | 把快照指标与 fsync 分位数画在同一时间轴上比对峰值 | 快照与备份输出到独立磁盘时,此项可以不再关注 |
控制面节点的资源画像和业务节点完全不同,拿业务节点的选型经验套过来,钱会花错地方。
CPU 是多余的那一档资源。Raft 的日志复制、序列化、boltdb 的读写,计算量都不大。中等规模的集群上,四到八核对 etcd 已经相当宽裕。只有在开启 TLS 且客户端连接数非常大、或者 watch 数量极高的场景下,CPU 才可能成为考虑项。反过来说,如果你看到控制面节点的 CPU 长期很高,那大概率不是 etcd 本身的计算压力,而是同机上跑了别的东西,或者客户端在做低效的全量 list。
内存的要求不是"越多越好",而是"要能装下 db"。etcd 通过 mmap 访问底层数据文件,db 越大,需要驻留在内存里的工作集越大。内存够的时候,读操作走页缓存,延迟是内存级别;内存不够的时候,缺页会退化成随机磁盘读,延迟抬升一到两个数量级,而这个抬升又会被算进请求延迟里,进一步逼近超时。内存容量按"db 预期大小加上充足余量"配置即可,超过这条线之后继续加内存的边际收益接近于零。把这笔钱挪到磁盘上更划算。
磁盘是唯一值得多花钱的地方,但花法有讲究。
优先级从高到低排:第一,确认是本地盘还是网络盘。本地 NVMe SSD 是最优解,SATA SSD 如果 fsync 尾延迟实测达标也能用,接口不是判断标准,实测才是。第二,确认 IO 是否独占。独享的物理盘优于共享后端,独享配额的云盘优于突发配额的云盘。第三,确认是否有掉电保护。带电容保护的盘在掉电时能把缓存里的数据落下去,这对"fsync 返回了就必须不丢"的语义是加成。第四,确认 WAL 与数据目录能否分开放在不同的盘上。第五,才是容量。
关于云盘,有三件事必须在下单前问清楚,这三条比看参数表有用:一是标称 IOPS 是持续值还是突发值,配额耗尽后降速到多少;二是这块盘是本地盘还是网络块存储,网络盘的 fsync 要走到存储端再返回;三是同宿主机上是否允许你独占 IO。问不清这三条,就只能靠实测兜底。
在机型比选这件事上,与其按"几核几 G 多少 G 硬盘"的固定套餐去挑,不如把上面这几条变成筛选条件——是否可选本地 NVMe、磁盘 IO 是否独享、能否单独加一块数据盘给 WAL 用。像一万网络这类深耕 19 年(成立于 2007 年)的服务商,控制面节点通常可以按需求单独调整磁盘与内存的组合,而不是只能从固定档位里挑一个最接近的;固定套餐的麻烦在于磁盘规格写死,你明明已经定位到 fsync 是瓶颈,却换不掉那块盘,只能整机升级,多花的钱全落在用不上的核数和容量上。控制面节点的配置与价格属于需询价范畴,公开渠道差异较大,以实时报价为准。
最后说节点数,这是一个明确的取舍。三节点配好盘,通常优于五节点配差盘。五节点的好处是能容忍两个节点同时故障,但它让多数派从 2 变成 3,一次写的最慢节点从"三个里第二慢"变成"五个里第三慢",尾延迟的期望值更差,机器成本也接近翻倍。生产环境用奇数节点是共识(偶数节点不会带来额外容错,却带来额外的确认成本)。只有在真的需要容忍双节点故障、或者有跨机房容灾的硬性要求时,才值得上五节点——而且上五节点的同时,磁盘规格不能降。
控制面节点之间的网络,要求同样不是带宽。etcd 的 peer 流量在稳定运行时很小,只有在新节点加入、快照同步、或者大量 watch 的情况下才会有明显突发。带宽从来不是这里的瓶颈。
第一要看的是 RTT 的分位数,不是平均值。心跳是周期性的单点事件,它遭遇的是"那一次的实际往返时间",不是平均往返时间。一条平均 8 毫秒、p99 60 毫秒的跨城链路,对心跳来说就是"大部分时候正常、偶尔要等 60 毫秒"。这个 60 毫秒会和磁盘 fsync 的尾延迟叠加。测量时要用足够长的时间窗口,覆盖链路的繁忙时段。
第二要看的是丢包与重传。peer 之间走 TCP,丢包触发重传,重传让单次往返时间成倍放大。一条 1% 丢包的链路,带来的不是"慢 1%",而是"偶尔慢好几倍",这正是心跳最怕的形态。监控上要同时看接口的重传统计与 TCP 的重传段计数,只 ping 几下看平均时延是看不出来的。
第三才轮到带宽,而且只在一种情况下才需要关注:新节点加入或者落后节点追赶时,leader 会直接发送完整快照,这个传输量等于整个数据库大小。一个 4G 的 db 在追赶时需要一次性传 4G,如果链路带宽太低且 db 很大,追赶时间会超过 leader 保留日志的时间窗口,导致追赶失败、需要再传一次快照,陷入循环。这也是"别让 etcd 数据库长太大"的一个具体理由。
另外两件属于网络与主机交叉地带、但经常被漏掉的事。
一是时钟同步。etcd 依赖成员之间的时钟不能偏差过大,这关系到租约(lease)的到期判断与证书的有效期校验。NTP 必须配置,而且要监控时钟偏移量——很多团队配了 NTP 就没再管,直到某台机器的时钟漂移导致证书校验失败、节点被踢出集群,才发现时钟已经偏了几分钟。
二是证书有效期。etcd 的 peer 通信与 client 通信都可以开启 TLS。开启之后的加解密开销在控制面这个规模下通常可以忽略,但证书过期是一个会突然发生的全集群故障——不是变慢,是全部连接失败。证书有效期必须纳入监控,并且要在过期前留出足够长的更换窗口。这件事和磁盘无关,但它是控制面不可用原因里排名很靠前的一项,值得在做选型时一起规划进去。
跨城与跨地域部署时,链路的选择与质量属于需询价范畴,公开渠道差异较大,以实时报价为准;不同机房之间的互联质量差异明显,建议先取一段时间的实测 RTT 分位数再决定节点分布。
把两种部署方式的账摊开看,会发现大部分成本不在机器账单上。
机器成本:跨地域通常更贵,但贵得不多。三个节点放在三个不同的城市,和三个节点放在同城三个机房,机器本身的租金差距没有想象中大。真正的差距来自下面几项。
链路成本:这是跨地域的显性增量。三个节点之间需要两两互通,跨城的机房互联链路要单独计费或者占用已有的带宽资源。这笔钱是持续的、按月付的,而且往往比机器租金更刚性——机器可以降配,链路不能随便断。
故障恢复时间:这是隐性的,但最贵。前面推演过,跨城 RTT 会撑大心跳间隔,选举超时按倍数关系跟着变大。这意味着 leader 真的故障之后,集群需要更长时间才选出新 leader,控制面不可用的窗口更长。你可以把它折算成"每次故障多停几秒 × 每年故障次数",虽然粗糙,但比"跨地域更高可用"这种空话有用。跨地域解决的是"整个城市级别的故障",代价是"单点故障恢复变慢",这两个目标不是同一个方向,不要混为一谈。
运维成本:跨地域明显高于同城。证书更换、时钟校准、defrag 的滚动执行、备份窗口的协调,在跨城部署下都要考虑成员不在同一个时区窗口、不在同一套运维通道里的问题。这些工作不体现在账单上,但会持续消耗人力。
然后是几个能真正省下钱的判断。
第一,把五节点的预算改成三节点加更好的盘。同样的钱,五节点意味着每个节点分到的磁盘预算只有五分之三;三节点意味着每个节点能拿到更好的盘。前面论证过,多数派取最慢,五节点让尾延迟的期望值更差。所以这个交换在绝大多数场景下都是净收益:容错从"容忍 1 个节点故障"变成"容忍 0 个",但故障概率本身大幅下降了,因为抖动的根源被消除了。要不要做这个交换,取决于你的故障模式——如果主要故障是节点宕机,五节点更抗;如果主要故障是控制面抖动(这在小规模集群里更常见),三节点好盘更抗。
第二,先花零成本做的事:用 fio 测一遍。跑一轮长时间的 fsync 尾延迟测试,几乎不花任何钱,但能提前排除掉绝大部分问题盘。相比之下,任何一次"上线之后才发现盘不行"的返工,成本都是它的几百倍。这一项的投入产出比高于任何硬件升级。
第三,内存的投入有明确的上限。按 db 预期大小加充足余量配置,超过这条线就停。多出来的内存对 etcd 没有任何收益,不如换一块更好的盘或者加一条监控。
第四,备份与恢复演练要单独算一笔。etcd 的快照备份是大块顺序写,落盘时不要和 WAL 抢同一块盘;备份文件要保存到集群之外的地方。更关键的是恢复演练——没有演练过的备份在可用性上等于零,而演练需要单独准备一套环境。这笔钱很多人省了,然后在真正需要恢复的时候发现备份文件损坏或者版本对不上。
监控这一节先说清楚限定:下面提到的指标名称以所用版本官方文档为准,不同版本之间有增删改,照抄旧版本的名字会取不到数据;本文不给出任何普适的告警阈值数字,阈值必须结合你自己的基线来确定。
etcd 默认暴露 Prometheus 格式的指标,与磁盘直接相关的核心指标是 wal_fsync_duration_seconds,它记录 WAL fsync 的耗时,带分位数标签。这是判断磁盘是否达标的第一指标,没有之一。用法上有一条硬要求:必须按分位数看,不要看平均值。平均值会完全掩盖掉那些真正触发选举的抖动。
与它配合的还有几个:disk_commit_duration_seconds 反映后端提交(把数据写进 boltdb)的耗时;快照相关的指标反映快照落盘的耗时与频率;db 大小相关的指标反映数据文件的实际尺寸;peer 之间 RTT 相关的指标反映成员间网络质量。这些指标的确切名称与标签,以所用版本官方文档为准。
除了磁盘指标,还有一组结果指标,它们不告诉你原因,但能告诉你要不要立刻处理:leader 变更的累计次数、慢应用的累计次数、提案失败与排队提案的数量、健康状态的失败次数。其中 leader 变更次数是最实用的一个——它是"最终受害者",任何底层的磁盘或网络问题最终都会反映在这里。把 leader 变更次数当作入口,把 fsync 分位数和 peer RTT 当作两条排查分支,这个组合能快速区分问题是出在磁盘还是出在网络。
告警设计上有四条思路,比拍脑袋定阈值可靠。
用分位数,不用平均值。p99 或者 p999,取决于你对抖动的容忍度。控制面这种"一次抖动就要选主"的场景,p999 也不算苛刻。
用持续时间,不用单点触发。一次 fsync 冲到几百毫秒可以容忍,连续五分钟 p99 超标才是需要处理的信号。单点触发的告警会淹没人,最后的结果是被静音,等于没有告警。
用速率,不用绝对值。leader 变更的告警应该做成"单位时间内的变更次数"或者"变更速率的突变",而不是"累计次数超过某个数"。累计次数是个只增不减的计数器,用它做阈值毫无意义。
用基线法,不用通用阈值。先跑一到两周采集基线,用基线的倍数作为告警线。不同硬件、不同规模、不同负载形态下的合理值差异极大,任何从别处抄来的绝对数字都不如自己的基线可靠。基线法还有一个附带好处:基线本身就是一份容量与性能档案,扩容和换盘的时候可以直接拿来对比。
最后把 fio 和指标对起来看,这是判断"盘到底行不行"的闭环。fio 要按 etcd 的真实写入形态来测:小块、顺序、每次同步刷盘、单任务(不靠并发掩盖延迟)、跑足够长的时间。etcd 官方文档给出过这种思路的 fio 测试示例(具体命令与参数以所用版本官方文档为准),核心是三个要素:fdatasync=1 保证每次写都真的刷盘,块大小贴近 WAL 的实际写入尺寸,测试时长要长到能覆盖 SSD 的垃圾回收与缓存写满阶段。测出来之后取 p99 与 p999,再和 wal_fsync_duration_seconds 的分位数对比——如果 fio 的 p99 就已经很高,etcd 的 p99 不可能低,这时候就该换盘而不是调参。
误判一:按容量选盘。etcd 的数据库通常只有几 G,容量在任何一块盘上都不是问题。按容量挑出来的盘,往往是"低速大容量"那一类,正好是 etcd 最不适配的。正确的顺序是先看 fsync 尾延迟,容量放到最后。
误判二:把 WAL 放在共享存储上。NAS、网络块存储、SAN 这类方案,每次 fsync 都要走到存储端,延迟基数高、抖动幅度大。它们适合做共享文件与归档,不适合做 WAL。至少把 WAL 目录留在本地盘上,数据目录可以妥协。
误判三:三个节点共用同一台宿主机或者同一块物理盘。这是最危险的一种。表面上是一个三节点高可用集群,实际上是一个单点——同一台宿主机上的 IO 抖动会同时打中三个节点,失去多数派。判断标准很简单:任何一个能同时影响两个及以上节点的故障,都让这个"三节点"失去意义。共用宿主机、共用物理盘、共用交换机、共用电源,都要按这个标准过一遍。
误判四:把 defrag 交给自动执行且不监控。自动 defrag 会让整理动作在不可控的时机发生,而且它执行期间会阻塞该节点的请求处理。改成手动、逐节点、在低峰期执行,执行前确认剩余节点健康、磁盘剩余空间足够,执行后记录 db 大小与耗时。
误判五:让数据目录与高 IO 的邻居共用一块盘。容器运行时、镜像层、系统日志、监控 agent 的本地缓冲、备份输出,这些都会产生大块或者高频写入。它们平时不显眼,一旦有大动作就占满 IO 队列。至少把快照输出与备份输出引导到独立磁盘。
误判六:用短时间的随机读写 fio 判断磁盘。跑三十秒、开高并发、测随机读写,测出几十万 IOPS,这个数字对 etcd 没有任何参考价值。etcd 要的是单任务、小块、顺序、每次同步刷盘的长时尾延迟。测法不对,测出来的数字越好看,误导性越强。
在标准化服务器租用方案里挑控制面节点时,上面这六条可以直接变成核对清单,重点核实三件事:磁盘类型与是否本地、IO 是否独享、能否单独加一块盘给 WAL 和数据目录分开使用。一万网络这类深耕 19 年(成立于 2007 年)的服务商,在这些规格项上通常能给出明确答复,也支持按控制面的需求单独搭配内存与磁盘;选型时优先把这几项核清楚,而不是只看套餐里写着的"SSD 云盘多少 G"。控制面节点的配置与报价属于需询价范畴,公开渠道差异较大,以实时报价为准。
还有一条容易被忽略的收尾工作:备份与恢复演练。etcd 的快照备份是大块顺序写,落盘位置要避开 WAL 所在的盘;备份文件要保存在集群之外(跨机房,跨城市更好);恢复演练必须真的做过一遍,验证备份文件完整、版本兼容、恢复流程可执行。没有演练过的备份,在真正需要的时候有相当概率是坏的,而这个概率你平时看不到。
问:etcd 必须用 NVMe 吗?
不是。判断标准从来不是接口,而是 fsync 的尾延迟是否达标。一块尾延迟稳定且 p99 达标的 SATA SSD,跑 etcd 完全没问题;一块标称 NVMe 但走网络后端、突发配额用完就降速的盘,反而更差。NVMe 的价值在于它更容易达标,且队列深度与延迟特性更好,但它不是准入条件。决定之前先跑一轮长时间的 sync 顺序写测试。
问:etcd 能和业务负载放在同一批机器上吗?
不推荐,小规模集群可以在严格限定的前提下做。风险有两条:一是业务的 IO 会挤占 fsync 队列,把尾延迟抬上去;二是资源竞争会让 etcd 进程的调度被推迟,同样算进延迟。如果确实要混布,至少做到三点:WAL 放在独立磁盘上、给 etcd 配置资源保障并设定合适的优先级、把混布节点上的高 IO 负载(镜像拉取、日志落盘、本地缓存)迁走。
问:云盘怎么判断 fsync 是否达标?
云盘的核心困难是"你看不见物理盘",只能用间接手段。三件事:一是用 fio 在真实负载下长时间测 sync 顺序写的 p99 与 p999,空载测出来的数字不作数;二是向服务商问清是否有 IOPS 与吞吐的突发配额、配额耗尽后降速到什么水平;三是问清是本地盘还是网络块存储。三条都问清楚了,再决定要不要把控制面放上去。
问:三节点还是五节点?
多数场景是三节点加好盘优于五节点加差盘。五节点让多数派从 2 变 3,一次写的最慢节点样本变差,尾延迟期望更高,成本也接近翻倍。需要容忍两个节点同时故障、或者有跨机房容灾的硬性要求时才上五节点,而且上五节点的同时磁盘规格不能降。偶数节点不要考虑——它不带来额外容错。
问:已经出现频繁选主了,第一步做什么?
不要第一步就去调心跳间隔和选举超时。调大超时只是把阈值抬高,抖动本身还在,而且代价是真实故障的恢复变慢。正确的第一步是采集两个数据:一是 wal_fsync_duration_seconds 的分位数,二是 peer 之间的 RTT 分位数。两个数据放一起看:fsync 分位数高而 RTT 正常,问题在磁盘;RTT 分位数高而 fsync 正常,问题在网络;两个都高,说明磁盘与网络都有余量问题。定位之后再决定是换盘、调参、还是改节点分布。
问:etcd 的数据库大小有没有上限?
具体上限以所用版本官方文档为准,本文不给数字。比起记住一个上限,更有用的是盯住 db 大小的增长趋势,并理解它变大之后的连锁反应:每次快照落盘的数据量变大、defrag 耗时变长、内存需要驻留的工作集变大、故障恢复时加载状态机的时间变长、新节点追赶时需要传输的快照变大。真正的解法是别把业务数据写进 etcd——etcd 是为集群状态设计的,不是为业务数据设计的。
把整篇的判断压缩成一个可执行的顺序,从上往下走,走到哪一层不满足就在哪一层解决。
第一层,fsync 尾延迟。用单任务、小块、顺序、每次同步刷盘的 fio 长时间测试,取 p99 与 p999。这一层不达标,后面所有优化都是徒劳。同时把 wal_fsync_duration_seconds 的分位数接进监控,作为长期观测项。
第二层,磁盘是否独占、是否本地。确认没有邻居抢队列,确认不是网络后端,确认没有突发配额在暗中降速。这一层决定第一层的数字能不能长期稳定,而不是只在空载时好看。
第三层,内存能否覆盖 db 大小。按 db 预期大小加充足余量配置,超过这条线就停止投入。这一层的失败不会立刻表现为故障,而是表现为"db 越大越慢",很容易被误判成磁盘问题。
第四层,成员间 RTT 的稳定性与丢包。看分位数与重传,不看带宽。跨城与跨地域部署时这一层的权重最高,因为网络 RTT 会吃掉 Raft 时间预算的一大块。
第五层,才轮到容量。按 db 大小的两倍加余量规划,为 defrag 的临时空间留足。
这套顺序背后的判断只有一句:控制面的磁盘问题是延迟问题,不是容量问题,也不是吞吐问题。开篇那个"三节点都在、CPU 空闲、kubectl 却卡几秒"的现场,如果按"资源使用率"的思路去查,会永远查不到原因;按这个顺序从上往下走,第一层就能定位。
还有一个更省钱的推论值得再说一遍:在这五层里,第一层和第二层的改进几乎不需要增加硬件预算——换一块已经决定要买的盘、把 WAL 挪到独立目录、把日志和镜像挪走,这些都是配置层面的动作。真正的增量成本在第三层之后。所以正确的动作顺序是:先测,再调配置,最后才谈升级硬件。跳过测量直接升级,是这个场景里最贵的一类错误。
指标名称与参数默认值口径:本文提到的 wal_fsync_duration_seconds、disk_commit_duration_seconds、快照相关指标、db 大小相关指标、peer RTT 相关指标,以及心跳间隔(heartbeat interval)、选举超时(election timeout)、快照触发阈值、自动碎片整理参数、--wal-dir 等配置项,名称、标签、默认值与行为均以所用的 etcd 版本官方文档为准。不同版本之间存在增删改,本文刻意不给出确定的毫秒数与具体阈值,读者应自行核对所用版本。涉及 Kubernetes 侧的报错文本(如 etcdserver: request timed out)为日志中的常见表述形式,具体措辞随版本变化。
机制推演部分:关于"写耗时等于多数派中最慢节点的 fsync 加上网络往返""选举超时应当为心跳间隔的若干倍""跨地域部署使节点抖动相互独立、从而提高多数派中出现慢节点的概率""五节点让尾延迟期望变差"等结论,均为基于 Raft 共识算法的一致性语义与复制机制的机制推演,不是实测数据。文中标注为(预估)的部分为工程经验值,需结合自身环境验证。
故障现象部分:开篇描述的"CPU 空闲、资源充足但 kubectl 偶发卡住数秒,日志中出现 leader 变更与 request timed out"为该类故障的典型表现归纳,用于说明问题机理,不代表某一具体客户环境。文中出现的停顿时长、抖动幅度等数字均为现象描述量级,非实测统计。
磁盘判断方式:fio 测试方法按 etcd 官方文档给出的思路整理(小块、顺序、每次同步刷盘、单任务、长时间运行、取高分位数),具体命令与参数以所用版本官方文档为准。测试需在真实负载下进行并覆盖 SSD 垃圾回收与缓存写满阶段,空载短时测试结果不具备参考价值。fio 的 p99/p999 应与 wal_fsync_duration_seconds 的分位数交叉验证。
告警阈值口径:本文仅给出告警设计思路(用分位数、用持续时间、用速率、用基线),未给出任何普适阈值数字。阈值应基于一到两周的自身基线确定。
自我核对方式:上线前按本文第五层顺序逐项核对,记录六项指标——fio 的 fsync p99 与 p999、WAL 与数据目录是否分离、db 大小与可用内存的比值、peer RTT 的 p99、数据目录剩余空间与 db 大小的两倍之比、快照与备份的输出磁盘是否独立。上线后持续比对,任一项接近临界即按上述顺序处理,且处理前先区分问题来自磁盘还是网络。
价格相关说明:本文涉及服务器配置、跨机房链路与租用方案的部分,一律为需询价,公开渠道差异较大,以实时报价为准。文中未给出任何具体金额。地域表述统一为「中国香港」等完整形式。品牌口径为「深耕 19 年(成立于 2007 年)」。
Copyright © 2013-2020 idc10000.net. All Rights Reserved. 一万网络 科技有限公司 版权所有 深圳市科技有限公司 粤ICP备07026347号
本网站的域名注册业务代理北京新网数码信息技术有限公司的产品