.. _fail_create_container_cannot_allocate_memory:
===================================================
内存分配错误无法创建容器异常排查
===================================================
最近 :ref:`helm3_prometheus_grafana` 完成生产环境的监控部署,在一次 :ref:`update_prometheus_config_k8s` 意外发现需要更新的pod没有正常替换( 原本是准备 :ref:`kube-prometheus-stack_persistent_volume` )。检查pods状态:
.. literalinclude:: fail_create_container_cannot_allocate_memory/ContainerCreating_fail
:caption: 创建pod失败,卡在容器创建步骤
:emphasize-lines: 3,6
检查容器创建失败原因:
.. literalinclude:: fail_create_container_cannot_allocate_memory/check_ContainerCreating_fail
:caption: 检查创建pod失败原因
:emphasize-lines: 6,13
可以看到两个替换pods的容器创建失败都是由于内存分配失败导致 ``cannot allocate memory``
由于正在走 :ref:`kube-prometheus-stack_persistent_volume` ,一旦切换就会出现 :ref:`grafana` 数据丢失,需要从备份中恢复,所以断开时间较长。非常巧,这个卡住并不影响 :ref:`prometheus_configuration` 更新,只是容器不能重建导致状态一直显示不正常,虽然一切功能运行正常。
模仿 ``kubernetes`` 的 ``events`` 中内容手工创建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`` :
.. literalinclude:: fail_create_container_cannot_allocate_memory/slabtop
:language: bash
:caption: 检查 ``lsabtop``
解决方法
===========
`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/grub`` 的 ``GRUB_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一定修复)
.. note::
:ref:`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`` 关闭内存记账功能,并重启服务器
参考
======
- `Kubernetes Cannot Allocate Memory | Solutions Revealed. `_
- `K8s - Slab memory leakage `_
- `The kmem enabled memory cgroup cannot be fully released unless all associated kmem_cache are "destroyed" `_