资讯动态

Kubernetes Pod 完全指南:从基础概念到故障排查

发布时间:2026/10/22 7:08:43 来源:尧图企业网站定制
Kubernetes 入门绕不开 Pod无论你用的是 Deployment、Job 还是 DaemonSet调度、编排、网络、存储这些概念最终都要落到 Pod 上。我刚开始接触 K8s 时被这个抽象概念折腾得够呛明明 Docker 容器用得好好的为什么还要在外面套一层 Pod同一个 Pod 里的容器到底共享什么Init 容器是干嘛的探针配不对会有什么后果这篇笔记打算把这些一次性讲透从一个“最小调度单元”的官方定义聊到创建、生命周期、资源限制、调度策略和排错实战过程中会把那些文档里不会写、但实际运维中一定会踩的坑也说出来。适合刚学完 kubectl 基础命令、准备深入 K8s 核心机制的读者也适合在写 Pod 相关 YAML 之前想先扫一遍雷的开发或运维同学。1. 先搞清楚 Pod 的本质为什么不是直接跑容器我第一次用 K8s 时脑子里最大的疑问就是容器已经是隔离和打包的粒度了K8s 直接调度容器不就行了为什么非要再抽象一层这个疑问不解决后面所有内容都是空中楼阁。1.1 容器已经够好了Pod 这层东西到底解决了什么Docker 时代我们习惯的做法是“一个容器一个进程”多个进程之间如果需要协作就用 docker-compose 把它们拉到同一个网络里再把共享数据卷挂进去。这套方案在小规模场景下是能跑通的但一旦到了集群、调度、弹性伸缩的维度问题就暴露了调度的最小单位是容器那“一组必须泡在同一台机器上的容器”该怎么处理比如业务容器旁边必须要有一个日志采集容器或者一个流量代理容器它们之间是强绑定关系要么一起调度要么干脆都不调度。Pod 的诞生的正是为了解决这个“容器组”的调度难题。Pod 是 Kubernetes 中最小、也是最基本的调度单元一个 Pod 可以包含一个或多个容器。同一个 Pod 内的容器共享同一个网络命名空间、共享存储卷、共享 IPC 等资源可以看作是一组“逻辑主机”上运行的紧密协作进程。这个设计最直观的收益是网络模型被简化了。同一个 Pod 内的容器共享一个 IP通过 localhost 就能互相访问端口冲突必须在容器设计时就避开。而 Pod 之所以能共享网络命名空间底层的核心是一个叫“pause 容器”的机制。kubelet 在创建 Pod 时会先启动一个什么也不干、只负责持有网络命名空间的占位容器其他业务容器通过 join 的方式挂到这个网络命名空间上。这样即使业务容器因为崩溃被重启了Pod 的 IP 不会变网络配置不会丢对调用方来说是透明的。你可以把 Pod 理解成一个“家”pause 容器是房子本身业务容器是住在里面的家庭成员。家庭成员可以换人但房子地址不变有人生病了管家会把药送到家里而不是直接把整个家搬到医院去。这个类比你可以在后续理解 Service、Deployment 时反复用上——K8s 的很多行为逻辑都是围绕“Pod 是可以消失和重建的但 Pod 作为逻辑单元的身份必须稳定”这一原则展开的。1.2 单容器 Pod 和多容器 Pod 的两种形态实际生产里最常见的当然是单容器 Pod也就是一个 Pod 里只放一个业务容器。这种形态最直接的优点生命周期简单、日志定位容易、资源评估也不复杂。绝大多数无状态应用比如 Nginx、API 网关、Web 后端用单容器 Pod 就够了。但 Pod 真正的价值在于多容器组合。常见的有三种组合模式一种是 sidecar 模式也就是边车模式。主容器负责核心业务旁边挂一个辅助容器比如日志采集器、健康检查代理、端口转发器等。日志采集器通过共享的 emptyDir 卷读取主容器产生的日志文件推送到底层日志系统这样主容器镜像里不需要预装采集工具镜像体积和职责都能保持纯粹。另一种是 adapter 模式这个比较少见但很典型。主容器暴露的接口格式不符合上层的预期格式adapter 容器负责把数据格式转换后再暴露出去。比如主容器输出 JSON 格式日志adapter 容器把它转换成特定系统要求的格式。还有一种是 ambassador 模式相当于给主容器配了一个“外交官”。主容器连接数据库时只连 localhost 的固定端口ambassador 容器负责把流量代理到真正的数据库实例上这样切换数据库环境时主容器不需要改任何配置。对于一个 Pod 内想放多个容器的场景必须清醒意识到一个重要约束Pod 内所有容器的生命周期是一致的。一个容器挂了整个 Pod 会被 kubelet 按重启策略处理不是只重启那一个“坏掉的容器”。所以如果你只是想把多个互不相关的进程塞到一台机器上多容器 Pod 并不是一个好方案——那应该用多个 Pod 加节点亲和性来实现而不是硬塞进一个 Pod 里。除了普通业务容器Pod 还有一种特殊的容器类型叫 Init 容器。Init 容器在 Pod 的正式容器启动之前按顺序执行全部跑完后 Pod 才会进入正式容器启动流程。它和普通容器最大的区别是跑完就退出且不支持 readiness 和 liveness 探针。常见用途包括等待依赖服务就绪比如数据库连上才继续、下载初始化文件、修改目录权限、生成配置文件。如果有多个 Init 容器它们会严格按 YAML 里的顺序逐个执行前一个不成功后一个不会启动。1.3 Pod 的网络和存储细节别等到使用时才补课在理解 Pod 的过程中Node、网络命名空间、CNI、emptyDir 这些概念容易混淆。简单梳理一下Pod 是 Kubernetes 调度的最小单位它运行在 Node 上Node 是 Kubernetes 集群中的一台工作节点。Pod 创建时kubelet 除了启动 pause 容器和业务容器还会通过 CNI 插件为 Pod 分配网络。Pod 的 IP 是整个集群内可达的但它是动态的、可变的所以一般不会直接用 Pod IP 对外服务而是通过 Service 做一层负载均衡和抽象。存储方面Pod 支持挂载多种卷。emptyDir 的生命周期和 Pod 完全绑定Pod 删了数据就没了适合临时缓存和容器间共享hostPath 挂载的是 Node 宿主机目录跨节点调度时会出问题所以生产环境谨慎使用。更合理的持久化方案是使用 PVC后面单开一篇单独聊这里先知道 Pod 的存储有几种选择即可。2. 创建 Pod 的正确姿势从命令到 YAML创建 Pod 是入门 K8s 的必经操作也是最容易产生“为什么我的 Pod 没起来”这类疑问的高发区。我先从最直观的命令行讲起再过渡到生产环境必须掌握的声明式 YAML。2.1 kubectl run 能干什么不能干什么如果你只是想快速验证一个镜像能不能跑用 kubectl run 是最高效的kubectl run nginx-demo --imagenginx:1.27 --restartNever这里必须注意一个版本和策略的细节如果不加--restartNeverkubectl run 默认创建的是 Deployment 而不是 Pod。因为默认 restartPolicy 是 Always而 Deployment 的管理对象最终会创建 Pod加了--restartNever后它创建的就是一个裸 Pod裸 Pod 一般指直接创建的、没有上层控制器管理的 Pod。用 kubectl run 创建的裸 Pod 适合哪些场景本地调试、临时验证镜像、快速排查某个容器能不能正常启动。但它不适合生产环境原因也很简单裸 Pod 没有控制器管理节点宕机或 Pod 被杀后不会自动重建。生产环境应该用 Deployment、StatefulSet 或 Job 这类控制器。这个区别不搞清楚后面遇到“Pod 莫名消失”会很懵。2.2 一份完整 Pod YAML 的逐个字段解读生产环境很少用命令行创建 Pod更多是写 YAML用kubectl apply -f提交。下面这份 YAML 基本涵盖了日常使用最频繁的字段apiVersion: v1 kind: Pod metadata: name: nginx-demo namespace: default labels: app: nginx env: prod annotations: description: demo pod for nginx spec: restartPolicy: Always containers: - name: nginx image: nginx:1.27 imagePullPolicy: IfNotPresent ports: - containerPort: 80 protocol: TCP resources: requests: cpu: 500m memory: 256Mi limits: cpu: 1000m memory: 512Mi livenessProbe: httpGet: path: /health port: 80 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /ready port: 80 initialDelaySeconds: 5 periodSeconds: 5 env: - name: ENV value: prod逐段拆开看metadata 里的 labels 是 Pod 的身份标签Service、Deployment、NetworkPolicy 都通过 label selector 来关联 Pod。labels 可以随便起名字但建议形成规范比如 app、tier、env 这种层级。annotations 则是给 Pod 加“备注信息”比如版本号、负责人、监控说明它们不能被 selector 选中只用于查询和展示。spec.restartPolicy 决定 Pod 内容器退出后 kubelet 怎么处理。Always 是默认值容器无论以什么退出码退出都会重启适合常驻服务OnFailure 表示容器非正常退出退出码非 0时才重启适合 Job 这种一次性任务Never 则不重启适合跑完就结束的短任务。需要注意的是restartPolicy 只会影响 kubelet 对容器重启动作的判断不会自动将 Pod 重新调度到别的节点。要理解这一点后面排错时才会少走弯路。containers 列表可以写多个容器但这里只写了一个。imagePullPolicy 有三个值Always 表示每次创建容器都拉取镜像适合 tag 为 latest 的场景IfNotPresent 表示本地有镜像就不拉取适合使用明确版本号Never 表示只从本地加载。一个常见的坑是把 imagePullPolicy 设置为 Always 但集群没有外网权限结果 Pod 一直拉取失败此时要改为 IfNotPresent 或提供镜像缓存。ports 这块要注意containerPort 本质上是声明性的就算你在 YAML 里不写端口容器也能通过 Pod IP 访问到对应的端口。那为什么还建议写因为这样在滚动更新、网络策略、端口冲突检查时K8s 可以借助这些声明做校验和文档化。写了端口但不代表 K8s 会去自动做端口映射和 Docker 的 -p 参数行为完全不同。resources 部分是最容易误用的字段后面第 4 章我会单独展开。env 用于向容器注入环境变量这是最常用的配置注入方式之一特别适合传应用启动参数、连接信息。生产环境建议用 ConfigMap 和 Secret 统一管理环境变量而不是硬编码在 YAML 里。2.3 提交 YAML 前先用 dry-run 和 explain 给自己省时间我见过不少同学拿到一个网上抄来的 YAML 就往集群里 apply然后报一堆字段错误。这里分享两个省时技巧。第一个是kubectl create --dry-runclient -f pod-nginx.yaml它会在客户端本地校验 YAML 的字段合法性、必填项是否齐全不会真正写入集群。校验错误会在终端直接显示比 apply 之后看 Events 高效太多。第二个是kubectl explain pod.spec.containers.resources这类命令它可以逐层查看某个字段的类型、用法和可选值。遇到不确定的字段用它查一下比翻文档快得多。K8s 的字段体系层级很深但 explain 给出的信息量足够了。3. Pod 生命周期从创建到销毁的完整状态轨迹Pod 的状态变化远比 Docker ps 里看到的 Up、Exited 复杂。不管是排查“Pod 一直 Pending”还是理解“为什么探针失败会重启容器”都要先弄懂状态机。3.1 Pod 状态机与背后的控制器逻辑kubectl get pod显示的 STATUS 列是 K8s 对外暴露的状态主要包括这几个状态含义常见诱因PendingPod 已创建但还没有被调度到节点上或调度后容器正在启动资源不足、PVC 未绑定、调度约束不满足RunningPod 已绑定到节点至少有一个容器在运行或正在启动正常状态Succeeded所有容器正常退出且不会再重启Job 跑完Failed至少有一个容器以非 0 码退出应用崩溃、启动失败UnknownAPI Server 无法获取 Pod 状态kubelet 失联、节点宕机除了这五个正式状态实际使用中还会经常看到 ContainerCreating、PodInitializing、Terminating、CrashLoopBackOff 这些“中间状态”或“复合状态”。它们本质上不是独立状态而是容器状态或事件聚合后的展示结果。比如 ContainerCreating 表示 kubelet 正在拉镜像、创建容器CrashLoopBackOff 表示容器反复启动又崩溃kubelet 已经进入退避重试。一个非常容易误判的场景kubectl get pod显示 Running你就认为服务能正常访问了。实际上 Pod 的 Running 状态只代表容器进程在运行它并不代表应用已经就绪、可以接收流量。有没有对外扩容、要不要把流量打进来完全取决于 readinessProbe 的结果。这个“Running 不代表 Ready”的差异是生产事故的高发根源之一。3.2 restartPolicy 与探针让 Pod 的“体检机制”真正看懂业务容器崩溃了谁负责重启答案是 kubelet它依据 restartPolicy 决定。但这里有一个很容易被忽略的问题kubelet 判定“容器需要重启”的依据是什么默认情况下如果容器进程没有退出kubelet 会认为容器还活着哪怕应用已经死锁或者端口无响应也不会重启。所以光靠 restartPolicy 不够还需要探针来给容器做“体检”。K8s 提供了三种探针livenessProbe存活探针用来回答“容器还活着吗”。如果失败kubelet 会根据 restartPolicy 杀掉容器并重启。readinessProbe就绪探针用来回答“容器准备好了吗”。如果失败K8s 会从 Service 的 Endpoints 列表中摘掉该 Pod流量不会打到它身上但不会重启容器。startupProbe启动探针用来应对启动特别慢的容器。容器启动成功前liveness 和 readiness 探针不会启用避免启动阶段被误判。探针支持四种探测方式HTTP 请求、TCP 端口检查、执行命令、gRPC。HTTP 类型最常用因为很多应用本身就暴露了 /health 或 /ready 端点。TCP 检查只做端口连通性适合没有现成 HTTP 接口的程序。Exec 模式会进容器执行一条命令命令返回 0 就认为健康。写探针时需要注意几个参数initialDelaySeconds 是容器启动后等待多久开始第一次探测给应用留出初始化时间periodSeconds 是探测间隔默认 10 秒timeoutSeconds 是单次探测超时默认 1 秒有些慢接口可能承受不住 1 秒超时failureThreshold 是连续失败多少次才判定不健康默认 3 次。关于探针配置我踩过最深的一个坑是只配了 liveness 没配 readiness。那次的情况是容器进程起来了liveness 探针也通过了但应用内部其实还在加载数据此时 Service 已经把流量打进来大量请求直接 502。所以我的建议是生产环境至少配置 readinessProbelivenessProbe 视情况配置启动慢的应用一定加 startupProbe。探测路径不要偷懒用根路径要针对业务特性设计否则会出现“根路径返回 200核心接口实际不可用”的假健康。4. 资源管理requests 和 limits 到底怎么设置Pod 的资源管理是运维最关注的话题之一也是一堆事故的根源。一个节点上跑了几十个 Pod任何一个 Pod 的恶意内存增长都可能拖垮整个节点上的其他租户。K8s 用 requests 和 limits 两个概念来约束这种行为。4.1 requests 决定“能不能坐上桌”limits 决定“能吃多少”拿饭店打比方requests 是占位你告诉调度器“我这个 Pod 至少要 0.5 核 CPU 和 256Mi 内存才够跑”调度器在分配节点时会把这部分视为已占用剩下的资源才能给其他 Pod 用。limits 是你的“饭量上限”超过了就得停筷。具体到调度过程调度器会遍历所有满足条件的节点检查每个节点上所有 Pod 的 requests 总和加上新 Pod 的 requests 是否超过节点可分配容量。如果所有节点都满足不了Pod 就停在 Pending 状态。这条规则解释了为什么“明明节点上看着有很多空闲内存但 Pod 一直 Pending”——因为空闲内存可能是 limits 之外的系统预留也可能是 K8s 不准许分配的余量。再说 limits 的执行机制。CPU 是可压缩资源超限时系统会限制 CPU 时间片容器变慢但不会被杀掉。内存是不可压缩资源一旦容器的内存使用超过 limits内核的 OOM Killer 会直接杀掉容器进程。很多 Java 应用被 OOMKilled就是 limits 设置得比 JVM 预期堆内存还小。另一个容易踩的坑是只设 requests 不设 limits或者反过来。K8s 的 QoS 等级计算规则里如果只设了 limits 而没设 requests经过默认处理后 requests 会被提升为等于 limits。在生产集群里这种情况会导致调度器认为资源余量紧张大量 Pod 争抢位置。所以我的建议是requests 和 limits 成对出现limits 一般是 requests 的 1.2 到 2 倍。4.2 QoS 三兄弟谁先被牺牲K8s 根据 requests 和 limits 的配置方式将 Pod 分为三个 QoS 等级等级条件典型配置Guaranteed所有容器 requests 和 limits 都相等且都设置limits requestsBurstable至少有一个容器设置了 requests 或 limits但不是所有都满足 Guaranteed边界情况BestEffort所有容器都没有设置 requests 和 limits裸奔节点资源紧张时kubelet 需要驱逐一些 Pod 来释放资源驱逐顺序是BestEffort 优先驱逐其次是 Burstable最后才会动 Guaranteed。而且在 Burstable 内部内存实际使用量超过 requests 越多的 Pod 越容易被驱逐。这背后的逻辑很实际你承诺的资源吃得越多系统就越优先把你请出去。说到驱逐我经常看到有人为了“保证服务质量”把所有 Pod 都设置成 Guaranteed。这个做法虽然能最大化 Pod 的稳定性但也容易造成资源利用率低下因为 requests 一上去调度器就不敢把别的 Pod 塞到同一节点了。生产环境的常见策略是核心服务设置成 Guaranteed非核心的批处理任务用 Burstable 甚至 BestEffort让节点在繁忙时能按优先级牺牲掉非关键任务。4.3 给 Pod 设置资源的小模板和检查命令资源字段的写法很简单但如何选值是个真正的经验活。我的建议是先跑压测或观察一段时间看正常负载下容器的 CPU 和内存峰值。requests 设置为平时的百分之七八十limits 设置成峰值的 1.2 到 1.5 倍。CPU 单位m表示千分之一核500m 是半核内存单位 Mi 是 1024 的倍数不要和 MB 混淆。请求量这块很多小型应用就是 128Mi 起步大型 Java 服务才需要 1Gi 级别。启动后可以用以下命令持续观察kubectl top pod --sort-bymemory kubectl get pod -o wide kubectl describe node node-namekubectl describe node里能看到每个节点的资源水位以及当前节点上每个 Pod 的 requests 和 limits 之和。这个信息在排“为什么 Pod Pending”时特别好用。5. Pod 不是你想调度就能调调度与绑定调度器决定 Pod 落在哪个节点上。多数情况下你不需要干预但某些场景——比如 GPU 服务器、特定机型的性能型节点、数据要靠近某个存储节点——需要显式控制调度。5.1 nodeSelector、nodeName 与亲和性的适用场景nodeName 是最粗暴的调度方式直接告诉 K8s “这个 Pod 必须跑在某节点”。它绕过了调度器由 kubelet 自己创建这个 Pod。优点是精准缺点是节点名写错会 Pending节点宕机时 Pod 也不会被其他节点接管。适合本地测试不适合生产。nodeSelector 是更常用的方式它通过标签来选择节点。比如给某个节点打上disktypessd标签然后 Pod 里写nodeSelector: {disktype: ssd}。这种方式的限制在于只能做等值匹配不支持不等于、不在列表里这类复杂条件。如果需要更灵活的调度策略就要上亲和性Affinity。亲和性分为 nodeAffinity 和 podAffinity/podAntiAffinity 两类。nodeAffinity 是节点亲和性支持 in、notin、exists 等操作符还区分 requiredDuringSchedulingIgnoredDuringExecution硬性要求不满足就不调度和 preferredDuringSchedulingIgnoredDuringExecution软性偏好调度器尽量满足但不强制。podAffinity 和 podAntiAffinity 用于控制 Pod 之间的位置关系。podAffinity 可以把两个相关的 Pod 拉到同一个拓扑域比如让业务 Pod 和 Redis Pod 同节点减少网络延迟。podAntiAffinity 则常用于把同一个服务的多个副本打散到不同节点比如每个节点只放一个实例避免单节点故障时服务全部不可用。判断场景用哪个大多数无状态应用不需要任何调度干预默认的 spread 策略就够用了。需要 IO 性能的用 nodeSelector 或 nodeAffinity。涉及多副本高可用的用 podAntiAffinity。需要服务贴近数据源的用 podAffinity。5.2 静态 Pod绕过 API Server 的特殊存在除了通过 API Server 创建的普通 PodK8s 还有一种特殊的 Pod 叫静态 PodStatic Pod。它的特点是不由 API Server 创建而是由 kubelet 直接读取本地目录中的清单文件来创建和管理。默认目录通常在/etc/kubernetes/manifests。K8s 控制平面组件比如 etcd、kube-apiserver、kube-controller-manager 通常就运行在这种静态 Pod 中。你可能会奇怪为什么控制平面自身的组件反而用“外挂”的方式运行因为如果 API Server 挂到普通管理的 Pod 上它本身就依赖于 API Server 的可用性这就形成了循环依赖。静态 Pod 直接由 kubelet 拉起kubelet 是最底层的守护进程这样就解决了这个问题。在实际操作中如果容器处于崩溃状态你需要查看位于该目录下的清单文件来诊断问题只需要删除这些文件就能让 kubelet 停止对应的静态 Pod。需要注意的是通过kubectl delete pod来删除一个静态 Pod 只会导致它被重新创建因为 kubelet 会不断地尝试将当前状态与清单文件保持一致。5.3 调度失败排查为什么一直 PendingPending 是最常见的调度失败状态。最常见的原因有三个第一节点资源不足。调度器无法在任何一个节点找到满足 requests 的剩余资源kubectl describe pod的 Events 里会显示 “0/3 nodes are available: 3 Insufficient memory” 这类信息。第二PVC 无法绑定。如果 Pod 使用了 PVC而对应的 PV 不存在或 StorageClass 不支持动态供应Pod 也会一直 Pending因为调度器认为存储卷没有准备好。第三调度约束无法满足。比如设置了硬性 nodeAffinity但没有节点满足标签要求或者设置了 podAntiAffinity 要求不同节点但副本数大于可用节点数。排查 Pending 的黄金命令永远是kubectl describe pod pod-nameEvents 里会直接告诉你调度器拒绝调度的原因。很多新手喜欢直接看 logs但 Pending 阶段容器还没创建logs 基本是空的白浪费时间。6. 排查 Pod 问题的实战顺序从 describe 到 events我见过太多人一上来就kubectl logs然后对着空日志发呆。实际上 Pod 排错有它固定的顺序和逻辑掌握这套顺序能把排查时间缩短一半以上。6.1 排错第一步不要只盯着 logs当 Pod 状态异常时我的排查顺序一般是这样的第一步kubectl get pod pod-name -o wide看 Pod 当前状态、所在节点、已重启次数。第二步kubectl describe pod pod-name看 Events、Conditions、ContainerStatuses。这条命令提供的信息量远大于 logs它会告诉你 Pod 是否被调度、镜像是否拉取成功、容器是否被 OOMKill、探针是否失败。第三步根据状态决定下一步。如果是 Pending查调度事件如果容器已创建再去看 logs。第四步kubectl logs pod-name -c container-name --tail50 --previous。--previous表示查看上一次失败实例的日志这对排查 CrashLoopBackOff 非常关键因为当前实例可能已经因为崩溃被重启你看的日志是新实例的空日志。第五步对于更复杂的问题可以用kubectl get events -n namespace --sort-by.lastTimestamp拉取整个命名空间的事件流判断是否存在节点异常、驱逐事件等。6.2 三个高频问题Pending、ImagePullBackOff、CrashLoopBackOff罪魁祸首基本可以归为三类第一次遇到 ImagePullBackOff 时我花了很大力气才意识到问题所在。后来我总结出优先排查以下三个原因镜像名称和 tag 是否正确、镜像是否存在于配置的源或私有仓库中、如果使用的是私有仓库是否配置了 imagePullSecrets 凭证。由于节点 Docker 引擎和集群可能使用不同的运行时你需要通过kubectl describe node等方式确认。在 Name 为生产环境的集群上遇到此问题时该检查项尤其重要。CrashLoopBackOff 是最让新手头疼的状态。它意味着容器反复启动又崩溃kubelet 在退避重启退避间隔逐渐增大到 5 分钟上限。排查思路分三步首先看当前容器运行kubectl get pod pod-name -o yaml检查containerStatuses[].exitCode和reason。然后看--previous日志重点找启动时的异常堆栈。最后分析 exitCode比如 137 表示被 SIGKILL 杀死往往是内存超限被 OOMKill127 表示命令不存在1 或 2 通常是应用自身的启动参数错误。CrashLoopBackOff 的根源绝大多数是启动参数、环境变量、依赖服务和权限问题而不是 K8s 本身的配置。6.3 一个比较隐蔽的坑Pod 一直 Terminating另外一个常见问题是删除 Pod 时Pod 一直停在 Terminating 状态怎么也删不掉。原因通常有这几种一是 Finalizers 没有执行完。Pod 上有 Finalizers 时删除操作会等待 finalizer 对应的清理逻辑执行完成如果某个 Finalizers 对应的控制器出了问题它永远清理不完Pod 就卡在 Terminating。二是节点失联。节点 notReady删除 Pod 时 API Server 联系不到 kubelet无法完成清理。三是挂载卷问题或底层运行时卡住。处理方式首先是尝试等一会儿有时 kubelet 会在底层容器停止后自动完成清理。如果长时间卡住可以试试kubectl delete pod pod-name --force --grace-period0但要注意--force只是直接从 API Server 侧删除对象如果节点失联底层残留的容器可能还在运行。这种情况最稳妥的做法是先把节点恢复或者手动在节点上清理容器再删除对象。在排查过程中另一个经验是不要把 Pod 当永久资源对待。Pod 随时可能因为资源驱逐、节点故障、手动更新而消失集群真正稳定的是 Deployment、StatefulSet 这些控制器。所以遇到问题时先看是不是控制器的行为导致的而不是一头扎进单个 Pod 的日志里面。说实话Pod 是我认为 K8s 里最值得花时间搞透彻的基础概念之一。它把网络、存储、调度、生命周期、容错这些核心机制都串在了一块儿把这些理解清楚后面学 Deployment、Service、HPA 会顺畅很多。回想自己刚开始那段时间很多坑都是在 Pod 层面踩的比如探针配不对导致滚动更新失败、把裸 Pod 当生产资源用、requests 和 limits 比例失调引发驱逐风暴。后来慢慢总结出一套适合自己的配置模板和排错顺序再遇到 Pod 异常时基本都能在几分钟内定位方向。这篇笔记算是我整个 K8s 学习系列的第一篇后续大概率会接着写 Deployment 与控制器模型因为那才是理解 Pod 如何被真正“管理”的下一站。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