3.3. 容器化与编排是生产部署的必备技能

容器化与编排是生产部署的必备技能

2026 年 4 月,Hermes Agent 在 GitHub 上的 Star 数突破 2 万,社区里最热门的 Issue 不是“如何实现记忆学习”,而是“怎么在生产环境稳定运行 Agent”。一个能自我进化的 Agent 系统,如果每次重启都丢失记忆、每次流量高峰都 OOM、每次发布都要手动 SSH 登录服务器,那它连及格线都够不上。

本章要解决的核心问题就是:把前两章配置好的模型路由和多终端后端,打包成一个可直接部署到 Kubernetes 的容器化应用。你将学会编写生产级 Dockerfile、部署到 K8s 并配置自动扩缩容,最终让 Hermes Agent 真正达到“一次部署,长期自运行”的生产标准。


你需要什么

资源 说明
Docker 环境 本地或远程,版本 ≥ 24.0
Kubernetes 集群 可使用 minikube、MicroK8s 或云服务商集群
kubectl 已配置 能正常执行 kubectl get nodes
前一章的配置文件 config.yamlmodels.yamlrouting.yaml
预计时间 45 分钟完成全流程

最终成果

你将得到一个运行在 Kubernetes 中的 Hermes Agent 实例,该实例具备以下能力:

  • 容器化运行:通过多阶段构建优化镜像体积,非 root 用户运行
  • 配置热加载:ConfigMap 挂载配置文件,修改后只需重启 Pod
  • 数据持久化:记忆、会话和技能数据保存在 PersistentVolume 中,Pod 重建不丢失
  • 自动扩缩容:基于请求并发数的 HPA 在流量高峰自动扩容,低谷回缩
  • 资源可控:CPU 和内存严格限制,防止 OOM 级联崩溃

为什么做这个:上一章我们配好了 15+ 平台的模型路由,但只在本地终端跑过一次——这离生产环境还差一整套基础设施。容器化不是可选项,是 AI Agent 从玩具变成基础设施的最后一道门槛。


步骤一:编写生产级 Dockerfile

多阶段构建 + 非 root 用户

在项目根目录创建 Dockerfile

# Dockerfile
# ============ 阶段 1: 构建依赖 ============
FROM python:3.12-slim AS builder

