4、第一个Pod:编写YAML文件部署vLLM Pod、Pod的生命周期管理、查看Pod日志与状态
好,咱们今天来点真格的。前面聊了那么多概念,什么容器、镜像、Pod,都是纸上谈兵。今天,我带你亲手把第一个vLLM推理服务跑起来。你想想看,一个能真正处理请求的Pod,从无到有,就在你眼前诞生——这种感觉,比看一百篇文档都来得实在。
我个人习惯,学K8s的第一步,就是先写一个Pod的YAML文件。别急着上什么Deployment、StatefulSet,先把最基础的Pod玩明白。Pod是K8s世界里最小的调度单元,说白了,它就是一组容器的集合。咱们的vLLM推理服务,就住在这个Pod里。
4.1 编写第一个Pod的YAML文件
YAML文件,就是K8s的“配置文件”。你告诉它你想跑什么镜像、开几个端口、需要多少资源,它就去帮你搞定。我刚开始接触YAML时,总觉得缩进很烦人,一个空格不对,整个Pod就起不来。嗯,这里要注意,YAML对缩进极其敏感,必须用空格,不能用Tab。
下面这个YAML,是我在生产环境中常用的一个简化版。它定义了一个名为 vllm-pod 的Pod,里面跑了一个vLLM容器。
apiVersion: v1
kind: Pod
metadata:
name: vllm-pod
labels:
app: vllm
tier: inference
spec:
containers:
- name: vllm-container
image: vllm/vllm-openai:latest
ports:
- containerPort: 8000
name: http
env:
- name: MODEL_NAME
value: "Qwen/Qwen2.5-7B-Instruct"
- name: MAX_MODEL_LEN
value: "4096"
resources:
requests:
memory: "8Gi"
cpu: "2"
limits:
memory: "16Gi"
cpu: "4"
command: ["python3", "-m", "vllm.entrypoints.openai.api_server"]
args: ["--model", "$(MODEL_NAME)", "--port", "8000", "--max-model-len", "$(MAX_MODEL_LEN)"]
restartPolicy: Always
我来拆解一下这个文件里的关键字段:
- apiVersion:K8s API的版本。对于Pod,v1是最稳定的版本,放心用。
- kind:资源类型。这里就是Pod。
- metadata:元数据。name是Pod的名字,labels是标签,后面做服务发现、筛选全靠它。
- spec.containers:核心部分。定义容器镜像、端口、环境变量、资源限制。
- resources:requests是“最低保障”,limits是“上限”。我在项目中遇到过,如果不设limits,某个Pod可能会把节点上的内存吃光,导致其他Pod被驱逐。所以,一定要设limits。
- command & args:覆盖镜像的默认启动命令。这里我直接调用了vLLM的OpenAI兼容API服务器。
- restartPolicy:Pod挂了怎么办?Always表示总是重启。对于推理服务,这很合理。
4.2 部署Pod:从YAML到运行
YAML写好了,怎么让它跑起来?很简单,用 kubectl apply 命令。这是K8s里最常用的命令之一,没有之一。
# 部署Pod
kubectl apply -f vllm-pod.yaml
# 查看Pod状态
kubectl get pods -o wide
# 查看更详细的信息
kubectl describe pod vllm-pod
执行 kubectl apply 后,K8s会做几件事:
- API Server校验YAML的合法性。
- Scheduler找一个合适的节点来运行这个Pod。
- Kubelet在节点上拉取镜像、启动容器。
整个过程,快则几秒,慢则几分钟(取决于镜像大小和网络速度)。我记得第一次部署vLLM时,镜像有十几个G,等了快十分钟。当时我还以为Pod卡住了,后来发现是镜像在后台慢慢拉。所以,建议你先用个小模型测试,比如Qwen2.5-0.5B,镜像小很多。
Pending 状态,别慌。用 kubectl describe pod 看看Events部分,通常会有提示。最常见的原因是资源不足(CPU/内存不够)或镜像拉取失败。
4.3 Pod的生命周期管理
Pod不是一成不变的。它有自己的一套生命周期。理解这个,你才能知道Pod现在在干嘛,以及接下来会干嘛。
Pod的生命周期,大致分为这几个阶段:
| 阶段 | 状态 | 说明 |
|---|---|---|
| Pending | 等待中 | Pod已被K8s接受,但容器还没启动。可能是在拉镜像,或等待调度。 |
| Running | 运行中 | 至少有一个容器正在运行,或者正在启动/重启。 |
| Succeeded | 成功 | Pod里的所有容器都正常退出,不会再重启。通常用于一次性任务。 |
| Failed | 失败 | 至少有一个容器以非0状态退出。 |
| Unknown | 未知 | K8s无法获取Pod的状态,通常是节点通信出了问题。 |
除了这些阶段,Pod内部还有更细粒度的容器状态:
- Waiting:容器正在等待,比如拉镜像、等待卷挂载。
- Running:容器正常运行。
- Terminated:容器已结束运行,可能是正常退出或异常退出。
为什么会这样设计?说白了,Pod是一个“容器组”,组里每个容器都有自己的状态。K8s把Pod的状态和容器的状态分开管理,这样更灵活。比如,一个Pod里有三个容器,其中一个挂了,Pod可能还是Running状态(因为其他容器还在跑),但那个挂了的容器会显示Terminated。
READY 列。比如 1/1 表示Pod里定义了一个容器,且这个容器已经就绪。如果是 0/1,说明容器还没准备好,服务不可用。
4.4 查看Pod日志与状态
Pod跑起来了,怎么知道它里面发生了什么?怎么排查问题?这时候,日志和状态信息就是你的眼睛。
4.4.1 查看Pod日志
用 kubectl logs 命令,可以实时查看Pod里容器的标准输出。
# 查看Pod日志
kubectl logs vllm-pod
# 如果Pod里有多个容器,需要指定容器名
kubectl logs vllm-pod -c vllm-container
# 实时跟踪日志(类似 tail -f)
kubectl logs -f vllm-pod
# 查看最近100行日志
kubectl logs --tail=100 vllm-pod
我曾经遇到过一个情况:vLLM Pod启动后,一直报 CUDA out of memory。我第一反应是资源限制设小了。但看了日志才发现,原来是模型加载时默认用了float32,显存占用翻倍。后来我在环境变量里加了 --dtype half,问题就解决了。你看,日志就是最好的老师。
4.4.2 查看Pod状态
除了日志,你还需要了解Pod的“体检报告”。kubectl describe pod 是最强大的诊断工具。
kubectl describe pod vllm-pod
这个命令会输出一大堆信息,我重点看这几个部分:
- Conditions:Pod的运行时条件,比如
PodScheduled(是否已调度)、Initialized(初始化容器是否完成)、Ready(Pod是否就绪)。 - Events:这是最关键的。Pod从创建到现在的所有事件,按时间倒序排列。如果Pod启动失败,这里会告诉你原因,比如
Failed to pull image、Back-off restarting failed container。 - Containers:每个容器的状态、镜像、资源限制、挂载点等。
kubectl describe pod,看看Events里有没有Warning。很多潜在问题,在Pod还没完全失败时,Events里就已经有提示了。别等到服务挂了才去看。
4.5 本章知识体系图
下面这张图,帮你把本章的核心逻辑串起来。从编写YAML,到部署Pod,再到监控生命周期和排查问题,每一步都环环相扣。
好了,第一个Pod的完整流程,咱们就走了一遍。从写YAML到部署,从看生命周期到查日志,每一步都是实战中必须掌握的。你想想看,以后不管遇到什么复杂的K8s问题,追根溯源,最后都会落到Pod上。把Pod玩明白,你就已经入门了。
嗯,我个人建议,你现在就去自己的集群上,亲手跑一遍这个vLLM Pod。哪怕只是跑起来再删掉,也比看十遍文档强。遇到问题别怕,用 describe 和 logs 这两个命令,大部分坑都能自己填上。