K8S上私有化部署Hermes Agent:多Agent集群管理与MCP实践
大模型应用跑到生产环境之后很多团队会撞上一个尴尬的现实单个Agent在Demo里跑得风生水起一旦要把多个Agent、多套工作流、一堆外部工具塞进同一个环境里问题就开始叠着往外冒。这篇文章是我前段时间在K8S集群上私有化部署Hermes Agent的完整复盘从虚拟机资源配置、集群搭建、MCP协议衔接到多Agent集群管理的调度策略和真实踩坑记录全部过一遍。如果你正准备在内网或本地环境搭一套自己的AI大模型多Agent集群管理平台这篇应该能帮你少走不少弯路。先说一下为什么最终选了Hermes Agent。当时团队的需求很明确既要能跑起多个角色分工的Agent又要让它们能调用外部工具和模型服务还必须支持横向扩容。对比了几个开源项目之后Hermes Agent对MCP协议的支持和它自身多实例管理的设计恰好匹配我们的场景。再加上它支持WebUI和Agent服务分离部署也就意味着在K8S里可以按模块独立扩展这一点非常关键。1. 先说清楚Hermes Agent解决的是哪一类问题1.1 单Agent跑得欢多Agent就露馅了很多人第一次接触Agent开发时体会最深的是单个Agent太好玩了。写一个Prompt接一个模型API再给它挂几个工具函数它能帮你查资料、写代码、做总结。但一旦业务场景复杂起来单Agent就会立刻暴露短板上下文窗口有限、工具调用链条太长容易出错、多个任务同时进来时一个Agent根本忙不过来。多Agent架构就是为了解决这些问题出现的。把任务拆给多个专用Agent比如一个负责意图识别、一个负责工具调用、一个负责结果审核每个Agent各司其职。但多Agent带来的新问题是这些Agent进程怎么调度它们之间怎么通信谁来统一管理生命周期如果只是在本机用进程方式跑那根本谈不上集群管理更不用提扩容和容灾。1.2 为什么是K8S而不是Docker Compose或者裸进程说实话如果只是验证功能Docker Compose完全够用我在开发环境就是这么跑的。但生产环境就不一样了。你要面对的是Agent实例宕机了要自动拉起流量上来之后要能自动扩容多个Agent之间要能互相发现配置变更不能靠手工登到服务器上改文件。这些需求正是K8S最擅长处理的领域。K8S的声明式API、Pod自动重启、Service服务发现、HPA水平扩缩容、ConfigMap配置下发几乎是为Agent集群管理量身定做的。你可以把每个Agent看作一个无状态的Pod大多数场景下有状态的特殊组件再用StatefulSet去兜底。这样一套体系下来整个多Agent集群的运维成本会低很多。这也是我在标题里强调K8S集群部署的原因不是炫技而是业务发展到一定阶段后绕不开的路。1.3 整套部署链路涉及的组件地图在正式开始之前我建议你先在脑子里建立一张部署地图。整个Hermes Agent私有化部署从上到下大概分四层基础设施层虚拟机或物理机承载K8S集群各节点需要完成操作系统初始化、容器运行时安装。集群编排层K8S集群本身包括控制平面、工作节点、CNI网络插件。中间件层镜像仓库、MCP Server、数据库如果有持久化需求、对象存储等。应用层Hermes Agent的Agent服务、WebUI服务以及对应的工作负载、配置、服务暴露规则。后面所有内容和章节都是沿着这条链路展开的。2. 虚拟机层面的资源规划没把底层算明白集群后面全是坑2.1 三节点还是单节点我的资源规划逻辑先强调一个观点如果是实验和学习用单节点K8S集群完全OK我的环境就是用kubeadm初始化的单节点控制平面。但如果你要模拟真实的多Agent集群管理我建议至少准备三台虚拟机组成一主两从的标准结构。原因很简单很多调度策略比如反亲和性在单节点上根本看不出效果你得有足够的节点来承载Pod的分布。当时我手里的资源是几台配置还不错的物理服务器然后通过KVM做了虚拟机。如果你是拿笔记本学习用VMware Workstation或者VirtualBox开三台Ubuntu虚拟器就行。不过要提醒一下虚拟机别开太小的规格否则后面装完K8S组件再跑Hermes Agent内存很容易被吃满。我建议的最小配置如下表节点角色操作系统CPU内存磁盘控制平面节点Ubuntu 22.04 LTS4核8GB100GB工作节点1Ubuntu 22.04 LTS4核8GB100GB工作节点2Ubuntu 22.04 LTS4核8GB100GB如果你只是单机学习那2核4GB是最低底线再低连系统都快不起来了。在生产或者长时间跑Agent任务时影响最大的其实是内存因为大模型推理和Agent的上下文管理都极度吃内存。别在这里抠抠了后面必后悔。2.2 系统初始化内核参数、文件描述符与容器运行时虚拟机开好之后先别急着装Docker或者kubeadm有些基础配置必须先做不然装到一半各种报错。首先是主机名和hosts解析。三台机器的主机名要不同并且互相能看到对方。我在实际操作中习惯把节点信息写进 /etc/hosts这样后续kubeadm join的时候不容易因为DNS解析问题卡住。然后是模块加载和内核参数调优。K8S依赖iptables做网络转发同时需要加载overlay和br_netfilter内核模块。初始化脚本里这几条是必须的cat EOF | sudo tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF sudo modprobe overlay sudo modprobe br_netfilter cat EOF | sudo tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-iptables 1 net.bridge.bridge-nf-call-ip6tables 1 net.ipv4.ip_forward 1 EOF sudo sysctl --system还有一处很容易被忽略文件描述符和线程数限制。Agent服务在长时间运行后会打开大量网络连接和文件如果不调高限制会出现Too many open files之类的诡异问题。我习惯在 /etc/security/limits.conf 里把普通用户和root的nofile都调到65535。另外不同Linux发行版对swap的处理不一样。K8S在默认情况下要求关闭swap虽然新版kubeadm允许开启swap运行但建议还是关掉避免性能抖动。用sudo swapoff -a临时关闭然后还要注释掉 /etc/fstab 里的swap挂载项否则重启后又回来了。2.3 存储与网络方案选型然后说说存储。Hermes Agent本身很多组件都可以做成无状态但一旦涉及会话记录、Agent知识库、WebUI上传的文件就需要持久化存储了。在生产环境里K8S通常会接一个分布式存储比如Ceph或NFS。在我的私有化场景里最省事的是先搭一个NFS Server把共享目录挂给集群然后通过PersistentVolume和PersistentVolumeClaim来对接。网络方案我选的是Calico不选Flannel。原因很直接Calico支持NetworkPolicy这在多Agent场景里特别重要。因为不同Agent之间可能需要隔离访问权限比如某个Agent只能访问MCP Server的特定端口而不能访问整个集群所有服务。Flannel在租户隔离和策略控制上偏弱所以当你有安全诉求时直接上Calico别纠结。3. K8S集群搭建实战从容器运行时到第一个Pod3.1 容器运行时安装与配置这块是很多人卡壳的地方。自K8S 1.24版本之后Docker的桥接层被移除容器运行时需要走CRI规范。我们一般直接装containerd然后在 /etc/containerd/config.toml 里把SystemdCgroup改成true。装containerd的方式很成熟官方源或系统源都可以。装完后要检查一下配置。注意containerd默认的sandbox镜像地址在国外如果你所在的网络环境拉取受限记得把如下配置改成可访问的镜像仓库地址[plugins.io.containerd.grpc.v1.cri] sandbox_image registry.aliyuncs.com/google_containers/pause:3.9这一步不做后续初始化集群时控制平面节点会一直卡在拉取pause镜像上。这是国内部署最常见的坑之一。3.2 控制平面初始化与工作节点加入装好kubeadm、kubelet、kubectl之后开始初始化控制平面。这里最关键的是指定Pod网段和API Server地址。我的命令大概是这样sudo kubeadm init \ --kubernetes-versionv1.28.2 \ --apiserver-advertise-address192.168.1.10 \ --pod-network-cidr10.244.0.0/16 \ --image-repositoryregistry.aliyuncs.com/google_containers--image-repository参数也是针对国内网络环境的常规操作换成可访问的镜像源就行。初始化成功后kubeadm会在终端打印两条信息一是kubectl配置文件的路径提示二是工作节点加入集群的完整kubeadm join命令。这两条信息建议立刻保存join命令过期了可以重新生成但保存下来能省事很多。工作节点上执行join命令时有几个小前置条件所有节点的时间必须同步用chrony或者ntp都可以所有节点的kubelet已经启动。我遇到过好几次join命令报错一看都是因为节点时钟差了一分多钟。集群对时间漂移的容忍度很低这是老生常谈但必须再次强调的点。3.3 网络插件与验证网络插件是在控制平面初始化完成之后、工作节点加入之前或之后都可以装我用的是Calico。直接应用官方提供的manifest文件kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.27/manifests/calico.yaml等Pod都变成Running之后跑一条命令检查节点状态kubectl get nodes三台机器都应该显示Ready。如果不Ready90%的可能是网络插件没正常工作或者Pod网段冲突了。检查一下kubectl get pods -n kube-system里的calico相关Pod日志基本都能定位。集群好了之后建议立刻部署一个K8S Dashboard或者装k9s命令行工具。Dashboard用来直观看到Pod状态k9s适合快速排查问题。我个人更推荐k9s它的交互效率在排查大量Pod时比Dashboard高很多。4. MCP协议衔接Hermes Agent如何长出工具能力4.1 MCP协议到底是干什么的MCP全称Model Context Protocol模型上下文协议。你可以把它理解成Agent和外部工具之间的USB接口。没有这个协议之前你想让Agent调用一个工具就得专门写一套调用代码每接一个新工具就意味着改一遍Agent的代码逻辑。而有了MCP之后工具方只要实现一套MCP Server可以提供工具、资源、提示三种能力Agent就能动态发现并调用。这对多Agent集群管理有什么意义意义非常大。在一个Agent集群里你可能希望每个Agent能调用不同的外部系统一个Agent要查数据库一个Agent要操作浏览器一个Agent要读取本地文件。如果所有工具逻辑都写在Agent代码里那这个系统基本没法维护了。而通过MCP工具能力被抽离成独立的Server分散部署在K8S集群里Agent在运行时动态发现需要的工具。这就是Hermes Agent对MCP协议的依赖极其重要的原因。4.2 在K8S中部署MCP Server我当时的部署方式是把MCP Server也容器化变成一个普通的K8S Deployment。举个例子我要给Agent加一个数据库查询能力就写一个MCP Server暴露一个固定的端口然后把它的服务发现交给K8S Service。简单看一下MCP Server的目录结构mcp-db-server/ ├── requirements.txt ├── Dockerfile └── server.pyserver.py里核心代码非常轻量本质是暴露一个实现工具逻辑的服务。Dockerfile也简单基于Python镜像把服务跑起来。之后构建镜像、推到私有仓库、写Deployment YAML这套流程跟在集群里部署任何Web服务没什么区别。这里提醒一个很多人会忽略的细节MCP Server很多时候是长连接或者流式输出。K8S的Service默认负载均衡模式是轮询对于一次性请求没问题但如果MCP Server和Agent之间维持一个长时间推流连接建议把Service的sessionAffinity配置成ClientIP保证同一个Agent的连接稳定指向同一个后端。4.3 Agent与MCP的配置对接在Hermes Agent里配置MCP Server的地址其实就是告诉Agent框架MCP Server在哪里。由于整个集群都在同一个K8S网络中你可以直接用Service DNS名来访问比如http://mcp-db-server.default.svc.cluster.local:8000还有一点值得注意MCP Server的鉴权信息不要明文写在Agent的配置里。Agent接入MCP Server时通常会带一个API Token或密钥这类信息应该通过K8S Secret下发而不是写死在ConfigMap里。我最初图省事直接把Token写进了ConfigMap后来做安全审计时被标记为高优先级问题才痛改前非。5. Hermes Agent私有化部署镜像、编排与配置下发5.1 私有镜像仓库的搭建与镜像推送在公网环境镜像直接推给Docker Hub就行。但私有化部署意味着内网环境没有公网拉取条件所以必须要有一个自己的镜像仓库。我用的是Harbor比Registry和Nexus更完整自带UI、项目隔离和权限控制适合团队内部使用。Harbor装起来很简单官方提供了离线安装包解压后改一下hostname和密码然后执行 install.sh 就可以。装好之后记得在每台K8S节点的 /etc/containerd/config.toml 里配置好Harbor的地址和证书如果用的是自签HTTPS证书的话否则节点在拉镜像时会报certificate signed by unknown authority。把Hermes Agent的镜像推到Harbor之后在K8S里创建一个docker-registry类型的Secret让Pod在拉取私有仓库镜像时能通过认证kubectl create secret docker-registry harbor-registry \ --docker-serverharbor.internal.lan \ --docker-usernameadmin \ --docker-passwordxxxxx5.2 工作负载编排Helm还是裸YAML这是一个非常现实的问题。Hermes Agent包含Agent服务和WebUI服务如果你只用裸YAML那意味着要维护Deployment、Service、ConfigMap、Secret、Ingress至少五类资源而且每个环境开发、测试、生产都要复制一份改动起来极易出错。我这次用的是Helm。先给Hermes Agent写一个Helm Chart把镜像、副本数、环境变量、Ingress配置全部参数化然后环境差异化通过values.yaml文件来区分。这样部署命令只需要一句helm install hermes-agent ./hermes-agent-chart -f values-prod.yaml升级也方便helm upgrade hermes-agent ./hermes-agent-chart -f values-prod.yaml如果你只是临时验证功能可以用kubectl apply直接跑裸YAML但凡是准备长久使用都建议直接上Helm。这个迁移成本越往后越高。5.3 ConfigMap与Secret的配置管理Hermes Agent运行需要一些配置比如模型API地址、模型名称、API Key、MCP Server地址列表等。这里分为两类非敏感的用ConfigMap敏感信息用Secret。我一般把model配置、日志级别、Agent角色定义这种放ConfigMap把API Key、数据库密码放Secret。ConfigMap和Secret都支持挂载成文件或环境变量的方式我在部署时倾向挂载文件因为Agent框架对配置文件的支持通常更友好而且文件方式修改后通过滚动重启就能生效。需要注意ConfigMap挂载的文件是只读的你想在Pod内改配置再让服务热加载是不可能的。如果Agent服务本身支持动态加载配置那你可以通过K8S的subPath方式挂载单个文件如果服务只会启动时读一次配置那就简单了每次改完ConfigMap执行kubectl rollout restart deployment就行。6. 多Agent集群管理的调度与伸缩设计6.1 工作负载类型选择Deployment还是StatefulSet这是设计多Agent集群时最核心的一个决策。我见过不少人在这个选择上栽了跟头。简单说如果Agent实例之间没有固定身份任意一个实例处理任意一个请求都行那就是典型的无状态应用用Deployment足够了。单实例多Agent并发不太一样这里说的是同一类Agent的多个副本。但如果你的Agent集群里有一个协调者角色它需要记录所有子Agent的状态并且需要稳定的网络标识和存储那它应该用StatefulSet。StatefulSet会为每个副本创建稳定的名称和独立的持久化存储。比如你的主控Agent如果它有选举机制或固定协调职责就值得用StatefulSet。我这次的架构是编排协调类Agent用StatefulSet承担任务执行的具体业务Agent用Deployment。这种混合模式既保证了协调者的稳定性又让执行类Agent可以随意扩缩容算是比较平衡的方案。6.2 多Agent的亲和性与反亲和性调度K8S的调度器默认会尽量把Pod打散到不同节点上但这是尽量不是强制约束。在Agent集群场景下你通常希望同类Agent的不同副本分散在不同节点上避免一台虚拟机挂了导致整个Agent类型不可用。这时就要用到PodAntiAffinity反亲和性。我这里举一个实际例子affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: app operator: In values: - hermes-worker topologyKey: kubernetes.io/hostname这个配置的意思很直白如果集群资源允许尽量让标记为hermes-worker的Pod不要落到同一个节点上。用preferred弱偏好而不是required强制是为了避免在节点数不足时Pod一直Pending。实际观察下来加了反亲和性之后集群单个节点宕机对整体Agent可用性的影响确实小了很多。另外还要考虑一种互补的场景如果你的Agent A需要访问某个独立的数据库而这个数据库跟Agent A在同一个节点上时网络延迟更低那你可以给Agent A加上NodeAffinity让它尽量调度到数据库所在的节点。这种互补用法很实用但要注意别跟反亲和性规则冲突了。6.3 HPA配置与并发压测验证多Agent集群管理里最有价值的能力之一就是自动伸缩。HPA可以根据CPU、内存或自定义指标自动增减副本数。我的Agent服务主要是CPU密集型涉及大量上下文处理所以直接用CPU指标。apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: hermes-worker-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: hermes-worker minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 60这个配置的意思是CPU使用率超过60%时HPA会逐渐增加副本数最多加到10个低于60%并持续一段时间后会逐渐缩容到最少2个。不要设置成1最低副本数要留一点冗余避免突发流量刚起来时只有一个Pod在那硬扛。压测验证我用的是部署在集群里的一个压测容器模拟多个并发会话请求。从监控面板上能清晰看到当并发请求数升高、CPU超过阈值后Pod数量开始上涨流量平稳后Pod逐步回收。这一步是整个集群活起来的关键验证。7. 部署中真实踩过的坑与排查思路7.1 请求的名称无效的根因定位这个报错是部署Hermes Agent时最容易遇到的。最开始我一脸懵以为是Agent配置里的API地址写错了。后来一查发现根本不是API的问题而是K8S拉取私有镜像时认证失败。表现是这样的在Pod描述信息里看到ImagePullBackOffdescribe Pod时events里滚动刷着一条类似request has invalid name的错误。原因是containerd在从Harbor拉镜像时配置的镜像仓库地址或者认证信息不正确导致请求格式不合法。解决思路并不复杂。先确认 containerd 里配置的registry端点和Harbor的实际地址完全一致包括端口再检查你创建的Secret名称是否在Deployment的imagePullSecrets字段里被引用了。这两处对上号问题基本就消失了。这里特别提醒不要只在Pod YAML里加imagePullSecretscontainerd的配置如果不正确这个坑照样踩。7.2 一直重启的CrashLoopBackOff然后是CrashLoopBackOff。这个问题曾经折磨了我大半天。Pod能启动但启动后几秒钟就挂掉然后K8S不断重启它。用kubectl logs看了半天日志发现Agent在启动时尝试连接一个MCP Server连接超时后直接抛异常退出。为什么会连接超时我查了一圈才发现MCP Server的Service地址写的是ClusterIP但Agent Pod所在的命名空间和MCP Server所在命名空间不同而集群里启用了NetworkPolicy默认没有放行跨命名空间访问。所以不是Agent本身的问题是网络策略把它挡了。这也印证了我前面推荐Calico的原因如果你的集群没有启用NetworkPolicy这个错误可能就一直被掩盖而生产环境启用策略后这类问题会成批出现。修复方式是在NetworkPolicy里显式放行Agent Pod到MCP Server的指定端口访问。7.3 无状态Agent的存储漂移还有一个很深坑就是以为所有Agent都能做成无状态。我的会话记忆Agent一开始用的是Deployment副本一扩问题立刻来了不同副本之间的会话数据完全不一致。用户第一次请求落到Pod A第二次请求被负载均衡到Pod BB根本不知道之前的对话历史整个交互直接断片。这个场景的正确解法有两种一是把会话数据外置比如放到Redis或数据库里让Agent本身变成无状态二是如果会话数据跟实例强绑定就把Deployment改成StatefulSet每个实例固定自己的存储。先把会话状态抽到独立的数据库里这才是最稳妥的。这个坑提醒我在设计工作负载类型时不能只看感觉上没有状态而是要从业务逻辑上认真盘一遍哪些数据是必须持久化、必须在多副本间共享的。7.4 慢启动与就绪探针的配合最后一个小细节Agent服务启动通常比较慢注册MCP工具列表、加载模型配置都要花不少时间有的甚至要几十秒才能对外提供服务。如果在Deployment里不配置存活探针和就绪探针K8S默认认为Pod启动完成就可以接流量结果就是刚开始一两个请求直接超时。我建议在容器的liveness和readiness探针上给足初始延时时长readinessProbe: httpGet: path: /health port: 8080 initialDelaySeconds: 20 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 6initialDelaySeconds设成20秒是等Agent框架把核心组件初始化完failureThreshold设成6次是容忍启动过程中短暂的波动。这个配置在发布新版本时能明显降低请求失败率。8. 最后聊点运维经验整套部署跑通之后我最大的感受是Hermes Agent和K8S部署本身都不是最难的难的是把每个环节的底层逻辑想清楚。为什么存储要外置、为什么要反亲和性、为什么MCP Server要有自己的Service这些决策点一旦想明白了部署过程就是水到渠成的事。最后再分享一个运营层面的小技巧。多Agent集群上线之后一定要做好监控告警。我除了K8S本身的事件告警之外还用Prometheus盯了每个Agent Pod的内存和CPU用量曲线。原因是Agent类型不同资源占用差异非常大有的Agent长时间闲置只占几百兆内存有的Agent处理一个长文本任务就吃掉4GB。只有把资源画像摸清楚了你才能给每类Agent设置合理的requests和limitsHPA的阈值才会有参考价值。这个经验踩过坑的人才懂提前说给正准备动手的你。