资源服务质量管理(Resource Qos)

本节对Kubernetes如何根据Pod的Requests和Limits配置来实现针对Pod的不同级别的资源服务质量控制(QoS)进行说明。

在Kubernetes的资源QoS体系中,需要保证高可靠性的Pod可以申请可靠资源,而一些不需要高可靠性的Pod可以申请可靠性较低或者不可靠的资源。在上节中讲到了容器的资源配置分为Requests和Limits,其中Requests是Kubernetes调度时能为容器提供的完全可保障的资源量(最低保障),而Limits是系统允许容器运行时可能使用的资源量的上限(最高上限)。Pod级别的资源配置是通过计算Pod内所有容器的资源配置的总和得出来的。

Kubernetes中Pod的Requests和Limits资源配置有如下特点。

(1)如果Pod配置的Requests值等于Limits值,那么该Pod可以获得的资源是完全可靠的。

(2)如果Pod的Requests值小于Limits值,那么该Pod获得的资源可分成两部分:

◎ 完全可靠的资源,资源量的大小等于Requests值;
◎ 不可靠的资源,资源量最大等于Limits与 Requests的差额,这份不可靠的资源能够申请到多少,取决于当时主机上容器可用资源的余量。

通过这种机制,Kubernetes可以实现节点资源的超售(Over Subscription),比如在CPU完全充足的情况下,某机器共有32GiB内存可提供给容器使用,容器配置为Requests值1GiB,Limits值为2GiB,那么在该机器上最多可以同时运行32个容器,每个容器最多可以使用2GiB内存,如果这些容器的内存使用峰值能错开,那么所有容器都可以正常运行。

超售机制能有效提高资源的利用率,同时不会影响容器申请的完全可靠资源的可靠性。

1.Requests和Limits对不同计算资源类型的限制机制

根据前面的内容可知,容器的资源配置满足以下两个条件:
◎ Requests<=节点可用资源;
◎ Requests<=Limits。

Kubernetes根据Pod配置的Requests值来调度Pod,Pod在成功调度之后会得到Requests值定义的资源来运行;而如果Pod所在机器上的资源有空余,则Pod可以申请更多的资源,最多不能超过Limits的值。下面看一下Requests和Limits针对不同计算资源类型的限制机制的差异。这种差异主要取决于计算资源类型是可压缩资源还是不可压缩资源。

1)可压缩资源

◎ Kubernetes目前支持的可压缩资源是CPU。
◎ Pod可以得到Pod的Requests配置的CPU使用量,而能否使用超过Requests值的部分取决于系统的负载和调度。不过由于目前Kubernetes和Docker的CPU隔离机制都是在容器级别隔离的,所以Pod级别的资源配置并不能完全得到保障;Pod级别的cgroups正在紧锣密鼓地开发中,如果将来引入,就可以确保Pod级别的资源配置准确运行。
◎ 空闲CPU资源按照容器Requests值的比例分配。举例说明:容器A的CPU配置为Requests 1 Limits 10,容器B的CPU配置为Request 2 Limits 8,A和B同时运行在一个节点上,初始状态下容器的可用CPU为3cores,那么A和B恰好得到在它们的Requests中定义的CPU用量,即1CPU和2CPU。如果A和B都需要更多的CPU资源,而恰好此时系统的其他任务释放出1.5CPU,那么这1.5CPU将按照A和B的Requests值的比例1∶2分配给A和B,即最终A可使用1.5CPU,B可使用3CPU。
◎ 如果Pod使用了超过在Limits 10中配置的CPU用量,那么cgroups会对Pod中的容器的CPU使用进行限流(Throttled);如果Pod没有配置Limits 10,那么Pod会尝试抢占所有空闲的CPU资源(Kubernetes从1.2版本开始默认开启--cpu-cfs-quota,因此在默认情况下必须配置Limits)。

2)不可压缩资源

◎ Kubernetes目前支持的不可压缩资源是内存。
◎ Pod可以得到在Requests中配置的内存。如果Pod使用的内存量小于它的Requests的配置,那么这个Pod可以正常运行(除非出现操作系统级别的内存不足等严重问题);如果Pod使用的内存量超过了它的Requests的配置,那么这个Pod有可能被Kubernetes杀掉:比如Pod A使用了超过Requests而不到Limits的内存量,此时同一机器上另外一个Pod B之前只使用了远少于自己的Requests值的内存,此时程序压力增大,Pod B向系统申请的总量不超过自己的Requests值的内存,那么Kubernetes可能会直接杀掉Pod A;另外一种情况是Pod A使用了超过Requests而不到Limits的内存量,此时Kubernetes将一个新的Pod调度到这台机器上,新的Pod需要使用内存,而只有Pod A使用了超过了自己的Requests值的内存,那么Kubernetes也可能会杀掉Pod A来释放内存资源。
◎ 如果Pod使用的内存量超过了它的Limits设置,那么操作系统内核会杀掉Pod所有容器的所有进程中使用内存最多的一个,直到内存不超过Limits为止。

