☰
OpenRIG:开源设备监控面板,统一管理多设备状态
2026/10/29 6:37:21 网站建设 项目流程

干我们这行的人,手里总会有几台跑着 24 小时不关的设备:可能是用来挂自动化任务的迷你主机,可能是放在阳台跑测试的树莓派,也可能是自己攒出来的高负载工作站。设备多了以后,最头疼的不是装系统,而是"怎么看它们的状态"。每一台都 SSH 上去敲top、sensors、df -h,时间长了真的会烦。我也经历过拿 Excel 记录状态的阶段,后来发现完全不现实,所以干脆自己折腾了一个开源的装备监控面板。项目代号就叫 OpenRIG。

OpenRIG 是一个开源的设备状态聚合平台,目标是把分散在不同物理位置的机器统一管理起来,在同一个网页里看到每台设备的温度、负载、内存、磁盘、风扇转速和功耗。它不需要多高深的技术背景,适合家里有小型服务器群、平时玩 3D 打印、搞嵌入式开发,或者单纯想把手头设备管清楚的读者。这篇文章会把项目的拆解思路、技术选型、完整部署过程和踩坑记录一次性讲清楚,想复刻的话照着步骤操作即可。

1. 项目背景与整体设计思路

1.1 为什么需要一个设备监控面板

先说个很现实的场景:四台设备,分布在书房、客厅和设备柜三个地方。书房那台跑着网页服务,客厅的树莓派负责传感器采集,设备柜里的迷你主机专门跑定时任务。以前想看它们的状态,得挨个登录,看top、sensors、df -h,每次少说五分钟。如果哪台温度异常或者磁盘满了,通常都是坏事发生之后才后知后觉。

商用监控平台不是没有,但问题也很明显:要么按节点收费,要么数据必须上云。数据上云这件事,对玩设备的人来说心里总觉得不踏实,毕竟有些数据是自家的隐私,比如摄像头、门锁传感器这类采集结果。还有一个更现实的问题,商用平台的告警规则、看板样式大多锁死在厂商预设里,想要自由度就得买更贵的套餐。

OpenRIG 解决的就是这三件事:统一入口、数据自托管、高度可定制。它本质上是一个聚合层,把各种来源的设备数据汇聚到一个页面。你可以把一台只有 1 核 1G 的旧机器变成整个监控系统的服务端,而所有被监控的设备只承担非常轻量的采集任务。有人可能会问,这不就是运维监控吗?对,核心思路和 Zabbix、Prometheus 这类工具很像,但 OpenRIG 的定位更轻——它不做复杂的告警编排,不搞服务发现,也不带一大堆企业级概念,只做好一件事:把设备状态讲清楚。

打个比方,Prometheus 像是一套完整的交通管理系统,OpenRIG 更像是车里的仪表盘。对于小规模设备管理来说,仪表盘反而更直接、更容易上手。

1.2 设计目标:从"能用"到"好用"

很多自托管项目做到"能用"就停了,打开页面能看到数据就以为完事。但我自己在用的时候发现,真正影响使用频率的往往是那些细枝末节。

第一个目标是入口统一。不能指望用户每次都打开 Grafana 再切到某个 Dashboard,最好打开一个地址就能看到全部设备的概览。

第二个目标是接入门槛低。如果新增一台设备需要折腾半小时,那这个系统迟早会被抛弃。所以 OpenRIG 约定了一个非常简单的接入方式:任何设备只需要能发送一个 MQTT 消息,就能被系统识别。不需要装特定的收集器,不需要你懂 SNMP 协议,最简单的方式就是跑一个几十行的 Python 脚本,甚至直接用mosquitto_pub命令都能上报数据。

第三个目标是存储可追溯。设备数据是时间序列数据,最终要能够回溯"昨天下午三点那台设备温度到了多少",这样才能在做故障分析时有依据。这意味着底层存储必须对时间序列友好,不能用普通的关系表硬扛。

还有一个容易忽略的点:告警。整个项目里告警并不是第一优先级,因为对于小规模设备群来说,人工盯看板是常态,告警属于加分项。OpenRIG 把告警接口留了出来,但不强绑定某一种通知渠道。你可以对接企业微信机器人、钉钉机器人,也可以用最简单的邮件通知。后续我会专门写一篇文章介绍告警接入,这里先不展开。

1.3 为什么叫 OpenRIG

