资源需求与资源限制

在K8S上,可由容器或Pod请求或消费的“计算资源”是指CPU和内存(RAM),这也是目前仅有的受支持的两种类型。相比较来说,CPU属于可压缩(compressible)型资源,即资源额度可按需收缩,而内存(当前)则是不可压缩型资源,对其执行收缩操作可能会导致某种程度的问题 目前来说,资源隔离尚且属于容器级别,CPU和内存资源的配置需要在Pod中的容器上进行,每种资源均可由”requests”属性定义其请求的确保可用值,即容器运行可能用不到这些额度的资源,但用到时必须确保有如此多的资源可用,而”limits”属性则用于限制资源可用的最大值,即硬限制。不过为了方便表述,人们通常依旧把资源配置称作Pod资源的请求和限制,只不过它是指Pod内所有容器上某种类型资源的请求和限制的总和 在K8S上,1个单位的CPU相当于虚拟机上的1颗虚拟CPU或物理机上的一个超线程或称为一个逻辑CPU,它支持分数计量方式,一个核心(1 core)相当于1000个微核心(millicores),因此500m相当于是0.5个核心,即二分之一个核心。内存的计量方式与日常的使用方式相同,默认单位是字节,也可以使用E、P、T、G、M和K作为单位后缀,或Ei、Pi、Ti、Gi、Mi和Ki形式的单位为后缀 为容器指定资源需求后,调度器就能更好的判断出来将容器调度到哪个节点上。如果还为容器指定了资源限制,K8S就可用按照指定的方式来处理节点上的资源竞争 Pod中的每个容器都可以指定以下的一个或者多个值

  • 限制内存资源可用的最大值:spec.containers.resources.limits.memory
  • 限制CPU资源可用的最大值: spec.containers.resources.limits.cpu
  • 设置CPU资源的可用值: spec.containers.resources.requests.cpu
  • 设置内存资源的可用值: spec.containers.resources.requests.memory
    具有资源请求的Pod如何调度
  • 当你创建一个Pod时,K8S调度程序将为Pod选择一个节点。每个节点具有每种资源类型的最大容量:可分为Pod提供的CPU和内存量。调度程序确保对于每种资源类型,调度的容器的资源请求的总和小于节点的容量。请注意,尽管节点上的实际内存或CPU资源使用量非常低,但是如果容量检查失败,则调度程序依然拒绝在该节点上放置Pod。当资源使用量稍后增加时,例如在请求率的每日峰值期间,这也可以防止节点上的资源短缺

资源需求

在下面的示例中,自主式Pod要求为stress容器确保128Mi的内存及五分之一个CPU核心(200m)资源可用,它运行stress-ng镜像启动一个进程(-m 1)进行内存性能压力测试,满载测试时它也会尽可能多得占用CPU资源,另外再启动一个专用的CPU压力测试过程(-c 1)。strss-ng是一个多功能系统压力测试工具,master/worker模型,master为主进程,负责生成和控制子进程,worker是负责执行各类特定测试的子进程,例如测试CPU的子进程,以及测试RAM的子进程等 因为我们这边没有stress-ng的镜像,所以还是直接使用之前的镜像来创建,通过下面的yaml文件可以看到加了一个字段resources,并且定义了requests

  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. annotations:
  5. created-by: cluster admin
  6. name: test-nodeselector1
  7. labels:
  8. env: pre
  9. spec:
  10. containers:
  11. - name: busybox
  12. image: registry.cn-hangzhou.aliyuncs.com/jiangyida/busybox:0.1
  13. ports:
  14. - name: http
  15. containerPort: 80
  16. protocol: TCP
  17. # livenessProbe:
  18. # tcpSocket:
  19. # port: 80
  20. # timeoutSeconds: 5
  21. # periodSeconds: 10
  22. # initialDelaySeconds: 10
  23. # failureThreshold: 3
  24. readinessProbe:
  25. exec:
  26. command: ["test","-e","/data/html/test.html"]
  27. initialDelaySeconds: 5
  28. periodSeconds: 5
  29. resources:
  30. requests:
  31. memory: "200Mi"
  32. cpu: "200m"
  33. lifecycle:
  34. postStart:
  35. exec:
  36. command: ["/bin/sh","-c","echo 'lifecycle hooks handler' > /data/html/test.html"]
  37. nodeSelector:
  38. disktype: ssd

shell操作

  1. 1.删除之前的Pod
  2. [root@k8s-master01 nginx]# kubectl delete -f nodeSelector.yaml
  3. pod "test-nodeselector1" deleted
  4. [root@k8s-master01 nginx]#
  5. 2.重新创建Pod
  6. [root@k8s-master01 nginx]# kubectl apply -f nodeSelector.yaml
  7. pod/test-nodeselector1 created
  8. [root@k8s-master01 nginx]#
  9. 3.查看资源使用 `kubectl exec `
  10. [root@k8s-master01 nginx]# kubectl exec test-nodeselector1 -- top
  11. Mem: 1842780K used, 187696K free, 98116K shrd, 2104K buff, 1160132K cached
  12. CPU: 0.4% usr 1.7% sys 0.0% nic 97.7% idle 0.0% io 0.0% irq 0.1% sirq
  13. Load average: 0.04 0.16 0.14 3/285 190
  14. PID PPID USER STAT VSZ %VSZ CPU %CPU COMMAND
  15. 181 0 root R 1300 0.0 0 0.0 top
  16. 1 0 root S 1292 0.0 0 0.0 /bin/httpd -f -h /data/html
  17. [root@k8s-master01 nginx]#

top命令给出的结果显示,CPU分配为0.4核,对于压缩型的资源CPU来说,未定义其请求用量以确保其最小的可用资源时,它可能会被其他的Pod资源压缩至极低的水平,甚至会达到Pod不能够被调度运行的境地。而对于非压缩型资源来说,内存资源在任何原因导致的紧缺情形下都有可能导致相关的进程被杀死。因此,在K8S系统上运行关键型业务相关的Pod时必须使用requests属性为容器定义资源的确保可用量 集群中的每个节点有拥有定量的CPU和内存资源,调度Pod时,仅那些被请求资源的余量可容纳当前被调度的Pod的请求量的节点才可以被作为目标节点。也就是说,K8S的调度器会根据容器的requests属性中定义的资源需求量来判定仅哪些节点可接收运行相关的Pod资源,而对于一个节点的资源来说,每运行一个Pod对象,其requests中定义的请求量都需要被预留,直到被所有Pod瓜分完毕为止

资源限制

容器资源的限制仅能达到为其保证可用的最少资源量的目的,它并不会限制容器的可用资源上限,因此对因应用自身存在Bug等多种原因而导致的系统资源被长时间占用的情况则无计可施,这就需要通过spec.containers.reousces.limits属性为容器定义资源的最大可用量。资源分配时,可压缩型资源CPU的控制阀可自由调节,容器进程无法获得超出其CPU配额的可用实际。不过,如果进程申请分配超出其limits属性定义的硬限制的内存资源时,它将被OOM killer杀死,不过,随后可能会被其控制器进程所重启,例如,容器进程的Pod对象会被杀死并重启(重启策略为Always或OnFailure时),或者是容器进程的子进程被其父进程所重启,下面的清单中定义了最大可用资源,在spec.containers.resource字段增加limits字段表示定义其最大可使用资源

  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. annotations:
  5. created-by: cluster admin
  6. name: test-nodeselector1
  7. labels:
  8. env: pre
  9. spec:
  10. containers:
  11. - name: busybox
  12. image: registry.cn-hangzhou.aliyuncs.com/jiangyida/busybox:0.1
  13. ports:
  14. - name: http
  15. containerPort: 80
  16. protocol: TCP
  17. livenessProbe:
  18. tcpSocket:
  19. port: 80
  20. timeoutSeconds: 5
  21. periodSeconds: 10
  22. initialDelaySeconds: 10
  23. failureThreshold: 3
  24. readinessProbe:
  25. exec:
  26. command: ["test","-e","/data/html/test.html"]
  27. initialDelaySeconds: 5
  28. periodSeconds: 5
  29. resources:
  30. requests:
  31. memory: "200Mi"
  32. cpu: "200m"
  33. limits:
  34. memory: "512Mi"
  35. cpu: "1000m"
  36. lifecycle:
  37. postStart:
  38. exec:
  39. command: ["/bin/sh","-c","echo 'lifecycle hooks handler' > /data/html/test.html"]
  40. nodeSelector:
  41. disktype: ssd

shell操作

  1. 1.先删除之前的Pod
  2. [root@k8s-master01 nginx]# kubectl delete -f nodeSelector.yaml
  3. pod "test-nodeselector1" deleted
  4. [root@k8s-master01 nginx]#
  5. 2.再重新创建Pod
  6. [root@k8s-master01 nginx]# kubectl apply -f nodeSelector.yaml
  7. pod/test-nodeselector1 created
  8. [root@k8s-master01 nginx]#

这个时候,如果该容器使用的资源超过了我们spec.containers.resource.limits字段定义的数值,那么容器必然会被OOM killer而终止,因此,limits属性中的memory设置一个合理值至关重要。与requests不同的是,limits并不会影响Pod的调度结果,也就是说,一个节点上的所有Pod对象的limits数量之和可用大于节点所拥有的资源数量,即支持资源的过载使用。不过,这么一来一旦资源耗尽,尤其是内存资源耗尽,则必然会有容器因 OOM killed而终止 另外需要说明的是,K8S仅会确保Pod能够获得他们请求的CPU时间额度,他们能否获得额外的CPU时间,则取决于其它症状运行的作业对CPU资源的占用情况,例如,对于总数为1000m的CPU资源来说,容器A请求使用200m,容器B请求使用500m,在不超出他们各自的最大限额的前提下,余下的300m在双方都需要时会以2:5(200m:500m)的方式进行配置

容器的可见资源

于容器中运行top等命令观察资源可用量时,即便定义了requests和limits属性,虽然其可用性受限于这两个属性的定义,但容器中可见的资源量依然是节点级别的可用总量,例如上面创建的Pod中定义的

  1. resources:
  2. requests:
  3. memory: "200Mi"
  4. cpu: "200m"
  5. limits:
  6. memory: "512Mi"
  7. cpu: "1000m"

