天天看點

圖解 K8S 源碼 - QoS 篇前言QoS結語

圖解 K8S 源碼 - QoS 篇前言QoS結語

前言

日常使用 Kubernetes 時,時長會出現 Node 節點中的 Pod 被 OOMKill 掉的情況,但 Node 節點中 Pod 衆多,為什麼單單選中這個 Pod Kill 掉呢?這裡就引出了 QoS 的概念,本篇文章就會從源碼的角度介紹 QoS 的分類、打分機制,并簡單介紹不同 QoS 的本質差別。看看這個機制是如何保證運作在 Kubernetes 中服務品質的。

QoS

QoS(Quality of Service) 即服務品質,是 Kubernetes 中的一種控制機制,其會對運作在 Kubernetes 中的 Pod 進行一個品質劃分,根據 Pod 中 container 的 Limit 和 request 将 Pod 分為

Guaranteed

Burstable

BestEffort

三類并對所有 Pod 進行一個打分。在資源尤其是記憶體這種不可壓縮資源不夠時,為保證整體品質的穩定,Kubernetes 就會根據 QoS 的不同優先級,對 Pod 進行資源回收。這也是有時叢集中的 Pod 突然被 kill 掉的原因。

圖解 K8S 源碼 - QoS 篇前言QoS結語

QoS 分類

以下代碼用來擷取 Pod 的 QoS 類,用于區分不同 Pod 的 QoS。

// github/kubernetes/pkg/apis/core/v1/helper/qos/qos.go
func GetPodQOS(pod *v1.Pod) v1.PodQOSClass {
  requests := v1.ResourceList{}
  limits := v1.ResourceList{}
  zeroQuantity := resource.MustParse("0")
  isGuaranteed := true
  allContainers := []v1.Container{}
  allContainers = append(allContainers, pod.Spec.Containers...)
  // InitContainers 容器也會被加入 QoS 的計算中
  allContainers = append(allContainers, pod.Spec.InitContainers...)
  for _, container := range allContainers {
    // 周遊 requests 中的 CPU 和 memory 值
    for name, quantity := range container.Resources.Requests {
      if !isSupportedQoSComputeResource(name) {
        continue
      }
      if quantity.Cmp(zeroQuantity) == 1 {
        delta := quantity.DeepCopy()
        if _, exists := requests[name]; !exists {
          requests[name] = delta
        } else {
          delta.Add(requests[name])
          requests[name] = delta
        }
      }
    }
    // 周遊 limits 中的 CPU 和 memory 值
    qosLimitsFound := sets.NewString()
    for name, quantity := range container.Resources.Limits {
      if !isSupportedQoSComputeResource(name) {
        continue
      }
      if quantity.Cmp(zeroQuantity) == 1 {
        qosLimitsFound.Insert(string(name))
        delta := quantity.DeepCopy()
        if _, exists := limits[name]; !exists {
          limits[name] = delta
        } else {
          delta.Add(limits[name])
          limits[name] = delta
        }
      }
    }

    // 判斷是否同時設定了 limits 和 requests,如果沒有則不是 Guaranteed
    if !qosLimitsFound.HasAll(string(v1.ResourceMemory), string(v1.ResourceCPU)) {
      isGuaranteed = false
    }
  }
  // 如果 requests 和 limits 都沒有設定,則為 BestEffort
  if len(requests) == 0 && len(limits) == 0 {
    return v1.PodQOSBestEffort
  }
  // 檢查所有資源的 requests 和 limits 是否都相等
  if isGuaranteed {
    for name, req := range requests {
      if lim, exists := limits[name]; !exists || lim.Cmp(req) != 0 {
        isGuaranteed = false
        break
      }
    }
  }
  // 都設定了 requests 和 limits,則為 Guaranteed
  if isGuaranteed &&
    len(requests) == len(limits) {
    return v1.PodQOSGuaranteed
  }
  return v1.PodQOSBurstable
}           

QoS 打分

QoS 會根據不同的分類進行 OOMScore 打分,當主控端上記憶體不足時,系統會優先 kill 掉 OOMScore 分數高的程序。

