ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Kubernetes Pod深度解析:从容器到调度单元的核心机制

Kubernetes Pod深度解析:从容器到调度单元的核心机制 第一次用 kubectl get pods 看到 Running 状态的时候我盯着那个由随机字符串拼成的名字看了很久。当时脑子里只有一个问题这个 Pod和容器到底有什么区别后来在线上环境被各种 Pod 状态折腾过几轮又翻了不少源码和官方文档才慢慢理解一件事Pod 是理解整个 Kubernetes 的钥匙。它是 API 层面最小的调度单元是 kubelet 与容器运行时之间的桥梁也是 Deployment、StatefulSet、DaemonSet 这些工作负载最终落地的实体。如果 Pod 没搞透后面学 Service、Ingress、调度器、网络插件都会觉得云里雾里。这篇文章我会按一条比较顺的路线来走先讲清楚 Pod 为什么存在、核心机制是什么再拆解从 YAML 到容器真正跑起来的完整链路最后落到实操和排障。适合刚入门 Kubernetes 的运维和开发也适合准备面试、或者已经在生产环境被 Pod 问题折磨过的人。1. 为什么说 Pod 是 Kubernetes 里最值得吃透的对象1.1 第一次 kubectl get pods 时看到的到底是什么先看一条最常见的输出NAME READY STATUS RESTARTS AGE web-0 1/1 Running 0 3mNAME 是 Pod 的名字READY 是“就绪容器数/总容器数”STATUS 是当前阶段RESTARTS 是重启次数AGE 是存活时间。我刚入门时只看 STATUS后来被线上问题毒打几次才明白READY 和 RESTARTS 往往比 STATUS 更有价值。一个 Running 状态的 PodREADY 一直是 0/1说明它虽然有进程在跑但业务还没真正就绪流量根本不该进来。从机制上看Pod 不是某个进程而是一个逻辑边界。它描述了一组容器的集合关系这些容器共享同一个网络命名空间、可以挂载同一批存储卷、必须被调度到同一台节点上。用生活里的例子类比Pod 就像一间合租房里面住着一个或多个容器租客共用 Wi-Fi网络和公共区域存储卷而且房东kubelet只会按整间房来出租、搬迁和收租。1.2 为什么 Kubernetes 不直接调度容器而是多包了一层 Pod这个设计一开始看起来有点绕但想明白之后会觉得非常合理。单个容器本质上是单进程粒度的隔离单元它没法天然表达“一组进程必须协同工作”的关系。比如一个 Web 服务应用进程和日志采集进程如果分属两个容器它们之间就需要共享网络栈、共享数据目录还要在同一个生命周期里被管理和回收。没有 Pod 这个概念时容器编排工具只能把多个容器写在同一个 Compose 文件里但调度只能限制在一台机器上没法让集群调度器把它们当作一个整体去搬移。Pod 解决的就是这个“整体性”问题。调度器以 Pod 为最小单位做调度决策计算资源总量时按 Pod 内所有容器的 requests 之和来算迁移时整个 Pod 的所有容器都在新节点重建。正是通过这一个抽象Kubernetes 把“一组容器的部署、扩容、故障恢复”固化成了集群层面的原子操作。我在实际使用中的体会是区分“该放一个 Pod 还是多个 Deployment”核心就一句话——如果两个容器需要共享网络栈或数据卷、必须同时扩缩容、生命周期强绑定就放进同一个 Pod如果它们只是相互调用关系就应该拆成独立工作负载中间通过 Service 通信这样扩展和发布才能互不影响。2. Pod 的核心机制拆解一组容器的“同生共死”2.1 共享网络为什么同一个 Pod 内访问 localhost 就能通Pod 里第一个被创建的不是业务容器而是一个叫 pause 的沙箱容器。这个容器通常什么都不做只负责创建并持有网络命名空间和 PID 命名空间。后面的业务容器通过 CRI 的接口加入这个命名空间于是同一个 Pod 内的所有容器会共享同一个 IP、同一套端口空间。这意味着两个后果。第一Pod 内容器间访问走 localhost 就行不需要经过 Service 转发延迟低、配置简单。我见过有的团队把 Envoy 和业务容器放进一个 PodEnvoy 监听 15001业务进程直接访问 localhost:15001这个模式在服务网格里非常常见。第二端口不能冲突。两个容器里如果都监听 8080启动时第二个容器就会报 address already in use这个排查起来很容易懵因为日志看起来是业务启动失败实际上是端口被同一个 Pod 里的另一个容器占了。共享网络还带来了另一个特性Pod 的 IP 实际上是沙箱容器的 IP。你查看 Pod 的 IP会在 pause 容器上看到这个地址业务容器如果单独去看自己的 eth0其实是同一个虚拟网卡。理解这一点后面看 tcpdump 抓包、排查网络问题时思路会清楚很多。2.2 共享存储Pod 内文件交换不需要走网络Pod 的另一个核心资源共享是存储卷。同一个 Pod 内的容器可以把同一个 Volume 挂载到不同路径一个写、一个读数据交换不需要走网络也不需要额外做分布式文件同步。典型的例子是日志采集主容器把日志写到共享目录Sidecar 容器读取同一个目录然后转发到日志平台。选择挂载方式时有几个细节值得注意。如果两个容器都挂载同一个 emptyDir 卷默认情况下它们看到的是同一个目录内容但每个容器内部的挂载点路径可以不同。另外emptyDir 的生命周期跟 Pod 一致Pod 被删除后数据就没了这不适合需要持久化的场景需要持久化时应该换用 hostPath 或 PVC。还有一个我踩过的坑同一个 Volume 挂到多个容器时如果其中一个容器修改了目录权限可能影响另一个容器的读写尤其是以非 root 运行的容器。在设计多容器 Pod 时我总是提醒自己共享存储和共享网络是“多容器同 Pod”的资格条件但不是充分条件。判断标准仍然是生命周期和调度是否强绑定。如果只是偶尔交换文件、扩展节奏不同拆开是更稳的选择。2.3 生命周期、重启策略与状态的对应关系Pod 的状态字段STATUS一共有五种Pending、Running、Succeeded、Failed、Unknown。Pending 表示还没被调度成功或者镜像还在拉取Running 表示 Pod 已绑定到节点且容器已创建Succeeded 表示所有容器正常退出Failed 表示至少有一个容器以非零状态退出Unknown 通常说明 kubelet 和 API Server 失联状态拿不到了。初学者最容易把状态和重启策略混在一起。重启策略有三个可选值Always默认、OnFailure、Never。当容器进程退出时kubelet 会根据这个策略决定是否拉起新容器。需要注意重启策略是 Pod 级别的不是容器级别的一个 Pod 内即使只有一个容器失败了kubelet 也会按 Pod 的整体策略来处理。另外RestartPolicy 只对同节点的容器重启生效如果 Pod 所在的节点挂了Pod 不会原地复活而是由上层控制器比如 Deployment在别的节点创建新 Pod这正好说明了两种“恢复”的区别。在实际运维中我看到最多的一个误区是直接创建 Pod 并把它当成服务长期运行然后发现节点重启后 Pod 没回来。原因很简单裸 Pod 没有控制器管理重建属于人工职责。所以生产环境服务一定不要用裸 Pod要交给 Deployment、StatefulSet 或 DaemonSet。2.4 Init 容器与 Sidecar一个前置把关一个常伴左右Pod 里除了主容器还有两类特殊容器。Init 容器按顺序串行执行全部成功退出后主容器才会启动。适合做前置条件准备比如等待数据库就绪、生成配置文件、执行权限修复。如果任何一个 Init 容器失败Pod 会一直重启 Init 容器直到成功为止主容器永远不会启动。Sidecar 则是伴随主容器一起常驻的容器常见用途是日志采集、网络代理、指标导出。旧版本里 Sidecar 也是普通容器终止顺序完全不确定从 Kubernetes 1.28 开始有了原生 Sidecar 容器支持能保证 Pod 终止时先停主容器再停 Sidecar。这里有一个实际经验Init 容器一定要设置 resources否则它在调度时可能被忽略或不公平地分配资源。早期版本中 Init 容器的资源 requests 取所有 Init 容器的最大值limits 取总和理解这个计算规则对你规划节点容量有帮助。3. 从 YAML 到运行的完整链路kubectl apply 之后发生了什么3.1 一个最小可用 Pod 的 YAML 长什么样先看一个最基础的 Pod YAMLapiVersion: v1 kind: Pod metadata: name: demo-pod labels: app: demo spec: containers: - name: main-app image: nginx:1.25 ports: - containerPort: 80 resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 256Mi readinessProbe: httpGet: path: / port: 80 initialDelaySeconds: 3 periodSeconds: 5 livenessProbe: httpGet: path: / port: 80 initialDelaySeconds: 10 periodSeconds: 10apiVersion 用 v1 而不是 apps/v1是因为 Pod 是核心 API 对象Deployment 才属于 apps 组。metadata.name 必填labels 用于选择器和后期关联。spec.containers 是核心列表image 指定镜像resources 声明和限制资源probe 配置健康检查。创建和查看这个 Pod 的命令分别是kubectl apply -f demo-pod.yaml kubectl get pods -o wide kubectl describe pod demo-pod我在练习时经常用这种最小 YAML 做实验因为它把 Pod 自身的字段暴露得很干净不会被 Deployment 的副本和滚动更新策略干扰。先把 Pod 字段吃透再去看 ReplicaSet、Deployment 的嵌套结构会顺很多。3.2 从 Pod 到 Deployment两者到底有什么区别这是面试里被问得最频繁的一个问题也是很多运维同学概念模糊的地方。一句话版本是Pod 是“实例”Deployment 是“控制器”。Deployment 负责声明我期望的最终状态——需要 3 个副本、镜像版本是多少、升级用滚动还是重建——然后通过 ReplicaSet 去创建和管理 Pod 的数量。落实到 yaml 层级Deployment 的 spec 里会有一层 templatetemplate 里描述的正是 Pod 的样子。也就是说Deployment 不是替代了 Pod而是包了一层 Pod 模板。我用个表格把区别列清楚维度PodDeployment定位最小调度单元工作负载控制器副本管理没有副本概念通过 ReplicaSet 管理指定副本数更新方式不支持滚动更新支持滚动更新、回滚重启行为由自身 restartPolicy 控制容器异常退出后会在合适节点重建节点故障裸 Pod 不会自动迁移控制器会在其他节点创建新 Pod典型用途调试、理解底层机制生产无状态服务所以玩 Kubernetes 不要用 kubectl run 直接创建裸 Pod 跑服务。正确姿势是写一个 Deployment用控制器去管理和维持 Pod 数量。理解了这条关系你自然就明白为什么 Deployment 的滚动更新里会同时出现两个版本的 Pod旧版本逐个缩容、新版本逐个扩容两边加起来的数量始终保持在期望值范围内。3.3 从 kubelet 到 containerdPod 是怎么被拉起来的这块是我当年纠结最久的问题。表面上看 kubectl apply 之后一切都很“自动”但中间到底经过了多少环节很多人说不清。我用一条最核心的链路来讲kubectl - API Server - etcd - kubelet(watch) - CRI - containerd - runc - 容器进程API Server 收到创建 Pod 的请求后校验并写入 etcd。kubelet 是节点上的常驻代理它通过 watch 机制监听到“有一个 Pod 应该调度在我这个节点上”于是根据 Pod spec 调用容器运行时创建容器。关键在于kubelet 并不直接操作 containerd 的底层接口而是走 CRIContainer Runtime Interface这个 gRPC 协议。CRI 定义了 kubelet 这个客户端需要调用哪些服务端方法containerd 作为服务端通过 CRI plugin 来响应。kubelet 启动参数里通常会配置 --container-runtime-endpointunix:///run/containerd/containerd.sock这个 socket 就是两者通信的通道。实际创建流程分三步RunPodSandboxcontainerd 先拉起 pause 沙箱容器创建网络命名空间和 PID 命名空间CreateContainer创建业务容器但此时还没有启动进程StartContainer真正执行业务容器进程。containerd 收到请求后如果本地没有镜像就按顺序从配置的镜像仓库拉取有镜像就直接通过 containerd-shim 调用 runc 创建并启动容器。runc 再通过 Linux 内核的 namespaces、cgroups 等能力把容器资源隔离出来。排查这个问题时crictl 比 docker 命令更直接。因为 kubelet 是走 CRI 调 containerd 的docker 命令看到的容器只是 containerd 的一个视角docker ps 未必能完整反映 Pod 内的容器状态。反过来crictl ps 能看到沙箱和业务容器的对应关系crictl logs 可以直接看某个容器的日志这些都是我在节点上排障最常用的工具。4. 实战用一套完整流程把 Pod“玩明白”4.1 创建、查看、进入、清理 Pod 全流程先用前面的 YAML 启动一个 demo-pod。启动后最常用的几个命令是# 查看 Pod 列表和所在节点 kubectl get pods -o wide # 进入 Pod 内的第一个容器 kubectl exec -it demo-pod -- /bin/sh # 查看日志-f 表示实时跟随 kubectl logs -f demo-pod # 删除 Pod kubectl delete pod demo-pod进入容器时如果 Pod 里有多个容器需要指定 -c 参数否则 kubectl 默认进入第一个容器。这一步经常有人漏掉结果发现进去的容器不对操作半天找不到自己的进程。删除 Pod 时有个细节值得单独说。默认情况下kubectl delete 会等待 grace period 结束才真正删除如果容器里的进程不响应 SIGTERM删除会卡住。这时你可能会想用 --force但我不建议一上来就 force。正确做法是先确定进程是否在优雅处理退出信号实在等不了再考虑 --force --grace-period0而且这个操作可能留下清理不干净的挂载点或网络资源属于“最后手段”。4.2 给 Pod 加上探针、资源限制和优雅终止健康探针是生产环境使用 Pod 必须配的东西。三种探针职责不同startupProbe 判断进程是否完成启动适合启动慢的容器readinessProbe 判断 Pod 是否就绪、是否可以接流量livenessProbe 判断容器是否还活着失败会触发重启。配置探针时initialDelaySeconds 要预估进程真实的启动时间。我踩过几次坑是 initialDelaySeconds 配短了进程还没开始监听端口探针就连连失败导致 Pod 被反复重启日志里全是业务本身启动慢的记录。反过来livenessProbe 的 timeoutSeconds 也不能太大否则探针本身可能拖慢主进程。资源限制方面requests 不仅用于调度还影响 QoS 等级limits 是硬性上限。memory 超限会触发 OOMKilledCPU 超限则只会被限流这个区别在排障时非常重要。如果看到 Pod 反复 OOMKilled第一反应应该是看是不是 memory limit 配得太紧而不是急着调业务代码。优雅终止的配置在 Pod 上是 terminationGracePeriodSeconds。Pod 被删除时kubelet 会先给主进程发 SIGTERM等一段时间后再发 SIGKILL。这个时间如果你不设置默认是 30 秒。对于需要做大量清理或排空旧连接的进程30 秒可能不够对于快速退出的进程太长又会拖慢发布速度可以根据业务响应速度单独调。4.3 临时调试 Podexec、logs、port-forward 与临时容器调试 Pod 最常用的几个手段里exec 和 logs 是最基础的。logs 有一个很关键的参数 --previous配合 CrashLoopBackOff 场景使用可以查看上一次崩溃容器的日志很多时候当前容器已经重启了不看 lastState 你就根本拿不到崩溃现场。如果需要把集群内的 Pod 端口暴露到本地临时调试用 port-forwardkubectl port-forward demo-pod 8080:80这样本地访问 localhost:8080 就会转发到 Pod 的 80 端口。注意这个方式适合联调和临时验证不适合当长期流量入口生产流量请走 Service 和 Ingress。还有一个高级能力从 Kubernetes 1.20 开始可以给运行中的 Pod 注入临时容器kubectl debug demo-pod -it --imagenicolaka/netshoot:latest这个临时容器不会影响目标 Pod 的运行也不会被业务杀掉非常适合在 Pod 没有 shell、没有工具包的情况下做网络诊断。我在排查网络问题时常用它跑 curl、tcpdump、dig 一类的工具相当于给每个 Pod 配了一个可以随用随走的“探针工具箱”。5. 面试与排障高频点从 Pod 视角看故障排查5.1 最常见的几个 Pod 级故障与排查路线这里把我在线上遇到频率最高的几类 Pod 故障整理成速查表现象常见原因首选排查命令ImagePullBackOff镜像名/tag 写错、私有仓库未配置 imagePullSecrets、仓库限流kubectl describe pod 看 EventsCrashLoopBackOff进程启动即退出、配置错误、依赖缺失kubectl logs --previousPendingCPU/内存不足、节点有 taint 未容忍、PVC 未绑定kubectl describe pod 看 EventsRunning 但 READY 0/1readiness 探针失败、依赖服务未就绪kubectl describe pod kubectl logsOOMKilledmemory limit 太小、内存泄漏kubectl logs kubectl describe一直 Terminating优雅终止超时、Finalizer 阻塞、containerd 卡住kubectl describe 处理节点侧问题以 ImagePullBackOff 为例碰到这个状态时先不要慌着删 Pod而是执行 kubectl describe pod xxx重点看 Events 里的事件描述。如果提示 manifest unknown大概率是 tag 拼错了如果提示拉取超时多半是仓库地址网络不通如果提示 unauthorized则要去检查 imagePullSecrets 或镜像仓库权限。还有一种比较容易迷惑的状态Pod 显示 Running 但 READY 一直是 0/1。这时候日志可能完全正常线程也起来了但 readinessProbe 就是不通。最常见原因是探针的端口或路径写错或者探针访问的地址被服务自身的防火墙规则拒了。用 kubectl describe 看探针参数再在容器里手动 curl 探针地址能很快定位问题。再提一个“Pod 删不掉”的情况。正常删除 Pod如果超过几十秒还卡在 Terminating常见原因是容器里的进程收到了 SIGTERM 但迟迟不退或者挂载点仍然被占用。这时可以先看 Pod 的 metadata.finalizers 里有什么再看看节点的 kubelet 日志。除非是 finalizer 被外部控制器卡住导致必须强制删除否则我一般不推荐一上来就 force 删除因为那可能导致容器进程残留、存储卷无法正确卸载。5.2 面试高频Pod 相关问题怎么答才能加分面试问 Pod本质是在考你对“容器编排”的理解深度。下面列出几类高频问题和我推荐的回答方向。“Pod 和容器的区别是什么”可以从三个维度回答调度粒度Pod 是最小调度单元、资源关系Pod 内容器共享网络和存储、生命周期Pod 是整体创建和销毁的。“为什么用 pause 容器”要点是pause 先创建并持有网络和 PID 命名空间业务容器再加入保证 Pod 的网络标识和进程树稳定即使业务容器重启Pod 的网络命名空间也不需要重建。“探针有哪些种类”回答 liveness、readiness、startup 的职责区别再带上 initialDelaySeconds、periodSeconds、failureThreshold 等核心参数面试官会觉得你确实配过。“Pod 一直 Pending 可能是什么原因”回答资源不足、节点选择器或亲和性不满足、污点未容忍、PVC 未就绪这几类然后补充一句实操中先用 describe 看 Events因为 Events 会直接告诉你阻塞原因。“节点资源不足时 Pod 如何被驱逐”这里要谈到 QoS 等级BestEffort 的 Pod 最先被驱逐Guaranteed 的最后PriorityClass 也会影响驱逐顺序。能答到这个深度说明你不只是停留在 kubectl get pods 的层面。“Deployment 和 Pod 的区别是什么”参考 3.2 那一节的对比表把控制器与实例的关系讲清楚再补充一句在生产环境Pod 的创建、更新、回滚都由 Deployment 这类控制器驱动不建议直接用裸 Pod 跑服务。面到 Pod 相关的问题时我的经验是尽量用实际排障中的案例来讲而不是背概念。比如你在哪个项目里碰到过 CrashLoopBackOff当时怎么查的最终定位到什么问题。一个真实的故障案例比十句标准定义更有说服力。最后分享一个我自己的练习方法找一台测试集群把一个 Pod 的 YAML 从头到尾写一遍然后故意把探针路径写错、把 resources 写超、把 restartPolicy 改成 Never挨个观察状态的演变。Pod 这个概念只有在反复折腾过它的异常状态之后才会真正变成你自己的东西。
返回列表