一、Pod驱逐
1、为什么要有驱逐
pod.spec.containers[].resources中会存在cpu或memory的request和limit。即该pod请求的最小资源和Node结点可以给的最大资源。
当一个容器的cpu使用率超过limit时会被进行流控,而当内存超过limit时则会被oom_kill。
完全依赖于oom_kill并不是一个很好的方案,一来对于cpu要求高的容器没有作用,二来单纯将pod杀死,并不能根本上解决困局,比如pod占用node绝大部分内存,假如pod被kill后再次调度到这个node上,oom的情况还会复现。所以kubelet增加了一套驱逐机制。 eviction中要设置触发驱逐的阈值Eviction Thresholds,这个阈值的配置可以是一个定值或一个百分比。如:
memory.available<10%
memory.available<1Gi
2、驱逐机制
Soft Eviction Thresholds(软驱逐机制)
当node的内存/磁盘空间达到一定的阈值后,我要观察一段时间,如果改善到低于阈值就不进行驱逐,若这段时间一直高于阈值就进行驱逐。
Hard Eviction Thresholds( 强制驱逐机制)
简单的多,一旦达到阈值,立刻把pod从本地kill。
3、Pod eviction(Pod 驱逐)
当资源使用情况触发了驱逐条件时,kubelet会启动一个任务去轮流停止运行中的pod,直到资源使用状况恢复到阈值以下。以硬驱逐为例,整体流程是:
-
每隔一段时间从cadvisor中获取资源使用情况,发现触发了阈值;
-
从运行中的pod里找到QoS策略最开放的一个,比如策略为bestEffort的一个pod(即便这个pod没有吃多少内存,大部分内存是另一个策略为burstable,但内存使用率也很高的pod),kubelet停止该pod对应的所有容器,然后将pod状态更新为Failed。如果该pod长时间没有被成功kill掉,kubelet会再找一个pod进行驱逐。
-
检查内存用量是否恢复到阈值以下,如果没有,则重复第二步(这里就要干掉那个罪魁祸首了)。一直到内存使用情况恢复到阈值以下为止。
在 Kubernetes 中,当资源不足需要驱逐 Pod 时,系统会根据 Pod 的优先级(由 PriorityClass 的 value 决定)和 Pod 的 Quality of Service (QoS) 类别等进行决策。而调度优先级主要由 Priority 值确定。
3.1、resource 的 requests 和 limits
Requests(请求):Requests 是指容器在运行时所需的资源的最小数量。它们用于告诉 Kubernetes 调度器在选择节点时要为 Pod 预留多少资源。如果没有足够的请求资源可用,Pod 可能无法被调度到节点上。
Limits(限制):Limits 是指容器在运行时所允许使用的资源的最大数量。它们用于限制容器的资源使用,以防止容器占用过多的资源导致其他容器或节点受到影响。如果容器尝试使用超过其限制的资源量,Kubernetes 将会限制其资源使用,并可能触发容器的重新启动。
3.2、QoS 类别
-
BestEffort:没有设置 resource requests 和 limits 的 Pod。
-
Burstable:设置了 requests 或者 limits,但不完全相同。
-
Guaranteed:requests 和 limits 都设置了,并且两者值相等。
驱逐顺序:BestEffort(lowest) -> Burstable -> Guaranteed(highest)。不影响调度的优先级。
3.3、PriorityClass 和 Priority
PriorityClass(优先级类):PriorityClass 是一种用于调度和优先级管理的对象。它允许您为 Pod 分配优先级。PriorityClass 定义了一个优先级类别,其中包含一个整数值 value 表示优先级的相对值。较高的 value 值表示较高的优先级。通过将 Pod 与特定的 PriorityClass 关联,可以影响 Pod 的调度和驱逐顺序。
Priority(优先级):Priority 是一个整数值,直接应用于 Pod 对象。它表示 Pod 的绝对优先级。较高的 Priority 值表示较高的优先级。同样可以影响 Pod 的调度和驱逐顺序。
查看 PriorityClass
kubectl get priorityclasses
查看系统组件 controller-manager 使用的 PriorityClass
[root@k8s-master ~]# kubectl describe pod -n kube-system kube-controller-manager-k8s-master | grep -i priority
Priority: 2000001000
Priority Class Name: system-node-critical
当集群中没有默认的 PriorityClass,也没有手动指定 Priority,那优先级的值就为 0。优先级的值越小,驱逐顺序越靠前,调度顺序越靠后。
还有一种情况是:尽管 PriorityClass 的 value 值大,但是 BestEffort 类型的 qos class 会比Burstable 或 Guaranteed 类别更容易被驱逐。当然还会有其他因素也会影响 pod 的驱逐顺序,但是影响力不如上面两种大,例如:Pod资源使用量越接近 limits,和 pod 运行时长越短等,那么这些 Pod 会被优先考虑驱逐。
当集群内有比较重要的服务时,可以把 Qos Class 设置为 Guaranteed,也就是都指定了 requests 和 limits 并且二者值相等,会有长时间运行稳定性的优势。且 Priority 的值尽可能设置大些,会有优先占用集群资源资源的优势。
在K8s 1.6之后还引入了Taint的两个新特性,TaintNodesByCondition与TaintBasedEvictions用来改善出现异常时对Pod的调度与驱逐问题
TaintNodesByCondition
特性如下(为节点添加NoSchedule的污点)
-
Node节点会不断的检查Node的状态,并且设置对应的Condition
-
不断地根据Condition的变更设置对应的Taint
-
不断地根据Taint驱逐Node上的Pod
主要污点如下:
node.kubernetes.io/not-ready 节点未就绪,节点Ready为False
node.kubernetes.io/unreachable 节点不可达
node.kubernetes.io/out-of-disk 磁盘空间已满
node.kubernetes.io/network-unavailable 网络不可用
node.kubernetes.io/unschedulable 节点不可调度
node.cloudprovider.kubernetes.io/uninitialized 如果 kubelet 从 外部 云服务商启动的,该污点用来标识某个节点当前为不可用状态,当云控制器 cloud-controller-manager 初始化这个节点后,kubelet 会将此污点移除
TaintBasedEvictions
特性添加的是NoExecute的污点,例如内存与磁盘有压力时,如果Pod没有设置容忍这些污点,则会被驱逐,以保证Node不会崩溃
主要污点如下:
node.kubernetes.io/memory-pressure 内存不足
node.kubernetes.io/disk-pressure 磁盘不足
1.13版本之后TaintNodesByCondition 与 TaintBasedEvictions 都是默认开启
二、Pod驱逐实战案例
1、k8s pod内存驱逐问题解决
**背景:**突然收到 web 无法访问告警,然后发现前段应用pod状态为Evicted,证明pod是被驱逐了
排查过程:
1.1、查看pod状态:
kubectl get pods
kubectl get pods -A | grep 0/1
web-nginx-865674789f-c7bv4 0/1 Evicted 0 25h <none> 192.168.3.10 <none>
web-nginx-865674789f-ggb27 0/1 Evicted 0 25h <none> 192.168.3.10 <none>
web-nginx-865674789f-fwp94 0/1 Evicted 0 25h <none> 192.168.3.10 <none>
web-nginx-865674789f-djj46 0/1 Evicted 0 25m <none> 192.168.3.10 <none>
1.2、查看pod事件日志:
kubectl describe pods web-nginx-xxx
从日志上可以看出来是内存不足导致了驱逐
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning Evicted 2m (x1 over 2m) kubelet The node was low on resource: [MemoryPressure].
1.3、排查节点内存监控
发现利用率在50%,没理由会导致内存不足
1.4、思考内存驱逐的原理
1.4.1 K8S通过kubelet来配置pod的驱逐参数,如果没有配置,则使用默认值。检查下驱逐阈值。
# 硬性驱逐条件
evictionHard:
memory.available: "200Mi"
nodefs.available: "10%"
nodefs.inodesFree: "5%"
imagefs.available: "15%"
# 软性驱逐条件
evictionSoft:
memory.available: "300Mi"
nodefs.available: "15%"
imagefs.available: "20%"
# 软性驱逐条件的宽限期
evictionSoftGracePeriod:
memory.available: "1m"
nodefs.available: "1m"
imagefs.available: "1m"
# 驱逐Pod前的最大宽限期
evictionMaxPodGracePeriod: 60
#驱逐开始前等待资源压力状态稳定的时间
evictionPressureTransitionPeriod: "5m"
硬性驱逐和软性驱逐的区别:
-
硬性驱逐是当资源达到或超过设定的硬性驱逐阈值时,Kubelet立即执行驱逐操作。硬性驱逐的特点是直接且无延迟。
-
软性驱逐是在资源使用达到设定的软性驱逐阈值后,给Pod一个宽限期(Grace Period)。如果在宽限期结束后资源使用仍然没有降低,Kubelet才会驱逐Pod。
1.4.2 查看node可用内存
kubectl describe node
Allocatable:
cpu: 15400m
ephemeral-storage: 1043358208Ki
hugepages-1Gi: 0
hugepages-2Mi: 0
memory: 63242364Ki #可分配60G内存
pods: 253
可分配内存为60G,而服务器内存为100G,
和现场同学(一线工程师)确认,问题出现前由于内存占用很高,做过一次在线扩容。
**故障复盘:**故障原因为前期内存资源不足后,虚拟机采用在线扩容内存的方式,服务器没有重启,并且K8S的kubelet服务也没有重启,获取到的内存配置仍然是60G,所以当主机内存达到60G的时候出现pod由于内存不足产生驱逐。
至于监控,node-exporter可以动态获取主机物理资源,所以过于依赖监控却忽略了检查kubelet。
优化方案:对node内存和kubelet可分配内存做对比,如果相差大于1G则触发告警
配置案例
好的,这里提供一些 Kubernetes Pod 驱逐(Eviction)参数设置的典型案例。这些参数主要配置在 Kubelet 上,用于控制节点资源压力时的驱逐行为。
核心概念:驱逐信号(Eviction Signals)与阈值(Thresholds)
Kubelet 监控多种资源信号,当这些信号达到设定的阈值时,就会触发驱逐。
| 驱逐信号 | 描述 |
|---|---|
memory.available |
节点可用内存 |
nodefs.available |
节点根磁盘(/)可用空间 |
nodefs.inodesFree |
节点根磁盘(/)可用 inode |
imagefs.available |
容器运行时存储镜像和可写层的磁盘可用空间 |
imagefs.inodesFree |
容器运行时磁盘的可用 inode |
pid.available |
节点可用进程 ID (PID) |
案例一:基础驱逐配置
这是一个最常见的配置案例,用于防止节点因内存或磁盘耗尽而完全宕机。
kubelet 启动参数(通常在 /var/lib/kubelet/config.yaml 或 systemd 的 kubelet.service 中配置):
# /var/lib/kubelet/config.yaml 片段
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
evictionHard:
memory.available: "500Mi" # 可用内存低于500Mi时开始驱逐
nodefs.available: "10%" # 根磁盘可用空间低于10%时开始驱逐
nodefs.inodesFree: "5%" # 根磁盘inode低于5%时开始驱逐
imagefs.available: "15%" # 容器镜像磁盘可用空间低于15%时开始驱逐
evictionSoft:
memory.available: "700Mi"
nodefs.available: "15%"
evictionSoftGracePeriod:
memory.available: "1m30s" # 软阈值持续1分30秒后才触发驱逐
nodefs.available: "2m" # 软阈值持续2分钟后才触发驱逐
evictionMaxPodGracePeriod: 60 # 驱逐Pod时,允许Pod体面终止的最大宽限期(秒)
evictionPressureTransitionPeriod: 30s # 脱离压力状态后,需要持续多久才报告状态正常
对应 systemd 参数(如果使用命令行参数):
/usr/bin/kubelet \
--eviction-hard=memory.available<500Mi,nodefs.available<10%,nodefs.inodesFree<5%,imagefs.available<15% \
--eviction-soft=memory.available<700Mi,nodefs.available<15% \
--eviction-soft-grace-period=memory.available=1m30s,nodefs.available=2m \
--eviction-max-pod-grace-period=60 \
--eviction-pressure-transition-period=30s \
...其他参数
案例二:为系统守护进程预留资源
这是强烈推荐的生产环境配置。通过为系统进程和 kubelet 本身预留资源,可以防止它们因资源竞争而被饿死,从而保证节点的稳定性。
kubelet 启动参数:
# /var/lib/kubelet/config.yaml 片段
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
# 1. 设置驱逐阈值
evictionHard:
memory.available: "500Mi"
nodefs.available: "10%"
# 2. 为核心系统预留资源!非常重要!
systemReserved:
memory: "1Gi"
cpu: "500m"
ephemeral-storage: "5Gi"
kubeReserved:
memory: "1Gi"
cpu: "250m"
ephemeral-storage: "1Gi"
# 3. 强制执行预留策略
enforceNodeAllocatable:
- pods
- system-reserved
- kube-reserved
-
systemReserved: 为操作系统守护进程(如 sshd、udev)预留的资源。 -
kubeReserved: 为 Kubernetes 系统守护进程(如 kubelet、容器运行时)预留的资源。 -
enforceNodeAllocatable: 指定 kubelet 需要强制执行哪些预留策略。pods必须包含在内。
工作原理: 节点的总资源 = Allocatable(可分配给 Pod 的资源) + SystemReserved + KubeReserved 驱逐阈值是基于 Allocatable 来计算的吗?不是,驱逐阈值是基于节点的总容量(Capacity) 来监控的。但预留机制确保了系统核心组件有资源可用,即使 Pod 已经耗尽了 Allocatable 部分。
案例三:仅使用软驱逐阈值实现平滑处理
软阈值搭配宽限期,可以让 Pod 有机会在被迫驱逐前完成一些清理工作(如通知中心、完成当前请求),适用于对中断敏感的应用。
kubelet 启动参数:
evictionSoft:
memory.available: "5%"
nodefs.available: "10%"
imagefs.available: "10%"
evictionSoftGracePeriod:
memory.available: "2m" # 内存压力持续2分钟后才驱逐
nodefs.available: "3m" # 磁盘压力持续3分钟后才驱逐
imagefs.available: "3m"
evictionMaxPodGracePeriod: 90 # 给予Pod更长的体面终止时间
案例四:高级镜像垃圾收集策略
驱逐经常与镜像磁盘空间不足有关,配置积极的镜像垃圾收集可以预防驱逐。
kubelet 启动参数:
# 镜像垃圾收集策略
imageGCHighThresholdPercent: 85 # 当磁盘使用率达到85%时,触发镜像GC
imageGCLowThresholdPercent: 80 # GC会一直清理,直到使用率降到80%
imageMinimumGCAge: 2m0s # 镜像必须至少存在2分钟才会被GC清理,避免误删正在使用的中间层
最佳实践与总结
-
始终设置
evictionHard:这是防止节点完全宕机的安全网。 -
必须配置
systemReserved和kubeReserved:这是生产环境的黄金准则,能极大提高节点稳定性。预留值大小需根据节点规格和实际系统开销调整。 -
理解软阈值的用途:用于需要"优雅驱逐"的场景,给应用和运维人员预留反应时间。
-
监控驱逐事件 :使用
kubectl get events -w --field-selector reason=Evicted或监控平台来关注集群中的驱逐事件,它们是指标应用资源规划不合理或节点压力过大的重要信号。 -
合理设置 Pod 的
requests和limits:这是防止 Pod 被驱逐的第一道防线。Kubelet 在决定驱逐哪个 Pod 时,会优先驱逐那些实际使用量远超其requests的BurstablePod。 -
使用 Pod 优先级(PriorityClass) :对于关键应用,可以创建高优先级的
PriorityClass,并配置到 Pod 上。这样即使节点压力巨大,低优先级的 Pod 也会在高优先级的 Pod 之前被驱逐。
通过组合使用这些参数,你可以精细地控制 Kubernetes 节点的驱逐行为,在保证应用可用性的同时,最大限度地维护节点本身的健康与稳定。