简述

默认情况下,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. 分布式一致性协议 Raft

    分布式领域,CP模型下 数据一致性协议至关重要,不然两边数据不一致容易出现数据读混乱问题.像Etcd Consul  zookeeper Eureka ,Redis集群方案这些中间件 都有一致性算法来 ...

  2. C语言实现的多线程定时器

    目录 1. 大致功能介绍 2. API库介绍 3. 一个例子 4. 库文件源码 注意事项 1. 大致功能介绍 实现任务列表,定时器会间隔一段时间遍历列表发现要执行的任务 任务列表中的所有任务并行执行 ...

  3. Oracle-序列-存储过程-视图-索引-触发器

    课程介绍 1. 约束(掌握) 2. 序列(掌握) 3. 索引(掌握) 4. 视图(掌握) 5. 存储过程(掌握) 6. 自定义函数(掌握) 7. 触发器(掌握) 数据库对象的命名规则 1.对象名称必须 ...

  4. ES快速开发,ElasticsearchRestTemplate基本使用以及ELK快速部署

    最近博主有一些elasticsearch的工作,所以更新的慢了些,现在就教大家快速入门,并对一些基本的查询.更新需求做一下示例,废话不多说开始: 1. ES快速上手 es下载:[https://ela ...

  5. springboot异常处理之404

    ps: 推荐一下本人的通用后台管理项目crowd-admin 以及newbee-mall增强版,喜欢的话给个star就好 源码分析 在springboot中默认有一个异常处理器接口ErrorConto ...

  6. 【Spring】Spring 入门

    Spring 入门 文章源码 Spring 概述 Spring Spring 是分层的 Java SE/EE 应用全栈式轻量级开源框架,以 IOC(Inverse Of Control,反转控制)和 ...

  7. DHCP最佳实践(二)

    这是Windows DHCP最佳实践和技巧的最终指南. 如果您有任何最佳做法或技巧,请在下面的评论中发布它们. 在本指南(二)中,我将分享以下DHCP最佳实践和技巧. 从DHCP作用域中排除IP 了解 ...

  8. html2canvas canvas webgl 截图透明空🤣

    1. React用这个插件html2canvas完成div截图功能,div里面嵌套canvas,返回base64是透明图片. html2canvas(document.getElementById(& ...

  9. docker save 保存导出镜像

    Docker保存镜像 tag 镜像 # 镜像打 tag 标签 # docker tag 镜像id/名 新名字 docker tag fce91102e17d tomcat01 commit 镜像 注意 ...

  10. MySQL常用的一些(就几个)聚合函数

    聚合函数 (常用) 函数名称 描述 CONUT() 记数 SUM() 求和 AVG() 平均值 MAX() 最大值 MIN() 最小值 -- ================= 聚合函数 ====== ...