【原】kubernetes 1.9 与 CentOS 7.3 内核兼容问题

    生产环境发现不定时 Java 应用出现 coredump 故障,测试环境不定时出现写入 /cgroup/memory 报  no space left on device 的故障,导致整个 kubernetes node 节点无法使用。设置会随着堆积的 cgroup 越来越多,docker ps 执行异常,直到把内存吃光,机器挂死。
    典型报错:

引用
kubelet.ns-k8s-node001.root.log.ERROR.20180214-113740.15702:1593018:E0320 04:59:09.572336 15702 remote_runtime.go:92] RunPodSandbox from runtime service failed: rpc error: code = Unknown desc = failed to start sa
ndbox container for pod “osp-xxx-com-ljqm19-54bf7678b8-bvz9s”: Error response from daemon: oci runtime error: container_linux.go:247: starting container process caused “process_linux.go:258: applying cgroup configuration
for process caused \”mkdir /sys/fs/cgroup/memory/kubepods/burstable/podf1bd9e87-1ef2-11e8-afd3-fa163ecf2dce/8710c146b3c8b52f5da62e222273703b1e3d54a6a6270a0ea7ce1b194f1b5053: no space left on device\””

或者

引用
Mar 26 18:36:59 ns-k8s-node-s0054 kernel: SLUB: Unable to allocate memory on node -1 (gfp=0x8020)
Mar 26 18:36:59 ns-k8s-noah-node001 kernel: cache: ip6_dst_cache(1995:6b6bc0c9f30123084a409d89a300b017d26ee5e2c3ac8a02c295c378f3dbfa5f), object size: 448, buffer size: 448, default order: 2, min order: 0

    该问题发生前后,进行过 kubernetes 1.6 到 1.9 的升级工作。怀疑问题与 kubernetes 、内核有关。

1.对比测试结果
使用同样的测试方法,结果为:
1)使用初次部署 k8s 1.6 版本测试,没有出现 cgroup memory 遗漏问题;
2)从 k8s 1.6 升级到 1.9 后,测试没有出现 cgroup memory 遗漏问题;
3)重启 kubelet 1.9 node 节点重启,再次测试,出现 cgroup memory 遗漏问题。
对比 k8s 1.6 和 1.9 创建的 POD 基础容器和业务容器,runc、libcontainerd、docker inspect 的容器 json 参数都一致,没有差异。
为什么同样是 k8s 1.9(其他 docker、kernel 版本一致)的情况下,结果不一样呢?重启的影响是?

2.问题重现
对于 cgroup memory 报 no space left on device ,是由于 cgroup memory 存在 64k(65535 个)大小的限制。
采用下面的测试方式,可以发现在删除 pod 后,会出现 cgroup memory 遗漏的问题。该测试方法通过留空 99 个 系统 cgroup memory 位置,来判断引起问题的原因是由于 pod container 导致的。
1)填满系统 cgroup memory

把系统 cgroup memory 填到 65535 个。

2)腾空 99 个 cgroup memory

在写入第 100 个的时候提示无法写入,证明写入了 99 个。

3)创建一个 pod 到这个 node 上,查看占用的 cgroup memory 情况
每创建一个 pod ,会占用 3 个 cgroup memory 目录:

这时再次创建 100 个 cgroup memory ,因为 pod 占用了 3 个,会出现 4 个无法成功:

写入到的 cgroup memory 增加到 65439 个。

4)删掉测试 pod ,看看 3 个占用的 cgroup memory 是否有释放
看到的结果:

可以看到,虽然 cgroup memory 减少到 65536 ,似乎 3 个位置释放了。但实际上测试结果发现,并不能写入,结果还是 pod 占用时的无法写入 97-100 。
这就说明,cgroup memory 数量减少,但被 pod container 占用的空间没有释放。

反复验证后,发现随着 pod 发布和变更的增加,该问题会越来越严重,直到把整台机器的 cgroup memory 用完。

3.问题根源
经过大量的测试和对比分析,在两个 k8s 1.9 环境中 kubelet 创建的 /sys/fs/cgroup/memory/kubepods 差异,发现:

也就是说,在有问题的环境下,cgroup kernel memory 特性被激活了。

关于 cgroup kernel memory,在 kernel-doc 中有如下描述:

引用
# vim /usr/share/doc/kernel-doc-3.10.0/Documentation/cgroups/memory.txt
2.7 Kernel Memory Extension (CONFIG_MEMCG_KMEM)
With the Kernel memory extension, the Memory Controller is able to limit
the amount of kernel memory used by the system. Kernel memory is fundamentally
different than user memory, since it can’t be swapped out, which makes it
possible to DoS the system by consuming too much of this precious resource.
Kernel memory won’t be accounted at all until limit on a group is set. This
allows for existing setups to continue working without disruption. The limit
cannot be set if the cgroup have children, or if there are already tasks in the
cgroup. Attempting to set the limit under those conditions will return -EBUSY.
When use_hierarchy == 1 and a group is accounted, its children will
automatically be accounted regardless of their limit value.
After a group is first limited, it will be kept being accounted until it
is removed. The memory limitation itself, can of course be removed by writing
-1 to memory.kmem.limit_in_bytes. In this case, kmem will be accounted, but not
limited.

