解决k8s集群的cgroup泄漏问题

在一些老版的linux和k8s及docker上,有时会出现。这里详细记录一下就地解决办法,毕竟,一个上生产的k8s集群,不是说升级就升级的。

一,现象

当k8s集群运行日久以后,有的node无法再调试新的pod应用,并且出现如下错误,当重启服务器之后,才可以恢复正常使用。

applying cgroup … caused: mkdir …no space left on device

二,原因:

memcg 是 Linux 内核中用于管理 cgroup 内存的模块,整个生命周期应该是跟随 cgroup 的,但是在低版本内核中(已知3.10),一旦给某个 memory cgroup 开启 kmem accounting 中的 memory.kmem.limit_in_bytes 就可能会导致不能彻底删除 memcg 和对应的 cssid,也就是说应用即使已经删除了 cgroup (/sys/fs/cgroup/memory 下对应的 cgroup 目录已经删除), 但在内核中没有释放 cssid,导致内核认为的 cgroup 的数量实际数量不一致,我们也无法得知内核认为的 cgroup 数量是多少。
这个问题可能会导致创建容器失败,因为创建容器为其需要创建 cgroup 来做隔离,而低版本内核有个限制:允许创建的 cgroup 最大数量写死为 65535,如果节点上经常创建和销毁大量容器导致创建很多 cgroup,删除容器但没有彻底删除 cgroup 造成泄露(真实数量我们无法得知),到达 65535 后再创建容器就会报创建 cgroup 失败并报错 no space left on device,使用 kubernetes 最直观的感受就是 pod 创建之后无法启动成功。

三,解决方案:

升级内核暂不可行,动静太大,所以这次先选用为runc及kubelet增加编译参数的方案,希望能解决。

1,升级runc

1.1 从https://github.com/opencontainers/runc克隆runc源代码最新版。

1.2 从根目录下的dockerfile创建一个docker镜像(更改后的dockerfile最后)。

1.3 启动并进入此容器,运行生成runc。
make BUILDTAGS="seccomp nokmem"

1.4 将runc更改为docker-runc,拷出容器,留待备用。

2,升级kubelet

2.1 从https://github.com/kubernetes/kubernetes克隆k8s的指定版本

2.2 运行如下命令,将k8s的项目挂载到容器中(此镜像的dockerfile见下文)。

docker run  -it --rm \
  -v /Downloads/kubernetes-1.14.1:\
  /usr/local/gopath/src/k8s.io/kubernetes \
  harbor.io/build-k8s:centos-7.6-go-1.12.9-k8s-1.14.6 \
  bash

2.3 进入容器,运行如下命令,在当前目录的_output/bin/下生成kubelet文件。

KUBE_BUILD_PLATFORMS=linux/amd64 make all \
WHAT=cmd/kubelet GOGCFLAGS="-N -l" GOFLAGS="-tags=nokmem"

2.4 将kubelet,拷出容器,留待备用。

四,操作步骤

1 先将出现此问题的节点驱逐出集群(这一操作可以让集群的服务不受影响)。
kubectl cordon k8s-node-xxx
2 使用如下命令先后停止kubelet和start服务。

systemctl stop kubelet
systemctl stop docker

3 将生成的docker-runc文件和kubelet文件替换掉(事先备份这两个文件)。请确认这两个文件执行可执行权限。

Docker-runc文件位置:/usr/bin/
Kubelet文件位置:/usr/bin/; /usr/local/bin/

4 重启服务器,验证docker进程和kubelet进程运行正常。
5 将此节点拉回集群。
kubectl uncordon k8s-node-xxx

五 附录
1 RUNC的dockerfile

FROM golang:1.12-stretch

RUN sed -i "s@http://deb.debian.org@http://mirrors.163.com@g" /etc/apt/sources.list \
    && apt-get update && apt-get install -y \
    build-essential \
    curl \
    sudo \
    gawk \
    iptables \
    jq \
    pkg-config \
    libaio-dev \
    libcap-dev \
    libprotobuf-dev \
    libprotobuf-c0-dev \
    libnl-3-dev \
    libnet-dev \
    libseccomp2 \
    libseccomp-dev \
    protobuf-c-compiler \
    protobuf-compiler \
    python-minimal \
    uidmap \
    kmod \
    --no-install-recommends \
    && apt-get clean

# Add a dummy user for the rootless integration tests. While runC does
# not require an entry in /etc/passwd to operate, one of the tests uses
# `git clone` -- and `git clone` does not allow you to clone a
# repository if the current uid does not have an entry in /etc/passwd.
RUN useradd -u1000 -m -d/home/rootless -s/bin/bash rootless

# install bats
RUN cd /tmp \
    && git clone https://github.com/sstephenson/bats.git \
    && cd bats \
    && git reset --hard 03608115df2071fff4eaaff1605768c275e5f81f \
    && ./install.sh /usr/local \
    && rm -rf /tmp/bats

# install criu
ENV CRIU_VERSION v3.12
RUN mkdir -p /usr/src/criu \
    && curl -sSL https://github.com/checkpoint-restore/criu/archive/${CRIU_VERSION}.tar.gz | tar -v -C /usr/src/criu/ -xz --strip-components=1 \
    && cd /usr/src/criu \
    && make install-criu \
    && rm -rf /usr/src/criu

# setup a playground for us to spawn containers in
ENV ROOTFS /busybox
RUN mkdir -p ${ROOTFS}

COPY script/tmpmount /
WORKDIR /go/src/github.com/opencontainers/runc
ENTRYPOINT ["/tmpmount"]

ADD . /go/src/github.com/opencontainers/runc

2 编译k8s的dockerfile

FROM centos:centos7.6.1811

MAINTAINER cg

ENV GOROOT /usr/local/go
ENV GOPATH /usr/local/gopath
ENV PATH /usr/local/go/bin:$PATH

RUN yum install rpm-build which where rsync gcc gcc-c++ automake autoconf libtool make -y \
    && curl -L https://studygolang.com/dl/golang/go1.12.9.linux-amd64.tar.gz | tar zxvf - -C /usr/local

你可能感兴趣的:(解决k8s集群的cgroup泄漏问题)