当前位置:首页 > 通信资讯 > 正文

kubernetes 驱逐(kubectl 驱逐pod)

kubernetes 驱逐(kubectl 驱逐pod)

Kubelet 出于对节点的保护,允许在节点资源不足的情况下,开启对节点上 Pod 进行驱逐的功能。最近对 Kubelet 的驱逐机制有所研究,发现其中有很多值得学习的地方,总结下来和大家分享。

Kubelet 的配置

Kubelet 的驱逐功能需要在配置中打开,并且配置驱逐的阈值。Kubelet 的配置中与驱逐相关的参数如下:

  1. typeKubeletConfigurationstruct{
  2. ...
  3. //Mapofsignalnamestoquantitiesthatdefineshardevictionthresholds.Forexample:{"memory.available":"300Mi"}.
  4. EvictionHardmap[string]string
  5. //Mapofsignalnamestoquantitiesthatdefinessoftevictionthresholds.Forexample:{"memory.available":"300Mi"}.
  6. EvictionSoftmap[string]string
  7. //Mapofsignalnamestoquantitiesthatdefinesgraceperiodsforeachsoftevictionsignal.Forexample:{"memory.available":"30s"}.
  8. EvictionSoftGracePeriodmap[string]string
  9. //Durationforwhichthekubelethastowaitbeforetransitioningoutofanevictionpressurecondition.
  10. EvictionPressureTransitionPeriodmetav1.Duration
  11. //Maximumallowedgraceperiod(inseconds)tousewhenterminatingpodsinresponsetoasoftevictionthresholdbeingmet.
  12. EvictionMaxPodGracePeriodint32
  13. //Mapofsignalnamestoquantitiesthatdefinesminimumreclaims,whichdescribetheminimum
  14. //amountofagivenresourcethekubeletwillreclaimwhenperformingapodevictionwhile
  15. //thatresourceisunderpressure.Forexample:{"imagefs.available":"2Gi"}
  16. EvictionMinimumReclaimmap[string]string
  17. ...
  18. }

其中,EvictionHard 表示硬驱逐,一旦达到阈值,就直接驱逐;EvictionSoft 表示软驱逐,即可以设置软驱逐周期,只有超过软驱逐周期后,才启动驱逐,周期用 EvictionSoftGracePeriod 设置;EvictionMinimumReclaim 表示设置最小可用的阈值,比如 imagefs。

可以设置的驱逐信号有:

  • memory.available:node.status.capacity[memory] - node.stats.memory.workingSet,节点可用内存
  • nodefs.available:node.stats.fs.available,Kubelet 使用的文件系统的可使用容量大小
  • nodefs.inodesFree:node.stats.fs.inodesFree,Kubelet 使用的文件系统的可使用 inodes 数量
  • imagefs.available:node.stats.runtime.imagefs.available,容器运行时用来存放镜像及容器可写层的文件系统的可使用容量
  • imagefs.inodesFree:node.stats.runtime.imagefs.inodesFree,容器运行时用来存放镜像及容器可写层的文件系统的可使用 inodes 容量
  • allocatableMemory.available:留给分配 Pod 用的可用内存
  • pid.available:node.stats.rlimit.maxpid - node.stats.rlimit.curproc,留给分配 Pod 用的可用 PID

Eviction Manager 工作原理

Eviction Manager的主要工作在 synchronize 函数里。有两个地方触发 synchronize 任务,一个是 monitor 任务,每 10s 触发一次;另一个是根据用户配置的驱逐信号,启动的 notifier 任务,用来监听内核事件。

kubernetes 驱逐(kubectl 驱逐pod)

notifier

notifier 由 eviction manager 中的 thresholdNotifier 启动,用户配置的每一个驱逐信号,都对应一个 thresholdNotifier,而 thresholdNotifier 和 notifier 通过 channel 通信,当 notifier 向 channel 中发送消息时,对应的 thresholdNotifier 便触发一次 synchronize 逻辑。

notifier 采用的是内核的 cgroups Memory thresholds,cgroups 允许用户态进程通过 eventfd 来设置当 memory.usage_in_bytes 达到某阈值时,内核给应用发送通知。具体做法是向 cgroup.event_control 写入 " "。

