☰
openrig:开源显卡算力主机远程管理控制台实践解析
2026/11/2 10:29:27 网站建设 项目流程

我其实纠结了很久要不要把这个项目拿出来写。原因很简单,“openrig”这个名字太大了,听起来像要叫板那些商业矿场管理平台,但实际上我写它的时候,真的就是为了解决我自己机房里那几台显卡主机来回折腾的问题。不过想想,很多朋友私信问我怎么远程管自己的机器、怎么自己搭面板、怎么处理机器死机后只能跑机房手动重启的尴尬,所以还是把整个折腾过程、设计思路和踩过的坑都整理出来,希望能帮到同路人。

这里先说明一下:这个项目本质上是一个开源的、面向自建算力集群和显卡主机的轻量级远程管理控制台。它不依赖任何云服务商,数据只存本地,只有一台能联网的路由器加一台常开的小主机就能部署。

1. 为什么我会动手写一个openrig:从一次凌晨四点的跑机房事件说起

先聊聊背景。去年年底我把自己那几台吃灰的显卡主机重新翻出来,组了个小规模的算力环境。硬件倒不复杂,就是普通的ATX工控板、几张不同型号的显卡、一个带温控的机箱风扇,电源是杂牌1200W。一开始挺顺利的,跑个两三天没问题,但问题出在稳定性和“远程管理”这两个词上。

那阵子我用过几款市面上的远程管理工具,要不就是需要注册账号、绑定手机号,要不就是免费版限制设备数量,最让我受不了的是有一次机器温度异常,我的告警信息根本没推送出来,等我发现问题的时候已经过了六个小时。我一个远程控制终端连上去看,系统负载不算高,但显卡风扇转速传感器读数明显不对,连带主板SMBus总线也有异常。折腾到凌晨四点,最后还是要跑进机房手动断电、清灰、换风扇接口,那一刻我下定决心要写一个属于自己的、可控、可定制的管理工具。openrig这个名字就是那时候定下来的,核心就两个词:open,我希望所有逻辑都摊开在自己面前,出了问题能直接看代码;rig,对应的是我手头这些算力主机。

所以openrig的项目定位很清晰:一个面向个人或小团队、用来自管理本地算力主机的开源控制台。它解决的核心痛点有三块:一是设备状态的可视化监控,包括温度、功耗、风扇转速、系统负载;二是远程控制能力,包括安全关机、重启、风扇转速调整;三是告警通知的可靠送达,至少不能让我再错过一次异常报警。整体技术选型上,我用了Python写后端,Flask做REST API,前端用Vue 3加ECharts做仪表盘,Agent端直接用Python的psutil加NVML库采集信息,数据库用SQLite,部署方式就是Docker Compose。这些技术都足够常见,不会有什么维护上的负担。

2. 从零开始的架构设计:Agent采集、服务端聚合、前端呈现的三层结构

动手写代码之前,我先把整个项目的骨架画了一遍。openrig在架构上分为三层,每一层各司其职,避免以后功能多了逻辑混成一团。

2.1 Agent端:带着NVML和psutil的“探针”

Agent是整个系统里最底层的部分,它负责采集每一台主机的实时状态。我用的Python做采集端,主要是因为psutil拿CPU、内存、磁盘信息太方便了,显卡数据则交给NVML,这个库是NVIDIA官方出的Python API包装,能拿到显存占用、核心温度、功耗、风扇转速、显存温度、PCIe带宽利用率这些关键值。

Agent端设计的重点不是“能采集多少数据”,而是“采集完数据之后怎么处理和上传”。我在Agent里做了两层缓冲:第一层是内存里的临时队列,每五秒采集一次数据,如果服务端暂时不可用,数据会暂存在队列里;第二层是磁盘上的滚动文件,队列满了之后会把旧数据写进本地日志文件,等网络恢复后再补传。这样是为了应对最常见的“服务端重启,Agent中断上报”场景,不至于服务端恢复之后数据全断档。

代码结构大致是这样:

