
去年我们团队接手了一个老旧的推荐系统——单体应用,Python写的,AI模型推理和业务逻辑揉在一起。每次模型更新要全量发布,流量突增时手动扩容,GPU资源利用率不到30%。
这是一个典型的"AI应用"而非"云原生AI应用"。两者的区别在于:
AI应用:把模型塞进一个服务里跑起来就行。 云原生AI应用:模型是独立的可扩展组件,通过声明式配置实现弹性、可观测、可演进。
于是我们开始了重构之旅,目标很明确:让AI能力像微服务一样被调度、被编排、被弹性伸缩。
传统做法是把模型和Web框架(Flask/FastAPI)打包在一起。云原生的做法是把模型推理封装成无状态的服务,通过标准HTTP/gRPC对外提供能力。
这是最核心的改造——模型推理服务化:
# inference_service.py
import torch
from transformers import AutoTokenizer, AutoModelForSequenceClassification
from fastapi import FastAPI, HTTPException
import asyncio
app = FastAPI()
# 模型加载(启动时加载一次,之后常驻内存)
model = AutoModelForSequenceClassification.from_pretrained("./bert-sentiment")
tokenizer = AutoTokenizer.from_pretrained("./bert-sentiment")
model.eval()
# 使用异步接口,不阻塞事件循环
@app.post("/v1/predict")
async def predict(payload: dict):
text = payload.get("text")
if not text:
raise HTTPException(status_code=400, detail="text required")
# 异步推理(如果模型支持异步)
inputs = tokenizer(text, return_tensors="pt", truncation=True, max_length=512)
with torch.no_grad():
outputs = model(**inputs)
probabilities = torch.softmax(outputs.logits, dim=1).tolist()[0]
return {"probabilities": probabilities, "text_length": len(text)}然后写一个标准的Dockerfile,让这个服务可被容器编排:
FROM pytorch/pytorch:2.0.1-cuda11.7-cudnn8-runtime
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE 8080
CMD ["uvicorn", "inference_service:app", "--host", "0.0.0.0", "--port", "8080"]这个Docker镜像只有80MB(基础镜像已含CUDA),启动时间<5秒——为弹性伸缩做准备。
把模型服务部署到Kubernetes后,真正的魔法开始了。
首先定义Deployment,关键是要声明资源限制,告诉调度器"我需要多少GPU":
apiVersion: apps/v1
kind: Deployment
metadata:
name: sentiment-analyzer
spec:
replicas: 2
selector:
matchLabels:
app: sentiment-analyzer
template:
metadata:
labels:
app: sentiment-analyzer
spec:
containers:
- name: inference
image: sentiment-analyzer:latest
ports:
- containerPort: 8080
resources:
limits:
nvidia.com/gpu: 1 # 每个Pod占用1张GPU
memory: "8Gi"
cpu: "4"
requests:
nvidia.com/gpu: 1
memory: "4Gi"
cpu: "2"然后配置HPA(Horizontal Pod Autoscaler),根据CPU使用率或自定义指标自动扩缩容:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: sentiment-analyzer-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: sentiment-analyzer
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Pods
pods:
metric:
name: requests_per_second # 自定义指标,基于Prometheus
target:
type: AverageValue
averageValue: "100" # 每个Pod超过100QPS就扩容HPA会监控指标,当QPS上升时自动增加Pod数量,流量下降时缩回。GPU资源不再是浪费的固定资产,而是按需分配的弹性资源。
当有多个AI模型版本同时运行(比如v1生产版、v2灰度版),我需要流量治理——让部分用户走新模型,验证效果再全量切换。
使用Istio实现灰度发布和流量镜像:
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: sentiment-routing
spec:
hosts:
- sentiment-analyzer
http:
- match:
- headers:
x-canary:
exact: "true" # 携带特定Header的请求走v2
route:
- destination:
host: sentiment-analyzer
subset: v2
weight: 100
- route:
- destination:
host: sentiment-analyzer
subset: v1
weight: 90
- destination:
host: sentiment-analyzer
subset: v2
weight: 10 # 10%的流量自动切到v2做灰度
mirror: # 同时复制一份流量给v2做阴影验证
host: sentiment-analyzer
subset: v2流量镜像(Mirroring)特别适合AI场景——把生产真实流量复制一份给新模型,但不影响线上结果,可以用来对比新旧模型的准确率差异。
复杂的AI任务往往不是单次推理,而是多步骤流水线:数据预处理->特征提取->模型推理->后处理->结果存储。
Kubeflow或Argo Workflows可以实现这种DAG(有向无环图)编排:
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: ai-pipeline-
spec:
entrypoint: ai-pipeline
templates:
- name: ai-pipeline
steps:
- - name: preprocess
template: preprocess
- - name: feature-extract
template: feature-extract
arguments:
parameters:
- name: input
value: "{{steps.preprocess.outputs.result}}"
- - name: model-inference
template: model-inference
dependencies: [feature-extract]
- - name: postprocess
template: postprocess
dependencies: [model-inference]
- name: preprocess
container:
image: preprocess:latest
command: [python, preprocess.py]
- name: model-inference
container:
image: inference:latest
command: [python, predict.py]
resources:
limits:
nvidia.com/gpu: 1每个步骤都是一个独立的容器,Kubernetes调度它们按顺序执行,失败可重试,步骤间可传递数据。这比单体式AI应用清晰得多。
AI应用比普通微服务多了三个可观测维度:
Prometheus + Grafana实现指标采集和可视化,在代码中埋点:
from prometheus_client import Counter, Histogram, Gauge
import time
# 定义指标
inference_count = Counter('inference_total', 'Total inferences', ['model_version', 'status'])
inference_latency = Histogram('inference_duration_seconds', 'Inference latency', ['model_version'])
confidence_gauge = Gauge('inference_confidence_avg', 'Average confidence', ['model_version'])
@app.post("/v1/predict")
async def predict(payload: dict):
start = time.time()
try:
result = do_inference(payload)
inference_count.labels(model_version='v1', status='success').inc()
latency = time.time() - start
inference_latency.labels(model_version='v1').observe(latency)
# 记录平均置信度,用于监控模型是否"发飘"
avg_conf = sum(result['probabilities']) / len(result['probabilities'])
confidence_gauge.labels(model_version='v1').set(avg_conf)
return result
except Exception as e:
inference_count.labels(model_version='v1', status='error').inc()
raise再配上分布式追踪(Jaeger),可以看清一个推理请求在预处理->推理->后处理每个环节的耗时,快速定位瓶颈。
云原生最大的优势是声明式部署——模型更新不再需要停机。
我们的CI/CD流程:
代码提交 -> 模型训练(Kubeflow) -> 模型验证(A/B测试) ->
构建镜像(Tekton) -> 推送到镜像仓库 ->
更新K8s Deployment(ArgoCD自动同步) ->
灰度发布(Istio流量切分) -> 全量上线ArgoCD监听Git仓库变化,一旦model-version:v2的镜像Tag更新,自动同步到生产集群:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: sentiment-analyzer
spec:
source:
repoURL: https://github.com/our-team/sentiment-k8s
path: overlays/production
targetRevision: main
destination:
server: https://kubernetes.default.svc
namespace: ai-production
syncPolicy:
automated:
prune: true
selfHeal: true整个更新过程用户无感知,K8s滚动更新保证零停机。
把以上六层整合起来,AI+云原生应用的架构图是这样的:
┌─────────────────────────────────────────────────────────────┐
│ 外部请求(HTTP/gRPC) │
└─────────────────────────┬───────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ 服务网格(Istio)流量治理/灰度/镜像 │
└─────────────────────────┬───────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ K8s Service(负载均衡 + 服务发现) │
└─────────────────────────┬───────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ Pod(AI推理容器)← HPA自动伸缩 │
│ ┌─────────────────────────────────────────────────┐ │
│ │ FastAPI + PyTorch/ONNX/TensorRT │ │
│ │ Prometheus埋点 + 分布式追踪 │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────┬───────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ 云原生基础设施:GPU节点池 + 对象存储 + 消息队列 │
└─────────────────────────────────────────────────────────────┘坑1:GPU调度效率
K8s默认GPU调度是"整卡分配",即使模型只用20%显存也占整张卡。解决方案:使用vGPU或MIG(多实例GPU)做显存切分。
坑2:模型加载时间
大模型(如LLaMA-70B)加载可能需要几分钟,HPA扩容时新Pod来不及启动。解决:配置startupProbe给更长的启动时间,并设置最小实例数保底。
startupProbe:
httpGet:
path: /health
port: 8080
failureThreshold: 30
periodSeconds: 10 # 最多等300秒坑3:状态管理
AI推理有时需要缓存(如向量数据库),但Pod是无状态的,重启后缓存丢失。解决:使用外部Redis/向量数据库作为共享缓存,Pod只做无状态计算。
重构完成后,效果是显而易见的:
但最大的收获不是这些数字,而是认知的转变:
AI不再是一个"特殊的存在",它和其他微服务一样,可以被编排、被伸缩、被观测、被持续交付。云原生不是AI的附加品,而是AI走向生产级的必经之路。
当你把模型当作"有状态的函数"来管理,把推理当作"可扩展的API"来设计,AI应用就真正融入了云原生的生态。这条路还有很长,但方向已经很清晰了。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。