日常使用 Kubernetes 时,时长会出现 Node 节点中的 Pod 被 OOMKill 掉的情况,但 Node 节点中 Pod 众多,为什么单单选中这个 Pod Kill 掉呢?这里就引出了 QoS 的概念,本篇文章就会从源码的角度介绍 QoS 的分类、打分机制,并简单介绍不同 QoS 的本质区别。看看这个机制是如何保证运行在 Kubernetes 中服务质量的。
QoS(Quality of Service) 即服务质量,是 Kubernetes 中的一种控制机制,其会对运行在 Kubernetes 中的 Pod 进行一个质量划分,根据 Pod 中 container 的 Limit 和 request 将 Pod 分为 Guaranteed
,Burstable
,BestEffort
三类并对所有 Pod 进行一个打分。在资源尤其是内存这种不可压缩资源不够时,为保证整体质量的稳定,Kubernetes 就会根据 QoS 的不同优先级,对 Pod 进行资源回收。这也是有时集群中的 Pod 突然被 kill 掉的原因。
复制 // 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
}
复制 // 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)
}