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.yaml、models.yaml、routing.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
预期结果:使用 ab 或 wrk 对 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 已经具备了生产部署的三个底线能力:容器化运行、持久化存储、自动伸缩。
行动清单
- 用本章的 Dockerfile 模板在你自己的项目中构建镜像,确认健康检查端点可用
- 将上一章的
routing.yaml内容迁移到 ConfigMap,注意处理 YAML 缩进 - 先用
minikube本地部署一遍,执行curl和并发测试验证 - 修改 HPA 的
averageValue阈值,观察扩容和缩容的实际触发时间 - 检查
/opt/data目录中的数据是否因 Pod 重启而丢失,确认 PVC 挂载正确
在下一章《监控与日志体系确保 Agent 在生产环境可观测》中,我们将为这个已经跑在 Kubernetes 上的 Hermes 实例接入 Prometheus 指标收集和 Grafana 可视化面板,并配置日志聚合管道——让你在 3 个 Pod 自动扩缩到 10 个时,依然能清楚看到每条对话请求走了哪个模型后端、延迟分布如何、记忆写入是否正常。
Hermes Agent 系统设计与工程落地
关于 LearnKu