import psutil import time from pynvml import nvmlInit, nvmlDeviceGetHandleByIndex, nvmlDeviceGetName class RigAgent: def __init__(self, rig_id: str, server_url: str): self.rig_id = rig_id self.server_url = server_url self.collect_interval = 5 self.buffer = [] self.buffer_limit = 60 nvmlInit() def collect_cpu(self): load = psutil.cpu_percent(interval=1) mem = psutil.virtual_memory() return { "cpu_load": load, "cpu_temp": self._get_cpu_temp(), "mem_used": mem.used, "mem_total": mem.total } def collect_gpu(self): gpus = [] device_count = nvmlDeviceGetCount() # 实际应为 nvmlDeviceGetCount_v2 for i in range(device_count): handle = nvmlDeviceGetHandleByIndex(i) name = nvmlDeviceGetName(handle) temp = nvmlDeviceGetTemperature(handle, 0) power = nvmlDeviceGetPowerUsage(handle) / 1000.0 fan = nvmlDeviceGetFanSpeed(handle) gpus.append({ "name": name, "temp": temp, "power": power, "fan": fan }) return gpus

Agent端的部署方式也做了简化。每台被监控的主机上只要运行一个systemd服务,读取一个位置固定的YAML配置文件,里面包含rig_id、服务器地址、上报间隔就行。我用的是零配置原则——除了这三个字段,其他的都从硬件状态实时读取,不用在客户端维护任何角色、权限之类的信息,那些都放在服务端统一管理。

2.2 服务端:一套Flask应用撑起的“神经中枢”

服务端的职责是收数据、存数据、算状态、发告警、提供API。我在服务端抽象出五个核心模块:Ingest、Storage、Aggregator、AlertManager、ApiServer。Ingest负责接收Agent的JSON上报,会先做一次基础的数据合法性校验,比如温度值不能是负的、功耗不能超过额定数的三倍以上,明显异常的数据直接丢弃,避免脏数据污染历史曲线。Storage层就是封装SQLite,按时间序列写入一张带索引的表。

Aggregator和AlertManager算是我花时间比较多的地方。Aggregator会周期性扫描最新的设备状态,根据最近几次上报数据判断设备当前处于哪种工况。例如满载、空闲、初始化中、离线,这些状态判定规则都不是随便拍脑袋写的,而是要结合设备的实际负载曲线来定。比如我们判定一个设备“离线”,不能单纯因为Agent没上报就下结论,因为Agent本身也可能卡死。我给Agent加了一个心跳间隔:默认每十秒上报一次心跳,服务端如果连续超过三十秒没有收到心跳,会先标记为“心跳丢失”,但不会立刻告警;再等一分钟仍然没有恢复,才会进入“离线”状态并触发告警。这一段双重判定逻辑在实际使用中帮我避免了很多误报。

AlertManager的规则配置也做成了简单的JSON文件,支持按设备分组设置阈值。比如我可以把会议室那台渲染机的GPU温度上限设到80℃,而机房里的计算卡则设到90℃。每种规则还支持告警冷却时间,避免同一个问题五分钟内重复发送通知。

2.3 前端面板:必须能用普通浏览器直接打开

可能有人会觉得,我这里已经有命令行工具了,还要什么前端面板?但实际用下来,一个能直接看到所有设备总览的仪表盘,真的能救急。比如有一天我人在外面,想确认某台机器的风扇是不是没有转,手机打开面板一眼就能看到那条温度曲线和风扇曲线之间的背离,这比翻日志快太多。

openrig的前端分为三个主要页面:总览页、单机详情页、告警历史页。总览页用卡片展示每一台主机,卡片上滚动更新GPU温度、总功耗、显存占用和运行状态。单机详情页展示更多的历史曲线,包括“最近24小时的温度变化”“在某个时间段内的功耗峰值”等,这些图表可以让异常发生前后的状态一目了然。告警历史页则记录每一次触发和恢复的事件,还带有时间戳和当时的设备快照,方便事后复盘——这个功能在排查“为什么凌晨断电”时真的好用。

前端和后端之间走WebSocket推送实时数据,同时保留REST API作为降级通道。WebSocket断开时,前端会自动切到轮询模式,不会中断展示。这个设计看着小,但很影响使用体验。