这是一个 cgroup memory 的扩展,用于限制对 kernel memory 的使用。但该特性在老于 4.0 版本中是个实验特性,若使用 docker run 运行,就会提示:

引用
# docker run -d –name test001 –kernel-memory 100M registry.vclound.com:5000/hyphenwang/sshdserver:v1
WARNING: You specified a kernel memory limit on a kernel older than 4.0. Kernel memory limits are experimental on older kernels, it won’t work as expected and can cause your system to be unstable.
# cat /sys/fs/cgroup/memory/docker/eceb6dfba2c64a783f33bd5e54cecb32d5e64647439b4932468650257ea06206/memory.kmem.limit_in_bytes
104857600


经过反复验证,当使用 docker run –kernel-memory 参数启动的容器,在删除后也不会释放 cgroup memory 占用的位置 ,存在同样的问题。

基于该现象,对比 kubernetes 1.6 和 kubernetes 1.9 对 cgroup kernel memory 设置的差异。
在k8s 1.9 vendor库中,少了if d.config.KernelMemory != 0 { 这行代码的判断, 而在k8s的Github,1.6.4版本在官方的runc/libcontainerd增加了这行代码,但在1.9.0删掉了,导致默认就使用kmem 。

这就引发了 cgroup memory 也不能释放的问题(与通过 docker run –kernel-memory 打开的情况一样。)
其实根据 libcontainerd 推荐的 kernel 4.3 以上版本,打开 cgroup kernel memory 应该也是没问题的。可惜我们使用的kernel版本是3.10.0-514.16.1.el7.x86_64,kernel memory在这个版本是不稳定的。因此让我们踩了这个坑。

4.解决问题
经过以上分析,造成该故障的原因,是由于 kubelet 1.9 激活了 cgroup kernel-memory 特性,而在 CentOS 7.3 kernel 3.10.0-514.10.1.el7.x86_64 中对该特性支持不好。
导致删除容器后,仍有对 cgroup memory 的占用,没有执行 free_css_id() 函数的操作。
解决方式,就是修改 k8s 1.9 代码,再次禁止设置 cgroup kernel-memory 配置,保持关闭状态。

5.疑问
1)为什么同样是 k8s 1.9 的版本,在不同的测试中没有问题
原因是第一次的 k8s 1.9 是在原来 k8s 1.6 的环境中,通过升级 kubelet 版本来测试的。
而 /sys/fs/cgroup/kubepods 是由 k8s 1.6 创建的,升级到 k8s 1.9 后,启动服务时,判断到该路径已经存在,就没有再创建新的。
所以,也就没有激活 cgroup kernel-memory 特性。
接下来创建的 POD 会继承该路径的 cgroup memory 属性,也没有激活 cgroup kernel-memoy ,所以没有引发问题。
相反,在重启 k8s 1.9 node 后,kubelet 新建了 /sys/fs/cgroup/kubepods ,激活了 cgroup kernel-memoy ,导致后续的 POD 在删除时也有问题。

2)cgroup kernel memory 激活后可以关闭吗?
按 kernel 的说明,以及 kernel 代码,激活的方式,就是传递非 -1 的值(0 也是激活)来激活。而且最小单位是 4096 PageSize 的大小。
在激活 cgroup kernel memory 后,是不能关闭的,只能通过设置 -1 关闭限制,但还是会继续计数。

引用
After a group is first limited, it will be kept being accounted until it is removed. The memory limitation itself, can of course be removed by writing -1 to memory.kmem.limit_in_bytes. In this case, kmem will be accounted, but not limited.

另外,在 CentOS 7.3 kernel 中,激活 cgroup kernel memory 后,即使通过 -1 设置不限制使用,但还是会频繁出现 SLUB 的报警,以及 Java crash 的问题。

引用
Mar 26 18:36:59 ns-k8s-noah-s0054 kernel: SLUB: Unable to allocate memory on node -1 (gfp=0x8020)
Mar 26 18:36:59 ns-k8s-noah-s0054 kernel: cache: ip6_dst_cache(1995:6b6bc0c9f30123084a409d89a300b017d26ee5e2c3ac8a02c295c378f3dbfa5f), object size: 448, buffer size: 448, default order: 2, min order: 0

3)判断 cgroup kernel memory 是否激活的方式
查看对应 POD container 下的 memory.kmem.slabinfo。
关闭:

开启:

需要注意的是,如果给 cgroup memory.kmem.limit_in_bytes 设置 -1 ,其结果为:

引用
# cat memory.kmem.limit_in_bytes
9223372036854771712

这个值在关闭时和不限制 kernel memory 时是一样的,不能作为 kernel memory 是否激活的判断条件。

6.参考资料
/usr/share/doc/kernel-doc-3.10.0/Documentation/cgroups/memory.txt

在下面两个 issue 中讨论这个问题:
application crash due to k8s 1.9.x open the kernel memory accounting by default #61937

Docker leaking cgroups causing no space left on device? #29638

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/98241.html

(0)
上一篇 2021年8月20日
下一篇 2021年8月20日

相关推荐

发表回复

登录后才能评论