刚把一台测试服务器从旧系统迁移到 openEuler 上时,顺手搭了一套轻量监控服务,就是标题里写的 Coolmonitor。这个过程不算复杂,但中间踩了几个不算深也不算浅的坑,正好有人问起,就整理一篇完整记录出来。
先说结论:如果只是几台服务器、想快速看清 CPU、内存、磁盘和网络的实时状态,不想为了看个曲线图就上 Prometheus + Grafana 那一整套,Coolmonitor 这种轻量方案非常合适。它采集指标、存数据、画图表、告警一条线全包,架构简单,维护成本低,跑在 openEuler 上尤其顺手。
这篇内容适合自己摸索过 Linux 但没正经搭过监控系统的朋友,也适合团队里需要给内网机器快速补一个“看得见”的运维面板的同行。我会把环境准备、整体设计、核心代码、部署步骤和排查实录都过一遍,偏实战。
1. 为什么要在 openEuler 上搭这么一套监控服务
1.1 先聊聊 openEuler 这个系统
openEuler 是华为发起的开源 Linux 发行版,本质上和 CentOS/RHEL 系同源,用 rpm 包、用 dnf/yum 管理软件,systemd 管理服务。如果你原来玩过 CentOS 7 或 8,切到 openEuler 几乎没什么学习成本,命令基本都能沿用。
这两年 CentOS 8 停更,CentOS Stream 又不适合直接当生产系统用,很多人都开始往替代品上迁。openEuler 在国内的镜像源、文档、社区支持都比较全,而且官方对 ARM 架构支持得很好,飞腾、鲲鹏这些国产芯片上跑起来都没问题。虽然我们这次是在 x86 机器上演示,但在 ARM 机器上步骤完全一样,这一点也是很多人最终选它的原因。
还有一个很现实的点是生命周期。openEuler 的 LTS 版本支持周期很长,比如 22.03 LTS 系列有社区维护,安全补丁跟得上,这对服务器系统来说很关键。监控服务是要 7×24 小时跑的,底下的系统如果动不动就没人管了,上面再好的方案都白搭。
1.2 Coolmonitor 是什么,为什么选它
Coolmonitor 名称里带了个 Cool,实际用起来也确实轻巧。它不是 Zabbix 那种重客户端 + 服务端的架构,也不是 Prometheus 那种拉取 + 时序数据库的组合。对个人和小团队来说,Coolmonitor 更像一个“开箱即用的小面板”:在被监控机器上跑一个进程,它负责采集系统指标、写入本地存储、提供 HTTP 接口,浏览器打开页面就能看到实时曲线。
我当时选它有三个原因。第一,部署足够简单,依赖就 Python3 + 几个 pip 包,没有单独的 agent、server、数据库三层概念,一个进程全搞定。第二,指标够用,CPU、内存、磁盘、网络这些最常看的都有,还能自定义扩展。第三,后续想要告警,直接在代码里加逻辑就行,不用去学一套新的模板语法。
当然,它也有不完美的地方。多主机集中管理不是它的强项,如果以后机器数量多了,还是得上 Prometheus。但在十台以内、以单机自检为目标的场景下,Coolmonitor 的效率是真高,从零到看到图表大概也就半小时。
1.3 和 Prometheus、Zabbix 这些老牌监控比,它赢在哪
这里不踩一捧一,就说说适用边界。Prometheus 强在指标收集模型和生态,Grafana 图表也确实漂亮,但要完整落地你需要部署 Prometheus、部署 exporter、部署 Grafana,再配置数据源和 dashboard,这一套下来光是理解架构流程就需要点时间。Zabbix 更重,还要管理 agent 生命周期。
Coolmonitor 的思路完全不同:它把这些东西简化成了一个服务。采集逻辑我直接在进程里写,存储落 SQLite,图表用前端开源库画,告警就是一段阈值判断。可以说它把“监控”这件事用最小的成本做到了“够用”的程度。
打个比方,Prometheus 是开一个中央厨房,配菜、炒菜、上菜全标准化,适合几十上百桌的宴席;Coolmonitor 更像是自己在家开小灶,一个灶台一口锅,三五个人吃饭很快就上桌。你要真问哪个好,得看你的饭桌有多大。
2. 动手前的准备:环境梳理与基础配置
2.1 安装 openEuler 时容易忽略的几个点
如果你是从零开始装 openEuler,建议直接去官网下载 22.03 LTS 版本的 DVD ISO。安装过程比 CentOS 还简单,图形界面引导很成熟。但有几个点我想单独提一下,都是我装过几台机器后总结的。
分区的时候,如果这台机器专门跑监控服务,给根目录多留点空间。SQLite 数据库文件不算大,但如果每 5 秒写一次指标,一个月下来数据量也能到几十 MB,加上系统日志和镜像缓存,根分区 50G 起步不亏。
语言和时区建议装的时候就选好中文和 Asia/Shanghai,不然后面日志时间对不上很容易懵。另外如果你是在虚拟机上装,网络模式建议用桥接,别用 NAT,尤其是后面想用其他机器访问监控页面的时候,省得再折腾端口转发。
装完系统第一件事是更新系统源并升级到最新补丁级别,然后再开始装依赖。这个顺序别乱。
2.2 换源与基础依赖安装
openEuler 默认的软件源在一些网络环境下速度一般,建议换成国内镜像源。网上搜“openEuler 换源”能找到对应版本的镜像站配置,一般就是把/etc/yum.repos.d/openEuler.repo里的 baseurl 换成镜像地址,注意把$releasever对应的版本号写对。
换完源执行一下清理和缓存生成:
dnf clean all dnf makecache然后安装基础依赖。Coolmonitor 核心是 Python3,用的包有 psutil(采集系统指标)、Flask(提供 HTTP 接口)、flask-cors(解决前端跨域)。openEuler 自带的 Python3 版本足够用,不需要编译升级。
dnf install -y python3 python3-pip git pip3 install psutil flask flask-cors如果是在内网环境,pip 源也可以换成国内的,比如清华源。这里有个小经验:pip3 install后用pip3 list确认一下安装版本,有时候系统里同时存在多个 Python 版本,容易装错环境。
2.3 把服务交给 systemd 管理而不是手动 nohup
很多人图省事,直接nohup python3 app.py &就把服务跑起来了。这在开发环境没问题,但作为常驻服务不太稳妥。进程意外退出没人拉起来、开机不会自启、日志管理全靠重定向,维护体验很差。
openEuler 和所有现代 Linux 发行版一样,用 systemd 管理服务。把监控服务写成一个 systemd unit 文件,配置好启动命令、工作目录、日志输出,就能获得开机自启、崩溃自动重启这些能力。
写成 unit 文件的另一个好处是可以用systemctl status、journalctl -u coolmonitor这些标准命令查看运行状态和日志,排障效率高得多。这一步一定要做,后面部署时我会贴出完整的 unit 文件内容。
3. 监控服务整体设计:采集、存储、展示三层拆解
3.1 采集层:用 psutil 一次拿全所有指标
做监控服务,第一步是解决“数据从哪来”的问题。我自己用下来最顺手的方案就是 psutil,它是 Python 的一个系统信息库,CPU、内存、磁盘、网络、进程、传感器信息全能拿,API 设计得也很简洁。
以 CPU 为例,psutil.cpu_percent(interval=1)返回的是一个 CPU 使用率百分比,注意这里interval=1表示 1 秒内的平均值,能平滑掉瞬时抖动。内存使用率用psutil.virtual_memory().percent,磁盘用psutil.disk_usage('/').percent,网络则用psutil.net_io_counters()取累计收发的字节数。
网络这块有个细节容易踩坑:net_io_counters()返回的是累计值,不是实时速率。想算每秒速率,需要在两次采样之间做差值,再除以时间间隔。这个逻辑是监控服务常见的设计点,后面代码里我会写清楚。
采集频率的设置也要想清楚。我实测下来 5 秒采一次比较合适,数据能形成平滑曲线,SQLite 写入压力也很小。如果间隔太短,比如 1 秒一次,短期数据是好看了,但存储膨胀快,而且刷新页面时前端反而会因为数据点太多而出现拥挤的曲线,视觉效果下降。
3.2 存储层:小规模监控没必要上时序数据库
数据采集下来必须落地,不然服务一重启就全丢了。选存储的时候有人会想到用 MySQL 或者时序数据库,但在这个场景下我觉得有点杀鸡用牛刀。
SQLite 够用了。它是文件型数据库,不需要单独的数据库服务进程,数据就放在一个文件里,备份直接拷文件就行。它支持 SQL 查询,对于按时间范围取数据、按分钟维度聚合这种需求完全能胜任。
有人会担心 SQLite 并发写的问题。监控服务是单进程写入,读取是 HTTP API 触发的,并发量很低,SQLite 默认的串行写模式完全没压力。如果真的想更稳妥,可以开启 WAL 模式:
PRAGMA journal_mode=WAL;这样读操作和写操作可以并发执行,页面查询数据时不会阻塞写入,体验会流畅一些。
数据表结构我建议简单一点,一张metrics表存全部指标即可:
CREATE TABLE metrics ( ts INTEGER PRIMARY KEY, cpu REAL, mem REAL, disk REAL, net_in INTEGER, net_out INTEGER );ts存 Unix 时间戳,net_in和net_out是累计字节数,前端展示速率时再由程序做差值计算。
3.3 展示与告警:前端图表加阈值通知,一个都别少
监控数据最终要给人看,展示层我用的是最简单的方案:Flask 提供 JSON API,前端页面用 Chart.js 画趋势图。页面打开后定时从 API 拉最近的数据,然后更新图表,基本实现了如丝般顺滑的实时刷新效果,不需要引入 websocket 或者 SSE,轮询在这种情况下已经非常够用了。
告警是这个方案里很容易被忽略但价值很高的功能。监控的意义不在于“看”,而在于“发现问题时有感知”。我的实现思路是在采集进程里做一个阈值检查:CPU 连续三次超过 90%,或者磁盘使用率超过 85%,就往日志里输出一条告警信息,同时可以调用一个外部脚本发邮件或发钉钉/企业微信通知。
告警里有个坑:CPU 瞬时冲高很常见,不能每次超过阈值都告警,否则会收到一堆重复消息。加一个“持续时间”条件,比如连续 3 次(15 秒)都超阈值才告警,能过滤掉绝大多数误报。
4. 核心实现与部署实录
4.1 采集模块代码与指标计算
这一节把代码贴出来,每一段都说明逻辑和参数选择原因。
采集模块collector.py:
import time import sqlite3 import psutil DB_PATH = "/var/lib/coolmonitor/metrics.db" def collect_once(): cpu = psutil.cpu_percent(interval=1) mem = psutil.virtual_memory().percent disk = psutil.disk_usage('/').percent net = psutil.net_io_counters() return { "ts": int(time.time()), "cpu": round(cpu, 1), "mem": round(mem, 1), "disk": round(disk, 1), "net_in": net.bytes_recv, "net_out": net.bytes_sent, } def insert_metric(conn, m): conn.execute( "INSERT INTO metrics (ts, cpu, mem, disk, net_in, net_out) VALUES (?, ?, ?, ?, ?, ?)", (m["ts"], m["cpu"], m["mem"], m["disk"], m["net_in"], m["net_out"]), ) conn.commit() def main(): conn = sqlite3.connect(DB_PATH) conn.execute("PRAGMA journal_mode=WAL") conn.execute(""" CREATE TABLE IF NOT EXISTS metrics ( ts INTEGER PRIMARY KEY, cpu REAL, mem REAL, disk REAL, net_in INTEGER, net_out INTEGER ) """) while True: m = collect_once() insert_metric(conn, m) check_alerts(m) time.sleep(5) def check_alerts(m): alerts = [] if m["cpu"] >= 90: alerts.append(f"CPU 使用率过高: {m['cpu']}%") if m["disk"] >= 85: alerts.append(f"磁盘使用率过高: {m['disk']}%") if m["mem"] >= 90: alerts.append(f"内存使用率过高: {m['mem']}%") if alerts: for a in alerts: print(f"[ALERT] {time.strftime('%Y-%m-%d %H:%M:%S')} {a}")这里有几个设计细节值得解释。psutil.cpu_percent(interval=1)第一次调用会返回 0,因为它是计算相对上一次调用以来的使用率,没有上一次基准。所以第一次采集的数据可能会不太准,实际部署时一般会先跑一次纯采集不写入,热个身再进入主循环。但为了代码简单,这里没用热身逻辑,因为一条 0 记录对整体曲线影响可以忽略。
net_in和net_out存储的是累计值,这是故意的。如果直接存速率,一旦两次采样间隔不稳定,算出来的速率就失真。前端获取数据后再根据相邻两条的时间差和字节差算速率,反而是拿到的瞬时值里最准确的一种折算方式。这个我要专门写进代码注释里,防止以后自己忘了为什么这么设计。
告警只打印到标准输出,实际生产可以改成调subprocess.call运行一个通知脚本。我自己的做法是写了个notify.sh,里面用 curl 调企业微信机器人接口,内网触发后手机几分钟内就能收到通知。
4.2 API 层与前端页面
Flask 应用app.py,负责提供查询接口和托管静态页面:
from flask import Flask, jsonify, send_from_directory import sqlite3 import time app = Flask(__name__) DB_PATH = "/var/lib/coolmonitor/metrics.db" @app.route("/") def index(): return send_from_directory("static", "index.html") @app.route("/api/metrics") def metrics(): minutes = int(request.args.get("minutes", 30)) since = int(time.time()) - minutes * 60 conn = sqlite3.connect(DB_PATH) conn.row_factory = sqlite3.Row rows = conn.execute( "SELECT * FROM metrics WHERE ts >= ? ORDER BY ts ASC", (since,) ).fetchall() data = [dict(r) for r in rows] # 计算相邻两次采样的网络速率 for i, d in enumerate(data): if i == 0: d["net_in_rate"] = 0 d["net_out_rate"] = 0 else: prev = data[i - 1] dt = d["ts"] - prev["ts"] if dt > 0: d["net_in_rate"] = (d["net_in"] - prev["net_in"]) / dt d["net_out_rate"] = (d["net_out"] - prev["net_out"]) / dt else: d["net_in_rate"] = 0 d["net_out_rate"] = 0 return jsonify(data) if __name__ == "__main__": app.run(host="0.0.0.0", port=5000, debug=False)前端页面static/index.html用 Chart.js 画图。核心逻辑就两条:fetch('/api/metrics?minutes=30')拿到数据,然后调用 Chart.js 的update()方法刷新图表。我设置的是每 5 秒刷新一次数据和图表,和采集频率保持一致,实测曲线滚动非常平滑。
为了避免因为存储了很多天数据导致查询结果量过大,接口支持minutes参数,前端只取最近 30 分钟、1 小时、6 小时或 1 天,切换时间范围时重新请求就行。
Chart.js 用 CDN 的方式引入的,如果在纯内网环境不能访问外网,就把图表库文件下载到本地放到 static 目录下。这个小问题在部署到隔离网络时确实会遇到。
4.3 systemd 配置与反向代理
采集脚本和 Web 服务要同时跑,我用两个 systemd service 分别管理。采集进程是常驻的、不能退出,Web 服务是响应式的,两者生命周期不同,分开管理反而清晰。
采集服务的 unit 文件/etc/systemd/system/coolmonitor-collector.service:
[Unit] Description=Coolmonitor Collector After=network.target [Service] Type=simple ExecStart=/usr/bin/python3 /opt/coolmonitor/collector.py WorkingDirectory=/opt/coolmonitor Restart=always RestartSec=5 User=root [Install] WantedBy=multi-user.targetWeb 服务的 unit 文件同理,改成ExecStart=/usr/bin/python3 /opt/coolmonitor/app.py即可。
部署时注意Restart=always这行,写完之后进程意外挂掉会自动拉起,间隔 5 秒再试,对监控服务来说这是加分项。日志输出到 journald,用journalctl -u coolmonitor-collector查看,比 nohup 输出文件方便。
如果不希望暴露 5000 端口给外部,可以用 Nginx 做反向代理,把/转发到127.0.0.1:5000。但说实话,如果只是内网几个人看,直接防火墙放行 5000 端口也完全可以,Nginx 这层不是必须的。
5. 常见问题与排查技巧实录
5.1 服务起不来的问题排了一遍
最常遇到的服务起不来原因有三类。第一类是 Python 依赖没装对环境。有时候系统默认的python3指向 3.6,但用 pip 装 Flask 时装到了 3.8 的 site-packages 里,启动就直接 ModuleNotFoundError。排查方式用which python3和python3 -c "import flask"验证。
第二类是权限问题。如果进程用普通用户跑,但数据库目录/var/lib/coolmonitor没有写权限,采集进程会在启动后第一次写库时崩溃。开Restart=always的话你会看到服务反复重启,journal 日志里一堆 Permission denied。
第三类是端口被占用。Flask 默认端口 5000,有时候系统里其他服务先用掉了。改端口或者用ss -lntp | grep 5000查一下占用进程。
排查服务问题第一步永远是:
journalctl -u coolmonitor-collector -n 50 --no-pager看日志再行动,比盲目重启高效得多。
5.2 图表不刷新、数据不对的几个坑
前端图表不刷新,原因一般是网络请求失败或者接口地址不对。打开浏览器开发者工具,看 Network 标签下的/api/metrics请求状态码,如果不是 200,多半是 Flask 服务没跑起来或者端口不对。如果是 200 但数据没更新,检查一下前端定时器是不是被浏览器节流了,切换浏览器标签后 setTimeout 的间隔会被拉长,这属于浏览器的节能机制,不算 bug。
数据不对的坑主要集中在网络速率溢出的问题上。因为存储的是累计值,如果服务重启了,系统网络计数会从 0 重新累计,API 计算差值时会瞬间算出一个超大速率,画在图上就是一根针状尖峰。第一次遇到这一幕都以为机器是不是被攻击了,其实只是计数器重置。
解决方案有两个思路:一是后端判断如果差值超过某个合理上限(比如 1GB/s)就直接置 0;二是每次服务启动写入一条基准记录。实操中我更推荐第一种,因为它不需要改采集逻辑,只在前端展示时过滤掉异常点就行。
时间对不齐是另一个高频问题。如果服务器时区没设成 Asia/Shanghai,存的时间戳和本地时间会差几个小时。解决办法是在采集脚本里明确time.tzset(),或者在系统层面设置时区。毕竟告警时间错上几个小时,很容易漏掉关键节点。
5.3 数据保留与性能问题的取舍
SQLite 文件会一直变大,虽然增长不快,但时间久了磁盘碎片和查询性能还是会受影响。我保留了最近 90 天的数据,更旧的定时清理:
DELETE FROM metrics WHERE ts < strftime('%s', 'now') - 90 * 86400这个清理任务我用 cron 每天凌晨跑一次,保持数据文件体积可控。
CPU 占用方面要算一笔账:每 5 秒一次cpu_percent(interval=1)意味着每秒都在采样,Python 进程常驻内存大概在 100MB 左右,在 2 核 2G 的机器上完全没压力。如果机器配置特别低,可以把采集间隔拉大到 10 秒,曲线虽然没那么平滑,但资源占用能降一半。
数据量大到一定程度后查询会很慢,因为 SQLite 对WHERE ts >= ?的查询如果没走索引,全表扫描会拖慢接口。解决方法是给ts建索引:
CREATE INDEX IF NOT EXISTS idx_metrics_ts ON metrics(ts);建了索引之后,查一天的数据基本是毫秒级。这个优化建议在表里有几万条数据之后就加上。
5.4 从踩坑到稳定运行,我对监控服务的几点心得
整套服务跑起来之后,最常用到的反而是告警。很多次磁盘快满、内存飙高都是手机先收到通知,再去看趋势图,确认是整个时段都在涨还是瞬时抖动。能提前发现问题,这套监控就已经值回成本了。
有一点很多人会忽略:监控服务是给人用的“安全网”,但安全网自己也可能晚点网住问题。我的习惯是每隔一段时间看看自己的监控面板好不好用,有没有假死、有没有告警风暴,然后顺手优化一下采集间隔和告警阈值。比如一开始磁盘告警设在 90%,发现这个阈值下经常是先把日志刷爆了才收到告警,后来改成 85% 并提前写清理脚本,效果明显好很多。
另外,如果你把监控服务开放给团队其他人用,强烈建议在页面上加上“系统说明”或“告警规则”的文字板块,写清楚数据多久刷一次、每个指标代表什么、告警阈值是多少。我最初没写,同事看到磁盘 80% 就想关电脑,后来加了规则说明大家才知道这是正常水位线以下。看似不起眼,实际上能省去不少沟通成本。