pod资源限制和QoS探索
简述
默认情况下,k8s不会对pod的资源使用进行限制,也就是说,pod可以无限使用主机的资源,例如CPU、内存等。为了保障k8s整体环境运行的稳定性,一般情况下,建议是对pod的资源使用进行限制,将其限制在一个范围内,防止起过度使用主机资源造成节点负载过大,导致其上面运行的其他应用受到影响。
前提
-
已有的k8s环境(这里安装的是k8s-v1.18.12版本)
-
k8s集群安装了metrics-server服务(这里借助rancher搭建k8s集群,默认安装了该服务)
何为CGroup
参考Wiki,cgroups其名称源自控制组群(英语:control groups)的简写,是Linux内核的一个功能,用来限制、控制与分离一个进程组的资源(如CPU、内存、磁盘输入输出等)。
也就是说,通过设置CGroup,可以达到对资源的控制,例如限制内存、CPU的使用。在k8s中,对pod的资源限制就是通过CGroup这个技术来实现的。
内存限制
pod示例
首先创建一个pod,并设置内存限制
resources:
limits:
memory: "200Mi"
requests:
memory: "100Mi"
内存单位:E、P、T、G、M、K、Ei、Pi、Ti、Gi、Mi、Ki
其中M = 1000 x 1000,Mi = 1024 x 1024
limit必须要≥request
完整yaml参考
apiVersion: v1
kind: Pod
metadata:
name: stress-memory
namespace: default
spec:
containers:
- name: stress
image: polinux/stress
imagePullPolicy: IfNotPresent
command: ["stress"]
args: ["--vm", "1", "--vm-bytes", "150M", "--vm-hang", "1"]
resources:
limits:
memory: "200Mi"
requests:
memory: "100Mi"
nodeName: node01 ## 这里指定调度到某个节点上,方便接下来的操作
这里设置了内存的limit为”200Mi”,内存的request为”100Mi”,并通过stress
,指定分配150M的内存空间
接着我们运行该yaml
kubectl create -f stress-memory.yaml
等到pod运行起来后,我们看一下pod的详细信息
kubectl get pod stress-memory -oyaml
输出结果如下
...
...
name: stress-memory
namespace: default
...
...
uid: b84cf8e8-03b3-4365-b5b5-5d9f99969705
...
...
resources:
limits:
memory: 200Mi
requests:
memory: 100Mi
...
...
status:
...
...
qosClass: Burstable
...
...
从上述输出中可以获取如下两个信息:
- pod的uid为
b84cf8e8-03b3-4365-b5b5-5d9f99969705
- pod的QoSClass为
Burstable
这里Burstable对应的就是pod在CGroup中的路径,我们进入到CGroup的memory目录下,并进入到kubepods/Burstable
目录中
cd /sys/fs/cgroup/memory/kubepods/burstable/
# ls
...
podb84cf8e8-03b3-4365-b5b5-5d9f99969705
...
通过执行ls
命令,能看到其中一个目录为podb84cf8e8-03b3-4365-b5b5-5d9f99969705
,正好对应上面我们获得pod的uid,也就是说,对pod的资源限制,就是在这个CGroup目录下实现的
我们看一下这个目录下有什么文件
cd podb84cf8e8-03b3-4365-b5b5-5d9f99969705/
ls -1
8c9adca52f2bfd9e1862f6abaac5b435bdaf233925b7d640edb28e36da0b9b39
ca20137f7d5a42910e22425cca06443c16246c90ee9caa27814442e34a832b21
cgroup.clone_children
cgroup.event_control
cgroup.procs
memory.failcnt
memory.force_empty
memory.kmem.failcnt
memory.kmem.limit_in_bytes
memory.kmem.max_usage_in_bytes
memory.kmem.slabinfo
memory.kmem.tcp.failcnt
memory.kmem.tcp.limit_in_bytes
memory.kmem.tcp.max_usage_in_bytes
memory.kmem.tcp.usage_in_bytes
memory.kmem.usage_in_bytes
memory.limit_in_bytes
memory.max_usage_in_bytes
memory.memsw.failcnt
memory.memsw.limit_in_bytes
memory.memsw.max_usage_in_bytes
memory.memsw.usage_in_bytes
memory.move_charge_at_immigrate
memory.numa_stat
memory.oom_control
memory.pressure_level
memory.soft_limit_in_bytes
memory.stat
memory.swappiness
memory.usage_in_bytes
memory.use_hierarchy
notify_on_release
tasks
相关文件作用如下:
文件 | 作用 |
---|---|
cgroup.event_control | 用于event_fd()接口 |
cgroup.procs | 展示process列表 |
memory.failcnt | 内存使用达到限制的次数 |
memory.force_empty | 强制触发当前CGroup中的内存回收 |
memory.limit_in_bytes | 内存限制的最大值 |
memory.max_usage_in_bytes | 记录该CGroup中历史最大内存使用量 |
memory.move_charge_at_immigrate | 设置/显示当前CGroup的进程移动到另一个CGroup时,当前已占用的内存是否迁移到新的CGroup中,默认为0,即不移动 |
memory.numa_stat | 显示numa相关的内存信息 |
memory.oom_control | 设置/显示oom相关信息,其中oom_kill_disable为0,则超过内存会被kill;oom_kill_disable为1则停止进程,直至额外的内存被释放,当进程被暂停时,under_oom返回1 |
memory.pressure_level | 设置内存压力的通知事件,配合cgroup.event_control一起使用 |
memory.soft_limit_in_bytes | 设置/显示内存的软限制,默认不限制 |
memory.stat | 显示当前CGroup中内存使用情况 |
memory.swappiness | 设置/显示vmscan的swappiness参数(参考sysctl的vm.swappiness) |
memory.usage_in_bytes | 显示当前内存使用情况 |
memory.use_hierarchy | 如果该值为0,将会统计到root cgroup里;如果值为1,则统计到它的父cgroup里面 |
notify_on_release | 是否在cgroup中最后一个任务退出时通知运行release agent,默认情况下是0,表示不运行。(release_agent在CGroup最顶层的目录) |
tasks | 控制的进程组(这里看不到对应进程,需要进入到子group中查看) |
不涉及内核内存(memory.kmem.*)和swap分区内存(memory.memsw.*),这里就不详细介绍
主要关注这几个文件
-
8c9adca52f2bfd9e1862f6abaac5b435bdaf233925b7d640edb28e36da0b9b39
和ca20137f7d5a42910e22425cca06443c16246c90ee9caa27814442e34a832b21
:分别对应的是pod运行的主容器ID和pause容器ID -
memory.usage_in_bytes
已使用的内存,例如我这里查看的结果是160817152,也就是153MB左右
# cat memory.usage_in_bytes
160382976
使用kubect top命令查看使用情况
# kubectl top pod
NAME CPU(cores) MEMORY(bytes)
stress-memory 13m 151Mi
-
memory.limit_in_bytes
:内存限制的最大值,等于我们设置的内存limit的值
# cat memory.limit_in_bytes
160587776
-
memory.max_usage_in_bytes
:历史内存最大使用量,再查看一下该CGroup下内存历史最大使用量,正好200M
# cat memory.max_usage_in_bytes
209715200
创建一个内存使用超出limit的pod
这时候我们将内存使用设置到250M,超出200M的限制
apiVersion: v1
kind: Pod
metadata:
name: stress-memory2
namespace: default
spec:
containers:
- name: stress
image: polinux/stress
imagePullPolicy: IfNotPresent
command: ["stress"]
args: ["--vm", "1", "--vm-bytes", "250M", "--vm-hang", "1"] ## 修改为250M,也就是分配250M内存
resources:
limits:
memory: "200Mi"
requests:
memory: "100Mi"
nodeName: node01
执行kubectl create
命令,运行这个pod
kubectl create -f pod-memory-2.yaml
查看pod状态
# kubectl get pod
NAME READY STATUS RESTARTS AGE
stress-memory 1/1 Running 1 10m6s
stress-memory2 0/1 OOMKilled 2 26s
此时会发现,pod在不断重启,并且stress-memory2这个pod的状态为OOMKilled,这个是怎么回事呢,我们可以进到pod对应的CGroup下查看内存使用情况,我们继续看一下目前pod的状态
kubectl get pod stress-memory2 -oyaml
...
...
status:
...
...
lastState:
terminated:
containerID: docker://a7d686a3b56aa03b66fd4fed07217693d8e41d75529c02bae34769dca6f01f9e
exitCode: 1
finishedAt: "2021-01-18T14:13:21Z"
reason: OOMKilled
startedAt: "2021-01-18T14:13:21Z"
可以看到pod退出的原因是OOMKilled,什么是OOMKilled呢?简单来说,就是当进程申请的内存超出了已有的内存资源,那么为了保证主机的稳定运行,就会基于进程oom_score的值,有选择性的杀死某个进程。也就是说,在这个例子中,pod申请的内存为250Mi,超过了限制的200Mi,那么就会从该进程所在的CGroup中,杀死对应的进程,具体我们可以看一下该CGroup中内存情况:
通过kubectl get pod stress-memory2 -oyaml
命令,获取pod uid,进入到对应的CGroup
cd /sys/fs/cgroup/memory/kubepods/burstable/pod92c2a4c2-3b5c-4a9a-8a00-5d59575e96e7/
首先看一下内存限制是多少
# cat memory.limit_in_bytes
209715200
再查看一下内存使用量,只有1M左右,这是因为此时pod状态不是运行状态
# cat memory.usage_in_bytes
1093632
再查看一下该CGroup下内存历史最大使用量,正好200M
# cat memory.max_usage_in_bytes
209715200
此时我们再看看内存使用量达到限制值的次数
# cat memory.failcnt
531
从以上信息可以得知,内存不断申请超出内存限制的值,导致进程被kill,最终导致pod退出
只设置request
设置request=100M,不设置limit,并设置pod使用内存150M
apiVersion: v1
kind: Pod
metadata:
name: stress-memory4
namespace: default
spec:
containers:
- name: stress
image: polinux/stress
imagePullPolicy: IfNotPresent
command: ["stress"]
args: ["--vm", "1", "--vm-bytes", "150M", "--vm-hang", "1"]
resources:
requests:
memory: "100Mi"
nodeName: node01
执行kubectl create
命令,运行这个pod
kubectl create -f pod-memory-4.yaml
查看pod状态
# kubectl get pod
NAME READY STATUS RESTARTS AGE
stress-memory3 1/1 Running 0 79s
查看pod内存使用
# kubectl top pod
NAME CPU(cores) MEMORY(bytes)
stress-memory3 51m 150Mi
可以发现,pod内存使用时150Mi,说明只设置内存request,并不会对其限制其内存的使用
注意:如果只设置limit,则request=limit
内存资源限制的目的
-
如果没有指定内存限制,则容器可以无限制的使用主机内存资源,当使用完主机的所有可用资源后,就会导致该节点调用OOMkilled,此时没有设置内存限制的pod对应的进程的score会更大,所以被kill的可能性更大。
-
为集群中的pod设置内存请求和限制,可以有效的利用集群上的内存资源,防止应用突发高峰期的时候内存猛涨影响其他应用的稳定运行
CPU限制
pod示例
首先创建一个pod,并设置CPU限制
resources:
limits:
cpu: "1"
requests:
cpu: "0.5"
CPU单位:小数值是可以使用。一个请求 0.5 CPU 的容器保证会获得请求 1 个 CPU 的容器的 CPU 的一半。 可以使用后缀
m
表示毫。例如100m
CPU、100 milliCPU 和 0.1 CPU 都相同。 精度不能超过 1m。
完整yaml参考
apiVersion: v1
kind: Pod
metadata:
name: stress-cpu
spec:
containers:
- name: stress-cpu
image: vish/stress
resources:
limits:
cpu: "0.9"
requests:
cpu: "0.5"
args:
- -cpus
- "2"
nodeName: node01 ## 这里指定调度到某个节点上,方便接下来的操作
这里设置了CPU的limit为”1″,CPU的request为”0.5″,并通过stress指定分配2个进程去跑满2个CPU
接着我们运行该yaml
kubectl create -f stress-cpu.yaml
等到pod运行起来后,我们看一下pod的详细信息
kubectl get pod stress-cpu -oyaml
输出结果如下
...
...
name: stress-cpu
namespace: default
...
...
uid: 10929272-932f-4b4d-85c8-046a9f0e39d8
...
...
resources:
limits:
cpu: 900m
requests:
cpu: 500m
...
...
status:
...
...
qosClass: Burstable
...
...
结合之前的内存相关的实验,从输出的结果中可以得知pod uid为10929272-932f-4b4d-85c8-046a9f0e39d8,对应的CGroup路径为kubepods/Burstable
在CGroup中可以看到cpu、cpuset、cpuacct三种跟CPU相关的CGroup,其中cpu
用于对cpu使用率的划分;cpuset
用于设置cpu的亲和性等,主要用于numa架构的os;cpuacct
记录了cpu的部分信息。通过定义可以得知,k8s CPU限制在cpu
这个目录中,我们进入到对应的pod的CGroup空间下,查看CGroup相关文件是如何工作的
# cd /sys/fs/cgroup/cpu/kubepods/burstable/pod10929272-932f-4b4d-85c8-046a9f0e39d8/
ls -1
cgroup.clone_children
cgroup.procs
cpuacct.stat
cpuacct.usage
cpuacct.usage_percpu
cpu.cfs_period_us
cpu.cfs_quota_us
cpu.rt_period_us
cpu.rt_runtime_us
cpu.shares
cpu.stat
d5b407752d32b7cd8937eb6a221b6f013522d00bb237134017ae5d8324ce9e30
eba8c20349137130cdc0af0e9db2a086f6ea5d6f37ad118394b838adfc1325bd
notify_on_release
tasks
相关文件作用如下:
文件 | 作用 |
---|---|
cgroup.clone_children |
子cgroup是否会继承父cgroup的配置,默认是0 |
cgroup.procs |
树中当前节点的cgroup中的进程组ID,现在我们在根节点,这个文件中是会有现在系统中所有进程组ID |
cpu.cfs_period_us |
统计CPU使用时间的周期,单位是微秒(us)。 例如如果设置该CGroup中的进程访问CPU的限制为每1秒访问单个CPU0.2秒,则需要设置 cpu.cfs_period_us=200000 ,cpu.cfs_quota_us=1000000
|
cpu.cfs_quota_us |
周期内允许占用的CPU时间,即CGroup中的进程能使用cpu的最大时间,该值是硬限(-1为不限制) 一旦cgroup中的任务用完了配额指定的所有时间,它们就会在该时间段指定的剩余时间中受到限制,并且直到下一个时间段才允许运行。 cpu.cfs_quota_us 参数的上限为1秒,下限为1000微秒 |
cpu.rt_period_us |
仅适用于实时调度任务,CPU使用时间的周期,单位是微秒(us),默认是1s(1000000us) |
cpu.rt_runtime_us |
仅适用于实时调度任务,此参数指定cgroup中的任务可以访问CPU资源的最长连续时间 |
cpu.shares |
cpu.shares 是软限制,理解为CPU的相对权重。例如,A、B、C三个进程的权重为100、200、300,那么在CPU满载的情况下,A进程最多使用1/6 CPU时间片;而如果CPU不是满载的情况,则各个进程允许使用超出相对权重的大小的CPU时间 |
cpu.stat |
CPU时间统计信息,其中:nr_periods —已经过去的周期间隔数(在cpu.cfs_period_us中指定)nr_throttled — cgroup中的任务被限制的次数(即,由于它们已经用尽了配额所指定的所有可用时间,因此不允许运行)throttled_time — cgroup中的任务已被限制的总持续时间(以纳秒为单位) |
不涉及到cpuacct,这里就不详细介绍
在CPU的CGroup中,可以使用两个调度程序来调度对CPU资源的访问:
-
CFS
:完全公平调度程序,比例共享调度程序,根据任务或分配给cgroup的优先级/权重,在任务组(cgroup)之间按比例划分CPU时间。 -
RT
:实时调度程序,一种任务调度程序,它提供一种方法来指定实时任务可以使用的CPU时间。(不做讨论)
这里主要讨论k8s是如何设置CFS调度算法去限制进程对CPU使用,主要关注这几个文件
-
cpu.cfs_period_us
和cpu.cfs_quota_us
,由这两个文件组成CPU硬限制,在这个例子中,我们设置CPUlimit的值为900m,所以cfs_quota_us/cfs_period_us
等于90000/100000
,也就是0.9个CPU
# cat cpu.cfs_period_us
100000
# cat cpu.cfs_quota_us
90000
-
cpu.shares
,CPU软限制,在这个例子中我们设置了CPU request为500m,可以看出,CPU request对应了cpu.shares
(此时软限制不会起到作用)
# cat cpu.shares
512
此时查看podCPU使用情况,可以看到确实已经被限制住了
# kubectl top pod
NAME CPU(cores) MEMORY(bytes)
stress-cpu 902m 0Mi
request之CPU软限制
在前面讲内存限制的时候说到,如果只设置request,则limit没有限制。
如果CPU只设置request,此时limit也是没有限制。但是不同于内存,CPU request的值会设置到cpu.shares
中,也就是说,只设置了request的pod,会有一个CPU软限制。
此时正常情况下,当节点CPU资源充足的时候,设置了request的pod,还是可以正常的请求超出request值的CPU资源,可是当节点可分配的CPU资源不足时,那么CPU软限制就会起到作用,限制pod对CPU的访问。
下面我们测试一下CPU软限制是如何生效的
查看节点可分配的CPU大小
kubectl describe node node01
...
...
Allocatable:
cpu: 4
ephemeral-storage: 57971659066
hugepages-2Mi: 0
memory: 8071996Ki
pods: 110
...
...
Allocated resources:
(Total limits may be over 100 percent, i.e., overcommitted.)
Resource Requests Limits
-------- -------- ------
cpu 380m (9%) 10m (0%)
memory 100Mi (1%) 190Mi (2%)
ephemeral-storage 0 (0%) 0 (0%)
目前该节点可用CPU数量为4000m,已使用了380m,也就是剩余可用CPU为3620m
我们先创建一个CPU,设置request为0.5的pod,并通过stress指定分配2个进程去跑满2个CPU
kind: Pod
metadata:
name: stress-cpu-1
spec:
containers:
- name: stress-cpu
image: vish/stress
resources:
requests:
cpu: "0.5"
args:
- -cpus
- "2"
执行kubectl create
命令,运行这个pod
kubectl create -f stress-cpu-1.yaml
查看pod状态
# kubectl get pod
NAME READY STATUS RESTARTS AGE
stress-cpu 1/1 Running 0 5s
查看podCPU使用情况
# kubectl top pod
NAME CPU(cores) MEMORY(bytes)
stress-cpu 2001m 0Mi
也可以使用top命令实时查看进程CPU使用情况
# top
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
30285 root 20 0 5824 3692 3120 R 200.0 0.0 56:18.95 stress
此时我们可以看到,我们进程的CPU使用率达到了2001m,超过了request设置的值
这时候我们再启动一个pod,设置request为2.5的pod,并通过stress指定分配2个进程去跑满3个CPU
apiVersion: v1
kind: Pod
metadata:
name: stress-cpu-2
spec:
containers:
- name: stress-cpu
image: vish/stress
resources:
requests:
cpu: "2.5"
args:
- -cpus
- "3"
执行kubectl create
命令,运行这个pod
kubectl create -f stress-cpu-2.yaml
查看pod状态
# kubectl get pod
NAME READY STATUS RESTARTS AGE
stress-cpu 1/1 Running 0 15m
stress-cpu-2 1/1 Running 0 8s
查看podCPU使用情况,此时由于pod占用了大量的CPU资源,执行kubectl
会卡住无法执行,可以通过top
命令查看CPU使用情况
# top
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
20732 root 20 0 5568 3688 3120 R 300.0 0.0 6:04.80 stress
30285 root 20 0 5824 3692 3120 R 96.2 0.0 63:57.08 stress
我们再来回顾一下,首先这台主机可用CPU资源3620m,总的CPU资源为4个CPU(4000m),其中380m的CPU资源已分配给其他pod使用,但是并没有满负载,所以总的资源我们可以按照4个CPU来算。
这里可以看到PID为30285的进程是之前我们创建的pod stress-cpu,当时设置的request是0.5(500m),并指定分配2个进程去跑满2个CPU(2000m),也就是软限制在资源充足的情况下,使用率为200%(2000m),超过了CPUrequest软限制。
后面我们又创建了一个request为2.5(2500m),并指定分配3个进程去跑满3个CPU(3000m)的pod stress-cpu-2,此时CPU总的CPU使用需求为2+3=5CPU(5000m),但是CPU的总资源只有4CPU(4000m),此时CPU软限制就会开始发挥作用。
我们查看一下对应的cpu.shares
,stress-cpu pod 的cpu.shares
的值为512,stress-cpu-2 pod 的cpu.shares
的值为2560。
cpu.shares
是软限制,理解为CPU的相对权重。根据之前表格中的算法,那么当CPU满负载的情况下(此时假设只有这两个pod正在满负载的运行):
-
stress-cpu可以使用
4 x 512/(512+2560)≈0.666
(666m CPU) -
stress-cpu-2可以使用
4 x 2560/(512+2560)≈3.333
(3333m CPU)
由这个公式可以得知,在软限制的前提下,stress-cpu-2可以跑满3333m CPU,但是我们只分配了3个进程去跑满CPU,也就是说,实际上stress-cpu-2可以跑到3000mCPU ,正好符合我们使用top看到的数据,占用了300%(3000m),还剩余333m左右的CPU资源未使用,而这部分的资源可以被其他进程使用。那么可以思考一下,stress-cpu可以使用多少CPU资源?
其实从上面top命令的结果就可以知道,stress-cpu使用了近1000mCPU的资源,这是因为,当CPU满负载时,会相应的分配666mCPU的资源,此时stress-cpu-2并没有完全使用完,还剩余333mCPU未被使用,那么由于软限制的作用下,实际上是可以使用这部分未被使用的资源,也就是说,stress-cpu可以使用666m + 333m = 999m
(CPU),也就是符合使用top命令看到的96%CPU使用率。
CPU资源限制的目的
- 如果没有指定CPU限制,则容器可以无限制的使用主机CPU资源。为集群中的pod设置CPU请求和限制,可以有效的利用集群上的CPU资源,防止应用突发高峰期的时候CPU猛涨影响其他应用的稳定运行
QoS
前面我们讲了如何通过设置资源的request和limit来限制pod对主机资源的使用,在前面几个例子中,我们看到,当我们设置了资源配额时,查看pod yaml可以看到qosClass
的值为 Burstable,这是因为在k8s中,会为pod设置不同的QoS类型,以保证pod的资源可用,其中QoS有三个类型:
-
Guaranteed
:Pod中的所有容器(包括init容器)都必须设置内存和CPU的请求(limit)和限制(request),且请求和限制的值要相等。(如果只设置limit,则默认request=limit); -
Burstable
:Pod中至少有一个容器设置了内存和CPU请求,且不符合Guaranteed QoS类型的标准; -
BestEffort
:Pod中所有的容器都没有设置任何内存和CPU的限制和请求。
即会根据对应的请求和限制来设置QoS等级,接下来我们分别创建对应的QoS等级来感受一下
Guaranteed
定义:Pod中的所有容器(包括init容器)都必须设置内存和CPU的请求(limit)和限制(request),且请求和限制的值要相等。其中如果只设置limit,则默认request=limit
举个例子:创建一个包含内存和CPU请求和限制的pod,其中内存的请求和限制都为300Mi,CPU的请求和限制都为500m
apiVersion: v1
kind: Pod
metadata:
name: pod-guaranteed
spec:
containers:
- name: pod-guaranteed
image: zerchin/network
resources:
limits:
memory: "300Mi"
cpu: "500m"
requests:
memory: "300Mi"
cpu: "500m"
创建pod
kubectl create -f pod-guaranteed.yaml
查看pod 详细信息
kubectl get pod pod-guaranteed -oyaml
输出结果如下,可以看到pod的qosClass为Guaranteed
...
...
name: pod-guaranteed
uid: 494e608c-d63e-41a9-925c-3ac7acf7b465
...
...
limits:
cpu: 500m
memory: 300Mi
requests:
cpu: 500m
memory: 300Mi
...
...
status:
qosClass: Guaranteed
根据前面的经验,这里Guaranteed对应的就是pod在CGroup中的路径,对应的CGroup路径如下:
- CPU:
/sys/fs/cgroup/memory/kubepods/pod494e608c-d63e-41a9-925c-3ac7acf7b465
- 内存:
/sys/fs/cgroup/cpu/kubepods/pod494e608c-d63e-41a9-925c-3ac7acf7b465
Burstable
定义:Pod中至少有一个容器设置了内存和CPU请求,且不符合Guaranteed QoS类型的标准;
回顾一下我们前面我们在了解内存和CPU限制的时候,创建的pod都是Burstable Qos类型,包括:
- 单一设置内存或CPU的request
- 同时设置了内存或CPU的request和limit,且request≠limit
- 同时设置了内存或CPU的request和limit,且request=limit
上述这些都是pod中只含有单个容器,还有一种情况就是单个pod包含多个容器,如果一个容器指定了资源请求,另一个容器没有指定任何请求和限制,则也是属于Burstable Qos类型
举个例子:创建一个多容器的pod,其中一个容器设置了内存和CPU的请求和限制且值相等,另一个容器不限制资源
apiVersion: v1
kind: Pod
metadata:
name: pod-burstable
spec:
containers:
- name: pod-burstable-1
image: zerchin/network
resources:
limits:
memory: "300Mi"
cpu: "500m"
requests:
memory: "300Mi"
cpu: "500m"
- name: pod-burstable-2
image: busybox:1.28
args: ["sh", "-c", "sleep 3600"]
创建pod
kubectl create -f pod-burstable.yaml
查看pod 详细信息
kubectl get pod pod-burstable -oyaml
输出结果如下,可以看到pod的qosClass为Guaranteed
...
...
name: pod-burstable
uid: 7d858afc-f88a-454e-85dc-81670a0ddb8b
...
...
status:
qosClass: Burstable
...
...
BestEffort
定义:pod中所有的容器都没有设置任何内存和CPU的限制和请求。
这个很好理解,我们创建个pod试试
apiVersion: v1
kind: Pod
metadata:
name: pod-besteffort
spec:
containers:
- name: pod-besteffort
image: zerchin/network
创建pod
kubectl create -f pod-besteffort.yaml
查看pod 详细信息
kubectl get pod pod-besteffort -oyaml
输出结果如下,可以看到pod的qosClass为BestEffort
...
...
name: pod-besteffort
uid: 1316dbf7-01ed-415b-b901-2be9d650163c
...
...
status:
qosClass: BestEffort
那么,在CGroup中对应的路径为kubepods/besteffort/pod1316dbf7-01ed-415b-b901-2be9d650163c
QoS优先级
当集群资源被耗尽时,容器会被杀死,此时会根据QoS优先级对pod进行处理,即优先级高的会尽量被保护,而优先级低的会优先被杀死
三种Qos类型优先级(由高到低):Guaranteed > Burstable > BestEffort
其中CPU属于可压缩资源,而内存属于不可压缩资源,这里我们主要讨论一下当内存耗尽时,是如何根据QoS优先级处理pod
-
BestEffort
:BestEffort
类型的pod没有设置资源限制,此类pod被认为是最低优先级,当系统内存不足时,这些pod会首先被杀死 -
Burstable
:Burstable
类型的pod设置有部分资源的请求和限制,此类pod的优先级高于BestEffort
类型的pod,当系统内存不足且系统中不存在BestEffort
类型的pod时才会被杀死 -
Guaranteed
:Guaranteed
类型的pod同时设置了CPU和内存的资源限制和请求,此类pod的优先级最高,只有在系统内存不足且系统系统中不存在BestEffort
和Burstable
的pod时才会被杀死
OOM score
在前面讲内存限制时提到过,就是当进程申请的内存超出了已有的内存资源,那么为了保证主机的稳定运行,就会基于进程oom_score
的值,有选择性的杀死某个进程,这个过程就是OOMKilled。
这里我们先了解一下什么是oom_score
:
当系统内存资源被耗尽时,就需要释放部分内存保证主机的运行。而内存是被进程所占用的,所以释放内存实际上就是要杀死进程。那么系统是如何选择进程进行杀死的呢?答案就是基于oom_score
的值选择可以杀死的进程。oom_score的值在0-1000范围,其中oom_score
的值越高,则被杀死的可能性越大。
oom_score
的值还会受到oom_score_adj
影响,最后的得分会加上oom_score_adj
的值,也就是说,可以通过设置oom_score_adj
的大小从而影响最终oom_score
的大小。
其中正常情况下,oom_score
是该进程消耗的内存百分比的10倍,通过oom_score_adj
进行调整,例如如果某个进程使用了100%的内存,则得分为1000;如果使用0%的内存,则得分为0(其他例如root用户启动的进程会减去30这里不讨论)
那么我们来看看不同的QoS类型的score值是如何设置的
-
BestEffort
:由于它的优先级最低,为了保证BestEffort
类型的pod最先被杀死,所以设置oom_score_adj
为1000,那么BestEffort
类型pod的oom_score
值为1000 -
Guaranteed
:由于它的优先级最高,为了保证Guaranteed
类型的pod的不被杀死,所以设置oom_score_adj
为-998,那么Guaranteed
类型pod的oom_score
值为0或者1 -
Burstable
:这里要分几种情况讨论- 如果总内存请求 > 可用内存的99.8%,则设置
oom_score_adj
的值为2,否则将oom_score_adj
设置为1000 - 10 x 内存请求的百分比
,这样可以确保Burstable
类型的pod的oom_score
> 1。 - 如果内存请求为0,则设置
oom_score_adj
的值为999。所以,如果Burstable
类型的pod和Guaranteed
类型的发生冲突时,保证Burstable
类型的pod被杀死。 - 如果
Burstable
类型的pod使用的内存少于请求的内存,则其oom_score
<1000,因此,如果BestEffort
类型的pod与使用少于请求内存的Burstable
类型的pod发生冲突时,则BestEffort
类型的pod将被杀死 - 如果
Burstable
类型的pod使用的内存大于内存请求时,oom_score
=1000,否则oom_score
<1000 - 如果一个使用的内存多于请求内存的
Burstable
类型的pod,与另一个使用的内存少于请求内存的Burstable
类型的pod发生冲突时,则前者将被杀死 - 如果
Burstable
类型的pod与多个进程发生冲突,则OOM分数的计算公式是一种启发式的,它不能保证“请求和限制”的保证
- 如果总内存请求 > 可用内存的99.8%,则设置
infra 容器(pause)或init 容器,oom_score_adj
为-998
默认kubelet和docker的oom_score_adj
为-999
基于上述oom_score,就能保证当系统资源耗尽时,首先被杀死的是BestEffort
类型的pod,其次是Burstable
类型的pod,最后才是Guaranteed
类型的pod