为什么Pod突然就不见了?

news/2024/8/24 8:15:32 标签: kubernetes, c++11, migration, ribbon, etcd


最近发生一件很诡异的事情, 某个 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

const (
 // gcCheckPeriod defines frequency of running main controller loop
 gcCheckPeriod = 20 * time.Second
 // quarantineTime defines how long Orphaned GC waits for nodes to show up
 // in an informer before issuing a GET call to check if they are truly gone
 quarantineTime = 40 * time.Second
)

首先是 gc 的时间间隔,很显然是 20s,而且这个数值不支持从命令参数中配置

quarantineTime 是在删除孤儿 pod 时等待节点 ready 前的时间

那根据什么删除的呢, 同样,在源码中给了答案

pod.status.phase

func (gcc *PodGCController) gcTerminated(pods []*v1.Pod) {
 terminatedPods := []*v1.Pod{}
 for _, pod := range pods {
  if isPodTerminated(pod) {
   terminatedPods = append(terminatedPods, pod)
  }
 }

 terminatedPodCount := len(terminatedPods)
 deleteCount := terminatedPodCount - gcc.terminatedPodThreshold

 if deleteCount > terminatedPodCount {
  deleteCount = terminatedPodCount
 }
 if deleteCount <= 0 {
  return
 }

 klog.Infof("garbage collecting %v pods", deleteCount)
 // sort only when necessary
 sort.Sort(byCreationTimestamp(terminatedPods))
 var wait sync.WaitGroup
 for i := 0; i < deleteCount; i++ {
  wait.Add(1)
  go func(namespace string, name string) {
   defer wait.Done()
   if err := gcc.deletePod(namespace, name); err != nil {
    // ignore not founds
    defer utilruntime.HandleError(err)
   }
  }(terminatedPods[i].Namespace, terminatedPods[i].Name)
 }
 wait.Wait()
}

这里的日志输出刚好也是 controllermanager.go 中的日志输出,主要的逻辑在如何判定一个 pod 是否需要被删除


func isPodTerminated(pod *v1.Pod) bool {
 if phase := pod.Status.Phase; phase != v1.PodPending && phase != v1.PodRunning && phase != v1.PodUnknown {
  return true
 }
 return false
}

判断一个 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

func (gcc *PodGCController) gcOrphaned(pods []*v1.Pod, nodes []*v1.Node) {
 klog.V(4).Infof("GC'ing orphaned")
 existingNodeNames := sets.NewString()
 for _, node := range nodes {
  existingNodeNames.Insert(node.Name)
 }
 // Add newly found unknown nodes to quarantine
 for _, pod := range pods {
  if pod.Spec.NodeName != "" && !existingNodeNames.Has(pod.Spec.NodeName) {
   gcc.nodeQueue.AddAfter(pod.Spec.NodeName, quarantineTime)
  }
 }
 // Check if nodes are still missing after quarantine period
 deletedNodesNames, quit := gcc.discoverDeletedNodes(existingNodeNames)
 if quit {
  return
 }
 // Delete orphaned pods
 for _, pod := range pods {
  if !deletedNodesNames.Has(pod.Spec.NodeName) {
   continue
  }
  klog.V(2).Infof("Found orphaned Pod %v/%v assigned to the Node %v. Deleting.", pod.Namespace, pod.Name, pod.Spec.NodeName)
  if err := gcc.deletePod(pod.Namespace, pod.Name); err != nil {
   utilruntime.HandleError(err)
  } else {
   klog.V(0).Infof("Forced deletion of orphaned Pod %v/%v succeeded", pod.Namespace, pod.Name)
  }
 }
}

gcUnscheduledTerminating

另外,回收 Unscheduled 并且 Terminating 的 pods,逻辑是遍历所有 pods,过滤那些 terminating(pod.DeletionTimestamp != nil)并且未调度成功的(pod.Spec.NodeName 为空)的 pods, 然后串行逐个调用 gcc.deletePod 删除对应的 pod

func (gcc *PodGCController) gcUnscheduledTerminating(pods []*v1.Pod) {
 klog.V(4).Infof("GC'ing unscheduled pods which are terminating.")

 for _, pod := range pods {
  if pod.DeletionTimestamp == nil || len(pod.Spec.NodeName) > 0 {
   continue
  }

  klog.V(2).Infof("Found unscheduled terminating Pod %v/%v not assigned to any Node. Deleting.", pod.Namespace, pod.Name)
  if err := gcc.deletePod(pod.Namespace, pod.Name); err != nil {
   utilruntime.HandleError(err)
  } else {
   klog.V(0).Infof("Forced deletion of unscheduled terminating Pod %v/%v succeeded", pod.Namespace, pod.Name)
  }
 }
}

disable podGC controller

Podgc 是不是可以配置呢?

很遗憾的是,配置项不是很多,可以定义是否开启 podgc controller

