Kubernetes 基础体系 · 第 13/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。
Kubernetes Pod 完整模型:共享 Namespace、容器、Sandbox 和生命周期
Pod 是 Kubernetes 调度、管理和观测的最小工作负载单元。它通常包含一个或多个容器,这些容器共享一部分运行环境和生命周期,但并不因此变成“一个容器”。
理解 Pod 需要同时回答四个问题:
- Pod 在 Kubernetes API 中是什么对象,和容器是什么关系?
- Pod 内哪些 Namespace 被共享,哪些资源仍然隔离?
- Sandbox、基础设施容器和业务容器如何协同工作?
- Pod 从创建、启动、运行到重启、终止或被驱逐时,状态如何变化?
下面以 Kubernetes 当前稳定 API 为基础说明。具体容器运行时、Linux 内核能力、云厂商网络插件和发行版可能改变实现细节,但不会改变 Pod 作为调度与管理边界的基本模型。
一、Pod 的抽象:一组共享上下文的容器
Pod 的 API 对象主要由两部分组成:
apiVersion: v1
kind: Pod
metadata:
name: web
spec:
containers:
- name: nginx
image: nginx:1.27
status:
phase: Running
podIP: 10.244.1.20
其中:
metadata描述 Pod 的身份、标签、注解和命名空间。spec是用户期望的状态,例如容器、镜像、资源、卷和重启策略。status是 Kubernetes 观察到的状态,例如调度结果、Pod IP、容器状态和条件。spec.containers至少包含一个应用容器。initContainers、原生 sidecar、ephemeral container 是不同生命周期语义的容器类型。
Pod 不是一个进程,也不是一个镜像。它是一个由 kubelet 管理的运行单元:
控制器或用户
│
│ 创建/更新 Pod 对象
▼
API Server
│
│ 调度结果:节点名
▼
kubelet
│
├── 创建 Pod Sandbox
├── 创建共享网络等运行环境
├── 启动 Init 容器
├── 启动普通容器和 Sidecar
└── 汇报 Pod Status
Pod 内的容器具有以下共同点:
- 通常共享网络 Namespace,因此可以通过
localhost互相访问。 - 通常共享 IPC Namespace,因此可以使用同一套 System V IPC 或 POSIX message queue。
- 通常共享 UTS Namespace,因此具有相同的 hostname。
- 可以通过 Pod 卷共享文件。
- 由同一个 kubelet 负责启动、重启、探测和终止。
- 作为一个整体被调度到同一个节点,不能把 Pod 内的容器分别调度到不同节点。
但它们仍然可能拥有不同的:
- 根文件系统和容器镜像;
- Mount Namespace;
- PID Namespace;
- 用户身份和 Linux capabilities;
- CPU、内存、设备及安全策略;
- 容器级状态与退出码。
因此,“Pod 是容器组”是一个起点,但“共享环境”不等于“所有隔离边界都消失”。
二、Pod 共享哪些 Namespace
2.1 Network Namespace:Pod 网络的核心
默认情况下,一个 Pod 内的容器共享同一个 Network Namespace。这个 Namespace 包含:
- 网络接口;
- IP 地址;
- 路由表;
- 端口空间;
- ARP 或邻居表;
- 网络相关的内核状态。
因此同一个 Pod 中两个容器监听同一个端口会冲突:
容器 A:监听 0.0.0.0:8080
容器 B:监听 0.0.0.0:8080
结果:后启动或后绑定的容器通常因 Address already in use 失败
它们之间访问服务通常使用:
http://127.0.0.1:8080
而不是通过 Pod IP。Pod IP 仍然可以用于从 Pod 外部访问,但容器间使用回环地址更直接。
一个简单的验证 Pod:
apiVersion: v1
kind: Pod
metadata:
name: shared-network
spec:
containers:
- name: web
image: nginx:1.27
ports:
- containerPort: 80
- name: client
image: curlimages/curl:8.10.1
command: ["sh", "-c"]
args:
- |
sleep 3600
创建并查看:
kubectl apply -f shared-network.yaml
kubectl get pod shared-network -o wide
进入 client 容器访问 Nginx:
kubectl exec -it shared-network -c client -- \
curl -sS http://127.0.0.1
预期会返回 Nginx 的 HTML 页面。这里的因果链是:
- kubelet 为 Pod 创建一个共享网络环境;
- 两个容器加入该环境;
- Nginx 在该网络 Namespace 中监听
80; client容器的127.0.0.1指向同一个 Network Namespace;- 请求到达 Nginx,而不是
client自己的独立网络栈。
需要注意,hostNetwork: true 会让 Pod 使用节点的网络 Namespace。此时 Pod 不再拥有通常意义上的独立 Pod 网络,端口会直接与节点端口竞争,也可能影响 DNS 行为和网络隔离:
spec:
hostNetwork: true
这通常只适用于确实需要节点网络的系统组件。它不是提高网络性能的通用开关。
2.2 IPC Namespace:共享进程间通信对象
Pod 内容器默认共享 IPC Namespace,因此可以共享某些 Linux 进程间通信对象,例如:
- System V shared memory;
- System V semaphore;
- POSIX message queue。
这使得一个容器创建的共享内存对象可能被另一个容器访问。但 IPC 共享不代表文件系统共享;要传递普通文件,仍然需要共享卷。
hostIPC: true 会让 Pod 使用节点的 IPC Namespace,隔离边界显著扩大,必须谨慎使用。
2.3 UTS Namespace:hostname 通常一致
UTS Namespace 管理 hostname 和 domain name。Pod 内的容器通常看到相同的 hostname:
kubectl exec shared-network -c client -- hostname
kubectl exec shared-network -c web -- hostname
两条命令通常输出相同值。这个 hostname 通常与 Pod 名称、Pod hostname 设置及运行时实现有关,不应把它当作稳定的服务发现标识。
Pod 的 DNS 名称由 Kubernetes DNS、Service 和相关 DNS 配置共同决定。容器间通信使用 localhost,跨 Pod 通信则应使用 Service DNS,而不是依赖 Pod hostname。
2.4 PID Namespace:默认不共享,可显式开启
默认情况下,Pod 内的容器通常拥有各自的 PID Namespace。也就是说:
- 容器 A 中的进程通常不会出现在容器 B 的
ps输出中; - 容器 A 中的 PID 1 不等于容器 B 中的 PID 1;
- 一个容器不能仅凭同 Pod 身份向另一个容器的进程发送信号。
可以通过 shareProcessNamespace: true 显式让 Pod 内容器共享 PID Namespace:
apiVersion: v1
kind: Pod
metadata:
name: shared-pid
spec:
shareProcessNamespace: true
containers:
- name: app
image: busybox:1.36
command: ["sh", "-c", "sleep 3600"]
- name: inspector
image: busybox:1.36
command: ["sh", "-c", "sleep 3600"]
此时 inspector 可以观察 app 容器中的进程。共享 PID Namespace 的代价是进程可见性扩大,调试能力和信息暴露面同时增加。
hostPID: true 则会让 Pod 使用节点 PID Namespace。这样 Pod 可能看到节点上的进程,属于更强的宿主机权限边界,不应与普通的 Pod 级进程共享混为一谈。
2.5 Mount Namespace 和文件系统:通常不共享
容器通常拥有自己的根文件系统和 Mount Namespace。下面的两个容器并不会因为属于同一 Pod 就自动看到对方镜像中的文件:
容器 A 的 /app/config.yaml
容器 B 通常看不到
如果需要共享文件,必须显式声明卷:
apiVersion: v1
kind: Pod
metadata:
name: shared-volume
spec:
volumes:
- name: shared-data
emptyDir: {}
containers:
- name: writer
image: busybox:1.36
command: ["sh", "-c"]
args:
- |
echo "written by writer" > /data/message
sleep 3600
volumeMounts:
- name: shared-data
mountPath: /data
- name: reader
image: busybox:1.36
command: ["sh", "-c"]
args:
- |
while true; do cat /data/message 2>/dev/null || true; sleep 5; done
volumeMounts:
- name: shared-data
mountPath: /data
这里共享的是 emptyDir 卷挂载点,不是两个容器的根文件系统。emptyDir 的生命周期与 Pod 绑定:容器重启时数据通常保留,但 Pod 被删除并重新创建后,原来的 emptyDir 不会保留。
2.6 用户 Namespace 与安全边界
容器是否使用独立的 Linux user namespace,取决于 Kubernetes 配置、版本、运行时和 Pod 安全设置。不能简单地把“同一个 Pod”理解为“同一个 Linux 用户空间”。
即使容器共享网络,也可以拥有不同的:
securityContext:
runAsUser: 1000
allowPrivilegeEscalation: false
因此以下概念必须分开:
- Pod 级共享 Namespace;
- 容器级用户和能力;
- 节点级 Namespace;
- Kubernetes RBAC 身份;
- Linux 文件权限。
Kubernetes ServiceAccount 也不是 Linux 用户。ServiceAccount 控制 API Server 访问身份,不能替代 runAsUser 或文件系统权限。
三、Pod Sandbox:容器之前存在的运行环境
3.1 Sandbox 的含义
Pod Sandbox 是容器运行时为 Pod 建立的共享运行环境。它不是 Kubernetes Pod API 中一个供用户直接编写的普通容器,而是 CRI(Container Runtime Interface)层的重要概念。
典型创建流程如下:
kubelet
│
│ RunPodSandbox
▼
容器运行时
│
├── 创建 Pod 的网络 Namespace
├── 调用 CNI 配置网卡、IP、路由
├── 创建或准备其他共享 Namespace
└── 返回 Sandbox ID
│
├── CreateContainer / StartContainer:Init 容器
└── CreateContainer / StartContainer:应用容器
在很多 Linux 容器运行时中,Sandbox 由一个很小的“基础设施容器”维持,例如常被称为 pause 容器。它的主要作用不是执行业务,而是持有或维持 Pod 的共享 Namespace,使其他容器可以加入这个环境。
但要区分三层概念:
- Pod:Kubernetes API 对象和调度单元;
- Pod Sandbox:CRI 运行时管理的 Pod 级运行环境;
- pause 或类似基础设施容器:某些运行时用来维持 Sandbox 的实现机制。
Kubernetes API 并不保证每个发行版一定使用名为 pause 的容器,也不保证其镜像、进程或内部实现完全相同。不要依赖 pause 容器的名称、ID 或 PID 编写业务逻辑。
3.2 Sandbox 与业务容器的关系
应用容器加入 Sandbox,而不是各自独立创建一个 Pod 网络。于是:
Pod Sandbox
├── 共享 Network Namespace
├── 共享 IPC Namespace
├── 可能共享 PID Namespace
├── app 容器
├── sidecar 容器
└── 其他容器
当某个普通容器退出时,kubelet 通常只重启该容器,而不必重新创建整个 Sandbox。例如应用进程崩溃:
容器进程退出
│
▼
容器状态变为 Terminated
│
▼
kubelet 根据 RestartPolicy 决定是否重启
│
└── 重启同一 Pod 中的该容器
但如果 Sandbox 丢失或损坏,影响范围会扩大:
Sandbox 不可用
│
▼
Pod 级网络和共享运行环境失效
│
▼
运行时重建 Sandbox
│
▼
重新创建或启动 Pod 中的容器
│
▼
Pod IP 可能发生变化
因此,“容器重启”和“Pod Sandbox 重建”不是同一类故障。前者通常只影响一个容器;后者可能影响整个 Pod,并导致网络连接中断。
3.3 Sandbox 创建失败的诊断
当 Sandbox 创建失败时,业务容器可能根本没有启动。常见原因包括:
- CNI 插件无法分配 IP;
- 节点网络配置错误;
- IP 地址池耗尽;
- 容器运行时异常;
- 节点磁盘、进程或内核资源不足;
- Pod 使用了节点不支持的 Namespace 或安全能力。
诊断顺序可以是:
kubectl describe pod <pod-name>
kubectl get events --sort-by=.lastTimestamp
kubectl get pod <pod-name> -o wide
重点观察 Events 中的:
FailedCreatePodSandbox
Failed to create pod sandbox
networkPlugin ...
如果没有任何业务容器日志,不代表应用没有问题;可能是 Sandbox 尚未创建成功,应用进程尚未获得启动机会。
四、Pod 的创建和启动时序
一个普通 Pod 的典型启动过程如下:
sequenceDiagram
participant U as 用户/控制器
participant A as API Server
participant S as Scheduler
participant K as kubelet
participant R as CRI Runtime
participant C as CNI
participant I as Init 容器
participant W as 普通容器
U->>A: 创建 Pod Spec
A->>S: 发现未调度 Pod
S->>A: 写入 nodeName
A->>K: kubelet watch 到 Pod
K->>R: RunPodSandbox
R->>C: 配置网络
C-->>R: 返回接口/IP
R-->>K: Sandbox Ready
K->>R: 创建并启动 Init 容器
R->>I: 执行初始化任务
I-->>K: 成功退出
K->>R: 创建并启动普通容器
R->>W: 启动业务进程
K->>A: 更新 Pod Status
实际系统会并发拉取镜像、创建容器和执行探针,但生命周期约束仍然成立:
- Pod 必须先被调度到节点;
- Sandbox 必须可用;
- Init 容器必须按顺序成功完成;
- 普通容器才会正常启动;
- kubelet 持续执行探针、收集状态并处理重启。
镜像拉取可能发生在不同阶段,具体并发细节由 kubelet 和运行时实现决定。不要把“镜像已下载”当作“容器已经开始执行”。
五、Init 容器、普通容器和 Sidecar
5.1 Init 容器:启动前置条件
Init 容器在应用容器之前运行,并且必须按声明顺序逐个成功完成:
apiVersion: v1
kind: Pod
metadata:
name: init-example
spec:
initContainers:
- name: prepare
image: busybox:1.36
command: ["sh", "-c"]
args:
- |
echo "prepare data"
touch /work/ready
volumeMounts:
- name: work
mountPath: /work
containers:
- name: app
image: busybox:1.36
command: ["sh", "-c"]
args:
- |
test -f /work/ready
echo "application started"
sleep 3600
volumeMounts:
- name: work
mountPath: /work
volumes:
- name: work
emptyDir: {}
启动逻辑是:
prepare 未启动 → app 不启动
prepare 运行中 → app 不启动
prepare 退出码为 0 → 允许 app 启动
prepare 退出码非 0 → 根据 Pod 重启策略重新执行 prepare
Init 容器适合做一次性准备,例如:
- 生成配置;
- 检查依赖是否满足;
- 初始化共享卷;
- 执行数据库 Schema 准备;
- 下载启动所需的静态文件。
Init 容器和普通容器都使用 Pod 的共享网络和显式共享卷,因此 Init 容器可以访问同一 Pod 网络,也可以向卷写入普通容器读取的文件。
查询 Init 状态:
kubectl get pod init-example \
-o jsonpath='{range .status.initContainerStatuses[*]}{.name}{" => "}{.state}{"\n"}{end}'
查看 Init 日志:
kubectl logs init-example -c prepare
常见误解是把“启动一次”理解成“只执行一次”。Init 容器是每次该 Pod 实例启动时都要执行的前置阶段;如果 Pod 被删除后由控制器重新创建,新的 Pod 会再次执行 Init 容器。
5.2 普通容器:Pod 的长期运行部分
spec.containers 中的普通容器是 Pod 的主要运行容器。它们可以并发启动,没有一个普通容器天然先于另一个普通容器完成初始化。
如果两个容器存在强启动依赖,例如:
app 必须等 proxy 完成某项初始化
不能仅通过声明顺序表达。可以使用:
- Init 容器完成一次性准备;
- 应用自身重试;
- readinessProbe 表示“已经可以接收流量”;
- 原生 sidecar 语义;
- 明确的进程间协议。
readinessProbe 不会阻止容器启动,而是影响该 Pod 是否进入可服务状态。启动阻塞和流量就绪是两个不同问题。
5.3 原生 Sidecar:由 Init 容器语义表达长期伴随进程
Kubernetes 支持使用带有 restartPolicy: Always 的 Init 容器表达原生 sidecar:
apiVersion: v1
kind: Pod
metadata:
name: native-sidecar
spec:
initContainers:
- name: log-agent
image: busybox:1.36
restartPolicy: Always
command: ["sh", "-c"]
args:
- |
while true; do
echo "sidecar is running"
sleep 5
done
containers:
- name: app
image: busybox:1.36
command: ["sh", "-c"]
args:
- |
echo "app started"
sleep 20
它与普通 Init 容器的关键区别是:
- 仍位于
initContainers中,因此会先于普通容器启动; restartPolicy: Always表示它是持续运行的 sidecar;- 它不会因为没有退出而阻塞普通容器启动;
- kubelet 会持续管理它;
- Pod 终止时,sidecar 的终止顺序与普通应用容器不同。
原生 sidecar 能力已经在较新的 Kubernetes 版本中稳定化,但集群必须满足对应版本和特性支持要求。对于版本较旧的集群,直接使用该字段可能被拒绝或表现不同,应先确认集群版本和 API 行为。
传统 sidecar 通常写在 spec.containers 中。这种方式也能工作,但生命周期语义较弱:如果日志代理永不退出,某些 Job 的完成判断、Pod 终止和容器顺序处理会更复杂。原生 sidecar 的价值在于把“先启动、持续运行、最后关闭”的意图交给 kubelet 管理。
5.4 Ephemeral Container:调试边界
Ephemeral Container 用于向已经运行的 Pod 临时注入调试容器。它不是正常的应用容器,也不是 Init 容器:
kubectl debug -it <pod-name> \
--image=busybox:1.36 \
--target=<existing-container> \
-- sh
典型用途:
- 原应用镜像没有 shell;
- 需要使用
ps、nsenter或网络诊断工具; - 需要观察目标容器的进程或 Namespace;
- 不希望修改原 Pod 的声明式配置。
Ephemeral Container 的边界包括:
- 不会自动重启;
- 不能作为应用正常生命周期的一部分;
- 不能依赖它完成启动前初始化;
- 添加后不能像普通容器一样任意修改其全部字段;
- 是否能观察目标容器进程,取决于 PID Namespace 是否共享以及
--target和运行时支持; - 需要相应 RBAC 权限,且可能受到 Pod Security 或节点安全策略限制。
查询:
kubectl get pod <pod-name> -o jsonpath='{.spec.ephemeralContainers[*].name}'
调试容器能看到什么,不由“属于同一个 Pod”单独决定,而由这些条件共同决定:
可见进程
= Pod 是否 shareProcessNamespace
+ 运行时 target 支持
+ 容器权限
+ 节点和内核安全策略
六、容器状态和 Pod Phase 不是一回事
6.1 容器状态
每个容器都有一个主要状态:
Waiting:尚未运行,例如拉取镜像、等待 Init 容器、退避重启;Running:容器进程正在运行;Terminated:容器进程已经结束,包含退出码、原因和时间。
查看容器状态:
kubectl get pod <pod-name> -o json \
| jq '.status.containerStatuses, .status.initContainerStatuses'
示例:
{
"name": "app",
"ready": false,
"restartCount": 3,
"state": {
"waiting": {
"reason": "CrashLoopBackOff",
"message": "back-off 40s restarting failed container"
}
},
"lastState": {
"terminated": {
"exitCode": 1,
"reason": "Error"
}
}
}
CrashLoopBackOff 不是独立的生命周期状态,而是 kubelet 对反复失败容器执行重启退避时显示的等待原因。它表达的是“现在暂缓重启”,不是“应用一定因为某个固定原因崩溃”。
诊断时应同时检查:
kubectl describe pod <pod-name>
kubectl logs <pod-name> -c <container-name>
kubectl logs <pod-name> -c <container-name> --previous
--previous 很重要,因为当前容器可能已经重启,当前日志不一定包含上一个失败实例的输出。
6.2 Pod Phase
Pod 的 status.phase 是较粗粒度的状态,合法值包括:
| Phase | 含义 |
|---|---|
Pending |
Pod 已被 API Server 接受,但尚未完成运行准备,例如等待调度、镜像或 Init 容器 |
Running |
Pod 已绑定节点,且至少有一个容器运行或正在启动 |
Succeeded |
Pod 中所有容器都成功终止,且不会再重启 |
Failed |
所有容器都已终止,至少一个容器失败,且不会再重启 |
Unknown |
API Server 无法获得 Pod 状态,常见于节点通信问题 |
Phase 不是健康检查结果,也不是容器状态的简单映射。例如:
- Pod 可以是
Running,但Ready为False; - Pod 可以是
Running,但其中一个容器处于重启退避; - Pod 可以因为 Readiness 失败而不接收流量,但仍然保持运行;
- Pod 被删除时,API 对象可能仍存在,但其终止过程由
deletionTimestamp和各容器状态体现。
因此不要使用类似下面的判断代替真正的就绪判断:
phase == Running ⇒ 可以接收流量
正确关系更接近:
可接收 Service 流量
≈ PodReady 条件为 True
且 Service/EndpointSlice 已纳入该端点
两者还存在控制器和网络传播延迟。
6.3 Pod Conditions
常见 Pod Conditions 包括:
PodScheduled:是否已经成功调度;Initialized:Init 容器是否完成;ContainersReady:所有应用容器是否就绪;Ready:Pod 是否满足对外服务的就绪条件;PodReadyToStartContainers:较新版本中用于表示 Pod Sandbox 和网络等启动前环境是否准备好。
查看条件:
kubectl get pod <pod-name> -o json \
| jq '.status.conditions'
Conditions 具有 type、status、reason、message、lastTransitionTime 等字段。排障时应结合这些字段判断“阻塞发生在哪一阶段”,而不是只看 phase。
七、RestartPolicy:重启谁、何时重启
Pod 的 restartPolicy 有三个合法值:
spec:
restartPolicy: Always
Always:容器结束后总是尝试重启;OnFailure:只有退出码非零时重启;Never:不重启。
默认值是 Always。
该策略主要由 kubelet在同一 Pod 内管理容器时使用。它不表示:
容器失败 ⇒ Kubernetes 一定创建一个新 Pod
更准确的模型是:
同一个 Pod 对象
├── 容器进程退出
├── kubelet 根据 RestartPolicy 判断
├── 可能重启同一容器
└── restartCount 增加
只有控制器,例如 Deployment、Job 或 StatefulSet,才会根据各自语义创建或替换 Pod。
7.1 RestartPolicy 与 Job
对于 Job,常见配置是:
apiVersion: batch/v1
kind: Job
metadata:
name: one-shot
spec:
backoffLimit: 3
template:
spec:
restartPolicy: Never
containers:
- name: task
image: busybox:1.36
command: ["sh", "-c", "echo task; exit 1"]
这里有两层失败处理:
- Pod 内的
restartPolicy: Never:容器失败后,当前 Pod 不在节点内重启该容器; - Job 控制器观察 Pod 失败:根据
backoffLimit决定是否创建新的 Pod。
如果使用 OnFailure,Job 可能让同一个 Pod 内的容器先重试;如果最终 Pod 失败,Job 控制器再根据策略处理。Pod 重启和 Pod 替换是两个不同层次的行为。
7.2 RestartPolicy 与 Init 容器
Init 容器失败时,kubelet 会根据 Pod 的重启语义重新执行它,直到成功或 Pod 进入失败路径。由于普通容器尚未启动,应用容器的失败日志不会解释 Init 阶段的问题。
查看 Init 容器是否反复失败:
kubectl describe pod <pod-name>
kubectl logs <pod-name> -c <init-container-name>
一个 Init 容器不断失败时,Pod 常见地停留在 Pending,同时 Initialized 条件为 False。
八、探针、就绪和重启之间的因果关系
探针不是容器生命周期本身,但它会影响 Pod 的可用性和重启行为。
8.1 Readiness Probe
Readiness 失败:
容器仍运行
├── Pod Ready 变为 False
└── Service 通常停止把新流量发给该 Pod
它不会直接重启容器。适用于“进程还活着,但暂时不能服务”的情况,例如:
- 正在加载配置;
- 依赖服务不可用;
- 连接池尚未建立;
- 过载保护触发。
8.2 Liveness Probe
Liveness 失败达到阈值后,kubelet 会杀死容器,随后根据 restartPolicy 处理:
Liveness 连续失败
│
▼
kubelet 终止容器
│
▼
容器状态 Terminated
│
▼
根据 RestartPolicy 重新启动或结束
如果应用启动很慢,却只配置了过早的 liveness 探针,可能出现:
应用尚未完成启动
→ liveness 失败
→ kubelet 杀死应用
→ 应用再次启动
→ 再次被杀死
→ CrashLoopBackOff
startupProbe 可以把“启动期”和“运行期”分开:startup 成功前,liveness 和 readiness 通常不会按正常运行阶段生效。
九、Pod 的终止流程
删除 Pod 时,Kubernetes 通常执行优雅终止,而不是立即杀死主进程:
sequenceDiagram
participant U as 用户/控制器
participant A as API Server
participant K as kubelet
participant C as 容器
participant E as EndpointSlice/Service
U->>A: DELETE Pod
A-->>K: Pod 带 deletionTimestamp
A->>E: 端点开始移除/标记不可用
K->>C: 执行 preStop(若配置)
K->>C: 发送 SIGTERM
Note over K,C: 等待 terminationGracePeriodSeconds
K->>C: 超时后发送 SIGKILL
K->>A: 更新容器和 Pod 状态
A-->>U: Pod 对象最终消失
默认终止宽限期通常为 30 秒,但应以 Pod 的 terminationGracePeriodSeconds 为准:
spec:
terminationGracePeriodSeconds: 60
容器终止通常包括:
- kubelet 发现 Pod 被删除或需要终止;
- 执行
preStop生命周期钩子(如果配置); - 向容器主进程发送
SIGTERM; - 等待宽限期;
- 超时后发送
SIGKILL; - 清理容器和 Sandbox;
- 更新 Pod 状态并最终删除对象。
应用必须真正处理 SIGTERM。如果容器中的 PID 1 是一个不会转发信号的 shell,应用可能收不到优雅退出通知。可以使用正确的进程启动方式,例如:
ENTRYPOINT ["/usr/local/bin/server"]
而不是让 shell 长期充当不转发信号的中间进程。
preStop 不是额外的宽限期。它消耗同一个终止宽限窗口;如果 preStop 已经等待很久,应用剩余的优雅退出时间会减少。
9.1 终止时的流量与并发请求
删除 Pod 时,Service 端点更新、负载均衡器传播和客户端连接关闭并不一定瞬时完成。应用的优雅退出逻辑应包括:
- 停止接受新请求;
- 等待正在处理的请求结束;
- 关闭连接和文件;
- 在宽限期内退出。
只调用 sleep 不能保证正确排空连接,因为它没有表达“当前请求何时完成”。
9.2 强制删除的风险
kubectl delete pod <pod-name> --grace-period=0 --force
强制删除会跳过正常的对象确认和部分优雅终止流程。它适合处理节点失联、对象长期卡住等特殊情况,但风险包括:
- 进程仍可能在失联节点上运行;
- 有状态应用可能出现双实例或数据冲突;
- 未完成的写入可能丢失;
- Service 端点和实际进程状态在短时间内不一致。
强制删除不是修复应用崩溃的常规手段。
十、Eviction:节点压力下的 Pod 驱逐
Eviction 是 Kubernetes 在资源压力下主动终止 Pod 的机制,常见触发源包括:
- 节点内存压力;
- 节点磁盘压力;
- 临时存储不足;
- PID 数量压力;
- 节点失去可用状态。
节点上的 kubelet 会根据压力信号和驱逐策略选择 Pod,并尝试进行终止。被驱逐的 Pod 常见状态可以通过以下命令观察:
kubectl get pod <pod-name> -o json \
| jq '{phase: .status.phase, reason: .status.reason, message: .status.message}'
可能看到类似:
{
"phase": "Failed",
"reason": "Evicted",
"message": "The node was low on resource: memory."
}
驱逐与容器崩溃不同:
容器崩溃:
kubelet 可能在原 Pod 内重启容器
Pod 被驱逐:
当前 Pod 实例被终止
控制器可能在其他节点创建替代 Pod
控制器是否创建替代 Pod,取决于工作负载类型和控制器状态。例如 Deployment 通常会维持副本数;裸 Pod 没有控制器负责补建。
10.1 资源请求、限制和驱逐选择
Pod 的资源声明影响调度和节点压力下的优先级。以内存为例:
requests.memory主要用于调度;limits.memory限制容器可使用的内存上限;- 节点压力下,QoS、请求与实际使用的关系会影响驱逐选择。
但设置了 requests 或 limits 并不表示 Pod 不会被驱逐。节点故障、磁盘压力、系统进程消耗和集群级策略都可能导致 Pod 终止。
10.2 PodDisruptionBudget 的边界
PodDisruptionBudget 主要约束自愿中断,例如节点排空操作,不是节点资源压力下的绝对保护。以下情况仍可能突破它的保护能力:
- 节点硬件或操作系统故障;
- kubelet 资源驱逐;
- 强制删除;
- 进程自身崩溃;
- 集群控制面或网络故障。
因此 PDB 不能替代副本、持久化、跨节点分布和应用恢复能力。
十一、一个完整的状态推导示例
考虑下面的 Pod:
apiVersion: v1
kind: Pod
metadata:
name: lifecycle-demo
spec:
restartPolicy: OnFailure
initContainers:
- name: init
image: busybox:1.36
command: ["sh", "-c", "echo initialized"]
containers:
- name: app
image: busybox:1.36
command: ["sh", "-c", "echo running; exit 1"]
状态变化可以逐步推导:
第一步:对象创建
API Server 接受对象,但 Pod 尚未调度:
phase = Pending
PodScheduled = False 或尚未出现
第二步:完成调度和 Sandbox
kubelet 在目标节点创建 Sandbox 并配置网络:
PodScheduled = True
PodReadyToStartContainers = True(如果该条件由当前版本报告)
phase 仍可能是 Pending
此时不能仅凭 PodScheduled=True 认为应用已启动,因为 Init 容器尚未完成。
第三步:执行 Init 容器
Init 容器输出 initialized 并以退出码 0 结束:
Initialized = True
随后 kubelet 才会启动 app。
第四步:应用容器失败
app 输出 running 后退出码为 1。由于:
restartPolicy: OnFailure
kubelet 会重启它,而不是立即结束 Pod:
app.state = Waiting
app.restartCount = 1
phase 通常仍为 Running 或处于过渡状态
如果它反复失败,可能看到:
state.waiting.reason = CrashLoopBackOff
第五步:为什么不应只看 phase
此时 Pod 可能仍显示:
Running
但应用根本不能提供服务。必须结合:
kubectl get pod lifecycle-demo
kubectl describe pod lifecycle-demo
kubectl logs lifecycle-demo -c app --previous
才能确认:
- Init 是否完成;
- 容器退出码是什么;
- kubelet 是否正在退避;
- Pod 是否 Ready;
- 是否存在探针、镜像或 Sandbox 错误。
十二、控制器如何改变 Pod 的生命周期
Pod 是相对短生命周期的对象。生产应用通常由控制器创建,而不是直接创建裸 Pod:
| 控制器 | Pod 生命周期特点 |
|---|---|
| Deployment | 维护无状态 Pod 副本,并执行滚动更新 |
| ReplicaSet | 维护指定数量的 Pod |
| StatefulSet | 提供稳定序号、网络身份和有序管理 |
| DaemonSet | 通常在符合条件的节点上运行一个 Pod |
| Job | 运行直到任务成功或失败 |
| CronJob | 按时间表创建 Job |
容器重启通常不会改变 Pod UID:
同一个 Pod
└── 容器重启,restartCount 增加
控制器重新创建 Pod 则会产生新的 Pod UID:
旧 Pod 被删除
└── 新 Pod 被创建
├── 新 UID
├── 可能新的 Pod IP
└── 新的容器实例
这解释了为什么不能把 Pod IP 当作稳定身份。需要稳定访问地址时,应使用 Service;需要稳定有序身份时,应考虑 StatefulSet。
十三、常用诊断路径
13.1 Pod 长时间 Pending
kubectl describe pod <pod-name>
重点区分:
未调度
→ 查看 Events、nodeSelector、affinity、taint、资源请求
已调度但 Sandbox 失败
→ 查看 FailedCreatePodSandbox、CNI、运行时和节点日志
Init 未完成
→ 查看 initContainerStatuses 和 Init 容器日志
镜像问题
→ 查看 ImagePullBackOff、镜像仓库认证和节点网络
Pending 不是一个单一故障原因,只表示 Pod 尚未进入稳定运行状态。
13.2 Pod Running 但没有流量
依次检查:
kubectl get pod <pod-name> -o wide
kubectl get pod <pod-name> -o json | jq '.status.conditions'
kubectl describe pod <pod-name>
kubectl get endpointslice -l kubernetes.io/service-name=<service-name> -o yaml
常见因果链是:
容器运行
→ readinessProbe 失败
→ Pod Ready=False
→ EndpointSlice 不纳入该地址
→ Service 不发送新流量
也可能是 Service selector、端口名称、targetPort 或网络策略错误,不能只检查容器进程。
13.3 CrashLoopBackOff
kubectl logs <pod-name> -c <container-name> --previous
kubectl describe pod <pod-name>
kubectl get pod <pod-name> -o json \
| jq '.status.containerStatuses[] | {
name,
restartCount,
state,
lastState
}'
按顺序确认:
- 上一次进程的退出码;
reason是OOMKilled、Error还是探针杀死;- 是否使用了错误的命令或参数;
- 是否依赖了尚未就绪的服务;
- 是否因权限、挂载或配置错误启动失败;
- 是否是 livenessProbe 过早触发。
13.4 Pod 被驱逐
kubectl get pod <pod-name> -o yaml
kubectl describe node <node-name>
kubectl get events --field-selector involvedObject.name=<pod-name>
需要同时查看:
- 节点 MemoryPressure、DiskPressure、PIDPressure;
- Pod 的资源 requests 和 limits;
- 容器实际内存与临时存储使用;
- 节点上是否存在异常日志、镜像或临时文件增长;
- 控制器是否会创建替代 Pod。
只删除被驱逐的 Pod 不会解决节点压力;如果根因仍在,替代 Pod 可能继续被驱逐。
十四、容易混淆的边界
14.1 Pod 不是虚拟机
Pod 不提供完整的硬件虚拟化边界。容器通常共享节点内核,某些 Pod 配置甚至可以共享节点的 Network、PID 或 IPC Namespace。安全边界需要由容器运行时、Linux 安全机制、Pod Security、节点隔离和云平台策略共同构成。
14.2 Pod 内共享网络不等于共享端口配置
容器共享端口空间,所以端口必须由应用设计协调。containerPort 主要是声明性元数据,不会自动为进程打开端口,也不会解决两个容器的端口冲突。
14.3 Pod 内多个容器不等于微服务部署单元
多个容器适合强耦合、共同生命周期和低延迟协作的场景,例如:
应用 + 本地代理
应用 + 日志收集器
应用 + 配置或证书刷新器
如果两个组件需要独立扩缩容、独立发布或独立故障域,把它们放在同一个 Pod 会使这些能力变差,因为它们共享调度、网络故障域和终止边界。
14.4 Readiness 不会修复崩溃
Readiness 只描述能否接收流量;它不会让退出的进程重新运行。重启由容器退出、liveness 失败、运行时错误和 RestartPolicy 等路径触发。
14.5 Pod 删除和容器退出不是同一件事
容器可以在 Pod 仍存在时多次退出并重启。Pod 删除则是对整个 Pod 生命周期的操作,通常会终止其中所有容器并清理 Sandbox。
14.6 Sandbox 不是业务容器
Sandbox 的实现可能包含基础设施容器,但它不是应用容器,不应写入业务配置、依赖其名称或试图直接管理它。排查 Sandbox 问题应从 kubelet、CRI、CNI 和节点日志入手,而不是只看应用容器日志。
十五、模型总结:用三个层次理解 Pod
可以把 Pod 的运行状态分成三个层次:
第一层:API 对象
Pod Spec、Pod Status、Phase、Conditions
第二层:Pod 级运行环境
Sandbox、网络 Namespace、IPC、UTS、卷挂载关系
第三层:容器级生命周期
Waiting、Running、Terminated、探针、退出码、重启退避
它们的关系不是一一映射:
一个 Pod
├── 一个 API 对象
├── 通常一个 Sandbox
├── 一个 Pod IP
├── 多个容器状态
└── 一个粗粒度 Phase 与多个 Conditions
因此排查问题时应先问故障属于哪一层:
Pending且没有节点:调度层;FailedCreatePodSandbox:Pod 运行环境层;CrashLoopBackOff:容器生命周期层;Running但Ready=False:就绪和流量层;Evicted:节点资源和驱逐层;- 删除卡住:终止流程、Finalizer、节点连通性或运行时层。
Pod 的完整模型不是“一个 IP 加几个容器”,而是:
Pod
= 调度单元
+ 共享的部分 Linux 运行环境
+ 一个由 CRI 管理的 Sandbox
+ 多种不同语义的容器
+ kubelet 驱动的容器级生命周期
+ API Server 中的状态观测
+ 控制器对 Pod 实例的持续调谐
掌握这些边界后,才能正确解释 Pod 内的 localhost、Init 顺序、Sidecar 终止、Ephemeral Container 调试、容器重启、Pod Phase、优雅终止和节点驱逐,而不会把某一层的现象误认为另一层的机制。
系列导航与关联阅读
- 系列入口:Kubernetes 完整学习路线:从 Pod 与控制面到安全、运维和 Operator
- 上一篇:Kubernetes CRI、CNI 与 CSI:运行时、网络、存储插件责任边界
- 下一篇:Pod 生命周期:Phase、Condition、RestartPolicy、终止和 Eviction
- 延伸:Init、Sidecar 与 Ephemeral Container:启动顺序、共享和调试边界
官方资料
本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。

评论
0 条讨论