"RIG" 这个词在动手党、DIY 圈子里有个很微妙的含义——不是"钻机"那种大家伙,而是指"为了某个特定任务搭起来的一整套设备组合"。你搭一套多屏工作站,叫 workstation rig;搭一套传感器采集装置,叫 sensor rig;搭一台游戏机,也能叫 gaming rig。在英文技术社区里,说 "my rig" 通常指"我自己那套设备"。

OpenRIG 这个名字起得很直白:开放版本的设备组合监控工具。它没有打算做成一个业界标杆,更多是分享一种思路——把设备管理这件事做得透明、可控、不依赖商业云平台。

2. 技术选型与系统架构

2.1 采集端:轻量级 Agent 加 MQTT

设备数据的采集方式,第一版我参考传统监控软件的做法,用 HTTP 轮询:服务端每 30 秒去每台设备拉一次状态。做了一半发现不对劲,如果设备在 NAT 网络后面,服务端根本访问不到采集端口,被迫改成 Agent 主动推送。

所以第二版果断切到 MQTT。MQTT 是基于发布订阅的轻量消息协议,天然适合这种场景,设备端主动连接到 Broker 上报数据,服务端作为订阅者接收,解决了 NAT 穿透问题。另外 MQTT 的消息体积小,心跳和离线状态也是协议自带的能力,省掉了自己实现超时判断的麻烦。

采集脚本本身用了纯 Python,依赖只有三个:psutil、paho-mqtt、socket。为什么不用 Node-RED 或者 Telegraf 这类现成工具?因为对于大多数设备来说,一个 Python 脚本就够了,而且在嵌入式和迷你主机上,Python 环境远比 Node-RED 轻量。脚本每 15 秒采集一次,上报间隔设成 30 秒,信息不会太滞后,也不会给设备增加明显负担。实测在树莓派 3B 上跑这个脚本,CPU 占用率不到 1%。

2.2 服务端与存储:Flask + InfluxDB

服务端这边我选了 Flask,原因很简单:OpenRIG 的前端页面是 Flask 提供的一个轻量管理页,负责登录鉴权、设备列表展示和简单的 API 接口。没有用 FastAPI,因为项目不需要自动生成接口文档和响应模型,Flask 用起来更顺手,middleware 和 session 机制也足够成熟。

数据存储没有用 SQLite,因为设备状态是典型的时间序列数据。SQLite 在数据量一大、查询时间范围一长的时候,性能和写入锁会成为明显的瓶颈。InfluxDB v2 是我用下来比较顺手的时序数据库,自带的 Flux 查询语言虽然刚开始需要适应,但做连续聚合和降采样确实方便。同时 InfluxDB v2 内置了 token 鉴权机制,不需要像 v1 那样单独配 RBAC 权限,配置量少很多。

这个项目里我没有让 Flask 直接操作 InfluxDB 做读写,而是拆成了两条链路:

写入链路是 MQTT Broker 到数据服务。数据服务订阅rig/+/metrics主题,接收 Agent 上报的 JSON 消息,解析后写入 InfluxDB。这样 Agent 完全感知不到存储层,以后就算把存储从 InfluxDB 换成 TimescaleDB,Agent 也不需要改。

查询链路则是前端页面通过 Flask API 从 InfluxDB 读取数据,渲染成图表。做了大概 8 小时的缓存窗口,避免每次前端刷新都去拉全量数据。

2.3 前端:Grafana Dashboard 复用

最早我想自己写图表,用的 ECharts。写到一半发现,为了实现时间范围选择、缩放、阈值高亮这些功能,代码量越来越大,效果还不一定有现成的好。最后决定前端图表干脆直接复用 Grafana,它跟 InfluxDB 的集成非常顺滑,数据源填一下地址和 token,写几条查询语句,看板就出来了。

Grafana 的看板配置是可以 JSON 导出的。我把全部看板配置存成了 JSON 文件放进 Docker 镜像里,部署新实例时自动导入,不需要手工一个个建 Panel。这个细节非常省心,有段时间我反复重置测试环境,每次都靠这个 JSON 文件一键恢复。

不过 Grafana 也有一个我不太满意的点,默认的用户体系太重,如果只是做内网的小规模监控,完全没必要用它的用户系统。所以 OpenRIG 在 Flask 这边做了统一登录,用户访问 Nginx 暴露的 80 端口时,先经过 Flask 登录验证,再进行反向代理,Grafana 本身不直接暴露到局域网外部。

