尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Docker Compose 实战:多技能 Agent 服务化部署与运维复盘
最近帮一个小团队搭了一套多技能 Agent 服务既要能回答内部知识库的问题又要能做日程任务编排最好还能顺手翻译、总结文档。一开始差点被各种“Agent 框架”带偏节奏越设计越复杂。后面冷静下来选了一条特别朴素的路——把所有能力拆成独立服务写进一个 docker-compose.yml全团队一条命令就能起服务、更新服务。这套方案跑了两三个月中间踩过不少坑也总结出一套适合小团队的落地姿势。这篇文章我就把这次的架构设计、compose 编排逻辑、Agent 多技能的实现方式还有实际运维里遇到的那些问题完整复盘一遍。目标读者很明确想用轻量方式落地 AI 服务但又不想一上来就整 Kubernetes 的小团队、独立开发者、以及准备做内部 AI 工具的技术负责人。1. 把“多技能 Agent”拆成服务之前先想清楚这四个问题1.1 为什么小团队需要多技能 Agent而不是一堆单功能 Bot一开始团队的想法是各做各的知识库问答一个方案任务编排一个方案翻译总结再套一个工具。结果就是账号一大堆、API Key 分散、界面割裂最后连我们自己都分不清该用哪个入口。所以“多技能 Agent”对小团队真正的价值不是技术上的炫酷而是收敛入口。用户只需要面对一个统一入口它会自己判断请求该交给哪个能力去处理。背后的技能可以有很多个但对用户来说就是一个 AI 助手。另一个现实好处是节省开发成本。很多底层能力是通用的比如大模型调用、用户身份校验、会话历史管理、权限控制。如果每个功能都单独做一套这些底层逻辑就重复了十遍。集中到一个 Agent 服务矩阵里公共能力下沉到基础设施层大家共用一套开发效率会高很多。1.2 单体 Agent 还是多 Agent 容器这个选择决定了后续的维护体验这是架构设计时最核心的取舍。网上很多框架喜欢做一个“超级 Agent”所有技能都往里面塞最后 Prompt 几千行工具列表几十个模型每次请求都要在一堆工具里做选择。我这次的方案反着来不同技能做成独立 Agent并通过网关统一对外。也就是说“多技能”不是在单个进程里实现而是通过多个服务于同一个网关的 Agent 容器来实现。每个 Agent 只专注一个核心技能容器服务核心技能典型场景agent-rag企业知识库问答内部文档检索、制度查询、故障排查指导agent-tools工具调用与任务编排创建日程、发通知、查天气、多步骤任务agent-text文本处理翻译、摘要、润色、格式转换agent-gateway路由与鉴权统一入口、请求分发、限流这样拆分的好处很明显可以独立迭代哪个 Agent 挂了只影响对应技能不会一崩全崩。而且每个 Agent 的 Prompt 和工具列表都很精简模型判断起来更快、更准。代价也有就是部署的容器数量变多服务之间的通信与协调需要额外的设计。这正好是 Docker Compose 擅长解决的场景。1.3 我采用的方案五类容器组成一个最小服务矩阵最终服务器上跑的服务是这些agent-gateway统一 API 入口负责身份校验、意图路由agent-rag知识库问答接向量检索 大模型生成agent-tools工具调用型 Agent负责具体任务的执行链agent-text纯文本处理 Agent翻译、摘要、润色redis会话上下文存储 小型任务队列postgres业务数据持久化启用 pgvector 后兼任向量库这里有个省资源的经验向量检索没有单独再部署一个向量数据库而是直接复用了 postgres 的 pgvector 插件。小团队的数据量撑死几十万条向量pgvector 完全够用能省掉一整类运维负担。1.4 容器之间如何协作路由、上下文、队列各司其职这个设计里容器间协作有两条路径同步路径用户请求到网关网关判断意图后把请求转发给对应 Agent拿到结果后直接返回给用户。适合知识库问答、文本翻译这类即时的场景。异步路径网关先把任务丢进 Redis 队列Agent 处理完后再把结果写回 Redis用户通过任务 ID 查询结果。适合耗时较长的任务编排比如“帮我整理资料并生成周报”这种需要分多步执行的请求。两条路径各有用途不要一刀切全走同步。2. Docker Compose 核心配置网络、数据卷、健康检查与资源限制2.1 自定义网络内部服务不裸奔Compose 默认会给项目创建一个 bridge 网络但我建议显式声明一个自定义网络并且把网络分成“内部”和“外部”两层。networks: agenthub-net: driver: bridge internal: false agenthub-internal: driver: bridge internal: true内部网络设为internal: true后接入这个网络的容器没有外网出口只能和同网络的容器通信。数据库、Redis 这类中间件放进内部网络需要对外提供服务的网关放在默认网络并且只暴露必要的端口。这样做的好处是即使某个 Agent 容器被攻破攻击者也无法直接从容器里访问数据库或向公网发起请求横向移动的成本变高了。容器之间调用时直接使用服务名作为主机名比如网关访问 agent-rag 就是http://agent-rag:8081。这个地址是 compose 内置 DNS 动态解析的容器 IP 变了也不影响。2.2 数据卷挂载什么该持久化什么不该刚开始图省事postgres 的数据直接写在容器里结果重新创建容器后数据全没了。数据卷的问题迟早会遇上不如一开始就规划好。services: postgres: image: pgvector/pgvector:pg16 volumes: - pg_data:/var/lib/postgresql/data - ./init-scripts:/docker-entrypoint-initdb.d:ro redis: image: redis:7-alpine volumes: - redis_data:/data command: redis-server --appendonly yes volumes: pg_data: redis_data:两个原则业务数据必须用命名卷pg_data、redis_data 这种。容器随便删数据不会丢。配置和初始化脚本用 bind mount比如./init-scripts:/docker-entrypoint-initdb.d:ro因为这类内容需要经常改直接映射宿主机文件更方便。注意别把整个项目代码目录直接挂进容器。我见过同事图方便把宿主机代码目录挂载到容器里结果日志文件和依赖文件也一起挂进去后续排查问题的时候很难判断容器里跑的到底是哪个版本。2.3 健康检查解决服务启动顺序问题的关键很多第一次用 Compose 的人都会踩depends_on的坑觉得写了depends_on就一定能等上一个服务启动好。实际上默认的depends_on只能保证“启动顺序”不能保证“服务就绪”。postgres 容器启动了不代表它已经能接受连接。正确姿势是用健康检查services: postgres: healthcheck: test: [CMD-SHELL, pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}] interval: 5s timeout: 5s retries: 10 start_period: 10s agent-rag: depends_on: postgres: condition: service_healthy redis: condition: service_healthy再加上 start_period 参数。比如 postgres 冷启动如果做了数据恢复可能 30 秒内都起不来。如果 start_period 设太短健康检查会把启动过程误判为失败直接杀掉容器重启反而更慢。Compose 从 v2 开始支持condition: service_healthy用起来很顺手。建议所有依赖数据库、Redis、向量库的 Agent 服务都加上这个前置条件。2.4 日志与资源限制防止小机器被拖垮小团队用的通常就是一两台云主机内存有限。如果不做限制任何一个 Agent 出现内存泄漏都可能把主机拖到无响应。我的建议是每个服务都做资源限制services: agent-tools: build: context: ./services/agent-tools mem_limit: 1g cpus: 1.0 logging: driver: json-file options: max-size: 10m max-file: 3mem_limit: 1g意思是这个容器最多用 1GB 内存超了会被 OOM Killer 杀掉。cpus: 1.0限制 CPU 占用不超过一个核心。很多小团队的主机就 4 核 8G不做限制的话两个 Agent 同时高频处理请求机器直接卡死。日志轮转也是必须的。有次一个 Agent 异常循环打印日志两天干了 30GB 磁盘最后整个服务挂了。配置了max-size: 10m和max-file: 3之后单服务最多保留 30MB 日志彻底杜绝了这个隐患。3. Agent 多技能的具体实现路由表、工具注册与上下文共享3.1 网关路由意图判断加路径分流两层保障多技能 Agent 服务最重要的一环就是“请求进来之后到底交给谁处理”。我的做法是两层路由。第一层如果调用方能明确指定能力类型直接在 API 路径里声明。比如/api/v1/rag、/api/v1/tool、/api/v1/text最简单也最稳。第二层如果调用方不带路径网关会用一个小模型做意图判断识别请求属于哪类技能后再转发给对应的 Agent。为了省成本意图判断用一个较小较快的模型不需要很强的推理能力。完整流程是用户请求 - 网关鉴权 - 路径匹配若有或 意图识别若无 - 按路由表转发 - 等待 Agent 返回 - 统一响应格式这层设计一定要做否则用户怎么知道该调那个接口。现在团队内部的所有应用都是统一对接网关根本不关心底层有多少个 Agent。3.2 工具注册中心新技能接入不用改网关这里有个核心经验网关不要写死每个 Agent 的路由否则每次新增技能、改个 Agent 名称都要动网关代码。我用了一个简单的“工具注册中心”模式每个 Agent 在启动的时候把自己的技能信息注册到 Redis# services/agent-tools/register.py import redis import json import os r redis.Redis.from_url(os.getenv(REDIS_URL)) agent_info { name: agent-tools, skills: [create_task, send_notification, query_calendar], endpoint: http://agent-tools:8082, description: 负责日程任务编排与工具调用 } r.hset(agent:registry, agent-tools, json.dumps(agent_info))网关启动和运行时定期从 Redis 拉取整个注册表动态生成路由表# services/agent-gateway/router.py import redis import json r redis.Redis.from_url(os.getenv(REDIS_URL)) agents r.hgetall(agent:registry) for name, info in agents.items(): info json.loads(info) # 动态生成路由规则 route_table[name] info[endpoint]这样做的好处是新增一个 Agent 时只需要部署新容器它自己会注册到 Redis网关下次刷新就能感知不用改任何网关代码。3.3 上下文与任务队列Redis 的多角色玩法Redis 在这个架构里承担了三个角色价值被发挥得比较充分。第一个角色是上下文存储。多轮对话里每个会话的短时记忆放在 RedisTTL 设置为一小时。同一会话的消息会带上会话 ID网关会先把历史消息取出来拼进当前 Agent 的 Prompt。第二个角色是工具调用结果的缓存。比如知识库问答中相同问题的向量查询有很强的重复性我设置了一个简单的缓存策略以“问题知识库ID”为 key结果缓存 30 分钟命中率相当可观。大模型调用成本降了不少。第三个角色是异步任务队列。耗时任务例如“对一份长文档做分段摘要并生成报告”网关先把任务写入 Redis 列表agent-tools 通过BLPOP消费任务处理完成后把结果写回 Redis并设置一个状态标识。调用方通过/api/v1/task/{task_id}查询结果。# Redis Stream 更合适的话也可以改用 Stream但列表简单够用 RPUSH task:queue {task_id: xxx, type: doc_summary, payload: {...}}这套基于 Redis 的轻量队列虽然做不到消息可靠性保证、死信队列这些高级特性但对小团队的内部场景来说足够。真到了要上专业消息队列的地步那就是系统确实长大到需要重构成微服务的阶段了。4. 一键启停与日常维护小团队也要有规范的运维姿势4.1 启动、停止、更新三条命令解决Docker Compose 对小团队最大的价值就是“运维降级”。不需要专职运维开发同学三条命令就能搞定日常操作。# 启动全部服务 docker compose up -d # 启动指定服务并跟踪日志 docker compose up -d agent-tools docker compose logs -f agent-tools # 停止所有服务 docker compose down # 关闭全部服务同时清空未使用的网络保留数据卷 docker compose down --remove-orphans注意docker compose down默认不会删除数据卷业务数据还在。想要连同数据卷一起删干净需要加-v。但这条命令慎用一旦执行数据库就彻底清空。我在测试环境误操作过一回从此把生产环境的 compose 文件里把-v这个选项从任何脚本里拿掉了。4.2 .env 环境变量管理敏感信息不写进镜像整个项目里最不该写死的就是 API Key、数据库密码。如果把它们直接写进 Dockerfile 或代码里镜像一旦被推送到镜像仓库等于把密钥公开了。我的做法是统一用.env文件管理项目级环境变量# .env 示例 LLM_API_BASEhttps://api.example.com/v1 LLM_API_KEYsk-xxxx POSTGRES_USERagent_admin POSTGRES_PASSWORDchange_me POSTGRES_DBagenthub REDIS_PASSWORDredis_pass EMBEDDING_MODELtext-embedding-3-small RAG_MODELgpt-4o-mini TOOL_MODELgpt-4o TEXT_MODELgpt-4o-miniCompose 文件里通过${VAR}方式引用services: agent-rag: environment: LLM_API_BASE: ${LLM_API_BASE} LLM_API_KEY: ${LLM_API_KEY} DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}postgres:5432/${POSTGRES_DB}.gitignore 里一定要把.env排除掉仓库里只放一个.env.example模板。团队成员 clone 之后复制一份模板改成自己的配置就能跑了。4.3 日常监控看日志和健康状态就够了小团队不需要上监控系统至少前中期不需要。我常用的诊断三连# 查看当前所有服务状态 docker compose ps # 实时看某个服务日志 docker compose logs -f agent-gateway # 直接进入容器排查 docker compose exec agent-tools sh如果想知道容器里某个 Python 进程占了多少内存可以在宿主机上用docker statsdocker stats --format table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}这套组合已经能覆盖 90% 的日常问题。4.4 资源规划按实例给一个参考标准有个团队问过我这套服务到底需要多大的机器。这里给出实测参考服务参考内存说明agent-gateway256MB无状态网关很轻agent-rag512MB加载向量检索模型 LLM 调用agent-tools512MB工具调用链逻辑较重agent-text384MB纯文本处理redis128MB缓存和队列postgres(pgvector)512MB数据库 向量索引合计大约 2.3GB加上系统基础占用买 4GB 内存的主机可以跑起来但生产环境我建议上 8GB留足余量。模型推理不在这台机器上进行大模型调用走的是第三方 API所以 CPU 压力反而不算大2 核到 4 核都行。5. 实操中踩过的坑与排查清单5.1 启动顺序导致的连接报错第一次部署时agent-rag 启动后一直报数据库连接失败。原因是容器启动顺序没有真正卡在“数据库就绪”上。虽然写了depends_on但默认只等容器起来了不等 postgres 完成初始化。后面在所有依赖中间件的服务上补了condition: service_healthy问题就消失了。如果你还在用旧版 Compose不认service_healthy可以在 Agent 启动代码里加一个简单的重试循环连接失败后退避重试最多试 10 次也能达到类似效果。5.2 日志撑爆磁盘前面提到过有次一个 Agent 死循环打印两天打满 30GB。排查之后才发现除了日志轮转没配还有一个隐藏问题——Python 的日志库默认不会自动清理旧日志文件。解决办法是在 compose 里配置好日志驱动参数。默认的 json-file 驱动就够用不用额外装插件。另外还可以在宿主机上挂一个定时任务定期清理一定时间之前的容器日志文件双保险。5.3 容器内访问宿主机服务开发阶段Agent 经常需要访问本机启动的临时服务比如本地启动的模型服务。在容器里访问宿主机不能用localhost因为localhost指的是容器自己。正确做法有两种使用 Docker DesktopmacOS/Windows时宿主机地址是host.docker.internal。Linux 服务器上可以在 compose 里加extra_hosts: - host.docker.internal:host-gateway。有了这个映射开发环境里 Agent 就能访问宿主机的调试服务了。5.4 更新后路由失效有次升级 agent-tools修改了端口号结果网关还是往旧端口转发。排查后发现是工具注册表里存的是旧地址新容器启动没成功注册。后面我在启动脚本里加了一步新 Agent 启动时先删除注册表里自己的旧记录再写入新记录。同时网关每次刷新路由表时会先检测对应 Agent 的健康状态不健康就不加进路由表。5.5 排查命令速查表症状快速排查命令常见原因服务一直重启docker compose ps看 STATUS健康检查失败看日志确认请求超时docker compose logs -f 服务名Agent 调用模型 API 慢可调超时时间数据库连接失败docker compose exec postgres pg_isready密码错误或未完成初始化502 / 连接拒绝docker compose ps确认目标服务状态路由表指向错误地址磁盘满df -h宿主机查看日志没轮转 / 数据卷膨胀容器间网络不通docker compose exec gateway ping agent-rag不在同一个自定义网络这个表是我踩坑踩出来的不一定覆盖所有情况但常见问题基本都能对上号。最后再分享一个小技巧这套服务跑顺之后我又加了一个“技能自检”脚本。每天凌晨起来依次向每个 Agent 发一个最简单的测试请求确认返回是否正常。如果有失败就自动把异常信息推到团队通知群。这样很多时候我们自己还没收到用户反馈问题就已经被发现了。小团队做 AI 服务最重要的不是架构有多先进而是别让运维成为瓶颈。Docker Compose 这套方案上手成本低迁移成本也低将来如果服务规模真的大到单机撑不住再平滑过渡到集群编排也不算折腾。如果你也在纠结怎么把多技能 Agent 落地不妨从我这条路线开始先把服务跑起来再逐步优化。
RELATED

