一个自托管的服务器性能监控面板:被控(被监控的服务器)上运行轻量 Agent 周期性采集 CPU / 内存 / 硬盘 / 网络 / 多节点 Ping 延迟并上报;主控聚合后落库,前端展示列表与详情。
- 主控后端 / 被控 Agent 均为 Go 单文件静态二进制(零依赖)。
- 存储用 SQLite 单文件(纯 Go 驱动,无 cgo)。
- 实时数据只刷新 latest_snapshot 供前端展示,不落历史库,失败不重试。
- 历史趋势数据由被控本地做 10 分钟窗口聚合后,通过
/api/history上报主控落库;失败本地持久化 + 自动重试。 - 前端 Vue3 + Vite + Element Plus,由主控直接托管
dist/(无需额外 nginx,公网部署时再在前面套 nginx 配证书)。 - 部署方式:Docker(Alpine 3.21 基础镜像),构建一次、随处运行。
被控 Agent (Go)
│ 每 COLLECT_INTERVAL 秒采集一次
│ POST /api/ingest(实时快照,HMAC 验签,失败不重试)
│ 本地 10 分钟窗口聚合 → POST /api/history(失败本地持久化 + 自动重试)
▼
主控 Server (Go :9000)
├─ /api/ingest 刷新 latest_snapshot(列表/详情读它,极轻,不落历史)
├─ /api/history 直接落库 metrics_agg(保留 RETENTION_DAYS,默认 7 天)
└─ 托管前端 dist/ + REST API
│
▼
SQLite (promonitor.db) + 前端轮询(每 10s)
鉴权边界:
- 匿名可读:服务器列表、服务器详情历史。
- 必须登录(单管理员
admin):管理页增删改被控、修改密码。服务端RequireAdmin中间件做真拦截,前端路由守卫仅作体验层。
server/ 主控后端(Go):config / store(sqlite) / ingest(hmac) / aggregator / auth / api
agent/ 被控 Agent(Go):gopsutil 采集 + HMAC 上报
src/ 前端(Vue3 + Vite + Element Plus)
migrations/ SQL 参考(实际建表由 server 启动时自动执行 schema)
scripts/ 构建/冒烟脚本(smoke.mjs)
Dockerfile / docker-compose.yml / .dockerignore 容器化部署
bin/ 预编译静态二进制(linux amd64/arm64,供非 Docker 部署直接取用)
前置:已安装 Docker。三种方式任选其一:从 GitHub 拉镜像(免构建)、docker run(推荐,单命令) 或 docker compose。
# 1) 拉取 CI 构建的镜像(首次或代码更新后,无需本地构建)
docker pull ghcr.io/letsopen/ghcr.io/letsopen/promonitor:latest
# 2) 启动主控(TCP 延迟探测,默认)
docker run -d --name promonitor --restart unless-stopped \
-p 9000:9000 \
-e HMAC_SECRET='<与所有被控一致的随机长串>' \
-e ADMIN_PASS='<初始管理员密码>' \
-e SESSION_SECRET='<随机串>' \
-e PING_TYPE=tcp \
-v promonitor-data:/app/data \
ghcr.io/letsopen/promonitor:latest
# 3) 访问面板 → 管理后台 → 延迟节点 → 添加探测节点(id/name/ip/port)
# 被控会自动从主控拉取节点清单,无需在环境变量配置若使用 ICMP 延迟探测(PING_TYPE=icmp):Alpine 容器默认无 CAP_NET_RAW,
raw socket 无法建立、ICMP 探测会全部失败,必须给容器显式加上该能力:
docker run -d --name promonitor --restart unless-stopped \
-p 9000:9000 \
--cap-add NET_RAW \ # ← ICMP 模式必需
-e HMAC_SECRET='<与所有被控一致的随机长串>' \
-e ADMIN_PASS='<初始管理员密码>' \
-e SESSION_SECRET='<随机串>' \
-e PING_TYPE=icmp \
-v promonitor-data:/app/data \
ghcr.io/letsopen/promonitor:latest提示:
docker run无法直接读取.env,密钥用-e KEY=value逐项传入; 嫌长可用--env-file .env(此时.env需含HMAC_SECRET、ADMIN_PASS、PING_TYPE等)。
停止 / 查看日志 / 升级:
docker stop promonitor && docker rm promonitor # 停止并移除容器(数据在 promonitor-data 卷里保留)
docker logs -f promonitor # 查看日志
docker pull ghcr.io/letsopen/promonitor:latest && docker run ... # 拉取新镜像后重新启动# 1) 准备环境变量
cp .env.example .env
# 编辑 .env,至少填入 HMAC_SECRET(被控/主控必须一致)与 ADMIN_PASS
# 2) 拉取并启动(镜像由 GitHub Actions 构建推送 GHCR,本地仅 pull,不执行构建)
docker compose up -d
# 3) 访问面板
# http://<你的服务器IP>:9000
# 首次用 admin / 你在 .env 设置的 ADMIN_PASS 登录compose 使用 ICMP 时同样需要加能力:在
docker-compose.yml的promonitor服务下加一行cap_add: [NET_RAW](并设置PING_TYPE=icmp)。 节点清单在管理后台 → 延迟节点 维护,不通过.env配置。
- 数据持久化在名为
promonitor-data的卷(/app/data),重建容器不丢数据。 - 想改端口:
docker-compose.yml的ports改成"8080:9000"之类。 - 公网 HTTPS:在容器前再放一个 nginx 反代并配证书(主控本身不内置 TLS)。
Agent 是独立二进制,跑在被监控的机器上,不是面板容器里。
用同一个 promonitor 镜像,以 promonitor-agent 覆盖默认命令启动(无需另装二进制):
# TCP 延迟探测(默认,跟随主控 PING_TYPE 配置)
docker run -d --name promonitor-agent --restart unless-stopped \
--network host \
-e MASTER_URL='http://<主控IP>:9000' \
-e HMAC_SECRET='<与主控一致的 HMAC_SECRET>' \
-e SERVER_ID='web-01' \
-e SERVER_NAME='阿里云-杭州' \
-e SERVER_IP='10.0.0.5' \
ghcr.io/letsopen/promonitor:latest promonitor-agent若主控配置了 ICMP 延迟探测:被控容器同样需要 --cap-add NET_RAW,否则 ICMP 探测全部失败:
docker run -d --name promonitor-agent --restart unless-stopped \
--network host \
--cap-add NET_RAW \ # ← ICMP 模式必需
-e MASTER_URL='http://<主控IP>:9000' \
-e HMAC_SECRET='<与主控一致的 HMAC_SECRET>' \
-e SERVER_ID='web-01' \
-e SERVER_NAME='阿里云-杭州' \
-e SERVER_IP='10.0.0.5' \
ghcr.io/letsopen/promonitor:latest promonitor-agent
--network host:让容器直接使用宿主机网络,便于访问主控并让探测流量走宿主网络栈 (探测目标通常是公网,无需 host 也可,但 host 模式最简单)。节点清单由主控 管理后台维护,被控无需再配置PING_*。
取二进制(二选一):
- 直接用仓库
bin/promonitor-agent_linux_amd64(arm64 用bin/promonitor-agent_linux_arm64)。 - 或从镜像里拷出:
docker create --name tmp ghcr.io/letsopen/promonitor:latest && docker cp tmp:/usr/local/bin/promonitor-agent ./ && docker rm tmp。
运行(环境变量或命令行 flag 均可):
promonitor-agent \
-master https://your-panel.example.com \
-secret "<与主控一致的 HMAC_SECRET>" \
-id web-01 \
-name "阿里云-杭州" \
-ip 10.0.0.5 \
-interval 30s延迟节点与方法由主控统一下发:Agent 启动即拉取
GET /api/ping-config, 之后每 5 分钟重新拉取(热更新),无需在被控侧配置探测目标。--targets(或PING_TARGETS)仅作为主控未配置任何节点时的可选回退覆盖。
flag 与环境变量对照:
| flag | 环境变量 | 说明 |
|---|---|---|
-master |
MASTER_URL |
主控地址(https 需目标机有 CA 证书) |
-secret |
HMAC_SECRET |
HMAC 预共享密钥(必填,与主控一致) |
-id |
SERVER_ID |
被控唯一 ID(必填,管理页增删时保持一致) |
-name |
SERVER_NAME |
展示名,默认同 ID |
-ip |
SERVER_IP |
被控 IP(可选) |
-targets |
PING_TARGETS |
可选:逗号分隔,最多 50 个节点;仅当主控未配置节点时回退使用 |
-interval |
COLLECT_INTERVAL |
实时采集间隔(秒),默认 30,最低 30,且必须是 30 的倍数 |
实时链路:每 COLLECT_INTERVAL 秒采集 CPU/内存/硬盘/网络/Ping 并 POST /api/ingest,
主控只刷新 latest_snapshot,失败不重试(只有最新数据有价值)。
历史链路:被控在本地按整数 10 分钟边界(0/10/20/... 分)聚合,通过 POST /api/history 上报;
失败则持久化到本地 SQLite (agent.db),后台自动重试,成功后才删除。网络恢复后历史数据会自动续传。
被控首次上报即自动注册到主控(无需先在管理页添加)。
- 列表页、详情页读取
latest_snapshot(每被控最新一组指标),前端每 10s 轮询一次。 - 详情页历史曲线读取
metrics_agg(每 10 分钟 1 行均值),保留期由RETENTION_DAYS控制(默认 7 天)。 - 实时高频上报只刷新 latest_snapshot,不落历史库;历史聚合由被控在本地完成后再上报,对服务端资源极友好。
| 变量 | 必填 | 默认 | 说明 |
|---|---|---|---|
PORT |
否 | 9000 |
监听端口 |
DB_PATH |
是* | — | SQLite 文件路径(容器内默认 /app/data/promonitor.db) |
HMAC_SECRET |
是 | — | 被控上报验签密钥(为空则启动报错) |
ADMIN_USER |
否 | admin |
管理员账号 |
ADMIN_PASS |
否 | — | 首次启动以此初始化 admin 密码(docker 推荐通过 .env 设置) |
SESSION_SECRET |
否 | change-me-session-secret |
会话 Cookie 签名密钥 |
FRONTEND_DIR |
否 | ./dist |
前端静态目录 |
PING_TYPE |
否 | tcp |
延迟探测方式:tcp(TCP 端口探测)或 icmp(需容器 --cap-add NET_RAW) |
RETENTION_DAYS |
否 | 7 |
历史聚合数据保留天数(最小 1),超期数据每日自动清理 |
节点清单不再通过环境变量维护,部署后进入 管理后台 → 延迟节点 添加(存 SQLite
ping_nodes表)。
DB_PATH/HMAC_SECRET在代码中缺失会log.Fatal退出,属"fail fast"。
本项目源码构建需要 Go ≥ 1.20,原因是纯 Go 的 SQLite 驱动 modernc.org/sqlite 与
gopsutil/v4 的最低要求分别为 1.20 / 1.18。因此:
- ❌ 无法用 debian 11 默认软件源的 Go 1.15 从源码直接构建(驱动底层卡死,非业务代码问题)。
- ✅ Docker 部署完全规避此问题:Go 二进制由 GitHub Actions(
golang:1.23)交叉编译为CGO_ENABLED=0静态二进制,运行期alpine:3.21镜像只做组装,目标机不需要安装任何版本的 Go。 - ✅ 若不使用 Docker、直接在目标机跑二进制:直接用仓库
bin/下已编译好的linux-amd64/linux-arm64静态二进制即可,同样不需要目标机装 Go。
源码层面已尽量老 Go 友好:去掉了 //go:embed(改用内联 SQL 字符串)、go.mod 的
go 指令设为 1.18;真正构建下限由上述依赖决定为 1.20。
被控会在启动目录生成 agent.db(SQLite),用于持久化待上传的 10 分钟聚合记录。
如果容器化部署,建议把 agent.db 所在目录挂载到宿主机或卷,避免容器重启丢失未上传的历史数据。
# 后端(需要 Go >= 1.20)
cd server && CGO_ENABLED=0 go build -o ../bin/promonitor-server ./cmd/server
cd ../agent && CGO_ENABLED=0 go build -o ../bin/promonitor-agent .
# 前端
npm install && npm run build # 产出 dist/
# 本地冒烟测试(先起 server,再跑)
PORT=9000 DB_PATH=./test.db HMAC_SECRET=testsecret ADMIN_PASS=admin123 \
FRONTEND_DIR=./dist ./bin/promonitor-server &
node scripts/smoke.mjs冒烟脚本覆盖:HMAC 验签、匿名列表、首次上报自动注册、鉴权拦截(401)、登录、增删被控、 改密(旧密码失效/新密码可用)、登出、/api/history 批量聚合上报、历史接口 —— 全绿即通过。