news 2026/10/20 15:22:01

线上K8s故障排查:从Pod驱逐到PVC与Service异常的根因分析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
线上K8s故障排查:从Pod驱逐到PVC与Service异常的根因分析

前两篇我把 k8s 的组件组成和集群初始化讲完了,结果后台留言里问得最多的不是“怎么装”,而是“装好之后为什么老出怪问题”。这篇 k8s - 3 我换个思路,不讲架构图,不贴 YAML 模板,专门聊最近几个月在几个不同环境里处理过的线上问题:Pod 被驱逐、PVC 挂不上、Service 访问超时。这些问题的根因大多不在 k8s 自身的调度逻辑,而在资源模型、存储插件、网络组件之间的配合缝隙里。这篇文章不是给第一次接触 k8s 的纯新手准备的,而是给集群已经跑起来、但时不时被稳定性问题折腾到半夜的人看。

如果你属于“容器能跑通,YAML 能敲出来,但一上量就出幺蛾子”的阶段,这篇应该会对你有用。我会尽量省去控制面组件的原理重复,直接把矛头对准数据面和资源面:Pod 在节点上的生存质量、存储卷的挂载链路、网络访问的可疑环节。每个问题我都按“现象→排查→根因→目前能接受的规避方案”来写,尽量做到看完能直接套用到自己的环境里。

1. 先对齐一个观念:能调度成功和稳定运行是两码事

1.1 前两篇的进度,以及这一篇的抓手

前两篇讲完了 k8s 的基础组件和怎么把集群拉起来,但这只是起点。集群拉起来之后,你面对的就不再是“能不能创建”,而是“创建之后能不能活得久、稳不稳、出问题怎么定位”。调度器把 Pod 放到某个节点上,只代表那一刻节点上有名义上的剩余资源;不代表接下来几天这个节点不会因为磁盘压力、内存水位、PID 耗尽把你刚部署的服务撵走。

所以这一篇的抓手选在“稳定性”而不是“功能”。你可能已经看过很多文章讲 Pod 的 requests 和 limits 怎么填、Service 的 YAML 怎么写,但很少有人把这几个环节串成一条完整链路给你看。我在这篇里要做的就是把链路串起来:资源声明→调度→节点压力→驱逐→存储事件→网络规则,每一步都有对应的现象和日志,也有我实际踩过之后总结的定位方法。

1.2 你盯着控制面,我建议你先盯数据面

很多人集群出问题第一反应是看控制面组件,比如 apiserver、调度器这些日志。但根据我的经验,大部分线上故障现象出现在数据面:Pod 起不来、Pod 起来了但不健康、Service 有 Endpoints 但访问超时。这些现象背后的根因,往往藏在节点 kubelet 的决策、存储 CSI 插件的事件、网络组件维护的转发规则里。

因此这篇的章节顺序也按“数据面排查”来设计:先讲资源与驱逐(决定 Pod 能否存活),再讲存储挂载(决定 Pod 能否正常使用数据),后讲网络访问(决定流量能否正确进入 Pod),最后给一个踩坑后的检查清单。这个顺序不是说控制面不重要,而是说排障的时候必须按这条链路由下往上查,先把数据面的嫌疑排除干净,再回头怀疑控制面。

2. requests 与 limits:驱逐、OOM、优先级这些事全从这里来

2.1 requests 和 limits 的语义差异

先做一个可能有点反直觉的说明:k8s 里的资源模型不是“你给 Pod 分配了 1 核 2G,它就真的独占这些资源”。requests 是调度时的承诺值,limits 是运行时强制上限。调度器只看 requests,不看 limits。也就是说,两个 Pod 都声明 requests 为 0.5 核,但它们各自的 limits 可能是 1 核,也可能是 4 核;只要节点的可分配资源还够 0.5+0.5,调度器就会把两个 Pod 放在同一个节点上,即使它们之后都可能把 CPU 冲到 4 核。这个设计本身是合理的,它允许节点资源被超卖,但你必须知道超卖的代价是什么:节点 CPU 争抢、内存水位上涨、最终触发驱逐。

