Kubernetes GarbageCollector

云计算 waitig 718℃ 百度已收录 0评论

GarbageCollector
controller

1.–enable-garbage-collector参数决定是否启动GCcontroller

2.通过disvoveryclient获取所有可以删除的资源

3.获取默认需要被忽略不删除的资源

4.创建GarbageCollectorGraphBuilder对象

5.调用syncMonitors为每个可以删除并不能忽略的资源创建monitor,即创建一个handler,这个handler为资源的创建更新删除操作创建event在追踪

6.调用gogarbageCollector.Run(workers,ctx.Stop)启动garbagecollector.

7.调用garbageCollector.Sync定期从discoveryservice中同步资源,当新的资源加进来就为其创建新的monitor,当资源被删除了就删除它对应的monitor

garbageCollector.Run

1.gogc.dependencyGraphBuilder.Run(stopCh)启动GraphBuilder,为所有资源增加monitor,根据监听到的event为资源生成含有依赖的Graph,即各资源相互的依赖图,再根据资源含有的Finalizer将资源加入到孤立资源的队列或删除资源的队列中

2.定期执行gc.runAttemptToDeleteWorker删除资源

3.定期执行gc.runAttemptToOrphanWorker孤立资源

二启动GraphBuilder

1.调用gb.startMonitors()启动各个资源的monitor

1.1调用各个资源的monitorinformerstart函数开始监控各资源。

2.定期调用runProcessGraphChanges()处理monitor产生的event

2.1event中获取资源对象

2.2如果event是第一次做增加或者更新,则将对象封装成node

type objectReference struct {
   metav1.OwnerReference
   // This is needed by the dynamic client
   Namespace string
}
type node struct {
   //node本身资源的详细信息
   identity objectReference
   dependentsLock sync.RWMutex
   //node的所有依赖,例如rc的依赖是pod
   dependents map[*node]struct{}
   //依赖是否需要删除,即deletionTimestamp不为nil且含有DeleteDependentsFinalizer
   deletingDependents     bool
   deletingDependentsLock sync.RWMutex
   //本身是否将要被删除,即deletionTimestamp不为nil
   beingDeleted     bool
   beingDeletedLock sync.RWMutex
   //nodeowner信息
   owners []metav1.OwnerReference
}

2.3调用insertNode将该node加入到gb.uidToNode,该函数将为node找到所有owner并将该node将入到ownerdependents列表中。

2.3.1调用gb.uidToNode.Write(n)node加入到gb.uidToNode中。

2.3.2获取node的所有owner,如果owner存在gb.uidToNode中,则将node加入到ownerdependents列表中。

2.3.3如果owner不存在gb.uidToNode中,则为该owner生成一个virtualnode,也将该virtualnode加入到gb.uidToNode中,并将它加入到gb.attemptToDelete(后期会遍历它确认是否存在)。

2.3.4如果该node含有orphanDependentsFinalizer,则将node加入gb.attemptToOrphan

2.3.5如果该node含有DeleteDependentsFinalizer,则将nodenodedependents一起将入gb.attemptToDelete中。

2.4如果node已经存在gb.uidToNode,并且产生了增加或者更新操作

2.4.1调用referencesDiffs比较之前的nodeowner和当前的nodeowner

2.4.2将不属于当前nodeowner和当前node发生了改变的owner加入gb.attemptToDelete

2.4.3更新nodeowner,并将node添加到新的onwerdependents列表中

2.4.4node从属于老node而不属于当前nodeownerdependents列表中删除

2.4.5如果该node含有orphanDependentsFinalizer,则将node加入gb.attemptToOrphan

2.4.6如果该node含有DeleteDependentsFinalizer,则将nodenodedependents一起将入gb.attemptToDelete中。

2.5如果event是删除资源

2.5.1更新onwerdependents,将nodedependentsowner将入gb.attemptToDelete

.定期执行gc.runAttemptToDeleteWorker删除资源

1:依赖或者本身是否需要被删除,在构建node的时候就判断好了,是根据finalizerDeletionTimestamp来判断的

2:等所有依赖删除完之后,删除FinalizerDeleteDependents

3DeletePropagationForeground是等依赖全部删除后才将自己删除

4DeletePropagationBackground是立即将自己删除再由GC去删除依赖

1.调用attemptToDeleteItem删除node

1.0如果node需要被删除,但它的依赖不需要删除,则直接返回,即只删除node

1.1如果nodevirtualnode则为它生成一个virtualdelete
event
把该nodegb.uidToNode中删除

1.2调用processDeletingDependentsItem处理依赖,如果依赖全部被删除了,则将nodeFinalizerDeleteDependents删除,如果没有删除依赖,则将依赖加入gc.attemptToDelete

