Kubernetes

k8s Pod Evicted 状态分析

前言

在Kubernetes中,pod是最小的原子调度单位,也就意味着,所有根调度和资源管理相关的配置都直接作用于pod的对象字段,这这其中最重要的就是pod cpu和内存的分配。

分析

cpu

在Kubernetes中,类似CPU这样的资源被称为“可压缩资源”,他的典型特征是,当可压缩资源不足时,pod只会饥饿,而不会退出。
CPU设置的范围是CPU的个数,比如 0.5 ,1.
CPU=1就代表pod的cpu限额是1个cpu。当然,具体一个cpu在宿主机上如何解释,是1个CPU核心,还是一个虚拟CPU,还是1个CPU的超线程,完全是由宿主机来解释的。kubernetes只负责保证pod能够使用“1个CPU”的算力。
你也可以直接把这个配置写成 cpu=0.5。但在实际使用时,还是使用 500m 的写法,这才是 Kubernetes 内部通用的 CPU 表示方式。

ram

像内存这样的资源,则被称为“不可压缩资源”,他的典型特征是:当内存不足时,pod就会因为OOM被Linux Kernel 杀掉。
1Mi = 1024*1024

1M=1000*1000

作用于pod资源清单resources字段实际上,还要分为requests和limits两种情况:
spec.containers[].resources.limits.cpu
spec.containers[].resources.limits.memory
spec.containers[].resources.requests.cpu
spec.containers[].resources.requests.memory
区别在于pod在进行调度的时候scheduler只会按照requests的值来进行设置,而在真正Cgroups限制的时候会根据limits的值来进行设置。

更确切地说,当你指定了 requests.cpu=250m 之后,相当于将 Cgroups 的 cpu.shares 的值设置为 (250/1000)*1024。而当你没有设置 requests.cpu 的时候,cpu.shares 默认则是 1024。这样,Kubernetes 就通过 cpu.shares 完成了对 CPU 时间的按比例分配。

而如果你指定了 limits.cpu=500m 之后,则相当于将 Cgroups 的 cpu.cfs_quota_us 的值设置为 (500/1000)*100ms,而 cpu.cfs_period_us 的值始终是 100ms。这样,Kubernetes 就为你设置了这个容器只能用到 CPU 的 50%。

而对于内存来说,当你指定了 limits.memory=128Mi 之后,相当于将 Cgroups 的 memory.limit_in_bytes 设置为 128 1024 1024。而需要注意的是,在调度的时候,调度器只会使用 requests.memory=64Mi 来进行判断。
Qos
在 Kubernetes 中,不同的 requests 和 limits 的设置方式,其实会将这个 Pod 划分到不同的 QoS 级别当中。

Guaranteed

当 Pod 里的每一个 Container 都同时设置了 requests 和 limits,并且 requests 和 limits 值相等的时候,这个 Pod 就属于 Guaranteed 类别。(被保证的)
需要注意的是,当 Pod 仅设置了 limits 没有设置 requests 的时候,Kubernetes 会自动为它设置与 limits 相同的 requests 值,所以,这也属于 Guaranteed 情况。

Burstable

至少有一个 Container 设置了 requests。那么这个 Pod 就会被划分到 Burstable 类别。(可被突破的)。

BestEffort

如果一个 Pod 既没有设置 requests,也没有设置 limits,那么它的 QoS 类别就是 BestEffort。(尽最大努力保证)

Kubernetes 为 Pod 设置这样三种 QoS 类别,具体有什么作用呢?

Eviction

QoS 划分的主要应用场景,是当宿主机资源紧张的时候,对 Pod 进行 Eviction(即资源回收)时需要用到的

Kubernetes 为你设置的 Eviction 的默认阈值如下所示:
memory.available<100Mi
nodefs.available<10%
nodefs.inodesFree<5%
imagefs.available<15%
Eviction 在 Kubernetes 里其实分为 Soft 和 Hard 两种模式:

Soft Eviction 允许你为 Eviction 过程设置一段“优雅时间”,比如上面例子里的 imagefs.available=2m,就意味着当 imagefs 不足的阈值达到 2 分钟之后, 才会开始 Eviction 的过程。

而 Hard Eviction 模式下,Eviction 过程就会在阈值达到之后立刻开始。

Kubernetes 计算 Eviction 阈值的数据来源,主要依赖于从 Cgroups 读取到的值,以及使用 cAdvisor 监控到的数据。

Eviction 发生的时候,kubelet 具体会挑选哪些 Pod 进行删除操作:

首当其冲的,自然是 BestEffort 类别的 Pod。

其次,是属于 Burstable 类别、并且发生“饥饿”的资源使用量已经超出了 requests 的 Pod。

最后,才是 Guaranteed 类别。并且,Kubernetes 会保证只有当 Guaranteed 类别的 Pod 的资源使用量超过了其 limits 的限制,或者宿主机本身正处于 Memory Pressure 状态时,Guaranteed 的 Pod 才可能被选中进行 Eviction 操作。

Reference

pressure时 node节点reject all pod

func (m *managerImpl) Admit(attrs *lifecycle.PodAdmitAttributes) lifecycle.PodAdmitResult {
m.RLock()
defer m.RUnlock()
if len(m.nodeConditions) == 0 {
    return lifecycle.PodAdmitResult{Admit: true}
}
// Admit Critical pods even under resource pressure since they are required for system stability.
// https://github.com/kubernetes/kubernetes/issues/40573 has more details.
if kubelettypes.IsCriticalPod(attrs.Pod) {
    return lifecycle.PodAdmitResult{Admit: true}
}

// Conditions other than memory pressure reject all pods
nodeOnlyHasMemoryPressureCondition := hasNodeCondition(m.nodeConditions, v1.NodeMemoryPressure) && len(m.nodeConditions) == 1
if nodeOnlyHasMemoryPressureCondition {
    notBestEffort := v1.PodQOSBestEffort != v1qos.GetPodQOS(attrs.Pod)
    if notBestEffort {
        return lifecycle.PodAdmitResult{Admit: true}
    }

    // When node has memory pressure, check BestEffort Pod's toleration:
    // admit it if tolerates memory pressure taint, fail for other tolerations, e.g. DiskPressure.
    if v1helper.TolerationsTolerateTaint(attrs.Pod.Spec.Tolerations, &v1.Taint{
        Key:    v1.TaintNodeMemoryPressure,
        Effect: v1.TaintEffectNoSchedule,
    }) {
        return lifecycle.PodAdmitResult{Admit: true}
    }
}

// reject pods when under memory pressure (if pod is best effort), or if under disk pressure.
klog.InfoS("Failed to admit pod to node", "pod", klog.KObj(attrs.Pod), "nodeCondition", m.nodeConditions)
return lifecycle.PodAdmitResult{
    Admit:   false,
    Reason:  Reason,
    Message: fmt.Sprintf(nodeConditionMessageFmt, m.nodeConditions),
}

}

soft and hard mode

func (m *memoryThresholdNotifier) Description() string {
var hard, allocatable string
if isHardEvictionThreshold(m.threshold) {
    hard = "hard "
} else {
    hard = "soft "
}
if isAllocatableEvictionThreshold(m.threshold) {
    allocatable = "allocatable "
}
return fmt.Sprintf("%s%smemory eviction threshold", hard, allocatable)

}

回复

This is just a placeholder img.