内存这边更容易踩坑。limits.memory 一旦设置,kubelet 会通过容器运行时为 Pod 创建对应的 cgroup 限制,内核在内存超限时会按 cgroup 的层级做回收和 OOM。很多人的误解是“Pod 内存超限应该被 kill 掉”,确实会,但 kill 的触发点不一定是 Pod 里的主进程。如果 Pod 里有多个容器,或者容器内部有多个进程组,内核的 OOM 选择器会结合 oom_score 来决定先杀谁,这经常导致一些你不认识的辅助进程先消失,而主进程还在苟活,表现出来就是“服务没崩,但状态不对”。这块光加内存没用,得先把 requests/limits 和 QoS 等级一起想清楚。

2.2 QoS 等级:为什么有的 Pod 先被驱逐

资源声明方式决定了 Pod 的 QoS 等级。三个等级分别是 Guaranteed、Burstable、BestEffort,优先级从高到低。只有“所有容器都同时设置 CPU 和内存的 requests 和 limits 且两者相等”的 Pod 才是 Guaranteed;只设置了 requests 或只设置了 limits 的算 Burstable;完全不设置的算 BestEffort。

这个等级直接决定节点内存压力下的驱逐顺序:先 BestEffort,再 Burstable,最后才轮到 Guaranteed。我见过不少线上事故,都是因为业务 Pod 图省事只写了 limits 没写 requests,结果节点一紧张,业务 Pod 比那些写完整的工具 Pod 更早被驱逐。记住一个原则:你越想让业务稳定,越要把 CPU 和内存的 requests、limits 都写上,并让它们相等。这不是教条,是 QoS 分级的实际规则。很多生产环境的最佳实践都会强调这个写法,原因就在这里。

2.3 一个真实案例:CPU 饥饿引发的 Pending

说个小案例。A 同学在某个环境里部署了一个计算任务,Pod 的 requests.cpu 写了 2,节点只有 4 核,两个同样规格的任务 Pod 被调度到同一节点。任务实际跑起来 CPU 瞬间超过 limits 的 4 核,结果 cgroup 的 CPU 配额把两个 Pod 都压到接近 2 核,加上系统本身还有基础进程要 CPU,任务从预期 30 分钟跑成了 90 分钟。

这还没完,节点负载升高后,kubelet 的 eviction manager 检测到节点内存压力信号,把节点标记为 MemoryPressure,新的 Pod 进入 Pending。A 同学一开始反复看调度器,觉得是资源不够,其实节点上的可分配资源还有,只是节点被标记了压力状态,调度器默认不会再往这个节点放新的 BestEffort 和高内存请求的 Pod。这种“看着像调度问题、实际是节点压力问题”的案例,没有资源模型的概念很难定位。后面我会在第 5 节展开节点压力怎么排查。

3. PVC 挂载失败的完整排查链路(从事件到插件日志)

3.1 现象:Pod 反复 FailedMount 或 CrashLoopBackOff

先说最常见的现象:Pod 里引用了 PVC,但 PVC 显示 Bound,Pod 事件里却反复出现 FailedMount,或者容器一直 CreateContainerConfigError。很多人第一反应是检查 PVC 的容量和 storageClass 名字,但这两个都没问题时,问题往往在挂载链路的下一层。

这里有个细节值得注意:PVC 显示 Bound 只代表 PV 对象与 PVC 对象完成了绑定,不代表底层卷真的可用了。存储系统的卷创建是异步的,尤其是一些云存储或网络存储,底层卷在创建过程中可能处于“预置中”状态,这会让挂载动作一直失败。你盯着 YAML 看半天看不出问题,因为问题根本不在 YAML 层面。

3.2 排查顺序:describe → 存储插件日志 → kubelet 日志

完整排查我先给一个可复现的顺序,这也是我自己踩过几次坑之后固定下来的操作链路。

第一步,用命令查看 Pod 详情,重点看 Events 段里 FailedMount 的完整原文。这个信息比你想的值钱,因为它会告诉你具体是“无法找到卷”还是“挂载超时”还是“权限拒绝”。这三种原因对应的排查路径完全不同,如果省略这一步直接去翻代码,基本是在浪费时间。

