Pod 生命周期:启动、就绪、重启与终止¶
Kubernetes 分别用 Pod phase、Pod Conditions 和 Container state 描述 Pod 的生命周期。kubectl get pods 的 STATUS 列是客户端生成的摘要;判断 vLLM 是否已经可用,还要检查应用接口。
default/vllm-demo 没有配置 Startup、Readiness 或 Liveness Probe。vLLM 进程启动后,Kubelet 很快会把容器和 Pod 标记为 Ready,EndpointSlice 也会发布对应的 ready Endpoint;此时模型可能仍在下载、加载权重或初始化 CUDA。/v1/models 返回 Qwen/Qwen3-0.6B 才能证明模型已经由 vLLM 发布。
Pod 状态模型¶
PodStatus 中的三组字段记录不同粒度的状态。
| 状态 | API 字段 | 典型值 | 含义 |
|---|---|---|---|
| Pod phase | status.phase |
Pending、Running、Succeeded、Failed、Unknown | Pod 所处的粗粒度生命周期 |
| Container state | status.containerStatuses[].state |
Waiting、Running、Terminated | 每个容器当前的运行状态 |
| Pod Conditions | status.conditions[] |
PodScheduled、Initialized、ContainersReady、Ready | 调度、初始化和就绪等条件是否满足 |
Pod phase 为 Running,表示 Pod 已经绑定节点,所有容器已经创建,并且至少一个容器正在运行、启动或重启。应用就绪属于另一层状态,需要由探针或应用请求确认。
当前单容器清单没有 Readiness Probe。容器进入 Running 后,ContainersReady 和 Ready 通常很快变成 True,而 vLLM 的 HTTP Server 可能仍在准备。下图展示了 Kubernetes 状态与应用状态之间的时间差。
stateDiagram-v2
[*] --> Pending: 等待 GPU、PVC 与调度
Pending --> ContainerCreating: 准备卷、Sandbox、网络和设备
ContainerCreating --> RunningLoading: vLLM 进程启动,Pod 很快 Ready
RunningLoading --> Serving: /v1/models 返回目标模型
RunningLoading --> Restarting: 进程退出
Serving --> Restarting: 进程退出
Restarting --> RunningLoading: Kubelet 重启同一容器
RunningLoading --> Terminating: Pod 被删除
Serving --> Terminating: Pod 被删除
Terminating --> [*]: 进程退出,节点资源清理
图中只有 Pending 是 Pod phase。ContainerCreating 和 Terminating 是 kubectl STATUS 列中的显示值;RunningLoading、Serving 和 Restarting 是根据应用行为划分的观察状态。API 记录 status.phase、containerStatuses 和 conditions,HTTP 请求用于确认 Serving。
应用就绪验证¶
EndpointSlice 的 conditions.ready=true 来自 Pod Ready Condition。对当前单容器 Pod,这个值反映默认的容器就绪判断,不包含 /v1/models 响应或真实推理结果。
setup-course.sh 在 vLLM 容器内请求本机端口,用这一步单独检查应用启动。下面的命令执行同一项检查:
pod_name="$(kubectl get pod -n default \
-l app=vllm-demo \
-o jsonpath='{.items[0].metadata.name}')"
kubectl exec -n default "$pod_name" -- python3 -c '
import json
import urllib.request
with urllib.request.urlopen("http://127.0.0.1:8000/v1/models", timeout=10) as response:
models = json.load(response)
assert any(model["id"] == "Qwen/Qwen3-0.6B" for model in models["data"])
print("Qwen/Qwen3-0.6B is ready")
'
请求失败时,查看 vLLM 日志中的模型下载、CUDA 初始化和 HTTP Server 启动记录。trace-vllm-deployment.sh 通过 vllm.default.svc.cluster.local:8000 执行同样的模型检查,将 Service DNS 和 ClusterIP 纳入验证。网络路径见 网络:通信与流量转发。
生产环境探针¶
vllm-demo 当前清单没有健康探针。生产部署可以在 containers[] 中加入下面的配置,让 Service Endpoint 跟随应用状态,并在进程失去响应时重启容器。
startupProbe:
httpGet:
path: /health
port: 8000
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 180
readinessProbe:
httpGet:
path: /v1/models
port: 8000
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
livenessProbe:
httpGet:
path: /health
port: 8000
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
Startup Probe 成功以前,Kubelet 暂停 Readiness 和 Liveness Probe。这个窗口应覆盖模型下载、权重加载和 CUDA 初始化,避免 Liveness Probe 把正常的慢启动当作故障。
Readiness Probe 失败会把 Pod Ready Condition 改为 False,EndpointSlice Controller 随后更新 Endpoint 的 ready 状态,容器继续运行。Liveness Probe 连续失败达到阈值后,Kubelet 终止并重启容器。
Kubelet 的 HTTP Probe 将 200 到 399 的响应状态码视为成功,不检查 /v1/models 的响应体是否包含指定模型。即使增加 Readiness Probe,验证脚本仍应检查模型 ID,并发送一次真实的推理请求。
容器重启与 Pod 替换¶
Deployment 创建的 Pod 使用 restartPolicy: Always。vLLM 进程正常退出、返回非零退出码或被 Linux OOM Killer 终止后,Kubelet 都会在同一个 Pod 中重启容器。普通容器重启会保留 Pod UID、Pod IP、PVC 引用和节点,restartCount 增加,上一进程的结束原因进入 lastState。
容器反复失败时,Kubelet 会逐步延长下一次重启前的等待时间。CrashLoopBackOff 出现在部分 kubectl 命令的 STATUS 列中,表示容器正在经历失败和退避循环;对应的 Pod phase 通常仍是 Running。
ReplicaSet Controller 创建替代 Pod 时,对象身份会改变。新 Pod 获得新的 UID,Scheduler 重新选择节点,Kubelet 重新准备 Sandbox、网络、PVC 和 GPU;vllm-model-cache PVC 保持不变,因此模型缓存可以继续使用。
| 变化 | 执行组件 | UID / Pod IP | GPU 路径 |
|---|---|---|---|
| vLLM 进程退出 | Kubelet | 不变 | 在同一个 Pod 中重新创建容器 |
| 手工删除 Pod | ReplicaSet Controller | 新 UID,通常是新 IP | Scheduler 与 Kubelet 重新分配 |
| 修改 Deployment template | Deployment Controller | 默认 RollingUpdate 创建新 ReplicaSet 和 Pod | 新旧 Pod 在更新期间可能同时申请 GPU |
setup-course.sh 或 trace-vllm-deployment.sh 重建 Deployment |
脚本、API Server、Controller | Deployment、ReplicaSet 和 Pod 都获得新 UID | 旧对象删除后重新走调度与设备分配 |
单卡环境的 RollingUpdate¶
vllm-demo 省略了 spec.strategy,API Server 将更新策略默认化为 RollingUpdate。单副本 Deployment 的默认 maxSurge: 25% 向上取整为 1,maxUnavailable: 25% 向下取整为 0,因此 Controller 可以先创建一个新 Pod,并保留旧 Pod,直到新 Pod Available。
k8s-ai-infra-worker 的 allocatable 中只有一个 nvidia.com/gpu,对应一张 A100。旧 vLLM Pod 已经请求 nvidia.com/gpu: 1 时,新 Pod 会因 Insufficient nvidia.com/gpu 保持 Pending;旧 Pod 又要等待新 Pod 可用,更新无法继续。直接修改镜像、启动参数或 Pod template 都可能触发这条路径。
setup-course.sh 和 trace-vllm-deployment.sh 都先执行前台级联删除并等待完成。追踪脚本随后重新提交 Deployment:
kubectl delete deployment vllm-demo \
--namespace default \
--cascade=foreground \
--wait=true
kubectl apply -f examples/chapter-01/12-vllm/02-deployment.yaml
前台级联删除会等待旧 ReplicaSet 和 Pod 对象消失,再提交新的 Deployment。节点上的 CNI、CSI 和 Device Manager 按各自的控制循环清理资源;新 Pod 创建后,GPU 或卷尚未释放时仍可能短暂等待。这种操作会产生服务空窗,同时避免创建一个等待第二张 GPU 的 surge Pod。
生产服务需要按容量和可用性目标选择更新方式。可用 GPU 足够时可以使用 RollingUpdate;只有一张 GPU 时,可以设置 Recreate,也可以把 RollingUpdate 配置为 maxSurge: 0、maxUnavailable: 1。当前清单依靠脚本先删除再创建 Deployment,没有配置这些策略字段。
当前资源边界¶
当前容器只声明一个 GPU limit:
Kubernetes 允许 GPU 只写在 limits 中,并把同名 request 取为相同值。Scheduler 因此按一张 GPU 核算资源,Kubelet Device Manager 再分配具体设备。
清单没有声明 CPU 和内存 request 或 limit,对应的 request 为 0,也没有由 PodSpec 设置的 CPU quota 或内存上限。Kubernetes 根据 CPU 和内存的 request/limit 计算 QoS,因此这个 Pod 属于 BestEffort。节点资源紧张时,它比 Burstable 和 Guaranteed Pod 更容易被驱逐。
默认终止宽限期¶
当前 PodSpec 使用默认的 terminationGracePeriodSeconds: 30,也没有配置 preStop hook。删除 Deployment 或 Pod 时,API Server 写入 deletionTimestamp,EndpointSlice Controller、Kubelet、containerd、CSI、CNI 和 Device Manager 随后分别处理自己的状态。
sequenceDiagram
autonumber
participant A as API Server
participant E as EndpointSlice Controller
participant K as Kubelet
participant R as containerd
participant V as vLLM
participant C as CNI
participant P as Volume / Device Manager
A->>A: 写 deletionTimestamp 与 30s grace
par Endpoint 更新
A-->>E: Watch 到 Pod 进入终止
E->>A: 更新 EndpointSlice 状态
and 节点终止
A-->>K: Watch 到 Pod 删除
K->>R: StopContainer
R->>V: 发送终止信号
alt 30 秒内退出
V-->>R: 进程结束
else 宽限期耗尽
R->>V: 强制结束进程
end
K->>R: StopPodSandbox
R->>C: DEL Pod 网络
K->>P: 清理卷和 GPU 分配
K->>A: 更新 Pod 终止状态
end
Note over E,K,P: Endpoint、进程与节点资源分别收敛
EndpointSlice 更新、应用退出和节点资源清理由不同组件分别推进,时间戳可能交错。Pod 进入终止状态后,新的正常 Service 流量停止选择该 Endpoint;已有 TCP 连接和正在生成的请求仍由客户端、转发层和 vLLM 的终止行为共同决定。
30 秒宽限期耗尽后,容器运行时会强制结束仍在运行的进程。需要排空长生成请求的服务应结合 vLLM 的关闭行为配置更长的 grace period、preStop hook 或专用 draining 机制,并采集删除阶段的应用与 containerd 日志验证实际耗时。
状态排障¶
| 现场 | 判断 |
|---|---|
Pending,PodScheduled=False |
Scheduler 仍在处理 GPU、PVC 或其他节点约束 |
| 已调度,Container state 为 Waiting | Kubelet 正在准备 Sandbox、卷、镜像或设备 |
Running 且 Ready=True,/v1/models 仍失败 |
Kubernetes 已启动容器,vLLM 仍在加载模型或初始化 HTTP Server |
| Endpoint 为 ready,Service 请求仍失败 | 当前清单没有 Readiness Probe,检查应用日志、监听端口和 Service 数据面 |
restartCount 增加,UID 不变 |
Kubelet 在同一 Pod 内重启容器 |
| 新 UID 出现 | Controller 创建了替代 Pod,或脚本重建了 Deployment |
lastState.reason=OOMKilled |
Linux 主机内存路径终止了进程,需要结合节点内存与事件继续判断 |
deletionTimestamp 已设置 |
Pod 正在终止,等待进程与节点资源清理 |
CUDA out of memory 通常记录在 vLLM 日志中。它表示设备显存分配失败,与 lastState.reason=OOMKilled 所指的 Linux 内存路径不同。
状态检查¶
下列命令同时读取 Pod phase、Conditions、Container state 和 QoS:
pod_name="$(kubectl get pod -n default \
-l app=vllm-demo \
-o jsonpath='{.items[0].metadata.name}')"
kubectl get pod "$pod_name" -n default -o json | jq '{
uid: .metadata.uid,
phase: .status.phase,
qosClass: .status.qosClass,
conditions: .status.conditions,
containerState: .status.containerStatuses[0].state,
lastState: .status.containerStatuses[0].lastState,
restartCount: .status.containerStatuses[0].restartCount
}'
Event、EndpointSlice 与应用日志分别保留控制面、Service 和进程证据:
kubectl get event -n default \
--field-selector "involvedObject.name=$pod_name" \
--sort-by='.lastTimestamp'
kubectl get endpointslice -n default \
-l kubernetes.io/service-name=vllm \
-o json | jq '.items[].endpoints'
kubectl logs -n default "$pod_name" --tail=200
Deployment 对象会显示 API Server 默认补充的 RollingUpdate 策略:
kubectl get deployment vllm-demo -n default -o json | jq '{
strategy: .spec.strategy,
generation: .metadata.generation,
observedGeneration: .status.observedGeneration,
replicas: .status.replicas,
readyReplicas: .status.readyReplicas,
unavailableReplicas: .status.unavailableReplicas
}'
持续观察时,在两个终端分别运行 kubectl get pod -n default -l app=vllm-demo -w 和 kubectl logs -n default -l app=vllm-demo -f --tail=100。