
前言
日常使用 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 掉的原因。
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 分數高的程序。
值得注意的是不久之前
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 時,系統會根據
值來選擇優先 kill 掉的程序,分數越高越先被 kill 掉。oom_score
由系統計算所得,使用者是不能設定的。但是如上文所述,而根據 QoS 的類型,kubelet 會計算出oom_score
的值,通過oom_score_adj
來調整oom_score_adj
的分數,進而影響 OOM 被 kill 程序的優先級。oom_score
- 對于資源的限制,是由 CGroup 來完成的。kubelet 會為三種 QoS 分别建立 QoS level CGroup:
-
Pod Qos 的 CGroup level 會直接建立在Guaranteed
下RootCgroup/kubepods
-
Pod Qos 的建立在Burstable
RootCgroup/kubepods/burstable
-
BestEffort
RootCgroup/kubepods/BestEffort
-
- 而在 Pod level CGroup 中還會建立 Container level CGroup,其結構如下圖所示:
結語
本文我們讨論了 Kubernetes 中 QoS 機制的分類、打分及其本質,除了這些 QoS 的實作
QOSContainerManager
中還有三種 QoS 以主控端上 allocatable 資源量為基礎為 Pod 配置設定資源,并通過多個 level cgroup 進行層層限制的邏輯,由于篇幅有限,就不做詳細介紹了。