3、存储与网络基础:PVC/PV概念与配置、Service与Ingress暴露服务、ConfigMap与Secret管理配置

好,咱们进入第三章。这一章聊的是基础设施——存储和网络。你别看它听起来像“后勤部门”,但在vLLM推理环境里,这俩货直接决定了你的模型能不能跑起来、跑得快不快、别人能不能访问到。

我个人习惯把这一章的内容分成三块:数据怎么存服务怎么暴露配置怎么管。说白了,就是让Kubernetes知道你的模型文件放哪、怎么让客户端连上来、以及怎么把那些乱七八糟的环境变量和密钥管好。

核心逻辑图: 下面这张图帮你理清本章的知识脉络。

vLLM 推理环境存储与网络基础 存储:PVC / PV 网络:Service / Ingress 配置:ConfigMap / Secret PVC/PV 配置要点 • 模型文件存储(大容量) • 访问模式:ReadWriteMany • 存储类(StorageClass)选择 • 动态/静态供给 Service/Ingress 配置要点 • ClusterIP / NodePort / LB • 端口映射与协议 • Ingress 路由规则 • TLS 终止 ConfigMap/Secret 配置要点 • 环境变量注入 • 配置文件挂载 • 敏感信息管理 • 更新与热加载 三者协同:存储提供数据 → 网络暴露服务 → 配置管理行为

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已经创建好。现在你要做的是:

  1. 用ConfigMap管理vLLM的启动参数
  2. 用Secret管理API密钥
  3. 创建Deployment,挂载PVC和配置
  4. 创建Service暴露服务
  5. (可选)创建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推理环境才算真正“能打”。


专注资料整理