往期相关技术回顾
容器技术回顾 - 如何让我的容器/进程不要被 OOM Kill?
容器技术回顾 - Kubernetes memory limit 产生的 OOM
为啥 Pod 被驱逐了?浅谈 Kubernetes 驱逐机制
【转载】Kubernetes 基于 cgroup 的资源限额:模型设计与代码实现
现实问题
Kubernetes 集群的节点上,除了运行 Pod 之外,实际上还会运行很多操作系统内核进程以及由 Systemd 管理的进程(例如 auditd,docker,kubelet等)。
如下所示:
# ps -f -p 2UID PID PPID C STIME TTY TIME CMDroot 2 0 0 1月11 ? 00:00:01 [kthreadd]# ps -f -p 1UID PID PPID C STIME TTY TIME CMDroot 1 0 6 1月11 ? 16:12:39 /usr/lib/systemd/systemd --switched-root --system --deserialize 22
带 [] 的都是内核进程,它们的父进程是 2 号进程,而 systemd 是 1 号进程。
因此节点可被 Pod 使用的资源往往受到其它进程的制约:
如何让节点的可用资源更加确定呢?
如何不要让节点的其它进程占用过多的资源,尤其是内存?
如何预留一些资源给到系统关键进程呢(例如sshd,以确保在关键时候我们可以远程登录)?
在现实中,笔者遇到过很多软件的内存泄漏问题,包括 systemd,auditd 等,既然 cgroup 可以限制进程的内存使用,那 Kubernetes 有什么方案可以使用吗?
Kubernetes 提供了“为系统守护进程预留计算资源方案”(https://kubernetes.io/zh-cn/docs/tasks/administer-cluster/reserve-compute-resources/) ,它可以在预留的同时,也可以限制每个部分的资源使用。
资源预留
kubelet中有几个参数,通过这几个参数可以为系统进程预留资源,不至于pod把计算资源耗尽,而导致系统操作都无法正常进行。
--enforce-node-allocatable--system-reserved--system-reserved-cgroup--kube-reserved--kube-reserved-cgroup--eviction-har
节点可分配资源
在kubernetes 1.6版本后,引入了Node的Allocatable特性,通过该特性我们可以控制每个节点可分配的资源。
Capacity 是指 Node 的容量,allocatable 的值为
allocatable = capacity - kube_reserved - system_reserved - eviction_threshhold
当 kubelet 启动后,Node 的 allocatable 就是固定的,不会因为 pod 的创建与销毁而改变。
allocatable vs requests vs limits
在 pod 的 yaml 文件中,我们可以为 pod 设置 requests 与 limits。其中 limits 与 allocatable 没有什么关系。但 requests 与 allocatable 关系紧密。
调度到某个节点上的 Pod的requests总和不能超过该节点的allocatable。limits 的总和没有上限。
比如某个节点的内存的allocatable为10Gi,有三个Pod(requests.memory=3Gi)已经调度到该节点上,那么第 4 个 Pod 就无法调度到该节点上,即使该 Node 上的空闲内存大于 3Gi。
资源预留 - 不配置 cgroup
假设我们现在需要为系统预留一定的资源,那么我们可以配置如下的kubelet 参数(在这里我们不设置对应的cgroup参数):
--enforce-node-allocatable=pods--kube-reserved=memory=...--system-reserved=memory=...--eviction-hard=...
在上面提到,节点上 Pod 的 requests 总和不能超过 allocatable。
当我们设置了以上的四个参数时,节点上所有 Pod 实际使用的资源总和不会超过 capacity - kube_reserved - system_reserved
我们可以通过实验进行验证。
1、参数设置
kubelet 的启动参数如下:
/usr/bin/kubelet --address=0.0.0.0 --allow-privileged=true --cluster-dns=10.254.0.10 --cluster-domain=kube.local --fail-swap-on=true --hostname-override=192.168.1.101 --kubeconfig=/etc/kubernetes/kubeconfig --pod-infra-container-image=10.142.232.115:8021/library/pause:latest --port=10250 --enforce-node-allocatable=pods --kube-reserved=memory=1Gi --system-reserved=memory=1Gi --cgroup-driver=cgroupfs --eviction-hard=memory.available<100Mi
2、查看capacity及allocatable
查看到 Node 的 capacity 及 allocatable 的值如下:
Capacity: cpu: 2 memory: 4016436Ki (约3.83Gi) pods: 110Allocatable: cpu: 2 memory: 1816884Ki (约1.73Gi) pods: 110
我们可以计算出 allocatable 的值,刚好与上面的一致:
allocatale = capacity - kube_reserved - system_reserved - eviction_hard1816884Ki = 4016436Ki - 1*1024*1024Ki - 1*1024*1024Ki - 100*1024Ki
我们可以通过 free 命令来查看 Node 的 total 值,与 capacity 一致:
$ free -k total used free shared buff/cache availableMem: 4016436 1224372 2234872 17100 557192 2453156Swap: 0 0 0
3、查看 kubepods 控制组
查看 kubepods 控制组中对内存的限制,该值决定了 Node 上所有的 Pod 能使用的资源上限(这个很重要):
$ cat /sys/fs/cgroup/memory/kubepods/memory.limit_in_bytes 19653468161965346816 Bytes = 1919284Ki = allocatable + 100Mi
根据上面的计算可知,Node 上 Pod 能实际使用的资源上限值为:
kubepods/memory.limit_in_bytes = capacity - kube_reserved - system_reserved
注意:根据上面的公式,我们可以知道,一个节点上所有Pod能使用的内存总和,与 eviction-hard 无关
4、查看内存的空闲情况
查看内存的使用情况,发现空闲内存为 2.3Gi
$ free -h total used free shared buff/cache availableMem: 3.8G 1.2G 2.1G 16M 544M 2.3GSwap: 0B 0B 0B
5、创建pod
此时内存的空闲值为2.3Gi,allocatable为1.73Gi,kubepod.limit为1.83Gi。
我们创建一个Pod,pod.request为0.1Gi,pod.limit为20Gi,Pod实际消耗内存1Gi。理论上该Pod能创建成功,实际也成功了,如下:
$ kubectl get podNAME READY STATUS RESTARTS AGEcentos-659755bf78-jdlrc 1/1 Running 0 44s
查看 Node 的内存使用情况:
$ free -h total used free shared buff/cache availableMem: 3.8G 2.2G 1.1G 16M 546M 1.3GSwap: 0B 0B 0B
此时,空闲内存为1.3Gi,Node 剩余的 request 为1.63Gi,Node 的kubepods.limit 还剩 0.83Gi。
我们再创建一个同样的 Pod,根据推测,Pod 可以调度成功,但是由于要消耗 1Gi 的实际内存,超过了0.83Gi,那么该 Pod 会出现 OOM。实验结果也的确如此:
$ kubectl get podNAME READY STATUS RESTARTS AGEcentos-659755bf78-j8wjv 0/1 OOMKilled 0 5scentos-659755bf78-jdlrc 1/1 Running 1 1m
资源预留 - 设置对应的 cgroup
如果还设置了对应的 --system-reserved-cgroup 和 --kube-reserved-cgroup参数,Pod能实际使用的资源上限不会改变(即kubepods.limit_in_bytes不变),但系统进程与kube进程也会受到资源上限的限制。如果系统进程超过了预留资源,那么系统进程会被cgroup杀掉。
但是如果不设这两个参数,那么系统进程可以使用超过预留的资源上限。
注意:systemd 本身不受这个约束,如下所示:
cat /proc/1/cgroup11:blkio:/10:cpuset:/9:perf_event:/8:freezer:/7:pids:/6:cpuacct,cpu:/5:net_prio,net_cls:/4:hugetlb:/3:memory:/2:devices:/1:name=systemd:/ls /sys/fs/cgroup/memory/cgroup.clone_children kubepods memory.kmem.limit_in_bytes memory.kmem.tcp.max_usage_in_bytes memory.memsw.failcnt memory.numa_stat memory.swappiness system.slicecgroup.event_control kube.slice memory.kmem.max_usage_in_bytes memory.kmem.tcp.usage_in_bytes memory.memsw.limit_in_bytes memory.oom_control memory.usage_in_bytes taskscgroup.procs memory.failcnt memory.kmem.slabinfo memory.kmem.usage_in_bytes memory.memsw.max_usage_in_bytes memory.pressure_level memory.use_hierarchy user.slicecgroup.sane_behavior memory.force_empty memory.kmem.tcp.failcnt memory.limit_in_bytes memory.memsw.usage_in_bytes memory.soft_limit_in_bytes notify_on_releasedocker memory.kmem.failcnt memory.kmem.tcp.limit_in_bytes memory.max_usage_in_bytes memory.move_charge_at_immigrate memory.stat release_agentcat /sys/fs/cgroup/memory/memory.limit_in_bytes9223372036854771712
kube.slice 为例:
ls /sys/fs/cgroup/memory/kube.slice/cgroup.clone_children memory.force_empty memory.kmem.tcp.failcnt memory.limit_in_bytes memory.memsw.usage_in_bytes memory.soft_limit_in_bytes notify_on_releasecgroup.event_control memory.kmem.failcnt memory.kmem.tcp.limit_in_bytes memory.max_usage_in_bytes memory.move_charge_at_immigrate memory.stat runtimecgroup.procs memory.kmem.limit_in_bytes memory.kmem.tcp.max_usage_in_bytes memory.memsw.failcnt memory.numa_stat memory.swappiness taskskubelet memory.kmem.max_usage_in_bytes memory.kmem.tcp.usage_in_bytes memory.memsw.limit_in_bytes memory.oom_control memory.usage_in_bytesmemory.failcnt memory.kmem.slabinfo memory.kmem.usage_in_bytes memory.memsw.max_usage_in_bytes memory.pressure_level memory.use_hierarchy
cat /sys/fs/cgroup/memory/kube.slice/memory.limit_in_bytes8000000000
配置建议
参考配置:
enforceNodeAllocatable:- pods- system-reserved- kube-reservedsystemReservedCgroup: /system.slicekubeReservedCgroup: /kube.slicekubeletCgroups: /kube.slice/kubeletsystemCgroups: /system.slicecgroupRoot: /systemReserved: cpu: 1500m memory: 8GkubeReserved: cpu: 1500m memory: 8GevictionHard: imagefs.available: 5% memory.available: 4000Mi nodefs.available: 5% nodefs.inodesFree: 5%
为了避免资源浪费,我们需要结合可观测平台来优化上述参数的配置。
总结
Node 的 allocatable 在 kubelet 启动后是一个固定的值,不会因为pod的创建与删除而改变;
当我们为 Pod 设置了 resources.requests 时,调度到 Node 上的 Pod 的 resources.requests 的总和不会超过Node的allocatable。但Pod的resources.limits 总和可以超过 Node 的 allocatable;
一个 Pod 能否成功调度到某个Node,关键要看该Pod的resources.request 是否小于 Node 剩下的 request,而不是看 Node 实际的资源空闲量。即使空闲资源小于 Pod 的 requests,Pod 也可以调度到该Node上;
当 Pod 的资源实际使用量超过其 limits 时,cgroup 会把该 Pod 内超出限额的进程杀掉(如果是容器内的 1 号进程被杀掉了,则容器就会退出);
当我们只设置如下四个参数时,可以达到为系统预留资源的效果,即Pod 的资源实际使用量不会超过 allocatable 的值(因为 kubepods 控制组中 memory.limit_in_bytes 的值就为 allocatable 的值)。即使系统本身没有使用完预留的那部分资源,Pod 也无法使用。当系统超出了预留的那部分资源时,系统进程可以抢占 allocatable 中的资源,即对系统使用的资源没有限制;
--enforce-node-allocatable=pods--kube-reserved=memory=...--system-reserved=memory=...--eviction-hard=...
当我们除了设置了以上四个参数,还设置了对应的 cgroup 时(如下),那么除了 Pod 使用的资源上限不会超过 allocatable 外,系统使用的资源上限也不会超过预留资源。当系统进程超过预留资源时,系统进程也会被 cgroup杀掉(推荐使用);
--enforce-node-allocatable=pods,kube-reserved,system-reserved--kube-reserved=memory=...--kube-reserved-cgroup=...--system-reserved=memory=...--system-reserved-cgroup=...--eviction-hard=...
allocatable与kubepods.limit的值不一样,它们之间相差一个 eviction_hard;
allocatable = capacity - kube_reserved - system_reserved - eviction_hardkubepods.limit = capacity - kube_reserved - system_reserved
参考
https://kubernetes.io/zh-cn/docs/tasks/administer-cluster/reserve-compute-resources/
https://pshizhsysu.gitbook.io/kubernetes/zi-yuan-guan-kong
欢迎加群