3、存储与网络基础:PVC/PV概念与配置、Service与Ingress暴露服务、ConfigMap与Secret管理配置
好,咱们进入第三章。这一章聊的是基础设施——存储和网络。你别看它听起来像“后勤部门”,但在vLLM推理环境里,这俩货直接决定了你的模型能不能跑起来、跑得快不快、别人能不能访问到。
我个人习惯把这一章的内容分成三块:数据怎么存、服务怎么暴露、配置怎么管。说白了,就是让Kubernetes知道你的模型文件放哪、怎么让客户端连上来、以及怎么把那些乱七八糟的环境变量和密钥管好。
核心逻辑图: 下面这张图帮你理清本章的知识脉络。
3.1 PVC与PV:你的模型文件住哪儿?
先聊聊存储。你想想看,vLLM加载一个大模型,比如Llama 3 70B,光参数文件就一百多GB。Pod一重启,数据全没了,那不得疯?所以我们需要持久化存储。
Kubernetes里,PV(PersistentVolume)是集群管理员准备的存储资源,好比一个“仓库”。PVC(PersistentVolumeClaim)是用户对存储的“申请单”,好比你说“我要一个100GB的仓库,读写速度要快”。
我在项目中遇到过一个问题:团队里有人直接把模型文件塞进Pod镜像里,结果镜像体积飙到200GB,每次拉取都像在下载一部4K电影。后来改用PVC挂载,香多了。
3.1.1 静态供给 vs 动态供给
静态供给就是管理员提前创建好PV,等着PVC来绑定。动态供给则是PVC一发出去,系统自动调用存储插件(比如NFS、Ceph、云厂商的EBS)创建PV。
我建议你在生产环境用动态供给。为什么呢?省事。你不需要提前算好要多大空间,PVC写多少,系统就给多少。
下面是一个PVC的YAML示例,用于vLLM挂载模型文件:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: model-storage-pvc
spec:
accessModes:
- ReadWriteMany # 多个Pod可以同时读
resources:
requests:
storage: 200Gi
storageClassName: nfs-client # 动态供给的存储类
小提示: 对于vLLM推理场景,ReadWriteMany模式很实用。你可以让多个推理Pod共享同一个模型文件目录,避免每个Pod都去复制一份模型。我见过有人用ReadWriteOnce,结果扩容时新Pod挂不上,因为旧的Pod还占着锁。
3.1.2 在vLLM Deployment中使用PVC
有了PVC,怎么用?很简单,在Deployment的volumes和volumeMounts里引用它:
apiVersion: apps/v1
kind: Deployment
metadata:
name: vllm-server
spec:
replicas: 2
template:
spec:
containers:
- name: vllm
image: vllm/vllm-openai:latest
volumeMounts:
- name: model-storage
mountPath: /models # 容器内的挂载点
volumes:
- name: model-storage
persistentVolumeClaim:
claimName: model-storage-pvc
这样,你的模型文件放在PVC对应的存储后端里,Pod重启、删除、迁移,数据都还在。
注意: 我曾经踩过一个坑——PVC的容量写小了,模型文件只拷了一半进去,vLLM启动时报错“模型加载失败”。后来我学乖了,先算好模型文件大小,再留20%的余量。比如模型150GB,PVC就申请200GB。
3.2 Service与Ingress:让别人找到你的推理服务
存储搞定了,模型文件安安稳稳地躺在PVC里。接下来要解决的是:客户端怎么访问你的vLLM推理接口?
Kubernetes里,Service是内部负载均衡器,Ingress是外部流量入口。我打个比方:Service是公司内部的交换机,Ingress是大门外的保安亭,负责把外面的请求引到正确的内部交换机上。
3.2.1 Service类型选择
对于vLLM推理服务,我推荐两种Service类型:
| Service类型 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| ClusterIP | 集群内部调用(如微服务架构) | 稳定、安全 | 外部无法直接访问 |
| NodePort | 开发测试、小规模暴露 | 简单、无需额外组件 | 端口范围有限、安全性一般 |
| LoadBalancer | 云环境生产使用 | 自动分配公网IP | 成本较高 |
我个人习惯在开发环境用NodePort,生产环境用LoadBalancer或者Ingress。下面是一个NodePort的Service示例:
apiVersion: v1
kind: Service
metadata:
name: vllm-service
spec:
type: NodePort
selector:
app: vllm-server
ports:
- port: 8000 # Service端口
targetPort: 8000 # 容器端口
nodePort: 30080 # 节点端口(可选)
配置好后,你就可以通过任意节点IP:30080访问vLLM的API了。比如用curl测试:
curl http://192.168.1.100:30080/v1/completions \
-H "Content-Type: application/json" \
-d '{"model": "llama3-70b", "prompt": "Hello", "max_tokens": 50}'
3.2.2 Ingress:更优雅的外部访问
NodePort虽然简单,但有个问题:你暴露的端口是随机的(或者固定的30000-32767),不够优雅。而且如果集群有多个服务,每个服务一个端口,管理起来很头疼。
Ingress就是来解决这个问题的。它让你用域名+路径来路由流量,还能帮你做TLS终止。说白了,就是让你用https://llm.example.com这样的地址访问服务。
下面是一个Ingress配置示例:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: vllm-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: llm.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: vllm-service
port:
number: 8000
小提示: 如果你用的是云厂商的Kubernetes服务(比如EKS、AKS、GKE),它们通常有自家的Ingress Controller。我建议优先用云厂商的,因为集成度更高,比如自动绑定证书、健康检查等。自己搭Nginx Ingress Controller也行,但要多花点心思维护。
3.3 ConfigMap与Secret:配置和密码别写死在代码里
最后一块,配置管理。你想想看,vLLM有很多参数,比如模型路径、GPU数量、最大并发数、API密钥等等。这些如果硬编码在Deployment YAML里,每次改参数都要改YAML、重新部署,太累了。
ConfigMap和Secret就是用来解耦的。ConfigMap存非敏感配置,Secret存敏感信息(比如API密钥、数据库密码)。
3.3.1 ConfigMap:存你的模型参数
比如vLLM的启动参数,可以放在ConfigMap里:
apiVersion: v1
kind: ConfigMap
metadata:
name: vllm-config
data:
model-path: /models/llama3-70b
max-model-len: "4096"
gpu-memory-utilization: "0.9"
tensor-parallel-size: "4"
然后在Deployment里通过环境变量引用:
env:
- name: MODEL_PATH
valueFrom:
configMapKeyRef:
name: vllm-config
key: model-path
- name: MAX_MODEL_LEN
valueFrom:
configMapKeyRef:
name: vllm-config
key: max-model-len
这样,你想调整模型参数,只需要更新ConfigMap,然后重启Pod就行。不用改YAML文件,也不用重新构建镜像。
3.3.2 Secret:藏好你的API密钥
Secret和ConfigMap用法类似,但数据是Base64编码的(注意,不是加密,只是编码)。生产环境建议用外部密钥管理服务,比如Vault、AWS Secrets Manager。
下面是一个Secret示例,用于存储vLLM的API密钥:
apiVersion: v1
kind: Secret
metadata:
name: vllm-secret
type: Opaque
data:
api-key: bXktc2VjcmV0LWFwaS1rZXk= # 这是 "my-secret-api-key" 的Base64编码
在Deployment中引用:
env:
- name: API_KEY
valueFrom:
secretKeyRef:
name: vllm-secret
key: api-key
警告: 我曾经见过有人把Secret的YAML文件直接提交到Git仓库里。嗯,这很危险。Base64编码不是加密,随便一个在线工具就能解码。正确的做法是:用kubectl create secret命令创建,或者用Sealed Secrets、External Secrets这类工具来管理。
3.4 实战:把这三者串起来
好了,理论讲完了。咱们来个实战小例子,把PVC、Service、ConfigMap、Secret串起来,搭建一个完整的vLLM推理服务。
假设你的模型文件已经放在NFS存储上,PVC已经创建好。现在你要做的是:
- 用ConfigMap管理vLLM的启动参数
- 用Secret管理API密钥
- 创建Deployment,挂载PVC和配置
- 创建Service暴露服务
- (可选)创建Ingress提供域名访问
Deployment的完整示例:
apiVersion: apps/v1
kind: Deployment
metadata:
name: vllm-server
spec:
replicas: 2
selector:
matchLabels:
app: vllm-server
template:
metadata:
labels:
app: vllm-server
spec:
containers:
- name: vllm
image: vllm/vllm-openai:latest
args: ["--model", "$(MODEL_PATH)", "--max-model-len", "$(MAX_MODEL_LEN)", "--gpu-memory-utilization", "$(GPU_MEMORY_UTILIZATION)", "--tensor-parallel-size", "$(TENSOR_PARALLEL_SIZE)"]
env:
- name: MODEL_PATH
valueFrom:
configMapKeyRef:
name: vllm-config
key: model-path
- name: MAX_MODEL_LEN
valueFrom:
configMapKeyRef:
name: vllm-config
key: max-model-len
- name: GPU_MEMORY_UTILIZATION
valueFrom:
configMapKeyRef:
name: vllm-config
key: gpu-memory-utilization
- name: TENSOR_PARALLEL_SIZE
valueFrom:
configMapKeyRef:
name: vllm-config
key: tensor-parallel-size
- name: API_KEY
valueFrom:
secretKeyRef:
name: vllm-secret
key: api-key
volumeMounts:
- name: model-storage
mountPath: /models
resources:
limits:
nvidia.com/gpu: 4
volumes:
- name: model-storage
persistentVolumeClaim:
claimName: model-storage-pvc
看到没?所有配置都从ConfigMap和Secret里拉,模型文件从PVC挂载。这样你的Deployment YAML就变得很干净,改配置不用动YAML,改模型文件不用动Pod。
总结一下: 存储(PVC/PV)解决数据持久化问题,网络(Service/Ingress)解决服务可达性问题,配置(ConfigMap/Secret)解决管理灵活性问题。这三者配合好了,你的vLLM推理环境才算真正“能打”。