一行 kubenetes 1.9 代码引起的血案(与 CentOS 7.x 内核兼容性问题)

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

典型报错:node

kubelet.ns-k8s-node001.root.log.ERROR.20180214-113740.15702:1593018:E0320linux

04:59:09.572336 15702 remote_runtime.go:92] RunPodSandbox from runtimegit

service failed: rpc error: code = Unknown desc = failed to start sagithub

ndbox container for pod “osp-xxx-com-ljqm19-54bf7678b8-bvz9s”: Errordocker

response from daemon: oci runtime error: container_linux.go:247:json

starting container process caused “process_linux.go:258: applyingvim

cgroup configuration for process caused “mkdirapp

/sys/fs/cgroup/memory/kubepods/burstable/podf1bd9e87-1ef2-11e8-afd3-fa163ecf2dce/8710c146b3c8b52f5da62e222273703b1e3d54a6a6270a0ea7ce1b194f1b5053:less

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

uname -r

3.10.0-514.10.2.el7.x86_64

kubelet –version Kubernetes 1.9.0

mkdir /sys/fs/cgroup/memory/test

for i in seq 1 65535 ;do mkdir /sys/fs/cgroup/memory/test/test-${i}; done

cat /proc/cgroups |grep memory memory 11 65535 1

把系统 cgroup memory 填到 65535 个。

2)腾空 99 个 cgroup memory

# for i in `seq 1 100`;do rmdir /sys/fs/cgroup/memory/test/test-${i} 2>/dev/null 1>&2; done

mkdir /sys/fs/cgroup/memory/stress/

for i in seq 1 100 ;do mkdir /sys/fs/cgroup/memory/test/test-${i}; done

mkdir: cannot create directory ‘/sys/fs/cgroup/memory/test/test-100’: No space left on device

for i in seq 1 100 ;do rmdir /sys/fs/cgroup/memory/test/test-${i}; done

cat /proc/cgroups |grep memory

memory 11 65436 1

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

3)建立一个 pod 到这个 node 上,查看占用的 cgroup memory 状况

每建立一个 pod ,会占用 3 个 cgroup memory 目录:

# ll /sys/fs/cgroup/memory/kubepods/pod0f6c3c27-3186-11e8-afd3-fa163ecf2dce/  

total 0  

drwxr-xr-x 2 root root 0 Mar 27 14:14 6d1af9898c7f8d58066d0edb52e4d548d5a27e3c0d138775e9a3ddfa2b16ac2b  

drwxr-xr-x 2 root root 0 Mar 27 14:14 8a65cb234767a02e130c162e8d5f4a0a92e345bfef6b4b664b39e7d035c63d1

这时再次建立 100 个 cgroup memory ,由于 pod 占用了 3 个,会出现 4 个没法成功:

# for i in `seq 1 100`;do mkdir /sys/fs/cgroup/memory/test/test-${i}; done      

mkdir: cannot create directory ‘/sys/fs/cgroup/memory/test/test-97’: No space left on device <-- 3 directory used by pod  

mkdir: cannot create directory ‘/sys/fs/cgroup/memory/test/test-98’: No space left on device  

mkdir: cannot create directory ‘/sys/fs/cgroup/memory/test/test-99’: No space left on device  

mkdir: cannot create directory ‘/sys/fs/cgroup/memory/test/test-100’: No space left on device

cat /proc/cgroups

memory 11 65439 1

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

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

看到的结果:

# cat /proc/cgroups   

memory  11      65436   1

for i in seq 1 100 ;do mkdir /sys/fs/cgroup/memory/test/test-${i}; done

mkdir: cannot create directory ‘/sys/fs/cgroup/memory/test/test-97’: No space left on device

mkdir: cannot create directory ‘/sys/fs/cgroup/memory/test/test-98’: No space left on device

mkdir: cannot create directory ‘/sys/fs/cgroup/memory/test/test-99’: No space left on device

mkdir: cannot create directory ‘/sys/fs/cgroup/memory/test/test-100’: No space left on device