第二步,如果是“无法找到卷”,先去确认存储服务端侧的卷状态。很多存储系统有自己的控制台或者命令行,卷可能处于“正在创建中”或“异常”状态,表面上看 PVC 是 Bound,但底层卷没有被真正准备好,挂载自然失败。此时在存储服务端把卷的状态调正常,Pod 往往能在下一次重试后自动恢复。

第三步,如果卷正常,但挂载还是失败,那就去查存储 CSI 插件和 kubelet 的日志。存储插件的容器日志可以直接通过查看对应 Pod 日志拿到;kubelet 日志在节点上,一般用系统服务日志拉取。我遇到过一个案例,挂载失败的原因居然是节点上缺少某个内核模块,存储插件的初始化命令返回了一个非常模糊的错误码,但这个错误码在 kubelet 日志里对应着一条“operation not supported”,再往前翻才发现是文件系统类型不匹配。这类问题没有第二、第三步的日志根本定位不到。

3.3 容量扩容与回收的那些坑

PVC 的容量扩存在几个很隐蔽的坑。首先,很多存储插件的扩容需要卷处于可扩容状态,如果 PVC 用的是旧的 storageClass,或者存储系统本身不支持在线扩容,你改了 size 之后 PVC 会一直保持 Pending。其次,即使扩容成功,Pod 里的文件系统也需要手动 resizefs(有些 CSI 插件会自动做,有些不会),如果没做,df 看到的容量还是老的,业务层会以为扩容失败。

回收策略同样容易被忽略。PVC 的 reclaimPolicy 设成 Delete 时,删除 PVC 就会连带删除后端卷,这个操作没有二次确认。某个环境里一次误删 PVC,直接导致一批数据找不回来。我现在的习惯是:所有存数据的 PVC 一律用 Retain,宁可手动清理卷,也不要让删除动作变成数据事故。这一点在测试环境尤其重要,因为测试环境最容易出现“顺手删一下”的情况。

4. Service 访问不稳定:连通性之外的三处盲区

4.1 DNS 解析异常:第一层盲区

Pod 内访问 Service 最常见的问题不是路由不通,而是 DNS 解析异常。很多业务容器的镜像里没有 dig、nslookup 这类工具,一旦出现解析失败,排查效率非常低。我的建议是:镜像里至少保留一个可用的小工具,比如 wget 或 curl;同时在集群侧记录一次 DNS 查询日志,确认核心组件是否响应。

DNS 解析出问题的原因很杂:配置了无效的 search 域、上游 DNS 响应慢、Pod 内的 resolv.conf 被覆盖,甚至有时候只是容器的 /etc/hosts 缺了条目。用一个小技巧可以快速定位:进入 Pod 后先用 getent hosts 命令看解析结果,如果有结果但业务还是超时,问题在连接层;如果没有结果,问题在 DNS 配置或组件。不要一上来就怀疑 Service 路由,先把解析这关确认掉。

4.2 后端摘除延迟:滚动更新时的连接残留

另一个隐蔽问题出现在滚动更新期间:Service 背后有 Endpoints 列表,但 Pod 从 ready 变成 not-ready 到 Endpoints 真正移除,中间存在一段时间差。如果业务 Pod 在收到终止信号后不主动排空连接,而 Service 的 targetPort 还在转发,就会出现请求打到正在销毁的 Pod 上,造成偶发超时。

这也是为什么我强调要配置 preStop 钩子和合理的 terminationGracePeriodSeconds。preStop 里 sleep 几秒,让前端的负载均衡器或调用方把连接切走,比在代码里反复处理“连接被重置”要省事得多。通常 sleep 5 到 10 秒就够,具体要看 Service 的更新频率和调用方的超时设置。如果你发现滚动发布时总有少量 5xx,这个方向优先检查。

4.3 节点网卡与连接跟踪表:沉默的三号盲区

第三种盲区最容易被忽略:节点网络层。流量从 Service 转发到 Pod,后端是 iptables 或 IPVS 规则,这些规则由网络组件维护。规则本身一般不会坏,但连接跟踪表(conntrack)可能会满。当一个节点上连接数非常高,conntrack 表被占满,新连接会被丢弃或超时,表现为“通但不稳定”:偶尔通,偶尔超时,重试几次又好了。

