关于Kubernetes中资源限制的一些笔记整理

我们的痛苦来源于“夸父追日”一般的对“更好”的追求,也来自于自己的自卑与狂妄。——–duoduokk

写在前面


  • 今天和小伙伴们分享K8s中pod资源限制的一些笔记
  • 博文内存涉及pod中通过request和limits实现资源的申请和限制
  • 理解不足小伙伴帮忙指正,生活加油 ^_^

我们的痛苦来源于“夸父追日”一般的对“更好”的追求,也来自于自己的自卑与狂妄。——–duoduokk


学习之前,我们要准备下实验环境,新建一个新的命名空间,并且切换过去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$mkdir resources
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$cd resources/
┌──[root@vms81.liruilongs.github.io]-[~/ansible/resources]
└─$kubectl create ns resources
namespace/resources created
┌──[root@vms81.liruilongs.github.io]-[~/ansible/resources]
└─$kubectl config set-context $(kubectl config current-context) --namespace=resources
Context "kubernetes-admin@kubernetes" modified.
┌──[root@vms81.liruilongs.github.io]-[~/ansible/resources]
└─$cd ..
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$ansible node -m shell -a "docker pull hub.c.163.com/library/centos"

为什么要资源限制

一般在本地做实验,或者Demo的时候,不会对pod的资源进行限制,只有在生产环境会严格配置pod的资源限制。

当不对pod的资源做限制时,K8s的调度器会认为当前pod所需要的资源很少,并且可以调度到任意节点上,但是这样有一个很严重的弊端,如果不做资源限制,比如拿内存来讲,如果pod中的容器存在内存不回收的情况,那么会无休止的使用宿主节点的内存资源,从而会引发宿主机的OOM,甚至会触发宿主机内核OOM Killer(内存杀手),来保证宿主机不挂掉。

看一个Demo,创建一个没有限制资源的pod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌──[root@vms81.liruilongs.github.io]-[~/ansible/resources]
└─$cat pod-demo.yaml
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: pod-demo
name: pod-demo
spec:
containers:
- image: hub.c.163.com/library/centos
imagePullPolicy: IfNotPresent
name: pod-demo
command: ['sh','-c','sleep 500000']
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}

pod创建成功后,我们可以看到调度到了vms83.liruilongs.github.io

1
2
3
4
5
6
7
8
9
10
11
12
┌──[root@vms81.liruilongs.github.io]-[~/ansible/resources]
└─$kubectl get pods -o wide
No resources found in resources namespace.
┌──[root@vms81.liruilongs.github.io]-[~/ansible/resources]
└─$vim pod-demo.yaml
┌──[root@vms81.liruilongs.github.io]-[~/ansible/resources]
└─$kubectl apply -f pod-demo.yaml
pod/pod-demo created
┌──[root@vms81.liruilongs.github.io]-[~/ansible/resources]
└─$kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-demo 1/1 Running 0 42s 10.244.70.50 vms83.liruilongs.github.io <none> <none>

这里我们在pod里安装一个内存分配工具bigmem,用于模拟pod中容器进程内存不回收的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$kubectl cp ./bigmem-7.0-1.r29766.x86_64.rpm pod-demo:/root/
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$kubectl exec -it pod-demo -- bin/bash
[root@pod-demo /]# cd root/
[root@pod-demo ~]# ls
anaconda-ks.cfg bigmem-7.0-1.r29766.x86_64.rpm original-ks.cfg
[root@pod-demo ~]# rpm -ivh bigmem-7.0-1.r29766.x86_64.rpm
Preparing... ################################# [100%]
Updating / installing...
1:bigmem-7.0-1.r29766 ################################# [100%]
[root@pod-demo ~]# bigmem 1000M
Attempting to allocate 1000 Mebibytes of resident memory...
Press <Enter> to exit^C
[root@pod-demo ~]# bigmem 2000M
Attempting to allocate 2000 Mebibytes of resident memory...
Killed

通过上下内存信息可以发现,当分配1000M内存时,宿主机用户使用内存增加了1000M,可用内存为117M,当申请内存为2000M时,超出宿主机可用内存, bigmem 2000M命令所在进程直接被kill了。

1
2
3
4
5
6
7
8
9
10
11
12
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$ansible vms83.liruilongs.github.io -m shell -a "free -h"
vms83.liruilongs.github.io | CHANGED | rc=0 >>
total used free shared buff/cache available
Mem: 4.4G 2.5G 583M 216M 1.4G 1.4G
Swap: 0B 0B 0B
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$ansible vms83.liruilongs.github.io -m shell -a "free -h"
vms83.liruilongs.github.io | CHANGED | rc=0 >>
total used free shared buff/cache available
Mem: 4.4G 3.5G 117M 216M 857M 456M
Swap: 0B 0B 0B