3. 远程控制链路:从断电重启到安全关机,每一步都有讲究

看板只是工具的一部分,对于需要远程管机器的人来说,控制能力才是真正的“刚需”。网上不少管理软件都是只监不控,出了问题还得靠人跑机房,那我写openrig就不搞这一套了。openrig支持三种远程控制:安全关机、重启、风扇调速。 但这里有个很重要的安全原则——远程控制必须设计成“默认拒绝”模式.

所谓“默认拒绝”,就是服务端收到控制指令后,不能直接对Agent下发执行命令,而是要先把请求记录到数据库,生成一个带时效的令牌,再通过WebSocket推送给Agent。Agent收到指令后会先校验令牌是否有效、指令是否合规,然后再执行。执行前Agent还会主动检查当前设备状态:如果设备负载很高,安全关机指令会自动变成“排队等待关机”,等负载降到阈值以下才执行。这个机制救过我一次——那次我误点了一台正在跑任务的机器的重启按钮,结果设备没有立刻重启,而是等任务跑完才执行了重启,避免了任务丢失。

远程重启这块其实还有不少人问过我:能不能远程控制AC电源开关?我只能说,真正的断电重启是需要硬件配合的。纯粹靠软件,你只能做到操作系统层面的重启,如果系统彻底死机、内核崩溃,软件层面的重启指令根本送不进去。所以openrig做了一个可选的扩展模块,通过支持PoE或者智能PDU的设备来实现断电级控制。如果你手头的硬件不支持,那这个功能就用不了,我建议还是配一个带网络管理的PDU,价格也不贵,但真到关键时刻能省一次跑机房的路途。

风扇调速这块则更“软”一些。我通过NVML控制原厂风扇转速,但不同显卡的风扇控制策略差异很大,有些卡不允许用户直接设置固定转速,所以我做了一层兼容:如果显卡支持PWM控制,就直接设置转速百分比;如果不支持,就通过修改功耗上限来做温控风扇策略的间接调节——例如当温度超过阈值时,提醒系统自动降低功耗上限,温度自然就下来了。这算是一个很实用的妥协方案。

4. 踩过的坑和想明白的道理:数据库写入、时间同步和传感器误报

在实际运行的项目里,很多时候决定成败的不是架构多高明,而是一些当时没想明白的小细节。openrig从雏形到现在稳定运行,我确实踩了不少坑,这里挑几个典型的说一下。

4.1 SQLite写锁和并发上报冲突

最早的时候我用SQLite直接让多台Agent并发写入,结果不到五个设备同时上报,数据库就开始报“database is locked”。原因是SQLite默认只支持一个写者,多个Agent并发写同一张表时,高频率的写入请求会互相阻塞。我的解决办法是两层:服务端加一个单线程的写队列,所有Agent上报的数据先进内存队列,由一个后台消费者线程统一写入数据库;另外把写入频率降为五秒一次,也就是一个Agent一分钟最多写十二次,写入压力小了非常多。改完之后并发数从五个提升到了几十个都没问题。

4.2 时间不同步导致告警时间线错乱

这个问题很隐蔽。因为我最开始设置告警规则时,用的是Agent上报的时间戳,假设各台机器的系统时间是准确的。但实际运行中发现,有两台机器的系统时间慢了将近一分半钟,当告警事件发生时,服务端记录的时间和Agent采集的时间不一致,在复盘时出现了一条告警对应两个不同时间点的情况,非常让人头疼。解决方法是Agent每次上报时同时携带两个时间:一是Agent本地的系统时间,二是server端收到数据时的接收时间。所有存储和告警判定都优先依赖server时间,Agent时间只作为参考字段。从这之后,告警时间线清清爽爽,再也没有错乱过。

4.3 传感器误报:风扇转速读取为零

