内存分配错误无法创建容器异常排查

最近 使用Helm 3在Kubernetes集群部署Prometheus和Grafana 完成生产环境的监控部署,在一次 更新Kubernetes集群的Prometheus配置 意外发现需要更新的pod没有正常替换( 原本是准备 kube-prometheus-stack 持久化卷 )。检查pods状态:

创建pod失败,卡在容器创建步骤
# kubectl get pods -o wide | grep prometheus | grep -v exporter
alertmanager-kube-prometheus-stack-1681-alertmanager-0            2/2     Running                  1          7d3h    10.233.76.51    i-2zeav45krsh6sr8t9r4q   <none>           <none>
kube-prometheus-stack-1681-admission-create-z7p94                 0/1     ContainerCreating        0          14h     <none>          i-2zeav45krsh6sr8t9r4q   <none>           <none>
kube-prometheus-stack-1681-operator-749d66d8d4-xjnrn              1/1     Running                  0          11d     10.233.76.31    i-2zeav45krsh6sr8t9r4q   <none>           <none>
kube-prometheus-stack-1681388621-grafana-7df74fffd4-86v54         3/3     Running                  0          11d     10.233.76.35    i-2zeav45krsh6sr8t9r4q   <none>           <none>
kube-prometheus-stack-1681388621-kube-state-metrics-598b99r4zfw   0/1     ContainerCreating        0          4d13h   <none>          i-2zeav45krsh6sr8t9r4q   <none>           <none>
kube-prometheus-stack-1681388621-kube-state-metrics-67cb67w6g76   1/1     Running                  0          5d19h   10.233.76.60    i-2zeav45krsh6sr8t9r4q   <none>           <none>
prometheus-kube-prometheus-stack-1681-prometheus-0                0/2     Init:RunContainerError   0          11d     10.233.76.33    i-2zeav45krsh6sr8t9r4q   <none>           <none>

检查容器创建失败原因:

检查创建pod失败原因
# kubectl describe pods kube-prometheus-stack-1681-admission-create-z7p94
...
Events:
  Type     Reason                    Age                    From     Message
  ----     ------                    ----                   ----     -------
  Warning  FailedCreatePodContainer  2m7s (x4132 over 14h)  kubelet  unable to ensure pod container exists: failed to create container for [kubepods besteffort pod3c701e3a-1cc8-4807-a96d-425fa37d979a] : mkdir /sys/fs/cgroup/memory/kubepods/besteffort/pod3c701e3a-1cc8-4807-a96d-425fa37d979a: cannot allocate memory

# kubectl describe pods kube-prometheus-stack-1681388621-kube-state-metrics-598b99r4zfw
...
Events:
  Type     Reason                    Age                       From     Message
  ----     ------                    ----                      ----     -------
  Warning  FailedCreatePodContainer  2m6s (x30278 over 4d13h)  kubelet  unable to ensure pod container exists: failed to create container for [kubepods besteffort podeb6abc31-be1d-46a3-b93e-4230893ac649] : mkdir /sys/fs/cgroup/memory/kubepods/besteffort/podeb6abc31-be1d-46a3-b93e-4230893ac649: cannot allocate memory

可以看到两个替换pods的容器创建失败都是由于内存分配失败导致 cannot allocate memory

由于正在走 kube-prometheus-stack 持久化卷 ,一旦切换就会出现 Grafana通用可视分析平台 数据丢失,需要从备份中恢复,所以断开时间较长。非常巧,这个卡住并不影响 Prometheus配置(文件) 更新,只是容器不能重建导致状态一直显示不正常,虽然一切功能运行正常。

模仿 kubernetesevents 中内容手工创建cgroup目录确实报错:

# mkdir /sys/fs/cgroup/memory/kubepods/besteffort/podeb6abc31-be1d-46a3-b93e-4230893ac649
mkdir: cannot create directory ‘/sys/fs/cgroup/memory/kubepods/besteffort/podeb6abc31-be1d-46a3-b93e-4230893ac649’: Cannot allocate memory