notifier 的初始化代码如下(为了方便阅读,删除了部分不相干代码),主要是找到 memory.usage_in_bytes 的文件描述符 watchfd,cgroup.event_control 的文件描述符 controlfd,完成 cgroup memory thrsholds 的注册。

  1. funcNewCgroupNotifier(path,attributestring,thresholdint64)(CgroupNotifier,error){
  2. varwatchfd,eventfd,epfd,controlfdint
  3. watchfd,err=unix.Open(fmt.Sprintf("%s/%s",path,attribute),unix.O_RDONLY|unix.O_CLOEXEC,0)
  4. deferunix.Close(watchfd)
  5. controlfd,err=unix.Open(fmt.Sprintf("%s/cgroup.event_control",path),unix.O_WRONLY|unix.O_CLOEXEC,0)
  6. deferunix.Close(controlfd)
  7. eventfd,err=unix.Eventfd(0,unix.EFD_CLOEXEC)
  8. deferfunc(){
  9. //Closeeventfdifwegetanerrorlaterininitialization
  10. iferr!=nil{
  11. unix.Close(eventfd)
  12. }
  13. }()
  14. epfd,err=unix.EpollCreate1(unix.EPOLL_CLOEXEC)
  15. deferfunc(){
  16. //Closeepfdifwegetanerrorlaterininitialization
  17. iferr!=nil{
  18. unix.Close(epfd)
  19. }
  20. }()
  21. config:=fmt.Sprintf("%d%d%d",eventfd,watchfd,threshold)
  22. _,err=unix.Write(controlfd,[]byte(config))
  23. return&linuxCgroupNotifier{
  24. eventfd:eventfd,
  25. epfd:epfd,
  26. stop:make(chanstruct{}),
  27. },nil
  28. }

notifier 在启动时还会通过 epoll 来监听上述的 eventfd,当监听到内核发送的事件时,说明使用的内存已超过阈值,便向 channel 中发送信号。

  1. func(n*linuxCgroupNotifier)Start(eventChchan<-struct{}){
  2. err:=unix.EpollCtl(n.epfd,unix.EPOLL_CTL_ADD,n.eventfd,&unix.EpollEvent{
  3. Fd:int32(n.eventfd),
  4. Events:unix.EPOLLIN,
  5. })
  6. for{
  7. select{
  8. case<-n.stop:
  9. return
  10. default:
  11. }
  12. event,err:=wait(n.epfd,n.eventfd,notifierRefreshInterval)
  13. iferr!=nil{
  14. klog.InfoS("Evictionmanager:errorwhilewaitingformemcgevents","err",err)
  15. return
  16. }elseif!event{
  17. //Timeoutonwait.Thisisexpectedifthethresholdwasnotcrossed
  18. continue
  19. }
  20. //Consumetheeventfromtheeventfd
  21. buf:=make([]byte,eventSize)
  22. _,err=unix.Read(n.eventfd,buf)
  23. iferr!=nil{
  24. klog.InfoS("Evictionmanager:errorreadingmemcgevents","err",err)
  25. return
  26. }
  27. eventCh<-struct{}{}
  28. }
  29. }

synchronize 逻辑每次执行都会判断 10s 内 notifier 是否有更新,并重新启动 notifier。cgroup memory threshold 的计算方式为内存总量减去用户设置的驱逐阈值。

synchronize

Eviction Manager 的主逻辑 synchronize 细节比较多,这里就不贴源码了,梳理下来主要是以下几个事项:

  1. 针对每个信号构建排序函数;
  2. 更新 threshold 并重新启动 notifier;
  3. 获取当前节点的资源使用情况(cgroup 的信息)和所有活跃的 pod;
  4. 针对每个信号,分别确定当前节点的资源使用情况是否达到驱逐的阈值,如果都没有,则退出当前循环;
  5. 将所有的信号进行优先级排序,优先级为:跟内存有关的信号先进行驱逐;
  6. 向 apiserver 发送 驱逐事件;
  7. 将所有活跃的 pod 进行优先级排序;
  8. 按照排序后的顺序对 pod 进行驱逐。

计算驱逐顺序

对 pod 的驱逐顺序主要取决于三个因素:

  • pod 的资源使用情况是否超过其 requests;
  • pod 的 priority 值;
  • pod 的内存使用情况;

三个因素的判断顺序也是根据注册进 orderedBy 的顺序。这里 orderedBy 函数的多级排序也是 Kubernetes 里一个值得学习(抄作业)的一个实现,感兴趣的读者可以自行查阅源码。

  1. //rankMemoryPressureorderstheinputpodsforevictioninresponsetomemorypressure.
  2. //Itranksbywhetherornotthepod'susageexceedsitsrequests,thenbypriority,and
  3. //finallybymemoryusageaboverequests.
  4. funcrankMemoryPressure(pods[]*v1.Pod,statsstatsFunc){
  5. orderedBy(exceedMemoryRequests(stats),priority,memory(stats)).Sort(pods)
  6. }

