• 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.*),这里就不详细介绍

    主要关注这几个文件

    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

  • 相关阅读:
    Dede CMS如何在文章中增加“附件下载”操作说明
    仿站模仿的三个网站
    PHP面相对象中的重载与重写
    面向对象思想
    最常用的正则表达式
    PHP第二阶段学习 一、php的基本语法
    PHP isset()与empty()的使用区别详解
    mysql索引总结----mysql 索引类型以及创建
    MySQL实现当前数据表的所有时间都增加或减少指定的时间间隔
    T-SQL语句以及几个数据库引擎
  • 原文地址:https://www.cnblogs.com/zerchin/p/k8s_pod_qos.html
Copyright © 2020-2023  润新知