WORKDIR /app
# 安装系统依赖(编译用)
RUN apt-get update && apt-get install -y --no-install-recommends \
    gcc libffi-dev && \
    rm -rf /var/lib/apt/lists/*

COPY requirements.txt .
# 将依赖安装到独立目录,便于后续复制
RUN pip install --user --no-cache-dir -r requirements.txt

# ============ 阶段 2: 运行镜像 ============
FROM python:3.12-slim AS runner

# 创建非 root 用户
RUN groupadd -r hermes && useradd -r -g hermes -s /bin/false hermes

WORKDIR /app

# 从 builder 复制已安装的依赖
COPY --from=builder /root/.local /home/hermes/.local
ENV PATH=/home/hermes/.local/bin:$PATH

# 复制应用代码
COPY --chown=hermes:hermes . .

# 切换到非 root 用户
USER hermes

# 健康检查配置
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
    CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1

EXPOSE 8000

CMD ["python", "-m", "hermes.main", "serve"]
# requirements.txt(精简示例)
fastapi>=0.110.0
uvicorn[standard]>=0.29.0
openai>=1.30.0
anthropic>=0.39.0
redis>=5.0.0
prometheus-client>=0.20.0

预期结果:执行 docker build -t hermes-agent:v1 . 后镜像构建成功,且 docker images 显示镜像体积显著小于直接使用 python:3.12 基础镜像。

踩坑经验:有人会问为什么不把 COPY . . 放在 builder 阶段更早的位置。因为 Docker 层缓存机制——如果先复制代码再 pip install,每次代码改动都会触发依赖重新安装,白白浪费数分钟的构建时间。依赖复制和安装必须放在代码复制之前。


步骤二:准备 Kubernetes 部署清单

Deployment + ConfigMap + PersistentVolume

我们将创建一个完整的 K8s 部署文件 k8s/manifest.yaml,按资源类型拆解如下。

ConfigMap — 将配置文件注入 Pod

# k8s/manifest.yaml(ConfigMap 部分)
apiVersion: v1
kind: ConfigMap
metadata:
  name: hermes-config
  namespace: hermes
data:
  config.yaml: |
    # 从宿主机 config.yaml 直接粘贴内容
    # 注意:这里不能包含 API Key 等敏感信息——那些用 Secret 注入
    run_mode: production
    log_level: info
    agent:
      max_rounds: 50
      nudge_interval: 10
  models.yaml: |
    # 粘贴 models.yaml 内容
    providers:
      - name: openai
        api_base: "https://api.openai.com/v1"
        default_model: gpt-4o
      - name: anthropic
        api_base: "https://api.anthropic.com/v1"
        default_model: claude-sonnet-4-20250514
  routing.yaml: |
    # 粘贴 routing.yaml 内容(从上一章的配置复制过来)
    default_strategy: least_latency
    backends:
      - platform: openai
        weight: 60
      - platform: anthropic
        weight: 40

Secret — 存储 API Key 等敏感信息

# 用 kubectl 创建,不要写在 YAML 文件中
# kubectl create secret generic hermes-secrets \
#   --from-literal=OPENAI_API_KEY=sk-xxx \
#   --from-literal=ANTHROPIC_API_KEY=sk-ant-xxx \
#   -n hermes

Deployment — 定义 Pod 运行形态

# k8s/manifest.yaml(Deployment 部分)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hermes-agent
  namespace: hermes
  labels:
    app: hermes-agent
spec:
  replicas: 2
  selector:
    matchLabels:
      app: hermes-agent
  template:
    metadata:
      labels:
        app: hermes-agent
    spec:
      # 安全上下文 — 非 root 运行
      securityContext:
        runAsUser: 1000
        runAsGroup: 1000
        fsGroup: 1000

      # 等待 30 秒后开始健康检查
      containers:
      - name: hermes-agent
        image: hermes-agent:v1
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 8000
          name: http

        # 从 ConfigMap 挂载配置文件
        volumeMounts:
        - name: config
          mountPath: /app/config
          readOnly: true
        - name: data
          mountPath: /opt/data    # Hermes 默认数据目录

        # 注入环境变量(来自 Secret)
        envFrom:
        - secretRef:
            name: hermes-secrets

        # 健康检查
        livenessProbe:
          httpGet:
            path: /health
            port: 8000
          initialDelaySeconds: 30
          periodSeconds: 15
        readinessProbe:
          httpGet:
            path: /ready
            port: 8000
          initialDelaySeconds: 10
          periodSeconds: 10

        # 资源限制 — 防止 OOM
        resources:
          requests:
            cpu: "500m"
            memory: "512Mi"
          limits:
            cpu: "2000m"
            memory: "2Gi"

      volumes:
      - name: config
        configMap:
          name: hermes-config
      - name: data
        persistentVolumeClaim:
          claimName: hermes-data-pvc

PersistentVolumeClaim — 持久化记忆数据

# k8s/manifest.yaml(PVC 部分)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: hermes-data-pvc
  namespace: hermes
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
  # 如果使用默认 StorageClass 可省略 storageClassName

Service — 暴露服务

# k8s/manifest.yaml(Service 部分)
apiVersion: v1
kind: Service
metadata:
  name: hermes-service
  namespace: hermes
spec:
  selector:
    app: hermes-agent
  ports:
  - port: 80
    targetPort: 8000
    protocol: TCP
  type: ClusterIP

预期结果kubectl apply -f k8s/manifest.yaml 后,kubectl get pods -n hermes 显示 2 个 Pod 运行中,kubectl logs -n hermes <pod-name> 输出正常启动日志。

踩坑经验:首次部署后 Pod 反复 CrashLoopBackOff?几乎都是两个原因:一是 API Key 没有正确注入(用 kubectl describe pod 检查 Secret 挂载),二是 ConfigMap 中的 YAML 缩进错误(缩进必须与原始文件完全一致,K8s 不会做任何格式修正)。


步骤三:配置自动扩缩容与资源限制

HPA — 基于并发请求数弹性伸缩

Hermes Agent 的请求处理能力与 LLM API 响应延迟强相关,不适合用 CPU/内存作为扩缩容指标——更好的选择是并发请求数

# k8s/hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: hermes-hpa
  namespace: hermes
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: hermes-agent
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Pods
    pods:
      metric:
        name: hermes_concurrent_requests    # 自定义指标
      target:
        type: AverageValue
        averageValue: 5                     # 每 Pod 平均 5 个并发请求时扩容
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300       # 5 分钟内不缩减
      policies:
      - type: Percent
        value: 50                           # 每次缩减不超过 50%
        periodSeconds: 60
    scaleUp:
      stabilizationWindowSeconds: 30
      policies:
      - type: Percent
        value: 100
        periodSeconds: 15

自定义指标 hermes_concurrent_requests 需要配合 Prometheus Adapter 暴露给 K8s Metrics API。如果暂时没有自定义指标基础设施,可以先使用资源指标作为过渡:

  # 备选:同时使用 CPU 指标,避免 scaling 滞后
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 75

预期结果:使用 abwrk 对 Hermes Service 发起并发请求后,kubectl get hpa -n hermes 观察到副本数自动增加。


步骤四:部署到集群并验证

现在执行全流程部署:

# 1. 创建命名空间
kubectl create namespace hermes

# 2. 创建 Secret(替换为你自己的 API Key)
kubectl create secret generic hermes-secrets \
  --from-literal=OPENAI_API_KEY=sk-xxx \
  --from-literal=ANTHROPIC_API_KEY=sk-ant-xxx \
  -n hermes

# 3. 应用清单文件
kubectl apply -f k8s/manifest.yaml

# 4. 等待 Pod 就绪
kubectl wait --for=condition=ready pod -l app=hermes-agent -n hermes --timeout=120s

# 5. 验证状态
kubectl get all -n hermes

# 输出示例:
# NAME                                READY   STATUS    RESTARTS   AGE
# pod/hermes-agent-7b8c5f9d6-4xk2j   1/1     Running   0          45s
# pod/hermes-agent-7b8c5f9d6-9n3pq   1/1     Running   0          45s
#
# NAME                      TYPE        CLUSTER-IP      PORT(S)   AGE
# service/hermes-service    ClusterIP   10.96.118.42    80/TCP    50s
#
# NAME                           READY   UP-TO-DATE   AVAILABLE   AGE
# deployment.apps/hermes-agent   2/2     2            2           50s

# 6. 测试访问(端口转发到本地)
kubectl port-forward -n hermes service/hermes-service 8080:80

# 7. 在新终端发起测试请求
curl -X POST http://localhost:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"messages":[{"role":"user","content":"今天是几号?"}],"model":"gpt-4o"}'

预期结果:返回 200 状态码和 LLM 的对话响应。


回顾

步骤 做了什么 耗时
Dockerfile 用多阶段构建 + 非 root 用户 + 健康检查封装了 Hermes Agent ~15 分钟
K8s 清单 编写了 Deployment、ConfigMap、Secret、PVC 和 Service ~15 分钟
HPA 配置 基于并发请求数设置了自动扩缩容策略 ~10 分钟
部署验证 部署到集群并测试了 API 调用 ~5 分钟

到此为止,你的 Hermes Agent 已经具备了生产部署的三个底线能力:容器化运行、持久化存储、自动伸缩


行动清单

  1. 用本章的 Dockerfile 模板在你自己的项目中构建镜像,确认健康检查端点可用
  2. 将上一章的 routing.yaml 内容迁移到 ConfigMap,注意处理 YAML 缩进
  3. 先用 minikube 本地部署一遍,执行 curl 和并发测试验证
  4. 修改 HPA 的 averageValue 阈值,观察扩容和缩容的实际触发时间
  5. 检查 /opt/data 目录中的数据是否因 Pod 重启而丢失,确认 PVC 挂载正确

在下一章《监控与日志体系确保 Agent 在生产环境可观测》中,我们将为这个已经跑在 Kubernetes 上的 Hermes 实例接入 Prometheus 指标收集和 Grafana 可视化面板,并配置日志聚合管道——让你在 3 个 Pod 自动扩缩到 10 个时,依然能清楚看到每条对话请求走了哪个模型后端、延迟分布如何、记忆写入是否正常。

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
讨论数量: 0
发起讨论 只看当前版本


暂无话题~