K8S中的QOS有什么用?

按网上的介绍: “Kubernetes做为目前主流的容器集群管理平台,需要整体统筹平台资源使用情况、公平合理的将资源分配给相关pod容器使用,并且要保证容器生命周期内有足够的资源来保证其运行。 与此同时,由于资源发放的独占性,即资源已经分配给了某容器,同样的资源不会在分配给其他容器,对于资源利用率相对较低的容器来说,占用资源却没有实际使用(比如CPU、内存)造成了严重的资源浪费,Kubernetes需从优先级与公平性等角度提高资源的利用率。为了实现资源被有效调度和分配的同时提高资源利用率,Kubernetes针对不同服务质量的预期,通过QoS(Quality of Service)来对pod进行服务质量管理,提供了个采用requests和limits两种类型对资源进行分配和使用限制。对于一个pod来说,服务质量体现在两个为2个具体的指标: CPU与内存。实际过程中,当NODE节点上内存资源紧张时,kubernetes会根据预先设置的不同QoS类别进行相应处理”

再加上Qos这个词,看起来是k8s用来做服务质量保证的一个玩意,那我们具体看下QOS实际是怎么实现的。

Qos是Pod上的概念,有3种:

  1. Guaranteed: request=limit, Pod中所有容器都必须统一设置limits,并且设置参数都一致,如果有一个容器要设置requests,那么所有容器都要设置,并设置参数同limits一致,那么这个pod的QoS就是Guaranteed级别。注:如果一个容器只指明limit而未设定request,则request的值等于limit值
  2. Burstable: requset≠limit, Pod中只要有一个容器的requests和limits的设置不相同,该Pod的QoS即为Burstable
  3. Best-Effort: request & limit not set, 如果对于全部的resources来说requests与limits均未设置,该Pod的QoS即为Best-Effort

回顾下背景知识
Cgroup中CPU资源的限制原理:

  1. 软限制:cgroup目录下cpu.shares 文件中的值 1024表示1个核心 4096则是4个核心,在所有容器都跑满的情况下,按cpu.shares 的比例分CPU时间片,如果没有用满,则可以用超设置的核心;
  2. 硬限制:
     #cpu.cfs_quota_us 就是在这期间内可使用的 cpu 时间,默认 -1,即无限制
     #cpu.cfs_period_us 时间周期,默认为 100000,即百毫秒
           cpu.cfs_quota_us =  800000;
           cpu.cfs_period_us = 100000;  
    以上表示8核,在设置了cfs_quota_us的情况下,容器中的进程至多只能用到8核
    

Pod的Request和Limit:

  1. Request: 对应cpu.shares的值(做相应单位转换)
  2. Limit: 对应cpu.cfs_quota_us的值(做相应单位转换)

不过这个本来就是Pod上的设置,跟Qos本身没有太大的关系,这里唯一的体现是在cgroup目录下的文件路径不一样,/sys/fs/cgroup/cpu/kubepods/ 目录下会有besteffort,burstable,guaranteed 3个目录里面放相应Pod的Cgroup文件。

再实际去看下Qos在k8s中代码相关的逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func GetContainerOOMScoreAdjust(pod *v1.Pod, container *v1.Container, memoryCapacity int64) int {
switch GetPodQOS(pod) {
case v1.PodQOSGuaranteed:
return guaranteedOOMScoreAdj
case v1.PodQOSBestEffort:
return besteffortOOMScoreAdj
}
memoryRequest := container.Resources.Requests.Memory().Value()
oomScoreAdjust := 1000 - (1000*memoryRequest)/memoryCapacity

if int(oomScoreAdjust) < (1000 + guaranteedOOMScoreAdj) {
return (1000 + guaranteedOOMScoreAdj)
}
if int(oomScoreAdjust) == besteffortOOMScoreAdj {
return int(oomScoreAdjust - 1)
}
return int(oomScoreAdjust)
}

只有这里用于计算每个Pod设置的OOMScoreAdj值:
对于Guaranteed级别的pod,OOM_ADJ参数设置成了-998,对于BestEffort级别的pod,OOM_ADJ参数设置成了1000,对于Burstable级别的POD,OOM_ADJ参数取值从2到999(根据内存申请量占总内存的比例算的一个值)。对于kuberntes保留资源,比如kubelet,docker,OOM_ADJ参数设置成了-999,表示不会被OOM kill掉。(OOM_ADJ参数设置的越大,通过OOM_ADJ参数计算出来OOM分数越高,当出现资源竞争时会越容易被系统kill掉。)

不过这里设置的这个值,只有在整个Node用完了全部内存时才会触发,但是通常情况下容器里面用超都会被自己的Cgroup的内存限制住,OOM掉。所以感觉也没什么明显的用处。

另外一个是kubelet上的preempt逻辑,这里比较诡,是kubelet重启后,发现有个Pod是自己的,但处于pending状态,然后node上会走调度器的逻辑把这个Pod GeneralPredicates下,但也是失败(正常这种调度就过不来,猜测可能是多调度器导致的),并且发现pending的原因是资源不满足类的,就看下有没有别的pod可以踢掉换成这个Pod,好诡异的逻辑。没碰到过。需要再细看下。而且这种踢pod的逻辑正常需要关掉比较好。

还有个东西在高版本的k8s代码中和Qos相关,高版本中如果开启了cpuManager功能,会针对Guaranteed的Pod进行绑核的操作。

总结下 QOS的功能 1. oom_score_adj 2. cpumanager绑定核心 3.kubelet上preempt 删pod排序(不是调度器里面那个)