2.对调度策略的影响

◎ Kubernetes的kubelet通过计算Pod中所有容器的Requests的总和来决定对Pod的调度。
◎ 不管是CPU还是内存,Kubernetes调度器和kubelet都会确保节点上所有Pod的Requests的总和不会超过在该节点上可分配给容器使用的资源容量上限。

3.服务质量等级

在一个超用(Over Committed,容器Limits总和大于系统容量上限)系统中,由于容器负载的波动可能导致操作系统的资源不足,最终可能导致部分容器被杀掉。在这种情况下,我们当然会希望优先杀掉那些不太重要的容器,那么如何衡量重要程度呢?Kubernetes将容器划分成3个QoS等级:Guaranteed(完全可靠的)、Burstable(弹性波动、较可靠的)和BestEffort(尽力而为、不太可靠的),这三种优先级依次递减。

从理论上来说,QoS级别应该作为一个单独的参数来提供API,并由用户对Pod进行配置,这种配置应该与Requests和Limits无关。但在当前版本的Kubernetes的设计中,为了简化模式及避免引入太多的复杂性,QoS级别直接由Requests和Limits来定义。在Kubernetes中容器的QoS级别等于容器所在Pod的QoS级别,而Kubernetes的资源配置定义了Pod的三种QoS级别,如下所述。

1) Guaranteed

如果Pod中的所有容器对所有资源类型都定义了Limits和Requests,并且所有容器的Limits值都和Requests值全部相等(且都不为0),那么该Pod的QoS级别就是Guaranteed。注意:在这种情况下,容器可以不定义Requests,因为Requests值在未定义时默认等于Limits。

在下面这两个例子中定义的Pod QoS级别就是Guaranteed:

containers:
  name: foo
    resources:
      limits:
        cpu: 10m
        memory: 1Gi
  name: bar
    resources:
      limits:
        cpu: 100m
        memory: 100Mi 

在上面的例子中未定义Requests值,所以其默认等于Limits值。而在下面这个例子中定义的Requests和Limits的值完全相同:

containers:
  name: foo
    resources:
      requests:
        cpu: 10m
        memory: 1Gi
      limits:
        cpu: 10m
        memory: 1Gi
  name: bar
    resources:
      requests:
        cpu: 10m
        memory: 1Gi
      limits:
        cpu: 100m
        memory: 100Mi 

2) BestEffort

如果Pod中所有容器都未定义资源配置(Requests和Limits都未定义),那么该Pod的QoS级别就是BestEffort。

例如下面这个Pod定义

containers:
  name: foo
    resources:
  name: bar
    resources:

3) Burstable
       当一个Pod既不为Guaranteed级别,也不为BestEffort级别时,该Pod的QoS级别就是Burstable。Burstable级别的Pod包括两种情况。第1种情况:Pod中的一部分容器在一种或多种资源类型的资源配置中定义了Requests值和Limits值(都不为0),且Requests值小于Limits值;第2种情况:Pod中的一部分容器未定义资源配置(Requests和Limits都未定义)。注意:在容器未定义Limits时,Limits值默认等于节点资源容量的上限。

下面几个例子中的Pod的QoS等级都是Burstable。
(1)容器foo的CPU Requests不等于Limits:

containers:
  name: foo
    resources:
      requests:
        cpu: 5m
        memory: 1Gi
      limits:
        cpu: 10m
        memory: 1Gi
  name: bar
    resources:
      requests:
        cpu: 5m
        memory: 1Gi
      limits:
        cpu: 100m
        memory: 100Mi 

(2)容器bar未定义资源配置而容器foo定义了资源配置:

containers:
  name: foo
    resources:
      requests:
        cpu: 10m
        memory: 1Gi
      limits:
        cpu: 10m
        memory: 1Gi
  name: bar

(3)容器foo未定义CPU,而容器bar未定义内存:

containers:
  name: foo
    resources:
      limits:
        memory: 1Gi
  name: bar
    resources:
      limits:
        cpu: 100m

(4)容器bar未定义资源配置,而容器foo未定义Limits值:

containers:
  name: foo
    resources:
      requests:
        cpu: 5m
        memory: 1Gi
  name: bar

4) Kubernetes Qos的工作特定

Pod的CPU Requests无法得到满足(比如节点的系统级任务占用过多的CPU导致无法分配足够的CPU给容器使用)时,容器得到的CPU会被压缩限流。

由于内存是不可压缩的资源,所以针对内存资源紧缺的情况,会按照以下逻辑进行处理。

(1)BestEffort Pod的优先级最低,在这类Pod中运行的进程会在系统内存紧缺时被第一优先杀掉。当然,从另外一个角度来看,BestEffort Pod由于没有设置资源Limits,所以在资源充足时,它们可以充分使用所有的闲置资源。

