一, 现象:
当 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
λ
从 https://github.com/opencontainers/runc 克隆 runc 源代码最新版.
λ
从根目录下的 dockerfile
创建一个 docker 镜像 (更改后的 dockerfile 见下文).
λ 启动并进入此容器, 运行生成 runc.
make BUILDTAGS="seccomp nokmem"
. 将 runc 更名为 docker-runc, 拷贝出容器, 留待备用.
2, 升级 kubelet
λ 从 https://github.com/kubernetes/kubernetes 克隆 k8s 的指定版本.
λ
运行如下命令, 将 k8s 的项目挂载到容器中 (此镜像的 dockerfile 见下文).
docker run -it --rm -v /Users/xxx/Downloads/kubernetes-1.14.1:/usr/local/gopath/src/k8s.io/kubernetes harbor.xxx.cn/build-k8s:CentOS-7.6-go-1.12.9-k8s-1.14.6 bash
λ 进入容器, 运行如下命令, 在当前目录的_output/bin / 下生成 kubelet 文件.
λ 将 kubelet, 拷出容器, 留待备用.
KUBE_BUILD_PLATFORMS=Linux/runrunamd64 make all WHAT=cmd/kubelet GOGCFLAGS="-N -l" GOFLAGS="-tags=nokmem"
四, 操作步骤
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
附一 RUNC 的 dockerfile
- FROM golang:1.12-stretch
- RUN sed -i "[email protected]://[email protected]://[email protected]" /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
附二编译 k8s 的 dockerfile
- FROM CentOS:centos7.6.1811
- MAINTAINER 4k
- 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
来源: http://www.bubuko.com/infodetail-3456572.html