前言
团队的产品中有一个组件叫做 volume-provisioner,主要实现了 provsioner 的能力:挂载目录,根据 pvc 申请去创建 pv 以及对应的目录。
组件是 Daemonset 资源,通过 LeaderElection 进行选主,实现逻辑也较为简单,主实例根据请求中的目标节点,去 exec 到该节点的实例中,在挂载目录下执行操作。
这样一个稳定跑了三四年的组件,突然一天在某个客户的环境中崩掉了,所有实例卡在选举过程中,导致流水线任务、Addon创建失败,为此研究了一下。
排查过程
集群环境是阿里云托管版,一开始的确怀疑过这个方向,因为 kubernetes 中的选举大概率与 ETCD 脱不了干系,托管版又无法直接看到 Master 组件相关的信息,于是直接从代码下手。
首先来说,所有的实例都卡在选主的过程中,怀疑的一个方向就是已经有人持锁当 leader 了,所以先从选举的方式来看起,抓出这个 leader 来。
选举方式
组件通过这个包实现的:https://github.com/kubernetes-sigs/sig-storage-lib-external-provisioner
从代码中可以看到,选举是通过 endpoints 作为资源锁来进行的,leader 持有锁以后会不断的进行续租。
LeaderElection 同样可以通过 configmap 作为资源锁
这时候查看 ep 资源,的确有两个与 sc 相关的 endpoint
查看具体的信息,在 annotaion 中可以看到当前持锁的实例是谁,如下两个字段是主要信息
- holderIdentity:当前的持锁的实例信息
- renewTime:最近一次的续租时间
control-plane.alpha.kubernetes.io/leader: '{"holderIdentity":"xxxxxxxxxxx_5928a5b2-968a-42c5-b5b0-85123e027030","leaseDurationSeconds":15,"acquireTime":"2021-12-14T06:05:15Z","renewTime":"2022-02-12T14:29:03Z","leaderTransitions":15}'
这个名称是怎么组成的,继续查看代码,发现由 hostname + _ + uuid
的格式组成
消失的 Pod
了解了命名规则就好说了,hostname 对于 pod 来说就是 pod 的名称,也就是 uuid 前面这部分就是当前主实例的 pod 名称。可找到你了,当兴奋的去查这个 pod 的时候,发现集群中并没有这个 pod。
稍微懵了一下,想到了下一个排查的方向,会不会某个节点上有进程没有退出,一直在参与选举,之后的一个操作让我更加的确信了这个想法。
首先给组件的 ds 增加亲和性,停掉所有的实例,这时候删除两个 ep 资源,马上会创建出新的 ep,并且查看锁持有者仍是那个 Pod,确认了这一点,快速遍历了一下所有节点的进程,奇怪的是并没有发现有残留的进程在 :(
临时解决方法
这时候客户急着用,加上手上还有很多其他工作。就关掉了选举,每个实例通过事件请求中的节点信息与自己的所在的节点是否匹配,来决定是否消费这个事件执行操作。
大概操作如下,如果你也有类似的问题可以临时这样解决掉, 传入 controller.LeaderElection(false)
即可
pc = controller.NewProvisionController(client, config.LocalProvisionerName, lvp, version.GitVersion, controller.LeaderElection(false))
可以通过环境变量注入来知道当前 ds 所在的节点信息
name NODE_NAME valueFrom fieldRef fieldPath spec.nodeName
二次排查
问题原因
手里工作宽松后,又继续看起了这个问题,这次找到了一些眉目。进入集群查看 ep 信息,发现锁已经被释放掉了,正是这个关键的锁,解开了疑惑。
可以看到锁是 2月12日申领的,到2月18日是最后的续约时间,与同事确认过,在 2月18日左右客户下线了一部分机器并且对部分机器进行了重启。
所以之前的怀疑方向没什么太大的问题,就是有残留的进程在不断的持锁。但是为什么当时没有搜到这个进程呢。
其中有个比较值得注意的地方,客户本次下线机器并没有按照我们提供的标准流程去进行,具体的操作已经不得而知了,根据这点,我进行了一种场景的模拟,得到了一样的结果。
场景复原
之前对所有节点的进程进行了排查,并没有发现异常,所以大概率这个进程并不在集群的节点中了。
但是不在集群中的节点 kubelet 会自动的去清理驱逐掉节点上的容器,如果这时候 kubelet 是异常的呢,容器并没有被清理掉,仍然在与 apiserver 进行通信,参与选举,带着这个疑问进行了一个场景复原,具体流程如下。
- 首先确认实验环境中持锁的 pod 以及其所在的节点。可以确认的是
zkb5w
这个 POD,在node132
这个节点
- 这时候我们到 132 节点,停止到它的 kubelet。此时查看 ep 发现还是在正常的进行续约。
- 此时从集群中移除掉
node132
这个节点
- 这时候我们再次查看 ep 中锁的持有者,仍然是
zkb5w
这个实例,多次查看,发现仍然在进行续约。此时从集群中已经找不到这个 Pod 了。
场景到这里基本就完毕了,这只是一种可能性。但是可以确定的是,集群中的实例都停止的情况下,资源锁仍然被持有被续约,是有其他的进程在作祟。这个 “看不见摸不着” 的 Leader 在背后抢占着这一切。
虽然无法查找到当时导致问题的详细步骤了,但是对于 LeaderElection 有了一个新的了解,还是很值得的。