2.4 整体架构总结

整套系统由四部分组成:

  • Agent:部署在被监控设备上,通过 Python 脚本或任意 MQTT 客户端上报数据。
  • Broker:采用 Eclipse Mosquitto,负责消息的订阅分发。
  • 数据服务:Flask 应用订阅 MQTT 主题,将数据写入 InfluxDB,同时提供 HTTP 接口给前端。
  • 可视化层:Grafana 负责图表展示,Flask 负责登录和页面路由。

全部组件都可以用 Docker Compose 一键拉起,这也是我觉得整个项目最方便的地方。部署环境不管是 x86 迷你主机还是 ARM 开发板,都一样跑。

3. 从零部署 OpenRIG

3.1 前期准备与目录规划

部署之前先确定机器的硬件。OpenRIG 服务端对性能要求不高,实测一台树莓派 4B 跑全部组件,在管理 20 台设备的情况下,内存占用大概 1.2G,CPU 日常使用率不到 10%。所以基本上任何一台跑得动 Docker 的机器都行。

我建议提前规划好目录结构,不要随手装完再整理。以我的部署习惯为例,目录是这样的:

/opt/openrig ├── docker-compose.yml ├── influxdb │ ├── data │ └── config ├── grafana │ ├── data │ └── provisioning ├── mosquitto │ ├── config │ └── data └── app └── openrig.py

提前建目录的好处是,后面做数据备份、迁移升级时只需要打包/opt/openrig整个目录,非常干净。在迁移到新机器时,我经常直接用 rsync 把这个目录搬到新机器,然后docker compose up -d就完成迁移。

安装 Docker 这步就略过细节了,如果你对 Docker 不熟,记住一条建议:不要在 Windows 上用 Docker Desktop 跑生产级服务,不稳定,资源占用也高。我推荐直接装 Ubuntu Server 或者 Debian,然后安装 Docker Engine 和docker-compose-plugin:

sudo apt update sudo apt install -y docker.io docker-compose-v2 sudo systemctl enable --now docker

3.2 编写 docker-compose 配置

OpenRIG 的编排文件包含四个服务。这里我贴出完整版,略有删减,实际使用直接复制即可:

version: "3.8" services: mosquitto: image: eclipse-mosquitto:2.0 container_name: openrig-mqtt restart: unless-stopped ports: - "1883:1883" volumes: - ./mosquitto/config:/mosquitto/config - ./mosquitto/data:/mosquitto/data influxdb: image: influxdb:2.7 container_name: openrig-influx restart: unless-stopped ports: - "8086:8086" environment: DOCKER_INFLUXDB_INIT_MODE: setup DOCKER_INFLUXDB_INIT_USERNAME: admin DOCKER_INFLUXDB_INIT_PASSWORD: your-password DOCKER_INFLUXDB_INIT_ORG: openrig DOCKER_INFLUXDB_INIT_BUCKET: rigdata DOCKER_INFLUXDB_INIT_ADMIN_TOKEN: openrig-token volumes: - ./influxdb/data:/var/lib/influxdb2 - ./influxdb/config:/etc/influxdb2 openrig-app: build: ./app container_name: openrig-app restart: unless-stopped depends_on: - mosquitto - influxdb environment: INFLUX_URL: http://influxdb:8086 INFLUX_TOKEN: openrig-token INFLUX_ORG: openrig INFLUX_BUCKET: rigdata MQTT_HOST: mosquitto MQTT_PORT: 1883 ports: - "5000:5000" grafana: image: grafana/grafana:11.0.0 container_name: openrig-grafana restart: unless-stopped depends_on: - influxdb environment: GF_SECURITY_ADMIN_USER: admin GF_SECURITY_ADMIN_PASSWORD: admin GF_INSTALL_PLUGINS: "" volumes: - ./grafana/data:/var/lib/grafana - ./grafana/provisioning:/etc/grafana/provisioning ports: - "3000:3000"

有个容易踩的坑是 InfluxDB 初始化环境变量。DOCKER_INFLUXDB_INIT_MODE: setup会在容器首次启动时自动创建组织、桶和 token,但是这些配置只在首次启动时生效。如果你改密码后想重新初始化,必须删除 InfluxDB 的 data 目录,容器重建才行。否则你会发现环境变量改了,但实际密码没变。

