01-资源模型与资源管理

作为一个容器集群编排与管理项目,kubernetes为用户提供基础设施能力,包括:

  • 应用定义和描述部分
  • 对应用的资源管理和调度处理

0.1. 资源模型

Pod是最小的原子调度单位,所有跟调度资源管理相关的属性都应该属于Pod对象的字段。这其中最重要的部分就是Pod的CPU和内存配置,如下所示:

apiVersion: v1
kind: Pod
metadata:
  name: frontend
spec:
  containers:
  - name: db
    image: mysql
    env:
    - name: MYSQL_ROOT_PASSWORD
      value: "password"
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"
  - name: wp
    image: wordpress
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"
  • 可压缩资源(compressible resources):CPU,当可压缩资源不足时,Pod只饥饿,但是不会退出
  • 不可压缩资源(incompressible resources):内存,当不可压缩资源不足时,Pod会因为OOM(out of memory)被内核杀掉

Pod可以由多个Container组成,所以CPU和内存资源的限额,是要配置在每个Container的定义上。这样Pod整体的资源配置,就由这些Container的配置值累加得到。

0.1.1. CPU

kubernetes里为CPU设置的单位是“CPU的个数”,比如,cpu=1指的就是这个Pod的CPU限额是1个CPU,具体的1个CPU在宿主机上如何解释:

  1. 1个CPU核心
  2. 1个vCPU
  3. 1个CPU的超线程 这完全取决于宿主机CPU实现方式。kubernetes只负责保证Pod能够使用到“1个CPU”的计算能力。

kubernets运行将CPU配额设置为分数,比如500m(指的是500millicpu,即0.5个CPU),这样Pod被分配到的就是1个CPU一半的计算能力。

0.1.2. 内存

内存资源的单位是bytes,kubernetes支持使用Ei、Pi、Ti、Gi、Mi、Ki(或者E、P、T、G、M、K)的方式来作为bytes的值。如64Mib。

主要Mib(mebibyte)和MB(megabyte)的区别,1Mi=1024×1024,1M=1000×1000

kubernetes中Pod的CPU和内存资源,实际上分为limits和requests两种情况,如下所示:

spec.containers[].resources.limits.cpu
spec.containers[].resources.limits.memory
spec.containers[].resources.requests.cpu
spec.containers[].resources.requests.memory

两者的区别:

  1. 在调度时,kube-scheduler只会按照requests的值进行计算
  2. 在真正设置Cgroups限制的时候,kubelet则会按照limits的值进行设置
pod字段Cgroups属性值描述默认值
requests.cpu=250mcpu.shares=(250/1000)*1024这样kubernetes就通过cpu.shares完成了对CPU时间的按比例分配cpu.shares默认值是1024
limits.cpu=500mcpu.cfs_quota_us=(500/1000)*100mskubernetes为容器只分配50%CPUcpu.cfs_period_us始终为100ms
limits.memory=128Mimemory.limit_in_bytes=128×1024×1024在调度的时候,调度器只会使用requets.memory=64Mi来进行判断

kubernetes这种对CPU和内存资源限额的设计,实际上参考了Borg论文中对"动态资源边界"的定义。即,容器化作业在提交时所设置的资源边界,并不一定是调度系统所必须严格遵守的,因为在大多数作业使用到的资源其实远小于它所请求的资源限额。基于这种假设,borg在作业被提交后,会主动减小它的资源配额,以便容纳更多的作业、提升资源利用率。当作业资源使用量增加到一定阈值后,通过快速恢复过程,还原作业原始的资源配额,防止出现异常情况。

kubernetes的requests和limits是上述思想的简化版:用户在提交Pod时,可以声明一个相对较小的requests值供调度器使用,而kubernetes真正给容器Cgroups的则是相对较大的limits值。这与borg的思路是相通的。

0.2. QoS模型

在kubernetes中,不同的requests和limits的设置方式,会将这个Pod划分到不同的QoS级别中。

0.2.1. Guaranteed

当Pod里的每个Container都同时设置了requests和limits,并且requests和limits值相等时,这个Pod就属于Guaranteed类别,如下所示:

apiVersion: v1
kind: Pod
metadata:
  name: qos-demo
  namespace: qos-example
spec:
  containers:
  - name: qos-demo-ctr
    image: nginx
    resources:
      limits:
        memory: "200Mi"
        cpu: "700m"
      requests:
        memory: "200Mi"
        cpu: "700m"

