Docker、Nginx 与 Kubernetes 部署 AI 后端服务

摘要

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、错误率和费用指标校准资源配置。

参考资料

  1. Spring Boot Container Images:https://docs.spring.io/spring-boot/reference/packaging/container-images/
  2. Docker Documentation:https://docs.docker.com/
  3. Kubernetes Documentation:https://kubernetes.io/docs/
  4. Nginx Documentation:https://nginx.org/en/docs/
相关推荐
Zhu7582 小时前
离线二进制部署-Kubernetes-v1.36.4
容器·贪心算法·kubernetes
谢亮_vipxieliang3 小时前
镜像安全:扫描、签名与软件供应链
安全·docker
天衍四九-4 小时前
Docker容器实战系列(八):Docker生产最佳实践与避坑指南,系列终章
运维·docker·容器
Zhou1411366 小时前
Docker_03_DockerCompose多容器编排
运维·docker·容器
程序猿阿越6 小时前
containerd如何拉取镜像
后端·kubernetes·源码阅读
Elastic 中国社区官方博客6 小时前
Kubernetes attributes processor v1:它对 EDOT Collector 意味着什么
java·大数据·elasticsearch·搜索引擎·贪心算法·kubernetes·全文检索
啊哈一半醒7 小时前
Docker 底层知识:从 Namespace 到 UnionFS
运维·docker·容器
吴声子夜歌8 小时前
Nginx应用与运维——Nginx核心配置指令(一)
运维·nginx
fruge10 小时前
飞牛OS部署Flare导航页:Docker Compose、YAML书签配置与cpolar公网访问
运维·docker·容器