# 深入理解Resource QoS

作者平时也得工作和干活~,尽量在有空的时候不断的去更新该博客...

如果有相关问题或反馈,可以加作者微信(微信号:SPE3SRU3STAY)


学习中的疑问:

Kubernetes是如何根据Pod的Requests配置和Limits配置,来实现针对Pod的资源服务质量控制(QoS)的呢,这个过程中都发生了什么?

# 什么是Requests和Limits

在Kubernetes的QoS体系中,需要保证高可靠的Pod可以申请可靠资源,而一些非高可靠性的Pod可以申请可靠性较低不可靠的资源。

Pod容器资源的配置分为Requests和Limits。

  • Requests:是Kubernetes调度时能为容器提供可保证的资源量(最低保障)。
  • Limits:是系统允许容器运行时可能使用的资源量的上限(最高上限)。

小提示:

Pod级别的资源配置是通过计算Pod内所有容器资源配置的总和得出来的。

# 超售机制

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

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

  • 如果Pod的Requests值小于Limits值,那么该Pod获得资源可以分为两个部分关注:

    • 完全可靠的资源,资源量的大小等于Requests值;

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

通过这种机制,Kubernetes可以实现节点资源的超售(Over Subscription),例如:

点击查看示例
  • 在CPU完全充足的情况下,某机器共有32GiB内存可供容器使用。
  • 将一个容器的Requests值为1GiB、Limits值为2GiB。
  • 那么在该机器上最多可以同时运行32个容器,每个容器最多可以使用2GiB内存。
  • 如果这些容器的内存使用峰值能错开,那么所有容器都可以正常运行。

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

# 限制机制

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

根据上文的内容可知,容器的资源配置满足以下两个条件:

核心条件:

  • Requests <= 节点可用资源
  • Requests <= Limits

Kubernetes根据Pod配置的Requests值来调度Pod,Pod在成功调度之后会得到Requests值定义的资源来运行;如果Pod所在机器上的资源有空余,则Pod可以申请更多的资源,最多不能超过Limits的值。

下面看一下Requests和Limits针对不同计算资源类型的限制机制的有什么差异,这种差异主要取决于计算资源类型是可压缩资源还是不可压缩资源

# 可压缩资源

Kubernetes目前支持的可压缩资源是CPU。

Pod可以得到Requests配置的CPU使用量,而使用超过Requests值的部分取决于系统的负载和调度

不过由于目前Kubernetes和Docker的CPU隔离机制都是在容器级别起作用的,所以Pod级别的资源配置并不能完全得到保障;Pod级别的cgroup正在紧锣密鼓的开发中,如果将来引入,就可以确保Pod级别的资源配置准确运行。

空闲的CPU资源按照容器Requests值的比例分配。举例说明:

  • 容器A的CPU配置为Requests 1Limits 10
  • 容器B的CPU配置为Requests 2Limits 8
点击查看示例

A和B同时运行在一个节点上,初始状态下容器的可用CPU为3 cores,那么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。
~
如果A的CPU用量超过了Limits 10的配置用量,那么cgroups会对Pod中容器的CPU用量进行限流(Throttled)。
~
如果A的CPU用量没有配置Limits 10的限制,那么A回去尝试抢占所有空闲的CPU资源(Kubernetes从1.2版本开始,默认开启--cpu-cfs-quota参数,因此在默认情况下必须配置Limits)。

# 不可压缩资源

Kubernetes目前支持的不可压缩资源是内存。

Pod可以得到在Requests中配置的内存。

如果Pod的内存用量小于它的Requests的配置,那么这个Pod可以正常运行(除非出现炒作系统级别内存不足等严重问题);如果Pod的内存容量超过了它的Requests配置,那么这个Pod有可能被Kubernetes “杀掉”。

点击查看示例

比如PodA使用了超过Requests而不到Limits的内存量,此时同一机器上另一个PodB之前只使用了远少于自己的Requests值的内存,此时程序压力增大,PodB向系统申请的总量不超过自己的Requests值的内存,那么Kubernetes可能会直接 “杀掉” PodA。
~
另外一种情况是PodA使用了超过Requests而不到Limits的内存,此时Kubernetes将一个新的Pod调度到这台机器上,新的Pod需要使用内存,而只有PodA使用了超过了自己Requests值的内存,那么Kubernetes也可能会 “杀掉” PodA来释放内存资源。

如果有Pod的内存用量超过了它Limits设置,那么操作系统内核会 “杀掉” Pod所有容器的所有进程中内存使用量最多的一个,直到内存不超过Limits时为止。

# 对调度策略的影响

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

# 服务质量等级(QoS Classes)

在一个超用(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级别:

三大分类:

  • Guaranteed

如果Pod中的所有容器对所有资源类型都定义了Requests和Limits,并且所有容器的Requests和Limits的值都相等(且都不为0),那么该Pod的QoS级别就是Guaranteed。

  • BestEffort

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

  • Burstable

当一个Pod即不为Guaranteed级别,也不为BestEffort级别时,该Pod的QoS级别就是Burstable。Burstable级别的Pod涉及两种情况:

  • 第一种情况:Pod中的一部分容器在一种或多种资源类型的资源配置中定义了Requests值和Limits值(都不为0),且Requests值小于Limits值。
    ~
  • 第二种情况:Pod中的一部分容器未定义资源配置(Requests和Limits都未定义)。注意:在容器未定义Limits时,Limits值默认等于节点资源容量的上限。

# 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中的进程也可能会被 “杀掉”。

# OOM计分规则

OOM(Out Of Memory)积分规则包括如下内容:

  • OOM积分的计算方法:

计算方法

  • 计算进程所使用的的内存在系统中所占的百分比,取其中不含百分号的数值,再乘以10,该结果是进程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。

QoS等级 oom_score_adj
Guaranteed -998
Burstable min(max(1,1000-(1000*memoryRequestBytes)/machineMemoryCapacityBytes),999)
BestEffort 1000

对表中的内容说明如下:

点击查看说明详情
  • BestEffort Pod设置OOM_SCORE_ADJ调整值为1000,因此BestEffort Pod中容器里所有进程的OOM最终分肯定是1000.

  • Guaranteed Pod设置OOM_SCORT_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最终分数始终大于Cuaranteed 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_SOCRE_ADJ始终为-998。
    • 如果配置进程调整值OOM_SCORE_ADJ为-999,那么这类进程不会被OOM Killer“杀掉”。

# QoS的演进

目前Kubernetes基于QoS的潮涌机制日趋完善,但还有一些限制。

不足之处

  • 不支持内存Swap,当前的QoS策略都假定了主机不启动内存swap,Kubernetes从1.8版本开始默认关闭Swap特性,如果主机启动了Swap功能,上面的QoS策略就可能失效。
  • 缺乏更丰富的QoS策略,当前的QoS策略都是基于Pod的(Requests和Limits)来定义的。社区有考虑过引入自定义QoS的功能,但是这一块目前还不太明朗,等待后续持续关注了解清楚后再进行内容的追加。

(到此为止,Resource QoS 原理分析完毕)








# 帮助作者改进文档

如果您喜欢这篇文档,想让它变得更好,您可以: