天天看點

Kubernetes Resource QoS Classes介紹

基本概念

Kubernetes根據Pod中Containers Resource的request和limit的值來定義Pod的QoS Class。其中,指定容器request,代表系統確定能夠提供的資源下限值。指定容器limit,代表系統允許提供的資源上限值。

Pods需要保證長期穩定運作需要設定“確定運作的最少資源”,然而pod能夠使用的資源經常是不能確定的。

通常,Kubernetes通過設定request和limit的值來指定超賣比例,進而提升資源使用率。K8S的排程基于request,而不是limit。Borg通過使用“non-guranteed”的資源,提升了20%的資源使用率。

在一個資源被“超賣”的系統(總limits > machine capacity),容器在資源被耗盡的情況下會被kill。理想情況是那些“不重要”的容器先被kill。

對于每一種Resource都可以将容器分為3中QoS Classes: Guaranteed, Burstable, and Best-Effort,它們的QoS級别依次遞減。K8S底層實際上是通過 limit和request值來實作不同等級QoS的劃分。

  • Guaranteed 如果Pod中所有Container的所有Resource的limit和request都相等且不為0,則這個Pod的QoS Class就是Guaranteed。

注意,如果一個容器隻指明了limit,而未指明request,則表明request的值等于limit的值。

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

 name: bar
 resources:
 limits:
 cpu: 100m
 memory: 100Mi
 requests:
 cpu: 100m
 memory: 100Mi
           
  • Best-Effort 如果Pod中所有容器的所有Resource的request和limit都沒有指派,則這個Pod的QoS Class就是Best-Effort.
Examples: containers:
 name: foo
 resources:
 name: bar
 resources:
           
  • Burstable 除了符合Guaranteed和Best-Effort的場景,其他場景的Pod QoS Class都屬于Burstable。 當limit值未指定時,其有效值其實是對應Node Resource的Capacity。

Examples: 容器bar沒有對Resource進行指定。

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

 name: bar
           

容器foo和bar對不同的Resource進行了指定。

containers:
 name: foo
 resources:
 limits:
 memory: 1Gi

 name: bar
 resources:
 limits:
 cpu: 100m
           

容器foo未指定limit,容器bar未指定request和limit。

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

 name: bar
           

可壓縮/不可壓縮資源的差別

kube-scheduler排程時,是基于Pod的request值進行Node Select完成排程的。Pod和它的所有Container都不允許Consume limit指定的有效值(if have)。

request和limit如何生效,依賴于資源是否是壓縮的

可壓縮資源的保證

  • 目前僅支援CPU。
  • Pods確定可以擷取請求的CPU總量,但并不能獲得額外的CPU時間。這并不能完全確定容器能夠用到設定的資源下限值,因為CPU隔離是容器級别的。之後會引入Pod級别的cgroups資源隔離來解決這個問題。
  • 過量/競争使用CPU資源,會基于CPU request設定。可通過cpu.share來分派不同比例的時間片來了解,如果某個容器A的request 設定為600 milli,容器B設定為300mili , 兩者競争CPU時間時,通過2:1的比例來配置設定。
  • 如果達到Pod CPU資源limit上限,CPU會減速(throttled),而不是kill pod。如果pod沒有設定limit上限,pods可以使用超過CPU limit上限。

不可壓縮資源的保證

  • 目前僅支援記憶體。
  • Pods可以拿到requests設定的記憶體總量。如果某個pod超過memory request值,當其他pod需要記憶體時,這個pod可能被kill掉。但是如果pods使用記憶體少于request值,它們不會被kill,除非系統任務或daemon需要更多資源。(說白了,還是要看觸發oom killer時,周遊系統上所有程序打分的情況。)
  • 當Pods使用記憶體超過了limit,某個在pod中容器内程序使用了大量記憶體,則該程序會被核心kill掉.

管理和排程政策

