简述

默认情况下,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

版权声明:本文为zerchin原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/zerchin/p/k8s_pod_qos.html