集群跑得好好的突然一堆Pod被驱逐上层服务雪崩——这种场景做过生产运维的基本上都见过或听说过。今天聊聊K8S的优先级和驱逐机制理解了这些你才能在故障来临知道该调什么参数、该查哪个日志。开篇一次真实的驱逐风暴先说个我经历过的案例。那天下午监控系统突然狂响一堆Pod状态变成Evicted。业务方反馈Web服务响应超时排查后发现是跑在这台虚拟机上的几个核心Pod被驱逐了。诡异的是当时节点的内存使用率明明只有60%左右远没到传说中的内存不足。后来定位到原因——运维当天做了虚拟机在线扩容内存从16G扩到了32G但kubelet进程没有重启它依然按照旧的内存阈值在判断是否需要驱逐。这个坑让我意识到很多人以为理解了K8S的驱逐机制其实只是知道有这个东西。真正理解它的运作原理才能避免踩坑也才能在故障时快速定位。今天这篇文章把Pod优先级和驱逐机制彻底讲透。一、先说QoS这是优先级和驱逐的底层基础很多人学K8S优先级和驱逐直接从PriorityClass开始看这是个错误的学习路径。QoS才是底层基础PriorityClass和驱逐策略都依赖它。三种QoS级别到底怎么判定K8S根据Pod的资源配置自动给Pod划分QoS等级一共三级QoS级别判定条件优先级GuaranteedPod中所有容器的requests和limits必须完全相等最高Burstable不满足Guaranteed但至少有一个容器设置了requests或limits中等BestEffort没有任何容器设置requests或limits最低Guaranteed的判定有个细节所有容器的requests和limits都要完全相等包括CPU和内存。如果Pod里有一个容器的requests和limits没设或者设了但不相等这个Pod就是Burstable而不是Guaranteed。# Guaranteed示例所有容器、所有资源类型都严格相等 apiVersion: v1 kind: Pod spec: containers: - name: core-service resources: requests: memory: 1Gi cpu: 500m limits: memory: 1Gi # 与requests相等 cpu: 500m # 与requests相等 - name: sidecar resources: requests: memory: 256Mi cpu: 100m limits: memory: 256Mi # 与requests相等 cpu: 100m # 与requests相等# Burstable示例至少有一个容器的requests/limits没设或不相等 apiVersion: v1 kind: Pod spec: containers: - name: web-app resources: requests: memory: 512Mi cpu: 200m # 没有设置limits降级为Burstable# BestEffort示例什么资源都不设 apiVersion: v1 kind: Pod spec: containers: - name: batch-job # 没有任何resources配置OOM_ADJ内核层面的优先级体现QoS不只是K8S层面的概念它直接映射到Linux内核的OOM Killer机制。每个容器在宿主机上对应一个cgroupcgroup的oom_score_adj参数就体现了QoS级别QoS级别oom_score_adj值含义Guaranteed-998最不容易被OOM KillBurstable2~999中等优先级具体值取决于资源使用情况BestEffort1000最容易被OOM Kill这里有个容易混淆的点OOM Kill和kubelet驱逐是两套独立的机制。OOM Killer是Linux内核在物理内存耗尽时的最后防线而kubelet的驱逐是用户在节点资源紧张时主动采取的保护措施。两者都会删Pod但触发条件和时机不同。很多时候你会看到Pod被驱逐而不是被OOM Kill就是因为kubelet在内存彻底耗尽之前就已经开始行动了。生产建议核心应用一定要设Guaranteed说了这么多理论来点实在的。核心业务Pod务必配置Guaranteed QoS。这不是过度设计而是保障。我见过太多团队为了省事所有Pod都不设资源限制结果在资源紧张时BestEffort Pod被大量驱逐业务服务也难逃一劫。对于确实需要尽力而为的服务——比如一些批处理任务、监控采集Agent等——BestEffort反而是合理的选择让它们在资源紧张时主动让步。二、PriorityClass给Pod排座次理解了QoS再来看PriorityClass。这是K8S提供的显式优先级机制和QoS是两条独立的线但会共同影响驱逐决策。两种抢占策略非抢占 vs 抢占PriorityClass有个关键字段preemptionPolicy控制Pod在资源不足时的行为# 高优先级Pod - 非抢占策略 apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: high-priority-non-preemptive value: 1000 preemptionPolicy: Never # 关键非抢占 globalDefault: false description: 高优先级但不抢占其他Pod# 最高优先级Pod - 抢占策略 apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: critical value: 10000 preemptionPolicy: PreemptLowerPriority # 默认值会抢占低优先级Pod globalDefault: false description: 关键业务Pod优先调度非抢占Never的适用场景数据库主从集群中的从节点宕机了不应该把主节点踢走有状态服务不同节点之间有依赖关系某些业务逻辑要求Pod一旦调度就必须稳定运行抢占PreemptLowerPriority的适用场景批量任务需要尽快调度无状态服务被驱逐后可以快速在其他节点重建紧急扩容时需要抢资源很多人以为设置了PriorityClass就一定能抢到资源这其实是误解。非抢占Pod在资源不足时只能排队等待它不会把已经运行中的低优先级Pod赶走。这是两种完全不同的策略选错了会出大问题。抢占的内部机制比你想象的复杂抢占有意思的地方在于它不是简单地把低优先级Pod一脚踢开。K8S的实现比直觉要复杂正常调度流程Pod进入activeQ等待调度Scheduler尝试为Pod选择节点如果所有节点都资源不足调度失败Pod进入unschedulableQ同时触发抢占流程抢占流程Scheduler从unschedulableQ取出Pod在每个候选节点上模拟抢占假设低优先级Pod被移除重新计算是否满足调度条件选择最优的牺牲者通常是多个低优先级Pod选中节点后向API Server发送删除牺牲者Pod的请求牺牲者Pod被标记为待删除调度器继续调度高优先级Pod这里有个细节牺牲者Pod的删除是异步的。删除请求发出去后Scheduler不会等它真正被终止而是直接继续调度高优先级Pod。这意味着你可能会看到一种诡异的状态——Pod A是牺牲者正在被删除同时Pod B高优先级已经在同一节点上开始调度。优雅关闭窗口的不确定性是另一个容易踩坑的点。牺牲者Pod默认有30秒的优雅关闭期terminationGracePeriodSeconds在这30秒内Pod可能还占用着资源。与此同时高优先级Pod已经被调度到同一节点如果节点资源紧张可能导致高优先级Pod也调度失败。这个30秒的窗口会带来调度的不确定性。节点优先级除了Pod优先级还有这些前面主要说的是Pod的优先级但实际上节点的调度优先级也很重要。Scheduler的优选阶段会计算每个节点的得分影响因素包括静态优先级通过节点注解node.kubernetes.io/node-priority配置亲和性优先级基于Pod的节点亲和性/反亲和性规则QoS优先级节点上运行的Pod的QoS分布插件优先级各类调度插件的评分结果这些优先级主要影响调度时的节点选择和Pod的PriorityClass是两个维度理解这一点很重要。三、驱逐机制当节点撑不住时发生了什么终于说到驱逐了。这是生产环境中最常遇到的问题。为什么不能只依赖OOM Killer很多人觉得既然Linux有OOM KillerK8S为什么还要自己搞一套驱逐机制原因有几个OOM Killer触发时系统已经很不健康了内存彻底耗尽可能导致系统进入swap困境磁盘IO暴增连日志都写不进去OOM Killer的选择不一定符合业务需求它只看内存使用不看服务重要性kubelet有全局视角可以协调多个Pod的驱逐而不仅仅是单个容器所以kubelet的驱逐机制是一个主动的、资源预保护的机制在问题还没到不可收拾之前就介入。软驱逐 vs 硬驱逐kubelet支持两种驱逐策略硬驱逐Hard Eviction达到阈值立即执行不留情面一旦触发Pod立即被标记为Evicted没有任何宽限期# kubelet配置示例 evictionHard: memory.available: 500Mi nodefs.available: 5% imagefs.available: 15%软驱逐Soft Eviction达到阈值后启动观察不会立即驱逐而是给Pod一个宽限期eviction-soft-grace-period在宽限期内如果资源使用降回阈值以下驱逐取消适合希望给系统一个喘息机会的场景# 软驱逐配置 evictionSoft: memory.available: 1Gi nodefs.available: 10% evictionSoftGracePeriod: memory.available: 1m30s nodefs.available: 1m30s实战建议生产环境必须配置硬驱逐软驱逐作为补充。软驱逐的宽限期不宜设太长1-2分钟足够了——设得太长反而可能让系统在临界状态挣扎更久。驱逐选择Pod的逻辑当触发驱逐时kubelet不是随机选一个Pod杀掉而是有一套优先级算法核心考量因素Priority值PriorityClass的值越高越不容易被驱逐设置了PriorityClass的情况下QoS类别BestEffort Burstable GuaranteedBestEffort最优先被驱逐资源使用接近limits的程度使用量越接近limits被驱逐优先级越高Pod运行时长运行时间越长的Pod越稳定优先级越高淘汰顺序的综合表达式大致是优先级 PriorityClass值 QoS权重 (资源使用量/limits的比例) - 运行时长因子这个公式不是官方明文规定的但反映了实际的驱逐倾向。我自己的理解是kubelet希望优先驱逐那些最应该为自己资源占用负责的Pod——BestEffort Pod什么都没承诺当然最该让步Guaranteed Pod承诺了资源保障不应该被轻易驱逐。关键驱逐配置参数# kubelet驱逐相关配置详解 evictionHard: memory.available: 500Mi # 硬驱逐阈值 nodefs.available: 5% # 节点根文件系统可用空间 nodefs.inodesFree: 5% # inode数量 imagefs.available: 15% # 镜像存储文件系统可用空间 # 还有这些可选信号 # memory.available, nodefs.available, nodefs.inodesFree # imagefs.available, imagefs.inodesFree # pid.available (可用进程ID数量) ​ evictionSoft: memory.available: 1Gi # 软驱逐阈值 ​ evictionSoftGracePeriod: memory.available: 1m30s # 软驱逐宽限期 ​ evictionMinimumReclaim: memory.available: 200Mi # 驱逐后保留的最小余量 nodefs.available: 2Gi # 防止反复触发驱逐 ​ evictionPressureTransitionPeriod: 5m # 退出压力状态的冷却时间四、实战踩坑那些年踩过的驱逐相关故障坑1虚拟机在线扩容后kubelet不感知这正是文章开头提到的那个案例。问题根因是虚拟机内存在线扩容宿主机内核已经识别到新内存kubelet进程持有的是启动时的内存信息kubelet的驱逐阈值基于旧内存计算扩容后阈值反而偏低了结果内存使用率明明不高kubelet却认为资源紧张触发驱逐排查方法# 查看节点内存容量对比Pod内看到的内存 kubectl describe node node-name | grep -A 5 Allocated resources ​ # 查看kubelet日志中的驱逐相关事件 journalctl -u kubelet | grep -i evict ​ # 查看Pod被驱逐的原因 kubectl describe pod pod-name | grep -A 20 Events解决方案虚拟机扩容后务必重启kubelet或者使用动态资源调整机制Kubelet动态配置自动化脚本检测到节点规格变化时自动重启kubelet坑2只设置了requests没设置limits这种情况会导致Pod被归类为Burstable驱逐优先级比Guaranteed高。但更严重的是requests没设limits意味着Pod可以使用任意多内存在共享节点的场景下可能影响其他Pod。最佳实践requests和limits成对设置核心服务设置Guaranteed非核心服务根据业务特点合理配置。坑3驱逐阈值设置过激进有人为了保护节点把驱逐阈值设得很高比如内存可用低于50%就驱逐。这反而是过度保护节点资源利用率低造成浪费Pod频繁驱逐影响稳定性应该让系统有一定的自愈空间建议驱逐阈值应该基于实际业务需求和历史监控数据调优不是一味追求安全。坑4忽略了系统预留资源kubelet本身、操作系统日志、容器运行时都需要内存。如果不预留kubelet驱逐Pod腾出的空间被系统进程吃掉形成死循环。# 系统预留配置 systemReserved: memory: 1Gi cpu: 500m kubeReserved: memory: 1Gi cpu: 500m五、生产环境配置建议kubelet驱逐参数推荐配置以下是我在生产环境中验证过的一套配置根据业务规模和节点规格调整# /var/lib/kubelet/config.yaml evictionHard: memory.available: 500Mi nodefs.available: 5% nodefs.inodesFree: 5% imagefs.available: 15% evictionSoft: memory.available: 1Gi nodefs.available: 10% evictionSoftGracePeriod: memory.available: 2m nodefs.available: 2m evictionMinimumReclaim: memory.available: 200Mi nodefs.available: 1Gi ​ # 建议同时配置系统预留 systemReserved: memory: 2Gi cpu: 1 kubeReserved: memory: 2Gi cpu: 500m ​ # 驱逐压力状态转换冷却期 evictionPressureTransitionPeriod: 5mPriorityClass推荐配置# 最高优先级 - 核心基础设施 apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: system-critical value: 2000000 preemptionPolicy: PreemptLowerPriority description: 系统关键组件 ​ --- # 高优先级 - 核心业务服务 apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: business-critical value: 1000000 preemptionPolicy: PreemptLowerPriority description: 核心业务服务 ​ --- # 中优先级 - 普通业务 apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: normal value: 0 preemptionPolicy: PreemptLowerPriority description: 普通优先级默认值 ​ --- # 低优先级 - 批处理/可抢占任务 apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: batch value: -10000 preemptionPolicy: PreemptLowerPriority description: 批处理任务可被抢占监控告警方案光配置好还不够还需要监控必须监控的指标节点驱逐事件数量rate(kubelet_evictions[5m])节点资源使用率趋势Pod重启次数OOM Kill次数建议的告警规则单节点驱逐Pod数量 5 / 分钟节点内存使用率 85% 持续 5 分钟Pod被OOM Kill条件性告警如果业务Pod被OOM Kill需要立即响应# 查看驱逐事件的命令 kubectl get events --all-namespaces --field-selector reasonEviction ​ # 统计近1小时的驱逐事件 kubectl get events --all-namespaces --field-selector reasonEviction \ --sort-by.lastTimestamp | tail -50总结写了这么多总结几条核心要点QoS是基础Guaranteed/Burstable/BestEffort不只是分类它们直接影响Pod的生存优先级和内核OOM策略。核心服务必须Guaranteed。PriorityClass解决的是调度优先级非抢占适合有状态服务抢占适合无状态批量任务。抢占流程有30秒窗口的不确定性设计架构时要考虑这点。驱逐是主动保护机制在资源彻底耗尽之前介入保护系统的整体稳定性。硬驱逐必须配置软驱逐可选。配置要成体系evictionHard systemReserved kubeReserved PriorityClass QoS这些要一起考虑不是单独配置某个就行。监控是最后一道防线配置再好也需要监控来验证效果和发现异常。最后说一句很多故障都是配置了但没理解导致的。看完这篇文章希望你对K8S优先级和驱逐机制的理解不止是知道有这个功能而是能真正用好它在生产环境中从容应对各种资源压力场景。如果你也踩过类似的坑欢迎在评论区分享。技术这条路踩坑不可怕可怕的是踩完还没长记性。