(2)Burstable Pod的优先级居中,这类Pod初始时会分配较少的可靠资源,但可以按需申请更多的资源。当然,如果整个系统内存紧缺,又没有BestEffort容器可以被杀掉以释放资源,那么这类Pod中的进程可能会被杀掉。

(3)Guaranteed Pod的优先级最高,而且一般情况下这类Pod只要不超过其资源Limits的限制就不会被杀掉。当然,如果整个系统内存紧缺,又没有其他更低优先级的容器可以被杀掉以释放资源,那么这类Pod中的进程也可能会被杀掉。

5)OOM计分系统

OOM(Out Of Memory)计分规则包括如下内容。
◎ OOM计分的计算方法为:计算进程使用内存在系统中占的百分比,取其中不含百分号的数值,再乘以10的结果,这个结果是进程OOM的基础分;将进程OOM基础分的分值再加上这个进程的OOM分数调整值OOM_SCORE_ADJ的值,作为进程OOM的最终分值(除root启动的进程外)。在系统发生OOM时,OOM Killer会优先杀掉OOM计分更高的进程。
◎ 进程的OOM计分的基本分数值范围是0~1000,如果A进程的调整值OOM_SCORE_ADJ减去B进程的调整值的结果大于1000,那么A进程的OOM计分最终值必然大于B进程,会优先杀掉A进程。
◎ 不论调整OOM_SCORE_ADJ值为多少,任何进程的最终分值范围也是0~1000。

在Kubernetes,不同QoS的OOM积分调整规则如下:

◎ BestEffort Pod设置OOM_SCORE_ADJ调整值为1000,因此BestEffort Pod中容器里所有进程的OOM最终分肯定是1000。
◎ Guaranteed Pod设置OOM_SCORE_ADJ调整值为-998,因此Guaranteed Pod中容器里所有进程的OOM最终分一般是0或者1(因为基础分不可能是1000)。
◎ Burstable Pod规则分情况说明:如果Burstable Pod的内存Requests超过了系统可用内存的99.8%,那么这个Pod的OOM_SCORE_ADJ调整值固定为2;否则,设置OOM_SCORE_ADJ调整值为1000-10×(% of memory requested);如果内存Requests为0,那么OOM_SCORE_ADJ调整值固定为999。这样的规则能确保OOM_SCORE_ADJ调整值的范围为2~999,而Burstable Pod中所有进程的OOM最终分数范围为2~1000。Burstable Pod进程的OOM最终分数始终大于Guaranteed Pod的进程得分,因此它们会被优先杀掉。如果一个Burstable Pod使用的内存比它的内存Requests少,那么可以肯定的是它的所有进程的OOM最终分数会小于1000,此时能确保它的优先级高于BestEffort Pod。如果在一个Burstable Pod的某个容器中某个进程使用的内存比容器的Requests值高,那么这个进程的OOM最终分数会是1000,否则它的OOM最终分会小于1000。假设在下面的容器中有一个占用内存非常大的进程,那么当一个使用内存超过其Requests的Burstable Pod与另外一个使用内存少于其Requests的Burstable Pod发生内存竞争冲突时,前者的进程会被系统杀掉。如果在一个Burstable Pod内部有多个进程的多个容器发生内存竞争冲突,那么此时OOM评分只能作为参考,不能保证完全按照资源配置的定义来执行OOM Kill。

 

OOM还有一些特殊的记分规则,如下所述:

◎ kubelet进程和Docker进程的调整值OOM_SCORE_ADJ为-998。
◎ 如果配置进程调整值OOM_SCORE_ADJ为-999,那么这类进程不会被OOM Killer杀掉。

6)QoS的演进

目前Kubernetes基于QoS的超用机制日趋完善,但还有一些问题需要解决。

(1)内存Swap的支持。当前的QoS策略都是假定主机不启用内存Swap。如果主机启用了Swap,那么上面的QoS策略可能会失效。举例说明:两个Guaranteed Pod都刚好达到了内存Limits,那么由于内存Swap机制,它们还可以继续申请使用更多的内存。如果Swap空间不足,最终这两个Pod中的进程就可能会被杀掉。由于Kubernetes和Docker尚不支持内存Swap空间的隔离机制,所以这一功能暂时还未实现。

(2)更丰富的QoS策略。当前的QoS策略都是基于Pod的资源配置(Requests和Limits)来定义的,而资源配置本身又承担着对Pod资源管理和限制的功能。两种不同维度的功能使用同一个参数来配置,可能会导致某些复杂需求无法满足,比如当前Kubernetes无法支持弹性的、高优先级的Pod。自定义QoS优先级能提供更大的灵活性,完美地实现各类需求,但同时会引入更高的复杂性,而且过于灵活的设置会给予用户过高的权限,对系统管理也提出了更大的挑战

小结:

        本节内容到此结束,谢谢大家的浏览。

        See You~

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