驱逐 Pod

接下来就是驱逐 Pod 的实现。Eviction Manager 驱逐 Pod 就是干净利落的 kill,里面具体的实现这里不展开分析,值得注意的是在驱逐之前有一个判断,如果 IsCriticalPod 返回为 true 则不驱逐。

  1. func(m*managerImpl)evictPod(pod*v1.Pod,gracePeriodOverrideint64,evictMsgstring,annotationsmap[string]string)bool{
  2. //Ifthepodismarkedascriticalandstatic,andsupportforcriticalpodannotationsisenabled,
  3. //donotevictsuchpods.Staticpodsarenotre-admittedafterevictions.
  4. //https://github.com/kubernetes/kubernetes/issues/40573hasmoredetails.
  5. ifkubelettypes.IsCriticalPod(pod){
  6. klog.ErrorS(nil,"Evictionmanager:cannotevictacriticalpod","pod",klog.KObj(pod))
  7. returnfalse
  8. }
  9. //recordthatweareevictingthepod
  10. m.recorder.AnnotatedEventf(pod,annotations,v1.EventTypeWarning,Reason,evictMsg)
  11. //thisisablockingcallandshouldonlyreturnwhenthepodanditscontainersarekilled.
  12. klog.V(3).InfoS("Evictingpod","pod",klog.KObj(pod),"podUID",pod.UID,"message",evictMsg)
  13. err:=m.killPodFunc(pod,true,&gracePeriodOverride,func(status*v1.PodStatus){
  14. status.Phase=v1.PodFailed
  15. status.Reason=Reason
  16. status.Message=evictMsg
  17. })
  18. iferr!=nil{
  19. klog.ErrorS(err,"Evictionmanager:podfailedtoevict","pod",klog.KObj(pod))
  20. }else{
  21. klog.InfoS("Evictionmanager:podisevictedsuccessfully","pod",klog.KObj(pod))
  22. }
  23. returntrue
  24. }

再看看 IsCriticalPod 的代码:

  1. funcIsCriticalPod(pod*v1.Pod)bool{
  2. ifIsStaticPod(pod){
  3. returntrue
  4. }
  5. ifIsMirrorPod(pod){
  6. returntrue
  7. }
  8. ifpod.Spec.Priority!=nil&&IsCriticalPodBasedOnPriority(*pod.Spec.Priority){
  9. returntrue
  10. }
  11. returnfalse
  12. }
  13. //IsMirrorPodreturnstrueifthepassedPodisaMirrorPod.
  14. funcIsMirrorPod(pod*v1.Pod)bool{
  15. _,ok:=pod.Annotations[ConfigMirrorAnnotationKey]
  16. returnok
  17. }
  18. //IsStaticPodreturnstrueifthepodisastaticpod.
  19. funcIsStaticPod(pod*v1.Pod)bool{
  20. source,err:=GetPodSource(pod)
  21. returnerr==nil&&source!=ApiserverSource
  22. }
  23. funcIsCriticalPodBasedOnPriority(priorityint32)bool{
  24. returnpriority>=scheduling.SystemCriticalPriority
  25. }

从代码看,如果 Pod 是 Static、Mirror、Critical Pod 都不驱逐。其中 Static 和 Mirror 都是从 Pod 的 annotation 中判断;而 Critical 则是通过 Pod 的 Priority 值判断的,如果 Priority 为 system-cluster-critical/system-node-critical 都属于 Critical Pod。

不过这里值得注意的是,官方文档里提及 Critical Pod 是说,如果非 Static Pod 被标记为 Critical,并不完全保证不会被驱逐:https://kubernetes.io/docs/tasks/administer-cluster/guaranteed-scheduling-critical-addon-pods 。因此,很有可能是社区并没有想清楚这种情况是否要驱逐,并不排除后面会改变这段逻辑,不过也有可能是文档没有及时更新??。

总结

本文主要分析了 Kubelet 的 Eviction Manager,包括其对 Linux CGroup 事件的监听、判断 Pod 驱逐的优先级等。了解了这些之后,我们就可以根据自身应用的重要性来设置优先级,甚至设置成 Critical Pod。

原文链接:https://mp.weixin.qq.com/s/xEE-GoYg0b6aeMHHar7qWw

如果您对该产品感兴趣,请填写办理(客服微信:xiaoxiongyidong)

为您推荐:

发表评论

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。