2、Kubernetes核心概念速览:Pod、Deployment、Service、ConfigMap、Secret,为部署vLLM做准备
好,咱们进入正题。在把vLLM塞进K8s之前,有几个核心概念必须得先搞明白。说白了,K8s就是一套管理容器的“操作系统”,而Pod、Deployment这些,就是它里面的“进程”、“文件”和“网络配置”。
我个人习惯,学新东西先看骨架,再看血肉。所以这一章,咱们就把K8s里跟vLLM部署最相关的五个概念捋一遍。你想想看,vLLM跑起来需要GPU、需要模型文件、需要对外暴露API端口,这些需求,正好对应着K8s里的不同资源。
核心逻辑图:K8s资源如何支撑vLLM运行
2.1 Pod:vLLM的“家”
Pod是K8s里最小的调度单元。一个Pod可以包含一个或多个容器,这些容器共享网络和存储。对于vLLM来说,一个Pod里通常就一个主容器——跑vLLM推理进程的那个。
我记得刚开始接触K8s时,总有人问:“为啥不直接跑容器,非要套个Pod?”原因很简单:Pod提供了“共享上下文”。比如vLLM需要加载一个很大的模型文件,你可以把模型放在一个共享卷里,Pod里的所有容器都能访问。虽然咱们一般只用单容器Pod,但这个设计让扩展变得非常灵活。
💡 我的经验: 在Pod定义里,一定要显式声明GPU资源。否则K8s不会自动把GPU分配给vLLM。我曾经见过一个同事,Pod跑起来了,但vLLM一直报“CUDA error”,查了半天发现是没写 resources.limits.nvidia.com/gpu。
apiVersion: v1
kind: Pod
metadata:
name: vllm-pod-example
spec:
containers:
- name: vllm
image: vllm/vllm-openai:latest
resources:
limits:
nvidia.com/gpu: 1 # 必须声明!否则GPU不可用
ports:
- containerPort: 8000 # vLLM默认API端口
2.2 Deployment:让vLLM“高可用”
Pod是“一次性”的。节点挂了,Pod就没了。你总不希望用户正问着问题,突然服务中断吧?这时候就需要Deployment出场了。
Deployment负责管理Pod的“期望状态”。你告诉它“我要3个vLLM副本”,它会一直确保有3个Pod在运行。如果某个Pod挂了,它会自动再起一个。如果模型版本更新了,它还能做滚动更新,逐个替换Pod,保证服务不中断。
说白了,Deployment就是给Pod加了一层“保险”。我在生产环境里,从来不会直接创建Pod,都是用Deployment。哪怕只跑1个副本,也用它——因为万一Pod被误删了,Deployment能秒级恢复。
apiVersion: apps/v1
kind: Deployment
metadata:
name: vllm-deployment
spec:
replicas: 2 # 跑2个vLLM实例
selector:
matchLabels:
app: vllm
template:
metadata:
labels:
app: vllm
spec:
containers:
- name: vllm
image: vllm/vllm-openai:latest
resources:
limits:
nvidia.com/gpu: 1
⚠️ 注意: 多副本部署vLLM时,每个Pod都会加载一份完整的模型到显存里。如果你的GPU显存是80GB,模型需要70GB,那一个节点上只能跑1个副本。别想着“我节点有8张卡,就开8个副本”——得看模型大小和显存容量。
2.3 Service:给vLLM一个“固定入口”
Pod的IP是动态的。每次重启,IP都会变。客户端总不能每次都去查IP吧?Service就是解决这个问题的——它提供一个稳定的虚拟IP和DNS名称,把请求转发到后端的Pod上。
对于vLLM,我们通常创建一个ClusterIP类型的Service,让集群内部的其他服务(比如你的前端应用)通过这个Service访问vLLM的API。如果需要对外暴露,可以改成NodePort或LoadBalancer。
嗯,这里要注意:Service的selector要跟Deployment里的label匹配。我见过有人把label写错了,结果Service找不到后端Pod,请求全超时。排查起来还挺费劲的。
apiVersion: v1
kind: Service
metadata:
name: vllm-service
spec:
selector:
app: vllm # 必须匹配Deployment的label
ports:
- protocol: TCP
port: 8000 # Service端口
targetPort: 8000 # Pod内vLLM端口
type: ClusterIP # 集群内部访问
2.4 ConfigMap:管理vLLM配置
vLLM有很多启动参数,比如模型路径、最大序列长度、张量并行度等。把这些参数硬编码在Deployment YAML里?太不灵活了。ConfigMap就是用来存这些配置的。
你可以把ConfigMap挂载到Pod里,或者通过环境变量注入。我个人习惯用环境变量方式,因为vLLM的启动命令可以直接读取环境变量,改动配置时只需要更新ConfigMap,然后重启Pod就行,不用重新构建镜像。
apiVersion: v1
kind: ConfigMap
metadata:
name: vllm-config
data:
model_name: "meta-llama/Llama-2-7b-chat-hf"
max_model_len: "4096"
tensor_parallel_size: "1"
💡 避坑指南: ConfigMap的value默认都是字符串。如果你的参数是数字或布尔值,记得在启动脚本里做类型转换。我曾经因为 max_model_len 被当成字符串传给vLLM,结果它报了个类型错误,排查了半小时。
2.5 Secret:保护敏感信息
ConfigMap存的是非敏感配置。那HuggingFace的access token、API密钥这些敏感信息怎么办?用Secret。它跟ConfigMap用法类似,但数据会做base64编码,并且K8s会控制访问权限。
比如你要从HuggingFace下载一个需要授权的模型,就得提供token。把这个token放在Secret里,然后挂载到Pod中,vLLM启动时就能读取到。
apiVersion: v1
kind: Secret
metadata:
name: hf-secret
type: Opaque
data:
token: aGZfxxxxxxxxxxxxxxxxxxxx # base64编码后的token
⚠️ 安全提醒: base64不是加密,只是编码。任何有权限查看Secret的人都能解码。在生产环境,建议配合外部密钥管理服务(如Vault)使用。另外,不要把Secret提交到Git仓库里——我见过有人把生产环境的token直接写在YAML里传到了GitHub,后果嘛...你懂的。
小结
这五个概念,就是你在K8s上部署vLLM的“工具箱”:
- Pod:vLLM进程的载体,记得声明GPU资源
- Deployment:保证vLLM高可用,支持滚动更新
- Service:提供稳定的访问入口
- ConfigMap:管理模型路径、推理参数等配置
- Secret:保护HuggingFace token等敏感信息
把这些概念理清了,下一章咱们就可以动手写一个完整的vLLM部署YAML了。到时候你会发现,原来这些概念组合起来,就是一套生产级的推理服务。