磁盘 IO
除了网络延迟,磁盘 IO 也严重影响 etcd 的稳定性, etcd需要持久化数据,对磁盘速度很敏感,强烈建议对 ETCD 的数据挂 SSD。
另外,要确认机器上没有其他高 IO 操作,否则会影响 etcd 的 fsync,导致 etcd 丢失心跳,leader更换等。一般磁盘有问题时,报错的关键字类似于:
took too long (1.483848046s) to execute etcdserver: failed to send out heartbeat on time
磁盘 IO 可以通过监控手段提前发现,并预防这类问题的出现
快照
etcd的存储分为内存存储和持久化(硬盘)存储两部分,内存中的存储除了顺序化的记录下所有用户对节点数据变更的记录外,还会对用户数据进行索引、建堆等方便查询的操作。而持久化则使用预写式日志(WAL:Write Ahead Log)进行记录存储。
在WAL的体系中,所有的数据在提交之前都会进行日志记录。在etcd的持久化存储目录中,有两个子目录。一个是WAL,存储着所有事务的变化记录;另一个则是snapshot,用于存储某一个时刻etcd所有目录的数据。通过WAL和snapshot相结合的方式,etcd可以有效的进行数据存储和节点故障恢复等操作。
既然有了WAL实时存储了所有的变更,为什么还需要snapshot呢?随着使用量的增加,WAL存储的数据会暴增,为了防止磁盘很快就爆满,etcd默认每10000条记录做一次snapshot,经过snapshot以后的WAL文件就可以删除。而通过API可以查询的历史etcd操作默认为1000条。
客户端优化
etcd 的客户端应该避免一些频繁操作或者大对象操作,如:
- put 时避免大 value,精简再精简(例如 k8s 中 crd 使用)
- 避免创建频繁变化的 kv(例如 k8s 中 node 信息汇报),如 node-lease
- 避免创建大量 lease,尽量选择复用(例如 k8s 中 event 数据管理)
- 合理利用 apiserver 中的缓存,避免大量请求打到 etcd上,如集群异常恢复后大量 pod同步
其他
你可能还看到过lease revoke 、boltdb、内存优化等方式,这些已经合入了最新的 etcd3.4版本,因此选择最新的 release 版本也是提高稳定性的一种方式。
压缩机制
Etcd作为 KV 存储,会为每个 key 都保留历史版本,比如用于发布回滚、配置历史等。
对 demo 写入值为 101,然后更为为 102,103。-w json 可以输出这次写入的 revision
etcdctl put demo 101 -w json etcdctl put demo 102 -w json etcdctl put demo 103 -w json 返回类似: {"header":{"cluster_id":4871617780647557296,"member_id":3135801277950388570,"revision":434841,"raft_term":2}}
取值:
etcdctl get demo 默认 --rev=0即最新值=103 如果要拿到历史值,需要制定 rev 版本 etcdctl get demo --rev=434841,得到 102
观察key的变化:
etcdctl watch foo --rev=0
历史版本越多,存储空间越大,性能越差,直到etcd到达空间配额限制的时候,etcd的写入将会被禁止变为只读,影响线上服务,因此这些历史版本需要进行压缩。
数据压缩并不是清理现有数据,只是对给定版本之前的历史版本进行清理,清理后数据的历史版本将不能访问,但不会影响现有最新数据的访问。
手动压缩
etcdctl compact 5。 在 5 之前的所有版本都会被压缩,不可访问 如果 etcdctl get --rev=4 demo,会报错 Error: rpc error: code = 11 desc = etcdserver: mvcc: required revision has been compacted
手动操作毕竟繁琐,Etcd提供了启动参数 “–auto-compaction-retention” 支持自动压缩 key 的历史版本,以小时为单位
etcd --auto-compaction-retention=1 代表 1 小时压缩一次
v3.3之上的版本有这样一个规则:
如果配置的值小于1小时,那么就严格按照这个时间来执行压缩;如果配置的值大于1小时,会每小时执行压缩,但是采样还是按照保留的版本窗口依然按照用户指定的时间周期来定。
k8s api-server支持定期执行压缩操作,其参数里面有这样的配置:
– etcd-compaction-interval 即默认 5 分钟一次
你可以在 etcd 中看到这样的压缩日志,5 分钟一次:
Apr 25 11:05:20 etcd[2195]: store.index: compact 433912 Apr 25 11:05:20 etcd[2195]: finished scheduled compaction at 433912 (took 1.068846ms) Apr 25 11:10:20 etcd[2195]: store.index: compact 434487 Apr 25 11:10:20 etcd[2195]: finished scheduled compaction at 434487 (took 1.019571ms) Apr 25 11:15:20 etcd[2195]: store.index: compact 435063 Apr 25 11:15:20 etcd[2195]: finished scheduled compaction at 435063 (took 1.659541ms) Apr 25 11:20:20 etcd[2195]: store.index: compact 435637 Apr 25 11:20:20 etcd[2195]: finished scheduled compaction at 435637 (took 1.676035ms) Apr 25 11:25:20 etcd[2195]: store.index: compact 436211 Apr 25 11:25:20 etcd[2195]: finished scheduled compaction at 436211 (took 1.17725ms)
碎片整理
进行压缩操作之后,旧的revision被清理,会产生内部的碎片,内部碎片是指空闲状态的,能被etcd使用但是仍然消耗存储空间的磁盘空间,去碎片化实际上是将存储空间还给文件系统。
# defrag命令默认只对本机有效 etcdctl defrag # 如果带参数--endpoints,可以指定集群中的其他节点也做整理 etcdctl defrag --endpoints
如果etcd没有运行,可以直接整理目录中db的碎片
etcdctl defrag --data-dir <path-to-etcd-data-dir>
碎片整理会阻塞对etcd的读写操作,因此偶尔一次大量数据的defrag最好逐台进行,以免影响集群稳定性。
etcdctl执行后的返回 Finished defragmenting etcd member[https://127.0.0.1:2379]
存储空间
Etcd 的存储配额可保证集群操作的可靠性。如果没有存储配额,那么 Etcd 的性能就会因为存储空间的持续增长而严重下降,甚至有耗完集群磁盘空间导致不可预测集群行为的风险。一旦其中一个节点的后台数据库的存储空间超出了存储配额,Etcd 就会触发集群范围的告警,并将集群置于接受读 key 和删除 key 的维护模式。只有在释放足够的空间和消除后端数据库的碎片之后,清除存储配额告警,集群才能恢复正常操作。
启动 etcd 时。–quota-backend-bytes 默认为 2G,2G 一般情况下是不够用的,
你可以通过 etcdctl endpoint status 命令来查看当前的存储使用量
在 3.4 版本中,etcd 的存储容量得到了提高,你可以设置 100G 的存储空间,当然并不是越大越好,key 存储过多性能也会变差,根据集群规模适当调整。
另外,–max-request-bytes 限制了请求的大小,默认值是1572864,即1.5M。在某些场景可能会出现请求过大导致无法写入的情况,可以调大到10485760即10M。
如果遇到空间不足,可以这样操作:
# 获取当前版本号 $ rev=$(ETCDCTL_API=3 etcdctl endpoint status --write-out="json" | egrep -o '"revision":[0-9]*' | egrep -o '[0-9]*') # 压缩所有旧版本 $ ETCDCTL_API=3 etcdctl compact $rev # 去碎片化 $ ETCDCTL_API=3 etcdctl defrag # 取消警报 $ ETCDCTL_API=3 etcdctl alarm disarm # 测试通过 $ ETCDCTL_API=3 etcdctl put key0 1234
快照备份
etcd可以定期做备份、以保证数据更好的持久化。通过加载备份数据,etcd可以将集群恢复到具有已知良好状态的时间点。
使用命令etcdctl:
etcdctl snapshot save backup.db etcdctl --write-out=table snapshot status backup.db +----------+----------+------------+------------+ | HASH | REVISION | TOTAL KEYS | TOTAL SIZE | +----------+----------+------------+------------+ | fe01cf57 | 10 | 7 | 2.1 MB | +----------+----------+------------+------------+
learner 角色
learner 是 etcd 3.4 版本中增加的新角色,类似于 zookeeper 的 observer, 不参与 raft 投票选举。通过这个新角色的引入,降低了加入新节点时给老集群的额外压力,增强了集群的稳定性。除此之外还可以使用它作为集群的热备或服务一些读请求。
举例,如果 etcd集群需要加入一个新节点,新加入的 etcd 成员因为没有任何数据,因此需要从 leader 那里同步数据,直到赶上领导者的日志为止。这样就会导致 leader 的网络过载,导致 leader 和 member 之间的心跳可能阻塞。然后就开始了新的leader选举,也就是说,具有新成员的集群更容易受到领导人选举的影响。领导者的选举以及随后向新成员的更新都容易导致一段时间的群集不可用,这种是不符合预期,风险也是很大的。
因此为了解决这个问题,raft 4.2.1 论文中介绍了一种新的节点角色:Learner。加入集群的节点不参与投票选举,只接收 leader 的 replication message,直到与 leader 保持同步为止。
learner 在网络分区等场景下的处理,可以详细参考:https://etcd.io/docs/v3.3.12/learning/learner/
具体操作:
# 增加一个节点作为learner member add --learner # 当learner的日志赶上了leader的进度时,将learner提升为有投票权的成员,然后该成员将计入法定人数 member promote etcd server 会验证 promote 请求以确保真实
在提升之前,learner仅充当备用节点,leader无法转移给learner。learner拒绝客户端读写(客户端平衡器不应将请求路由到learner)
另外,etcd限制了集群可以拥有的learner总数,并避免了日志复制导致领导者过载。learner永远不会自我提升。
etcd client v3
Etcd client v3是基于grpc实现的,而grpc又是基于http2.0实现的,借用了很多 http2的优势如二进制通讯、多路复用等,因此整体上借用grpc的框架做地址管理、连接管理、负载均衡等,而底层对每个Etcd的server只需维持一个http2.0连接。
Etcd client v3实现了grpc中的Resolver接口,用于Etcd server地址管理。当client初始化或者server集群地址发生变更(可以配置定时刷新地址)时,Resolver解析出新的连接地址,通知grpc ClientConn来响应变更。
client v3的原理解析可以看这篇文章:https://www.jianshu.com/p/281b80ae619b
我们是用etcd client做应用的选主操作,可以看下这篇
这里提一下,最早的时候以为 kubernetes 中的 scheduler、controller-manager是基于 etcd 做选主的,client拿来直接用很方便。后来发现不是,kubernetes 是用抢占 endpoint 资源的方式实现选主逻辑,不依赖外部 etcd,这么想来也合理,严格来讲,etcd 不是kubernetes的东西,不应该有太多依赖。
k8s 中 scheduler 的选主逻辑可以看这篇文章
问题排查
列几个常遇到的 etcd 问题,后面监控部分会提到如何监测、预防这类问题
一个节点宕机
一个节点宕机,并不会影响整个集群的正常工作,慢慢修复。
- 移出该节点:etcdctl member remove xx
- 修复机器问题,删除旧的数据目录,重新启动 etcd 服务
- 因为 etcd 的证书需要签入所有节点 ip,因此这里的节点不能更改 ip,否则要全部重签证书,重启服务
- 重启启动 etcd 时,需要将配置中的 cluster_state改为:existing,因为是加入已有集群,不能用 new
- 加入 memeber: etcdctl member add xxx –peer-urls=https://x.x.x.x:2380
- 验证:etcdctl endpoint status
迁移数据
如果你的集群需要更换所有的机器,包括更换 IP,那就得通过快照恢复的方式了
使用 etcdctl snapshot save 来保存现有数据,新集群更换后,使用 restore 命令恢复数据,在执行快照时会产生一个 hash 值,来标记快照内容后面恢复时用于校验,如果你是直接复制的数据文件,可以–skip-hash-check 跳过这个检查。
迁移集群会更换证书和端点,因此一定会影响上层服务,在迁移之前一定要做好新旧切换,如 apiserver 分批升级(会有部分数据不一致)、避免服务宕机时间过长等
failed to send out heartbeat on time
这个前面已经提过,大概率是因为磁盘性能不足,导致心跳失败,更换 SSD 或者排查机器上高 IO 的进程
request ... took too long to execute 这类报错也是同理
mvcc: database space exceeded
存储空间不足,参考上面提到的清理和恢复步骤,或者提高存储空间
endpoints问题
尽量不要使用lb 作为 etcd endpoints 配置,etcd client 是 grpc 访问,请使用默认的 全量list ,客户端做负载均衡的方式。详细内容可以参考 grpc 负载均衡场景解析
监控
etcd 默认以/metrics的 path 暴露了监控数据,数据为 prometheus 标准格式。
通过 metric 数据可以配置出如下面板,一般我们关心的数据,或者说需要配置报警的内容:
- 是否有 leader:集群就不可用了
- leader 更换次数:一定时间内频率过高一般是有问题,且leader 更换会影响到上层服务
- rpc 请求速率:即 qps,可以评估当前负载
- db 总大小:用于评估数据量、压缩策略等
- 磁盘读写延迟:这个很关键,延迟过高会导致集群出现问题