能够看到,虽然 cgroup memory 减小到 65536 ,彷佛 3 个位置释放了。但实际上测试结果发现,并不能写入,结果仍是 pod 占用时的没法写入 97-100 。

这就说明,cgroup memory 数量减小,但被 pod container 占用的空间没有释放。

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

$ cat /proc/cgroups

subsys_name hierarchy num_cgroups enabled

cpuset 10 229418 1 — 但 cpuset 数量很恐怖

cpu 6 118 1

cpuacct 6 118 1

memory 7 109 1 — 看上去很少,实际没有释放空间的

devices 3 229504 1

freezer 5 32 1

net_cls 4 32 1

blkio 9 118 1

perf_event 11 32 1

hugetlb 2 32 1

pids 8 32 1

net_prio 4 32 1

3.问题根源

通过大量的测试和对比分析,在两个 k8s 1.9 环境中 kubelet 建立的 /sys/fs/cgroup/memory/kubepods 差别,发现:

没问题的:

[root@k8s-node01 kubepods]# cat memory.kmem.slabinfo cat:

memory.kmem.slabinfo: Input/output error

有问题的:

[root@k8s-node01 kubepods]# cat memory.kmem.slabinfo slabinfo –

version: 2.1

name : tunables : slabdata

 

也就是说,在有问题的环境下,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 设置的差别。

 

$ git diff remotes/origin/vip_v1.6.4.3 remotes/origin/vip_v1.9.0 -- vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/memory.go  

@@ -29,14 +35,18 @@ func (s *MemoryGroup) Apply(d *cgroupData) (err error) {  

    path, err := d.path("memory")  

    if err != nil && !cgroups.IsNotFound(err) {  

            return err  

+       } else if path == "" {  

+               return nil  

    }  

    if memoryAssigned(d.config) {  

-               if path != "" {  

+               if _, err := os.Stat(path); os.IsNotExist(err) {  

                    if err := os.MkdirAll(path, 0755); err != nil {  

                            return err  

                    }  

-               }  

-               if d.config.KernelMemory != 0 { // 删除了这行的判断,使得 1.9 默认就 enable cgroup kernel memory 特性  

+                       // Only enable kernel memory accouting when this cgroup  

+                       // is created by libcontainer, otherwise we might get  

+                       // error when people use `cgroupsPath` to join an existed  

+                       // cgroup whose kernel memory is not initialized.  

                    if err := EnableKernelMemoryAccounting(path); err != nil {  

                            return err  

                    }

这就引起了 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在这个版本是不稳定的。所以让咱们踩了这个坑。

经验证,CentOS 7.4(3.10.0-693.11.1.el7)存在一样的问题。

4.寻根问底

附上同事对该问题在 kernel 中追踪的结果。

memcg是Linux内核中用于管理cgroup中kernel 内存的模块,整个生命周期应该是跟随cgroup的,可是当在3.10.0-514.10.2.el7版本中,一旦开启了kmem_limit就可能会致使不能完全删除memcg和对应的cssid。

在建立过程当中,若是咱们设定了kmem_limit就会激活memcg

memcg和cssid的释放的调用链很长,其中在这个过程当中,mem_cgroup_css_free的过程当中会调用kmem_cgroup_destroy

而kmem_cgroup_destroy函数会检查memcg是否还占用memory,若是发现还占用,就不会调用mem_cgroup_put函数:

而mem_cgroup_put函数会将memcg的refcnt减去1,并查看是否等于0,若是为0即表示没有其余东西引用memcg,memcg能够放心释放

惋惜当开启了kmem_limit后,这个refcnt不会等于0,致使永远没法调用到free_rcu去释放cssid。

5.解决问题

通过以上分析,形成该故障的缘由,是因为 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 配置,保持关闭状态。

6.疑问

为何一样是 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 在删除时也有问题。

详情可进一步参考: kubernetes 1.9 与 CentOS 7.3 内核兼容问题

相关文章
相关标签/搜索