如何根據不同的QoS回收Resources

  • CPU 當CPU使用不能達到request值,比如系統任務和daemons使用了大量CPU,則Pods不會被kill,CPU效率會下降(throttled)。
  • Memory 記憶體是不可壓縮資源,從記憶體管理的角度做如下區分:
    • Best-Effort pods 優先級最低。如果系統記憶體耗盡,該類型的pods中的程序最先被kill。這些容器可以使用系統上任意量的空閑記憶體。
    • Guaranteed pods 優先級最高。它們能夠確定不達到容器設定的limit上限一定不會被kill。隻有在系統存在記憶體壓力且沒有更低優先級容器時才被驅逐。
    • Burstable pods 有一些形式的最小資源保證,但當需要時可以使用更多資源。在系統存在記憶體瓶頸時,一旦記憶體超過他們的request值并且沒有Best-Effort 類型的容器存在,這些容器就先被kill掉。

Node上的OOM Score 配置

Pod OOM 打配置設定置

mm/oom_kill.c 中的badness()給每個程序一個OOM score,更高OOM得分的程序更容易被kill。得分取決于:

  • 主要是看程序的記憶體消耗情況,包括駐留記憶體、pagetable和swap的使用
    • 一般是記憶體耗費的百分比*10(percent-times-ten)
  • 參考使用者權限,比如root權限啟動的程序,打分會減少30。
  • OOM打分因子:/proc/pid/oom_score_adj (加減) 和 /proc/pid/oom_adj(乘除)
    • oom_adj: -15~ 15的系數調整
    • oom_score_adj:oom_score會加上oom_score_adj這個值
    • 最終oom score的值 還是在 0~1000

這裡提供一個計算系統上oom_score分數TPO10程序(最容易被oom killer殺掉的程序)腳本:

# vim oomscore.sh #!/bin/bash for proc in $(find /proc -maxdepth 1 -regex '/proc/[0-9]+'); do printf "%2d %5d %s\n" \
 "$(cat $proc/oom_score)" \
 "$(basename $proc)" \
 "$(cat $proc/cmdline | tr '\0' ' ' | head -c 50)" done 2>/dev/null | sort -nr | head -n 10
           

以下是幾種K8S QoS 等級的OOM score:

Best-effort

  • Set OOM_SCORE_ADJ: 1000
  • 是以best-effort容器的OOM_SCORE 值為1000

Guaranteed

  • Set OOM_SCORE_ADJ: -998
  • 是以guaranteed容器的OOM_SCORE 值為0 或 1

Burstable

  • 如果總的memory request 大于 99.9%的可用記憶體,OOM_SCORE_ADJ設定為 2。否則, OOM_SCORE_ADJ = 1000 - 10 * (% of memory requested),這確定了burstable的 POD OOM_SCORE > 1
  • 如果memory request設定為0,OOM_SCORE_ADJ 預設設定為999。是以如果burstable pods和guaranteed pods沖突時,前者會被kill。
  • 如果burstable pod使用的記憶體少于request值,那它的OOM_SCORE < 1000。如果best-effort pod和這些 burstable pod沖突時,best-effort pod會先被kill掉。
  • 如果 burstable pod容器中程序使用比request值的記憶體更多,OOM_SCORE設定為1000。反之,OOM_SCORES少于1000。
  • 在一堆burstable pod中,使用記憶體超過request值的pod,優先于記憶體使用少于request值的pod被kill。
  • 如果 burstable pod 有多個程序沖突,則OOM_SCORE會被随機設定,不受“request & limit”限制。

Pod infra containers or Special Pod init process

  • OOM_SCORE_ADJ: -998

Kubelet, Docker

  • OOM_SCORE_ADJ: -999 (won’t be OOM killed)
  • 系統上的關鍵程序,如果和guranteed 程序沖突,則會優先被kill 。将來會被放到一個單獨的cgroup中,并且限制記憶體。

已知的issue和潛在優化點

  • 支援swap: 目前QoS政策預設swap關閉。如果開啟swap,那些guaranteed 容器資源使用達到limit值,還可以使用磁盤來提供記憶體配置設定。最終,當swap空間不夠時,pod中的程序才會被kill.此時,node需要在提供隔離政策時,把swap空間考慮進去。
  • 提供使用者指定優先級:使用者讓kubelet指定哪些tasks可以被kill.

源碼分析

QoS的源碼位于:pkg/kubelet/qos,代碼非常簡單,主要就兩個檔案pkg/kubelet/qos/policy.go,pkg/kubelet/qos/qos.go。 上面讨論的各個QoS Class對應的OOM_SCORE_ADJ定義在:

