简述

默认情况下,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.*),这里就不详细介绍

主要关注这几个文件

  1. 8c9adca52f2bfd9e1862f6abaac5b435bdaf233925b7d640edb28e36da0b9b39ca20137f7d5a42910e22425cca06443c16246c90ee9caa27814442e34a832b21:分别对应的是pod运行的主容器ID和pause容器ID

  2. 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
  1. memory.limit_in_bytes:内存限制的最大值,等于我们设置的内存limit的值
# cat memory.limit_in_bytes
160587776
  1. 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=200000cpu.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使用,主要关注这几个文件

  1. cpu.cfs_period_uscpu.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
  1. 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.sharesstress-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

  • BestEffortBestEffort类型的pod没有设置资源限制,此类pod被认为是最低优先级,当系统内存不足时,这些pod会首先被杀死
  • Burstable Burstable 类型的pod设置有部分资源的请求和限制,此类pod的优先级高于BestEffort类型的pod,当系统内存不足且系统中不存在BestEffort类型的pod时才会被杀死
  • Guaranteed Guaranteed类型的pod同时设置了CPU和内存的资源限制和请求,此类pod的优先级最高,只有在系统内存不足且系统系统中不存在BestEffortBurstable 的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分数的计算公式是一种启发式的,它不能保证“请求和限制”的保证

infra 容器(pause)或init 容器,oom_score_adj为-998

默认kubelet和docker的oom_score_adj为-999

基于上述oom_score,就能保证当系统资源耗尽时,首先被杀死的是BestEffort类型的pod,其次是Burstable类型的pod,最后才是Guaranteed类型的pod

pod资源限制和QoS探索的更多相关文章

  1. K8s QoS Pod资源服务质量控制

    Kubernetes 中如果一个 Node 节点上的 Pod 占用资源过多并且不断飙升导致 Node 节点资源不足,可能会导致为了保证节点可用,将容器被杀掉.在遇见这种情况时候,我们希望先杀掉那些不太 ...

  2. 在 Web 级集群中动态调整 Pod 资源限制

    作者阿里云容器平台技术专家 王程阿里云容器平台技术专家 张晓宇(衷源) ## 引子 不知道大家有没有过这样的经历,当我们拥有了一套 Kubernetes 集群,然后开始部署应用的时候,我们应该给容器分 ...

  3. kubernetes之资源限制及QOS服务质量

    1.什么是资源限制? 1.1在kubernetes集群中,为了使得系统能够稳定的运行,通常会对Pod的资源使用量进行限制.在kubernetes集群中,如果有一个程序出现异常,并且占用大量的系统资源, ...

  4. Kubernetes Pod 资源限制

    Kubernetes Pod 资源限制 官方文档:https://kubernetes.io/docs/concepts/configuration/manage-compute-resources- ...

  5. k8s管理pod资源对象(下)

    一.标签与标签选择器 1.标签是k8s极具特色的功能之一,它能够附加于k8s的任何资源对象之上.简单来说,标签就是键值类型的数据,它们可于资源创建时直接指定,也可随时按需添加于活动对象中,而后即可由标 ...

  6. k8s管理pod资源对象(上)

    一.容器于pod资源对象 现代的容器技术被设计用来运行单个进程时,该进程在容器中pid名称空间中的进程号为1,可直接接收并处理信号,于是,在此进程终止时,容器即终止退出.若要在一个容器中运行多个进程, ...

  7. Kubernetes-5.Pod资源控制器(1)

    docker version:20.10.2 kubernetes version:1.20.1 本文概述Kubernetes Pod资源控制器的ReplicaSet.Deployment.Daemo ...

  8. pod资源的健康检查-readiness探针的httpGet使用

    livenessProbe:健康状态检查,周期性检查服务是否存活,检查结果失败,将重启容器 readinessProbe:可用性检查,周期性检查服务是否可用,不可用将从service的endpoint ...

  9. pod资源的健康检查-liveness探针的exec使用

    使用探针的方式对pod资源健康检查 探针的种类 livenessProbe:健康状态检查,周期性检查服务是否存活,检查结果失败,将重启容器 readinessProbe:可用性检查,周期性检查服务是否 ...

随机推荐

  1. 阿里面试:问springBoot自动装配我这样回答的,面试官对我竖起了大拇指

    引言 最近有个读者在面试,面试中被问到了这样一个问题"看你项目中用到了springboot,你说下springboot的自动配置是怎么实现的?"这应该是一个springboot里面 ...

  2. 使用代码管理工具(git)管理代码的常用指令合集

    create a new repository on the command line echo "# test" >> README.md git init git ...

  3. JavaWeb代码复用

    servlet部分,可能用得到的复用的代码: 1.dopost设置字符 request.setCharacterEncoding("utf-8"); response.setCha ...

  4. jQuery的data()方法

    jQuery文档对.data()方法的描述: As of jQuery 1.4.3 HTML 5 data- attributes will be automatically pulled in to ...

  5. VIM操作快捷键

    i:插入光标前一个字符I:插入行首a:插入光标后一个字符A:插入行末o:向下新开一行,插入行首O:向上新开一行,插入行首M:光标移到中间行L:光标移动到屏幕最后一行行首G:移动到指定行{:按段移动,上 ...

  6. 重入锁ReentrantLock

  7. logback日志对象要素

    <logger>节点 分两种 1.是普通日志对象 logger分为2种,一种是普通日志对象,另一种是根日志对象.对于大部分应用来说,只设置根日志对象即可. 在java日志系统中,无论是lo ...

  8. 二进制格式mysql

    1.二进制MySQL安装 #下载二进制格式的mysql软件包 wget https://downloads.mysql.com/archives/get/p/23/file/mysql-5.7.31- ...

  9. TurtleBot3使用课程-第一节b(北京智能佳)

    目录 1.模拟运行TurtleBot 2 1.1 ROS安装和设置2 1.1.1 turtlebot3 在Gazebo中模拟 3 1.1.1.1用于Gazebo的ROS包装 3 1.1.1.2 tur ...

  10. 改进你的c#代码的5个技巧(二)

    在本文中,我将向你展示c#编程的5个最佳实践.我从日常编程经验中学到了这些实践.我在release模式下测试了所有的代码,并在开发环境稳定后进行了截屏.我想你会喜欢这些建议的. 在使用数据类型之前选择 ...