1. 事故现场Pod像被狙击一样逐个消失凌晨两点半值班手机连着震了七八下。群里已经炸了锅订单服务又挂了支付回调超时怎么全是Connection refused。我登上生产环境一看节点倒是健康Deployment副本数也没变但Pod列表里一堆OOMKilled状态RestartCount从几十到几百不等。这不是我第一次被Pod频繁被杀这种问题半夜叫醒但这次的情况有点特殊所以印象特别深。当时我习惯性地先看kubectl get events --sort-by.metadata.creationTimestamp满屏都是同一类告警被kubelet反复重启有的直接标记为Killing。第一反应是查代码里有没有内存泄漏但查了半天也没有明显征兆反而是某几个业务Pod在流量低峰期也在不断重启。最后定位的时候才发现根子不在业务代码上而是在资源配置的写法上——我在Deployment里同时设置了requests和limits但是给limits留的余量太小cgroup触发了内核的OOM Killer而kubelet那边看到的健康信号还没来得及上报就被认定为异常并杀掉重启了。这个坑表面上是资源不够本质上是资源配额与业务真实画像错配。这篇文章我不打算只讲结论而是把整个排查链路、判断依据、参数选择逻辑都整理出来方便你遇到同类问题时能少走弯路。2. 第一现场先分清楚是被杀还是被驱逐2.1 同样是Pod消失OOMKilled和Evicted是完全不同的两回事很多人在排查Pod频繁被杀时第一步就走错了方向把Evicted驱逐当成OOMKilled内存溢出被杀一起处理或者在两者之间来回纠结。实际上这两者虽然都表现为Pod没了但背后的触发方和机制差很远。OOMKilled容器进程的内存使用量超过了cgroup的限制即limits.memory触发了Linux内核的OOM Killer。内核会按评分挑进程杀掉优先杀内存占用大的、存活时间短的。Pod的状态会显示为OOMKilledRestartCount会一直往上加。Evicted节点级别的物理资源内存、磁盘、文件系统inode低于kubelet设置的驱逐阈值kubelet作为一种节点保护机制主动回收Pod。Pod通常被标记为Evicted节点上可能伴随MemoryPressure、DiskPressure等条件。判断方法很简单kubectl get pods -o wide | grep -E OOMKilled|Evicted kubectl describe pod pod-name | grep -A 10 State:State里如果是OOMKilled最后一行通常会写着reason: OOMKilled或者Exit Code: 137。137这个退出码值得记一下它等于128 9即进程被SIGKILL信号杀死的标准退出码而OOM Killer用的正是这个信号。如果是驱逐kubectl describe里会有Status: Evicted且events里能看到Evicting的日志。回到当时的情况我手上的Pod全部是OOMKilled所以排查目标很明确要么改代码降内存要么改资源配置。考虑到业务流量和代码都没有明显异常我倾向于后者。2.2 节点层和容器层都要看别被表象骗了还有一个容易踩的坑系统内存充足不代表容器安全。free -h显示还有大几十个GB可用但容器还是被杀原因在于cgroup限制是独立于物理内存总线的。你需要同时检查节点层和容器层的指标。节点层看的是kubectl top nodes容器层看的是kubectl top pods。但对于快速定位来说kubectl describe node里的Allocated resources字段更直观它会告诉你当前节点所有Pod的requests之和、limits之和以及相对于节点容量占用多少百分比。那次事故里节点的Allocated数据显示limits.memory已经占了节点内存的98%而实际用量只有40%左右。我当时就意识到问题不在物理资源短缺而在于我分配给Pod的limits像一个紧箍咒把每一个容器的内存天花板压得太死导致业务一有波动就撞头。这个观察直接决定了后面整个优化方向。3. 逐步定位从看起来都正常到抓到元凶3.1 从事件和指标里找异常特征既然目标是搞清楚为什么被杀第一步必然是全面收集事件和指标。我按下面的顺序操作了一遍建议你也按这个顺序来# 1. 查看集群事件过滤出与Pod生命周期相关的记录 kubectl get events --sort-by.metadata.creationTimestamp | grep -i kill # 2. 查看目标Pod的详细状态 kubectl describe pod namespace/pod-name # 3. 查看目标Pod的资源占用历史 kubectl top pod pod-name --containers # 4. 进入容器看cgroup实际限制 kubectl exec -it pod-name -n namespace -- cat /sys/fs/cgroup/memory.max kubectl exec -it pod-name -n namespace -- cat /sys/fs/cgroup/memory.current在kubectl describe pod的输出里重点看Last State字段。如果Last State: Terminated且Reason: OOMKilled那基本就锁定是内存问题。如果RestartCount特别高说明这个容器反复被杀又反复拉起不是一次性的偶发问题。还有一个容易被忽略的细节kubectl top pod看到的是当前快照不代表峰值。OOM Killer是在瞬间发生的当时的真实峰值可能已经超过了limits.memory而等你执行命令时内存已经被回收了看起来一切正常。所以如果条件允许最好用Prometheus cAdvisor的历史数据去看container_memory_working_set_bytes的时间序列找出被杀之前的那个最高点。我这里因为还没有接到监控系统只能从events和容器启动时间推算结果发现重启周期很有规律大约每45分钟一次每次重启前都没有明显的CPU飙升。这个规律性本身就指向固定阈值触发而不是随机性的代码崩溃。3.2 三张表快速定位被杀级别为了帮助你更快进入排查状态我把kubectl describe pod输出里常见的几个关键定位信息整理成了对照关系建议先过一遍这张表再决定下一步动作。现象关键状态常见原因下一步动作Pod重启退出码137OOMKilled容器内存超过limits.memory调大limits或优化业务内存Pod重启退出码1Error应用启动失败、配置错误看应用日志检查环境变量Pod显示EvictedEvicted节点磁盘/内存达到驱逐阈值清理节点资源调整驱逐策略容器不重启但无响应Running但readiness探针失败健康检查路径/端口错误检查探针配置查看探针日志那次事故的所有Pod清一色落在第一行只有OOMKilled所以我才敢跳过代码层的排查直接聚焦到资源限制字段上。如果同一时间出现OOMKilled和Evicted混合的情况那就要多个维度一起看先解决掉节点的驱逐问题再回过来看OOM否则排查顺序会乱。3.3 查看容器启动时间与业务峰值的对应关系另一个有价值的观察维度是时间序列对比。把每个Pod的STARTED时间和业务访问量曲线放在一起看能判断出被杀事件和业务高峰是否有重叠。当时我拉了一份Pod列表kubectl get pods -n production -o wide | awk {print $1, $5, $6}发现订单服务的Pod重启时间刚好卡在下午两三点和晚上八九点这两个业务高峰段。这两个时间段对应的是数据库批量查询和定时任务集中执行内存占用会明显冲高。这就进一步验证了业务峰值瞬间内存 limits.memory。所以排查时一定不能只看静态的当前状态要把时间维度纳入分析。这类问题如果你只看凌晨低峰期的正常会被表象迷惑很久。4. 根因拆解资源限制的坑到底在哪里4.1 requests和limits的关系很多人从第一天就理解错了在K8s里requests和limits是配额体系里最核心的两个字段但不少同学配置的习惯是差不多就行。requests决定调度kube-scheduler会统计所有节点上Pod的requests之和选出剩余容量足够的节点它的本质是我保证给你留这么多。limits限制了运行时的资源上限cgroup会强制执行一旦超过这个阈值内核的OOM Killer就会介入。这个机制本身没问题但有两个隐藏逻辑值得注意第一requests和limits可以不一致但调度只看requests运行只看limits。如果你只设置了limits没设置requestsK8s会默认把两个值设为一致。如果你设置了requests比limits小调度时按小值计算运行时按大值限制。这中间存在一个错位节点以为某Pod只需要1Gi但实际操作中允许它冲到2Gi一旦多个Pod同时冲到limit节点物理内存就会被打爆触发更可怕的节点级OOM。第二limits里的memory不是业务进程占用的全部还包括页面缓存等内核可回收内存。container_memory_working_set_bytes这个指标里cgroup计算时会把文件缓存也算进去。如果你没有正确设置readiness探针或者应用自己没有做优雅退出前的缓存清理很多在内存里挂着的缓存页也会被计入配额。我那次出问题的配置长这样resources: requests: cpu: 500m memory: 1Gi limits: cpu: 1 memory: 1.5Gi表面看很合理请求1Gi限制1.5Gi留了0.5Gi余量。但业务的实际内存画像是什么高峰期瞬间能达到1.8Gi低峰期只有600Mi。也就是说低峰期余量很大高峰期直接超了limit 20%。这0.5Gi的余量在全链路调用暴增的瞬间根本不够缓冲于是OOM Killer精准狙杀。4.2 OOM Killer的评分机制决定了谁会先死这里补充一点底层原理搞清楚OOM Kill怎么选择幸运儿后面调整参数才有依据。Linux内核的OOM Killer会为每个进程算一个oom_score得分越高越容易被杀掉。这个得分主要看两个维度oom_score_adj进程的受保护度。K8s在创建Pod时会根据QoS等级给容器设置对应的oom_score_adj。内存占用百分比实际使用的物理内存占整体内存的比例。K8s的QoS等级有Guaranteed、Burstable、BestEffort三类对应关系如下QoS等级配置形式oom_score_adj被杀优先级Guaranteedrequests limits-998最低Burstablerequests limits默认区间中等BestEffort不设置requests/limits1000最高在我那个案例里订单服务配置的是Burstable因为requests和limits不相等。如果同一节点上同时有一个Guaranteed的Pod内存爆了和一个Burstable的Pod内存爆了内核会更倾向于先杀Burstable的那个。这就会造成一种现象某个Pod本身不是最占内存的但它优先级低于是成了替死鬼。这个原理的实操启发是重要业务别用Burstable级别尽量让requests和limits相等把QoS提到Guaranteed让内核在极端情况下尽量保护你的进程。代价是你必须严格压制内存上限或在配好limit的同时保证业务峰值不超过它。如果业务本身内存波动大Guaranteed不一定最优但你可以从另一个角度来做——把limits放宽到业务真实峰值的1.2倍以上避免它动不动就触发OOM。4.3 节点级别的cgroup v2坑的位置还不一样如果你的K8s集群跑在较新的系统上比如Ubuntu 22.04、CentOS Stream 9大概率已经启用了cgroup v2文件路径和语义都有变化。cgroup v1时代内存控制的文件在/sys/fs/cgroup/memory/memory.limit_in_bytescgroup v2时代变成了/sys/fs/cgroup/memory.max。如果你还用老路径去排查会直接cat: No such file or directory。更关键的差异在于cgroup v2 在计算内存占用时把内核的不可回收内存也纳入统计而且对memory.current的统计口径更严格。这意味着同样一份配置在cgroup v1上可能勉强不触发OOM在cgroup v2上就可能频繁被杀。Kubernetes在1.25版本后GA了cgroup v2支持新集群默认都在这个体系里。所以排查时要先确认节点cgroup版本stat -fc %T /sys/fs/cgroup/如果是cgroup2fs就是v2。如果是tmpfs就是v1。不同版本对应的路径、语义、触发表现都不同这个确认动作能帮你少踩很多坑。5. 解决方案从改配置到补监控全套落地5.1 方案一调大limits先止血不管后面做什么优化第一步必须先把正在业务高峰期的Pod保住否则排查还没结束用户投诉就已经把电话打爆了。我当时采取的是最直接的止血措施把limits.memory从1.5Gi调到2.5Girequests.memory从1Gi调到1.5Gi。调整方式有两种第一种是直接kubectl edit deployment改YAMLkubectl edit deployment order-service -n production将resources字段按下面的格式更新resources: requests: cpu: 500m memory: 1.5Gi limits: cpu: 1 memory: 2.5Gi保存后Deployment会自动触发滚动更新。注意limits只能增大如果调小的话对于已经超过新limit的容器kubelet不会主动杀它但要等它重建后才生效这个过程中表现会非常奇怪——看着好像改配置了实际上旧Pod还在按旧配置跑。第二种方式是通过kubectl patch适合在自动化平台或脚本里快速操作kubectl patch deployment order-service -n production -p {spec:{template:{spec:{containers:[{name:order-service,resources:{requests:{memory:1.5Gi},limits:{memory:2.5Gi}}}]}}}}调整完成后观察几个Pod的重启次数kubectl get pods -n production -l apporder-service -o custom-columnsNAME:.metadata.name,RESTARTS:.status.containerStatuses[0].restartCount正常来说滚动出新Pod后RestartCount会归零或者停留在个位数。如果几个小时后还是稳定增长说明2.5Gi也压不住业务的真实峰值那就得回头查业务代码或JVM参数了。注意线上止血时limits不建议一次性调到特别大比如4Gi、8Gi原因有二一是你不知道节点能不能扛得住这么多Pod同时超配二是limits调太大后调度器可能把过多Pod塞到一个节点上反而埋下节点级OOM的隐患。5.2 方案二量化业务内存画像别靠猜止血只是临时措施真正要解决频繁被杀的问题必须搞清楚业务内存的时间分布画像。这里我给出一套可以复用的量化方法需要用到Prometheus或者云厂商的监控面板。主要看三个指标container_memory_usage_bytescgroup内存当前使用量container_memory_working_set_bytes容器工作集内存即进程实际使用的物理内存量K8s官方和kubelet执行内存回收时主要参考这个container_memory_max_usage_bytes容器历史最大内存使用量找一个业务高峰段至少看连续7天的数据把每个Pod的container_memory_working_set_bytes最大值、P99、平均值都统计出来。我当时的结论平均值700MiP991.8Gi最大值1.95Gi这是一个非常典型的高波动内存型业务平时很省一到批量任务就脉冲式涨。针对这种画像我把limits.memory定到了2.5Gi——比P99高出大概40%的缓冲比最大值也留了30%左右的空间。原因很简单CPU有Request/Limit的配额平滑机制内存没有要么在limit内活着要么撞墙被杀。内存的limit设置必须给足承压冗余。同时我建议把requests.memory定得比平均值略高但不要超过P50太多。这样调度时既能保证Pod被均匀分散避免所有高波动Pod堆到一个节点又不会因为requests太大导致节点可调度资源紧张。5.3 方案三启动探针与优雅退出配置给足逃生通道这里还有一个很多运维容易忽略的点应用自己不做优雅退出和内存清理即使你调大了limits迟早还会再触发。我在压测和线上复盘中反复验证过这个结论。一个健康的Pod生命周期应该是这样的收到SIGTERM信号后应用先摘流量再等存量请求处理完然后再退出。但在默认配置下很多Java/Python应用没有捕捉SIGTERM信号kubelet一发出删除命令应用进程就来不及做任何清理直接死掉期间内存还可能又冲高了一次。为了给应用更多处理和清理的时间给容器加上terminationGracePeriodSeconds和preStop钩子spec: terminationGracePeriodSeconds: 30 containers: - name: order-service lifecycle: preStop: exec: command: - sh - -c - sleep 10这段配置的意思很直白删除Pod时先执行sleep 10给存量请求一个排空时间整个终止宽限期30秒超过这个时间kubelet才会执行SIGKILL。虽然sleep 10这种方法被人说不优雅但在线上快速止血阶段它确实能有效降低重启时的内存尖峰避免因为进程被杀导致的级联OOM。如果你的应用本身实现了优雅停机那preStop可以放得更短甚至去掉。关键是你要确认一件事从收到SIGTERM到进程完全退出所需的时间是否小于terminationGracePeriodSeconds。如果配置时间小于应用实际退出时间kubelet会在超时后暴力SIGKILL问题依旧。5.4 方案四HPA与垂直扩容双管齐下如果你发现即使调大limits节点内存总量也快撑不住了那就要考虑从单个Pod变大转向多开Pod分摊。HPAHorizontalPodAutoscaler可以基于内存或CPU指标自动伸缩副本数。以下是一个基于内存利用率的HPA配置注意它依赖metrics-server提供指标kubectl autoscale deployment order-service -n production \ --cpu-percent70 \ --min3 \ --max10不过基于memory的HPA有个小坑它用的是Pod的requests作为分母的百分比而非实际limit。如果你的request定的很小哪怕实际内存已经用到limit的90%HPA算出来的利用率可能只有70%不到导致迟迟不扩容。所以HPA公式里的分母是requests想让HPA对你业务有实际保护作用requests必须贴近真实用量不能定得过低。我那次实战的顺序是先手动调limits恢复再核对指标数据最后配置HPA。实际跑下来副本数从3扩展到7单Pod的压力降到了1.2Gi左右重启次数归零。5.5 方案五节点压力驱逐策略兜底的最后一道防线还有一个重要的兜底配置是kubelet的节点驱逐策略。默认情况下当节点内存达到memory.available低于100Mi时kubelet会开始驱逐Pod这个默认值对很多高内存节点来说并不合理。建议在kubelet配置里加上更合理的软、硬驱逐阈值evictionHard: memory.available: 500Mi nodefs.available: 10% nodefs.inodesFree: 5% evictionSoft: memory.available: 1Gi evictionSoftGracePeriod: memory.available: 1m30s注意这里的memory.available是节点维度指的是节点总内存 - 所有Pod已用内存总和。把硬驱逐阈值从默认的100Mi提到500Mi可以让kubelet在真正山穷水尽之前就开始清理不重要的Pod而不是把某个业务Pod往死里压榨。但这个兜底措施有个前提你需要对节点上部署的Pod做量化分级哪些是核心业务Guaranteed哪些是边缘任务Burstable/BestEffort哪些可以被驱逐。否则kubelet驱逐时可能优先把你正在压测的重要Pod干掉了那就得不偿失。6. 常见问题与排查技巧实录6.1 经典QA排查资源限制问题时的高频疑问Q1为什么调整limits后Pod还在被杀答大部分情况是内存画像没有摸透新限制依然低于峰值。检查历史监控数据尤其是container_memory_max_usage_bytes的7天峰值。如果峰值高于你现在设置的limit调了也白调。Q2为什么我设置了limits但节点内存还是被打满答这通常是因为你把requests调得太低导致调度器把过多Pod塞到同一个节点。limits总和可以超过节点容量这就是超卖当多个Pod同时冲到limit节点物理内存就会被耗尽TKE/ACK等云厂商环境下还可能触发云平台层面的热迁移或强制隔离。不要把超卖比例放得太大尤其不要超过1.5倍。Q3怎么确认是cgroup的OOM而不是业务自身panic答看两个地方。第一是Pod状态里的Last State. Reason是OOMKilled就是cgroup OOM是Error则多半是应用崩溃。第二是看内核日志journalctl -u kubelet或者dmesg | grep -i oom会看到类似Memory cgroup out of memory或Killed process xxx的记录。Q4修改资源限制后为什么没有触发滚动更新答因为kubectl edit改的是Deployment模板Deployment只会在template里内容变化时才滚动更新。如果你不小心只改了spec.replicas或者改了metadata相关的字段template哈希没有变化当然不会触发。确认改动后执行kubectl rollout status deployment/order-service -n production观察是否真的进入了滚动更新的流程。6.2 我踩过的几个隐形坑提前帮你排雷第一个坑是对Java应用设置limits.memory时忽略了堆外内存。JVM的-Xmx限制了堆但还有Metaspace、线程栈、DirectByteBuffer、JNI等堆外内存。如果limits.memory只容下了堆堆外一涨照杀不误。我的建议是JVM应用的memory limit至少要比-Xmx大25%~30%且配合-XX:MaxDirectMemorySize等参数一起控制。第二个坑是NodePressure导致调度器误判。有一个比较隐蔽的场景节点上某块磁盘满了kubelet报DiskPressure然后会把大量Pod标记为Evicted。这种被驱逐和内存OOM无关但表现都是Pod频繁被杀。所以看到Pod被杀时先看一下kubectl describe node里的Conditions把MemoryPressure和DiskPressure都查一遍。如果只有DiskPressure那么你调内存limits就是在白费力气。第三个坑是云厂商节点自动恢复机制带来的假重启。部分云环境里节点如果进入了NotReady状态管控系统会在几分钟内强制重启该节点或迁移Pod。在集群events里看只看到一堆Pod被杀但原因可能不是资源限制而是节点本身出了故障。遇到这种情况光看Pod的events远远不够一定要同时检查节点日志和云平台侧的状态记录。6.3 一些快速定位的实用命令收藏就会用最后整理一份我实际排查时用到的命令清单按使用频率排列。你可以复制到自己的笔记里遇到类似问题直接套用。# 查看所有异常状态的Pod kubectl get pods -A | grep -v Running | grep -v Completed # 详细查看单个Pod的状态和事件 kubectl describe pod pod-name -n namespace # 查看节点实际资源分配 kubectl describe node node-name | grep -A 20 Allocated resources # 进入容器查看cgroup限制cgroup v2 kubectl exec -it pod-name -n namespace -- cat /sys/fs/cgroup/memory.max kubectl exec -it pod-name -n namespace -- cat /sys/fs/cgroup/memory.current # 查看容器内存监控指标需要metrics-server或Prometheus kubectl top pod -n namespace --containers # 查看kubelet日志中的OOM记录 journalctl -u kubelet --since 2 hours ago | grep -i oom # 查看内核OOM日志 dmesg -T | grep -i out of memorydmesg -T那条命令要在节点上执行注意权限。内核日志里通常会明确告诉你Killed process 12345 (java) total-vm:...kB, anon-rss:...kB那个java就是被牺牲的那个进程。如果你看到的是Killed process后面跟着的是容器里的进程而不是PID 1说明是cgroup OOM如果直接杀掉的是Kubelet或containerd相关进程那就是节点级别的OOM严重得多基本可以判定节点已经物理内存耗尽。7. 一套可以直接抄作业的资源配置模板说了这么多原理和分析最后给你一套我在多个环境实际用过的、经过验证的配置模板。这不是万能的但它能解决大多数默认配置拍脑袋导致的Pod被杀问题。你可以根据自己的业务类型做调整。7.1 无状态Web服务的推荐配置这类服务的特点是流量有波动但整体可控内存消耗相对平稳对延迟敏感。resources: requests: cpu: 200m memory: 512Mi limits: cpu: 1 memory: 1Girequests定到512Mi是为了保证调度时像样的节点都放得下同时给HPA留出足够的膨胀空间limits定到1Gi比requests翻了倍给GC、缓存和瞬时批量操作留了缓冲。注意cpulimit不是必须卡死如果没有CPU密集型任务limits.cpu可以干脆不设让它用满但CPU的requests建议保留帮助调度器合理分布。7.2 高并发批处理任务的推荐配置这类服务的特点是内存峰值波动极大峰值来得快、去得也快对实时性要求不高副本可以随时杀死重建。resources: requests: cpu: 500m memory: 1Gi limits: cpu: 2 memory: 4Gilimits.memory我直接放到了4Gi比requests大了4倍。这种超卖看起来有点夸张但批处理任务平时确实只需要1Gi左右只有跑批那几分钟会暴力冲到3.5Gi。前提是节点总容量能扛住多副本同时冲高的风险建议给这类任务单独用一台节点池避免和在线业务混部。7.3 Java/Go中大型微服务的推荐配置这类服务是资源限制问题的高发区。因为JVM/Go runtime本身有自己的内部内存管理机制如果和K8s的cgroup没有配合好非常容易触发连锁OOM。resources: requests: cpu: 1 memory: 1Gi limits: cpu: 2 memory: 2Gi这里有个细节Java应用如果用了-XX:MaxRAMPercentage70且K8s的limits.memory设置为2Gi那么JVM堆最多只能用到约1.4Gi剩下的600Mi留给堆外、线程栈和GC overhead。如果你发现JVM频繁Full GC还是被OOM杀大概率是堆外内存涨破了那600Mi空间这时候需要从业务代码或框架层面排查堆外内存泄漏而不是继续调大K8s limit。7.4 配置加好之后怎么验证才安心改完配置不是看一眼不再报错就完事了。我的习惯是至少做两项验证第一项是流量压测验证。用压测工具打到正常峰值的1.5倍持续5~10分钟观察kubectl top pod确认Pod内存实际使用量始终低于limitsRestartCount不再增长。第二项是历史数据复盘。大概3天后用Prometheus查询container_memory_working_set_bytes对比调整前后的最大值、P99和平均值。如果P99还是逼近limit说明缓冲不够继续调大如果P99只用了limit的50%说明你浪费了资源可以适当回收一点给其他业务。压测的小技巧不要一上来就全量打先用10%的流量观察5分钟确认内存曲线稳定后再逐步加压。我曾经图省事直接全量压测结果内存直接击穿limit一批新的OOMKilled又冒出来场面一度很难看。8. 最后说几句实在话排查K8s Pod频繁被杀这件事本质上是一场资源画像和基础设施配置之间的校准。大多数人栽跟头不是因为不懂K8s而是因为没有花足够时间去量化业务的真实资源需求曲线又过于相信模板和默认值。requests和limits不是填空题它们是你对业务运行规律的认知在配置层面的投影。再说了K8s默认给你的是够用但不聪明的策略默认Pod不设置requests/limits意味着QoS是最差的BestEffort内核OOM时最先拿你开刀如果你设置了limits但没设置requests等于默认requestslimits又没有超卖空间。所以每个Pod都应该根据业务特征显式声明资源而不是依赖平台的兜底。我自己现在每接到一个Pod频繁被杀的工单上来先不看代码先看它三天的内存监控曲线同时确认cgroup版本、QoS等级、节点压力条件。这三样对齐了80%的问题都迎刃而解。剩下的20%才是真正需要深入到应用层去解决的难题比如堆外内存泄漏、线程数超配、缓存无界增长这些。如果你正好也在被类似问题困扰按这篇文章的排查顺序走一遍大概率能在半小时内定位到具体环节。先确认是OOM还是驱逐再对比高峰期内存曲线和limits接着调配置止血最后把监控和HPA补上。一套组合拳下来K8s会从老杀你的Pod变成稳稳托住你的业务。