1.3调用classifyReferences找出nodeowner

1.3.1假如含有不被回收的owner,则只更新资源,将需要被删除的owner从资源的中删除,修改etcd中资源的数据。

1.3.2假如它的owner是需要被删除并且它的依赖还存在也是需要被删除的,则直接调用metav1.DeletePropagationForeground将资源删除

1.3.3否则,根据资源的finalizer来确定删除的policyDeletePropagationForeground或者

DeletePropagationBackground(没有finalizer应该使用DeletePropagationBackground,之前没有删除,是因为这段代码没有设置删除的policy)

总结:1.node需要删除依赖不需要删除,这是orphan,这里不处理

2.nodevirtual的,直接从graph中删除node

3.owner不需要删除,node需要删除,修改nodeowner属性值

4.node需要删除,依赖也要删除,则调用DeletePropagationForeground等依赖删除后,再删node

5.其它一律根据policy删除node

.定期执行gc.runAttemptToOrphanWorker孤立资源

1.调用orphanDependentsnode与所有依赖的关系断开,即从依赖的owner中删除node

2.将自己的FinalizerOrphanDependentsfinalizer删除。

1.发送删除deployment命令,apiserver删除orphanfianlizer并把deployment删除

2.GC检测到删除的event,将deployment对应的node删除,并将rs加入attemptToDelete队列

3.GC调用apiserver删除orphanfianlizer利用DeletePropagationForeground删除rs

4.GC检测到删除的event,将rs对应的node删除,并将pod加入attemptToDelete队列

5.GC调用apiserver删除pod

6.删除pod对应的node

PodGCController的流程
1.创建pod informer监视pod

2.定期执行gc()来回收pod

一:gc()

1.获取pod列表

2.如果定义了terminatedPodThreshold,则将需要删除的pod放入缓存,如果缓存数量超过了terminatedPodThreshold,则将超出的pod删除。

3.调用gcOrphanedpod对应不存在的node的情况,将pod删除

4.调用gcUnscheduledTerminating回收没有被调度且需要删除的pod

containerGC的流程

type ContainerGCPolicy struct {
   // Minimum age at which a container can be garbage collected, zero for no limit.
   MinAge time.Duration
   // Max number of dead containers any single pod (UID, container name) pair is
   // allowed to have, less than zero for no limit.
   MaxPerPodContainer int
   // Max number of total dead containers, less than zero for no limit.
   MaxContainers int
}

1.根据用户启动kubelet的参数初始化ContainerGCPolicy对象

2.调用kubecontainer.NewContainerGC初始化containerGC

3.StartGarbageCollection()调用kl.containerGC.GarbageCollect()开始垃圾回收container

.kuberuntimegc

// Note that gc policy is not applied to sandboxes. Sandboxes are only removed when they are not ready and containing no containers.
// GarbageCollect consists of the following steps:
// * gets evictable containers which are not active and created more than gcPolicy.MinAge ago.
// * removes oldest dead containers for each pod by enforcing gcPolicy.MaxPerPodContainer.
// * removes oldest dead containers by enforcing gcPolicy.MaxContainers.
// * gets evictable sandboxes which are not ready and contains no containers.
// * removes evictable sandboxes.

1.调用evictContainers删除需要被删除的container

1.1调用evictableContainers获取所有需要被删除的container,即状态为notrunning并且创建时间超过MinAge

1.1.1调用runtimeService.ListContainers获取所有containers

1.1.2去除状态为runningcontainer,去除创建时间小于MinAgecontainer,按age排序生成container列表

1.2调用manager.removeContainer删除container列表中的所有container

1.3调用enforceMaxContainersPerEvictUnit确保每个pod中的deadcontainer个数不超过MaxPerPodContainer,否则删除最老的超过个数的container

1.4确保总的deadcontainer个数不超过MaxContainers,如果超过个数过大,则每个pod删除相应个数的container,如果还是超过,则删除最老的超过个数的container

2.调用evictSandboxes删除空的sandboxes

2.1过滤不是ready状态的sandbox,过滤仍然含有containersandbox,将剩下的sandbox删除

3.调用evictPodLogsDirectories删除podsandboxlog

3.1将“/var/log/pods中找出所有已经删除了的pod对应的目录,将这些目录删除

3.2将“"/var/log/containers"中找出所有已经deadcontainer对应的symlinks,将他们删除

ImageGCManager的流程
type ImageGCPolicy struct {
   // Any usage above this threshold will always trigger garbage collection.
   // This is the highest usage we will allow.
   HighThresholdPercent int
   // Any usage below this threshold will never trigger garbage collection.
   // This is the lowest threshold we will try to garbage collect to.
   LowThresholdPercent int
   // Minimum age at which an image can be garbage collected.
   MinAge time.Duration
}

