Kubernetes Pod 资源清单实战:从生命周期到 YAML 编写与排障

发布时间:2026/10/21 13:02:27
Kubernetes Pod 资源清单实战:从生命周期到 YAML 编写与排障
1. 从实操看 Pod它在集群里的真实角色和生命周期1.1 为什么集群的最小调度单位是 Pod 而不是容器上一轮搭建集群的时候很多人会有个疑问容器明明可以一个个直接跑为什么 k8s 偏偏要在外面再包一层 Pod我在第一次接触这个设计时也犯过嘀咕。直到自己真实去部署一个前后端联调的小项目才明白 Pod 这个抽象有多关键。核心原因是容器是进程级的隔离单元而 Pod 是“一组需要共享网络和存储的容器”的调度单元。打个比方如果你要把日志采集 sidecar 和应用主容器放到一台机器上希望它们能通过 localhost 互相访问在裸容器时代你得自己解决端口共享、存储共享、生命周期绑定这些事在 k8s 里Pod 天然把这些问题都包住了。Pod 内的容器共享同一个 network namespace、同一个 IPC namespace还能挂载同一个 volume所以它们之间的通信成本几乎为零而且 Pod 作为一个整体被调度、被重启、被伸缩。实际项目里最常见的组合是“主业务容器 日志采集容器 临时工具容器”。例如在模拟项目 X 中我用一个 Pod 同时运行了 Nginx 和应用容器应用把访问日志写到共享卷里Nginx 负责对外统一入口。如果没有 Pod 这层封装我就得自己写脚本去协调两个容器的启停顺序和资源归属这违背了容器编排的初衷。理解了这个“容器组”的概念再看后面所有 Pod 管理的操作就不会觉得只是记命令而是有逻辑在里面。1.2 Pod 生命周期状态机别等出问题才去翻文档运维 Pod 的人几乎每天都要和状态打交道。kubectl get pods 输出的那一列 STATUS其实对应一个完整的状态机搞清楚它能省掉大量排查时间。PendingPod 已经被 API Server 接受但还没有完成调度或者镜像还在拉取中。如果卡在 Pending 很久多半是节点资源不足、存在未匹配的污点或节点选择器。ContainerCreating容器正在创建通常是镜像拉取阶段或存储卷挂载阶段。这个状态是过渡态长时间停留多半是镜像仓库拉不动或者存储卷权限有问题。RunningPod 至少有一个容器在运行中。这个状态只表示进程活着不表示业务健康。SucceededPod 内所有容器都正常退出退出码是 0。一般用于一次性任务比如 Job。FailedPod 内至少有一个容器以非零码退出或者被系统终止。UnknownAPI Server 与节点失联拿不到 Pod 的最新状态。这种情况要先检查节点本身而不是盯着 Pod 看。我个人的经验是看到 Running 别急着觉得“稳了”一定要结合后面的 readinessProbe 和日志判断业务是否真的通了。就像你看到一台服务器开机成功不代表上面那个 Web 服务就一定能返回 200。1.3 用 kubectl 快速管理单个 Pod 的高频命令在进入资源清单之前先把命令行下最常用的几组操作讲清楚。虽然我们提倡用 YAML 管理但排障和临时验证时命令行的速度是最快的。创建与查询kubectl run 可以快速创建 Podkubectl get pods -o wide 可以看到 Pod 落在哪个节点、内网 IP 是什么。我经常在排查网络问题时加 -o wide因为不仅要看到状态还要看到 IP 和 NODE 列。进入容器kubectl exec -it pod-name -- /bin/sh。如果 Pod 有多个容器需要加 -c 指定容器名。这个操作频率极高尤其是镜像里没有 curl、ping 等工具时我都是靠 exec 进去用宿主机网络排查问题。查看日志kubectl logs pod-name 看单容器日志多容器用 -c 指定如果容器已经 CrashLoopBackOff用 kubectl logs pod-name --previous 查看上一次退出时的日志这个参数在排查崩溃问题时是救命级的。删除与强制清理kubectl delete pod pod-name 是优雅删除如果遇到卡在 Terminating 的 Pod可以加 --force --grace-period0 强制清理但这个操作要谨慎只适用于确定不会影响数据的情况。这些命令是 Pod 管理的“快拳”。真正要持久化、要可复用、要进 CI/CD 的还是下一部分要讲的声明式资源清单。2. 两种管理方式对比命令式一时爽声明式保平安2.1 kubectl run 创建的 Pod最后都去哪儿了很多人上手 k8s 的第一条命令就是 kubectl run我最早也是。但用过一段时间你就会发现直接用命令创建的 Pod 有天然的短板它不是“可追踪”的配置。首先kubectl run 生成的 Pod 是你敲的这条命令的历史而不是代码仓库里的一个文件。团队协作的时候别人根本不知道这个 Pod 是怎么配置的镜像版本、资源限制、探针这些都散落在聊天记录和个人终端里。其次kubectl run 创建的 Pod 不受 ReplicaSet 或 Deployment 管理它挂了就是挂了k8s 不会自动帮你拉起来。你在生产环境里想要的高可用、滚动更新、回滚能力在这种“裸 Pod”上统统不存在。所以我的建议是kubectl run 可以用于本地测试、临时验证某个命令或镜像行为但凡是需要长期运行的业务一律使用声明式清单。这也是为什么这篇内容重点放在资源清单上——命令只是敲门砖清单才是你真正要写的东西。2.2 声明式资源清单的工作流写 YAML 而不是敲命令声明式管理的核心思路是你告诉 k8s “我想要什么状态”k8s 自己想办法把现状变成目标状态。在 Pod 管理这个层面最常见的承载对象其实是 Deployment而不是裸 Pod但先搞懂 Pod 清单后面扩展 Deployment、StatefulSet、DaemonSet 才有基础。一个标准流程是这样的编写 Pod 的 YAML 文件描述镜像、端口、环境变量、资源限制、探针等。使用 kubectl apply -f pod.yaml 提交到集群。之后每次修改都修改 YAML 文件再重新 apply 一次。通过 git 保存 YAML 文件的变更历史随时可以回滚。相比命令式这套流程的优势是显而易见的配置可审计、可评审、可回滚。我在公司团队里推行这套方式后最直观的变化是“出错之后知道反过来查哪个文件”而不是让同事回忆“上次用哪个参数创建的”。2.3 为什么新手也要从 YAML 学起而不是只背命令可能有人觉得我只是自己玩一玩集群背几条命令足够了。但实际上命令背后的参数就是 YAML 字段的另一种表达。比如 kubectl run 后面加的 --image、--port、--limits都可以一一对应到清单里的 spec.containers[].image、ports、resources。你如果不懂 YAML 的组织逻辑遇到命令行不支持的参数时会直接卡住。另一个重要理由是kubectl explain 是学习清单的最佳入口。比如你输入 kubectl explain pod.spec.containers它会告诉你每个字段的类型、含义、默认值这比翻任何教程都准确。我强烈建议把 kubectl explain 当作随身手册记住大骨架之后细节全部靠它现场查。3. 资源清单核心字段拿到一份 YAML到底先看哪里3.1 从 apiVersion 到 spec一份清单的基本骨架任何 Kubernetes 资源清单都逃不开四段结构apiVersion、kind、metadata、spec。先记住这个骨架再看细节就不会乱。apiVersion表示资源所属的 API 组和版本。Pod 属于核心组所以是 v1。Deployment 属于 apps 组所以是 apps/v1。版本写错了apply 时会直接报 no matches for kind。kind资源类型比如 Pod、Deployment、Service、ConfigMap。metadata元数据最重要的是 name 和 namespace。同一种资源在同一 namespace 下不能重名这是 k8s 的硬性规则。labels 也在这里定义后面做选择器、做关联都靠它。spec期望状态描述。不同 kind 的 spec 结构完全不同。在这个骨架里真正需要你动脑的只有 spec。metadata 基本是复制模板apiVersion 和 kind 也是固定值。所以我分析一份陌生的 YAML永远是先看 spec再看 metadata 里有没有 labels。3.2 容器描述image、imagePullPolicy 与 command 的取舍spec.containers 是一个数组意味着一个 Pod 可以塞多个容器。每个容器最常用的字段如下name容器名。exec、logs 指定 -c 时用的就是它不要起太随意的名字。image镜像地址。需要注意容器会按 tag 决定是否拉取这受 imagePullPolicy 控制。imagePullPolicyAlways、IfNotPresent、Never 三选一。默认规则是如果 tag 是 latest 或用数字 tag默认 Always否则默认 IfNotPresent。我踩过的坑是本地测试时改了代码重新构建镜像却忘了重新打 tag结果 Pod 一直用旧镜像排除了半天才想起 imagePullPolicy 是 Always 且本地仓库没有缓存清理机制。command 和 args对应 Docker 镜像里的 ENTRYPOINT 和 CMD。我个人的习惯是镜像尽量做到自包含command 和 args 能少写就少写。因为一旦你在 YAML 里指定了 command它会覆盖 Dockerfile 里的 ENTRYPOINT行为可能和本地 docker run 时完全不同。ports声明容器监听的端口。这里有个容易误解的点ports 只是“宣告”不是“自动暴露”。即使你不写 ports应用在容器里监听 80 照样能工作写上去更多是给人看给服务发现工具提供信息。3.3 CPU 和内存的 requests/limits容量分配的心算方法资源清单里最让新手头疼的就是 requests 和 limits。我用一个通俗比喻来解释requests 是“最低保障”limits 是“上限封顶”。CPU 的单位是 core1 核就是 1 个完整的 CPU。你经常看到 100mm 是 milli-core100m 就是 0.1 核。调度器用 requests 来选节点比如一个节点有 2 核可分配已经有两个 Pod 各 request 了 800m那剩下的可分配就只有 400m新 Pod 如果 request 500m 就调度不上去了。搞清楚这一点就能理解为什么集群明明看着很空闲Pod 却一直 Pending。内存的单位是字节常用的是 Mi、Gi。64Mi 是 64 兆字节注意这里不是 64Mb 比特。为什么生产环境一定要设置 limits因为如果没有 limits一个进程可能无限吃内存最后把节点打爆引发宿主机 OOM甚至拖垮同节点其他 Pod。而设了 requests 不设 limits 会导致 QoS 等级变成 Burstable行为较复杂我建议线上环境 requests 和 limits 必须成对出现。心算分配的时候我一般遵循“一核一 Gi留两成余量”的经验requests 按实际负载的 80% 估limits 按峰值或两倍估。比如模拟项目 X 里的一个 PHP 应用日常负载大约 300m CPU、300Mi 内存我就写 requests CPU 200m、内存 256Milimits CPU 500m、内存 512Mi。这个配法不是拍脑袋而是看监控曲线之后做的决策。3.4 探针与重启策略让故障恢复自动化Pod 管理的核心目标之一就是让 k8s 替你处理故障。探针Probe就是实现这个目标的关键组件。livenessProbe存活探针判断容器是否还活着。失败后 kubelet 会根据 restartPolicy 杀死并重启容器。相当于“心跳检测”。readinessProbe就绪探针判断容器是否准备好接收流量。失败的话Service 的 Endpoints 里会临时摘掉这个 Pod。相当于“营业状态牌”。startupProbe启动探针用于启动很慢的应用。在 startupProbe 成功之前livessProbe 和 readinessProbe 不会启动避免在应用启动阶段就反复被杀。探针的探测方式有 exec、httpGet、tcpSocket 三种。我最常用 httpGet因为绝大部分业务都暴露 HTTP 接口比如 livenessProbe 打 /healthzreadinessProbe 打 /ready。写探针时有两个重要的时间参数initialDelaySeconds 是启动后等多久才开始探测periodSeconds 是探测间隔。首次给一个启动慢的 Java 应用配探针时我把 initialDelaySeconds 设为 5结果应用还在加载类就被连续杀了好几次最后查到日志才知道问题。后来我把 startupProbe 加上initialDelaySeconds 调大问题立刻消失。restartPolicy 有三个值Always、OnFailure、Never。Pod 的默认是 Always也就是说只要容器非正常退出kubelet 就会重新拉起来。对长期运行的服务型 Pod默认值就行对跑一次性批处理的 Job才需要改成 OnFailure 或 Never。4. 从需求到落地手写一份 Pod 资源清单的完整过程4.1 一个真实可复现的模拟需求假设我们要在测试环境部署一个轻量级的 Web 服务镜像名定为 registry.example.local/demo-web:1.2.0。业务上它需要接管 8080 端口接口 /healthz 返回 200 表示存活/ready 在依赖的本地缓存初始化完成后返回 200。由于测试环境机器内存有限我们希望把资源占用控制住同时允许 k8s 自动重启异常退出的容器。这个需求虽然简单但已经覆盖了 Pod 清单的绝大多数核心字段。下面我把它逐步写成 YAML。4.2 逐步完善清单从最小可用到符合生产习惯第一步先写一个最小可用的版本apiVersion: v1 kind: Pod metadata: name: demo-web namespace: default spec: containers: - name: demo-web image: registry.example.local/demo-web:1.2.0这个文件 apply 后能跑但不够“生产化”。第二步加上端口声明和两个探针apiVersion: v1 kind: Pod metadata: name: demo-web namespace: default labels: app: demo-web env: test spec: containers: - name: demo-web image: registry.example.local/demo-web:1.2.0 imagePullPolicy: IfNotPresent ports: - name: http containerPort: 8080 livenessProbe: httpGet: path: /healthz port: http initialDelaySeconds: 10 periodSeconds: 5 timeoutSeconds: 2 failureThreshold: 3 readinessProbe: httpGet: path: /ready port: http initialDelaySeconds: 5 periodSeconds: 5 timeoutSeconds: 2这里注意一个小细节探针的 port 可以直接写 http对应上面 ports 里的 name。这样如果以后端口变了只需要改 ports.name 的映射关系探针不用跟着改提高可维护性。我在多次维护里体验到这个小技巧的好处。第三步加上 resources 限额和环境变量resources: requests: cpu: 200m memory: 256Mi limits: cpu: 500m memory: 512Mi env: - name: APP_ENV value: test - name: CACHE_SIZE value: 1024到这一步这份清单已经可以在测试环境稳定部署了。如果还想要更完整可以在 metadata 加上 namespace或者在 spec 里加上 tolerations 调整调度行为但对单个 Pod 来说不是必须的。4.3 校验、提交与回顾apply 之前的最后一道闸写好的 YAML 不要直接 apply先做三件事。第一件事是语法校验。我常用 kubectl create --dry-runclient -f demo-web.yaml -o yaml 这种干跑模式它会在客户端做一次校验有问题会直接报错不会去碰集群。第二件事是看字段合法性我会用 kubectl explain pod.spec.containers.resources 这种命令逐一确认没把握的字段。第三件事是检查命名空间和 labels确认不会和已有资源冲突尤其是 namespace 忘写时 Pod 会落进 default这在测试环境很容易造成资源混乱。确保没问题后再执行kubectl apply -f demo-web.yaml然后立刻用 kubectl get pods -o wide 和 kubectl describe pod demo-web 观察状态。如果状态卡在 ContainerCreatingdescribe 里会给出具体事件这是最直接的线索。我见过不少人一遇到 Pod 起不来就反复删了建、建了删其实大部分答案都写在 Events 里。5. 常见问题与排查技巧实录Pod 管理里那些真实踩过的坑5.1 Pod 一直 Pending到底卡在哪一环Pending 是新手最常遇到的状态也是我排查次数最多的一个。列一个排查清单给你按顺序过一遍基本都能找到原因。第一看调度器有没有选择到节点。kubectl describe pod 里如果出现 0/1 nodes are available后面会跟着具体原因最常见的是 insufficient cpu、insufficient memory这时候去 kubectl describe node 看各节点剩余资源即可。第二看是否有 nodeSelector 或亲和性配置导致所有节点都不满足条件。第三看是否有 pending 状态下的 taints 和 tolerations 冲突。一个带有专用污点的节点默认是拒绝普通 Pod 上去的需要你在清单里显式声明 tolerations。这几个场景我都真实遇到过。最隐蔽的一次是 nodeSelector 写错了键值把测试环境的标签写成了disktype: ssd但集群里根本没有节点带这个标签导致 Pod 一直 Pendingdescribe 里只有一句 “didnt match node selector”。遇到这种事情别慌逐行检查 metadata.labels 和节点标签是否对得上。5.2 CrashLoopBackOff从日志到退出码的逐层排查CounterlyCrashLoopBackOff 是仅次于 Pending 的高频问题。它的含义是容器启动后立刻崩溃kubelet 尝试重启但反复失败所以进入退避状态。我的排查顺序一般是这样的先看当前日志kubectl logs pod-name。如果当前日志为空看上一次日志kubectl logs pod-name --previous。仍然没线索就查事件kubectl describe pod pod-name。最后实在不行手动复现kubectl run test-pod --image镜像名 --command -- 你的启动命令到宿主机侧直接看输出。这个顺序看起来简单但很有效。有一次我排查一个应用它一直 CrashLoopBackOff日志里全是正常启动信息没有报错。后来用 --previous 才发现容器其实已经启动成功只是启动时往 stdout 写了一行固定输出后立刻执行 os.Exit(1)。问题出在程序内部的启动校验而不是 k8s 配置。如果当时不查 previous我可能还会以为自己写的 livenessProbe 有误。另外如果状态是 OOMKilled说明是内存超限被杀。这种问题优先调大 limits 或者优化应用内存而不是瞎改重启策略。你可以用 kubectl describe pod 看 Last State 里的 OOMKilled 标记确认后再调参。5.3 镜像拉取失败与配置错误的典型现场镜像拉取问题一般会在状态列显示 ImagePullBackOff、ErrImagePull。常见原因有四个镜像地址写错tag 不存在或者仓库里根本没有这个镜像。拉取认证失败。私有仓库没有在 k8s 里配置 imagePullSecret或者 secret 里的凭证过期了。镜像仓库域名解析不了特别是自建仓库或内网环境。网络限制导致拉取超时尤其是镜像比较大时。如果确认地址没错第二件事就是检查 secret。用 kubectl get secrets 看有没有相应认证然后确认 Pod 的 spec.imagePullSecrets 里有没有引用它。我经常看到有人把 secret 建好了却忘了在 Pod 清单里写 imagePullSecrets导致拉取依然 401。配置错误类的典型是 CreateContainerConfigError常见于环境变量引用了不存在的 ConfigMap 或 Secret 的 key。kubectl describe 里也会给出“references non-existent config key”之类的信息。这种问题的核心是k8s 启动容器时会先解析依赖任何缺失项都会让容器创建失败所以排查优先级要放在“配置依赖”上而不是容器代码里。还有一个细节kubectl get events --sort-by.lastTimestamp 可以按时间排序查看集群事件。很多问题光看单个 Pod 的事件不够全局事件的顺序能帮你还原整个出问题的链条比如某段时间多个 Pod 同时 Pending可能就是某个节点 NotReady 导致的大面积驱逐。5.4 现场排障的几组实用组合命令最后分享几组我自己定义的高频组合基本可以覆盖日常 Pod 排障 80% 的场景。你可以把它们存成 shell 别名临时用的时候敲一下效率提升很明显。# 看所有 Pod 及所在节点适合排查调度问题 kubectl get pods -A -o wide # 看某个 Pod 的完整事件 kubectl describe pod pod-name # 实时跟踪日志 kubectl logs -f pod-name -c container-name # 进入容器并执行命令 kubectl exec -it pod-name -c container-name -- /bin/sh # 快速查看资源占用 kubectl top pod pod-namekubectl top pod 如果报 metrics 不可用说明集群没安装 metrics-server。这个工具对查看真实资源占用非常有用因为 get pods 的表示和实际负载差距很大。我强烈建议有条件的集群先把 metrics-server 装好排查问题时能少走很多弯路。6. 最后关于 Pod 管理资源清单我还想说的一件事如果你照着上面的方式自己练一遍就会体会到一个规律Pod 管理表面上是记命令、写 YAML本质上是在训练一种“声明式思维”。你不再关心“怎么启动这个容器”而是关心“这个容器最终应该是什么样”。这种思维一旦建立后面学 Deployment、Service、StatefulSet全都是在同一个心智模型上加零件而已。我个人的体会是刚开始写清单时不要急着追求一步到位更不要盲目抄网上大段大段的模板。先用最小可用版本跑起来再逐步加上探针、资源限制、健康检查每一步都搞清楚为什么。我接手过的不少问题根因都是“照着别人模板复制却不知道那个字段到底在干什么”。写 YAML 不是背字段是理解状态。理解了状态k8s 这个系统在你眼里就不再是一个黑盒而是一套可预测、可控制、可复盘的编排逻辑。顺便说一个实战里最后的小技巧给每个 Pod 清单都加上 labels并且 label 里包含应用名和环境名。这个习惯前期看不出价值等集群里 Pod 数量超过几十个你靠 kubectl get pods -l appdemo-web,envtest 一条命令就能精准锁定目标组时就会感谢当时的这个决定。Pod 管理这条路没有捷径但每踩过一个坑都会变成你排障经验里最可靠的一块底牌。