相关推荐

C# WinForm集成Halcon图像控件:解决坐标偏移与缩放卡顿

C# WinForm集成Halcon图像控件:解决坐标偏移与缩放卡顿

简介:本资源是一个基于C#与Halcon联合开发的WinForm图像交互演示项目,面向计算机视觉初学者、工业检测应用开发者及自动化软件工程师,解决图像显示控件中基础交互功能(拖拽、缩放)与实时像素信息反馈缺失的实践痛点。项…

📅 2026/10/21 2:45:48
命题逻辑重言式判别系统:从真值表到AST的工程化实现

命题逻辑重言式判别系统:从真值表到AST的工程化实现

简介:本资源是一份面向计算机专业本科生的数据结构课程设计实践材料,聚焦重言式判别程序的完整实现与原理剖析,解决布尔表达式逻辑恒真性自动验证这一典型算法问题。压缩包共4个文件(2个DOC文档、2个C源码)&#xff0c…

📅 2026/10/21 2:45:48
Cocos Creator拖拽排序列表:交互状态机实现指南

Cocos Creator拖拽排序列表:交互状态机实现指南

简介:本资源是一份面向Cocos Creator初学者与中级开发者的拖拽排序列表实战项目,聚焦游戏UI与工具类应用中常见的用户自定义排序需求。项目完整实现了拖拽移动、实时排序存储与界面状态同步三大核心功能,涵盖事件监听(onDragStart…

📅 2026/10/21 2:45:48
MORE NEWS

更多资讯

📰

阿里EDAS示例工程上云实战:本地启动、部署配置与灰度避坑指南

简介:这份资源是阿里EDAS(企业级分布式应用服务平台)的入门示例工程,面向希望学习微服务架构与云端应用部署的Java开发者,尤其适合刚接触EDAS、需要动手实践的中初级人员。压缩包共15个文件,以8个xml配置文…

📰

Redis哨兵实战:高可用架构部署、故障转移与踩坑经验

Redis 哨兵这套东西,我最早接触是在一个电商促销活动前夜。当时主从架构跑得好好的,结果主节点凌晨挂了,运维手动切了四十分钟,老板在群里连发十几条消息。从那以后我就把哨兵当成了高可用方案的标配。这篇文章不讲教科书定义&…

📰

C# WinForm Panel 边框颜色改不动?三种自绘方案一次讲清

简介:面向C# WinForm开发者的Panel控件美化方案,解决默认边框颜色与宽度不可直接调整的问题。资源通过继承Panel并重写OnPaint方法,以Graphics绘制自定义边框,提供BorderColor与BorderWidth两个扩展属性,示例代码清晰易…

📰

多模型接入太难了?用 litellm 统一 OpenAI 兼容格式,打造 LLM 网关

最近在处理一个多模型接入的需求时,我最大的感受是:真正让人崩溃的其实不是模型效果,而是调用方式五花八门。同样是聊天补全,有的需要messages数组,有的偏要prompt字符串;同样是工具调用,有的叫…

📰

2013-2024年 中国各省级数字经济指数数据(+代码+文献)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

Unity渲染纹理Camera.targetTexture全解析:从原理到小地图/监控/截图实战

1. 先说清楚:targetTexture到底是干什么的做Unity开发的人,早晚都会遇到摄像机目标渲染纹理(RenderTexture Camera.targetTexture)这个组合。我第一次接触它,是做一个设备监控类项目,需要在主界面里嵌一个…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