Kubernetes部署AI服务-从单机到集群
前一章介绍了用 Docker 打包 AI 服务。Docker 解决了"把应用和它的依赖打包在一起"的问题,但它解决不了更复杂的问题:流量高峰时如何自动增加实例?某个实例挂了如何自动恢复?多个服务之间如何发现对方?如何不停机更新模型?
Kubernetes 部署 AI 服务:从单机到集群
前一章介绍了用 Docker 打包 AI 服务。Docker 解决了"把应用和它的依赖打包在一起"的问题,但它解决不了更复杂的问题:流量高峰时如何自动增加实例?某个实例挂了如何自动恢复?多个服务之间如何发现对方?如何不停机更新模型?
Kubernetes(简称 K8s,因为 K 和 s 之间有 8 个字母)是专门解决这些问题的容器编排平台(自动管理大量容器的运行、扩缩容、故障恢复的系统)。对于 AI 服务来说,它尤其重要:LLM 推理流量在白天和夜晚可能相差 10 倍,GPU 资源需要精细调度,多个微服务(Embedding 服务、重排序服务、推理服务)需要统一管理。
1.1 为什么 AI 服务需要 Kubernetes
Kubernetes AI服务部署架构——Ingress到Service到Deployment到Pod,含HPA自动扩缩容
1.1.1 挑战一:流量波动大
一个 ToC(面向普通消费者,相对于 ToB 企业客户)的 AI 助手应用,白天 QPS(每秒请求数)可能是 500,凌晨只有 50。用单机部署,要么为峰值配置资源(夜间 90% 浪费),要么按平均配置(高峰期宕机)。Kubernetes 的 HPA(水平自动扩缩容,后文详解)可以根据实际负载自动增减实例数量。
1.1.2 挑战二:GPU 资源调度
GPU 是稀缺的昂贵资源,不同 Pod(运行单元,后文详解)不能同时抢占同一张 GPU。Kubernetes 通过资源声明机制,确保每个需要 GPU 的推理服务都能被调度到有 GPU 的节点上,且不会超额分配。
1.1.3 挑战三:多服务依赖管理
一个完整的 RAG 系统通常包含:FastAPI 主服务、Embedding 服务、向量数据库、Redis 缓存、Celery Worker。用 Docker Compose 只能管理单机,Kubernetes 可以把这些服务跨多台机器部署,并统一管理服务发现、负载均衡和配置。
1.2 Kubernetes 核心概念:对照 Java 开发者的世界
在看配置文件之前,先用 Java 开发者熟悉的概念做类比,建立直觉:
| K8s 概念 | Java 世界的类比 | 一句话描述 |
|---|---|---|
| Pod | 一个运行中的 Java 进程(JVM 实例) | 最小的可运行单元,包含一个或多个容器 |
| Deployment | Spring Boot 多实例部署脚本 | 声明"我要跑 N 个 Pod,并保持它们健康" |
| Service | Nginx 反向代理 + 负载均衡 | 给一组 Pod 提供统一的访问入口 |
| ConfigMap | application.yml |
存放非敏感配置(环境变量、配置文件) |
| Secret | 加密的 application.yml 或密钥库 |
存放敏感配置(API Key、数据库密码) |
| Namespace | Maven 的 groupId / Java 包名 | 逻辑隔离不同环境(dev、staging、prod) |
| Node | 一台物理服务器或虚拟机 | K8s 集群中的一台机器 |
1.3 AI 服务的 Kubernetes 特殊配置
1.3.1 GPU 资源请求
Kubernetes 通过 resources.limits 和 resources.requests 来声明资源需求。GPU 是扩展资源,需要提前安装 NVIDIA Device Plugin,然后用 nvidia.com/gpu 声明。
关键原则:GPU 不支持超额分配(Overcommit)。如果你声明 nvidia.com/gpu: 1,就必须有一张可用的 GPU,K8s 不会让多个 Pod 共享同一张 GPU(除非使用 MIG,Multi-Instance GPU,英伟达提供的 GPU 分割技术,可以将一张物理 GPU 划分为多个隔离的小 GPU 实例)。
1.3.2 内存限制
LLM 推理需要大量内存(一个 7B 参数的模型用 float16 精度需要约 14GB 内存)。必须根据实际内存需求设置 limits,否则在多租户集群中会被其他 Pod 挤压导致 OOM(内存溢出)崩溃。
1.3.3 健康检查探针调整
Kubernetes 有三种探针(Probe,健康检查机制):
- startupProbe(启动探针):等待应用启动完成。LLM 服务加载模型可能需要 60-120 秒,必须把超时设长,否则 K8s 会认为服务启动失败并无限重启。
- livenessProbe(存活探针):定期检查应用是否还活着,失败则重启。
- readinessProbe(就绪探针):检查应用是否准备好接受流量,失败则从负载均衡摘除。
1.4 完整的 AI 服务 K8s 部署示例
以下为代码示例(YAML 配置文件),非程序员可跳过代码,重点看注释说明。
1.4.1 Deployment:管理 Pod 副本
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: ai-service
namespace: prod
labels:
app: ai-service
spec:
replicas: 3 # 初始副本数:3个 Pod
selector:
matchLabels:
app: ai-service
strategy:
type: RollingUpdate # 滚动更新(不停机升级,后文详解)
rollingUpdate:
maxSurge: 1 # 升级期间最多多出1个 Pod
maxUnavailable: 0 # 升级期间最少有多少 Pod 不可用:0,保证不停机
template:
metadata:
labels:
app: ai-service
spec:
containers:
- name: ai-service
image: your-registry/ai-service:v2.1.0
ports:
- containerPort: 8000
# 资源声明:类比 JVM 的 -Xms -Xmx
resources:
requests:
memory: "4Gi" # 启动时最少需要 4GB 内存
cpu: "1000m" # 1核 CPU(1000m = 1核)
limits:
memory: "8Gi" # 最多使用 8GB 内存
cpu: "4000m" # 最多使用 4核 CPU
# 环境变量(从 ConfigMap 和 Secret 注入,不硬编码在镜像里)
env:
- name: OPENAI_API_KEY
valueFrom:
secretKeyRef:
name: api-keys # 对应 Secret 的名字
key: openai-key
- name: APP_ENV
valueFrom:
configMapKeyRef:
name: app-config # 对应 ConfigMap 的名字
key: app-env
# 健康检查配置(AI 服务必须调整,否则启动期间会被误杀)
startupProbe: # 启动探针:等待应用加载模型完成
httpGet:
path: /health
port: 8000
failureThreshold: 30 # 最多等 30 次
periodSeconds: 10 # 每10秒检查一次
# 意味着最多等 300 秒(5分钟)才判定启动失败
livenessProbe: # 存活探针:检查应用是否挂死
httpGet:
path: /health
port: 8000
initialDelaySeconds: 0
periodSeconds: 30 # 每30秒检查一次
timeoutSeconds: 10 # 超过10秒无响应算失败
readinessProbe: # 就绪探针:检查应用是否能接受流量
httpGet:
path: /ready
port: 8000
periodSeconds: 10
timeoutSeconds: 5
1.4.2 GPU 推理服务 Deployment
# deployment-gpu.yaml(需要 GPU 的推理服务)
apiVersion: apps/v1
kind: Deployment
metadata:
name: llm-inference
namespace: prod
spec:
replicas: 1 # GPU 资源有限,通常只跑1个副本
selector:
matchLabels:
app: llm-inference
template:
metadata:
labels:
app: llm-inference
spec:
# 节点亲和性:只调度到有 GPU 标签的节点
nodeSelector:
accelerator: nvidia-gpu
containers:
- name: llm-inference
image: your-registry/llm-inference:v1.0.0
resources:
limits:
nvidia.com/gpu: 1 # 声明需要1张 GPU(不支持小数)
memory: "40Gi" # LLM 需要大内存
cpu: "8000m"
requests:
nvidia.com/gpu: 1
memory: "32Gi"
cpu: "4000m"
1.4.3 Service:服务发现和负载均衡
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: ai-service
namespace: prod
spec:
selector:
app: ai-service # 匹配所有 label 为 app=ai-service 的 Pod
ports:
- port: 80 # Service 对外暴露的端口
targetPort: 8000 # 转发到 Pod 的 8000 端口
type: ClusterIP # 只在集群内可访问(通过 Ingress 对外暴露)
Service 创建后,集群内其他服务可以通过 http://ai-service.prod.svc.cluster.local 访问,无需知道具体的 Pod IP。这就是服务发现机制。
1.4.4 ConfigMap 和 Secret
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: prod
data:
app-env: "production"
llm-model: "gpt-4o"
max-tokens: "4096"
log-level: "info"
---
# secret.yaml
# 注意:Secret 的 value 需要 base64 编码,但 K8s 1.25+ 支持 stringData 直接写明文
apiVersion: v1
kind: Secret
metadata:
name: api-keys
namespace: prod
type: Opaque
stringData:
openai-key: "sk-your-actual-key-here" # 真实部署时从 Vault 或 CI/CD 注入
redis-password: "your-redis-password"
1.4.5 HPA:水平自动扩缩容
HPA(Horizontal Pod Autoscaler,水平 Pod 自动扩缩容,即根据负载自动增加或减少运行实例数量)根据 CPU/内存使用率或自定义指标,自动增减 Pod 数量。
# hpa.yaml
apiVersion: autoscaling/v2
kind: HPA
metadata:
name: ai-service-hpa
namespace: prod
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: ai-service # 对哪个 Deployment 生效
minReplicas: 2 # 最少保持 2 个 Pod(保证高可用)
maxReplicas: 20 # 最多扩到 20 个 Pod
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70 # CPU 超过 70% 就扩容
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80 # 内存超过 80% 就扩容
1.5 滚动更新与金丝雀发布
1.5.1 滚动更新:不停机升级
Deployment 默认的更新策略就是滚动更新(Rolling Update)。当你更新镜像版本时,K8s 会:
- 启动一个新版本的 Pod
- 等它通过 readinessProbe(就绪探针)
- 关掉一个旧版本的 Pod
- 重复直到全部替换完成
整个过程服务不中断,用户无感知。
# 触发滚动更新(只需更新镜像版本)
kubectl set image deployment/ai-service \
ai-service=your-registry/ai-service:v2.2.0 \
-n prod
# 查看更新进度
kubectl rollout status deployment/ai-service -n prod
# 如果新版本有问题,一键回滚到上个版本
kubectl rollout undo deployment/ai-service -n prod
1.5.2 金丝雀发布:灰度上线
金丝雀发布(Canary Release,灰度发布的一种,得名于煤矿工人用金丝雀测试矿井空气质量的做法)——先让少量流量(比如 5%)访问新版本,观察稳定后再全量切换。
在 K8s 中,最简单的金丝雀发布利用 Pod 数量比例来控制流量:
# 旧版本 Deployment:19 个 Pod(95% 流量)
# deployment-stable.yaml
metadata:
name: ai-service-stable
spec:
replicas: 19
selector:
matchLabels:
app: ai-service # 与 Service 的 selector 一致
version: v2.1.0
template:
metadata:
labels:
app: ai-service
version: v2.1.0
spec:
containers:
- image: your-registry/ai-service:v2.1.0
---
# 新版本 Deployment:1 个 Pod(5% 流量)
# deployment-canary.yaml
metadata:
name: ai-service-canary
spec:
replicas: 1
selector:
matchLabels:
app: ai-service # 与 Service 的 selector 一致
version: v2.2.0
template:
metadata:
labels:
app: ai-service
version: v2.2.0
spec:
containers:
- image: your-registry/ai-service:v2.2.0
Service 的 selector 只匹配 app: ai-service,所以流量会按 Pod 数量比例(19:1 = 95%:5%)自动分配到两个 Deployment。
1.6 Docker Compose vs Kubernetes 选型对比
| 维度 | Docker Compose | Kubernetes |
|---|---|---|
| 适用规模 | 单机,开发/测试环境 | 多机,生产环境 |
| 学习成本 | 低(1天入门) | 高(1-2周入门) |
| 自动扩缩容 | 不支持 | 原生支持(HPA) |
| 服务自愈 | 基础(容器崩溃重启) | 强(Pod 重调度、节点故障迁移) |
| 配置管理 | .env 文件 |
ConfigMap + Secret(加密,版本化) |
| 滚动更新 | 手动,有停机 | 原生支持,无停机 |
| GPU 调度 | 需手动指定 | 声明式自动调度 |
| 运维复杂度 | 低 | 高(需要 K8s 运维知识) |
| 建议场景 | 本地开发、CI 测试、小型内部工具 | 对外服务、高可用、弹性需求 |
决策原则:如果只有 1 台服务器,或者项目处于 MVP 阶段,用 Docker Compose 完全足够。当服务需要高可用、弹性扩缩容、或管理 5 个以上微服务时,迁移到 Kubernetes。
1.7 小结
Kubernetes 对 AI 服务的价值体现在三个核心能力:
- 弹性:HPA 根据负载自动扩缩容,应对 AI 应用的流量波动。
- 可靠性:探针机制确保不健康的实例被自动替换,滚动更新保证零停机上线。
- 资源隔离:GPU 声明和内存限制确保 LLM 推理服务不会被其他服务抢占资源。
对 Java 开发者来说,Kubernetes 的 YAML 配置与 Spring Boot 的 application.yml 类似,都是声明式配置——你声明"我要什么状态",框架负责实现并维持这个状态。这个心智模型一旦建立,K8s 的配置就不再陌生。