在线被驱逐实例数据最近,网上发现很多实例处于被驱逐状态。通过podyaml可以看到是由于节点资源不足导致实例被逐出,但是这些实例并没有被自动清理掉,平台大部分用户都在操作有时候看到服务下出现Evictedinstances,会觉得服务或平台出现问题,影响用户体验。实际上,这些处于Evicted状态的Pod关联的容器在底层已经销毁,不会对用户服务造成任何影响。也就是说k8s里面只存放了一个Podshell,但是需要手动清理。本文将分析为什么不生成Evicted实例,为什么不自动清理Evicted实例,以及如何自动清理。kubernetes版本:v1.17$kubectlgetpod|grep-iEvictedcloud-1023955-84421-49604-5-deploy-c-7748f8fd8-hjqsh0/1Evicted073dcloud-1023955-84421-49604-5-deploy-c-7748f8fd8-mzd8x0/1Evicted081dcloud-1237162-276467-199844-2-deploy-7bdc7c98b6-26r2r0/1Evicted018dEvicted实例状态:status:message:'PodThenodehadcondition:[DiskPressure].'phase:Failedreason:EvictedstartTime:"2021-09-14T10:42:32Z"实例被逐出原因Kubelet默认配置节点资源不足时淘汰实例的策略。当节点资源不足时,k8s会停止该节点上的实例,并在其他节点上启动新的实例。在某些情况下,也可以通过配置--eviction-hard=参数Empty来禁用逐出策略,我们在之前的生产环境中就是这样做的。节点资源不足导致实例被逐出k8s中Evicted状态的实例主要是由于节点资源不足导致实例被主动逐出。kubeleteviction_manager模块会定时检查节点内存使用情况、inode使用情况、磁盘使用情况、pid等资源,根据kubelet的配置达到一定阈值时,可回收资源优先回收。如果回收后资源使用量仍然超过阈值,实例将被逐出。驱逐信号描述memory.availablememory.available:=node.status.capacity[memory]??-node.stats.memory.workingSetnodefs.availablenodefs.available:=node.stats.fs.availablenodefs.inodesFreenodefs.inodesFree:=node.stats.fs.inodesFreeimagefs.availableimagefs.available:=node.stats.runtime.imagefs.availableimagefs.inodesFreeimagefs.inodesFree:=node.stats.runtime.imagefs.inodesFreepid.availablepid.available:=node.stats.rlimit.maxpid-node.stats.rlimit.curprockubelet中pod的stats数据一部分是通过cAdvisor接口获取的,一部分是通过CRIruntimes接口获取的。memory.available:当前节点的可用内存,由cgroup内存子系统中memory.usage_in_bytes中的值减去memory.stat中total_inactive_file的值计算得出;nodefs.available:nodefs包含kubelet配置中--root-dir指定的文件磁盘使用分区和/var/lib/kubelet/;nodefs.inodesFree:nodefs.available分区的inode使用情况;imagefs.available:镜像所在分区的磁盘使用情况;imagefs.inodesFree:image所在分区的inode使用率pid.available:/proc/sys/kernel/pid_max中的值是系统可用pid的最大数量;kubelet可以通过参数--eviction-hard配置以上参数的阈值,该参数默认值为imagefs.available<15%,memory.available<100Mi,nodefs.available<10%,nodefs.inodesFree<5%,当达到阈值时,节点上的容器将被逐出。kubeletevictinstances时资源处理相关的已知问题1.kubelet不实时感知节点内存数据的变化。Kubelet通过cadvisor接口定期收集节点内存使用数据。无法感知,不会有MemoryPressure相关事件,但仍会调用OOMKiller停止容器。可以通过为kubelet配置--kernel-memcg-notification参数来启用memcgapi。当触发内存使用阈值时,memcg会主动通知;memory.available的阈值写在cgroup/memory/cgroup.event_control文件中,阈值与inactive_file文件的大小有关。kubelet也会定期更新阈值。当memcg使用率达到配置的阈值时,会主动通知kubelet,kubelet会通过epoll机制接收通知。2.kubeletmemory.available不会计算活跃页。当kubelet通过内存占用逐出实例时,内存占用数据包括pagecache中active_file的数据。在某些场景下,内存使用率超过阈值是因为页面缓存过高。导致实例被逐出,因为inactive_file在内存不足时会先被内核回收,而active_file在内存不足时也会被内核回收。社区对这个机制也有一些质疑。内核回收内存的情况比较复杂。暂无回复,请参考kubeletcountsactivepagecacheagainstmemory.available(maybeitshouldn'tshouldn't?)[1]了解详情。kubelet计算节点可用内部存储的方式如下:#!/bin/bash#!/usr/bin/envbash#Thisscriptreproduceswhatthekubeletdoes#tocalculatememory.availablerelativetorootcgroup.#currentmemoryusagememory_capacity_in_kb=$(cat/proc/meminfo|grepMemTotal|awk'{print$2)memory_capacity_in_bytes=$((memory_capacity_in_kb*1024))memory_usage_in_bytes=$(cat/sys/fs/cgroup/memory/memory.usage_in_bytes)memory_total_inactive_file=$(cat/sys/fs/cgroup/memory/memory.stat|greptotal_inactive_file|awk'{print$2}')memory_working_set=${memory_usage_in_bytes}if["$memory_working_set"-lt"$memory_total_inactive_file"];thenmemory_working_set=0elsememory_working_set=$((memory_usage_in_bytes-memory_total_inactive_file))fimemory_available_in_bytes=$((memory_capacity_in_bytes-memory_working_set))memory_available_in_kb=$((memory_available_in_bytes/1024))memory_available_in_mb=$((memory_available_in_kb/1024))echo"memory.capacity_in_bytes$memory_capacity_in_bytes"echo"memory.usage_in_bytes$memory_usage_in_bytes"echo"memory.total_inactive_file$memory_total_inactive_file"echo"memory.working_set$memory_working_set"echo"memory.available_in_bytes$memory_available_in_bytes"echo"memory.available_in_kb$memory_available_in_kb"echo"memory.available_in_mb$memory_available_in_mb"驱逐实例未被删除原因源码分析,Statefulset和DaemonSet会自动删除Evictedinstances,Deployment不会自动删除,看了一些官方文档和issues,没有找到官方解释为什么DeploymentEvictedinstances不删除。statefulset:pkg/controller/statefulset/stateful_set_control.go//Examineeachreplicawithrespecttoitsordinalfori:=rangereplicas{//deleteandrecreatefailedpodsifisFailed(replicas[i]){ssc.recorder.Eventf(set,v1.EventTypeWarning,"RecreatingFailedPod","StatefulSet%s/%sisrecreatingfailedPod%s",set.Namespace,set.Name,replicas[i].Name)iferr:=ssc.podControl.DeleteStatefulPod(set,replicas[i]);err!=nil{return&status,err}ifgetPodRevision(replicas[i])==currentRevision.Name{status.CurrentReplicas--}ifgetPodRevision(replicas[i])==updateRevision.Name{status.UpdatedReplicas--}......daemonset:pkg/controller/daemon/daemon_controller。gofunc(dsc*DaemonSetsController)podsShouldBeOnNode(......)(nodesNeedingDaemonPods,podsToDelete[]string){......switch{......caseshouldContinueRunning:......for_,pod:=rangedaemonPods{ifpod.DeletionTimestamp!=nil{continue}ifpod.Status.Phase==v1.PodFailed{//这是一个关键的地方,DSisoftenfightingwithkubeletthatrejectspods.//Weneedtoavoidhotloopingandbackoff.backoffKey:=failedPodsBackoffKey(ds,node.Name)...解决方法1、团队中有一套k8s集群事件采集链接。我们在k8s中消费pod的相关事件消费事件时,过滤pod中Evicted实例相关的事件,然后处理Evicted实例判断逻辑:const(podEvictedStatus="Evicted")//判断是否是Evicted实例中的实例Evicted状态且Pod中容器数为0直接删除podifstrings.ToLower(status)==strings.ToLower(podEvictedStatus)&&len(pod.Status.ContainerStatuses)==0{}2.社区有人通过配置提供podgccontroller–terminated-pod-inkube-controller-managergc-threshold参数来自动清理:Podgccontrollerflags:--terminated-pod-gc-thresholdint32Numberofterminatedpodsthatcanexistbeforetheterminatedpodgarbagecollectorstartsdeletingterminatedpods.If<=0,theterminatedpodgarbagecollectorisdisabled.(default12500)该参数配置的是保留的异常实例数,默认值为12500,但podgccontroller回收对pod使用forcekill模式不支持实例的优雅退出,所以暂时不考虑使用。3.其他处理方式请参考社区提供的Kubeletdoesnotdeleteevictedpods[2]。总结由于之前公司对稳定性的高度重视,上线节点没有开启驱逐实例的功能,所以不会出现Evicted状态的实例。当节点资源严重不足时,会有告警,需要人工介入处理。还会有二次调度、故障自愈等辅助处理措施。这篇对Evicted相关实例的分析发现,k8s与操作系统之间有很多联系。要想吃透一些机制,就需要对操作系统的一些原理有一定的了解。
