跳转至

端到端流程:部署并访问 vLLM

一次成功的 vLLM 部署会留下连续的证据:API Server 接收并持久化对象,Controller 创建 ReplicaSet 和 Pod,Scheduler 绑定 GPU 节点,Kubelet 准备卷、网络和设备,容器运行时启动 vLLM,EndpointSlice Controller 发布 Ready Pod 地址,应用最终返回模型列表和推理结果。

排障时从这条证据链的起点向后检查。第一个缺失的对象、状态或响应,对应当前应当查看的组件。

提交对象

kubectl apply -k 渲染并提交 StorageClass、PVC、Deployment 和 Service:

kubectl apply -k examples/chapter-01/12-vllm

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 用于发送真实推理请求。

kubectl port-forward -n default service/vllm-demo 18000:8000

另一个终端发送与验证脚本一致的 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 和日志用于解释该阶段的具体错误。

相关资料