查看宿主机日志 /var/log/messages,可以发现bigmem 所在进程造成OOM。被OOM killer 杀掉了。

1
2
3
4
┌──[root@vms83.liruilongs.github.io]-[~]
└─$cat /var/log/messages | grep -i memory
Aug 10 20:37:27 vms83 kernel: [<ffffffff81186bd6>] out_of_memory+0x4b6/0x4f0
Aug 10 20:37:27 vms83 kernel: Out of memory: Kill process 25143 (bigmem) score 1347 or sacrifice child

如果不对pod做资源限制,他会认为宿主机的资源全是自己的,会无休止的使用,直到宿主机内存不足被OOM killer 直接杀了,为什么会被宿主机杀掉,容器的本质即运行在宿主机上的一个进程组。

通过 top 命令监控 node资源,可以发现由于 OOM的问题, 可能会造成的节点短暂性死机,无法采集同步节点信息。

1
2
3
4
5
6
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$kubectl top nodes
NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
vms81.liruilongs.github.io 189m 9% 1816Mi 58%
vms82.liruilongs.github.io 112m 3% 819Mi 17%
vms83.liruilongs.github.io <unknown> <unknown> <unknown> <unknown>

必须对资源进行限制,虽然上面的OOM情况下,杀掉了bigmem进程,但是实际上 OOM Killer 杀进程是不确定的,确定OOM杀手应该杀死哪个进程,内核会为每个进程保持一个运行不良评分,分数越高,进程越有可能被OOM杀手杀死。许多因素被用来计算这个分数。

所以当前节点上的任何一个Pod 进程都有可以能被杀掉。但有些Pod可能担负着很重要的职责,比其他Pod更重要,比如与数据存储相关的、与登录相关的、与查询余额相关的,即使系统资源严重不足,也需要保障这些Pod的存活。

所以Kubernetes中该提供了一些保障机制:

  1. 通过资源限额来确保不同的Pod只能占用指定的资源
  2. 允许集群的资源被超额分配,以提高集群的资源利用率。
  3. 为Pod划分等级,确保不同等级的Pod有不同的服务质量(QoS),资源不足时,低等级的Pod会被清理,以确保高等级的Pod稳定运行。

今天和小伙伴分享的主要第一种方式

Kubernetes集群里的节点提供的资源主要是计算资源,计算资源是可计量的能被申请、分配和使用的基础资源,这使之区别于API资源(API Resources,例如Pod和Services等)。当前Kubernetes集群中的计算资源主要包括CPU、GPU及Memory,绝大多数常规应用是用不到GPU的,因此这里重点介绍CPU与Memory的资源管理问题。

我们知道,一个程序所使用的CPU与Memory是一个动态的量,确切地说,是一个范围,跟它的负载密切相关:负载增加时,CPU和Memory的使用量也会增加。因此最准确的说法是,某个进程的CPU使用量为0.1个CPU~1个CPU,内存占用则为500MB~1GB。对应到Kubernetes的Pod容器上,就是下面这4个参数:

  • spec.container[].resources.requests.cpu
  • spec.container[].resources.limits.cpu
  • spec.container[].resources.requests.memory
  • spec.container[].resources.limits.memory

Request&&limits

在配置Pod时可以通过参数Request为其中的每个容器指定所需使用的CPU与Memory量,类似于一种申请,Kubernetes会根据Request的值去查找有足够资源的Node来调度此Pod,如果没有,则调度失败。pod会一直处于pending

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
┌──[root@vms81.liruilongs.github.io]-[~/ansible/resources]
└─$cat pod-demo-momory.yaml
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: pod-demo
name: pod-demo
spec:
containers:
- image: hub.c.163.com/library/centos
imagePullPolicy: IfNotPresent
name: pod-demo
command: ['sh','-c','sleep 500000']
resources:
requests:
memory: "5000Mi"
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}

当前pod所调度的node只有4.4G内存,所以pod会一直处于pending

1
2
3
4
5
6
7
8
9
┌──[root@vms81.liruilongs.github.io]-[~/ansible/resources]
└─$kubectl apply -f pod-demo-momory.yaml
pod/pod-demo created
┌──[root@vms81.liruilongs.github.io]-[~/ansible/resources]
└─$kubectl get pods
NAME READY STATUS RESTARTS AGE
pod-demo 0/1 Pending 0 6s
┌──[root@vms81.liruilongs.github.io]-[~/ansible/resources]
└─$

limits对应资源量的上限,即最多允许使用这个上限的资源量。

