K8S异常诊断之俺的内存呢

简介: K8S异常诊断之俺的内存呢

阿里妹导读


本文讲述作者如何解决客户集群中出现的OOM(Out of Memory)和Pod驱逐问题。文章不仅详细记录了问题的发生背景、现象特征,还深入探讨了排查过程中的关键步骤和技术细节。

人在工位坐,锅又双叒叕从天上来:

某日下午,正当我在工位勤恳工作时,我沉寂已久的电话铃声突然响起,刚接起来就听见对面哭喊着:“牧原老哥,救救我啊!”。原来某TAM同学拜访客户,恰逢客户集群多个节点和业务出现OOM以及驱逐pod的情况,需要我们快速救援,那咱必须救兄弟姐妹于金木水火土之中啊

问题特征有几个....:

  • 多集群,多业务,多节点出现驱逐
  • 不同的业务pod的内存都相当大,独占节点(节点16c32G ,pod limit 15c 31G-主业务container)
  • 既有podOOM又有节点内存不足的驱逐 "reason":"Evicted","message":"The node was low on resource: memory
  • 调小kubelet保留内存(调整前可用26G,调整后29G),依然约26G触发OOM

 

闻弦歌而知雅意,这个问题不高级~:

这现象看起来就是业务正常的增长导致内存用完了,还要啥自行车啊? 

用户表示,我业务实际没有使用那么多内存,并提供了内存使用的监控截图。

对于这类问题,如果我们想要细致的分析,是需要采集一些关键信息来佐证和分析。

a) 找到驱逐状态的pod查看下驱逐的原因,可以看到这个pod的驱逐原因是节点内存不足,同时输出了这个container使用了约27G的内存,初始的request内存是24G。

 

b) 我们看下pod的yaml对资源这块的设置,主业务确实是31G,以及当前是否有大内存使用的pod,发现多个25G左右的内存使用率pod。

 



c) 查看节点如top node,节点的可分配资源,以及运行的pod,超卖信息等,这里面满满都是信息啊。

 

   

d) 客户反馈调小过系统的资源保留,因此我们也要看下kubelet的参数是否生效,可以看到,reserved的内存已经降低到了100m,说明调整已经生效了。

 

既然基础信息采集完毕,开始整活:

触发了OOM,那么系统会输出oom的相关信息,且会输出具体的进程占用的内存开销,我们来一起看看系统日志吧。 

OOM日志分析必备小知识之看图说话:
OOM日志之开始:



  • ranker_service进程申请1个page(4kb)的内存时,由于内存不足触发OOM(order=0 2的0次方=1),下图来自网络:



  • cgroup memcg这两个字样很重要,说明oom是在cgroup里面触发的。
OOM日志之关键信息:



  • 触发OOM的task是在这个cgroup路径里面。
  • 当前的内存使用以及limit上限,failecnt代表申请失败的次数(申请失败不会立刻oom,会先尝试回收内存 )。
  • 同时输出了很多cgroup的统计信息,每个cgroup使用的内存统计,可以先忽略。



  • OOM时统计到的进程内存开销信息,开销最大就是客户的ranker_service,并且最终这个进程被kill了。

 



这里有个知识点,rss是page数,因此计算进程的内存开销应该用rss*4kb去计算。 



通过计算rss的page值得出,内存还远远没达到系统的最大内存:



echo "scale=2; 6901788 * 4 / 1024/1024" |bc26.32G

少年,有没有感觉哪里不对劲?

这个cgroup它不正经啊,兄弟:

是的,你的感觉没有错,cgroup的limit是 27968328 kB。

 

节点的Allocatable: 31273800 Ki,

   

按理说cgroup的limit应该是Allocatable差不多的值,这里偏差有点大,拿出我当年尝试论证「哥德巴赫猜想」的数学能力,经过一番缜密的计算得出,cgroup的limit小于节点的Allocatable,这个cgroup它不正经啊,兄弟 







cgroup limit:%  echo "scale=2; 27968328 / 1024/1024" |bc    26.67node Allocatable:%  echo "scale=2; 31273800 / 1024/1024" |bc29.82

cgroup设置的总得limit在这个文件中设置,它是kubelet初始化的时候设置的。

 

我们看下ack的初始化日志,读取自定义配置文件,计算要保留的内存。

 

可以看下大概的时间点,在17:34:26分的时候完成的计算。

 

客户通过自定义kubelet参数修改了保留内存,那我们得看下文件/etc/kubernetes/kubelet-customized-args.conf 的变动时间,以及kubelet加载的时间,文件是17:35:18左右修改的,难道是文件修改的延迟太大,导致没有覆盖?



不然,kubelet监测到配置文件变更会有一次reload的行为,因此我们可以继续看下日志,是否有重新加载。

可以看到kubelet的参数被配置成了100m。

 

配置难道不成功? 

kubelet刷新cgroup的配置也是有日志记录的,我们可以通过关键词“Updated Node Allocatable limit across pods”看下同步的时间。



