端到端流程:部署并访问 vLLM¶
一次成功的 vLLM 部署会留下连续的证据:API Server 接收并持久化对象,Controller 创建 ReplicaSet 和 Pod,Scheduler 绑定 GPU 节点,Kubelet 准备卷、网络和设备,容器运行时启动 vLLM,EndpointSlice Controller 发布 Ready Pod 地址,应用最终返回模型列表和推理结果。
排障时从这条证据链的起点向后检查。第一个缺失的对象、状态或响应,对应当前应当查看的组件。
提交对象¶
kubectl apply -k 渲染并提交 StorageClass、PVC、Deployment 和 Service:
chapter01-hostpath StorageClass 为 default/vllm-model-cache PVC 提供 10 GiB 存储。default/vllm-demo Deployment 维持一个 Pod,Pod 请求一张 nvidia.com/gpu,使用 nvidia RuntimeClass,并挂载模型缓存。default/vllm Service 通过 app: vllm-demo selector 将 8000 端口指向当前 Pod。
下图展示了四个提交对象如何扩展为运行中的 vLLM 服务。
flowchart TD
M["kubectl apply -k\nStorageClass + PVC + Deployment + Service"]
A["API Server\n对象入口"]
T["etcd\n对象持久化"]
C["Deployment / ReplicaSet Controller\nReplicaSet → Pod"]
S["Scheduler\nGPU + VolumeBinding → worker"]
K["Kubelet\nCSI + CRI/CNI + Device Plugin"]
P["containerd + shim + runc\nvLLM 进程"]
V["Service/vllm\nselector: app=vllm-demo"]
EC["EndpointSlice Controller"]
E["EndpointSlice\nReady Pod 地址"]
D["kube-proxy nftables\nClusterIP → Pod IP"]
Q["Service 请求\n/v1/models"]
F["port-forward 请求\n/v1/chat/completions"]
M --> A
A -->|"写入"| T
A -->|"List / Watch"| C
C -->|"Pod 已创建"| S --> K --> P
A --> V
K -->|"写回 Pod Status"| A
A --> EC --> E
E --> D
Q --> V --> D --> P
F --> P
请求处理与持久化¶
kubectl 从 kubeconfig 取得 API Server 地址和身份凭据,再为每个资源发送 API 请求。API Server 处理认证、授权、准入、默认值和对象校验,通过存储层将对象写入 etcd,并返回带有 uid 与 resourceVersion 的对象。
Controller、Scheduler 和 Kubelet 通过 API Server 的 List/Watch 获得后续变化,并把处理结果写回 API。API 请求路径见 API Server:集群的统一入口,持久化过程见 etcd:Kubernetes 的状态存储。
下面的命令同时显示 Deployment 的持久化标识和 Controller 更新的状态:
kubectl get deployment vllm-demo -n default -o json | jq '{
uid: .metadata.uid,
resourceVersion: .metadata.resourceVersion,
generation: .metadata.generation,
observedGeneration: .status.observedGeneration,
replicas: .status.replicas,
availableReplicas: .status.availableReplicas
}'
uid 和 resourceVersion 来自对象创建结果。Deployment Controller 处理当前 generation 后更新 status.observedGeneration,副本状态再随 ReplicaSet 和 Pod 的变化推进。
Controller 对象链¶
Deployment Controller 根据 Pod template 和副本数创建 ReplicaSet,ReplicaSet Controller 再创建 Pod。每个对象都通过 API Server 写入,并通过 ownerReferences 记录上级对象:
Deployment/vllm-demo
└─ ReplicaSet/vllm-demo-<pod-template-hash>
└─ Pod/vllm-demo-<pod-template-hash>-<suffix>
Pod 重建时会获得新的名称和 UID,Calico CNI 为新 Pod 分配 IP。ReplicaSet 继续以 app: vllm-demo 识别副本。Deployment、ReplicaSet 与 Pod 的对象关系见 Controller:从 Deployment 到 Pod。
kubectl get deployment vllm-demo -n default -o wide
kubectl get replicaset,pod -n default -l app=vllm-demo -o wide
节点选择¶
新 Pod 同时请求一张 nvidia.com/gpu 和 vllm-model-cache PVC。Scheduler 的 Filter 插件检查 GPU 可分配量、Pod 约束和卷绑定条件,再从可行节点中选择 k8s-ai-infra-worker。
StorageClass 使用 WaitForFirstConsumer。VolumeBinding 为 PVC 写入 volume.kubernetes.io/selected-node,external-provisioner 按该节点创建后端卷和 PV;PVC 进入 Bound 后,Scheduler 通过 API Server 将 Pod 绑定到 worker。节点选择过程见 Scheduler:Pod 的节点选择,PVC 供给过程见 存储:PersistentVolume、CSI 与数据路径。
Pod 的 spec.nodeName 记录 Scheduler 的结果。具体 GPU 由 worker 上的 Kubelet Device Manager 选择。
节点执行¶
Kubelet Watch 到绑定给本节点的 Pod 后,由 Pod Worker 推动本地状态向 PodSpec 收敛。卷、Sandbox、镜像和 GPU 的准备动作可能交错执行;下图中的箭头表示依赖关系。
flowchart LR
K["Kubelet Pod Worker"]
V["Volume Manager\nCSI NodePublishVolume"]
R["CRI / containerd\nRunPodSandbox"]
N["Calico CNI\nPod IP"]
D["Device Manager\nDevice Plugin Allocate"]
C["CDI 设备编辑"]
O["containerd / shim / runc\nvLLM PID 1"]
K --> V --> O
K --> R --> N --> O
K --> D --> C --> O
Volume Manager 通过 CSI Node Plugin 将 vllm-model-cache 发布到 Pod 目录。Kubelet 通过 CRI 请求 containerd 创建 Pod Sandbox,containerd 调用 Calico CNI 配置 Pod IP。Device Manager 选择一张健康 GPU,调用 NVIDIA Device Plugin 的 Allocate,并将返回的 CDI 设备信息交给 containerd。containerd 准备 OCI 配置,shim 调用 runc 启动 vLLM 进程。
各节点组件的内部过程分别见 Kubelet:节点上的 Pod 管理、容器运行时:从 CRI 到容器进程、网络:通信与流量转发、存储:PersistentVolume、CSI 与数据路径和 GPU 设备:Device Plugin 与设备注入。
应用就绪¶
vLLM 进程从 /root/.cache/huggingface 读取或下载 Qwen/Qwen3-0.6B,随后加载权重、初始化 CUDA 和 KV Cache,并启动 OpenAI-compatible HTTP Server。
当前 Deployment 没有配置 Readiness Probe。容器入口进程运行后,Kubelet 会将容器标记为 Ready,EndpointSlice Controller 随后把 Pod IP 作为 Ready endpoint。verify-course.sh 继续轮询 /v1/models,直到响应中包含 Qwen/Qwen3-0.6B。
三类状态提供不同证据:phase=Running 表示 Pod 已绑定节点、所有容器已创建,并且至少一个容器正在运行、启动或重启;Ready=True 与 EndpointSlice conditions.ready=true 记录 Kubernetes 已将 Pod 接入 Service;/v1/models 的响应记录 vLLM 已经发布目标模型。
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 '{
node: .spec.nodeName,
podIP: .status.podIP,
phase: .status.phase,
conditions: .status.conditions,
container: .status.containerStatuses[0]
}'
kubectl get endpointslice -n default \
-l kubernetes.io/service-name=vllm \
-o yaml
Pod 的启动、就绪、重启和终止状态见 Pod 生命周期:启动、就绪、重启与终止。
Service 与推理请求¶
verify-course.sh 先在 vLLM Pod 内请求 vllm.default.svc.cluster.local:8000/v1/models。DNS 将 Service 名解析为 ClusterIP,kube-proxy 的 nftables 规则再把连接转到 Ready endpoint。客户端和服务端位于同一个 Pod,这次调用会经过同节点 hairpin 路径。
脚本随后运行 kubectl port-forward service/vllm-demo 18000:8000。kubectl 根据 Service 选择后端 Pod,再通过 API Server 和 Kubelet 建立到 Pod 端口的转发流。Service ClusterIP 数据面由前一次 /v1/models 请求验证,port-forward 用于发送真实推理请求。
另一个终端发送与验证脚本一致的 Chat Completions 请求:
curl --fail --silent --show-error \
http://127.0.0.1:18000/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{
"model": "Qwen/Qwen3-0.6B",
"messages": [
{"role": "user", "content": "Reply briefly: Kubernetes GPU is ready."}
],
"chat_template_kwargs": {"enable_thinking": false},
"temperature": 0,
"max_tokens": 32
}' | jq .
验证脚本要求响应的 object 为 chat.completion、choices 至少包含一项,并且 choices[0].message.content 包含非空文本。模型可以使用不同措辞回答请求,响应结构与非空内容构成稳定的自动检查项。
检查点¶
| 观察结果 | 已完成的阶段 | 检查位置 |
|---|---|---|
Deployment 有 uid 和 resourceVersion |
API 请求与 etcd 持久化 | Deployment Status 与 Controller 日志 |
| ReplicaSet 和 Pod 已创建 | Deployment / ReplicaSet Controller 调谐 | Pod ownerReferences 与 Controller 日志 |
Pod 没有 spec.nodeName |
Pod 对象已经创建 | Scheduler、GPU 可分配量、PVC 与 Pod 约束 |
PVC 有 selected-node,状态仍为 Pending |
Scheduler 已选择卷拓扑 | external-provisioner 与 CSI Controller 日志 |
Pod 已绑定,状态停在 ContainerCreating |
Scheduler 已完成 Pod binding | Kubelet、CSI Node、CRI/CNI、镜像与 Device Plugin |
容器 Running,/v1/models 请求失败 |
vLLM 进程已经启动 | 模型下载、CUDA 初始化与应用日志 |
Pod Ready,EndpointSlice 没有 Ready endpoint |
Kubelet 已写回就绪状态 | Service selector 与 EndpointSlice Controller |
集群内 /v1/models 请求成功 |
Service DNS、EndpointSlice 和 nftables 数据面可用 | port-forward 推理请求 |
| Chat Completions 返回有效响应 | vLLM 已执行一次真实推理 | 保存对象、组件日志和应用响应 |
从上到下检查这些状态,可以把故障定位到第一个尚未完成的阶段。对应组件的 Event 和日志用于解释该阶段的具体错误。