3.3 Mosquitto 配置与账号认证

Mosquitto 默认只监听本机,而且允许匿名访问,这对内网环境来说也不是不能用,但总归不放心。我给 OpenRIG 的 MQTT 加了一个简单密码认证,配置文件mosquitto/config/mosquitto.conf内容如下:

persistence true persistence_location /mosquitto/data/ listener 1883 allow_anonymous false password_file /mosquitto/config/password.txt

生成密码文件:

docker exec -it openrig-mqtt mosquitto_passwd -c /mosquitto/config/password.txt riguser docker exec -it openrig-mqtt mosquitto_passwd -b /mosquitto/config/password.txt riguser your-mqtt-password

注意mosquitto_conf里的allow_anonymous false一定要配,不然局域网里任何设备都能连上 Broker,看到你所有设备上报的数据。MQTT 默认不加密传输,所以我只在可信内网部署。如果要跨公网传输数据,建议在边缘加一层 TLS 代理,或者直接把 MQTT 服务只绑定内网接口,不映射端口到公网。

3.4 Agent 采集脚本说明

被监控设备上的 Agent 脚本,我做成了一份可以复用多个设备的模板。核心逻辑是使用psutil采集系统指标,然后组包上报。需要注意,不同设备的温度数据来源不太一样,所以这段脚本里做了一层兜底:

import json import time import socket import psutil import paho.mqtt.client as mqtt RIG_NAME = socket.gethostname().lower().replace(" ", "-") MQTT_HOST = "192.168.1.10" MQTT_PORT = 1883 MQTT_USER = "riguser" MQTT_PASS = "your-mqtt-password" def get_cpu_temp(): temps = [] # 优先尝试常见的 hwmon 路径 for path in ["/sys/class/thermal/thermal_zone0/temp", "/sys/devices/platform/coretemp.0/hwmon/hwmon0/temp1_input"]: try: with open(path) as f: value = int(f.read().strip()) / 1000.0 temps.append(round(value, 2)) except Exception: continue return temps[0] if temps else None def read_metrics(): mem = psutil.virtual_memory() disk = psutil.disk_usage("/") temp = get_cpu_temp() ts = int(time.time()) payload = { "host": RIG_NAME, "ts": ts, "cpu_percent": psutil.cpu_percent(interval=1), "mem_percent": mem.percent, "disk_percent": disk.percent, "cpu_temp": temp, "cpu_load": psutil.getloadavg()[0], "boot_time": psutil.boot_time() } return json.dumps(payload) def main(): client = mqtt.Client(client_id=f"rig-{RIG_NAME}", protocol=mqtt.MQTTv311) client.username_pw_set(MQTT_USER, MQTT_PASS) client.connect(MQTT_HOST, MQTT_PORT, keepalive=60) client.loop_start() while True: payload = read_metrics() client.publish(f"rig/{RIG_NAME}/metrics", payload, qos=1) print(f"[{RIG_NAME}] published: {payload}") time.sleep(30) if __name__ == "__main__": main()

我之所以在get_cpu_temp()里做路径兼容,是因为很多迷你主机和开发板的温度节点位置并不一样。有些设备的thermal_zone0根本不是 CPU 温度,而是主板温度,但你拿不到更准确的数据时,拿它做参考总比没有强。我碰到过一台工控机,只有一个温度节点,而且是硬盘温度,这种场景直接把cpu_temp字段改成一个你关心的指标就行,比如主板温度。

qos=1这里也说明一下。QoS 0 会丢消息,QoS 2 握手又太重。对于状态采集来说,丢一两条消息无所谓,但 QoS 0 在网络不稳定时丢得太频繁,看板数据容易断层,所以用了 QoS 1,既保证消息至少送达一次,又不会过分消耗带宽。

3.5 服务端接收与写入

服务端这边我写了一个轻量的 MQTT 订阅程序,逻辑非常直接:监听rig/#主题,收到 JSON 后解析,写入 InfluxDB。使用 InfluxDB 的 Python Client 库写入,这样比直接拼 Line Protocol 更安全,能避免字段注入问题。