定位方式很简单:在节点上查看 conntrack 统计,或者观察内核日志里有没有 conntrack 相关警告。如果确认是这个问题,常规做法是调大 conntrack 上限、缩短超时时间、或者优化业务侧的连接复用。我这里更想提醒的是:很多“Service 不稳定”的锅最后都甩给网络组件,实际上大部分是 conntrack 压力和后端摘除延迟两个原因,它们都在节点侧,不在组件规则侧。

5. 节点压力导致的连锁反应,比你想的更隐蔽

5.1 磁盘压力与镜像清理

节点压力不只是内存压力一种。磁盘压力(DiskPressure)是另一大类,表现是节点上有镜像无法拉取、Pod 无法创建新容器,甚至已有容器被驱逐。磁盘压力经常不是因为你业务写了很多数据,而是容器日志、镜像层垃圾、临时文件累积导致。

我遇到过这样一个场景:一个节点上并存多个应用,日志没有做轮转,单个日志文件涨到几十 GB,kubelet 在周期性检查可用空间时触发了驱逐信号。当时我第一反应是“磁盘快满了”,df 一看根分区还有空间,问题出在 kubelet 配置的镜像目录和日志路径挂载在一个独立分区,那个分区已经 90% 了。所以排查磁盘压力,先确认 kubelet 的根目录和数据目录各指向哪个分区,再去查对应的容量,别被根分区的剩余空间误导。

5.2 PID 压力:最冷门的驱逐信号

内存和磁盘很多文章都讲过,PID 压力(PIDPressure)反而是最容易被忽略的。节点上的 PID 数量有上限,如果某个节点的进程或线程总数过高,新容器可能无法创建进程,kubelet 会把节点标记为压力状态。这类问题在 Java 类应用、大量线程的中间件上特别常见。

诊断 PID 压力,直接用节点系统命令查看当前 PID 数量和上限,对比 kubelet 或内核日志里的压力信号即可。处理手段有两层:治标的做法是给节点上的 Pod 设置合理的 PID limit,避免单个 Pod 的线程无限膨胀;治本的做法是排查应用层创建了大量线程或协程泄漏。我见过一个中间件因为配置了过大的连接池,每个连接都起线程,直接把节点 PID 撑爆,最后整个节点上的新 Pod 都起不来。

5.3 驱逐预算:留多少余量才算合理

如果把驱逐视为 kubelet 的自我保护机制,那你要做的不是“让驱逐永不发生”,而是“让驱逐尽量发生在不重要的 Pod 上”。这也是为什么要设置 PodDisruptionBudget(PDB),它约束的是自愿中断场景;对非自愿的节点驱逐,PDB 不直接拦,但它能帮助你在滚动更新和集群维护时避免一次掉太多副本。

实际项目里,我会给有状态服务配置 PDB,允许的最小可用副本数保持在 n-1 或 50% 左右;同时把核心服务的 QoS 提升到 Guaranteed。这样即使节点压力真的来了,最坏情况也是先驱逐非核心 Pod,核心服务有更高的存活概率。这个“预算”思路比单纯调大节点内存更实用,因为节点资源总有限,关键是让有限资源优先保谁。

6. 我沉淀下来的排查顺序与检查清单

6.1 一个固定的排查顺序

历经各种线上问题之后,我给自己定了一个固定顺序,遇到“Pod 不稳定、Service 访问异常、存储挂载失败”这类问题,基本按这个顺序走,能省一半时间:先看现象和事件(describe 和 get events 是第一步),再看节点水位(节点内存、磁盘、PID),然后看对应组件日志(存储插件、网络组件、kubelet),最后才去翻业务代码和配置。顺序不能乱,否则很容易在业务侧浪费大量时间,最后发现根因在节点层。

我见过不少同事一上来就进 Pod 调代码,折腾半天发现是节点压力驱逐;也见过有人反复重建 Service,结果问题在 conntrack 表。固定顺序的意义不是迷信,而是让你在慌乱的时候有个不犯错的操作基线。排障最怕的不是慢,是乱。

6.2 可以直接抄走的检查清单

最后放一张我实际在用的检查清单,你可以直接拿去对号入座。