但是非常奇怪的是,通过 top 观察可以看到系统内存充足:

Tasks: 365 total,   1 running, 364 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.9 us,  0.2 sy,  0.0 ni, 98.8 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem : 25982569+total, 19016694+free, 10491360 used, 59167380 buff/cache
KiB Swap:        0 total,        0 free,        0 used. 24839529+avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
21993 admin     20   0   42.7g   4.9g   1.4g S  23.6  2.0   4510:43 prometheus
...

检查 slab

# ls /sys/kernel/slab | wc -l
182210

检查 slabtop :

检查 lsabtop
slabtop -s -c

解决方法

Kubernetes Cannot Allocate Memory | Solutions Revealed. 提供了相关信息和解决方案:

  • 上述报错是因为 cgroup memory allocation 无法分配

  • 内核 3.10.0-1062.4.1 或更新版本解决了 slab 泄漏问题,这个 slab leak 会导致 kmem control group 奔溃(不过,也有讨论提到高于 3.10.0-1160.36.2.el7.x86 64 还是存在问题)

我的服务器内核版本确实是 3.10.0-1160.83.1.el7.x86_64 ,看起来确实存在这个 slab leak 问题

解决方法有两个:

方法一

  • 配置内核参数 cgroup.memory=nokmem (修订 /etc/default/grubGRUB_CMDLINE_LINUX 行配置)

  • 修改grub并重启:

    grub2-mkconfig -o /boot/grub2/grub.cfg
    reboot
    

方法二

升级内核

我检查了我的服务器,当前操作系统版本:

# cat /etc/redhat-release
CentOS Linux release 7.9.2009 (Core)

但是如果不做大版本升级,则内核版本升级也只有 3.10.0-1160.88.1.el7 : 已检查 mirrors.163.com ,CentOS 7.9.2009 已经是最高的7系列版本,从仓库能够找到的最新内核版本也只有 3.10.0-1160.88.1.el7 ,这已经是 2023年3月8日的更新版本

Red Hat官方解决方案

根据Red Hat官方知识库 The kmem enabled memory cgroup cannot be fully released unless all associated kmem_cache are “destroyed” 提供的解决方案:

  • 如果你的工作负载环境经常遇到这个问题,则建议升级到 RHEL 8.1 版本,内核补丁已经修复了这个问题

  • 对于RHEL7系统(RHEL 7.7及更高版本),需要在内核命令行参数添加 cgroup.memory=nokmem 来禁止内核内存记账功能( disable the kernel memory accounting )

  • 目前RedHat内部私有bug修复补丁正在调研 BZ#1925619 (需要注意RHEL7已经进入Maintenance phase II, RedHat不保证这个bug一定修复)

备注

Kernel Memory Extension (CONFIG_MEMCG_KMEM) 解释内核内存扩展

根因

在分层记账(hierarchical accounting)的上下文(context)中,根据 mm/memcontrol.c 中的逻辑,只有在所有未决的内核内存记账(关联的slab对象)被删除以后,内存控制的cgroup才会被正确删除。即一旦页面被释放,并且相应的 kmem_cache (slab) 被销毁。此时,分配给每个内存控制的 cgroup ( memcg-ID )的唯一标识符在最后一次引用计数下从 “私有IDR” (private IDR)中删除。所以,确实有可能在正常运行时消耗掉整个IDR。此时遇到 mkdir 返回 -ENOMEM (或无法分配内存),即使同一层次结构中的 cgroup 数量表明依然有可用空间(以创建额外的内存控制cgroup)。

这个问题的已知/适当补丁解决方案是将 kmem 记账移动/重新设置为父内存cgroup(RHEL 8.1补丁);但是RHEL7缺乏先决的几个补丁来修改内存记账基础架构,以便能够重新设置内存记账,所以这个补丁不太可能移植到RHEL 7。

我的修复

我参考Red Hat官方方法: 首先升级CentOS 7.9操作系统(但是由于生产限制不能升级到8.1版本),然后修改内核参数添加 cgroup.memory=nokmem 关闭内存记账功能,并重启服务器

参考