
本文是《部署 OCManager 智能运维平台并集成 AI 服务实现 EKS 节点的监控和维护》的续篇。
前一篇我们完成了 OCManager 控制平面部署、ocm-agent 批量下发到 EKS 节点、以及 OCAI 的接入。但留下了一个遗憾:OCAI 只能"咨询",无法到节点上执行命令做诊断。本文将解决这个问题。
为什么 OCAI "只能看不能动"?
前一篇结尾,我提到 OCAI service 无法直接执行命令"还是有点麻烦的"。当时系统里其实已经存在两条命令执行路径:
- 受控命令下发(agent mTLS 通道):控制台 → sysdiagnose → TMS(task-manager) → gateway →(mTLS :8989)→ ocm-agent。这是"命令助手"用的,安全受控、有审计、走制品 MD5 校验,但流程较重。
- AI 诊断(SSH 旁路):OCAI → diagagent → diaglite → ansible → SSH → 目标节点。这是 ops-mate,灵活、AI 驱动多轮诊断。
ops-mate 到底是什么?官方文档对这块讲得比较模糊,实际上:ops-mate = diaglite + diagagent
- diaglite(:8787)本质是封装 ansible,负责通过 SSH 在目标机执行命令
- diagagent(:8788)是AI 诊断编排器,负责多轮 LLM 调用和诊断逻辑编排
启用过程
由于 diaglite 依赖ansible,因此需要在控制平面安装 ansible,目标 EKS 节点不需要装任何东西(EC2 默认自带)。因为 ansible 是控制端工具。
bash
sudo yum install -y ansible-core
装完验证版本:
bash
$ ansible --version
ansible-core 2.15.3
同时会装上 sshpass,后面用得上。
编译 diaglite/diagagent
源码在 ocai-service/ops-mate/diaglite/,用 go build 编译:
bash
cd ocai-service/ops-mate/diaglite/
go build -o diaglite ./cmd/diaglite
go build -o diagagent ./cmd/diagagent
生成 diaglite 和 diagagent 两个二进制文件。
配置文件
修改 diaglite 的 config.yaml(listen 指定端口,ansible 段指定 SSH 参数)。这里指定的cluster-key.pem是因为我们在创建EKS节点的时候指定了SSH密钥,因此可以直接拿来复用,如果没有指定密钥,也可以通过SSM document下发密钥。
yaml
# ocai-service/ops-mate/diaglite/config.example.yaml
listen: ":8787"
sqlite_path: "./data/diaglite.sqlite"
ansible:
binary: ansible
user: ec2-user
port: 22
private_key: "/home/ec2-user/.ssh/cluster-key.pem"
become: false
work_dir: "./data/ansible_work"
host_key_checking: false
ssh_extra_args: "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
run_timeout: 300s
forks: 10
batch_max_hosts: 500
rag:
index_paths:
- "./corpus"
max_file_bytes: 1048576
修改 diagagent.yaml(base_url指向 diaglite):
yaml
# ocai-service/ops-mate/diaglite/diagagent.yaml
listen: ":8788"
llm:
base_url: "https://bedrock-mantle.us-east-1.api.aws/v1"
api_key: "" # 留空,由环境变量 DIAGAGENT_LLM_API_KEY 注入
model: "moonshotai.kimi-k2.5"
temperature: 0.2
max_tokens: 4096
diaglite:
base_url: "http://127.0.0.1:8787"
agent:
max_rounds: 16
task_poll_interval: 2s
task_timeout: 15m
max_output_chars: 16000
server:
default_session_timeout: 30m
Docker 网络问题
ocai-service 跑在 Docker 里,如果 .env 里配 127.0.0.1:8788,容器内会指向自己而不是宿主机。
要用宿主机的 docker0 网桥 IP:
bash
$ ip addr show docker0
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
所以 .env 里要配:
bash
OCAI_DIAGAGENT_BASE_URL=http://172.17.0.1:8788
OCAI_DIAGLITE_BASE_URL=http://172.17.0.1:8787
OCAI 容器镜像重新构建
改配置后 OCAI 容器需要重建。遇到的问题如下
(1)Dockerfile syntax 镜像源问题
Dockerfile 第一行是 # syntax=docker/dockerfile:1.7,构建时要拉取 syntax 镜像。默认走了某个出 TLS 错误的镜像源(docker.mirrors.sjtug.sjtu.edu.cn)。因为这个 Dockerfile 其实没用任何 1.7 特有语法,直接把 syntax 那行删掉即可绕过。
(2)ModuleNotFoundError: No module named 'ocai'
启动之后,容器一直重启,日志报找不到 ocai 模块。
通过以下命令确认 /app/ocai 目录确实存在
bash
$ docker run --rm --entrypoint ls ocai-service:latest -la /app/ocai/
total 40
-rw-rw-r-- 1 ocai ocai 0 __init__.py
drwxrwxr-x 8 ocai ocai 185 ai
...
# 看启动脚本 deploy/start.py,核心是:uvicorn.run("ocai.main:create_app", factory=True, host=..., port=...)
# 在 /app 目录下手动导入没有异常
$ cd /app
$ python3 -c 'import ocai; print(ocai.__file__)'
/app/ocai/__init__.py
# 但换个目录再运行脚本能复现现象
$ cd /tmp && python3 /app/deploy/start.py
ModuleNotFoundError: No module named 'ocai'
关键差异在 sys.path[0]:python3 /app/deploy/start.py 时,sys.path0 是脚本所在目录 /app/deploy。
由于Dockerfile 用了 uv sync --no-install-project(只装依赖不装项目本身),而 python deploy/start.py 运行时 sys.path[0] 是脚本所在目录 /app/deploy 而不是 /app,所以找不到 /app/ocai。在 docker-compose.yml 的 environment 里显式补上 PYTHONPATH:
yaml
services:
ocai-service:
environment:
PYTHONPATH: /app
(3)日志目录权限问题
PermissionError: [Errno 13] Permission denied: '/app/logs/ocai.log'
挂载进容器的宿主机日志目录属主不对。容器里以 ocai 用户(uid 1001)运行,而宿主机目录默认属主是别的用户,于是写不进去。把目录属主改成 1001:
bash
sudo chown -R 1001:1001 <LOG_DIR>/ocai-service
进程常驻
我们用 systemd service托管diaglite,配置如下diaglite.service:
ini
[Unit]
Description=OCManager DiagLite Service
After=network.target
[Service]
Type=simple
User=ec2-user
WorkingDirectory=/home/ec2-user/ocai-service/ops-mate/diaglite
ExecStart=/home/ec2-user/ocai-service/ops-mate/diaglite/diaglite -config config.yaml
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
diagagent.service (注意 Environment 注入 LLM key,对应 diagagent.yaml 里留空的 api_key):
ini
[Unit]
Description=OCManager DiagAgent Service
After=network.target diaglite.service
[Service]
Type=simple
User=ec2-user
WorkingDirectory=/home/ec2-user/ocai-service/ops-mate/diaglite
Environment=DIAGAGENT_LLM_API_KEY=<你的API Key>
ExecStart=/home/ec2-user/ocai-service/ops-mate/diaglite/diagagent -config diagagent.yaml
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
启动服务
bash
sudo systemctl daemon-reload
sudo systemctl enable diaglite diagagent
sudo systemctl start diaglite diagagent
端到端验证
测试底层 diaglite
diaglite 的任务是异步 的:先 POST /v1/tasks 拿到任务 id,再轮询 GET /v1/tasks/{id} 到终态。
bash
# 创建任务
$ curl -s -X POST http://127.0.0.1:8787/v1/tasks \
-H "Content-Type: application/json" \
-d '{"ip":"192.168.19.155","command":"hostname && uptime","title":"e2e"}'
{"id":"bb05d7649d81...","status":"pending", ...}
# 轮询结果
$ curl -s http://127.0.0.1:8787/v1/tasks/bb05d7649d81...
{
"status": "success",
"output": "192.168.19.155 | CHANGED => {\n \"rc\": 0,\n \"stdout\": \"ip-192-168-19-155...\"\n}"
}
底层用的是 ansible,所以 output 是 ansible 的 CHANGED / rc=0 结构。
测试 diagagent(AI 编排层)
bash
curl -s -X POST http://127.0.0.1:8788/v1/agent/sessions \
-H "Content-Type: application/json" \
-d '{"ip":"192.168.19.155","q":"查看这台机器的主机名和运行时间","timeout":"3m"}'
AI 多轮采集后会返回 Markdown 报告:
markdown
## 主机信息诊断报告
### 目标机
- IP: 192.168.19.155
### 关键信息
| 项目 | 值 |
|------|-----|
| 主机名 | ip-192-168-19-155.cn-north-1.compute.internal |
| 操作系统 | Amazon Linux 2023 |
| 内核版本 | 6.12.92-122.166.amzn2023.x86_64 |
### 补充信息
- 实例类型: AWS EC2(从主机名判断,位于 AWS 中国宁夏区域)
- 系统状态: 正常,无 systemd 失败服务
- 资源概览: 内存 7.8GB(可用 5.9GB),磁盘 30GB(使用率 26%)
OCAI 前端验证
打开 OCAI 聊天窗口,输入"在192.168.19.155 执行 top 命令查看进程",能看到 AI 主动调用 diagnose_machine 工具。

