Kubernetes 为什么要把容器再包一层 Pod?
容器已经很成熟了,为什么 Kubernetes 还要再抽象出一个 Pod,而且几乎所有人都说它是最核心的概念?
1. 先换一个视角:容器太“干净”,现实应用往往不干净
以前我习惯把容器理解成“一个进程、一个沙盒”。隔离性很好,封装也干净。但一进真实场景,麻烦就来了:很少有应用是完全独立跑的。
有的关系还比较松散,比如 Nginx、WordPress、MariaDB,可以分别调度,靠 IP 通信。还有一类关系很紧:启动前要有人帮它初始化配置,或者日志代理必须读同一个本地文件再转发出去。这两个进程如果被拆到不同机器上,联系一断,就没法干活。
那能不能把它们塞进同一个容器?
可以,但我觉得这不是好办法。容器的初衷就是一个进程、一个应用。一个容器里塞多个应用,既难管,也违背隔离。
所以需要在容器外面再加一层“收纳舱”:里面的容器相对独立,同时又共享一小块网络和存储,而且永远绑在一起调度。这一层就是 Pod。
Pod 原意是豌豆荚,后来也有舱室、太空舱的意思。容器就是豆荚里那些豌豆,spec.containers 本来就是数组,允许定义多个容器。
对我来说,这句话最好记:容器解决“怎么跑一个进程”,Pod 解决“哪些进程必须待在一起跑”。
2. 为什么 Pod 会变成 Kubernetes 的核心
有了这层打包,里面的容器总是一起调度、一起运行,不会被拆开。更重要的是,Pod 属于 Kubernetes,可以在不直接碰下层容器运行时的前提下做定制。集群级别管理应用,就会顺手很多。
Kubernetes 把 Pod 当成应用调度部署的最小单位。它像原子:对外是最小单位,对内其实还有结构。后面的 ConfigMap、Job、Deployment,都是从 Pod 往外长出来的。
经典那张资源关系图就是这个意思,从 Pod 扩到配置、离线作业、多实例部署:

但随着发展,这张图已经不能完全概括了,于是有了一张更完整的图,后面很多对象都可以按“围着 Pod 转”来看:

两张图看下来,结论很简单:Kubernetes 里几乎所有功能,最后都要通过 Pod 落地。 所以它不是众多对象里普通的一个,而是中心。
3. YAML 里 Pod 怎么写
Pod 也是 API 对象,四个基本部分少不了:apiVersion、kind、metadata、spec。
对 Pod 来说,apiVersion 是 v1,kind 是 Pod。Docker 创建容器可以不起名字,Kubernetes 里不行,Pod 必须有 metadata.name。光有名字还不够,labels 用来贴分类标签,后面做筛选、关联会很方便,比如 env=dev/test/prod、tier=front/back。
spec 里最重要的是 containers。每个容器至少要有 name 和 image,否则校验过不去。其他字段大多能对上 Docker 的经验:
| 字段 | 我的对应理解 |
|---|---|
| ports | 有点像 docker -p,声明容器端口 |
| imagePullPolicy | Always / Never / IfNotPresent,本地没有才拉 |
| env | 运行时环境变量,比写死在镜像里灵活 |
| command | 相当于 Dockerfile 的 ENTRYPOINT |
| args | 相当于 Dockerfile 的 CMD,注意和 Docker 命令行含义不完全一样 |
一份最小例子可以长这样:
1 | apiVersion: v1 |
YAML 把期望状态写清楚,比一条很长的 docker run 好读,也对机器友好。这就是声明式。
4. kubectl 怎么操作,以及我踩到的 CrashLoopBackOff
创建和删除还是上次那套:
1 | kubectl apply -f busy-pod.yml |
和 Docker 不一样,Pod 不会在前台跑,默认就像加了 -d。想看输出,用 kubectl logs:

kubectl get pod 看状态时,这个 busy-pod 并不健康:

排错要看 kubectl describe,重点在末尾 Events。它被调度到 minikube,镜像也在本地,容器能创建、能启动,但马上退出。Kubernetes 默认会重启 Pod,于是就进入反复停止-启动,最后变成 CrashLoopBackOff。

原因很直接:这个容器只跑了一条 echo 就结束了。集群里大多数应用是常驻服务,不会主动退出,所以把 busy-pod 删掉,换成 Nginx 这种才更像日常用法。
ngx-pod 起来后就是 Running:

日志也能看到 Nginx 启动过程:

拷文件、进容器也有对应命令。kubectl cp 把本地文件拷进 Pod,kubectl exec 进去执行命令。和 Docker 有个小差别:Pod 名后面要加 --,把 kubectl 参数和容器里的命令隔开。

1 | kubectl cp a.txt ngx-pod:/tmp |
5. 一张表把关系记住
| 概念 | 我理解的角色 | 一句话 |
|---|---|---|
| 容器 | 进程的沙盒 | 一个进程、一份隔离 |
| Pod | 调度原子 | 把必须在一起的容器绑死 |
| metadata.name | 身份证 | 集群里对象必须有名字 |
| metadata.labels | 分类贴 | 方便识别、筛选、关联 |
| spec.containers | 豆荚里的豌豆 | 名字 + 镜像是底线 |
| kubectl apply/delete | 声明落地 / 删掉 | 用 YAML 管对象 |
| kubectl get/describe/logs | 看状态、看事件、看输出 | 排错先看这三件 |
| kubectl cp/exec | 拷文件、进容器 | 多容器时用 -c 指定 |
没有 Pod,紧耦合进程就只能硬塞进一个容器,或者拆开后靠跨节点通信,调度和对齐都会变难。有了 Pod,共享网络和存储、一起调度,这条线才清楚。
几点思考与理解
第一,Pod 和容器不是替换关系。容器还是真正干活的运行时,Pod 是 Kubernetes 的调度单位。有点像进程组和进程:进程还在,但调度和管理按组来。
第二,生产上通常不要直接创建“裸 Pod”。它只是对容器做了简单包装,比较脆弱。真正扛业务的是 Job、CronJob、Deployment 这些对象,它们会给 Pod 补上重启策略、副本、自愈。即使只跑一个实例,后面也会更倾向用 Deployment,replicas: 1,让控制器盯着它。
第三,image: xxx:latest 要小心。imagePullPolicy 不是永远默认 IfNotPresent。标签是 latest 时,默认往往是 Always,没外网就会拉取失败。离线环境最好写死具体版本。
第四,排错顺序可以固定下来:get 看 Ready 和 STATUS,describe 看 Events,logs 看容器输出。READY 列是 x/y,y 是容器总数,x 是已经 Ready 的数量。CrashLoopBackOff 不一定是镜像坏了,也可能只是进程跑完就退出了。
图片来源:极客时间 《Kubernetes入门实战课》 第 12 讲《Pod:如何理解这个Kubernetes里最核心的概念?》,作者 Chrono。