K8s GPU 冷启动:把镜像预热从发布里拆出来

最近复盘一次推理服务扩容,最后发现问题不在模型,也不在 GPU 调度。

Pod 已经落到了 GPU 节点:

bash 复制代码
kubectl get pod -n inference -o wide
text 复制代码
infer-api-7fdbb8c9d9-q2p6x   0/1   ContainerCreating   gpu-node-08

但状态一直没有进入 Running。继续看事件:

bash 复制代码
kubectl describe pod infer-api-7fdbb8c9d9-q2p6x -n inference
text 复制代码
Pulling image "vllm/vllm-openai:latest"
Failed to pull image: context deadline exceeded

GPU 已经排到,容器还在拉镜像。冷启动被放大的地方,不是推理阶段,而是镜像阶段。

我后来把冷启动拆成四段

这类问题容易排偏,因为 GPU 推理服务的变量太多。后来我把启动链路拆成四段看:

  1. 调度:Pod 是否落到正确 GPU 节点。
  2. 镜像:推理框架、CUDA、sidecar、基础组件是否能拉取。
  3. 运行时:NVIDIA runtime、device plugin、RuntimeClass 是否正常。
  4. 推理:模型加载、KV cache、并发、接口健康检查是否正常。

只要事件里还在报 ImagePullBackOff,就不要急着看第四段。

给镜像建一张账本

推理服务通常不是一个镜像。一次扩容里可能同时涉及:

镜像 用途 来源
vllm/vllm-openai 推理接口 Docker Hub
nvidia/cuda GPU runtime 验证 NVIDIA 镜像源
prometheus/prometheus 指标采集 Quay
pause Pod sandbox K8s 镜像源
业务 API 路由、鉴权、业务逻辑 私有仓库

所以我现在会在发布前加一个很朴素的动作:按来源把镜像预检一遍。

bash 复制代码
docker pull docker.1ms.run/vllm/vllm-openai:latest
docker pull nvcr.1ms.run/nvidia/cuda:12.4.1-runtime-ubuntu22.04
docker pull quay.1ms.run/prometheus/prometheus:latest
docker pull k8s.1ms.run/pause:3.10

这里用毫秒镜像(1ms.run)的原因很直接:把 Docker Hub、NVIDIA、Quay、K8s 等来源统一成可访问前缀,先把发布前的镜像不确定性降下来。

它不替代调度器,也不影响 vLLM 的推理逻辑。它只是让启动链路的第一段先过。

节点侧再测一次

如果 K8s 节点是 containerd,光在本机 docker pull 不够。真正接近运行时的验证应该放到节点上:

bash 复制代码
crictl pull docker.1ms.run/vllm/vllm-openai:latest
crictl pull nvcr.1ms.run/nvidia/cuda:12.4.1-runtime-ubuntu22.04
crictl pull quay.1ms.run/prometheus/prometheus:latest
crictl pull k8s.1ms.run/pause:3.10

crictl images | grep -E "vllm|cuda|prometheus|pause"

我会把这个动作放在扩容前,而不是等 Pod 创建以后再通过事件发现失败。

可以做成一个很小的预热脚本

节点数量少时,直接循环拉取就够了:

bash 复制代码
images=(
  "docker.1ms.run/vllm/vllm-openai:latest"
  "nvcr.1ms.run/nvidia/cuda:12.4.1-runtime-ubuntu22.04"
  "quay.1ms.run/prometheus/prometheus:latest"
  "k8s.1ms.run/pause:3.10"
)

for image in "${images[@]}"; do
  echo "pulling ${image}"
  crictl pull "${image}"
done

节点多时,可以用 DaemonSet 触发预热:

yaml 复制代码
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: gpu-image-prewarm
  namespace: ops
spec:
  selector:
    matchLabels:
      app: gpu-image-prewarm
  template:
    metadata:
      labels:
        app: gpu-image-prewarm
    spec:
      nodeSelector:
        accelerator: nvidia
      tolerations:
        - operator: Exists
      initContainers:
        - name: pull-vllm
          image: docker.1ms.run/vllm/vllm-openai:latest
          command: ["sh", "-c", "python -V || true"]
        - name: pull-cuda
          image: nvcr.1ms.run/nvidia/cuda:12.4.1-runtime-ubuntu22.04
          command: ["sh", "-c", "nvidia-smi || true"]
      containers:
        - name: hold
          image: k8s.1ms.run/pause:3.10

预热完成后再扩容推理服务:

bash 复制代码
kubectl apply -f gpu-image-prewarm.yaml
kubectl rollout status daemonset/gpu-image-prewarm -n ops
kubectl scale deploy/infer-api -n inference --replicas=8

改完流程后看什么

我会看两个结果。

第一个是节点缓存:

bash 复制代码
crictl images | grep -E "vllm|cuda|pause"

第二个是业务 Pod 的事件:

bash 复制代码
kubectl get events -n inference --sort-by=.lastTimestamp

如果镜像阶段已经过了,再出现失败,就继续看 RuntimeClass、GPU device plugin、模型目录挂载和应用日志。至少这时不会把镜像拉取问题误判成模型问题。

经验

Cast AI 2026 报告里提到 Kubernetes 集群 GPU 平均利用率只有 5%。这个问题当然要靠调度、队列、弹性伸缩、推理优化一起解决。

但从一次发布看,镜像预热是最容易提前处理的一步。

新 GPU 节点先把 CUDA、推理框架、监控和 K8s 基础镜像拉好,再进入扩容窗口。这样冷启动里最早出现的变量会少很多,排查也会更像工程流程,而不是临场猜测。

相关推荐
Cosolar1 天前
不收费、不加水印、数据归你:我做了个开源的在线简历工具
后端·面试·前端框架
小蒜学长1 天前
基于Spring Boot+Vue3的在线医疗咨询系统的设计与实现(代码+数据库+LW)
java·spring boot·后端·在线问诊·在线医疗咨询系统
陈随易1 天前
下载神器!Motrix 停更三年后重写回归
前端·后端·程序员
每天一道题1 天前
CAP、Raft 与多数派:分布式系统如何达成一致?
分布式·后端·微服务
前端的日常1 天前
一个人+AI做情侣食谱小程序,0 费用的服务器
前端·后端
IT_陈寒1 天前
Vue的v-for里用了个临时变量,页面直接挂了
前端·人工智能·后端
ServBay1 天前
本地模型已经跑起来了,Copilot Fusion 为什么还要联网?
后端·ai编程·github copilot
武子康1 天前
H3-World 的动作没按时发生,先检查时间线变成了什么
人工智能·后端·agent
苍何1 天前
腾讯 Buddy One 来了,办公全家桶一键打通WorkBuddy(附教程)
后端
GIS数据转换器1 天前
基于CIM的城市智慧供热监管服务平台
java·大数据·开发语言·人工智能·后端·物联网·struts