另一个有趣的问题是传感器误报。有一台主机在正常运行中,GPU温度才50℃,但风扇转速传感器读数一直是零,这直接触发了“风扇异常”告警。我一开始以为是风扇坏了,后来用命令行的方式手动读传感器,发现转速值确实为零,但显卡并没有过热。仔细查了一下,发现是这款显卡在风扇完全停转的低温状态下,它的传感器会直接归零,并不是异常。这也说明监控系统的阈值设计不能光看单一数值,最好结合上下文。后来我在告警规则里加了一条:当风扇转速为零且温度低于60℃时,判定为“风扇停转(低温待机)”,不告警;只有同时满足“转速为零”和“温度持续上升”时,才触发真正的告警。

5. 实际部署效果与资源占用:一台树莓派就能扛起全部服务

整个openrig服务端跑下来,部署压力和资源占用都比想象中小很多。目前我的生产环境是一台树莓派4B,2GB内存版本,外挂一块移动硬盘做数据存储,Docker Compose启动服务端,Agent则分别跑在三台X86主机上。运行了将近一个半月,服务端的CPU占用率长期在15%以下,内存占用稳定在大概580MB左右,包含数据库服务、后端进程和前端静态文件服务。数据库文件目前大约220MB,记录了所有主机两个多月的秒级状态,还包含告警事件表,整体查询速度仍然很快,没到需要做分表或上时序数据库的程度。

这是一份实测的设备和告警表格式,如果要做参考可以拿来直接用:

字段名含义采集频率告警阈值(默认)
rig_id主机唯一标识Agent启动时生成无
gpu_tempGPU核心温度(℃)5秒85℃触发警告,90℃触发严重
gpu_powerGPU实时功耗(W)5秒超出TDP的120%触发警告
gpu_fan风扇转速百分比5秒0且温度>65℃触发警告
cpu_loadCPU总负载百分比5秒持续95%以上5分钟触发警告
mem_used内存已用量(GB)5秒占用率>95%触发警告
net_throughput网卡吞吐量(MB/s)10秒无默认阈值

部署的过程也做了简化。服务端使用Docker Compose一键启动,数据库和数据目录通过卷挂载到宿主机,升级代码时只要重新拉镜像启动即可,不会丢数据。Agent端更简单,一台全新的主机只需执行一行curl命令下载Agent安装脚本,然后填入rig_id和服务端地址,装个systemd服务,基本两分钟就能接入监控。

6. 往后的路线规划:多集群聚合、机器自动诊断和告警通道扩展

openrig现在的版本能完成“看状态、管状态、发告警”这些基础工作,但后续还有不少可以继续深入的方向。就目前观察到的实际需求而言,我最想做的有三个方向。

第一个方向是多集群聚合。现在openrig是单服务端模式,适合个人用户和单机房场景。但如果设备分散在多个地点,单服务端会变成一个瓶颈,而且网络波动也会影响数据完整性。我计划做一个联邦模式:每个地点的openrig实例独立运行,上层有一个总控实例,通过标准API与其他实例同步状态摘要和告警事件。这样每个机房的中控还是自治的,但总控可以提供一个跨区域的综合视图,对于设备分布在多个机房的人来说非常方便。

第二个方向是自动诊断和优化建议。现在告警只是告诉你“温度高了”,但不能告诉你“风扇是不是堵了”或者“风道是不是不合理”。理想的告警系统应该具备基础诊断能力:综合温度、风扇转速、功耗、环境温度等数据,自动生成一段描述性的诊断结论,例如“GPU温度持续上升,但风扇转速已接近峰值,可能是风道积灰导致散热失效”。这也是我未来想给openrig加的一个能力。

第三个方向是告警通道的扩展。目前告警支持邮件和Webhook推送,Webhook可以接到飞书、钉钉、企业微信这些聊天工具里,已经够用。后续我想再加上短信和电话告警通道,针对“真正严重的告警”用电话通知,避免只有邮件时错过重要报警。

最后分享一个单独的经验:如果你是刚开始尝试这类自建工具,我不建议一上来就想把所有功能都做了。先只做监控和温度告警,跑一段时间,让数据积累起来,再根据自己的实际痛点加功能,这样的系统才会真正用得顺手。openrig这个项目对我来说就是这样一步一步长起来的。

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

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

立即咨询