import json import influxdb_client from influxdb_client.client.write_api import SYNCHRONOUS import paho.mqtt.client as mqtt # InfluxDB 配置 INFLUX_URL = "http://influxdb:8086" INFLUX_TOKEN = "openrig-token" INFLUX_ORG = "openrig" INFLUX_BUCKET = "rigdata" client = influxdb_client.InfluxDBClient(url=INFLUX_URL, token=INFLUX_TOKEN, org=INFLUX_ORG) write_api = client.write_api(write_type=SYNCHRONOUS) def save_metrics(host, ts, fields): point = influxdb_client.Point("device_metric") \ .tag("host", host) \ .field("cpu_percent", float(fields.get("cpu_percent", 0))) \ .field("mem_percent", float(fields.get("mem_percent", 0))) \ .field("disk_percent", float(fields.get("disk_percent", 0))) if fields.get("cpu_temp") is not None: point = point.field("cpu_temp", float(fields["cpu_temp"])) write_api.write(bucket=INFLUX_BUCKET, record=point) def on_message(client, userdata, msg): try: payload = json.loads(msg.payload.decode("utf-8")) host = payload.get("host") if not host: return ts = int(payload.get("ts", 0)) if ts > 0: point = influxdb_client.Point("device_metric") \ .tag("host", host) \ .field("cpu_percent", float(payload.get("cpu_percent", 0))) \ .field("mem_percent", float(payload.get("mem_percent", 0))) \ .field("disk_percent", float(payload.get("disk_percent", 0))) \ .time(ts, write_precision="s") if payload.get("cpu_temp") is not None: point = point.field("cpu_temp", float(payload["cpu_temp"])) write_api.write(bucket=INFLUX_BUCKET, record=point) print(f"saved: {host} {ts}") except Exception as e: print("parse error:", e) def main(): mqtt_client = mqtt.Client(client_id="openrig-server", protocol=mqtt.MQTTv311) mqtt_client.username_pw_set("riguser", "your-mqtt-password") mqtt_client.on_message = on_message mqtt_client.connect("mosquitto", 1883, keepalive=60) mqtt_client.subscribe("rig/+/metrics", qos=1) mqtt_client.loop_forever() if __name__ == "__main__": main()

注意到我写了两遍 point 构造,这是有意保留的,因为第一处在save_metrics函数里是在没有时间戳时的兜底方案,但实际使用中我更倾向显式把设备上报的ts作为时间戳。为什么不用接收时间?因为如果设备与服务端存在时间偏差,或者消息在 MQTT broker 里积压了一段时间,接收时间就会掩盖设备端的真实采集时刻。我用设备上报的ts字段作为数据时间点,这样 Grafana 展示的就是设备真实状态时间,而不是服务器收到消息的时间。

3.6 Grafana 数据源和看板导入

Grafana 启动后,第一次打开会要求登录,默认账号密码都是admin。登录进去以后要干两件事:添加 InfluxDB 数据源、导入看板 JSON。

添加数据源时,URL 填http://influxdb:8086,因为 Grafana 容器在同一个 Docker 网络里,不需要填宿主机 IP。InfluxDB 的 Details 区域要填 Organization 和 Default Bucket 以及 Token。这些配置在 provisioning 里设置更稳妥,避免每次容器重建都要手工操作。我用的 provisioning 文件在grafana/provisioning/datasources/influxdb.yaml里:

apiVersion: 1 datasources: - name: InfluxDB type: influxdb access: proxy url: http://influxdb:8086 isDefault: true jsonData: version: Flux organization: openrig defaultBucket: rigdata tlsAuth: false secureJsonData: token: openrig-token

由于一份完整的看板 JSON 内容太长,这里不贴全量,只给一个核心查询面板的 Flux 查询语句,其他面板照葫芦画瓢改字段名即可:

from(bucket: "rigdata") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn: (r) => r._measurement == "device_metric") |> filter(fn: (r) => r._field == "cpu_temp") |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) |> yield(name: "mean")

如果你之前是写 InfluxQL 或者 SQL 的,第一次接触 Flux 会有点绕,但看几遍就能上手。核心逻辑就是:从桶里先框定时间范围,再过滤测量值和字段,最后做时间窗口聚合。注意aggregateWindow那一行必须写,否则 Grafana 面板上点位太多,缩小时会卡顿。

3.7 可选:硬件传感器接入

光有 CPU 温度、内存占用这些系统指标,对于搞环境监测、3D 打印的朋友来说还差点意思——你可能更关心机箱内部温度、湿度和耗电量。OpenRIG 这套架构最舒服的一点是,硬件传感器都能直接塞进同一套 MQTT 数据链路里。

