openrig:开源显卡算力主机远程管理控制台实践解析
我其实纠结了很久要不要把这个项目拿出来写。原因很简单“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(interval1) 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 gpusAgent端的部署方式也做了简化。每台被监控的主机上只要运行一个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服务端跑下来部署压力和资源占用都比想象中小很多。目前我的生产环境是一台树莓派4B2GB内存版本外挂一块移动硬盘做数据存储Docker Compose启动服务端Agent则分别跑在三台X86主机上。运行了将近一个半月服务端的CPU占用率长期在15%以下内存占用稳定在大概580MB左右包含数据库服务、后端进程和前端静态文件服务。数据库文件目前大约220MB记录了所有主机两个多月的秒级状态还包含告警事件表整体查询速度仍然很快没到需要做分表或上时序数据库的程度。这是一份实测的设备和告警表格式如果要做参考可以拿来直接用字段名含义采集频率告警阈值默认rig_id主机唯一标识Agent启动时生成无gpu_tempGPU核心温度℃5秒85℃触发警告90℃触发严重gpu_powerGPU实时功耗W5秒超出TDP的120%触发警告gpu_fan风扇转速百分比5秒0且温度65℃触发警告cpu_loadCPU总负载百分比5秒持续95%以上5分钟触发警告mem_used内存已用量GB5秒占用率95%触发警告net_throughput网卡吞吐量MB/s10秒无默认阈值部署的过程也做了简化。服务端使用Docker Compose一键启动数据库和数据目录通过卷挂载到宿主机升级代码时只要重新拉镜像启动即可不会丢数据。Agent端更简单一台全新的主机只需执行一行curl命令下载Agent安装脚本然后填入rig_id和服务端地址装个systemd服务基本两分钟就能接入监控。6. 往后的路线规划多集群聚合、机器自动诊断和告警通道扩展openrig现在的版本能完成“看状态、管状态、发告警”这些基础工作但后续还有不少可以继续深入的方向。就目前观察到的实际需求而言我最想做的有三个方向。第一个方向是多集群聚合。现在openrig是单服务端模式适合个人用户和单机房场景。但如果设备分散在多个地点单服务端会变成一个瓶颈而且网络波动也会影响数据完整性。我计划做一个联邦模式每个地点的openrig实例独立运行上层有一个总控实例通过标准API与其他实例同步状态摘要和告警事件。这样每个机房的中控还是自治的但总控可以提供一个跨区域的综合视图对于设备分布在多个机房的人来说非常方便。第二个方向是自动诊断和优化建议。现在告警只是告诉你“温度高了”但不能告诉你“风扇是不是堵了”或者“风道是不是不合理”。理想的告警系统应该具备基础诊断能力综合温度、风扇转速、功耗、环境温度等数据自动生成一段描述性的诊断结论例如“GPU温度持续上升但风扇转速已接近峰值可能是风道积灰导致散热失效”。这也是我未来想给openrig加的一个能力。第三个方向是告警通道的扩展。目前告警支持邮件和Webhook推送Webhook可以接到飞书、钉钉、企业微信这些聊天工具里已经够用。后续我想再加上短信和电话告警通道针对“真正严重的告警”用电话通知避免只有邮件时错过重要报警。最后分享一个单独的经验如果你是刚开始尝试这类自建工具我不建议一上来就想把所有功能都做了。先只做监控和温度告警跑一段时间让数据积累起来再根据自己的实际痛点加功能这样的系统才会真正用得顺手。openrig这个项目对我来说就是这样一步一步长起来的。