圖解 K8S 源碼 - QoS 篇前言QoS結語

值得注意的是不久之前

guaranteedOOMScoreAdj

的值還是

-998

,今年 9 月 22 日才合并

PR

将其修改為

-997

,而修改的 PR 及

相關 ISSUE

在 2018 年就已經提出了,感興趣的同學可以去看看。這裡附上源碼:

// github/kubernetes/pkg/kubelet/qos/policy.go
const (
  // KubeletOOMScoreAdj is the OOM score adjustment for Kubelet
  KubeletOOMScoreAdj int = -999
  // KubeProxyOOMScoreAdj is the OOM score adjustment for kube-proxy
  KubeProxyOOMScoreAdj  int = -999
  guaranteedOOMScoreAdj int = -997
  besteffortOOMScoreAdj int = 1000
)

func GetContainerOOMScoreAdjust(pod *v1.Pod, container *v1.Container, memoryCapacity int64) int {
  // 高優先級 Pod 直接傳回 guaranteedOOMScoreAdj
  if types.IsCriticalPod(pod) {
    // Critical pods should be the last to get killed.
    return guaranteedOOMScoreAdj
  }

  // 根據 QoS 等級,傳回 guaranteedOOMScoreAdj 或 besteffortOOMScoreAdj 的分數,這裡隻處理 Guaranteed 與 BestEffort
  switch v1qos.GetPodQOS(pod) {
  case v1.PodQOSGuaranteed:
    // Guaranteed containers should be the last to get killed.
    return guaranteedOOMScoreAdj
  case v1.PodQOSBestEffort:
    return besteffortOOMScoreAdj
  }

  memoryRequest := container.Resources.Requests.Memory().Value()
  // 記憶體占用越少,分數越高
  oomScoreAdjust := 1000 - (1000*memoryRequest)/memoryCapacity
  // 保證 Burstable 分數高于 Guaranteed
  if int(oomScoreAdjust) < (1000 + guaranteedOOMScoreAdj) {
    return (1000 + guaranteedOOMScoreAdj)
  }
  // 保證 Burstable 分數低于 BestEffect
  if int(oomScoreAdjust) == besteffortOOMScoreAdj {
    return int(oomScoreAdjust - 1)
  }
  return int(oomScoreAdjust)
}           

QoS 的本質差別

三種 QoS 在排程和實作都存在着差別:

  • 排程時,排程器隻會根據 request 值進行排程,這也就解釋了有些 Node 節點 Resource Limit 超出 100% 的情況
  • 當 OOM 時,系統會根據

    oom_score

    值來選擇優先 kill 掉的程序,分數越高越先被 kill 掉。

    oom_score

    由系統計算所得,使用者是不能設定的。但是如上文所述,而根據 QoS 的類型,kubelet 會計算出

    oom_score_adj

    的值,通過

    oom_score_adj

    來調整

    oom_score

    的分數,進而影響 OOM 被 kill 程序的優先級。
  • 對于資源的限制,是由 CGroup 來完成的。kubelet 會為三種 QoS 分别建立 QoS level CGroup:
    • Guaranteed

      Pod Qos 的 CGroup level 會直接建立在

      RootCgroup/kubepods

    • Burstable

      Pod Qos 的建立在

      RootCgroup/kubepods/burstable

    • BestEffort

      RootCgroup/kubepods/BestEffort

  • 而在 Pod level CGroup 中還會建立 Container level CGroup,其結構如下圖所示:
圖解 K8S 源碼 - QoS 篇前言QoS結語

結語

本文我們讨論了 Kubernetes 中 QoS 機制的分類、打分及其本質,除了這些 QoS 的實作

QOSContainerManager

中還有三種 QoS 以主控端上 allocatable 資源量為基礎為 Pod 配置設定資源,并通過多個 level cgroup 進行層層限制的邏輯,由于篇幅有限,就不做詳細介紹了。

圖解 K8S 源碼 - QoS 篇前言QoS結語

繼續閱讀