# 这个Pod创建之后,它的QoSClass字段被kubernetes自动设置为Guaranteed

需要注意的是,Pod仅设置了limits没有设置requests的时候,kubernetes会自动为它设置与limits相通的requests值,因此也属于Guaranteed类别。

0.2.2. Burstable

当Pod不属于Guaranteed类别,但是至少有一个Container设置了requests,那么Pod就会被化为Burstable类别,如下所示:

apiVersion: v1
kind: Pod
metadata:
  name: qos-demo-2
  namespace: qos-example
spec:
  containers:
  - name: qos-demo-2-ctr
    image: nginx
    resources:
      limits
        memory: "200Mi"
      requests:
        memory: "100Mi"

0.2.3. BestEffort

如果一个Pod既没有设置requests也没有设置limits,那就属于BestEffort类别,如下所示:

apiVersion: v1
kind: Pod
metadata:
  name: qos-demo-3
  namespace: qos-example
spec:
  containers:
  - name: qos-demo-3-ctr
    image: nginx

QoS的划分主要的应用场景是,当宿主机资源紧张的时候,kubelet对Pod进行Eviction(即资源回收)时候需要用到

当kubernetes所管理的宿主机上不可压缩资源短缺时,就有可能触发Eviction。如可用内存memory.available)、可用宿主机磁盘空间nodefs.available),以及容器运行时镜像宿主机空间imagefs.available)等。

目前,kubernetes设置的Eviction的默认阈值如下:

memory.available<100Mi
nodefs.available<10%
nodefs.inodesFree<5%
imagefs.available<15%

# 上述各个触发条件在kubelet里都是可配置的

kubelet --eviction-hard=imagefs.available<10%,memory.available<500Mi, \
                        nodefs.available<5%,nodefs.inodesFree<5% \
        --eviction-soft=imagefs.available<30%,nodefs.available<10% \
        --eviction-soft-grace-period=imagefs.available=2m,nodefs.available=2m \
        --eviction-max-pod-grace-period=600

# 在这个配置中,可用看到Eviction在kubernetes里其实分为soft和hard两种模式
  • soft Eviction运行为Eviction过程设置一端“优雅时间”,比如上述例子中的imagefs.available=2m就意味着imagefs不足的阈值达到2分钟之后,kubelet才会开始Eviction的过程
  • hard Eviction过程就会在阈值到达之后立刻开始

kubernetes计算Eviction阈值的数据来源,主要依赖于从Cgroups读取的值,以及使用cAdvisor监控到的数据。

当宿主机上的Eviction阈值达到后,就会进入MemoryPressure或者DiskPressure状态(此时给node打上污点),从而避免新的Pod被调度到这台宿主机上。

而当Eviction发生的时候,kubelet具体会挑选哪些Pod进行删除操作,就需要参考这些Pod的QoS类别:

  • 首当其冲的是BestEffect类别的Pod
  • 其次是Burstable类别,并且发生“饥饿”的资源使用量已经超出requests的那些Pod
  • 最后是Guaranteed类别:
    • 并且kubernetes会保证只有当Guaranteed类别的Pod的资源用量超过limits的限制
    • 或者宿主机本身处于MemoryPressure状态时,Guaranteed的Pod才可能被选中进行Eviction操作

对于同QoS类别的Pod,kubernetes会根据Pod的优先级进行排序和选择。

0.3. cpuset

在使用容器的时候,通过设置cpuset把容器绑定到某个CPU的内核上,而不是像cpuset那样共享CPU计算能力。这种情况下,由于操作系统在CPU之间进行上下文切换的次数大大减少,容器里应用的性能会大幅提升。事实上,cpuset方式是生产环境中部署在线类型(long running task)的Pod时,非常有用的一种方式

如何在kubernetes中实现这样的操作?

  1. pod必须是Guaranteed的QoS类型
  2. 将Pod的CPU资源的requests和limits设置为同一个相等的整数值即可。

如下例子所示:

spec:
  containers:
  - name: nginx
    image: nginx
    resources:
      limits:
        memory: "200Mi"
        cpu: "2"
      requests:
        memory: "200Mi"
        cpu: "2"

这时候,该Pod就会被绑定在2个独占的CPU核上,具体是哪两个CPU核由kubelet分配。

0.4. 总结

DaemonSet的Pod都设置为Guaranteed的QoS类型,否则一旦DaemonSet的Pod被回收,它又会立即在原宿主机上被重建出来,这就使得前面资源回收的动作,完全没有意义了。

上次修改: 14 April 2020