第 2 章:彻底搞懂 K8s Pod------从 YAML 到调度全流程
上一章认识了 Pod、Deployment、Service 三个核心对象。本章聚焦 Pod:它到底是什么?生命周期是怎样的?怎么写 YAML?kubelet 又是怎么把它跑起来的?
2.1 为什么 K8s 的最小单位是 Pod,不是容器
Docker 容器有很多好用的特性:隔离、可移植、快速启动。但容器之间是强隔离的------两个容器默认不共享网络、文件系统和进程空间。

实际业务中,有些场景需要容器"亲密无间":
| 场景 | 为什么需要同一个 Pod |
|---|---|
| 主容器 + 日志 sidecar | 共享日志卷,sidecar 读主容器写的日志 |
| 主容器 + 监控 agent | 共享进程命名空间,agent 能读到主容器的运行时信息 |
| Init 容器准备数据 | 在主容器启动前完成初始化工作 |
| 适配器模式 | 把老协议转换成新协议,主容器无感知 |
用一个具体例子感受(典型的 sidecar 模式):

yaml
apiVersion: v1
kind: Pod
metadata:
name: web-with-logger
spec:
containers:
- name: nginx
image: nginx:alpine
volumeMounts:
- name: log-vol
mountPath: /var/log/nginx
- name: log-collector
image: fluent/fluent-bit:latest
volumeMounts:
- name: log-vol
mountPath: /logs
volumes:
- name: log-vol
emptyDir: {}
这里 Nginx 写日志到 /var/log/nginx,fluent-bit 从 /logs 读,两个路径指向的是同一个 emptyDir 卷。只有同一个 Pod 内的容器才能这样共享存储。
2.2 Pod 的生命周期
一个 Pod 从创建到销毁,会经历这些阶段:

sql
Pending → ContainerCreating → Running → Succeeded/Failed → Terminating
| 阶段 | 含义 |
|---|---|
| Pending | 已创建对象,但容器还没运行起来(可能在拉镜像、等调度) |
| ContainerCreating | 正在创建容器 sandbox(pause 容器 + 网络) |
| Running | 至少一个容器正在运行 |
| Succeeded | 所有容器都正常退出(适用于 Job) |
| Failed | 有容器异常退出 |
| Terminating | 正在删除,kubelet 执行优雅停机 |
真实查看:
bash
$ kubectl get pod web-xxx
NAME READY STATUS RESTARTS AGE
web-xxx 0/1 ContainerCreating 0 5s
READY 0/1 表示这个 Pod 期望 1 个容器,目前 0 个 Ready。
2.2.1 优雅停机:preStop 与 terminationGracePeriodSeconds
Pod 被删除时,默认有 30 秒优雅停机时间:
arduino
kubectl delete pod web-xxx
→ kubelet 收到删除事件
→ 发送 SIGTERM 给主进程
→ 等 30 秒(terminationGracePeriodSeconds)
→ 进程未退出 → 发送 SIGKILL 强制终止
可以通过 preStop 钩子在收到 SIGTERM 前执行一些清理:
yaml
spec:
terminationGracePeriodSeconds: 60
containers:
- name: web
image: nginx:alpine
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "nginx -s quit; sleep 10"]
为什么要先 sleep 10?为了让 Service 的 Endpoints 从列表中移除,避免新流量再进来, graceful shutdown 更干净。
2.3 写一个完整的 Pod YAML
yaml
apiVersion: v1
kind: Pod
metadata:
name: myapp
namespace: dev
labels:
app: myapp
tier: frontend
spec:
containers:
- name: myapp
image: nginx:alpine
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
protocol: TCP
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
env:
- name: LOG_LEVEL
value: "info"
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 3
periodSeconds: 5
volumeMounts:
- name: cache
mountPath: /cache
volumes:
- name: cache
emptyDir: {}
restartPolicy: Always
逐字段解释:
| 字段 | 作用 |
|---|---|
metadata.labels |
标签,供 Service/Deployment 选择器使用 |
imagePullPolicy |
Always 每次拉;IfNotPresent 本地没有才拉;Never 只使用本地镜像 |
resources.requests |
调度时判断资源是否够用的基础值 |
resources.limits |
实际运行时的硬上限 |
livenessProbe |
存活探针,失败则重启容器 |
readinessProbe |
就绪探针,失败则不接流量 |
restartPolicy |
Always/OnFailure/Never |
2.4 探针:Pod 级自愈的触发器
Pod 有两个核心探针,必须分清楚:

| 探针 | 作用 | 失败后果 |
|---|---|---|
| livenessProbe | "这个容器还活着吗?" | 容器被 kubelet 重启 |
| readinessProbe | "这个容器准备好接流量了吗?" | 从 Service Endpoints 列表移除,不被访问 |
为什么需要分开?
- 应用死锁了:liveness 失败 → 重启
- 应用正在启动,还没连上数据库:readiness 失败 → 暂时不接入流量,但不重启
探针支持三种探测方式:
yaml
livenessProbe:
httpGet:
path: /healthz
port: 8080
# 或 exec
# exec:
# command: ["cat", "/tmp/healthy"]
# 或 tcpSocket
# tcpSocket:
# port: 8080
真实排障:
bash
$ kubectl describe pod myapp
...
Events:
Warning Unhealthy Liveness probe failed: HTTP probe failed with statuscode: 500
Normal Killing Container myapp failed liveness probe, will be restarted
2.5 Init 容器:在主容器之前干活
Init 容器在业务容器启动前按顺序执行,全部成功后业务容器才启动。典型用途:
- 等待数据库就绪
- 生成配置文件
- 拉取外部依赖
yaml
spec:
initContainers:
- name: wait-for-db
image: busybox:1.31
command: ['sh', '-c', 'until nc -z mysql 3306; do echo waiting; sleep 2; done']
containers:
- name: myapp
image: myapp:v1
如果 init 容器失败,Pod 会反复重启 init 容器,业务容器永远不会启动。kubectl logs myapp -c wait-for-db 可以看到 init 容器的日志。
2.6 从 kubectl run 到 Pod 调度全流程
bash
kubectl run mynginx --image=nginx:alpine
这条命令背后发生的事:
kubectl把 Pod 对象发给 apiserver- apiserver 写入 etcd
- scheduler 发现这个 Pod 还没绑定节点,选优后写入
nodeName - 对应节点的 kubelet watch 到分配给自己的 Pod
- kubelet 调用 containerd:创建 pause 容器 → 配置网络 → 拉 Nginx 镜像 → 起 Nginx 容器
- kubelet 持续执行 readiness/liveness 探针
- Pod Ready 后,Endpoints 控制器把它的 IP 加入相关 Service
注意:裸 Pod 没有控制器盯着。如果节点重启或 Pod 被删,它不会自动重建。所以生产环境用 Deployment 管理 Pod。
2.7 常见 Pod 状态排障
| 状态 | 排查方向 |
|---|---|
Pending |
资源不够、节点有污点、镜像拉取慢 |
ContainerCreating |
CNI 插件问题、镜像拉取中、存储挂载失败 |
CrashLoopBackOff |
容器启动后立刻退出,看 kubectl logs --previous |
ImagePullBackOff |
镜像名错误、没权限拉、网络超时 |
ErrImagePull |
镜像不存在或网络不通 |
Terminating 卡住 |
kubelet 没收到删除完成信号,需要强制删除或检查节点 |
2.8 本章小结
- Pod 是 K8s 最小调度单元,多个容器可共享网络和存储
- Pod 生命周期:Pending → ContainerCreating → Running → Succeeded/Failed → Terminating
livenessProbe管重启,readinessProbe管流量接入- Init 容器在主容器前执行,适合做初始化和等待依赖
- 裸 Pod 没有自愈能力,生产环境必须使用 Deployment 等控制器
- 排障首选命令:
kubectl describe pod看 Events,kubectl logs看日志
2.9 课后练习
- 创建一个包含两个容器的 Pod:一个写文件、一个读同一个文件并打印内容。
- 故意让
livenessProbe失败,观察 Pod 的 RESTARTS 数字增长。 - 用 Init 容器实现"等待某个域名可以解析后再启动主容器"。