示例查询在192.168.19.155 执行ss -ntlp命令查看链接情况

再看 health 端点确认依赖已启用(OCAI 容器对外映射到 18000):
bash
$ curl -s http://127.0.0.1:18000/health
{
"status": "ok",
"capabilities": ["streaming", "tool_calling", "user_confirmation", ...],
"dependencies": {
"oc_manager": {"enabled": true},
"msg_etl": {"enabled": true},
"diag_agent": {"enabled": true},
"mcp": {"enabled": false}
}
}
diag_agent: {"enabled": true} 就是启用成功的标志。容器启动日志里也能看到注册了 5 个诊断工具:
Diag agent wired: diagagent=http://172.17.0.1:8788 diaglite=http://172.17.0.1:8787 tools=5
Registered diag tool: diagnose_machine
Registered diag tool: get_diagnosis_session
Registered diag tool: list_recent_diagnosis_sessions
Registered diag tool: list_diagnosis_presets
Registered diag tool: search_diagnosis_kb
结语
从"只能咨询"到"能到节点上执行诊断",算是补上了前一篇的遗憾。
主要的两条命令执行通道的分工为:
- AI 诊断(SSH 旁路):追求灵活,适合只读/诊断类操作,AI 可以自主决定采集哪些信息
- 受控命令下发(agent mTLS):追求可信可审计,关键操作必须有人工审批和制品校验
可以理解为,AI 的"自主性"应该被限制在诊断层面,真正需要"动手"改配置、重启服务这类操作,还是应该走受控通道并保留人工审批环节。如果要让 AI 走 agent 受控通道执行命令(带人工审批),需要在 ocai-service 里新增工具调用 TMS 的 /api/v1/ops_tasks 接口。这需要写代码,是另一个话题了。