刚把一台测试服务器从旧系统迁移到 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(interval1)返回的是一个 CPU 使用率百分比注意这里interval1表示 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_modeWAL;这样读操作和写操作可以并发执行页面查询数据时不会阻塞写入体验会流畅一些。数据表结构我建议简单一点一张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.pyimport time import sqlite3 import psutil DB_PATH /var/lib/coolmonitor/metrics.db def collect_once(): cpu psutil.cpu_percent(interval1) 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_modeWAL) 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(fCPU 使用率过高: {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(interval1)第一次调用会返回 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(host0.0.0.0, port5000, debugFalse)前端页面static/index.html用 Chart.js 画图。核心逻辑就两条fetch(/api/metrics?minutes30)拿到数据然后调用 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] DescriptionCoolmonitor Collector Afternetwork.target [Service] Typesimple ExecStart/usr/bin/python3 /opt/coolmonitor/collector.py WorkingDirectory/opt/coolmonitor Restartalways RestartSec5 Userroot [Install] WantedBymulti-user.targetWeb 服务的 unit 文件同理改成ExecStart/usr/bin/python3 /opt/coolmonitor/app.py即可。部署时注意Restartalways这行写完之后进程意外挂掉会自动拉起间隔 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没有写权限采集进程会在启动后第一次写库时崩溃。开Restartalways的话你会看到服务反复重启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(interval1)意味着每秒都在采样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% 就想关电脑后来加了规则说明大家才知道这是正常水位线以下。看似不起眼实际上能省去不少沟通成本。
阅读完成 · 觉得有帮助?