AI部署最佳实践:这次怎么落地的
回想这几年在 AI 项目上的折腾,真正让我在夜深人静时抱着电脑叹气的,从来不是模型推理本身——那通常是技术方案中最确定的一环——而是那些为了把一个本地能跑的东西变成稳定服务的琐碎:资源隔离、版本管理、灰度发布、监控告警。
哪怕起初只是用 Docker Compose 起两个服务,这也是在为后续迁到 K8s 打基础。
先把容器和编排说清楚
我现在的做法基本围绕 Docker + Kubernetes 展开。哪怕起初只是用 Docker Compose 起两个服务,这也是在为后续迁到 K8s 打基础。原因很简单:环境隔离和统一配置能省掉 80% 的“换台机器就跑不通”的麻烦。
Dockerfile 要做到两点:尽量减少层数,用好构建缓存。下面这个模板用过几次,拿来做推理服务还行:
FROM python:3.10-slim as builder
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
gcc \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
FROM python:3.10-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
libgomp1 \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=builder /usr/local/lib/python3.10/site-packages /usr/local/lib/python3.10/site-packages
COPY . .
EXPOSE 8000
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
注意那几行 apt-get 清理和 --no-cache-dir。我吃过镜像太大导致拉取超时的亏,也试过给别人一个几乎没人用的镜像,后来补上了这几行,体积从 1.2GB 缩到 400MB 左右,不少 CI 跑得也快了。
K8s 的 Deployment 和 Service 模板照着官方例子改就行,关键在资源限制。GPU 情况下我一般这样写:
resources:
requests:
memory: "2Gi"
cpu: "1000m"
nvidia.com/gpu: 1
limits:
memory: "8Gi"
cpu: "4000m"
nvidia.com/gpu: 1
别忘记给内存和 CPU 留点余量。我曾经在一次突发的流量高峰里看到服务 OOM,才发现 limits.memory 写得太紧,模型在处理长序列时耗得比预期多。后来稍微上调了一些,再看监控曲线,总算没那么尖了。
监控和告警比我想象的重要得多
之前总想“先把功能上线再说”,结果常常是被一个本来早该发现的 bug 从被窝里叫起来。现在,我基本在部署阶段就顺手把监控加进去。
常用的工具是 Prometheus + Grafana。下面是一个比较基础的 Prometheus 抓取配置:
scrape_configs:
- job_name: 'ai-inference'
scrape_interval: 30s
static_configs:
- targets: ['ai-inference:8000']
Grafana 里的看板,我起码会关注三条曲线:请求 QPS、推理延迟、GPU 利用率。前两个看流量和性能,第三个看资源有没有撑起来。记得有一次 QPS 上来了,但 GPU 利用率始终在 20% 左右徘徊,一看代码,才发现模型加载时没有设 torch.device("cuda"),被默默地放在了 CPU 上,后来修正后 GPU 利用率直接到了 80%,响应时间也降下来了。
告警的话,我习惯用 Alertmanager,下面这个规则很简单但实用:
groups:
- name: ai-inference-alerts
rules:
- alert: HighLatency
expr: histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le, instance)) > 2
for: 5m
labels:
severity: warning
annotations:
summary: "推理延迟偏高: {{ $labels.instance }}"
- alert: ServiceDown
expr: up == 0
for: 1m
labels:
severity: critical
annotations:
summary: "服务宕机: {{ $labels.instance }}"
这几条规则把常见的几种紧急情况都挡了前面。别偷懒不配告警,半夜三更收到第一封邮件时你会感谢早上的自己。
灰度发布不是奢侈,是保命
把一个刚修好的模型直接扔给全部用户,我干过一次,结果是一次线上事故——新版本有概率在特定输入上崩溃,而偏偏测试集没覆盖到。后来我给自己定了一条规矩:宁可麻烦,也要灰度。
最简单的做法是 Traffic Splitting,把 10% 的流量导到新版本,观察一段时间没问题再慢慢放开。下面用 Istio 的 VirtualService 做个示例:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: ai-inference-vs
spec:
http:
- match:
- uri:
prefix: /predict
route:
- destination:
host: ai-inference
subset: v1
weight: 90
- destination:
host: ai-inference
subset: v2
weight: 10
subset 定义在 DestinationRule 里:
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: ai-inference-dr
spec:
host: ai-inference
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
你可以手动调整 weight,也可以写一个脚本根据监控指标自动调整权重。我试过后者,有时候你未必看得到细节指标的变化,但让工具帮你按延迟和成功率来自动切流,总体上比盯着屏幕几个小时靠谱。
再给一个小建议:从灰度到全量之间,最好留一个“回滚按钮”。那次线上事故最后还是靠回滚到上一个版本止血的,这让我明白,回滚路径必须和发布路径一样清晰。
配置管理与环境隔离
别把配置硬编码到镜像里,这条已经被无数的部署事故验证过了。我现在的做法是用 ConfigMap 挂载配置文件,用 Secret 放敏感信息。
先定义一个 ConfigMap:
apiVersion: v1
kind: ConfigMap
metadata:
name: ai-inference-config
data:
config.yaml: |
model_name: "gpt-4"
max_tokens: 2048
temperature: 0.7
然后在 Deployment 里挂载:
spec:
containers:
- name: ai-inference
image: my-registry/ai-inference:latest
volumeMounts:
- name: config
mountPath: /app/config
volumes:
- name: config
configMap:
name: ai-inference-config
Secret 也是类似的:
apiVersion: v1
kind: Secret
metadata:
name: ai-inference-secret
type: Opaque
data:
api_key: <base64-encoded-key>
挂载时同样使用 volumeMounts。这样改配置再也不用重新打镜像,而且开发和生产环境可以用不同的 ConfigMap,尽量把环境之间的差异从代码里抽出来。
日志和追踪,别等出事再补
日志的锅我也背过。一开始所有东西都打印到 stdout,可等到线上出问题,真正关键的信息淹没在几千行无关日志里,那时你才会明白结构化日志的重要。
我给日志加了几个固定字段:时间戳、日志级别、请求 ID、耗时。例如:
{
"timestamp": "2026-07-16T21:10:05Z",
"level": "INFO",
"request_id": "abc-123",
"latency_ms": 245,
"message": "inference completed"
}
顺便把日志打到统一的日志平台,比如 ELK 或者 Loki,不然你依然会像在黑夜里抓鬼一样排查问题。
追踪 Distributed Tracing 对于微服务尤其有用,不过即使只是一个独立服务,给每个请求挂一个 ID 也能减少你排查问题的困扰。我一般会在最开始的网关生成一个 request_id,然后在服务之间透传。当你发现一条异常请求一路传下去,追踪链路能帮你快速定位到是哪个环节出了问题。
常见坑点总结
有几条经验我反复验证过:
资源限制写得太紧会让模型在高负载时直接 OOM 或者被内核杀掉。给 requests 和 limits 留出 20–30% 的缓冲区,除非你对模型的资源消耗有非常准确的测试数据。
监控指标选错了会让你被误导。有时候高 CPU 不代表负载高,内存占用增加也可能只是缓存增长。建议在训练阶段就收集几组你真正关心的指标:推理延迟、吞吐量、GPU 利用率、显存占用。
灰度发布不是真的“稳”。我在一次 5% 的灰度里照样被一个角落的 bug 打中,后来才明白,灰度的真正作用是让你有更多时间观察、捕获和回滚,而不是彻底避免问题。
配置写死在代码里是最大的雷。哪怕一开始只有一两个参数,也尽量抽到配置文件里,不然后面每次改参数都要重新构建和部署。
日志写得太少和太多同样害人。太少时关键信息没有记录,太多则淹没真正有用的内容。适度结构化,尽量让每一行日志都能回答一个“为什么会这样”的问题。
一些个人取舍
我一直认为,所谓的“最佳实践”都是别人踩过坑后的产物。它们有用,但不意味着照搬就能无忧无虑。
容器、编排、监控、灰度、配置管理、日志追踪,这些工具堆在一起确实有点重。如果你的项目一开始只有几个调用次数有限的接口,你也许可以用更简单的方案,比如 Docker Compose + Nginx 反向代理 + 手动切流量。但只要你准备在生产上真正把服务交给别人用,这些重活是迟早要干的。
我曾经为了“快速上线”绕过了监控和灰度,结果花在回滚和熬夜修复上的时间,远比一开始就做好这些要多。也许你没经历过那些被异常流量突然冲垮的晚上,那你可以当这篇是故事;如果你已经经历过,那我希望上面那些条目能帮你少经历几次。
技术的选择,从来不是照着清单画勾,而是根据现实场景权衡。AI 推理服务尤甚:模型本身会变,负载会变,用户的期望也会变。把部署方案当成活的系统,定期审视和调整,别让它变成一份落灰的文档。
结束语
从本地那行 python app.py 到生产里的一条稳定的 API,中间需要的不仅是技术,还有对自己和系统边界的清醒认识。
把这篇文章读完,你可以先回去检查一下你当前的部署方案:资源有没有预留,监控有没有告警,灰度有没有路径,配置有没有抽离,日志有没有结构化。每补上一项,下次出事故时你就能少一次看日志却找不到线索的绝望。
生产环境没有银弹。只有一次次修补、调整和谨慎,才会在问题真的出现时,让你不至于措手不及。
祝你的线上服务比我的更稳一些。
版权声明: 本文首发于 指尖魔法屋-AI部署最佳实践:这次怎么落地的(https://blog.thinkmoon.cn/post/999-ai-deployment-best-practices-from-dev-to-prod/) 转载或引用必须申明原指尖魔法屋来源及源地址!
评论
使用 GitHub 账号登录后即可留言,支持 Markdown。