controller-manager 的启动参数中有个参数:

--terminated-pod-gc-threshold int32     Default: 12500
Number of terminated pods that can exist before the terminated pod garbage collector starts deleting terminated pods. If <= 0, the terminated pod garbage collector is disabled.

这个参数指的是在 pod gc 前可以保留多少个 terminated pods, 默认是 12500 个,这个数值还是挺大的,一般集群怕是很难能到,作者由于是训练集群,存在着大量的短时间任务,因此会出现大于该值的 pod,当该值小于等于 0 时,相当于不对 terminated pods 进行删除,但还是会对孤儿 pod 及处于 terminating 状态且没有绑定到 node 的 pod 进行清除.

参考: https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/[1]

作者只查到这一个跟 podgc 相关的参数,目测好像在不修改 controllermanager 的情况下是没办法直接禁用 podgc

到此,真相大白:

同时也给作者纠正了一个错误, 不是只有 Failed 状态的 pod 才会被 gc,Successed 状态的 pod 也会被 gc 掉,这个出乎作者意料之外

最后,想说的是,podgc 跟 k8s 中的垃圾回收还不是一回事,虽然他们都是以 controller 运行,

podgc 解决的是 pod 到达 gc 的条件后会被 delete 掉.

而 garbage 则解决的是对节点上的无用镜像和容器的清除

从 k8s 的源码也能够看出来这两者的不同.

参考文章

  • https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/[2]

  • https://cloud.tencent.com/developer/article/1097385[3]

脚注

[1]

https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/: https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/

[2]

https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/: https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/

[3]

https://cloud.tencent.com/developer/article/1097385: https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/


原文链接:https://izsk.me/2021/07/26/Kubernetes-podGC/


你可能还喜欢

点击下方图片即可阅读

在 Kubernetes 上运行 GitHub Actions Self-hosted Runner

云原生是一种信仰 ????

关注公众号

后台回复◉k8s◉获取史上最方便快捷的 Kubernetes 高可用部署工具,只需一条命令,连 ssh 都不需要!

点击 "阅读原文" 获取更好的阅读体验!

发现朋友圈变“安静”了吗?


http://www.niftyadmin.cn/n/1749985.html

相关文章

search,search_n

直接代码&#xff1a; #include<iostream> #include<string> #include<vector> #include<algorithm> #include<functional> #include<cmath> using namespace std;int main(){int a[]{1,2,3,4,5,6,7,7,7,7,8,8,8,9,10};vector<int>…

必看!云原生 Meetup 参会指南!

7 月 29 日驭势科技云平台研发总监中移物联网云计算高级开发工程师马上消费金融高级云平台开发视源股份资深运维工程师KubeSphere 可观测性研发等诸多惊喜伙伴将出席云原生 Meetup等你来约围绕“云原生”的主题带来实践与思考&#xff0c;交流与碰撞倒计时 1 天&#xff01;温馨…

find_end,find_first_of

直接代码&#xff1a; #include<iostream> #include<string> #include<vector> #include<algorithm> using namespace std;int main(){int a[]{1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10};vector<int>v(a,a20);vector<int>vv(a3,a8);ve…

容器应用优雅关闭的终极大招

概述优雅关闭&#xff1a;在关闭前&#xff0c;执行正常的关闭过程&#xff0c;释放连接和资源&#xff0c;如我们操作系统执行 shutdown。目前业务系统组件众多&#xff0c;互相之间调用关系也比较复杂&#xff0c;一个组件的下线、关闭会涉及到多个组件 对于任何一个线上应用…

adjacent_find,equal

代码如下&#xff1a; #include<iostream> #include<string> #include<vector> #include<algorithm> #include<functional>using namespace std; int main(){int a[]{1,2,3,4,5,6,6,8,9,10};vector<int>v(a,a10);vector<int>vv(a3,…

1.Class.forName()报错问题

Class.forName()报错连接数据库的时候要用到try catchString url "jdbc:mysql://localhost:3306/survey?userroot&password&useUnicodetrue&characterEncodingutf-8&autoReconnecttrue&failOverReadOnlyfalse";protected Connection connnull;…

mismatch和lexicographical_compare

代码如下&#xff1a; #include<iostream> #include<string> #include<vector> #include<algorithm> #include<functional> using namespace std; bool jianyi(int a,int b){return ab-1; } int main(){int a[]{1,2,3,4,5,6,7,8,9,10};int b[]{…

Kubernetes 常见问题排查与解决方案!(纯干货)

毫无疑问&#xff0c;Kubernetes 是个伟大的开源作品&#xff0c;给作者所在团队中极大地提高了生产力&#xff0c;但在使用过程中&#xff0c;相信很多人跟作者一样&#xff0c;会遇到各种各样的问题&#xff0c;有时候解决的过程也是非常享受&#xff0c;某些问题还可能反复遇…