使用kubectl exec命令运行一个cat程序来查看容器的CPU资源和内存资源总量

  1. [root@k8s-master01 nginx]# kubectl exec test-nodeselector1 -- cat /proc/cpuinfo |grep -c ^processor
  2. 2
  3. [root@k8s-master01 nginx]#
  4. [root@k8s-master01 nginx]# kubectl exec test-nodeselector1 -- cat /proc/meminfo |grep ^MemTotal
  5. MemTotal: 2030476 kB
  6. [root@k8s-master01 nginx]#

命令结果显示可用内存资源总量为为2G,CPU核心数为2个,这是节点的资源数量,而非容器的limits所定义的512M和1000m。其实这种结果不仅仅使得其查看命令的显示结果看起来有些奇怪,而且对有些容器应用的配置也会带来不小的负面影响 较为典型的是Pod中运行java应用程序时,若未使用-Xmx选项指定jvm的堆内存可用总量,它默认会设置为主机总内存量的一个空间比例,这会导致容器中的应用程序申请内存资源时将会达到上限而转为OOMkilled。另外,即便使用了-Xmx选项设置其堆内存上限,但它对于非堆内存的可用空间不会产生任何限制作用,但它对于非堆内存的可用空间不会产生任何限制作用,结果是仍然存在达到容器内存资源上限的可能性 另一个颇具代表性的场景是Pod中运行的Nginx应用,在配置参数worker_processes的值为auto时,主进程会创建与Pod中能够访问的CPU核心数相同数量的worker进程。若Pod的实际可用CPU核心远低于主机级别的数量时,那么这种设置在较大的并发访问符合下会导致严重的资源竞争,并将带来更多的内存资源消耗。一个较为妥当的解决方案是使用downward API将limits定义的资源暴漏给容器

Pod的服务质量类别

K8S允许节点资源对limits的过载使用,这意味着节点无法同时满足其上的所有Pod对象以资源满载的方式运行。于是,在内存资源紧缺时,应该以何种次序先后终止哪些Pod对象,K8S无法对此自行做出决策,它需要借助于Pod对象的优先级完成判定。根据Pod对象的requests和limits属性,K8S将Pod对象归类到BestEffort,Burstable和Guaranteed三个服务质量(Quality of Service,Qos)类别下,具体说明如下

  • Guaranteed:每个容器都为CPU资源设置了具有相同值的requests和limits属性,以及每个容器都为内存资源设置了具有相同值的requests和limits属性的Pod资源会自动归属于此类别,这类Pod资源具有最高优先级
  • Burstable:至少有一个容器设置了CPU或内存资源的requests属性,但不满足Guaranteed类别要求的Pod资源将自动归属此类别,它们具有中等优先级
  • BestEffort:未为任何一个容器设置requests或limits属性的Pod资源将自动归属于此类别,它们的优先级为最低级别
    内存资源紧缺时,BestEffort类别的容器将首当其冲的被终止,因为系统不为其提供任何级别的资源保证,但换来的好处是,它们能够在可用时做到尽可能多的占用资源。若已然不存在任何BestEffort类别的容器,则解下来是有着中等优先级的Burstable类别的Pod被终止,Guaranteed类别的容器拥有最高优先级,它们不会被杀死,除非其内存资源需求超限或者OOM时没有其他更低优先级的Pod资源存在
    每个运行状态容器都有其OOM得分,得分越高越会被优先杀死,OOM得分主要根据两个维度进行计算:由Qos类别继承而来的默认分值和容器的可用内存资源比例。同等类别的Pod资源的默认分支相同
    需要特别说的是,OOM是内存耗尽时的处理机制,它们与可压缩资源CPU无关,因此CPU资源的需求无法得到保证时,Pod仅仅是暂时获取不到相应的资源而已

总结

之前主要介绍了Pod资源的基础概念,Pod的基础管理操作,如何定义和管理容器,资源标签和标签选择器,资源注解等,详细讲解了Pod生命周期中的事件,容器的存活性探测和就绪性探测机制等话题

  • Pod就是联系紧密的一组容器,它们共享network、UTS和IPC名称空间及存储卷资源
  • 分布式系统设计主要有Sidecar、ambassador和adapter三种主要模式
  • K8S资源对象的管理操作基本上是由增、删、改、查等操作组成,并且支持陈述式命令,陈述式对象配置和声明式对象配置三种管理模式
  • Pod的核心目标在于运行容器,容器的定制配置常见的包括暴漏端口及传递环境变量等
  • 标签是附加在K8S系统上的键值类型的元数据,而标签选择器是基本等值或集合关系的标签过滤机制;注解类似于标签,但不能被用于标签选择器
  • Pod的生命周期中可能存在多种类型的操作,但运行主容器是其核心任务
  • 存活性探测及就绪性探测是辅助判定给容器状态的重要工具
  • 资源需求及资源限制是管理Pod对象系统资源分配的有效方式