以我在树莓派上接 DS18B20 温度传感器为例。接好线后,在/boot/config.txt里启用 1-Wire 接口。DS18B20 会出现在/sys/bus/w1/devices/28-xxxxxxxxxxxx/w1_slave这个路径,读取脚本如下:

def read_ds18b20(device_id): path = f"/sys/bus/w1/devices/{device_id}/w1_slave" with open(path) as f: lines = f.read().splitlines() if lines[0].strip().endswith("YES"): temp_raw = int(lines[1].split("=")[1], 16) return round(temp_raw / 1000.0, 2) return None

SHT30 走 I2C 总线,更简单,用smbus2库读两字节数据,得到温湿度,然后同样打包成 JSON 发到rig/{host}/metrics主题,主题不变,字段加一个board_temp、humidity。写入 InfluxDB 的代码不用改,因为我只取了几个固定字段,但如果需要变得通配,可以在点对象构造时遍历 JSON 动态加字段:

for k, v in payload.items(): if isinstance(v, (int, float)): point = point.field(k, float(v))

这样以后无论上报什么数值指标,后端都能自动入库,不需要频繁改写入代码。这个改法我强烈建议做,因为后面你总会想多加一些自定义指标,比如 UPS 电量、网络延迟、风扇转速,一次改好永久受益。

4. 踩坑实录与常见排查办法

4.1 数据迟迟不更新

这是初学者最容易遇到的问题,看板打开一片空白,或者有历史数据但最新时间停在一个多小时前。我的排查顺序是这样的:

先去 Grafana 的数据源检查页,点击 "Save & test",如果提示连接失败,先看 Flask 应用容器日志:

docker logs openrig-app --tail 100

确认服务端有没有在正常收到消息。如果没有消息输出,问题大概率出在 Agent 到 Broker 这一段。这时候直接在设备上手动跑一次发布脚本,看会不会报连接错误。

mosquitto_pub -h 192.168.1.10 -p 1883 -u riguser -P your-password -t rig/test/metrics -m '{"host":"test","ts":0,"cpu_percent":1}'

如果这个命令能成功,但 Agent 脚本不行,多半是脚本里 MQTT 用户名或密码写错了。如果连不上,就要怀疑防火墙 1883 端口是不是被拦了。注意 Docker 的防火墙规则经常和ufw冲突,我遇到过一次,宿主机端口明明映射了,外部就是连不通,排查半天发现是ufw默认策略拒绝了 Docker 容器映射端口。解决办法是放行端口:

sudo ufw allow 1883/tcp

还有一个经常被忽略的问题,MQTT 客户端 ID 冲突。如果两个 Agent 的client_id设成一样的,比如都叫rig-ubuntu,MQTT 协议规定同一个 client ID 的新连接会踢掉旧连接,导致两个设备交替掉线。我排查这个问题时,发现日志里一会儿有数据一会儿没数据,最后挨个设备看脚本里的client_id才找到原因。

4.2 时间戳差了 8 小时

如果你发现 Grafana 显示的时间和服务器本地时间相差 8 小时,这就是典型的容器时区没设置。Docker 容器默认使用 UTC 时区,而 InfluxDB 存储时间戳是毫秒级时间戳,本身不包含时区信息,Grafana 展示时会按浏览器的时区来渲染。所以数据没问题,但显示时差了 8 小时。

解决办法是在容器环境变量里加时区:

environment: - TZ=Asia/Shanghai

在 MQTT 上报的ts字段我也统一使用 UNIX 时间戳,不用本地时间字符串。如果设备上的时间本来就不准,那上报的时间戳也都是错的,最好先同步设备时间。我所有设备都启用了 systemd-timesyncd 自动同步,确保时间基准一致。

4.3 传感器数值乱跳

DS18B20 在长距离走线后数值会不稳定,比如温度从 35 度突然跳到 50 度又跳回来。原因大多在于 1-Wire 总线上没有正确的上拉电阻。DS18B20 数据线必须接一个 4.7kΩ 的上拉电阻到 3.3V,很多初学者漏掉这一步,导致信号电平不稳。加了电阻之后,数值明显稳定很多。

