最近发生一件很诡异的事情, 某个ns下的pods会莫名其妙地被删了, 困扰了好一阵子,排查后发现问题的起因还是挺有意思。
问题现象
交代一下背景, 这些pod都是由argo-workflow发起的pod, 执行完特定的任务之后就会变成Succeeded, 如果执行时有问题,状态可能是Failed.
结果很直接,就是在ns下的某些状态为 Failed pod会被删掉(后来证实Succeeded状态的也会被删掉),所以会出现尴尬的情况是想找这个pod的时候,发现这个pod却没了,之前反映过类似的问题,但一直以为是被别人删了,没有在意,但是第二次出现,感觉不是偶然
开发同学肯定没权限做这个事,运维侧也可以肯定没有这类操作,排查了一圈几乎可以肯定的是,不是人为的, 那不是人做的,就只能中k8s这边的某些机制触发了这个删除的操作,kubernetes可以管理千千万万的pod资源,因此gc机制是必不可少的,作者也是第一时间想到了可能是gc机制引起的.
在详细追踪k8s的podGC问题之前,其实还有一个嫌疑犯需要排查,那就是argo-workflow, argo-workflow做为一种任务workflow的实现方式,argo-workflow本身也可以通过CRD来检测当workflow执行到达什么状态时进行podGC, 如下图:
但作者可以肯定的是,那些被删除的pod中并未使用argo-workflow的podGC,因此argo-workflow的嫌疑可以排除.
那么现在就剩k8s本身的机制了
PodGC
k8s中存在在各种各样的controller(感兴趣的可以看看controllermanager.go中的NewControllerInitializers中列出来的controllers对象), 每一个controller专注于解决一个方面的问题, podGC controller也是如此,专门回收pod。
既然pod被回收了,是不是可以从controllermanager的日志中看到什么呢?果然
从上面的日志也可以证实,pod确实是controller被回收了,但是怎么个回收法呢?依据是什么,时间间隔多久等等一系列问题相继涌出
gc_controller.go
源码能够得到一切答案,大多数都来自于pkg/controller/podgc/gc_controller.go
1 | const ( |
首先是gc的时间间隔,很显然是20s,而且这个数值不支持从命令参数中配置
quarantineTime是在删除孤儿pod时等待节点ready前的时间
那根据什么删除的呢, 同样,在源码中给了答案
pod.status.phase
1 | func (gcc *PodGCController) gcTerminated(pods []*v1.Pod) { |
这里的日志输出刚好也是controllermanager.go中的日志输出,主要的逻辑在如何判定一个pod是否需要被删除
1 | func isPodTerminated(pod *v1.Pod) bool { |
判断一个pod是否需要被删除,主要看一个pod的状态,在k8s,一个pod大概会有以下的状态(phases)
- Pending
- Running
- Succeeded
- Failed
- Unknown
得到所有的pods实例,对于status.phase不等于Pending、Running、Unknown的且与terminatedPodThreshold的差值的部分的pod进行清除,会对要删除的pod的创建时间戳进行排序后删除差值个数的pod,注意这里也会把succeeded的状态pod给删除,作者对这个把succeeded状态的pod给gc了还是比较奇怪的
gcOrphaned
另外,回收那些Binded的Nodes已经不存在的pods,这个没什么好说的,node都不存在了,pod也没存在的必要了
逻辑是调用apiserver接口,获取所有的Nodes,然后遍历所有pods,如果pod bind的NodeName不为空且不包含在刚刚获取的所有Nodes中,最后串行逐个调用gcc.deletePod删除对应的pod
1 | func (gcc *PodGCController) gcOrphaned(pods []*v1.Pod, nodes []*v1.Node) { |
gcUnscheduledTerminating
另外,回收Unscheduled并且Terminating的pods,逻辑是遍历所有pods,过滤那些terminating(pod.DeletionTimestamp != nil
)并且未调度成功的(pod.Spec.NodeName为空)的pods, 然后串行逐个调用gcc.deletePod删除对应的pod
1 | func (gcc *PodGCController) gcUnscheduledTerminating(pods []*v1.Pod) { |
disable podGC controller
Podgc 是不是可以配置呢?
很遗憾的是,配置项不是很多,可以定义是否开启podgc controller
controller-manager的启动参数中有个参数:
1 | --terminated-pod-gc-threshold int32 Default: 12500 |
这个参数指的是在pod gc前可以保留多少个terminated pods, 默认是12500个,这个数值还是挺大的,一般集群怕是很难能到,作者由于是训练集群,存在着大量的短时间任务,因此会出现大于该值的pod,当该值小于等于0时,相当于不对terminated pods进行删除,但还是会对孤儿pod及处于terminating状态且没有绑定到node的pod进行清除.
参考: https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/
作者只查到这一个跟podgc相关的参数,目测好像在不修改controllermanager的情况下是没办法直接禁用podgc
到此,真相大白:
同时也给作者纠正了一个错误, 不是只有Failed状态的pod才会被gc,Successed状态的pod也会被gc掉,这个出乎作者意料之外
最后,想说的是,podgc跟k8s中的垃圾回收还不是一回事,虽然他们都是以controller运行,
podgc解决的是pod到达gc的条件后会被delete掉.
而garbage则解决的是对节点上的无用镜像和容器的清除
从k8s的源码也能够看出来这两者的不同.