从limit和request来理解k8s的资源调配

背景交代

今天用阿里云的k8s做实验,在worker(2核4G)上执行这么一句话:

1
kubectl run chengx --image=registry.cn-hangzhou.aliyuncs.com/lechangetest/chentest:chengx --port=80 --replicas=5 --limits="cpu=200m,memory=512Mi"

发现命令执行之后,只剩成了4个pod,一个卡在Pending的状态,如图:
akb48

使用kubectl describe pod/chengx-5bb8bcb9c9-tlgz4查看为什么会失败,看到理由是0/4 nodes are available: 1 Insufficient memory, 3 node(s) had taints that the pod didn't tolerate.,如图:
akb48

错误直译过来就是“4个node里已经没有可用的,现在内存爆缸了,其中三个node都因为有污点同时这个pid无法容忍这个污点”。

limit和request的不同

我上面的命令里面用到了limit,所以先研究一下limit和request这俩参数,先说request:

1
2
3
容器使用的最小资源需求, 作为容器调度时资源分配的判断依赖。
只有当前节点上可分配的资源量 >= request 时才允许将容器调度到该节点。
request参数不限制容器的最大可使用资源

再说limit:

1
2
容器能使用资源的最大值
设置为0表示对使用的资源不做限制, 可无限的使用

request和limit的关系:

1
2
3
4
request能保证pod有足够的资源来运行, 而limit则是防止某个pod无限制的使用资源, 导致其他pod崩溃. 两者的关系必须满足:
0 <= request <= limit <= Infinity
复制代码如果limit=0表示不对资源进行限制, 这时可以小于request。
目前CPU支持设置request和limit,memory只支持设置request, limit必须强制等于request, 这样确保容器不会因为内存的使用量超过request但是没有超过limit的情况下被意外kill掉。

举个例子,在一个2核4G的node里,运行一个(CPU Requst,CPU Limit,Memory Requst, Memory Limit)= (1U, 1U, 2G,2G)的POD是完全OK的,这个POD不一定一定要用满2G,它可以用到0.1G或者1.99G,只要是内存在2G以内,这个POD都是不受影响的。

如果这个时候,又来了一个POD,他的资源参数为(CPU Requst,CPU Limit,Memory Requst, Memory Limit)= (1U, 1U, 1G,2G),那么这个POD2的内存在2G以内的情况下,POD1和POD2都是OK的。如果POD2的超过了2G,那么POD2会挂掉,而POD1安全无事。

若namespace里事前设定了CPU和内存的request和limit,那么在生成pod的时候,若无特殊说明,pod的request和limit值与所处的namespace相同。如果pod说明了request没说明limit,那么pod的limit等于声明的request。如果pod说明了request没有说明limit,那么limit值等于namespace默认的limit。

注意!namespace的limit值是可以比实际pod的limit值小的,如图:
akb48

可见这个叫default-mem-example的namespace默认的request是256Mi,limit是512Mi,而我是可以在这个namespace里创建一个request是1G的pod,如图:
akb48

额外补充一下,k8s里的计量单位:1Mi=1024x1024,1M=1000x1000,其它单位类推,如Ki/K、Gi/G。

重新说回来

再次说回0/4 nodes are available: 1 Insufficient memory, 3 node(s) had taints that the pod didn't tolerate.,从这句话里我们看到虽然这个k8s集群有4个node(3个master+1worker),使用kubectl describe node master节点名称来查看master上是否存在默认的taint:
akb48

再看一下worker节点的taint:
akb48

在master上默认是不会将Pod调度到具有该污点的Node上,也就是说所有pod都是在worker这个节点上的。worker上只有4G,而我生成了5个limit=512Mi的pod,需要2.5G的内存空间。然而worker这个pod现在有多少可用的内存呢?kubectl describe node worker名称可见剩余的memory已经不足,如图:

1
2
3
4
5
6
7
Allocated resources:
(Total limits may be over 100 percent, i.e., overcommitted.)
Resource Requests Limits
-------- -------- ------
cpu 200m (10%) 0 (0%)
memory 1736Mi (62%) 2248Mi (80%)
Events: <none>

现在剩余的内存值仅有20%,所以由于内存不够而生成失败,需要在kubectl run里适当调小内存的limit值,或者干脆扩容一个worker,让它在另一个worker里出现。

如果想要查看某个pod是具体落在哪个node里,使用命令:kubectl get pods -o wide即可。

关于Docker的资源调配

docker默认情况对cpu和内存都是无限制的,如果cpu跑爆了,那么容器也不会死掉,而是慢慢的跑。但是如果内存跑爆了,容器会被oom,不过可以通过设置oom的值让容器被干掉的几率低一点。

如果在一个四核的服务器里通过docker run -it --rm --cpus=2 镜像:latest /bin/bash启动了一个容器,那么这个进程跑到200%就到顶了,而且这200%是平均分配到4个核上,每个核50%,而不是把1和2核跑100%,剩下3和4是空着的。

如果想要把某一个CPU跑满,命令是docker run -it --rm --cpuset-cpus="1,3" 镜像名:latest /bin/bash,这样的话就会只在第一个和第三个CPU上跑,而不会动用其他的CPU。

前面说的是CPU,现在说内存。如果是想要限制某个docker的内存最大是300M:docker run -it -m 300M --memory-swap -1 --name con1 镜像名 /bin/bash,设置 memory-swap 值为 -1,它表示容器程序使用内存的受限,而可以使用的 swap 空间使用不受限制(宿主机有多少 swap 容器就可以使用多少)。

如果这个容器大于300M了,那它就直接被OOM kill了。如果有足够的 swap,程序至少还可以正常的运行。当然,我们也可以通过--oom-kill-disable选项强行阻止OOM kill的发生。限制内存上限虽然能保护主机,但是也可能会伤害到容器里的服务:如果为服务设置的内存上限太小,会导致服务还在正常工作的时候就被 OOM 杀死;如果设置的过大,会因为调度器算法浪费内存。所以内存压力测试是不可避免的,而且也尽量不要使用swap,因为swap的使用会导致内存计算复杂,对调度器非常不友好。
akb48

参考资料

https://www.qikqiak.com/post/understand-kubernetes-affinity/
https://blog.frognew.com/2018/05/taint-and-toleration.html
https://jimmysong.io/kubernetes-handbook/concepts/taint-and-toleration.html
https://stackoverflow.com/questions/53192999/pod-dont-run-insufficient-resources
http://dockone.io/article/2509
https://www.yangcs.net/posts/understanding-resource-limits-in-kubernetes-cpu-time/
http://blog.whysdomain.com/blog/171/
https://www.cnblogs.com/sparkdev/p/8052522.html

感谢您请我喝咖啡~O(∩_∩)O,如果要联系请直接发我邮箱chenx1242@163.com,我会回复你的
-------------本文结束感谢您的阅读-------------