1.初始化ImageGCPolicy对象

2.创建imageManager对象

3.调用imageManager.GarbageCollect()垃圾回收镜像

3.1调用im.cadvisor.ImagesFsInfo()获取文件系统中容器镜像的使用情况

3.2如果使用的空间大于HighThresholdPercent,则计算出需要回收多少空间(bytesToFree)才能达到LowThresholdPercent,并调用freeSpace()回收

3.2.1依次删除不使用的并且存在时间超过minage的老containerimage,直到回收空间大于bytesToFree

evictionManager的流程
OOM Killer回收资源的缺点(主要在还没有触发eviction,却触发了node上的linux kernel oom_killeroom阈值由pod qos为每个container设置):
1.它会停止node直到完成了OOM Killing Process
2.OOM Killer干掉container后,scheduler可能很快又会调用新的pod到该node上,导致再次OOM
3.如果Pod中某个容器被oom_killer干掉之后,会根据该容器的RestartPolicy决定是否restart这个容器,如果再次restart又会触发oom
eviction manager的优点(缺点处理时间久):
1.当触发eviction后,eviction manager直接fail pod来回收资源,而不是通过Linux OOM killer这样本身耗资源的组件进行回收
2.evictedpod会在其他node上重新调度(会设置node conditoins,并继续按照--node-status-update-frequency(default 10s)配置的时间间隔,周期性的与kube-apiserver进行node status updates,如果node conditions标识为资源不足(相当于做一个准入检查),kubelet拒绝创建pod中的container,并标记podPodFailed状态,导致重新调度。。再者kubelet会定期将node condition传给kubeapiserver并存如etcd中,kube-scheduler watchnode condition pressure之后,会阻止pod bind到该node),不会再次触发eviction

KubeletEviction Policy的工作机制。

  • kubelet预先监控本节点的资源使用,并且阻止资源被耗尽,这样保证node的稳定性。

  • kubelet会预先FailN(>= 1)Pod以回收出现紧缺的资源。

  • kubeletFail一个pod时,会将Pod内所有Containnerskill掉,并把PodPhase设为Failed

  • kubelet通过事先人为设定EvictionThresholds来触发Eviction动作以回收资源。

触发eviction的资源有memorynodefs(存储volumelogs等数据的空间)和imagefsdocker/rkt存放镜像的空间和容器的writable layer
eviction含有Soft Eviction ThresholdsHard Eviction Thresholds
soft:达到eviction阈值后,再次监测grace period一段时间,这段时间仍然达到阈值则触发eviction action
hard:达到eviction阈值后,直接触发eviction action
注:

Kubelet通过EvictionSignal来记录监控到的Node节点使用情况。

  • EvictionSignal支持:memory.available,nodefs.available, nodefs.inodesFree, imagefs.available,imagefs.inodesFree

  • 通过设置HardEviction ThresholdsSoftEviction
    Thresholds
    相关参数来触发Kubelet进行EvictPods的操作。

  • EvictPods的时候根据PodQoS和资源使用情况挑选Pods进行Kill

  • Kubelet通过eviction-pressure-transition-period防止NodeCondition来回切换引起scheduler做出错误的调度决定。

  • Kubelet通过--eviction-minimum-reclaim来保证每次进行资源回收后,Node的最少可用资源,以避免频繁被触发EvictPods操作。

  • NodeConditionMemoryPressure时,Scheduler不会调度新的QoSClassBestEffortPods到该Node

  • NodeConditionDiskPressure时,Scheduler不会调度任何新的Pods到该Node

流程:
0.调用ParseThresholdConfig根据用户传入的参数生成各资源的Threshold
1.定期调用synchronize()驱逐pod,并返回需要被驱逐回收的pod
2.调用waitForPodsCleanup等待pod上的资源被回收,防止再次触发eviction
2.1假如pod含有还在runningcontainer,即没有被回收完,会导致等待其回收完
2.2假如podterminatedpodcontainer没有被删除,即没有被回收完,会导致等待其回收完
2.3假如podvolume是需要删除的但还存在,即没有被回收完,会导致等待其回收完
2.4假如podcgroup sandbox没有被删除,即没有被回收完,会导致等待其回收完