pkg/kubelet/qos/policy.go:21 const (
 PodInfraOOMAdj int = -998
 KubeletOOMScoreAdj int = -999
 DockerOOMScoreAdj int = -999
 KubeProxyOOMScoreAdj int = -999
 guaranteedOOMScoreAdj int = -998
 besteffortOOMScoreAdj int = 1000
)
           

容器的OOM_SCORE_ADJ的計算方法定義在:

pkg/kubelet/qos/policy.go:40 func GetContainerOOMScoreAdjust(pod *v1.Pod, container *v1.Container, memoryCapacity int64) int {
 switch GetPodQOS(pod) {
 case Guaranteed:
 // Guaranteed containers should be the last to get killed. return guaranteedOOMScoreAdj
 case BestEffort:
 return besteffortOOMScoreAdj
 }

 // Burstable containers are a middle tier, between Guaranteed and Best-Effort. Ideally, // we want to protect Burstable containers that consume less memory than requested. // The formula below is a heuristic. A container requesting for 10% of a system's // memory will have an OOM score adjust of 900. If a process in container Y // uses over 10% of memory, its OOM score will be 1000. The idea is that containers // which use more than their request will have an OOM score of 1000 and will be prime // targets for OOM kills. // Note that this is a heuristic, it won't work if a container has many small processes.
 memoryRequest := container.Resources.Requests.Memory().Value()
 oomScoreAdjust := 1000 - (1000*memoryRequest)/memoryCapacity
 // A guaranteed pod using 100% of memory can have an OOM score of 10. Ensure // that burstable pods have a higher OOM score adjustment. if int(oomScoreAdjust) < (1000 + guaranteedOOMScoreAdj) {
 return (1000 + guaranteedOOMScoreAdj)
 }
 // Give burstable pods a higher chance of survival over besteffort pods. if int(oomScoreAdjust) == besteffortOOMScoreAdj {
 return int(oomScoreAdjust - 1)
 }
 return int(oomScoreAdjust)
}
           

擷取Pod的QoS Class的方法為:

pkg/kubelet/qos/qos.go:50 // GetPodQOS returns the QoS class of a pod. // A pod is besteffort if none of its containers have specified any requests or limits. // A pod is guaranteed only when requests and limits are specified for all the containers and they are equal. // A pod is burstable if limits and requests do not match across all containers. func GetPodQOS(pod *v1.Pod) QOSClass {
 requests := v1.ResourceList{}
 limits := v1.ResourceList{}
 zeroQuantity := resource.MustParse("0")
 isGuaranteed := true for _, container := range pod.Spec.Containers {
 // process requests for name, quantity := range container.Resources.Requests {
 if !supportedQoSComputeResources.Has(string(name)) {
 continue
 }
 if quantity.Cmp(zeroQuantity) == 1 {
 delta := quantity.Copy()
 if _, exists := requests[name]; !exists {
 requests[name] = *delta
 } else {
 delta.Add(requests[name])
 requests[name] = *delta
 }
 }
 }
 // process limits
 qosLimitsFound := sets.NewString()
 for name, quantity := range container.Resources.Limits {
 if !supportedQoSComputeResources.Has(string(name)) {
 continue
 }
 if quantity.Cmp(zeroQuantity) == 1 {
 qosLimitsFound.Insert(string(name))
 delta := quantity.Copy()
 if _, exists := limits[name]; !exists {
 limits[name] = *delta
 } else {
 delta.Add(limits[name])
 limits[name] = *delta
 }
 }
 }

 if len(qosLimitsFound) != len(supportedQoSComputeResources) {
 isGuaranteed = false
 }
 }
 if len(requests) == 0 && len(limits) == 0 {
 return BestEffort
 }
 // Check is requests match limits for all resources. if isGuaranteed {
 for name, req := range requests {
 if lim, exists := limits[name]; !exists || lim.Cmp(req) != 0 {
 isGuaranteed = false break
 }
 }
 }
 if isGuaranteed &&
 len(requests) == len(limits) {
 return Guaranteed
 }
 return Burstable
}
           

PodQoS會在eviction_manager和scheduler的Predicates階段被調用,也就說會在k8s處理超配和排程預選階段中被使用。

本文轉自開源中國-

Kubernetes Resource QoS Classes介紹