17:35:18是有刷新的日志的,且该日志表明强制配置cgroup成功后返回,兜兜转转又双叒叕来到了我最不喜欢的翻代码环节。

此处省略一万字


// If Node Allocatable is enforced on a node that has not been drained or is updated on an existing node to a lower value,  // existing memory usage across pods might be higher than current Node Allocatable Memory Limits.  // Pod Evictions are expected to bring down memory usage to below Node Allocatable limits.  // Until evictions happen retry cgroup updates.  // Update limits on non root cgroup-root to be safe since the default limits for CPU can be too low.  // Check if cgroupRoot is set to a non-empty value (empty would be the root container)  if len(cm.cgroupRoot) > 0 {    go func() {      for {        err := cm.cgroupManager.Update(cgroupConfig)        if err == nil {          cm.recorder.Event(nodeRef, v1.EventTypeNormal, events.SuccessfulNodeAllocatableEnforcement, "Updated Node Allocatable limit across pods")          return        }        message := fmt.Sprintf("Failed to update Node Allocatable Limits %q: %v", cm.cgroupRoot, err)        cm.recorder.Event(nodeRef, v1.EventTypeWarning, events.FailedNodeAllocatableEnforcement, message)        time.Sleep(time.Minute)      }    }()  }  // Now apply kube reserved and system reserved limits if required.  if nc.EnforceNodeAllocatable.Has(kubetypes.SystemReservedEnforcementKey) {    klog.V(2).Infof("Enforcing System reserved on cgroup %q with limits: %+v", nc.SystemReservedCgroupName, nc.SystemReserved)    if err := enforceExistingCgroup(cm.cgroupManager, cm.cgroupManager.CgroupName(nc.SystemReservedCgroupName), nc.SystemReserved); err != nil {      message := fmt.Sprintf("Failed to enforce System Reserved Cgroup Limits on %q: %v", nc.SystemReservedCgroupName, err)      cm.recorder.Event(nodeRef, v1.EventTypeWarning, events.FailedNodeAllocatableEnforcement, message)      return fmt.Errorf(message)    }    cm.recorder.Eventf(nodeRef, v1.EventTypeNormal, events.SuccessfulNodeAllocatableEnforcement, "Updated limits on system reserved cgroup %v", nc.SystemReservedCgroupName)  }  if nc.EnforceNodeAllocatable.Has(kubetypes.KubeReservedEnforcementKey) {    klog.V(2).Infof("Enforcing kube reserved on cgroup %q with limits: %+v", nc.KubeReservedCgroupName, nc.KubeReserved)    if err := enforceExistingCgroup(cm.cgroupManager, cm.cgroupManager.CgroupName(nc.KubeReservedCgroupName), nc.KubeReserved); err != nil {      message := fmt.Sprintf("Failed to enforce Kube Reserved Cgroup Limits on %q: %v", nc.KubeReservedCgroupName, err)      cm.recorder.Event(nodeRef, v1.EventTypeWarning, events.FailedNodeAllocatableEnforcement, message)      return fmt.Errorf(message)    }    cm.recorder.Eventf(nodeRef, v1.EventTypeNormal, events.SuccessfulNodeAllocatableEnforcement, "Updated limits on kube reserved cgroup %v", nc.KubeReservedCgroupName)  }
不可辜负的运维三宝,重启,重装,换电脑:

为了验证kubelet设置的值是否正确,祭出运维三宝之重启下kubelet试试,重启kubelet后memory.limit_in_bytes被设置为了正确值。

 

so,谁改了我的memory.limit 呢?

灵光一闪,How old are you systemd?

云上ACK集群默认使用的cgroup驱动是systemd,会不会是systemd在搞鬼呢?


systemctl cat kubepods.slicesystemctl show kubepods.slice

可以看到,systemd记录到的memory_limit是初始值(kubelet加载自定义配置之前的初始值)。

 

重启kubelet后会覆盖成新值,但是触发daemon-reload的话会再次覆盖回去。





小锤40,大锤80,github的issue 120~:

daemon-reload跟kubelet分开运行,互相覆盖对方的值,从issue来看,systemd覆盖了cgroup的limit值,而systemd记录的memory.limit是kubelet第一次计算的值,不是重新加载客户自定义配置的值,因此当daemon-reload时会导致覆盖。

https://github.com/kubernetes/kubernetes/issues/104289

只要锄头挥得好,没有bug挖不了~:

所以这个问题我们主要考虑的点在 kubelet计算完保留值,写入systemd的memory.limit后,再加载客户自定义配置重新计算limit的逻辑,是不是没有再次更新systemd的memory.limit的值呢?

难道又双叒叕到了最“喜欢”的翻代码环节?

代码翻不好,还是谷歌更美妙:

再次拿出媲美我小学三年级儿子的英文水平。

“kubelet change memory limit but not change systemd memorylimit” 

https://github.com/kubernetes/kubernetes/issues/88197

kubelet计算自定义的预留值后,没有update systemd的配置文件导致。

根本解法:1.20的集群升级到1.22及以上版本的集群,修复了覆盖systemd的问题。

