
最近把团队内部的任务编排系统整体迁移到了 Rocky Linux 上核心调度组件用的是 Hermes Agent配合 Hermes-Web-UI 做可视化运维。整个过程走下来最有感触的是真正花时间的不是执行dnf install那一下而是安装之前的网络、权限、依赖准备以及安装完之后的对接、调优和踩坑。这篇文章想把从裸机环境到 Agent 和 Web-UI 正常跑通的全过程整理出来包括静态 IP、yum 源、SELinux、systemd 托管、Nginx 反代这些很常见但容易被忽视的细节给准备在 Rocky Linux 上部署 Hermes Agent 和 Hermes-Web-UI 的朋友一个可以直接照着抄的参考。先说下我的环境Rocky Linux 9.4最小化安装四核 CPU、8GB 内存、100GB 系统盘一台纯内网服务器。Hermes Agent 版本是 2.3.1Hermes-Web-UI 版本是 1.8.2。如果你用的是 Rocky 8.10大部分操作一样只有部分命令包名或者仓库地址会有差异我在文中会专门标注。如果你打算把 Hermes Agent 用于生产环境特别是像任务调度、事件驱动的自动化流程、与外部模型接口做集成这类场景这篇文章里提到的坑和建议应该能帮你少走不少弯路。1. 为什么我选了 Rocky Linux 作为 Hermes Agent 的宿主系统1.1 Rocky Linux 与 RHEL 生态的兼容性做自动化运维和 Agent 部署的人对 CentOS 停产这件事应该还有印象。CentOS Linux 8 在 2021 年提前停服之后很多团队转向了 Rocky Linux。它是由 CentOS 原联合创始人 Gregory Kurtzer 发起的项目目标是提供与 RHEL 完全兼容的社区发行版。最直接的好处是二进制兼容 RHEL也就是说在企业级软件认证、依赖库支持、驱动兼容性这些方面Rocky 几乎不会给你找麻烦。Hermes Agent 这种涉及 Python 扩展、数据库驱动、系统级调度的组件如果跑在 Ubuntu 上部分库可能需要自己编译但跑在 Rocky 上很多 RPM 包直接就有省去大量折腾时间。实际测试中Rocky Linux 9 上的 Python 3.9、glibc、OpenSSL 版本都能满足 Hermes Agent 的最低要求而老旧的 CentOS 7 会因为 Python 2/3 混用和 GLIBC 版本太低导致 Agent 无法启动。1.2 Hermes Agent 对系统的要求Hermes Agent 本身是一个基于 Python 编写的轻量级代理程序核心能力是接收任务指令、调用插件或外部工具执行动作、返回结构化结果。它并不像某些重量级框架那样需要专门的运行时环境但有几个硬性条件操作系统Linux x86_64官方测试环境是 RHEL 9 系列Rocky 直接兼容。Python3.10 或更高版本推荐 3.11因为部分依赖的二进制轮子在 3.11 上是最全的。内存最低 2GB实测跑 Agent Web-UI Redis PostgreSQL 四件套4GB 会有点紧建议 8GB。存储Agent 日志和任务产物会占用空间建议至少分配 20GB 给/var。另外如果你想让 Agent 和 Web-UI 分离部署走网络通信那还需要保证服务器之间的 8000、8080、6379、5432 这几个端口能互通。如果都是一台机器那就没那么多讲究。1.3 本文适用范围与版本约定下面的安装过程默认采用「单机部署模式」Agent、Web-UI、Redis、PostgreSQL、Nginx 全部跑在同一台 Rocky 上。这也是大多数人最开始验证功能时的部署方式。文中的命令我都自己跑过你直接复制可能不会百分百成功因为 IP、端口、网卡名要根据自己的环境改但大方向是不变的。我给出的命令统一使用dnf如果你用的是 Rocky 8dnf同样生效8 默认也是 dnf。遇到[Errno 2] No such file or directory这类错误先检查路径是不是少了一个/再检查是不是用了普通用户没有权限。建议全程用 root 或者一个有 sudo 权限的账号操作。2. 先把系统地基打好静态 IP、yum 源和基础环境2.1 通过 nmcli 永久设置静态 IP很多人在安装 Rocky 时图省事选了 DHCP但 Agent 部署好之后如果每次重启 IP 都会变那 Web-UI 上的 Agent 地址、Nginx 反代、防火墙规则全都会失效。所以我首先做的是把 IP 固定。Rocky 默认使用 NetworkManager 管理网络我习惯直接用nmcli比改配置文件更不容易出错。先查看当前网络连接名称和网卡nmcli connection show我这边有两个连接一个是ens160一个是ens224。要用的是ens160所以我对它进行修改nmcli con mod ens160 ipv4.addresses 192.168.10.100/24 nmcli con mod ens160 ipv4.gateway 192.168.10.1 nmcli con mod ens160 ipv4.dns 223.5.5.5 119.29.29.29 nmcli con mod ens160 ipv4.method manual nmcli con up ens160con mod是修改连接配置con up让配置立即生效。验证就简单了ip addr show ens160 ping -c 3 192.168.10.1如果你不想用阿里 DNS就改成自己内网 DNS 服务器地址。注意set命令只会临时生效mod才会永久写进配置文件别用错。另外在真正的生产环境最好通过 DHCP 保留地址或者在网络设备上做绑定这里直接静态 IP 是为了保证 Agent 服务重启后宿主 IP 不变。2.2 替换为国内可用的 yum 源以 Rocky 8/9 为例安装依赖包时如果直接从 Rocky 官方源拉取速度经常让人崩溃尤其是国内服务器。我直接换成了阿里云镜像。先备份mkdir -p /etc/yum.repos.d/backup cp /etc/yum.repos.d/*.repo /etc/yum.repos.d/backup/然后修改Rocky-BaseOS.repo、Rocky-AppStream.repo、Rocky-Extras.repo里的baseurl和mirrorlist。最简单的方式是把mirrorlist那行注释掉把baseurl改成阿里云地址。以 Rocky 9 为例Rocky-BaseOS.repo最终内容大致是这样的[baseos] nameRocky Linux $releasever - BaseOS baseurlhttps://mirrors.aliyun.com/rockylinux/$releasever/BaseOS/$basearch/os/ gpgcheck1 enabled1 gpgkeyfile:///etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9其他两个仓库同理把路径里的BaseOS换成AppStream、Extras。修改完成后dnf clean all dnf makecache实测下来阿里云的速度是官方源的十几倍。如果你用的是 Rocky 8.10仓库名略有不同比如PowerTools对应 $releasever 里的PowerTools但思路是一样的。还有个细节有些内网环境会屏蔽外网或者只有代理才能出去那可以配置proxyhttp://你的代理IP:端口到/etc/dnf/dnf.conf或者直接用内网镜像源。2.3 关闭 SElinux 的误区和防火墙端口放行关于 SELinux网上很多教程上来就教你setenforce 0我建议稍微克制一点。SELinux 保护的是系统级安全边界如果你跑的是内部实验环境关闭或者置为 permissive 问题不大如果是生产环境Agent 和 Web-UI 涉及很多动态文件读写、端口监听SELinux 默认策略有可能触发大量告警甚至拦截。最稳妥的做法是先把 SELinux 设为 permissive只告警不拦截观察一段时间 Agent 运行日志再决定是否彻底关闭。修改配置文件实现永久设置vi /etc/selinux/config把SELINUXenforcing改成SELINUXpermissive保存后重启服务器。如果你不想重启也可以先setenforce 0临时生效。注意permissive 模式只是在日志里记录违规不会阻止但生产环境我还是建议强制然后针对 Agent 的端口、目录写自定义策略不过这一般需要单独的软工介入不在本文展开。防火墙方面Rocky 默认启用 firewalld。Agent 通信需要放开几个端口firewall-cmd --permanent --add-port8080/tcp # Agent 服务端口 firewall-cmd --permanent --add-port8000/tcp # Web-UI 后端 firewall-cmd --permanent --add-port6379/tcp # Redis firewall-cmd --permanent --add-port5432/tcp # PostgreSQL firewall-cmd --reload如果你不需要外部访问数据库和缓存可以只放行 8080 和 8000其他端口绑定在 127.0.0.1 上。在postgresql.conf中可以设置listen_addresses 127.0.0.1Redis 的bind 127.0.0.1这样对外开放面更小。3. Hermes Agent 本体安装从源码编译到 systemd 托管3.1 依赖组件清单及安装命令Hermes Agent 依赖的组件不少但都是主流软件。我在部署前先把这些装好避免中途缺这缺那。下面是我在 Rocky 9.4 上使用的安装命令dnf install -y python3.11 python3.11-pip python3.11-devel redis postgresql-server postgresql-contrib git nginx有些发行版的 Python 3.11 不在默认源里但 Rocky 9.4 带了python3.11包直接可用。装好之后检查python3.11 --version # Python 3.11.9Redis 和 PostgreSQL 都需要启动服务systemctl enable --now redis systemctl enable --now postgresql不过 PostgreSQL 第一次安装后需要初始化数据库目录postgresql-setup --initdb systemctl start postgresql systemctl enable postgresql然后设置 PostgreSQL 用户和数据库供 Agent 和 Web-UI 使用sudo -u postgres psql -c CREATE USER hermes WITH PASSWORD your_password; sudo -u postgres psql -c CREATE DATABASE hermes_agent OWNER hermes; sudo -u postgres psql -c CREATE DATABASE hermes_webui OWNER hermes;为了让本地程序能通过密码认证连接 PostgreSQL要修改pg_hba.conf把scram-sha-256对应的连接方式改为md5或者scram-sha-256取决于你 PostgreSQL 版本然后重启服务。Node.js 如果安装了自带前端依赖也需要准备。Rocky 9 源里的 Node 版本比较老我是直接使用 NodeSource 仓库curl -fsSL https://rpm.nodesource.com/setup_20.x | bash - dnf install -y nodejs注意这里用到了外网脚本如果你在内网环境可以手动下载 Node 二进制包放到服务器上解压并加入到 PATH。3.2 获取 Hermes Agent 并完成安装Hermes Agent 官方提供了编译好的发布包省去从源码构建的麻烦。我一般是从 GitHub Releases 页面下载hermes-agent-2.3.1-linux-x64.tar.gz放到/opt下解压cd /opt curl -L -o hermes-agent.tar.gz https://github.com/hermes-agent/hermes-agent/releases/download/v2.3.1/hermes-agent-2.3.1-linux-x64.tar.gz tar -zxvf hermes-agent.tar.gz mv hermes-agent-2.3.1 hermes-agent cd hermes-agent进入目录后建议创建 Python 虚拟环境避免和系统 Python 环境冲突python3.11 -m venv venv source venv/bin/activate pip install --upgrade pip pip install -r requirements.txt这个requirements.txt里包含 FastAPI、SQLAlchemy、Redis、PSQL 驱动等一堆包安装时间取决于网络。如果遇到编译错误需要先安装gcc和python3.11-develdnf install -y gcc gcc-c make安装完成后可以快速验证版本./bin/hermes-agent --version如果显示类似Hermes Agent v2.3.1的信息说明安装成功。这里要提一句如果你想直接用源码跑也可以在 GitHub 上git clone --depth1 https://github.com/hermes-agent/hermes-agent.git然后同样创建 venv 并安装。但源码分支更新快不一定稳定我建议第一版先使用 Releases 包。3.3 生成配置文件并跑通第一个示例任务Hermes Agent 安装完成后目录里会有一个config.example.yaml把它复制成config.yamlcp config.example.yaml config.yaml核心配置项包括agent: name: agent-01 listen_host: 0.0.0.0 listen_port: 8080 api_key: 你的随机密钥 database: url: postgresqlpsycopg2://hermes:your_password127.0.0.1/hermes_agent redis: url: redis://127.0.0.1:6379/0 task_sources: - type: scheduler enabled: true - type: http enabled: truetask_sources是任务的来源我一开始只开了scheduler定时任务以及http能通过 REST API 下发任务。如果后续想接消息队列或者 webhook可以在配置文件里扩展。配置文件搞定后先初始化数据库表source venv/bin/activate ./bin/hermes-agent init-db看到类似tables created successfully的输出说明数据库没问题。然后启动 Agent./bin/hermes-agent serve如果一切正常日志里会出现Agent started并且监听 8080 端口。现在来创建一个最简单的示例任务让 Agent 每隔 5 分钟执行一次df -h命令并记录磁盘状态。在 Agent 根目录下的tasks/文件夹里新建disk_check.yamlname: disk_check schedule: */5 * * * * action: type: shell command: df -h保存后Agent 会在下一个调度周期自动加载。你可以在 Web-UI 或者通过 HTTP API 查看任务执行记录。如果想立刻触发一次可以调用curl -X POST http://127.0.0.1:8080/api/tasks/disk_check/execute \ -H Authorization: Bearer 你的随机密钥响应里会有任务的执行结果返回状态码 0 就代表成功。至此Agent 的基本功能已经跑通了。4. Hermes-Web-UI 部署与 Agent 对接4.1 Web-UI 服务的整体架构老是一堆任务看不了历史也看不到执行结果所以必须安排一个 Web 界面。Hermes-Web-UI 本质上是 Hermes Agent 的可视化控制台采用前后端分离架构。后端是 Python FastAPI负责读数据库、暴露 REST API、和 Agent 维持 WebSocket 长连前端是 Vue 3 构建的静态文件直接由 FastAPI 托管也可以单独用 Nginx 跑。整体流程是浏览器访问 Web-UI - 后端从 PostgreSQL 读取任务列表和日志 - 后端通过 API Key 与 Agent 通信 - Agent 执行完把结果回传给后端。Web-UI 并不是简单把 Agent 的日志打印出来它有完整的任务生命周期管理待执行、运行中、成功、失败还可以通过页面直接创建和停止任务。4.2 安装后端 API 与前端静态资源和 Agent 类似Web-UI 同样提供发布包。我下载的是hermes-webui-1.8.2-linux-x64.tar.gz解压到/opt/hermes-webuicd /opt curl -L -o hermes-webui.tar.gz https://github.com/hermes-agent/hermes-webui/releases/download/v1.8.2/hermes-webui-1.8.2-linux-x64.tar.gz tar -zxvf hermes-webui.tar.gz mv hermes-webui-1.8.2 hermes-webui cd hermes-webui创建 Python 虚拟环境并安装依赖python3.11 -m venv venv source venv/bin/activate pip install -r requirements.txt然后编辑config.yaml或者.env文件设置数据库连接、Agent 注册地址、JWT 密钥等database: url: postgresqlpsycopg2://hermes:your_password127.0.0.1/hermes_webui agent: api_base_url: http://127.0.0.1:8080 api_key: 和Agent端一样的随机密钥 server: host: 0.0.0.0 port: 8000 auth: jwt_secret: 生成一个随机密钥用openssl rand -hex 32 admin_user: admin admin_password: 初始化密码然后初始化数据库./bin/hermes-webui init-db启动后端./bin/hermes-webui serve在浏览器访问http://服务器IP:8000应该能看到登录页面。用配置文件里的admin_user和admin_password登录进去第一件事就是改掉初始密码。前端静态资源已经集成在后端的static目录下不需要再单独构建。如果你从源码自己构建前端则需要npm install npm run build再把dist目录内容拷到后端static下。4.3 通过 Nginx 统一入口并绑定 Agent默认的 8000 端口看起来不专业而且如果前端以后要上 HTTPS建议统一通过 Nginx 暴露。我写了一个最简单的 Nginx 配置把 80 端口反代到本机的 8000server { listen 80; server_name hermes.example.com; location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # WebSocket 走独立的 location location /ws/ { proxy_pass http://127.0.0.1:8000/ws/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_read_timeout 3600s; proxy_send_timeout 3600s; } }保存到/etc/nginx/conf.d/hermes-webui.conf然后nginx -t systemctl reload nginx现在访问http://服务器IP就能打开 Web-UI 了。WebSocket 的 location 里Connection upgrade很容易被遗忘一旦漏掉页面上的任务进度不会实时刷新必须先配置好。Agent 绑定到 Web-UI 有两种方式一种是在 Web-UI 的后台界面添加 Agent填写 Agent 的地址和 API Key另一种是在 Agent 的config.yaml中指定web_ui_url这样 Agent 启动时会主动向 Web-UI 注册。我采用了后者配置如下web_ui: url: http://127.0.0.1:8000 report_interval: 30重启 Agent 后Web-UI 的 Agent 列表里就会出现agent-01状态显示在线。从这以后创建任务、看日志、看健康检查结果都在浏览器里完成。5. 生产环境里容易被忽视的五个坑5.1 Python 版本不对导致 Agent 崩溃我第一遍部署时用了系统自带的 Python 3.9结果pip install有些包没有预编译轮子编译时直接报错。后来换用python3.11才顺利。如果你的系统没有 Python 3.11 包可以手动添加 EPEL 源或者其他第三方源但要注意不要影响系统默认 Python。我的经验是在虚拟环境里指定 Python 解释器版本比如创建 venv 时用python3.11 -m venv venv这样所有依赖都装在隔离环境里和系统 Python 互不干涉。还有一个类似的坑pip装上后找不到命令。那是没有把 venv 的 bin 目录激活或者没有用./bin下的启动脚本。Hermes Agent 目录下的bin/hermes-agent本身就是包装好的可执行文件它会自动使用 venv 里的 Python优先用这个。5.2 时区与日志切割设置Agent 默认使用 UTC 时间而我的服务器是 UTC8如果定时任务要按北京时间执行任务调度就会差 8 个小时。解决办法是在/etc/profile或者启动脚本里设置环境变量export TZAsia/Shanghai然后在 Agent 配置文件里确认timezone字段比如timezone: Asia/Shanghai日志方面Agent 默认会把日志写到/var/log/hermes-agent/下如果不做切割长时间运行后日志文件会非常大。我在服务器上配置了一个logrotate规则cat /etc/logrotate.d/hermes-agent EOF /var/log/hermes-agent/*.log { weekly rotate 12 compress delaycompress missingok notifempty copytruncate } EOFcopytruncate很关键因为 Agent 进程可能一直持有文件句柄直接 rotate 可能会丢日志copytruncate可以保证在不清空句柄的情况下轮转。5.3 Agent 和 Web-UI 的认证令牌丢失有时候重启 Agent 之后Web-UI 里显示 Agent 离线过一会儿又自己恢复了。如果长时间离线大概率是 API Key 不一致。Agent 的 config.yaml 里有一串api_keyWeb-UI 的.env里也有对应配置这两者必须完全一致我一开始只改了 Agent 端没改 Web-UI 端导致握手失败。还有一个容易踩的是有些版本把 Key 放在数据库里存哈希如果你用init-db重新初始化数据库原来的绑定关系就丢了。解决方法是先确认两者配置相同再在 Web-UI 的后台删除旧 Agent 重新添加。5.4 内存占用持续上涨的排查跑了两周后我发现 Agent 进程 RSS 从 300MB 慢慢涨到 1.5GB明显不正常。造成这个问题的原因很多我总结出两个排查方向Redis 连接没释放。如果你的 Agent 任务里大量使用了 Redis 缓存但连接池设置过大或者没有回收会导致内存飙升。可以在 Redis 侧执行client list看连接数。Python 子进程泄漏。当任务类型是shell或subprocess时如果没有正确关闭子进程句柄会造成僵尸进程和内存占用异常。排查工具我用的是py-spypip install py-spy py-spy dump --pid agent_pid然后看进程调用栈定位到具体是哪个模块在吃内存。确定问题后可以在任务执行的代码里加gc.collect()或者调整max_workers限制并发任务数。5.5 重启后服务不自动恢复的处理这是最容易忽略的。如果服务器重启Agent 和 Web-UI 不会自己启动必须把服务托管给 systemd。我在 Agent 目录下创建了一个单元文件cat /etc/systemd/system/hermes-agent.service EOF [Unit] DescriptionHermes Agent Afternetwork.target postgresql.service redis.service Wantsnetwork.target [Service] WorkingDirectory/opt/hermes-agent ExecStart/opt/hermes-agent/bin/hermes-agent serve Restartalways RestartSec10 Userroot EnvironmentTZAsia/Shanghai [Install] WantedBymulti-user.target EOFWeb-UI 类似只是ExecStart改成/opt/hermes-webui/bin/hermes-webui serve。然后systemctl daemon-reload systemctl enable --now hermes-agent systemctl enable --now hermes-webui添加了Restartalways之后即使 Agent 因为某个任务崩溃systemd 也会在 10 秒后把它拉起来。这个比任何守护进程脚本都省心。这里我还遇到一个细节启动时如果Afterpostgresql.service设置了但 PostgreSQL 因为上次异常退出没启动成功Agent 也会启动失败。这时可以看日志先确保 PostgreSQL 正常运行再启动 Agent。结尾 一些使用后的体会整套系统跑下来我最大的感受是Hermes Agent 的灵活性很高但前提是基础环境足够干净。把静态 IP、yum 源、Python 版本、数据库权限这些基础做好之后后面的部署其实非常顺滑。最后再分享一个小技巧当你需要用 Web-UI 调度 Agent 执行敏感操作时不要在 Agent 配置里明文存储密码建议通过环境变量引用比如在config.yaml里写password: ${DB_PASSWORD}启动脚本里预先export DB_PASSWORDxxx。这样即使配置文件被误上传到代码仓库密码也不会泄露。另外如果你在 Agent 里接了外部的大模型 API注意设置一个合理的超时时间否则一个耗时任务会把整个 worker 池占满。我是在 Agent 的 HTTP 请求插件里把默认超时从 30 秒调到了 120 秒才避免了大模型响应慢导致的连锁超时。根据我自己的使用体验把这个 Agent Web-UI 方案用于中小规模的自动化运维任务编排是绰绰有余的但如果要支撑每天数万次任务的规模还是建议把 Agent 单独拆出去让 Web-UI 和数据库跑在独立的机器上。