ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用Python+MCP+LLM手搓一个AI运维助手:从架构到排坑实录

用Python+MCP+LLM手搓一个AI运维助手:从架构到排坑实录 手头管着十几台服务器日常无非是“看监控、查日志、重启服务、被同事叫醒处理告警”。直到某天凌晨我发现自己在重复三年前刚入行时就在做的事——SSH登录、敲top、翻journalctl、甩systemctl restart。那一刻我意识到与其继续当一个“带键盘的人形监控脚本”不如把这件事彻底自动化。这年头正好赶上 MCPModel Context Protocol模型上下文协议概念爆发——它把大模型LLM和外部工具用一种标准方式连起来让 AI 不再只会聊天而是能真正动手干活。我用 Python 把这个协议和开源 LLM 组合到一起手搓了一个服务器运维助手让它自己看负载、自己查日志、自己判断故障、自己执行修复干完活还要跟我汇报“为什么这么干”。这篇博文就是完整的折腾记录从架构设计、代码实现到排坑实录照着做你也能搭一套出来。1. 为什么是 Python MCP LLM先想清楚再动手1.1 从 Function Calling 到 MCP一次痛苦的协议演进一年前我就在尝试用函数调用Function Calling给 LLM 扩展能力思路很简单告诉模型“你可以调用这些函数参数长这样”模型输出 JSON代码解析后执行。听起来很美好实际维护起来全是坑OpenAI 有自己的一套 schemaClaude 有另一套国产模型又各不相同每个模型升级一次我的配置就废一次。更麻烦的是团队里如果有两个服务想同时接入 LLM工具代码根本无法共享只能复制粘贴再各改各的。MCP 解决的就是这个“重复造轮子”的问题。它的核心模型可以理解成给 AI 世界定了一套标准的“USB 接口协议”工具提供方MCP Server把能力包装成标准化的工具列表和参数定义客户端MCP Client负责和 LLM 交互并路由调用请求两边只认协议不认厂商。我写一个 MCP Server 暴露几个运维工具任何支持 MCP 的客户端——Claude Desktop、VS Code 插件甚至我自己用 Python 写的控制台——都能直接复用再不用为一个模型写一套适配代码。1.2 整体架构没有大脑的服务器只是一堆零件这套助手的架构可以用一条链路说清楚你 → MCP Client → LLM → MCP Server → 工具集 → 服务器。真正干活的部分是 MCP Server它里面挂着一批运维工具系统状态读取、日志检索、服务管理、命令执行、告警收敛。LLM 是“决策大脑”它根据你的自然语言请求和当前工具返回的数据决定下一步调哪个工具、怎么调MCP Client 是“信使”专门负责在 LLM 和 Server 之间传话。这个设计的关键在于“把决定权交给模型但把执行边界焊死”。LLM 只负责决策是否调用某个工具、生成调用参数真正吃吐数据的还是代码——我不会让模型直接生成 Shell 命令去碰生产环境所有命令都经过白名单校验和参数过滤超时、审计、熔断全都有兜底。这样既享受了 LLM 的推理能力又不至于被模型的幻觉带进沟里。1.3 它能帮我解决哪些问题在完全没有“智能”之前我的运维日常是流程化的苦力活有了这套助手之后它的价值主要体现在这四件事上告警处理提速凌晨收到 CPU 过高告警不用爬起来先让助手自己登录排查看是负载均衡问题还是代码 bug再决定要不要重启。日志问答化从“给我 grep 一下这个时间段的报错”变成“这个时间段服务为什么重启了”LLM 会自己组合日志检索工具去翻证据链。巡检报告自动生成每天早晨让助手把各组件的健康度、资源趋势、潜在风险汇总成一份简报不用再手动筛监控面板。知识沉淀每次故障处理完让助手把排查过程整理成 Markdown 文档归档后辈接手时能少走一大截弯路。这套系统适合谁像我一样的个人开发者、小团队运维、SRE 新手手里有零散服务器的场景最合适。它不是一个能直接拿来卖钱的产品但能实打实帮你从重复劳动里解放出来。2. 核心组件选型参数背后的真实考量2.1 LLM 选型对比按场景选脑子LLM 是这套系统里唯一带“智力”的部分选型直接决定了助手是靠谱还是人工智障。我实际测过几类方案把关键对比整理成了下面这张表方案推理能力函数调用稳定度数据私密性成本适合场景Claude 3.5/4 系列最强高低数据出域较高复杂故障推理可接受数据外送GPT-4o / o3 系列较强高低较高同上DeepSeek-V3 / Qwen-Max API强中高低但国内链路低高性价比默认方案Qwen-72B / DeepSeek-R1 本地部署中上中高硬件成本隐私敏感场景Qwen-7B / Llama-3.1-8B 本地小模型中等中高极低测试开发环境或可接受较弱势场景我个人最终的选择是开发调试用 Qwen-7B 本地小模型免费、随便折腾生产巡检用 DeepSeek-V3 API性价比高复杂故障复盘的“智囊”用 Claude 3.5 Sonnet推理稳但只在数据脱敏后送出去。如果你没有数据私密的硬约束直接用 DeepSeek 或 Qwen 的 API 就足够跑起来。2.2 MCP SDK官方的和社区的你都要知道Python 生态里接入 MCP 有两条路一是官方提供的mcpPython SDK二是社区封装的各种框架。我一开始用的是原生的mcp.server.fastmcp它是官方推出的一套高层 API用装饰器就能把函数暴露成工具写起来很爽from mcp.server.fastmcp import FastMCP mcp FastMCP(ops-agent) mcp.tool() def check_cpu_load(threshold: float 0.8) - str: 读取当前系统 CPU 平均负载返回是否超过阈值。 import psutil load psutil.getloadavg()[0] return f当前 1 分钟负载: {load}, 阈值: {threshold}, 状态: {超载 if load threshold else 正常}写完之后反射出来的 JSON Schema 会自动带进 MCP 协议里LLM 读到描述就会知道自己能用什么、参数怎么传。除了这条官方路线社区还有fastmcp之外的一些封装比如基于 pydantic 的代码生成工具、基于 WebSocket 的 MCP 网关但它们本质上没有跳出官方协议我建议新人直接基于官方 SDK 上手坑少、文档全、遇到问题还能翻源码。2.3 服务器端工具链psutil 够用但别止于此工具集的实现可以纯靠 Python 标准库 psutil但真实运维场景远远不止看 CPU。我把工具分成了四个层级系统巡检类psutil封装 CPU、内存、磁盘、网络 IO、进程列表uptime查看系统负载。日志检索类封装journalctl、grep和tail带时间范围和关键字过滤返回最近 N 条。服务管理类封装systemctl和supervisorctl的状态查询、启停、重启操作。命令执行类通用 Shell 执行但做了命令白名单校验只允许预先登记过的安全指令。这里必须强调一点不要把通用 shell 任意执行暴露给 LLM。我在第一版就踩过坑——让模型用pkill关掉一个服务结果正则匹配太宽差点把另一个核心进程带走。后来所有命令执行都改成“先查询白名单中匹配项再让用户确认参数”整体稳定性提升了一个量级。3. 手搓全过程从零到能跑每一步都要能抄3.1 30 分钟搭好 MCP Server 骨架先把环境准备好。我是在 Ubuntu 22.04 Python 3.10 上做的依赖只有几个一套命令搞定python3 -m venv .venv source .venv/bin/activate pip install mcp psutil然后用fastmcp建一个最简 Server。这一步你就已经拥有一个“能被 LLM 驱动”的工具服务了。# ops_server.py import json import shlex import psutil from mcp.server.fastmcp import FastMCP mcp FastMCP(ops-agent) mcp.tool() def get_system_summary() - str: 获取系统整体状态CPU、内存、磁盘、负载、开机时间。 cpu_percent psutil.cpu_percent(interval1) mem psutil.virtual_memory() disk psutil.disk_usage(/) load psutil.getloadavg() return json.dumps({ cpu_percent: cpu_percent, memory_percent: mem.percent, disk_percent: disk.percent, load_avg: {1min: load[0], 5min: load[1], 15min: load[2]}, }, ensure_asciiFalse) mcp.tool() def get_process_top(n: int 10) - str: 获取当前 CPU 占用最高的 n 个进程默认 10 个。 procs [] for p in psutil.process_iter([pid, name, cpu_percent, memory_percent]): try: procs.append(p.info) except (psutil.NoSuchProcess, psutil.AccessDenied): continue procs.sort(keylambda x: x[cpu_percent], reverseTrue) return json.dumps(procs[:n], ensure_asciiFalse) mcp.tool() def run_systemctl(service: str, action: str) - str: 执行 systemctl 管理命令。action 只能为 status/start/stop/restart。 示例: run_systemctl(servicenginx, actionrestart) allowed {status, start, stop, restart} if action not in allowed: return f不允许的 action: {action}只能使用 {allowed} cmd fsystemctl {action} {shlex.quote(service)} return cmd注意run_systemctl这一步我没有真的执行只返回了命令字符串。为什么因为在开发阶段你需要先看模型的“意图”是否正确——它经常写错服务名或 action直接执行很容易出事故。把“决策”和“执行”分开是调试期的关键策略。最后跑起来python ops_server.py mcp.run(transportstdio)3.2 工具研发四条链路一个都不能少纯看状态的工具没有太多含金量真正有用的是“查询到问题 → 定位原因 → 给出结论”的组合链路。我一套一套拆给你们看。链路一状态快照与趋势判断只读当前的 CPU 数字意义不大关键是判断趋势。这里我加了一个“历史采样”机制每隔 5 分钟抓一次负载快照存到 SQLite 或本地 JSON 文件里。LLM 请求时返回的不只是一瞬间的值而是最近 6 小时的走势它才能判断“现在 80% 是在涨还是在跌”。实现上很简单用apscheduler定时抓或者干脆用cron跑一个collect_metrics.py写入一张时间序列表CREATE TABLE metrics ( ts TEXT PRIMARY KEY, cpu REAL, mem REAL, load1 REAL, disk_used REAL );链路二日志检索闭环日志是排查故障的第一现场。我用两个工具一个负责搜关键词支持时间范围一个负责抓取某个服务的最近journalctl -u xxx -n 100。这两个工具返回的都是原始文本但 LLM 真正厉害的一点是能从中提炼因果链——比如“13:41 报 OOM13:42 服务退出13:43 自动重启”它会把这三行自动串联成“内存耗尽导致进程崩溃被 systemd 拉起”。链路三服务生命周期管理前面的run_systemctl只返回命令这是安全的。但为了让整个流程自动化闭环我还加了一个“确认执行”接口LLM 先把要执行的命令和理由写在请求里只有我们本地 MCP Server 判断命令在白名单内才真实调用subprocess执行并把 stdout/stderr 回给模型。如果你想让整个闭环更自动化还可以接一个“人工确认”钩子比如通过企业微信或钉钉推一条审批消息。链路四告警收敛与自愈最让人头疼的是告警风暴——同一个服务挂了监控系统五分钟发一次消息。我在 Server 里写了一个简单的“故障聚合器”相同服务、相似报错在 30 分钟内只发一次告警并自动触发一次 LLM 排查。如果排查结果明确是内存泄漏且服务名在白名单内LLM 会主动给出重启建议如果重启后五分钟内再次告警系统会自动“熔断”——不再自动重启转而升级给人处理。这个“自愈但永远保留逃生舱”的思路是 AI 运维最容易被低估的工程实践。3.3 MCP Client 接入让 LLM 真正“看见”工具MCP Server 建好了但 LLM 要怎么连上来我推荐三套方案由易到难。方案一Claude Desktop 直接接入在 Claude Desktop 的配置文件claude_desktop_config.json里加一行{ mcpServers: { ops-agent: { command: python, args: [/绝对路径/ops_server.py] } } }重开客户端你就能在对话里直接输入“帮我看看刚才 1 分钟负载多少”Claude 会自动调用get_system_summary和get_process_top整个推理过程还会显示“正在使用工具”。这是体验最丝滑的一条路适合快速验证工具描述写得好不好。方案二自写 Python Client 走 MCP 协议如果你不想依赖闭源客户端可以用官方的mcp客户端库写一个极简 REPLimport anyio from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client async def main(): server_params StdioServerParameters( commandpython, args[ops_server.py], ) async with stdio_client(server_params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() tools await session.list_tools() for t in tools.tools: print(f工具: {t.name} - {t.description}) res await session.call_tool(get_system_summary, {}) print(结果:, res) anyio.run(main)这个客户端的意义在于你可以完全不用商业大模型的客户端而是把你选的任何 LLM包括本地小模型接入 MCP。它把你的系统从“依赖 Claude Desktop”解放成了“依赖任何 LLM API”自由度一下子高了很多。方案三完整对话式封装这是最接近真实产品形态的一种。我写了一个ops_chat.py先启动本地 LLM或调用 DeepSeek API每次用户输入先让 LLM 输出工具调用 JSON{ tool: get_process_top, args: {n: 5} }然后由代码调用 MCP 工具拿到结果再喂给 LLM 循环推演。这个过程中需要维护一个“会话上下文窗口”记录每次工具调用和结果LLM 才能在前序结果之上继续推演。比起前面两种方案这个封装能完全控制推理次数、工具调用上限和超时策略是真正适合生产级别的结构。4. 避坑实录我在真实环境里踩过的那些雷4.1 工具描述写得差LLM 再强也白搭这是我第一周最深刻的教训。最初我写的工具描述只有一句话“获取系统状态”然后 LLM 各种误解参数过 threshold 传字符串、把服务名写成 IP 地址。后来我老老实实按照“This tool does XParametersxxx means yyyReturnszzz format”的规范重写了一遍调用成功率从不到 50% 直接拉到 90% 以上。工具描述就是给 LLM 看的 API 文档你糊弄它它就糊弄你。4.2 命令执行失控差点带走半个集群前面已经提到过一次但这是我真正最恐慌的一刻必须单独拎出来讲一开始我以为 LLM 足够聪明直接允许它调subprocess跑任意命令。某次模拟演练我故意让它“重启所有内存占用超过 1G 的进程”它生成的pkill -f正则把java、nginx、mysql全部匹配了进去如果不是测试环境那就是一场生产事故。从那以后我的每一条命令都必须满足以下三个条件之一才允许执行在白名单内、参数必须通过shlex.quote、服务名必须存在于 systemd 单元列表。宁可让流程多一步人工确认绝不把裸命令执行权交给模型。4.3 LLM 的上下文窗口是“死亡陷阱”日志工具返回的是大文本动辄几万字符LLM 的上下文窗口有限一旦塞满模型就会“遗忘”前面的工具结果开始自说自话。我在这里做了三层处理一层是裁剪日志行数工具只返回最重要的前 20 行一层是摘要优先如果 LLM 需要看详细日志先让它链式调一个summarize_lines工具最后一层是上下文策略超过多少 token 就强制开启新会话。你可以用 token 计数库如tiktoken实时监控也可以用最笨的“字数切分”在测试环境先用起来。4.4 模型幻觉它可能会编造“看起来合理”的命令幻觉问题无解只能缓解。我见过它编造一个根本不存在的 systemd 服务名也见过它把--force参数拼在systemctl restart后面实际上 systemctl 根本没有这个参数。缓解办法是给工具加上严格的参数枚举验证服务名传进来先查一次 systemd 列表action 必须匹配status/start/stop/restart四选一。一旦验证失败MCP Server 必须返回明显错误信息而不是静默忽略。4.5 常见问题速查表现象原因解决办法LLM 不调用任何工具答非所问工具描述太抽象模型不知道能用重写描述加入具体示例参数调用总是报参数错误Pydantic Schema 与实际参数不匹配用tools.schema现场检查 JSON Schema本地模型输出 JSON 格式错乱小模型函数调用能力弱升级到 70B 级模型或加输出格式校验后重试工具执行超时导致会话中断日志检索文本太大了加timeout10并在工具内做截断MCP Server 启动后客户端连不上路径或 Python 环境不对先在命令行手动跑一遍 Server确认能正常启动重启服务后仍然异常systemd 服务依赖外部资源未就绪让 LLM 查看服务状态后二次诊断不要盲目重启5. 实战演练一次完整的 CPU 飙升自动排查为了让你更直观地理解整个闭环我写了一段模拟对话。这是我在测试环境真实跑过 20 多次之后的最佳效果。用户输入帮我看下为什么刚才 1 分钟负载特别高MCP 链路实际发生的事我先手动把一台测试机压了一个高负载进程然后触发对话。我的自写 Client 把用户输入交给 LLM。LLM 发现用户要“看负载”自动调用get_system_summary。工具返回{cpu_percent: 97, load_avg: {1min: 12.3, 5min: 8.5}, ...}。LLM 看到 1 分钟负载远高于 5 分钟判断“短时尖峰”于是调用get_process_top(n5)。工具返回[{pid: 3201, name: python3 stress.py, cpu_percent: 400, ...}]。LLM 给出结论并附上建议CPU 尖峰由 PID 3201stress.py导致该进程占用 4 核。建议观察 5 分钟如仍持续可终止该进程。用户点头确认LLM 才继续调用run_systemctl或者一个专门的kill_process白名单工具去清理。这一步的体验真的“上头”——从“发现故障”到“定位原因”只需要一轮对话过去我要自己敲至少五条命令。更妙的是整个过程里 LLM 还会主动拉住你“不建议立即 restart因为进程可能是压测任务建议先确认业务影响。”这种“能动手但不乱动”的克制恰好是 AI 运维最重要的工程素养。6. 进阶玩法与经验谈这玩意儿还能怎么浪6.1 引入“LLM as Judge”做自动化巡检“模型当评委”是个很香的进阶玩法。做法是让一个专门的评审 LLM 每天审视运维助手的排查日志和行动记录给它的推理质量打分并挑出错判、漏判和过度自信的地方。这样相当于给你的 AI 运维系统装了一个“AI 质检员”。我在prompt里写了一组评分细则是否依据前一环数据推理、是否给出可回溯证据、是否在操作前提示风险、是否在未知情况时主动求助。这些评分会定期回流成微调样本或规则补充让助手越用越准。6.2 多服务器纳管它能不能管 100 台单机版跑通后自然想扩展。我的做法是让 MCP Server 不再只连本机而是通过 SSH 或 agent 节点管理多台机器。你可以给 MCP 加一个“节点选择”参数nodeweb-01时工具内部登到对应主机执行命令。这里有个大坑SSH 会话延迟高LLM 很容易超时。我的优化是给每个节点加本地缓存队列状态查询走缓存命令执行才实时拉取同时把get_system_summary的响应压缩成一行短格式减少上下文开销。扩展到 100 台时最核心的不是你会不会写工具而是“工具返回的数据如何被 LLM 高效消费”——信息密度和格式统一比工具数量重要得多。6.3 容错与自愈AI 运维最后一道防线有一件事我印象特别深刻某次调试中我故意让 MCP Server 在调用中途崩溃文件处置不当抛异常结果 LLM 自动嗅探到“工具调用失败”信号居然编造了一个不存在的“重试成功”结果反馈给我。这件事让我明白LLM 会本能地“圆谎”因为它被训练成要博取认同。从此我在工具抛错时一律返回结构化错误码并强制要求 LLM 必须描述错误码含义后重新决策。这套容错设计的原则很简单异常一旦出现先阻断执行再让模型说清楚“我看到什么、我要做什么、依据是什么”不满足这三条就不放行下一步。6.4 最后分享几个真实经验如果你要复刻这套系统我最大的建议是别想着一步到位。先跑通“人工发消息 → 看工具描述 → 手动调用工具 → 把结果贴给模型”的最初形态再慢慢加自动化。我在测试期写过一个“傻瓜模式”就是不真正执行任何命令只打印 LLM 认为应该执行的命令跑了整整三天把所有可能触发危险的情况都暴露出来才开始让它在测试环境放行真实命令。靠着一级一级放宽权限现在它已经能独立完成绝大部分巡检和基础故障自愈。回过头来看这件事最有价值的地方也许不是“运维自动化”本身而是它让你真正摸清了 MCP 和 LLM 的能力边界。以前我会怀疑“AI 能不能取代运维”经过这一年的实践我的答案是它能取代那些“重复的、有规律的、可描述的”工作但它既需要充分的工具支持也需要人的工程兜底。把这套 MCP 思路从运维挪到别的领域——数据库管理、CI/CD 流水线、甚至客服工单自动分类——你会发现过去所有依赖 API 调用的场景都能用同样一套架构套进去让 LLM 变成真正的“数字员工”而不是“聊天机器人”。这就是我这一整版折腾下来最核心的收获先把工具交给模型再给世界加上一点永不关闭的保险丝。
返回列表