4. K8s核心概念:Pod、Deployment、Service、ConfigMap、Secret、Namespace
好,咱们进入正题。Kubernetes 的概念很多,但真正跟 vLLM 部署强相关的,其实就这几个。我刚开始接触 K8s 的时候,也被一堆术语搞得头大。后来发现,你只要把 Pod、Deployment、Service、ConfigMap、Secret、Namespace 这六个东西搞明白,80% 的日常运维场景你都能应付。
说白了,这六个概念就是 K8s 世界的“基本零件”。咱们一个一个拆开看。
4.1 Pod:最小的部署单元
Pod 是 K8s 里你能创建和管理的最小计算单元。一个 Pod 可以包含一个或多个容器。但在 vLLM 的场景下,我强烈建议一个 Pod 只跑一个 vLLM 容器。为什么?
- 资源隔离更干净:每个 vLLM 实例独占 GPU 显存,互不干扰。
- 故障域最小:一个 Pod 挂了,只影响它自己,不会拖累其他推理任务。
- 扩缩容更灵活:你可以针对单个 Pod 做精细化的扩缩容策略。
核心要点:Pod 是“临时”的。它可能会因为节点故障、资源不足、OOM(内存溢出)等原因被 K8s 杀掉并重建。所以,永远不要依赖 Pod 的 IP 地址,那是会变的。
我在项目中遇到过一个问题:有个同事把 vLLM 的推理结果直接缓存在 Pod 的本地磁盘上。结果 Pod 一重启,缓存全丢了,用户反馈说模型回答变慢了。嗯,这就是典型的“把 Pod 当宠物养”的思维。Pod 是“牲口”,不是“宠物”。
4.2 Deployment:声明式的 Pod 管理器
Deployment 是干嘛的?它帮你管理 Pod 的期望状态。你告诉它:“我要跑 3 个 vLLM 实例。” Deployment 就会一直盯着,确保集群里始终有 3 个 Pod 在运行。
我个人习惯用 Deployment 来管理无状态的推理服务。比如 vLLM 这种,每个 Pod 都是等价的,谁处理请求都一样。
一个典型的 vLLM Deployment YAML 长这样:
apiVersion: apps/v1
kind: Deployment
metadata:
name: vllm-deployment
spec:
replicas: 3
selector:
matchLabels:
app: vllm
template:
metadata:
labels:
app: vllm
spec:
containers:
- name: vllm
image: vllm/vllm-openai:latest
args: ["--model", "meta-llama/Llama-2-7b-chat-hf"]
resources:
limits:
nvidia.com/gpu: 1
ports:
- containerPort: 8000
这里有几个关键点:
- replicas: 3:告诉 K8s,我要 3 个副本。
- resources.limits.nvidia.com/gpu: 1:每个 Pod 申请 1 块 GPU。这是 vLLM 部署的标配。
- args:vLLM 的启动参数,指定模型路径。
避坑指南:我曾经把 replicas 设成 5,但集群里只有 4 块 GPU。结果有 1 个 Pod 一直处于 Pending 状态。K8s 不会主动告诉你“资源不够”,它只会默默等着。所以,部署前先算好 GPU 总量。
4.3 Service:稳定的网络入口
Pod 的 IP 会变,那客户端怎么访问 vLLM 呢?Service 就是来解决这个问题的。它提供一个固定的虚拟 IP 和 DNS 名称,然后把请求转发给后端的 Pod。
对于 vLLM 推理服务,我们通常用 ClusterIP 类型的 Service。这样集群内部的其他服务(比如你的前端应用)可以通过服务名来访问 vLLM。
apiVersion: v1
kind: Service
metadata:
name: vllm-service
spec:
selector:
app: vllm
ports:
- protocol: TCP
port: 8000
targetPort: 8000
type: ClusterIP
你想想看,有了 Service,你的前端应用只需要记住 vllm-service:8000 这个地址就行了。不管后端 Pod 怎么重启、怎么漂移,客户端都不受影响。
重要:Service 的 selector 必须和 Deployment 的 matchLabels 保持一致。否则 Service 找不到后端 Pod,请求就会超时。我见过有人把 label 拼写错了,排查了半天才发现。
4.4 ConfigMap:配置与代码分离
vLLM 有很多配置参数,比如模型路径、最大输入长度、量化方式等。把这些参数硬编码在 YAML 里?不优雅。ConfigMap 就是用来存这些非敏感配置的。
举个例子,你可以把 vLLM 的启动参数抽出来:
apiVersion: v1
kind: ConfigMap
metadata:
name: vllm-config
data:
model-name: "meta-llama/Llama-2-7b-chat-hf"
max-model-len: "4096"
dtype: "auto"
然后在 Deployment 里通过环境变量引用:
env:
- name: MODEL_NAME
valueFrom:
configMapKeyRef:
name: vllm-config
key: model-name
这样做的好处是:改配置不需要重新构建镜像。你只需要更新 ConfigMap,然后重启 Pod 就行了。我在生产环境里经常用这招来快速切换模型版本。
4.5 Secret:敏感信息的保险箱
ConfigMap 存的是明文,那 API Key、数据库密码、Hugging Face Token 这些敏感信息怎么办?用 Secret。
Secret 和 ConfigMap 很像,但它的值会被 Base64 编码(注意,这只是编码,不是加密)。K8s 会尽量保证 Secret 的数据不被泄露到日志或环境变量里。
对于 vLLM,如果你需要从 Hugging Face 下载私有模型,就需要用到 Secret:
apiVersion: v1
kind: Secret
metadata:
name: hf-token
type: Opaque
data:
token: aGZfxxxxxxxxxxxxxxxxxxxxx # 你的 Base64 编码后的 Token
然后在 Pod 里挂载成环境变量:
env:
- name: HUGGING_FACE_HUB_TOKEN
valueFrom:
secretKeyRef:
name: hf-token
key: token
警告:千万不要把 Secret 的原始值提交到 Git 仓库里。我曾经见过有人把生产环境的数据库密码直接写在 YAML 文件里,然后 push 到了公开仓库。那场面,简直是灾难。建议用 sealed-secrets 或 external-secrets 这类工具来管理。
4.6 Namespace:虚拟集群的隔离墙
Namespace 是 K8s 用来逻辑隔离资源的机制。你可以把它理解成“虚拟集群”。不同的团队、不同的环境(开发、测试、生产)可以用不同的 Namespace 来隔开。
对于 vLLM 部署,我建议这样划分:
| Namespace | 用途 | 示例 |
|---|---|---|
vllm-dev |
开发测试环境 | 跑小模型,调试参数 |
vllm-prod |
生产环境 | 跑大模型,对外提供服务 |
vllm-monitoring |
监控和日志 | 部署 Prometheus、Grafana |
这样做的好处是:你在 vllm-dev 里随便折腾,不会影响到 vllm-prod 里的服务。而且,你可以给每个 Namespace 设置不同的资源配额(ResourceQuota),防止某个团队把集群资源吃光。
知识体系总览
下面这张图,是我自己总结的 vLLM 在 K8s 上的核心概念关系图。你看一眼,基本就明白这几个东西是怎么配合的了。
从这张图你可以看到:
- ConfigMap 和 Secret 把配置注入到 Deployment 里。
- Deployment 负责创建和管理 Pod。
- Pod 是真正跑 vLLM 模型的地方。
- Service 给 Pod 提供一个稳定的网络入口。
- 所有这些资源,都跑在同一个 Namespace 里,实现逻辑隔离。
嗯,这六个概念你掌握了,K8s 的大门就算正式打开了。后面咱们会基于这些概念,一步步把 vLLM 部署到生产环境里。
我的建议:别急着背 YAML 语法。先理解每个概念是干什么的,它们之间怎么配合。语法忘了可以查文档,但架构思维一旦建立,你就能举一反三。
公众号:蓝海资料掘金营,微信deep3321