当前位置: 首页 > 科技观察

Kubelet驱逐机制分析

时间:2023-03-17 19:58:57 科技观察

Kubelet允许在节点资源不足时驱逐节点上的Pod的功能,以保护节点。最近在研究Kubelet的驱逐机制,发现有很多值得学习的地方。我总结一下,分享给大家。Kubelet的配置在配置中需要开启Kubelet的逐出功能,并配置逐出阈值。Kubelet的配置中与驱动相关的参数如下:typeKubeletConfigurationstruct{...//Mapofsignalnamestoquantitiesthatdefineshardevictionthresholds.Forexample:{"memory.available":"300Mi"}.EvictionHardmap[string]string//Mapofsignalnamestoquantitiesthatdefineshardevictionthresholds.Forexample:{"memory.available":"300Mi"}.EvictionHardmap[string]string//Mapofsignalnamestoquantitiesthatdefinessoftevictionthreshold.available{formorexexvictionmethreshold":"300Mi"}.EvictionSoftmap[string]string//Mapofsignalnamestoquantitiesthatdefinesgraceperiodsforeachsoftevictionsignal.Forexample:{"memory.available":"30s"}.EvictionSoftGracePeriodmap[string]string//Durationforwhichthekubelethastowaitbeforetransitioningoutofanevictionpressurecondition.EvictionPressureTransitionPeriodmetav1.Duration//Maximumallowedgraceperiod(inseconds)tousewhenterminatingpodsinresponsetoasoftevictionthresholdbeingmet.EvictionMaxPodGracePeriodint32//信号名称映射到定义最小回收量的数量,它描述了执行adevicti时kubelet将回收的给定资源的最小量//onwhile//thatresourceisunderpressure.Forexample:{"imagefs.available":"2Gi"}EvictionMinimumReclaimmap[string]string...}其中EvictionHard表示硬驱逐,一旦达到阈值就直接驱逐;EvictionSoft表示软驱逐,可以设置软驱逐周期,超过软驱逐周期才开始驱逐,周期由EvictionSoftGracePeriod设置;EvictionMinimumReclaim表示设置最小可用阈值。例如imagefs可以设置的驱逐信号有:memory.available:node.status.capacity[memory]??-node.stats.memory.workingSet,节点可用内存kubelet使用的文件系统的数量nodefs.inodesFree:node.stats.fs.inodesFree,Kubelet使用的文件系统可用的inodes数量imagefs.available:node.stats.runtime.imagefs.available,文件系统的可用容量用于在容器运行时存储图像和容器可写层imagefs.inodesFree:node.stats.runtime.imagefs。inodesFree,容器运行时用于存储镜像和容器可写层的文件系统的可用inode容量。allocatableMemory.available:为分配Pod保留的可用内存。rlimit.curproc,为可用的PIDEvictionManager保留,用于分配Pod工作原理EvictionManager的主要工作在synchronize函数中。触发synchronize任务的地方有两个,一个是monitor任务,每10s触发一次;另一个是根据用户配置的驱逐信号启动的通知程序任务,用于监视内核事件。notifiernotifier由逐出管理器中的thresholdNotifier启动。用户配置的每个驱逐信号对应一个thresholdNotifier,thresholdNotifier和notifier通过channel进行通信。当通知器向通道发送消息时,相应的thresholdNotifier会触发同步逻辑。notifier使用内核的cgroups内存阈值,cgroups允许用户态进程设置eventfd在memory.usage_in_bytes达到一定阈值时向应用程序发送通知。这是通过将""写入cgroup.event_control来完成的。notifier的初始化代码如下(为了阅读方便删除了一些不相关的代码),主要是找到memory.usage_in_bytes的文件描述符watchfd和cgroup.event_control的文件描述符controlfd来完成cgroup内存阈值的注册.funcNewCgroupNotifier(path,attributestring,thresholdint64)(CgroupNotifier,error){varwatchfd,eventfd,epfd,controlfdintwatchfd,err=unix.Open(fmt.Sprintf("%s/%s",path,attribute),unix.O_RDONLY|unix.O_CLOEXEC,0)deferunix.Close(watchfd)controlfd,err=unix.Open(fmt.Sprintf("%s/cgroup.event_control",path),unix.O_WRONLYunix.O_CLOEXEC,0)deferunix.Close(controlfd(){//Closeepfdifwegetanerrorlaterinitializationiferr!=nil{unix.Close(epfd)}}()config:=fmt.Sprintf("%d%d%d",eventfd,watchfd,threshold)_,err=unix.Write(controlfd,[]byte(config))return&linuxCgroupNotifier{eventfd:eventfd,epfd:epfd,stop:make(chanstruct{}),},nil}notifier在启动时也会通过epoll监听上面的eventfd,当内核发送一个事件发生,表示内存使用量超过阈值,向通道发送信号。func(n*linuxCgroupNotifier)Start(eventChchan<-struct{}){err:=unix.EpollCtl(n.epfd,unix.EPOLL_CTL_ADD,n.eventfd,&unix.EpollEvent{Fd:int32(n.eventfd),事件:unix.EPOLLIN,})for{select{case<-n.stop:returndefault:}event,err:=wait(n.epfd,n.eventfd,notifierRefreshInterval)iferr!=nil{klog.InfoS("Evictionmanager:errorwhilewaitingformemcgevents","err",err)return}elseif!event{//Timeoutonwait.Thisisexpectedifthethresholdwasnotcrossedcontinue}//从theeventfdbuf消耗事件:=make([]byte,eventSize)_,err=unix.Read(n.eventfd,buf)iferr!=nil{klog.InfoS("Evictionmanager:errorreadingmemcgevents","err",err)return}eventCh<-struct{}{}}}同步逻辑会在每次执行notifier时判断10s内是否有更新,并重启通知者。cgroup内存阈值计算为内存总量减去用户设置的逐出阈值。synchronizeEvictionManager的主要逻辑synchronize有很多细节,这里就不贴源码了。整理的主要内容如下:为每个信号构建一个排序函数;更新阈值并重新启动通知程序;获取当前节点的资源使用情况(cgroup信息)和所有活跃的pod;对于每个信号,判断当前节点的资源使用量是否达到驱逐阈值,否则退出当前循环;所有信号的优先级如下:首先驱逐与内存相关的信号;向apiserver发送驱逐事件;优先考虑所有活跃的pod;按排序顺序逐出pod。计算驱逐顺序Pod驱逐的顺序主要取决于三个因素:Pod的资源使用是否超过其请求;pod的优先级值;pod的内存使用情况;三个因素的判断顺序也是按照orderedBy中注册的顺序。这里的orderedBy函数的多级排序也是Kubernetes中一个值得学习(抄作业)的实现。有兴趣的读者可以自行查看源码。//rankMemoryPressureorderstheinputpodsforevictioninresponsetomemorypressure.//Itranksbywhetherornotthepod'susageexceedsitsrequests,thenbypriority,and//finallybymemoryusageaboverequests.funcrankMemoryPressure(pods[]*v1.Pod,statsstatsFunc){orderedBy(exceedMemoryRequests(stats),priority,memory(stats)).Sort(pods)}逐出Pod接下来是逐出Pod的实现。EvictionManager驱逐Pods是一个干净的杀戮。里面的具体实现这里就不分析了。值得注意的是,开除之前还有一个判决。如果IsCriticalPod返回true,则不会被逐出。func(m*managerImpl)evictPod(pod*v1.Pod,gracePeriodOverrideint64,evictMsgstring,annotationsmap[string]string)bool{//如果podismarkedascriticalandstatic,andsupportforcriticalpodannotationsisenabled,//donotevictsuchpods.Staticpodsarenotre-admittedafterevictions.//https://github.com/kubernetes/kubernetes/issues/40573hasmoredetails.ifkubelettypes.IsCriticalPod(pod){klog.ErrorS(nil,"Evictionmanager:cannotevictacriticalpod","pod",klog.KObj(pod))returnfalse}//recordthatweareevictingthepodm.recorder.AnnotatedEventf(pod,annotations,v1.EventTypeWarning,Reason,evictMsg)//thisisablockingcallandshoulddonlyreturnwhenthepodanditscontainersarekilled.klog.V(3).InfoS("Evictingpod","pod",klog.KObj(pod),"podUID",pod.UID,"message",evictMsg)err:=m.killPodFunc(pod,true,&gracePeriodOverride,func(status*v1.PodStatus){status.Phase=v1.PodFailedstatus.Reason=Reasonstatus.Message=evictMsg})iferr!=nil{klog.ErrorS(电子rr,"Evictionmanager:podfailedtoevict","pod",klog.KObj(pod))}else{klog.InfoS("Evictionmanager:podisevictedsuccessfully","pod",klog.KObj(pod))}returntrue}再看IsCriticalPod代码:funcIsCriticalPod(pod*v1.Pod)bool{ifIsStaticPod(pod){returntrue}ifIsMirrorPod(pod){returntrue}ifpod.Spec.Priority!=nil&&IsCriticalPodBasedOnPriority(*pod.Spec.Priority){returntrue}returnfalse}//IsMirrorPodreturnstrueifthepassedPodisaMirrorPod.funcIsMirrorPod(pod*v1.Pod)bool{_,ok:=pod.Annotations[ConfigMirrorAnnotationKey]returnok}//IsStaticPodreturnstrueifthepodisastaticpod.funcIsStaticPod(pod*v1.Pod)bool{source,err:=GetPodSource=pod)=nil&&source!=ApiserverSource}funcIsCriticalPodBasedOnPriority(priorityint32)bool{returnpriority>=scheduling.SystemCriticalPriority}从代码看,如果Pod是Static、Mirror、CriticalPod都不会驾驭其中Static和Mirror是根据Pod的注解来判断的;而Critical是根据Pod的Priority值来判断的。如果Priority是system-cluster-critical/system-node-critical,则两者都属于CriticalPod。不过这里值得注意的是,官方文档中提到CriticalPod的意思是,如果一个非StaticPod被标记为Critical,不保证它不会被驱逐:https://kubernetes.io/docs/tasks/管理集群/guaranteed-scheduling-critical-addon-pods。所以,很有可能是社区没有想清楚这种情况要不要驱逐,不排除后面会改这个逻辑,但也有可能是文档没有及时更新??.总结本文主要分析了Kubelet的EvictionManager,包括其对LinuxCGroup事件的监控以及对Pod驱逐优先级的判断。了解了这些之后,我们就可以根据自己应用的重要性来设置优先级,甚至可以设置为CriticalPod。