由于CPU资源是可压缩的,进程无论如何也不可能突破上限,因此设置起来比较容易。对于Memory这种不可压缩资源来说,它的Limit设置就是一个问题了,如果设置得小了,当进程在业务繁忙期试图请求超过Limit限制的Memory时,此进程就会被Kubernetes杀掉。因此,Memory的Request与Limit的值需要结合进程的实际需求谨慎设置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
┌──[root@vms81.liruilongs.github.io]-[~/ansible/resources]
└─$cat pod-demo-momory.yaml
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: pod-demo
name: pod-demo
spec:
containers:
- image: hub.c.163.com/library/centos
imagePullPolicy: IfNotPresent
name: pod-demo
command: ['sh','-c','sleep 500000']
resources:
requests:
memory: "2000Mi"
limits:
memory: "3000Mi"
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
1
2
3
4
5
6
7
8
9
10
11
┌──[root@vms81.liruilongs.github.io]-[~/ansible/resources]
└─$vim pod-demo-momory.yaml
┌──[root@vms81.liruilongs.github.io]-[~/ansible/resources]
└─$kubectl apply -f pod-demo-momory.yaml
pod/pod-demo created
┌──[root@vms81.liruilongs.github.io]-[~/ansible/resources]
└─$kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-demo 1/1 Running 0 12s 10.244.70.55 vms83.liruilongs.github.io <none> <none>
┌──[root@vms81.liruilongs.github.io]-[~/ansible/resources]
└─$
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$kubectl cp ./bigmem-7.0-1.r29766.x86_64.rpm pod-demo:/root/
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$kubectl exec -it pod-demo -- bin/bash
[root@pod-demo /]# cd /root/
[root@pod-demo ~]# rpm -ivh bigmem-7.0-1.r29766.x86_64.rpm
Preparing... ################################# [100%]
Updating / installing...
1:bigmem-7.0-1.r29766 ################################# [100%]
[root@pod-demo ~]# bi
bigmem bind
[root@pod-demo ~]# free -h
total used free shared buff/cache available
Mem: 4.4G 548M 3.1G 216M 818M 3.4G
Swap: 0B 0B 0B
[root@pod-demo ~]# bigmem 3000M
Attempting to allocate 3000 Mebibytes of resident memory...
Killed
[root@pod-demo ~]# bigmem 2000M
Attempting to allocate 2000 Mebibytes of resident memory...
Press <Enter> to exit^C
[root@pod-demo ~]# bigmem 20000M
Attempting to allocate 20000 Mebibytes of resident memory...
Killed
[root@pod-demo ~]# exit
exit
command terminated with exit code 137

可以发现,我们设置了limits 的值,所以由cgroup实现资源隔离来处理内存不够,可以看到顶层的控制组为kubepods.slice,内存不够的时候通过Cgroup 来kill掉进程,而不是通过宿主机内核OOM Killer 来杀

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌──[root@vms83.liruilongs.github.io]-[~]
└─$cat /var/log/messages | grep -i memory
Aug 10 21:08:06 vms83 kernel: [<ffffffff81186c24>] pagefault_out_of_memory+0x14/0x90
Aug 10 21:08:06 vms83 kernel: memory: usage 3072000kB, limit 3072000kB, failcnt 2760
Aug 10 21:08:06 vms83 kernel: memory+swap: usage 3072000kB, limit 9007199254740988kB, failcnt 0
Aug 10 21:08:06 vms83 kernel: Memory cgroup stats for /kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod804e1f6c_d5c0_4f8a_aa27_bcd66393087c.slice: cache:0KB rss:0KB rss_huge:0KB mapped_file:0KB swap:0KB inactive_anon:0KB active_anon:0KB inactive_file:0KB active_file:0KB unevictable:0KB
Aug 10 21:08:06 vms83 kernel: Memory cgroup stats for /kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod804e1f6c_d5c0_4f8a_aa27_bcd66393087c.slice/docker-bf3af784f917c54a50d0cb422d8f2624be3ba65a904e126e89081817d457c4d4.scope: cache:0KB rss:40KB rss_huge:0KB mapped_file:0KB swap:0KB inactive_anon:0KB active_anon:40KB inactive_file:0KB active_file:0KB unevictable:0KB
Aug 10 21:08:06 vms83 kernel: Memory cgroup stats for /kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod804e1f6c_d5c0_4f8a_aa27_bcd66393087c.slice/docker-80f5aff910f2d6e0203fb6fc871cec9bf1d358d7e06ab9d4a381e46bac311465.scope: cache:0KB rss:3071960KB rss_huge:0KB mapped_file:0KB swap:0KB inactive_anon:0KB active_anon:3071912KB inactive_file:0KB active_file:0KB unevictable:0KB
Aug 10 21:08:06 vms83 kernel: Memory cgroup out of memory: Kill process 47482 (bigmem) score 1561 or sacrifice child
Aug 10 21:09:11 vms83 kernel: [<ffffffff81186c24>] pagefault_out_of_memory+0x14/0x90
Aug 10 21:09:11 vms83 kernel: memory: usage 3072000kB, limit 3072000kB, failcnt 2770
Aug 10 21:09:11 vms83 kernel: memory+swap: usage 3072000kB, limit 9007199254740988kB, failcnt 0
Aug 10 21:09:11 vms83 kernel: Memory cgroup stats for /kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod804e1f6c_d5c0_4f8a_aa27_bcd66393087c.slice: cache:0KB rss:0KB rss_huge:0KB mapped_file:0KB swap:0KB inactive_anon:0KB active_anon:0KB inactive_file:0KB active_file:0KB unevictable:0KB
Aug 10 21:09:11 vms83 kernel: Memory cgroup stats for /kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod804e1f6c_d5c0_4f8a_aa27_bcd66393087c.slice/docker-bf3af784f917c54a50d0cb422d8f2624be3ba65a904e126e89081817d457c4d4.scope: cache:0KB rss:40KB rss_huge:0KB mapped_file:0KB swap:0KB inactive_anon:0KB active_anon:40KB inactive_file:0KB active_file:0KB unevictable:0KB
Aug 10 21:09:11 vms83 kernel: Memory cgroup stats for /kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod804e1f6c_d5c0_4f8a_aa27_bcd66393087c.slice/docker-80f5aff910f2d6e0203fb6fc871cec9bf1d358d7e06ab9d4a381e46bac311465.scope: cache:0KB rss:3071960KB rss_huge:0KB mapped_file:0KB swap:0KB inactive_anon:0KB active_anon:3071948KB inactive_file:0KB active_file:0KB unevictable:0KB
Aug 10 21:09:11 vms83 kernel: Memory cgroup out of memory: Kill process 48483 (bigmem) score 1561 or sacrifice child

