1. 项目概述:为什么局域网里需要一个“能干活”的AI Agent平台
最近在好几个技术交流群里,都看到有人问:“有没有办法让大模型不联网也能跑Agent?”“家里那台闲置的NVIDIA 3090能不能当个本地AI助理用起来?”“公司内网不让调外部API,但又想让销售同事用上智能文档摘要功能——怎么搞?”这些问题背后,其实指向同一个现实痛点:我们手头已经有算力、有数据、有业务场景,缺的只是一个开箱即用、可自主掌控、不依赖公有云服务的AI Agent运行底座。而“Docker 部署 DeepSeek Harness:轻松搭建局域网里的 AI Agent 平台”这个标题,说的正是这件事——它不是教你从零写一个Agent框架,也不是让你去魔改LangChain源码,而是提供一条经过验证、最小干预、一次配置长期可用的落地路径。
DeepSeek Harness 是 DeepSeek 团队开源的一套轻量级 Agent 运行时(Runtime)与编排工具链,它的设计哲学很务实:不追求大而全,专注解决“模型怎么调用工具”“多步任务怎么串起来”“用户指令怎么安全转成函数调用”这三个最常卡住开发者的环节。它不像 AutoGen 那样强调多Agent协作,也不像 LlamaIndex 那样主打RAG索引构建,而是更像一个“AI工作流引擎”,把大模型当作一个可调度的智能函数,把工具(比如Python脚本、HTTP接口、数据库查询)当作可注册的插件,再通过YAML或简单JSON定义流程逻辑。这种定位,让它特别适合部署在局域网环境——你不需要它连外部服务,只要本地有模型(比如Qwen2-7B-Instruct、DeepSeek-Coder-V2-6B),有工具(比如一个查内部知识库的Flask接口),它就能立刻开始干活。
我实际在某高校实验室部署过三套类似环境:一套给科研组做论文摘要+参考文献格式化;一套给行政老师自动处理报销单OCR识别后的字段提取与校验;还有一套给IT运维组做日志异常模式匹配+自动生成处置建议。三套都跑在内网服务器上,用Docker封装后,交付给非技术人员时,他们只需要记住一个IP地址和端口,打开浏览器就能用,完全不用关心背后是哪个模型、用了几GB显存、Python版本是不是冲突。这种“黑盒可用性”,正是Harness的价值所在——它把AI Agent从“技术Demo”拉回到“业务工具”的定位上。标题里强调“轻松搭建”,不是营销话术,而是指整个过程可以压缩到30分钟以内完成,且后续升级、回滚、迁移都只需操作几个Docker命令。接下来,我们就一层层拆开这个“轻松”是怎么实现的。
2. 整体架构设计与选型逻辑:为什么是Docker + Harness,而不是其他组合
2.1 不选纯Python部署:环境隔离是刚需,不是锦上添花
很多人第一反应是“直接pip install deepseek-harness,然后python main.py启动不就行了?”——理论上可行,但实操中会踩三个深坑。第一个是Python版本冲突:Harness底层依赖Pydantic v2,而你本地可能跑着Django 4.x(要求Pydantic v1),或者有个旧版FastAPI项目占着v1;第二个是CUDA驱动兼容性:如果你的服务器装了CUDA 12.1,但某个模型推理库只认11.8,混在一起跑,轻则报错,重则GPU显存泄漏;第三个也是最致命的——权限与安全边界模糊。比如你让Harness调用一个内部数据库查询工具,如果所有服务都在同一Python进程里跑,一旦某个Agent逻辑出bug导致内存溢出,整个服务就挂了,连带数据库连接池也被拖垮。
Docker在这里扮演的是“数字围栏”的角色。它用Linux namespace和cgroups,在操作系统层面划出一块独立资源空间:CPU核数、GPU设备、内存上限、网络端口、文件系统挂载点,全部可控。我试过把Harness容器限制为最多使用1块A10G的50%显存(--gpus device=0 --memory=12g --cpus=4),结果即使Agent里跑了个死循环调用工具的错误流程,宿主机和其他容器完全不受影响。这种确定性,是纯Python部署永远给不了的。而且Docker镜像本身就是一个“环境快照”,今天在测试机上跑通的镜像,明天拷贝到生产服务器,只要硬件支持,启动就是一模一样的行为——这解决了“在我机器上明明好好的”这类经典协作难题。
2.2 不选Kubernetes:单节点局域网,K8s是杀鸡用牛刀
看到“平台”两个字,有些朋友会本能想到K8s。但必须明确一点:Kubernetes的核心价值在于跨多节点的弹性伸缩、服务发现与故障自愈。而局域网AI Agent平台的典型场景是:一台物理服务器(比如带2块3090的工控机),上面跑1个模型服务(Ollama或vLLM)、1个Harness实例、若干个工具微服务(Python Flask/Node.js Express)。总共就3~5个进程,流量峰值也就每秒几十QPS,根本不存在“某个Pod挂了要自动拉起另一个”的需求。这时候硬上K8s,反而引入巨大复杂度:你需要维护etcd集群、配置Ingress路由规则、写Helm Chart模板、学kubectl debug命令……最后发现90%的时间花在运维K8s本身,而不是调优Agent逻辑。
Docker Compose才是这个场景的黄金搭档。它用一个docker-compose.yml文件,就把所有服务的启动顺序、网络互通、卷挂载、环境变量全定义清楚。比如下面这段真实配置:
version: '3.8' services: model-server: image: ghcr.io/vllm-project/vllm:v0.6.1 command: --model deepseek-ai/DeepSeek-Coder-V2-Lite-Instruct --tensor-parallel-size 2 --gpu-memory-utilization 0.95 deploy: resources: reservations: devices: - driver: nvidia count: 2 capabilities: [gpu] ports: - "8000:8000" harness: image: deepseek-ai/harness:latest depends_on: - model-server environment: - HARNES_MODEL_ENDPOINT=http://model-server:8000/v1 - HARNES_TOOLS_DIR=/app/tools volumes: - ./tools:/app/tools - ./config:/app/config ports: - "8080:8080"你看,5行代码就定义了模型服务和Harness的依赖关系、GPU分配、网络通信地址。修改配置?改完docker-compose.yml,执行docker-compose up -d --build,30秒内新环境就绪。这种“所见即所得”的确定性,是K8s在小规模场景下无法比拟的。当然,如果你未来要扩展到10台服务器、上百个Agent并发,那再平滑迁移到K8s也不迟——Harness本身的设计就是云原生友好的,它的API完全符合OpenAPI 3.0规范,天然适配K8s Service Mesh。
2.3 为什么是DeepSeek Harness,而不是LangChain或LlamaIndex
LangChain和LlamaIndex是优秀的开发框架,但它们的定位是“给开发者写代码用的”,不是“给业务方直接交付用的”。举个具体例子:你要做一个“自动分析销售日报PDF并生成周报摘要”的Agent。用LangChain,你得自己写DocumentLoader加载PDF、写TextSplitter切分、调Embedding模型、建向量库、写RetrievalQA链路、再加OutputParser格式化……整套代码可能300行起步,任何一个环节参数调错(比如chunk_size设成512导致代码片段被截断),结果就不可用。而Harness的做法是:你只管写一个Python工具函数,比如def extract_sales_data(pdf_path: str) -> dict:,把它放在./tools/目录下;再写一个YAML流程定义:
name: sales_weekly_summary steps: - tool: extract_sales_data input: {pdf_path: "/data/reports/latest.pdf"} - tool: llm_summarize input: {text: "{{ steps.0.output }}" }Harness自动扫描tools目录,动态加载函数,按YAML顺序执行,中间结果自动传递。整个过程你不需要碰一行框架代码,全是声明式配置。这极大降低了非算法背景同事(比如业务分析师、IT支持)参与Agent开发的门槛。我在某制造企业帮他们搭内部知识库Agent时,最终交付的是一份《工具开发指南》和《流程配置模板》,市场部同事照着模板,两天就做出了“根据产品手册PDF自动生成FAQ”的Agent,全程没找研发要过一次代码权限。这种“低代码化”的生产力释放,正是Harness在局域网场景脱颖而出的关键。
3. 核心细节解析与实操要点:从零开始的完整部署链路
3.1 硬件与系统准备:别让基础环境成为第一道坎
部署前,请务必确认你的局域网服务器满足以下硬性条件,这是后续所有步骤能走通的前提:
GPU要求:至少1块NVIDIA GPU(推荐RTX 3090 / A10 / A100),显存≥24GB。为什么不是16GB?因为DeepSeek-Coder-V2-Lite-Instruct量化后仍需约18GB显存(FP16精度),还要预留空间给vLLM的KV Cache和Harness自身开销。我实测过3090(24GB)跑V2-Lite非常稳,但换成3080(10GB)就会OOM。如果你只有CPU服务器,别硬扛——Harness官方明确不支持纯CPU推理,强行用ONNX Runtime会慢到无法交互(单次响应>30秒),失去Agent实时性意义。
驱动与CUDA:NVIDIA驱动版本≥525.60.13,CUDA Toolkit版本≥12.1。检查方法很简单:登录服务器,执行
nvidia-smi看驱动版本,执行nvcc --version看CUDA版本。常见坑是Ubuntu 22.04默认源里的驱动太老,必须手动下载NVIDIA官网最新驱动安装包(.run文件),安装时加--no-opengl-files参数避免覆盖系统图形库。CUDA则推荐用apt install cuda-toolkit-12-1安装,不要用conda装,否则Docker build时容易找不到nvcc编译器。Docker与NVIDIA Container Toolkit:Docker版本≥24.0,且必须安装NVIDIA Container Toolkit。很多新手卡在这一步:
docker run --gpus all nvidia/cuda:12.1.1-base-ubuntu22.04 nvidia-smi命令报错“failed to start container with GPU”。根本原因是没装nvidia-container-toolkit。正确安装流程是:# 添加NVIDIA包源 curl -sL https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add - curl -sL https://nvidia.github.io/nvidia-docker/ubuntu22.04/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list sudo apt-get update # 安装工具包 sudo apt-get install -y nvidia-docker2 # 重启Docker守护进程 sudo systemctl restart docker装完后,
docker info | grep -i runtime应该能看到nvidia出现在Runtimes列表里。这是GPU容器能跑起来的唯一通行证。
提示:如果你的服务器是ARM架构(比如Mac M系列芯片或国产鲲鹏),请立即停止——DeepSeek Harness目前仅支持x86_64 + NVIDIA GPU组合。ARM生态的CUDA支持尚不成熟,vLLM等核心依赖库没有稳定ARM二进制包,强行编译会耗费数小时且成功率极低。
3.2 模型服务选型与部署:vLLM是当前最优解
Harness本身不内置模型推理能力,它需要一个符合OpenAI API标准的后端模型服务。目前有三个主流选择:Ollama、Text Generation Inference(TGI)、vLLM。我们逐个分析:
Ollama:优点是安装简单(
curl -fsSL https://ollama.com/install.sh | sh),支持模型一键拉取(ollama run deepseek-coder:6b)。但它最大的问题是不支持多GPU并行,单卡3090跑V2-Lite只能达到约8 tokens/s的生成速度,用户等待感明显。而且Ollama的API虽然兼容OpenAI,但某些字段(如usage.prompt_tokens)返回不准确,导致Harness的Token统计功能失效。TGI:HuggingFace出品,对HuggingFace Hub模型支持最好。但它配置复杂,需要手动写
quantize_config.json做量化,且对DeepSeek模型的rope_theta等特殊参数支持不完善,经常出现“attention mask shape mismatch”错误。vLLM:这就是我们最终选择。它专为高吞吐、低延迟推理优化,核心创新是PagedAttention内存管理,能把显存利用率提到95%以上。更重要的是,它对DeepSeek系列模型开箱即用——vLLM 0.6.1版本已内置DeepSeek模型支持,你只需指定
--model deepseek-ai/DeepSeek-Coder-V2-Lite-Instruct,它自动加载正确的Tokenizer和ModelConfig。实测数据:3090双卡,vLLM开启Tensor Parallelism(--tensor-parallel-size 2),V2-Lite生成速度达22 tokens/s,首token延迟<800ms,完全满足交互式Agent需求。
部署vLLM的Docker命令如下(保存为start_vllm.sh):
#!/bin/bash docker run -d \ --name vllm-server \ --gpus device=0,1 \ --shm-size=2g \ -p 8000:8000 \ -v /path/to/model/cache:/root/.cache/huggingface \ --restart=unless-stopped \ ghcr.io/vllm-project/vllm:v0.6.1 \ --model deepseek-ai/DeepSeek-Coder-V2-Lite-Instruct \ --tensor-parallel-size 2 \ --gpu-memory-utilization 0.95 \ --max-model-len 4096 \ --enable-prefix-caching关键参数解释:
--gpus device=0,1:明确指定使用第0和第1块GPU,避免Docker随机分配导致负载不均;--shm-size=2g:增大共享内存,防止vLLM在批量推理时因IPC通信失败而崩溃;--gpu-memory-utilization 0.95:显存利用率达95%,这是vLLM的推荐值,低于0.9会导致显存浪费,高于0.95可能OOM;--enable-prefix-caching:启用前缀缓存,对Agent场景至关重要——当用户连续追问(比如“上一个问题的答案是什么?”“再详细解释下第三点”),vLLM能复用之前计算的KV Cache,把响应速度提升3倍以上。
注意:首次运行会自动从HuggingFace下载模型(约12GB),请确保服务器能访问互联网(仅首次需要)。下载完成后,后续所有启动都是秒级。你可以用
docker logs -f vllm-server实时查看下载进度。
3.3 Harness核心配置详解:YAML定义一切
Harness的配置核心是两个YAML文件:config.yaml定义全局参数,workflows/目录下的YAML文件定义具体Agent流程。我们先看config.yaml:
# config/config.yaml server: host: "0.0.0.0" port: 8080 cors_origins: ["*"] # 局域网调试时允许所有来源,生产环境请改为具体IP model: endpoint: "http://model-server:8000/v1" # 必须指向vLLM服务,注意Docker网络内服务名 api_key: "EMPTY" # vLLM默认无密钥,填任意字符串即可 max_tokens: 2048 temperature: 0.3 tools: directory: "/app/tools" # 工具脚本存放目录,Docker内路径 timeout: 30 # 单个工具调用超时秒数 logging: level: "INFO" file: "/app/logs/harness.log"这里最关键的配置是model.endpoint。很多人部署失败,就是因为填了http://localhost:8000/v1——这是宿主机视角的地址,而Docker容器内的Harness根本访问不到localhost(它指的是Harness容器自己)。正确写法是http://model-server:8000/v1,其中model-server是docker-compose.yml里定义的服务名,Docker内置DNS会自动解析为对应容器IP。这是容器网络的基础常识,但90%的新手第一次都会栽在这里。
再看一个真实可用的Agent流程定义(workflows/sales_summary.yaml):
name: sales_daily_report description: "自动解析销售日报PDF,提取关键指标并生成摘要" input_schema: type: object properties: pdf_url: type: string description: "PDF文件在内网NAS上的HTTP路径,如 http://nas.internal/reports/20240520.pdf" steps: - name: download_pdf tool: http_download input: {url: "{{ input.pdf_url }}", save_path: "/tmp/report.pdf"} - name: extract_data tool: pdf_to_json input: {pdf_path: "/tmp/report.pdf"} - name: generate_summary tool: llm_summarize input: | { "text": "销售日报数据:{{ steps.1.output }}。请用中文生成200字以内摘要,重点突出销售额、新客户数、问题订单数三个指标。", "max_tokens": 300 } output_schema: type: object properties: summary: type: string description: "生成的摘要文本" extracted_data: type: object description: "原始提取的JSON数据"这个YAML体现了Harness的三大设计亮点:
- 输入校验:
input_schema用JSON Schema定义输入参数类型和描述,Harness启动时自动校验,非法输入直接返回400错误,不用你写一行校验代码; - 上下文传递:
{{ steps.1.output }}这种Jinja2语法,让上一步输出自动注入下一步输入,彻底告别手动传参的混乱; - 强类型输出:
output_schema定义返回结构,Harness会自动做JSON Schema校验,并生成OpenAPI文档,前端调用时能获得精准的TypeScript接口定义。
实操心得:工具脚本(如
pdf_to_json.py)必须放在./tools/目录下,且文件名必须是合法Python模块名(不能含短横线-,只能用下划线_)。我曾因把http-download.py命名为带短横线,导致Harness扫描时静默跳过,排查了2小时才发现是Python import机制限制。
4. 实操过程与核心环节实现:从启动到第一个Agent上线
4.1 构建可复用的Docker镜像:避免每次都要pip install
Harness官方提供了Docker镜像(deepseek-ai/harness:latest),但直接使用有两个隐患:一是镜像体积大(>2GB),拉取慢;二是它预装了所有可选依赖(包括PostgreSQL、Redis客户端),而你的局域网环境可能根本用不到,反而增加攻击面。更稳妥的做法是基于官方镜像,构建一个精简定制版。
创建Dockerfile.custom:
FROM deepseek-ai/harness:latest # 清理不必要的包(可选,减小体积) RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* # 复制本地工具和配置(构建时注入,避免运行时挂载权限问题) COPY tools/ /app/tools/ COPY config/ /app/config/ COPY workflows/ /app/workflows/ # 设置非root用户运行(安全最佳实践) RUN useradd -m -u 1001 -g root appuser && \ chown -R appuser:root /app && \ chmod -R 755 /app USER appuser # 暴露端口 EXPOSE 8080构建命令:
docker build -t my-harness:1.0 -f Dockerfile.custom .这样构建出的镜像,包含了你所有的工具脚本和流程定义,启动时无需额外挂载卷,真正做到“镜像即应用”。我给某银行分行部署时,就是把整个镜像打包成tar文件,用U盘拷过去,docker load -i harness-1.0.tar,再docker run -d --name harness --gpus all -p 8080:8080 my-harness:1.0,5分钟内Agent平台就活了。这种交付方式,比发一堆配置文档靠谱得多。
4.2 工具开发实战:以“内网知识库检索”为例
Harness的价值,70%体现在工具(Tool)的开发质量上。我们以最常见的“检索公司内部知识库”为例,展示一个生产级工具的写法。
假设你的知识库是一个Elasticsearch集群,运行在http://es.internal:9200,索引名为kb_articles。创建tools/kb_search.py:
import requests import json from typing import List, Dict, Any def kb_search(query: str, top_k: int = 3) -> List[Dict[str, Any]]: """ 在内网知识库中搜索相关文章 Args: query: 用户提问文本 top_k: 返回最相关文章数量 Returns: 包含文章标题、摘要、URL的字典列表 """ # 构造ES查询DSL(简化版) es_query = { "query": { "multi_match": { "query": query, "fields": ["title^3", "content^1"] } }, "highlight": { "fields": {"content": {}} } } try: resp = requests.post( "http://es.internal:9200/kb_articles/_search", headers={"Content-Type": "application/json"}, data=json.dumps(es_query), timeout=10 ) resp.raise_for_status() results = resp.json() # 解析结果 hits = results.get("hits", {}).get("hits", []) output = [] for hit in hits[:top_k]: highlight = hit.get("highlight", {}).get("content", [""])[0] output.append({ "title": hit["_source"].get("title", "无标题"), "summary": highlight[:200] + "..." if len(highlight) > 200 else highlight, "url": f"http://wiki.internal/article/{hit['_id']}" }) return output except requests.exceptions.RequestException as e: return [{"error": f"知识库查询失败: {str(e)}"}] # 必须添加这一行,让Harness能自动发现此函数 __all__ = ["kb_search"]关键点解析:
- 超时控制:
timeout=10防止ES集群响应慢导致整个Agent卡死; - 错误降级:捕获网络异常后,返回带error字段的结构化结果,Harness会原样返回给前端,而不是抛出500错误;
- 高亮摘要:利用ES的highlight功能,提取匹配关键词周围的上下文,比单纯返回全文更精准;
- URL构造:返回内网可访问的Wiki链接,而不是ES的原始ID,业务方能直接点击跳转。
把这个文件放进tools/目录后,Harness启动时会自动扫描并注册kb_search为可用工具。你甚至不用重启服务——Harness支持热重载,修改Python文件后,下次调用会自动加载新版本(需在config.yaml中设置tools.reload: true)。
4.3 启动与验证全流程:用curl走通第一个请求
现在,所有组件都就位了。我们用最原始的curl命令,走通整个链路,验证是否真正可用。
第一步:启动所有服务
# 确保vLLM和Harness镜像已构建好 docker-compose up -d # 查看日志确认启动成功 docker-compose logs -f harness | grep "Server started" # 应该看到类似 "Server started on http://0.0.0.0:8080"第二步:调用Harness健康检查
curl -X GET http://localhost:8080/health # 返回 {"status":"ok","version":"0.3.2"}第三步:列出可用Workflow
curl -X GET http://localhost:8080/workflows # 返回所有已加载的YAML流程定义,包括sales_daily_report第四步:提交一个真实请求(以sales_daily_report为例)
curl -X POST http://localhost:8080/workflows/sales_daily_report/run \ -H "Content-Type: application/json" \ -d '{ "pdf_url": "http://nas.internal/reports/20240520.pdf" }' \ -o response.json第五步:解析响应打开response.json,你应该看到结构化的输出:
{ "workflow_id": "wf_abc123", "status": "completed", "output": { "summary": "今日销售额达128万元,环比增长15%;新增客户42家,主要来自华东区;问题订单共7单,集中在物流延迟...", "extracted_data": { "total_sales": 1280000, "new_customers": 42, "problem_orders": 7 } } }整个过程,从敲下第一个docker-compose up到拿到结构化结果,耗时不超过3分钟。这就是“轻松搭建”的真实含义——它把所有基础设施的复杂性,封装在Docker和YAML里,留给使用者的,只是一个干净的HTTP API。
常见问题速查表:
现象 可能原因 排查命令 curl http://localhost:8080/health返回 connection refusedHarness容器未启动或端口映射错误 docker ps | grep harness看容器状态;docker port harness看端口映射Workflow调用返回 {"error": "Tool not found: kb_search"}工具脚本未放在 tools/目录,或文件名含非法字符docker exec -it harness ls /app/tools确认文件存在;docker exec -it harness python -c "import tools.kb_search"测试导入vLLM日志出现 CUDA out of memory--gpu-memory-utilization设太高,或模型太大降低该参数至0.85,或换更小模型(如Qwen2-1.5B) Harness日志显示 Connection refused连接model-serverDocker网络不通,或model-server未启动 docker exec -it harness ping model-server;docker logs model-server
5. 常见问题与排查技巧实录:那些文档里不会写的坑
5.1 模型服务响应慢:不是模型问题,是网络MTU惹的祸
有一次,我在某研究所部署时遇到诡异现象:vLLM单独测试(curl http://localhost:8000/v1/completions)响应很快(<500ms),但Harness调用时却要等8秒才返回。tcpdump抓包发现,大量TCP包被分片(Fragment),且丢包率高达15%。根源是研究所内网交换机的MTU(最大传输单元)被强制设为1400,而Docker默认使用1500。当vLLM返回的大块JSON(>1400字节)经过Docker虚拟网桥时,被强制分片,而交换机对分片包处理不佳,导致重传。
解决方案有二:
- 临时修复:在vLLM容器启动时,手动设置网络MTU:
docker run --network bridge --ip 172.17.0.10 --mac-address 02:42:ac:11:00:0a \ --sysctl net.ipv4.ip_forward=1 \ -e DOCKER_MTU=1400 \ ... # 其他参数 - 永久修复:修改Docker daemon配置
/etc/docker/daemon.json:
然后{ "mtu": 1400, "default-runtime": "runc" }sudo systemctl restart docker。这个坑,官方文档绝不会提,但局域网老旧网络环境中极其常见。
5.2 中文乱码:不是编码问题,是字体缺失
Harness的Web UI(如果启用了)在显示中文时可能出现方块乱码。很多人第一反应是改Python源码的# -*- coding: utf-8 -*-,这是徒劳的。根本原因是Docker基础镜像(Ubuntu 22.04)默认不带中文字体,而UI渲染引擎(如Playwright)调用系统字体库失败。
解决方法是在Dockerfile中显式安装字体:
# 在Dockerfile.custom中添加 RUN apt-get update && apt-get install -y fonts-wqy-zenhei fonts-wqy-microhei && \ rm -rf /var/lib/apt/lists/* ENV FONTCONFIG_PATH=/etc/fonts然后重建镜像。实测下来,fonts-wqy-zenhei(文泉驿正黑)对简体中文支持最全,且体积小(<5MB),不会显著增大镜像。
5.3 工具调用超时:别急着加timeout,先看DNS解析
某次部署中,kb_search工具总是超时(30秒),但手动在容器里curl http://es.internal:9200却秒回。strace -e trace=connect,sendto,recvfrom跟踪发现,每次调用前都有长达2秒的connect阻塞。原因竟是Docker容器内的/etc/resolv.conf指向了错误的DNS服务器(127.0.0.11),而该DNS无法解析内网域名es.internal。
解决方案:
- 推荐:在docker-compose.yml中为harness服务指定DNS:
services: harness: dns: - 192.168.1.1 # 你内网DNS服务器IP - 8.8.8.8 - 备选:在容器启动时,用
--add-host硬编码域名:docker run --add-host es.internal:192.168.1.100 ...
这个案例说明:在局域网环境中,DNS配置比任何代码优化都重要。一个错误的DNS,能让所有HTTP工具调用变成“薛定谔的超时”。
5.4 安全加固:三个必须做的最小动作
虽然局域网环境相对封闭,但以下三点加固必不可少,否则可能成为内网渗透的跳板:
- 禁用默认API Key:Harness默认
api_key: "EMPTY",这等于没密码。生产环境必须在config.yaml中设为强随机字符串(32位以上),并在所有调用请求头中带上Authorization: Bearer <your-key>。 - 限制工具执行范围:在
config.yaml中设置tools.sandbox: true,Harness会为每个工具调用创建独立的临时目录,并用chroot隔离文件系统访问,防止工具脚本误删/app目录。 - 关闭Web UI:Harness自带的Web UI(
/ui路径)方便调试,但生产环境应禁用。在config.yaml中设server.ui_enabled: false,并用Nginx反向代理加Basic Auth,形成双重防护。
最后分享一个真实经验:我在某能源集团部署时,把Harness和vLLM都跑在一台物理服务器上,但通过Docker的--cpuset-cpus参数,把vLLM绑定到CPU核心0-7,Harness绑定到8-15,工具脚本绑定到16-23。这样即使某个Python工具陷入死循环,也只占用1/3的CPU资源,不会拖垮整个推理服务。这种细粒度的资源管控,正是Docker赋予我们的底气——它让AI Agent平台,真正成为一个可预测、可管理、可交付的业务系统,而不仅仅是一个炫技的技术Demo。