摘要
AI 后端通常包含 API 服务、模型适配、知识库检索、异步任务、对象存储和数据库。只把 Java 服务打成一个镜像并启动起来,并不等于完成了生产部署。
本文从容器化开始,介绍 AI 后端的镜像构建、Nginx 反向代理、SSE 流式连接、配置与密钥管理、健康检查、Kubernetes 部署、资源限制、滚动更新和故障排查,形成一套可用于小型生产环境的部署基线。
一、背景与问题
AI 应用的部署难点主要来自三个方面:
1. 进程运行时间长
模型调用可能持续几十秒,流式接口会长期保持连接。默认的代理超时、缓冲和连接回收配置,可能导致客户端提前断开或响应被整体缓存。
2. 资源波动大
文档解析、Embedding、模型调用和 Agent 工具执行的资源模型不同。把所有任务放进一个容器,会导致批量任务影响在线对话。
3. 配置和密钥复杂
模型 API Key、数据库密码、向量库地址和对象存储凭据不能写进镜像。不同环境还需要不同的模型、域名和限流参数。
一个基本的部署拓扑如下:
text
客户端
│ HTTPS
▼
Nginx / Ingress
├─ /api/chat → AI API
├─ /api/stream → 流式 API
└─ /internal/task → 异步任务服务
│
├─ MySQL / PostgreSQL
├─ Redis
├─ 对象存储
├─ 向量数据库
└─ 模型供应商
二、核心概念
1. 镜像与运行配置分离
镜像应包含固定版本的应用和运行时,不应包含环境密钥。运行时通过环境变量、Secret、配置中心或挂载文件注入配置。
2. 存活、就绪与启动探针
| 探针 | 作用 | 失败后的行为 |
|---|---|---|
| Startup | 判断应用是否完成启动 | 暂不执行其他探针 |
| Liveness | 判断进程是否卡死 | 重启容器 |
| Readiness | 判断能否接收流量 | 从服务端点摘除 |
AI 服务的就绪检查不能简单等同于"JVM 进程已启动",还要考虑数据库迁移、关键配置和依赖可用性。
3. 流式连接与普通请求不同
普通接口可以使用较短的读超时;SSE 需要允许长连接,并关闭代理缓冲。否则模型生成的增量内容可能积累到响应结束后才一次性发送。
4. 水平扩展与会话状态
如果会话、流式任务和幂等状态保存在本地内存,多个副本之间会出现数据不一致。生产环境应把共享状态放到数据库、Redis 或专用任务系统中。
三、工作原理
1. 容器请求链路
text
HTTPS 请求
→ Nginx / Ingress TLS 终止
→ Service 负载均衡
→ AI API Pod
→ Redis / 数据库 / 模型供应商
→ 响应或 SSE 增量事件
滚动发布时,旧 Pod 不能立刻终止正在执行的流式任务,需要结合连接排空、优雅停机和任务状态恢复。
2. 优雅停机
应用收到终止信号后,应停止接收新请求,但允许已有请求在有限时间内完成:
text
收到 SIGTERM
→ 标记 Pod NotReady
→ 停止接收新任务
→ 等待流式连接和短任务
→ 取消或转移超时任务
→ 关闭连接池和线程池
→ 进程退出
等待时间不能无限延长,否则发布会卡住。
3. 资源隔离
建议至少拆分三类工作负载:
| 工作负载 | 特点 | 部署建议 |
|---|---|---|
| 在线 API | 延迟敏感、长连接 | 独立 Deployment |
| 文档处理 | CPU、内存和磁盘波动 | 独立 Worker |
| 批量 Agent | 任务耗时长、可排队 | 队列消费者 |
模型推理如果使用本地 GPU,也应与业务 API 分离,避免发布业务服务时重启推理进程。
四、实战示例
1. Dockerfile
dockerfile
FROM eclipse-temurin:21-jre
WORKDIR /app
RUN addgroup --system app && adduser --system --ingroup app app
COPY target/ai-backend.jar /app/app.jar
USER app
ENV JAVA_OPTS="-XX:MaxRAMPercentage=75 -Dfile.encoding=UTF-8"
EXPOSE 8080
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app/app.jar"]
生产镜像应固定基础镜像版本,定期扫描漏洞,并避免使用 root 运行。JVM 内存参数要结合容器限制调试,不要直接照搬开发机配置。
2. 多阶段构建
dockerfile
FROM maven:3.9-eclipse-temurin-21 AS build
WORKDIR /workspace
COPY pom.xml .
COPY src ./src
RUN mvn -B -DskipTests package
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=build /workspace/target/*.jar /app/app.jar
USER 10001
EXPOSE 8080
ENTRYPOINT ["java", "-XX:MaxRAMPercentage=75", "-jar", "/app/app.jar"]
构建阶段和运行阶段分离后,最终镜像不包含 Maven、源码和构建缓存。
3. Spring Boot 健康检查
yaml
management:
endpoints:
web:
exposure:
include: health,info,prometheus
endpoint:
health:
probes:
enabled: true
health:
livenessstate:
enabled: true
readinessstate:
enabled: true
yaml
server:
shutdown: graceful
spring:
lifecycle:
timeout-per-shutdown-phase: 45s
健康端点不要暴露到公网。外部负载均衡只需要访问受控的探针路径。
4. Nginx 流式代理
nginx
upstream ai_backend {
server ai-api:8080;
keepalive 32;
}
server {
listen 443 ssl;
server_name ai.example.com;
location /api/stream {
proxy_pass http://ai_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header X-Request-Id $request_id;
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 120s;
proxy_send_timeout 30s;
}
location /api/ {
proxy_pass http://ai_backend;
proxy_http_version 1.1;
proxy_set_header X-Request-Id $request_id;
proxy_read_timeout 60s;
}
}
实际部署还要配置 TLS 证书、请求体大小、访问日志脱敏和连接数上限。SSE 路径与普通 API 分开配置,便于控制超时和缓冲。
5. Kubernetes Deployment
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: ai-api
spec:
replicas: 3
selector:
matchLabels:
app: ai-api
template:
metadata:
labels:
app: ai-api
spec:
terminationGracePeriodSeconds: 60
containers:
- name: ai-api
image: registry.example.com/ai-api:2026.09.29
ports:
- containerPort: 8080
envFrom:
- configMapRef:
name: ai-api-config
- secretRef:
name: ai-api-secret
resources:
requests:
cpu: "500m"
memory: "1Gi"
limits:
cpu: "2"
memory: "2Gi"
startupProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
failureThreshold: 30
periodSeconds: 5
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
periodSeconds: 10
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
periodSeconds: 5
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 10"]
preStop 只是给流量摘除留出缓冲时间,真正的优雅停机仍需要应用处理 SIGTERM。
6. Service 与 HPA
yaml
apiVersion: v1
kind: Service
metadata:
name: ai-api
spec:
selector:
app: ai-api
ports:
- name: http
port: 8080
targetPort: 8080
yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: ai-api
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: ai-api
minReplicas: 3
maxReplicas: 12
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 65
只按 CPU 扩容对 AI 服务往往不够。更有效的指标可能是正在执行的模型请求数、队列长度、首 Token 延迟和上游限流率。可以通过自定义指标扩展 HPA。
7. 配置与密钥
yaml
apiVersion: v1
kind: Secret
metadata:
name: ai-api-secret
type: Opaque
stringData:
MODEL_API_KEY: replace-in-secret-manager
DATABASE_PASSWORD: replace-in-secret-manager
示例只用于说明结构。生产环境应通过外部 Secret 管理系统或密钥同步控制器注入,避免把真实内容提交到 Git。
五、常见问题与实践建议
1. SSE 能连接,但收不到增量内容
检查 Nginx、Ingress 和 CDN 是否启用了响应缓冲,检查应用是否真的以流式方式消费上游响应,还要确认中间层没有把 text/event-stream 转成普通 JSON。
2. Pod 经常被 OOMKilled
常见原因包括:
- JVM 堆占满容器内存。
- 长对话上下文在内存中重复保存。
- 流式响应缓冲没有上限。
- 文档解析一次性加载大文件。
- 并发过高导致请求对象堆积。
设置容器限制只是最后一道保护,还要在应用层限制上下文、文件大小、并发和队列长度。
3. 滚动发布导致流式请求中断
检查 Readiness、连接排空、优雅停机和客户端重连设计。对于不能中断的长任务,应转为异步任务并持久化状态,不要依赖单个 HTTP 连接一直存活。
4. 为什么副本数增加后会话异常?
如果会话、幂等键或取消状态保存在单 Pod 内存中,请求切换到其他副本后就会丢失。把状态迁移到共享存储,并确保所有副本使用相同的序列化和过期策略。
5. 是否应该把模型 API Key 放到 ConfigMap?
不应该。ConfigMap 适合非敏感配置,密钥应使用 Secret 或外部密钥系统,并限制读取权限和审计访问。
六、进阶思考
1. 按 Token 和并发量扩容
AI 服务的真实负载不只由请求数决定:
text
负载 = 在线请求数
+ 输入上下文长度
+ 输出 Token 速率
+ 工具调用次数
+ 文档处理队列
可以将 ai_inflight_requests、Token 速率和任务队列长度作为扩容依据,并在扩容前确认模型供应商配额是否足够。
2. API 与 Worker 分离
在线 API 只负责接收请求、校验权限和返回结果;文档解析、批量 Embedding、报告生成等任务进入队列,由 Worker 独立扩缩容。这样批量任务不会直接占满在线请求线程。
3. 发布策略
对模型配置、Prompt 和 Agent 工具的变化,可以采用灰度发布:
text
新版本
→ 5% 流量
→ 检查错误率、延迟、费用和质量
→ 25%
→ 100%
出现异常时,回滚的不一定是镜像,也可能是模型路由、Prompt 版本或工具配置。
4. 安全边界
生产部署至少需要:
- 入口 TLS 和身份认证。
- 网络策略限制 Pod 间访问。
- 非 root 容器和只读文件系统。
- 镜像漏洞扫描。
- 访问日志脱敏。
- 工具调用审计。
- 数据库和对象存储最小权限。
AI 服务的安全风险不仅来自 HTTP 接口,也来自模型输出驱动的工具调用。
结论
部署生产级 AI 后端,需要同时处理镜像、代理、流式连接、健康检查、优雅停机、资源隔离、密钥管理和扩缩容。Docker 解决可重复运行,Nginx 或 Ingress 解决入口和连接管理,Kubernetes 解决编排、探针、滚动发布与弹性。
建议先完成单机容器化和流式代理验证,再迁移到 Kubernetes,并将在线 API、异步 Worker 和本地模型推理逐步拆分。部署完成后,还要用真实的延迟、Token、错误率和费用指标校准资源配置。
参考资料
- Spring Boot Container Images:https://docs.spring.io/spring-boot/reference/packaging/container-images/
- Docker Documentation:https://docs.docker.com/
- Kubernetes Documentation:https://kubernetes.io/docs/
- Nginx Documentation:https://nginx.org/en/docs/