三年之久的 etcd3 数据不一致 bug 分析

本文分析了一起因etcd3数据不一致导致的K8S滚动更新异常问题,经过排查确定是由于etcd集群节点间的AuthRevision不一致。问题源于权限操作后consistentIndex未持久化,导致重启后幂等性破坏。修复方案是确保auth操作调用commitRevision后的consistentIndex持久化。升级到修复版本前,建议避免在升级过程中触发相同问题。
摘要由CSDN通过智能技术生成

问题背景

诡异的 K8S 滚动更新异常

笔者某天收到同事反馈,测试环境中 K8S 集群进行滚动更新发布时未生效。通过 kube-apiserver 查看发现,对应的 Deployment 版本已经是最新版,但是这个最新版本的 Pod 并未创建出来。

针对该现象,我们最开始猜测可能是 kube-controller-manager 的 bug 导致,但是观察 controller-manager 日志并未发现明显异常。第一次调高 controller-manager 的日志等级并进行重启操作之后,似乎由于 controller-manager 并没有 watch 到这个更新事件,我们仍然没有发现问题所在。此时,观察 kube-apiserver 日志,同样也没有出现明显异常。

于是,再次调高日志等级并重启 kube-apiserver,诡异的事情发生了,之前的 Deployment 正常滚动更新了!

etcd 数据不一致 ?

由于从 kube-apiserver 的日志中同样无法提取出能够帮助解决问题的有用信息,起初我们只能猜测可能是 kube-apiserver 的缓存更新异常导致的。正当我们要从这个切入点去解决问题时,该同事反馈了一个更诡异的问题——自己新创建的 Pod,通过 kubectl查询 Pod 列表,突然消失了!纳尼?这是什么骚操作?经过我们多次测试查询发现,通过 kubectl 来 list pod 列表,该 pod 有时候能查到,有时候查不到。那么问题来了,K8s api 的 list 操作是没有缓存的,数据是 kube-apiserver 直接从 etcd 拉取返回给客户端的,难道是 etcd 本身出了问题?

众所周知,etcd 本身是一个强一致性的 KV 存储,在写操作成功的情况下,两次读请求不应该读取到不一样的数据。怀着不信邪的态度,我们通过 etcdctl 直接查询了 etcd 集群状态和集群数据,返回结果显示 3 个节点状态都正常,且 RaftIndex 一致,观察 etcd 的日志也并未发现报错信息,唯一可疑的地方是 3 个节点的 dbsize 差别较大。接着,我们又将 client 访问的 endpoint 指定为不同节点地址来查询每个节点的 key 的数量,结果发现 3 个节点返回的 key 的数量不一致,甚至两个不同节点上 Key 的数量差最大可达到几千!而直接通过 etcdctl 查询刚才创建的 Pod,发现访问某些 endpoint 能够查询到该 pod,而访问其他 endpoint 则查不到。至此,基本可以确定 etcd 集群的节点之间确实存在数据不一致现象。

问题分析和排查过程

遇事不决问Google

强一致性的存储突然数据不一致了,这么严重的问题,想必日志里肯定会有所体现。然而,可能是 etcd 开发者担心日志太多会影响性能的缘故,etcd 的日志打印的比较少,以至于我们排查了 etcd 各个节点的日志,也没有发现有用的报错日志。甚至是在我们调高日志级别之后,仍没有发现异常信息。

作为一个21世纪的程序员,遇到这种诡异且暂时没头绪的问题,第一反应当然是先 Google 一下啦,毕竟不会 StackOverFlow 的程序员不是好运维!Google 输入“etcd data inconsistent” 搜索发现,并不是只有我们遇到过该问题,之前也有其他人向 etcd 社区反馈过类似问题,只是苦于没有提供稳定的复现方式,最后都不了了之。如 issue:

https://github.com/etcd-io/etcd/issues/9630

https://github.com/etcd-io/etcd/issues/10407

https://github.com/etcd-io/etcd/issues/10594

https://github.com/etcd-io/etcd/issues/11643

由于这个问题比较严重,会影响到数据的一致性,而我们生产环境中当前使用了数百套 etcd 集群,为了避免出现类似问题,我们决定深入定位一番。

etcd 工作原理和术语简介

在开始之前,为方便读者理解,这里先简单介绍下 etcd 的常用术语和基本读写原理。

术语表:

etcd 是一个强一致性的分布式 KV 存储,所谓强一致性,简单来说就是一个写操作成功后,从任何一个节点读出来的数据都是最新值,而不会出现写数据成功后读不出来或者读到旧数据的情况。etcd 通过 raft 协议来实现 leader 选举、配置变更以及保证数据读写的一致性。下面简单介绍下 etcd 的读写流程:

写数据流程(以 leader 节点为例,见上图):

  1. etcd 任一节点的 etcd server 模块收到 Client 写请求(如果是 follower 节点,会先通过 Raft 模块将请求转发至 leader 节点处理)。
  2. etcd server 将请求封装为 Raft 请求,然后提交给 Raft 模块处理。
  3. leader 通过 Raft 协议与集群中 follower 节点进行交互,将消息复制到follower 节点,于此同时,并行将日志持久化到 WAL。
  4. follower 节点对该请求进行响应,回复自己是否同意该请求。
  5. 当集群中超过半数节点((n/2)+1 members )同意接收这条日志数据时,表示该请求可以被Commit,Raft 模块通知 etcd server 该日志数据已经 Commit,可以进行 Apply。
  6. 各个节点的 etcd server 的 applierV3 模块异步进行 Apply 操作,并通过 MVCC 模块写入后端存储 BoltDB。
  7. 当 client 所连接的节点数据 apply 成功后,会返回给客户端 apply 的结果。

读数据流程&#x

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值