检查项常用命令或方法常见根因
Pod 反复重启describe 查看 Pod、日志查看容器requests/limits 不合理、探针过严、节点压力驱逐
PVC 一直 Pendingdescribe 查看 PVCstorageClass 不存在、容量超配额、存储插件未部署
FailedMountdescribe 查看 Pod 事件后端卷异常、节点缺内核模块、CSI 插件日志报错
Service 偶发超时进入 Pod 用 getent 解析、节点查 conntrackDNS 解析慢、后端摘除延迟、conntrack 表满
节点 MemoryPressuredescribe 查看节点requests 总量超过节点可分配、有 Pod 内存泄漏
节点 DiskPressure查看 kubelet 数据目录所在分区日志未轮转、镜像残留、临时文件累积
节点 PIDPressure节点查看 PID 上限与当前值应用线程泄漏、连接池过大

这张表我打印过贴在工位边上,每次排障直接按表格拉一遍。它不保证一次定位,但能保证你不会在错误的方向上停留太久。时间在故障处理里是最贵的,方向错了比查得慢更致命。

最后再分享一个很实际的体会:k8s 的排障没有银弹,但它有“链路意识”。只要你始终记得资源声明、节点状态、存储卷、网络转发这四层是层层联动的,出问题时从上往下或从下往上顺一遍,大多数问题都能在半小时内缩小到具体组件。这篇 k8s - 3 如果只能留下一句话,我希望是:别只在控制面找答案,去节点、去事件、去日志里看数据面到底发生了什么。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 15:22:00

北斗B1I信号软接收机仿真包:从ICD合规生成到闭环跟踪验证

简介:本资源是一个面向卫星导航领域初学者与科研人员的北斗B1I信号软件接收机仿真工具包,聚焦于北斗导航系统B1I频点信号的生成、捕获、解扩与接收全流程建模。适用于高校通信/导航方向课程实验、GNSS算法验证及软接收机原理教学,帮助用户深入…

作者头像 李华
网站建设 2026/10/20 15:21:15

10个AI论文网站实战指南:自考生毕业论文全流程写作

自考毕业生的毕业论文,恐怕是很多人整个自考过程中最头疼的一环。平时上班、带娃、处理家里一堆杂事,学习全靠挤时间,好不容易把所有科目都考完了,最后一关却卡在一篇上万字的论文上,谁碰上都会心态崩。我前前后后帮身…

作者头像 李华
网站建设 2026/10/20 15:20:16

用Docker Compose部署Zabbix 7.4监控平台:从配置到告警的完整实践

想了很久在监控工具里到底该选谁,最后还是把 Zabbix 7.4 用 Docker 跑了起来。这套组合不是灵光一闪,而是因为 Zabbix 本身是开源监控里功能最完整的那一档,加上 7.4 这个版本在可视化、告警和采集性能上都做了不少优化,Docker 又…

作者头像 李华
网站建设 2026/10/20 15:19:35

从零搭建代码评审自动化流水线:踩坑与最终方案

1. 从零搭建一套代码评审自动化流水线,我踩过的坑和最终方案代码评审这件事,说大不大,说小不小。一个人写代码的时候怎么都行,但只要是两人以上的协作项目,评审环节就是代码质量的最后一道闸门。我待过几个不同规模的研…

作者头像 李华
网站建设 2026/10/20 15:17:46

无法生成博客:输入信息严重缺失

我无法基于当前输入生成符合要求的博文。 原因如下: 输入中仅提供了项目标题 "rea" ,未提供任何有效上下文: 项目正文 为空( 项目正文: [通常比较零散、不完整的原始描述,可是任意领域内容] &#x…

作者头像 李华
网站建设 2026/10/20 15:16:54

无人机热成像光伏巡检:从数据集构建到模型部署的深度学习实战指南

最近在整理一个低空无人机光伏巡检的深度学习项目,任务很聚焦:用无人机航拍的热红外图像识别光伏板上的热斑、裂缝、隐裂和二极管异常。这类项目现在不算新鲜,但真正做完一轮你会发现问题不少——网上很多教程都在教调参,却很少讲…

作者头像 李华