这次不是写模型调参。
服务用 vLLM 跑,测试环境里接口能返回结果,但准备上 K8s 时问题开始变多:新节点拉镜像慢,Pod 偶尔 Pending,探针在模型加载完成前就开始失败,压测时 GPU 利用率也不稳定。
我一开始也想看 vLLM 参数。后来发现更应该先把运行环境排清楚:镜像能不能进来,GPU 资源是否声明准确,探针是否给冷启动留时间,日志能不能看出 GPU 到底有没有被有效使用。
下面是这次留下来的检查顺序。
先把关键镜像拆开
推理服务不止一个镜像。vLLM、CUDA 基础环境、监控组件、网关组件都可能来自不同来源。直接 apply YAML,失败时很容易只看到 ImagePullBackOff 或 ContainerCreating 卡住。
我先单独拉关键镜像:
bash
docker pull docker.1ms.run/vllm/vllm-openai:latest
docker pull docker.1ms.run/nvidia/cuda:12.4.1-runtime-ubuntu22.04
docker pull docker.1ms.run/prom/prometheus:latest
这一步只解决依赖进入环境的问题。它不是性能优化,也不是 GPU 调度方案。先把这一层排掉,后面看 Pending、探针和日志时会少很多干扰。
Deployment 里把 GPU 写明确
最小 Deployment 可以先这样写:
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: llm-api
namespace: ai-infer
spec:
replicas: 1
selector:
matchLabels:
app: llm-api
template:
metadata:
labels:
app: llm-api
spec:
nodeSelector:
accelerator: nvidia
containers:
- name: vllm
image: docker.1ms.run/vllm/vllm-openai:latest
args:
- "--model"
- "/models/qwen"
- "--host"
- "0.0.0.0"
- "--port"
- "8000"
ports:
- containerPort: 8000
resources:
limits:
nvidia.com/gpu: "1"
volumeMounts:
- name: model-dir
mountPath: /models
volumes:
- name: model-dir
persistentVolumeClaim:
claimName: qwen-model-pvc
这里的重点不是这份 YAML 能直接适配所有环境,而是几个边界要清楚:
- GPU 节点用标签筛出来。
- Pod 明确声明 GPU。
- 模型目录用 PVC 或固定挂载,不要混在镜像里。
- 镜像地址写在
image:,后续回滚和审计都看得见。
Kubernetes v1.36 继续推进 DRA 相关能力,方向也是让设备类资源更可声明、更可调度、更可观察。即使当前集群还没用 DRA,把 GPU 资源和事件先写清楚也很有必要。
Pending 先看调度,不要急着改模型
Pod 没起来时,我先看事件:
bash
kubectl -n ai-infer get pod -l app=llm-api
kubectl -n ai-infer describe pod -l app=llm-api
kubectl get node -L accelerator
kubectl describe node <gpu-node-name>
常见分支:
| 现象 | 优先看 |
|---|---|
Insufficient nvidia.com/gpu |
GPU 资源是否被占满 |
node(s) didn't match node selector |
节点标签是否正确 |
ImagePullBackOff |
镜像地址、网络、凭证 |
长时间 ContainerCreating |
PVC、模型目录、镜像体积 |
这一步不要被模型参数带偏。Pod 还没进入稳定运行前,先看调度和依赖。
探针要给模型冷启动留时间
vLLM 服务的冷启动通常比普通 API 慢。模型加载、显存分配、KV cache 准备都会占时间。
我会把探针拆开:
yaml
startupProbe:
httpGet:
path: /health
port: 8000
failureThreshold: 60
periodSeconds: 5
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 20
periodSeconds: 10
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 120
periodSeconds: 20
startupProbe 给模型加载时间;readinessProbe 决定什么时候接流量;livenessProbe 再决定是否重启。GPU 服务不要只配一个很激进的健康检查。
日志和 GPU 状态一起看
启动以后我会看四组信息:
bash
kubectl -n ai-infer logs deploy/llm-api --tail=120
kubectl -n ai-infer describe pod -l app=llm-api
kubectl top pod -n ai-infer
nvidia-smi
如果业务日志显示服务正常,但 nvidia-smi 里利用率很低,就要看请求是否排队、batch 是否合理、服务是否真正打到 GPU。
如果 kubectl top pod 看起来正常,但请求延迟很高,就要补应用侧指标:首 token 延迟、排队时间、生成 token 速度、错误率。
回滚要提前验证
我会在上线前跑一次回滚链路:
bash
kubectl -n ai-infer rollout status deploy/llm-api
kubectl -n ai-infer rollout history deploy/llm-api
kubectl -n ai-infer rollout undo deploy/llm-api
推理服务的回滚还要看模型目录。镜像回去了,模型权重没回去,也可能出现行为不一致。比较稳的做法是让镜像版本、模型版本和配置版本都能对应起来。
这次留下来的清单
- 镜像先单独拉,别把依赖问题留到 Pod 事件里。
- GPU 节点标签和资源声明要明确。
- Pending 先看调度和节点事件。
startupProbe给模型加载留时间。- readiness 和 liveness 分开看。
- 日志要能对上 GPU 利用率、显存和请求延迟。
- 回滚要同时覆盖镜像、模型目录和参数配置。
复盘
vLLM 上 K8s 以后,问题不只在模型参数。GPU 服务是一类更贵、更慢、更依赖资源边界的工作负载。
我现在会先问:依赖能不能进环境?GPU 有没有被正确声明?探针是不是太急?日志能不能解释 GPU 在做什么?回滚是不是演练过?
这些问题答清楚,再谈吞吐和成本优化会更踏实。