如果不设置CPU或Memory的Limit值,会怎样呢?(不考虑上面的问题)

在这种情况下,该Pod的资源使用量有一个弹性范围,我们不用绞尽脑汁去思考这两个Limit的合理值,但问题也来了,考虑下面的例子:

Pod A的Memory Request被设置为1GB,NodeA当时空闲的Memory为1.2GB,符合PodA的需求,因此PodA被调度到NodeA上。运行3天后,PodA的访问请求大增,内存需要增加到1.5GB,此时NodeA的剩余内存只有200MB,由于PodA新增的内存已经超出系统资源,所以在这种情况下,PodA就会被Kubernetes杀掉。

没有设置Limit的Pod,或者只设置了CPULimit或者Memory Limit两者之一的Pod,表面看都是很有弹性的,但实际上,相对于4个参数都被设置的Pod,是处于一种相对不稳定的状态的。

CPU和内存这两种计算资源的特点进行说明。

  1. CPU CPU的Requests和Limits是通过CPU数(cpus)来度量的。CPU的资源值是绝对值,而不是相对值,比如0.1CPU在单核或多核机器上是一样的,都严格等于0.1CPU core。

  2. Memory 内存的Requests和Limits计量单位是字节数。使用整数或者定点整数加上国际单位制(International System of Units)来表示内存值。国际单位制包括十进制的E、P、T、G、M、K、m,或二进制的Ei、Pi、Ti、Gi、Mi、Ki。KiB与MiB是以二进制表示的字节单位,常见的KB与MB则是以十进制表示的字节单位,Kubernetes的计算资源单位是大小写敏感的

官方文档的一些描述

如果你没有指定内存限制

如果你没有为一个容器指定内存限制,则自动遵循以下情况之一:

  • 容器可无限制地使用内存。容器可以使用其所在节点所有的可用内存, 进而可能导致该节点调用 OOM Killer。 此外,如果发生 OOM Kill,没有资源限制的容器将被杀掉的可行性更大。

  • 运行的容器所在命名空间有默认的内存限制,那么该容器会被自动分配默认限制。 集群管理员可用使用 LimitRange 来指定默认的内存限制。

内存请求和限制的目的

通过为集群中运行的容器配置内存请求和限制,你可以有效利用集群节点上可用的内存资源。 通过将 Pod 的内存请求保持在较低水平,你可以更好地安排 Pod 调度。 通过让内存限制大于内存请求,你可以完成两件事:

  • Pod 可以进行一些突发活动,从而更好的利用可用内存。
  • Pod 在突发活动期间,可使用的内存被限制为合理的数量。

博文参考

发布于

2022-08-06

更新于

2023-06-21

许可协议

评论
Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×