一:synchronize()
1.调用makeSignalObservations创建观察者获取当前资源情况
1.1创建以下观察者:
memory.available:可用的内存
nodefs.available:系统可用的volume空间
nodefs.inodesFreeinode可用的volume空间
imagefs.available:系统可用的镜像空间
imagefs.inodesFreeinode可用的镜像空间
allocatableMemory.available:可分配给pod的内存大小
allocatableNodeFs.available:可分配给pod的存储大小
2.根据参数为memory.available创建soft/hard threshold notifier
3.将用户设置的Thresholds跟各观察者获取的当前资源情况比较,将触发了阈值的Threshold和之前没有处理完的Threshold存入Thresholds列表
4.设置第一次触发threshold的时间和最近一次触发threshold的时间
5.调用thresholdsMetGracePeriod获取在grace period时间段内一直触发阈值的threshold
6.如果本地卷临时存储中存在资源使用违规,则会调用localStorageEviction驱逐pod
6.1localStorageEviction判断pod使用的EmptyDir volumeephemeral-storage
是否超过限制,超过则将pod驱逐,
6.2localStorageEviction判断container使用的EmptyDir volumeephemeral-storage
是否超过限制,超过则将container对应的pod驱逐
6.3evictPod()执行驱逐操作,它调用podWorkers设置status managerpod的状态,并调用containerRuntime.KillPodpod中的container全部删除,遗留下一个空的pod
7.从触发阈值的thresholds中获取资源类型,并调用byEvictionPriority将资源类型排序,获取最迫切需要被回收的资源,memory优先,其它同级
8.调用reclaimNodeLevelResources尝试回收node级别的资源(非memory资源),即删除已经终止的container和删除不使用的镜像,如果能满足threthold则不驱逐pod
8.1调用containerGC.DeleteAllUnusedContainers()删除所有不使用的container
8.2调用imageGC.DeleteUnusedImages()删除所有不使用的image,最终调用freeSpace释放空间
9.根据最迫切需要被回收的资源获取rank
10.rank将所有active pod排序,
memory通过rankMemoryPressure来排序
其它存储资源通过rankDiskPressureFunc来排序
11.调用killPodFunc删除排序靠前的一个pod(只删除一个pod返回,仍旧触发threthold就再次回收一个pod),跟6的步骤类似


OOM watcher :调用cadvisor.WatchEvents监视系统的OOM, 如果发生了OOM 就产生相应的event


QOS服务质量管理
Kubelet根据Pod QoS给每个container都设置了oom_score_adj
oom_killer根据container使用的内存占Node总内存的百分比计算得到该containeroom_score,然后再将该oom_sore和前面对应的oom_score_adj相加作为最终的oom_scoreNode上所有containers的最终oom_score进行排名,将oom_score得分最高的container kill掉。通过如此方式进行资源回收。

oom_killer这样做的目标就是干掉QoS低的又消耗最多内存(相对request)的容器首先被kill掉,如此回收内存。

对于每一种Resource都可以将容器分为3QoSClasses:
Guaranteed, Burstable, andBest-Effort
,它们的QoS级别依次递减。

Guaranteed如果Pod中所有Container的所有Resourcelimitrequest都相等且不为0,则这个PodQoSClass就是Guaranteed

Best-Effort如果Pod中所有容器的所有Resourcerequestlimit都没有赋值,则这个PodQoSClass就是Best-Effort.

BurstablePOD中只要有一个容器,这个容器requestslimits的设置同其他容器设置的不一致,那么这个PODQoS就是Burstable级别

KUBELET根据limitrequset数值判断qos类型,并为container设置对应的OOM分数,kubelet在创建container的时候会把这个分数写到container对应的进程pid对应的/proc/<pid>/oom_score_adj中,OOMkiller根据这个分数kill进程

OOM分数值是根据OOM_ADJ参数计算出来的,对于Guaranteed级别的PODOOM_ADJ参数设置成了-998,对于BestEffort级别的PODOOM_ADJ参数设置成了1000,对于Burstable级别的PODOOM_ADJ参数取值从2999,对于kube保留的资源,比如kubeletOOM_ADJ参数设置成了-999OOM_ADJ参数设置的越大,通过OOM_ADJ参数计算出来的OOM分数越高,OOM分数越高,这个POD的优先级就越低,在出现资源竞争的时候,就越早被kill

注意,如果一个容器只指明了limit,而未指明request,则表明request的值等于limit的值。

从容器的角度出发,为了限制容器使用的CPU和内存,是通过cgroup来实现的,目前kubernetesQoS只能管理CPU和内存,所以kubernetes现在也是通过对cgroup的配置来实现QoS管理的

newActiveDeadlineHandler
调用statusManager获取pod的状态,从状态中获取pod的开始时间,再用当前时间减去开始时间的值与podActiveDeadlineSeconds的值比较,如果超过了ActiveDeadlineSeconds,则认为这个pod需要被驱逐

本文由【waitig】发表在等英博客
本文固定链接:Kubernetes GarbageCollector
欢迎关注本站官方公众号,每日都有干货分享!
等英博客官方公众号
点赞 (0)分享 (0)