临时方案:手动修改systemd记录的memory.limit值,然后重启,不然直接重启kubelet的话还是会被覆盖,极度不推荐。

收工,又是快乐的一天~

如果想了解K8S 集群中更多常见的故障类型及其成因,点击阅读原文,免费阅读《云运维工程师系列之云容器 K8S 异常诊断》。不仅能够加深对 Kubernetes 工作原理的理解,还能掌握一系列实用技巧来提升自己在面对突发状况时的应变能力。

相关实践学习
深入解析Docker容器化技术
Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。Docker是世界领先的软件容器平台。开发人员利用Docker可以消除协作编码时“在我的机器上可正常工作”的问题。运维人员利用Docker可以在隔离容器中并行运行和管理应用,获得更好的计算密度。企业利用Docker可以构建敏捷的软件交付管道,以更快的速度、更高的安全性和可靠的信誉为Linux和Windows Server应用发布新功能。 在本套课程中,我们将全面的讲解Docker技术栈,从环境安装到容器、镜像操作以及生产环境如何部署开发的微服务应用。本课程由黑马程序员提供。     相关的阿里云产品:容器服务 ACK 容器服务 Kubernetes 版(简称 ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情: https://www.aliyun.com/product/kubernetes
相关文章
|
8月前
|
运维 Kubernetes 监控
K8S异常诊断之俺的内存呢
本文讲述作者如何解决客户集群中出现的OOM(Out of Memory)和Pod驱逐问题。文章不仅详细记录了问题的发生背景、现象特征,还深入探讨了排查过程中的关键步骤和技术细节。
573 108
K8S异常诊断之俺的内存呢
|
11月前
|
弹性计算 Kubernetes Perl
k8s 设置pod 的cpu 和内存
在 Kubernetes (k8s) 中,设置 Pod 的 CPU 和内存资源限制和请求是非常重要的,因为这有助于确保集群资源的合理分配和有效利用。你可以通过定义 Pod 的 `resources` 字段来设置这些限制。 以下是一个示例 YAML 文件,展示了如何为一个 Pod 设置 CPU 和内存资源请求(requests)和限制(limits): ```yaml apiVersion: v1 kind: Pod metadata: name: example-pod spec: containers: - name: example-container image:
1381 2
|
Prometheus Kubernetes 监控
在K8S中,Pod占用内存和cpu较高,该如何解决?
在K8S中,Pod占用内存和cpu较高,该如何解决?
|
Kubernetes NoSQL Redis
容器服务ACK常见问题之修改内存限制失败如何解决
容器服务ACK(阿里云容器服务 Kubernetes 版)是阿里云提供的一种托管式Kubernetes服务,帮助用户轻松使用Kubernetes进行应用部署、管理和扩展。本汇总收集了容器服务ACK使用中的常见问题及答案,包括集群管理、应用部署、服务访问、网络配置、存储使用、安全保障等方面,旨在帮助用户快速解决使用过程中遇到的难题,提升容器管理和运维效率。
|
Kubernetes 网络性能优化 调度
k8s的内存分配
k8s的内存分配
|
Prometheus Kubernetes 监控
Kubernetes APIServer 内存爆满分析
董江,容器技术布道者及实践者,中国移动高级系统架构专家,曾担任华为云核心网技术专家,CloudNative社区核心成员,KubeServiceStack社区发起者,Prometheus社区PMC,Knative Committer,Grafana社区Contributer。 欢迎关注:https://kubeservice.cn/
Kubernetes APIServer 内存爆满分析
|
移动开发 JSON Kubernetes
k8s异常诊断之no space left on device.
某用户反馈,特定节点一直拉不起来pod,提示no space left on device.,手动去docker run也是相同的报错 # docker run --name aestools-perf --cap-add CAP_SYS_ADMIN --privileged -ti --rm registry-vpc.cn-beijing.aliyuncs.com/my-nettools/aestools:onlyperf docker: Error response from daemon: error creating overlay mount to /var/li
2962 3
|
3月前
|
存储
阿里云轻量应用服务器收费标准价格表:200Mbps带宽、CPU内存及存储配置详解
阿里云香港轻量应用服务器,200Mbps带宽,免备案,支持多IP及国际线路,月租25元起,年付享8.5折优惠,适用于网站、应用等多种场景。
786 0
|
3月前
|
存储 缓存 NoSQL
内存管理基础:数据结构的存储方式
数据结构在内存中的存储方式主要包括连续存储、链式存储、索引存储和散列存储。连续存储如数组,数据元素按顺序连续存放,访问速度快但扩展性差;链式存储如链表,通过指针连接分散的节点,便于插入删除但访问效率低;索引存储通过索引表提高查找效率,常用于数据库系统;散列存储如哈希表,通过哈希函数实现快速存取,但需处理冲突。不同场景下应根据访问模式、数据规模和操作频率选择合适的存储结构,甚至结合多种方式以达到最优性能。掌握这些存储机制是构建高效程序和理解高级数据结构的基础。
222 1

热门文章

最新文章

推荐镜像

更多