SHT30 的湿度数据有时会出现持续偏差,这通常是传感器暴露在气流中或者靠近发热元件导致的。建议传感器远离主板的南桥或 CPU 散热器,至少隔开 10 厘米。如果已经固定位置不好挪,可以在软件里做滑动平均,我有一段很小但很实用的处理:

FILTER_WINDOW = 5 _temp_buffer = [] def smooth_temp(value): _temp_buffer.append(value) if len(_temp_buffer) > FILTER_WINDOW: _temp_buffer.pop(0) return round(sum(_temp_buffer) / len(_temp_buffer), 2)

但是平滑处理只能掩盖问题,不能根除。如果你发现温度变化幅度特别夸张,还是优先从硬件排查:供电是不是稳定、线材是不是太细、连接器有没有氧化。

4.4 常见问题速查表

症状可能原因解决办法
Grafana 数据源连接失败反向代理时 URL 填了宿主机 IP容器内应填http://influxdb:8086
MQTT 连接被拒账号密码错误或allow_anonymous false检查password.txt和客户端配置
消息发出去但没入库主题不匹配订阅rig/+/metrics,发布rig/{host}/metrics
数据重复显示两个 Agent 用相同client_id改成设备唯一 ID,比如加 MAC 后缀
看板时间差 8 小时容器没有设置时区添加TZ=Asia/Shanghai环境变量
InfluxDB 修改密码不生效初始化环境变量只在首次启动生效清空 data 目录重新初始化
磁盘空间逐渐减少InfluxDB 没有配置保留策略创建 bucket 时设置默认保留 30 天,或者定时删除过期数据
重启后看板为空容器卷没有挂载确保/var/lib/influxdb2和/var/lib/grafana挂载到宿主机目录

4.5 数据备份与恢复

OpenRIG 跑起来之后,最担心的是数据丢。不过设备监控数据的价值是有时效的,超过两周的数据基本不会再看了。所以我不建议做每日全量备份,更推荐给 InfluxDB 设置保留策略,只保留 30 天数据,减少磁盘占用。如果你确实想备份关键配置,只需要备份docker-compose.yml、Grafana 的 provisioning 目录和看板 JSON 文件即可。InfluxDB 数据本身不需要备份,因为它丢了之后重新上报一段时间就会恢复。

我遇到过最折腾的一次是 Docker 数据卷损坏,容器完全启不来。排查后发现是宿主机磁盘满了,InfluxDB 在写入时报错。所以给 InfluxDB 挂载的目录一定要预留至少 10% 磁盘空间,不然不仅写不进去,还会拖垮整个容器。建议在宿主机上加一个磁盘用量检查脚本,超过阈值就发告警。

5. 后续扩展玩法

OpenRIG 这套架构稳定运行之后,我陆陆续续加了不少有意思的功能。最实用的是在 Agent 端加了一个远程命令通道,通过订阅rig/{host}/cmd主题,可以在设备上执行一些预设命令,比如安全关机、重启采集进程、清理临时文件。为什么不做任意命令执行?因为太危险,一旦 Broker 被未授权设备接入,简直等于把设备控制权交出去。所以我只允许一组白名单命令,比如/sbin/reboot和/usr/bin/du -sh /tmp。

另外一个方向是把 Agent 接到 UPS 上。我用树莓派接了一个 USB 接口的 UPS,通过apcaccess获取电池电量、输入电压。这些数据上报后,在 Grafana 上加了一个 UPS 看板,直观看到电力状态。

还有功耗监测。现在很多插座带功率计,有些可以通过局域网 API 读取实时功耗。如果你手头的插座没有开放接口,可以买一个带 Tasmota 固件的智能插座,它本身就支持 MQTT,直接接入 Broker 即可,完全不需要额外开发。

这些扩展都在验证同一件事:OpenRIG 的数据通道足够开放,任何设备只要懂 MQTT,就能轻松接入。这也是为什么我一直强调不要搞复杂的私有协议,MQTT 就是这套系统最大的兼容层。

最后说一点个人体会。设备监控这种事,一开始总觉得随便写个脚本就够了,但真正折腾过之后才发现,一个标准化的上报格式和统一看板才是最有价值的部分,它让"管理设备"从每次登录终端变成了打开浏览器看一眼。OpenRIG 目前已经在我家稳定运行了大半年,中途只因为断电重启过一次,整体非常可靠。如果你也在为多设备状态管理头疼,不妨按这份部署文档试试。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询