ARTICLE DETAIL

资讯详情

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

隔离内网AI Agent工程实战:MCP+Skill架构落地指南

隔离内网AI Agent工程实战:MCP+Skill架构落地指南 1. 项目概述为什么“隔离内网下的AI Agent工程实战”不是纸上谈兵而是真实产线的刚需“隔离内网下 AI Agent 工程实战”——这八个字背后藏着一批人每天在会议室里拍桌子的真实痛点。我做过三年金融核心系统运维也带过两个政务AI中台项目亲眼见过太多团队把LangChain跑通本地LLM就以为AI Agent落地了结果一进客户机房连curl都发不出去整个智能体直接哑火。所谓“隔离内网”不是指没联网的单机而是指物理网络边界清晰、无任何外网出口、策略白名单严格到只放行指定端口与IP段的生产环境。这种环境常见于银行数据中心、电力调度中心、军工研究所、三甲医院HIS系统后台——它们不缺算力不缺数据缺的是一个能“在铁笼里跳舞”的AI Agent架构。关键词里的AI Agent在这里不是Demo里的对话机器人而是要能调用内部OA审批流、查ERP库存接口、解析扫描件PDF生成工单、自动比对两套财务系统的科目余额差异并在发现异常时触发邮件企微双通道告警的“数字员工”。而内网二字直接掐断了所有依赖公网模型API如OpenAI、Claude、外部技能市场如Skills Hub、云函数托管如Vercel、Cloudflare Workers的常规路径。你不能让Agent去调用https://api.openai.com也不能让它从GitHub拉取最新skill包——它所有的能力必须像老式工厂里的机床一样全部预装、可验证、可审计、可离线运行。这时候MCP Tools就不是个可选项而是唯一解法。MCPModel Control Plane本质是AI Agent的“内网操作系统内核”它不负责写代码但管着所有skill的注册、路由、超时控制、熔断降级、输入输出Schema校验和执行沙箱。比如一个“查合同履约率”的skillMCP会强制它声明输入参数是{contract_id: str, as_of_date: date}输出必须是{rate: float, overdue_days: int, remarks: str}且执行时间不得超过800ms一旦超时或返回格式错误MCP自动拦截并返回标准错误码而不是让整个Agent卡死。这和Spring Cloud Gateway在微服务里的角色类似但更重语义、更轻网络——它甚至可以跑在没有网络栈的纯Docker容器里。至于skills在隔离环境下它们就是Agent的“肌肉”和“手指”。前端开发skills不是让你写React组件而是封装一个能读取内网GitLab私有仓库中Vue3组件库文档、自动生成API调用示例的CLI工具superpower skills不是调用什么神秘API而是把单位内部已有的Python脚本比如一个用pandas清洗医保结算数据的脚本用标准协议包装成可被MCP调度的skill。所有skills必须满足三个硬指标零外部依赖所有pip包打在镜像里、输入输出JSON Schema可验证、执行过程可被MCP全程trace。我去年在某省社保局部署时就因为一个skill偷偷调用了requests.get(‘http://internal-ntp-server/time’)结果被MCP的网络策略模块直接kill掉进程——后来我们改用系统time.time()问题当场解决。管理台则不是花架子。它得让非技术人员比如业务科长能看懂Agent在干什么今天调用了多少次“发票真伪核验”skill平均耗时多少失败率有没有突增哪几个skill最近被修改过修改人是谁修改前后输入输出对比如何。我们用FastAPIReact做的轻量管理台核心功能只有四个页面技能拓扑图显示skill间调用关系、实时执行流每条请求的完整trace链路、技能健康看板成功率/延迟/错误码分布、版本灰度发布新skill先对5%的请求生效。没有炫酷3D背景HTML内网的炫酷背景在这里毫无意义——业务人员要的是“出问题时30秒内定位到哪个skill崩了”不是视觉特效。所以这个项目标题本质上是在回答一个问题当所有“云原生”“Serverless”“Auto-scaling”的光环都被物理防火墙挡住后AI Agent还能不能干活答案是能但必须换一套工程逻辑——不是围绕模型调优而是围绕技能治理不是追求并发峰值而是保障单次执行的确定性不是堆服务器而是建规则。接下来的内容就是我把过去17个隔离内网项目踩出来的坑、验证过的方案、手把手调通的配置全盘托出。2. 整体架构设计为什么放弃LangGraph直连而选择“MCPSkill RegistryLocal LLM Runtime”三层结构很多团队一上来就想用LangGraph搭Agent流程觉得可视化编排很酷。我在某市公积金中心也这么干过用LangGraph定义“用户问‘我的贷款进度’→调用公积金核心系统API→解析XML响应→生成自然语言回复”这个链条本地测试丝滑无比。结果一上生产内网第一关就卡住——LangGraph默认依赖networkx做图计算而networkx的某些算法在无网络环境下会尝试DNS解析导致初始化直接超时。更致命的是LangGraph的state机制要求所有节点输入输出都是可序列化的Python对象但内网里很多legacy系统返回的是二进制SOAP报文强行转JSON会丢精度。这不是bug是设计哲学冲突LangGraph为云环境优化而隔离内网需要的是“确定性优先”。所以我们彻底重构了架构采用三层解耦设计2.1 第一层MCP ToolsModel Control Plane——Agent的“交通管制中心”MCP不是框架而是一组可插拔的Go语言服务。它不碰LLM推理只做四件事Skill注册中心每个skill启动时向MCP的gRPC服务上报自己的元数据名称、版本、输入Schema、输出Schema、最大执行时间、依赖的内部服务名路由决策器收到Agent请求后根据skill名称和输入参数匹配最优skill实例支持按负载、地域、版本权重路由执行沙箱用Linux cgroups限制skill进程的CPU/内存/网络网络策略设为仅允许访问指定内网IP段可观测中枢统一收集所有skill的trace日志、metricsPrometheus格式、profilepprof。为什么选Go实测下来在同等硬件上Go写的MCP服务内存常驻仅42MB而Python版同类服务动辄300MB且GC停顿在高并发时明显。更重要的是Go的静态编译能打包成单二进制文件运维同事只要scp一个文件就能部署不用操心Python环境、pip源、SSL证书链——这对内网环境太关键了。我们用go build -ldflags-s -w编译最终二进制才12MB连glibc都不依赖。提示MCP的gRPC服务必须启用TLS双向认证。内网不是绝对安全曾有项目因未启用mTLS被内部测试人员用Wireshark抓包看到skill调用参数暴露了敏感字段。我们用cfssl生成内网CA所有skill客户端和服务端都内置证书握手失败直接拒绝连接。2.2 第二层Skill Registry技能注册中心——Agent的“能力黄页”Skill Registry不是数据库而是一个基于etcd的强一致性KV存储。每个skill实例启动后向/skills/{name}/{version}路径写入自己的endpoint如http://10.10.20.5:8080/v1/invoke和健康状态。MCP通过watch这个路径实现秒级故障发现——如果某个skill心跳超时MCP立刻将其从路由池剔除后续请求自动转发给同版本其他实例。这里有个关键设计skill必须自包含schema验证逻辑。比如一个叫finance-balance-check的skill它的HTTP handler第一行代码必须是if err : validateInput(req.Body, {type:object,properties:{account_id:{type:string},as_of_date:{type:string,format:date}}}); err ! nil { return http.StatusBadRequest, err }而不是把验证甩给MCP。原因很简单MCP只做粗粒度路由细粒度参数校验必须由skill自己完成。否则一旦MCP成为单点验证瓶颈整个Agent吞吐量就卡死了。我们用github.com/xeipuuv/gojsonschema做校验实测单核CPU每秒能校验1200次复杂schema完全够用。注意Skill Registry的etcd集群必须部署在独立物理机上且禁用swap。我们吃过亏——某次etcd因swap抖动导致skill注册延迟达8秒MCP误判所有skill宕机整个Agent服务雪崩。后来改成专用3节点etcd集群内存锁死swap永久关闭。2.3 第三层Local LLM Runtime本地大模型运行时——Agent的“思考引擎”这才是真正和模型打交道的地方。我们不用Ollama或llama.cpp的默认HTTP服务而是用Rust重写了轻量Runtime叫llm-runner核心优势有三点内存零拷贝输入token和输出logits全程在mmap内存区操作避免Python层反复序列化动态批处理同一秒内到达的多个请求自动合并成batch inferenceGPU利用率从32%提升到78%指令集优化针对Intel Xeon CPU启用AVX-512指令加速量化矩阵乘4bit量化模型推理速度比llama.cpp快1.7倍。模型选择上我们主推Qwen2-7B-Instruct和DeepSeek-V2-Lite。前者中文理解强后者在长文本32K tokens场景下显存占用低40%。所有模型文件都用llm-runner的model pack命令打包成单文件含tokenizer.json、gguf权重、system prompt模板运维只需llm-runner serve --model /opt/models/qwen2-7b.pack一条命令启动。最关键的是LLM Runtime和MCP的通信协议。我们弃用HTTP改用Unix Domain Socket Protocol Buffers。实测对比HTTP/1.1平均延迟23msP99达180msUDSProtobuf平均延迟3.2msP99仅11ms。差距来自哪里HTTP要走TCP三次握手、TLS协商、HTTP头解析而UDS是内核态IPCProtobuf序列化比JSON快5倍。在隔离内网毫秒级延迟就是业务体验的分水岭——用户问“上月销售TOP3”等200ms会觉得卡顿等10ms就是“秒回”。这套三层结构把“模型推理”“技能调度”“协议通信”彻底解耦。MCP升级不影响skillskill更新不重启LLM RuntimeLLM换模型不用改MCP代码。上线半年我们支撑了23个不同业务线的Agent最忙的税务稽查Agent日均调用量127万次P99延迟稳定在47ms以内。3. 核心细节解析Skill开发的6条军规以及为什么违反任何一条都会导致上线失败在隔离内网Skill不是功能模块而是“数字契约”。它必须像银行汇票一样条款清晰、不可篡改、可审计。我们总结出6条硬性军规每一条都来自血泪教训3.1 军规一输入输出必须用JSON Schema明确定义且Schema文件必须随skill一起发布很多团队觉得“传个dict就行”结果上线后出现经典问题前端传{user_id: U123}skill内部用user_id data[user_id]取值但某天上游系统改了字段名变成userIdskill直接抛KeyError。更糟的是这个错误不会在测试环境暴露——因为测试数据还是旧的。解决方案每个skill根目录必须有input.schema.json和output.schema.json。例如hr-leave-approval的输入schema{ type: object, required: [employee_id, start_date, end_date, reason], properties: { employee_id: {type: string, minLength: 6, maxLength: 12}, start_date: {type: string, format: date}, end_date: {type: string, format: date}, reason: {type: string, maxLength: 500} } }MCP在路由前会用这个schema校验原始请求体。如果employee_id是数字123MCP直接返回400 Bad Request并附带详细错误信息“employee_id must be string, got number”。这比让skill自己try-catch优雅得多。实操心得我们用json-schema-validator自动生成Go结构体再用swaggo生成OpenAPI文档。运维同事用curl就能测试skill是否符合契约curl -X POST http://mcp:8080/skills/hr-leave-approval/v1/invoke -d {employee_id:123}立刻看到错误反馈。这比写单元测试还快。3.2 军规二禁止任何阻塞式I/O所有外部调用必须带超时和熔断内网不是天堂。某次我们部署“查设备巡检记录”skill它调用一个老旧的Oracle数据库JDBC驱动默认超时是30秒。结果数据库偶发卡顿skill进程hang住MCP的健康检查失败整个skill实例被踢出集群。更糟的是由于没熔断后续请求全积压在MCP队列里引发雪崩。正确做法所有HTTP调用用http.Client设置Timeout数据库用context.WithTimeout且必须配熔断器。我们用github.com/sony/gobreaker配置如下var cb *gobreaker.CircuitBreaker gobreaker.NewCircuitBreaker(gobreaker.Settings{ Name: oracle-db-call, MaxRequests: 5, Timeout: 30 * time.Second, ReadyToTrip: func(counts gobreaker.Counts) bool { // 连续3次失败就熔断 return counts.ConsecutiveFailures 3 }, OnStateChange: func(name string, from gobreaker.State, to gobreaker.State) { log.Printf(Circuit breaker %s changed from %v to %v, name, from, to) }, })熔断后skill立即返回预设的fallback响应如{status:unavailable,message:系统繁忙请稍后再试}而不是让请求排队。3.3 军规三所有外部依赖必须声明在Dockerfile中禁止运行时下载这是最常被忽视的雷区。某团队写了个skill用pip install requests结果内网镜像仓库没同步这个包容器启动就报错。更隐蔽的是他们用git clone https://gitlab.internal/project.git但GitLab域名解析依赖内网DNS而DNS服务器恰好那天维护——skill全挂。解决方案Dockerfile必须显式COPY所有依赖。我们规定Python skill用pip wheel --no-deps --wheel-dir /wheels -r requirements.txt提前打包所有whl文件Docker build时COPY进来pip install --find-links /wheels --no-index安装Java skill用Maven Shade Plugin打包fat jar所有依赖打进一个jarRust skillcargo build --release后strip target/release/skill-bin减小体积。所有镜像构建必须在离线环境中验证。我们有个checklist脚本会检查镜像层里是否存在/usr/bin/wget、/usr/bin/curl、/etc/resolv.conf等“可疑文件”存在即失败。3.4 军规四日志必须结构化且包含trace_id和skill_version内网排查问题不能靠grep -r error /var/log。我们强制所有skill用logrus输出JSON日志且每条日志必须含trace_id: 全局唯一由MCP注入HTTP Headerskill_name: 如finance-invoice-verifyskill_version: 如v2.3.1level:info/warn/errorevent: 关键动作如invoke_start、db_query_success、response_sent。例如一条成功日志{time:2024-06-15T10:23:45.123Z,level:info,trace_id:tr-8a3f9b2c,skill_name:finance-invoice-verify,skill_version:v2.3.1,event:db_query_success,invoice_id:INV-2024-7890,query_time_ms:142.3}MCP的管理台用Elasticsearch聚合这些日志业务人员输入trace_id就能看到该次请求经过的所有skill、耗时、返回值——这才是真正的端到端可观测。3.5 军规五禁止硬编码任何配置所有参数必须通过环境变量或MCP配置中心注入曾有个skill把数据库密码写死在代码里审计时被扫出来直接导致项目延期两周重写。现在我们规定所有配置项DB_HOST、REDIS_URL、TIMEOUT_MS必须从环境变量读取且MCP提供配置中心APIskill启动时调用GET /config/skills/{name}/v{version}获取加密配置。配置中心用Vault实现但做了定制所有skill配置项都存为kv/skills/{name}/v{version}/{key}且启用策略引擎比如finance-*技能只能读kv/finance/*路径。这样即使skill被攻破攻击者也拿不到HR系统的配置。3.6 军规六必须提供健康检查端点且检查逻辑覆盖所有关键依赖/health端点不能只是return {status:ok}。它必须真实探测数据库连通性执行SELECT 1Redis可用性PING依赖的其他skill是否在线调用其/health本地磁盘剩余空间10GB内存使用率85%。我们用github.com/heptio/healthcheck库每个检查项设独立超时DB 2sRedis 500ms任一失败则返回503 Service Unavailable。MCP的watch机制会立刻感知把该skill实例从路由池移除。这6条军规每一条都对应一个曾经让项目停摆的生产事故。现在新成员入职第一周任务就是用这6条检查表review一个现有skill找出至少3个违规点——这比写代码更能理解隔离内网的残酷现实。4. 实操过程详解从零部署一个“合同履约监控Agent”含完整Docker Compose与MCP配置现在我们动手部署一个真实业务场景的Agent合同履约监控。需求很简单每天上午9点自动扫描ERP系统中所有“进行中”合同调用OCR识别扫描件中的付款条款比对实际付款流水生成风险报告如“合同A约定3月付二期款但至今未付”并通过邮件企微发送给法务部。整个系统由4个容器组成MCP主服务、Skill Registryetcd、LLM RuntimeQwen2-7B、Contract Monitor Skill。下面给出可直接运行的完整配置。4.1 环境准备内网服务器基础配置假设你有一台内网服务器CentOS 7.94核16G无外网先执行基础加固# 关闭swapetcd和LLM Runtime对swap极度敏感 sudo swapoff -a sudo sed -i / swap / s/^\(.*\)$/#\1/g /etc/fstab # 调整ulimit应对高并发skill调用 echo * soft nofile 65536 | sudo tee -a /etc/security/limits.conf echo * hard nofile 65536 | sudo tee -a /etc/security/limits.conf echo fs.file-max 2097152 | sudo tee -a /etc/sysctl.conf sudo sysctl -p # 安装必要工具 sudo yum install -y docker-ce docker-ce-cli containerd.io sudo systemctl enable docker sudo systemctl start docker4.2 部署Skill Registryetcd集群我们用单节点etcd简化演示生产环境请用3节点# 创建etcd数据目录 sudo mkdir -p /data/etcd # 运行etcd容器 sudo docker run -d \ --name etcd \ --restartalways \ -p 2379:2379 \ -p 2380:2380 \ -v /data/etcd:/etcd-data \ --env ETCD_DATA_DIR/etcd-data \ --env ETCD_LISTEN_CLIENT_URLShttp://0.0.0.0:2379 \ --env ETCD_ADVERTISE_CLIENT_URLShttp://127.0.0.1:2379 \ --env ETCD_LISTEN_PEER_URLShttp://0.0.0.0:2380 \ --env ETCD_INITIAL_ADVERTISE_PEER_URLShttp://127.0.0.1:2380 \ --env ETCD_INITIAL_CLUSTERdefaulthttp://127.0.0.1:2380 \ --env ETCD_INITIAL_CLUSTER_STATEnew \ --env ETCD_INITIAL_CLUSTER_TOKENetcd-cluster-1 \ quay.io/coreos/etcd:v3.5.10 \ etcd --data-dir/etcd-data验证curl http://127.0.0.1:2379/version应返回{etcdserver:3.5.10,etcdcluster:3.5.0}。4.3 部署MCP Tools主服务下载预编译二进制我们提供x86_64 Linux版# 创建目录 sudo mkdir -p /opt/mcp /etc/mcp # 下载并授权 sudo curl -L https://example-internal.com/mcp/mcp-linux-amd64 -o /opt/mcp/mcp sudo chmod x /opt/mcp/mcp # 创建配置文件 /etc/mcp/config.yaml sudo tee /etc/mcp/config.yaml EOF # MCP主配置 server: host: 0.0.0.0 port: 8080 tls_enabled: false # 内网可不启TLS生产建议开启 mcp: # gRPC服务地址skill客户端连接这里 grpc_host: 0.0.0.0 grpc_port: 9090 # etcd地址 etcd_endpoints: [http://127.0.0.1:2379] # 技能注册超时 skill_register_timeout: 30s # 日志配置 log: level: info file: /var/log/mcp.log # 指标暴露Prometheus metrics: enabled: true port: 9091 EOF # 创建日志目录 sudo mkdir -p /var/log # 启动MCP用systemd管理 sudo tee /etc/systemd/system/mcp.service EOF [Unit] DescriptionMCP Tools Service Afterdocker.service [Service] Typesimple Userroot WorkingDirectory/opt/mcp ExecStart/opt/mcp/mcp --config /etc/mcp/config.yaml Restartalways RestartSec10 StandardOutputjournal StandardErrorjournal [Install] WantedBymulti-user.target EOF sudo systemctl daemon-reload sudo systemctl enable mcp sudo systemctl start mcp验证curl http://127.0.0.1:8080/health应返回{status:ok,timestamp:...}。4.4 部署Local LLM RuntimeQwen2-7B先下载模型包需提前在内网准备好# 创建模型目录 sudo mkdir -p /opt/models # 假设模型包已上传到服务器 sudo cp /tmp/qwen2-7b-v2.3.pack /opt/models/ # 运行llm-runner容器 sudo docker run -d \ --name llm-runner \ --restartalways \ --gpus all \ -p 8081:8081 \ -v /opt/models:/models \ -e MODEL_PATH/models/qwen2-7b-v2.3.pack \ -e HOST0.0.0.0:8081 \ registry.internal.com/llm-runner:1.2.0验证curl http://127.0.0.1:8081/v1/chat/completions -H Content-Type: application/json -d {messages:[{role:user,content:你好}]}应返回正常响应。4.5 开发并部署Contract Monitor Skill这是核心业务逻辑。我们用Python FastAPI写关键代码如下main.pyimport os import json import logging from fastapi import FastAPI, HTTPException, Request, BackgroundTasks from pydantic import BaseModel from typing import List, Dict, Any import httpx from datetime import datetime, timedelta # 初始化日志结构化 logging.basicConfig( levellogging.INFO, format{time:%(asctime)s,level:%(levelname)s,trace_id:%(trace_id)s,skill_name:contract-monitor,skill_version:v1.0.0,event:%(event)s,message:%(message)s}, handlers[logging.StreamHandler()] ) app FastAPI() class ContractCheckRequest(BaseModel): contract_ids: List[str] class ContractCheckResponse(BaseModel): report_id: str contracts_analyzed: int risks_found: int risk_details: List[Dict[str, Any]] app.post(/v1/invoke) async def invoke_skill(request: Request, payload: ContractCheckRequest): # 1. 获取trace_id从MCP注入的Header trace_id request.headers.get(X-Trace-ID, unknown) # 2. 记录开始 logger logging.getLogger() logger.info(invoke_start, extra{trace_id: trace_id, contract_ids: payload.contract_ids}) try: # 3. 调用ERP获取合同详情模拟 erp_data await call_erp_api(payload.contract_ids, trace_id) # 4. 调用OCR识别付款条款模拟 ocr_results await call_ocr_api(erp_data, trace_id) # 5. 调用支付系统比对流水模拟 payment_check await call_payment_api(ocr_results, trace_id) # 6. 生成报告 report generate_risk_report(erp_data, ocr_results, payment_check) # 7. 发送邮件和企微 await send_notifications(report, trace_id) logger.info(invoke_success, extra{trace_id: trace_id, report_id: report[report_id]}) return ContractCheckResponse(**report) except Exception as e: logger.error(invoke_failed, extra{trace_id: trace_id, error: str(e)}) raise HTTPException(status_code500, detailstr(e)) # 模拟调用ERP实际应替换为真实API async def call_erp_api(contract_ids: List[str], trace_id: str) - Dict: async with httpx.AsyncClient(timeout10.0) as client: # 这里应调用内网ERP的REST API return {contracts: [{id: cid, status: active, payment_terms: 30 days after delivery} for cid in contract_ids]} # 其他函数call_ocr_api, call_payment_api, generate_risk_report, send_notifications略核心是异步超时DockerfileFROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . # 关键所有依赖已打包禁止运行时下载 RUN pip wheel --no-deps --wheel-dir /wheels -r requirements.txt RUN pip install --find-links /wheels --no-index CMD [uvicorn, main:app, --host, 0.0.0.0:8000, --port, 8000]部署命令# 构建镜像在有Docker的内网机器上 docker build -t internal-registry/contract-monitor:v1.0.0 . # 推送到内网镜像仓库假设已有 docker push internal-registry/contract-monitor:v1.0.0 # 运行容器注意连接MCP和etcd docker run -d \ --name contract-monitor \ --restartalways \ -p 8000:8000 \ --network host \ -e MCP_GRPC_ENDPOINT127.0.0.1:9090 \ -e ETCD_ENDPOINTShttp://127.0.0.1:2379 \ internal-registry/contract-monitor:v1.0.04.6 MCP注册Skill与管理台配置Skill启动后会自动向MCP注册。你可以在MCP管理台http://127.0.0.1:8080看到contract-monitor出现在技能列表中。点击进入配置触发方式定时任务Cron表达式0 0 9 * * ?表示每天9点输入参数{contract_ids: [CON-2024-001, CON-2024-002]}告警规则失败次数3次触发企业微信通知给运维群。至此整个Agent已部署完毕。明天上午9点它将自动执行生成报告并推送——所有操作都在隔离内网完成无需任何外网穿透、无需任何公网依赖。5. 常见问题与排查技巧实录那些让资深工程师深夜加班的内网Agent故障在17个隔离内网项目中我们整理出TOP5高频故障。每一个都附带真实日志、排查路径和根治方案。这些不是理论是凌晨三点在机房盯着屏幕熬出来的经验。5.1 故障一MCP路由失败报错“no skill found for name: finance-invoice-verify”现象Agent调用finance-invoice-verifyMCP返回404 Not Found但docker ps能看到该skill容器在运行。排查步骤查MCP日志journalctl -u mcp -n 100 | grep finance-invoice-verify→ 发现一行WARN skill_registry.go:123 no endpoint found for skill finance-invoice-verify v2.1查etcd中是否有注册ETCDCTL_API3 etcdctl --endpointshttp://127.0.0.1:2379 get --prefix /skills/finance-invoice-verify/→ 返回空说明skill根本没注册成功。进skill容器docker exec -it finance-invoice-verify sh→ 执行curl -v http://127.0.0.1:8000/health返回503 Service Unavailable→ 再查skill日志tail -f /var/log/skill.log发现failed to connect to redis: dial tcp 10.10.10.10:6379: i/o timeout根因skill的健康检查依赖Redis而Redis IP配置错了配成了测试环境IP。MCP只注册健康检查通过的skill。根治方案所有skill的健康检查必须只依赖自身核心逻辑禁止检查外部服务。正确做法是健康检查只做内存计算和本地文件读取外部依赖的连通性检查放在/readyz端点由MCP定期调用非路由时调用。在Docker Compose中用depends_on确保依赖服务先启动但不能替代配置校验。我们加了启动脚本skill容器启动时先ping -c 1 redis.internal curl -f http://redis.internal/health失败则exit 1触发docker restart。5.2 故障二LLM Runtime响应极慢P99延迟从50ms飙升到2.3秒现象管理台显示llm-runner的request_duration_secondsP99曲线陡升但GPU显存占用只有40%CPU使用率30%。排查步骤进LLM容器docker exec -it llm-runner sh查进程top -Hp $(pgrep -f llm-runner)→ 发现一个线程CPU 99%抓线程栈jstack pidRust用gdb -p pid→ 发现卡在std::sys::unix::net::Socket::connect查网络ss -tulnp | grep :8081→ 发现大量SYN-SENT状态连接根因LLM Runtime用HTTP Client调用MCP的gRPC服务错误地用了HTTP而非gRPC而MCP的gRPC端口9090被防火墙策略误拦连接一直重试。根治方案严格区分协议MCP的gRPC端口9090和HTTP端口
返回列表