拓冰建站拓冰建站
首页 / 资讯中心 / 正文

群晖助手2.0:基于配置驱动的NAS自动巡检与Webhook告警实践

群晖 NAS 的日常维护比很多人想象中琐碎日志文件会慢慢占满系统分区硬盘温度异常不会提前发消息某个套件进程可能悄悄退出等我们发现时往往已经影响了下载或备份任务。如果每次都要人工登录 DSM 去翻看存储管理器、资源监控和日志中心既费时间也很容易漏掉关键异常。这也是不少群晖用户会自己封装一套“群晖助手”的原因。本文围绕一套自研的群晖助手 2.0 展开重点讲解它相比早期脚本在设计上做了哪些调整以及如何从零部署到自己的群晖 NAS 上。文章会包含可复制的 Python 代码、任务计划配置步骤、Webhook 告警推送示例以及常见问题的排查清单。新手可以按顺序搭起来老手可以直接把配置和脚本思路迁移到自己的工具里。1. 群晖助手到底是什么1.1 群晖 NAS 运维中的真实痛点先说一个很常见的场景群晖 NAS 的存储卷使用率超过 90%但系统不会主动弹窗提醒直到某个套件因为磁盘空间不足写不进去数据才开始报错。此时再登录 DSM 去清理往往已经影响到正在运行的服务。类似的松散状态还有很多内存占用长期偏高、硬盘温度过高、某个 Docker 容器意外退出、定时备份任务失败、DDNS 域名到期没有续费。这些信息分散在 DSM 的不同页面里靠人工巡检很难每天坚持尤其当 NAS 放在家里或分公司身边没有专业运维人员时更容易被忽略。群晖助手这类工具本质上就是把这些分散的状态检查、阈值判断、消息通知和日志记录统一起来用很小的脚本成本替代大部分人工巡检工作。它解决的不是“能不能正常用”的问题而是“出了问题能不能第一时间知道”的问题。1.2 从 1.0 到 2.0架构思路的转变很多朋友最早的 1.0 版本通常是从网上复制一两个 Shell 脚本改一改路径然后手动执行。脚本和脚本之间没有统一配置检查项越多维护成本越高。等到想要增加一个“温度告警”可能要把整个脚本从头读一遍才能下手。2.0 版本的核心变化是把“散落的脚本”升级成“配置驱动的检查框架”。可以简单对比一下能力项1.0 时代的常见形态2.0 时代的推荐形态脚本组织多个散落的 Shell/Python 脚本统一目录入口脚本 配置文件扩展检查项修改脚本逻辑容易改坏在配置文件中增加检查项即可告警通知只在终端打印结果或单一邮件通知Webhook 多通道支持告警冷却运行方式手动执行忘了就没结果任务计划定时执行自动告警日志处理print 输出重启后找不到历史文件日志方便回溯历史状态这种设计不是为了炫技而是为了让工具真正能“长期跑下去”。一个脚本如果每次改需求都要重新读一遍代码那它很难坚持维护。配置驱动之后日常最多改动的是 config.json主程序可以保持稳定。1.3 2.0 版本适用于哪些场景群晖助手 2.0 并不追求覆盖所有运维场景它更适合以下使用方式家用或小型工作室的群晖 NAS需要定时巡检磁盘、内存、温度等基础指标。已经有 1.0 脚本但告警逻辑混乱、通知方式单一希望整理成可维护版本。需要接入钉钉、企业微信或 Bark 等手机推送实现“异常主动找上门”。希望后续扩展更多检查项但不希望每次扩展都重写主程序。如果只是偶尔登录一下 DSM 看看状态那没必要上助手工具但如果你已经有过“半夜发现 NAS 磁盘满了”的经历这套框架就能体现价值。2. 环境准备与版本说明2.1 硬件与系统环境在部署群晖助手 2.0 之前需要先确认运行环境。本文示例以常见群晖 NAS 环境为例具体版本需要根据你的项目实际情况调整重点演示配置思路。建议环境如下群晖 NAS 一台系统为 DSM 7.x 及以上。已开启 SSH 功能便于排查问题也可以在本地终端执行脚本。系统自带 Python 3版本通常为 3.8 或更高。有管理员权限用于创建任务计划和配置脚本运行用户。如果想收到手机推送需要有一个可用的 Webhook 地址例如钉钉机器人、企业微信机器人或 Bark。需要注意不同 DSM 小版本之间的路径和命令可能会略有差异。比如某些机型访问/sys/class/thermal时可能没有温度传感器节点脚本会做容错处理如果你发现某个检查项无法生效优先检查该机型是否支持对应硬件接口。2.2 项目目录规划为了让脚本长期可维护建议单独创建一个目录不要直接放在很多人共用的临时目录里。这里规划一个简单的目录结构mkdir -p /volume1/tools/assistant/logs cd /volume1/tools/assistant后续生成的目录结构如下/volume1/tools/assistant/ ├── assistant.py ├── config.json └── logs/ ├── assistant.log └── last_alert.json一个文件放主程序一个文件放配置日志目录单独存放。这样做的好处是升级脚本时只需要替换 assistant.py配置可以保留查看历史日志时也集中在 logs 目录下不会散落得到处都是。2.3 凭据与密钥准备群晖助手 2.0 在示例中会用到 Webhook 地址这类地址通常相当于一个“推送入口”一旦泄露别人可能向你的机器人推送垃圾消息。建议将 Webhook 地址视为敏感信息不要随意截图发到公开平台。不要把 Webhook 地址硬编码进代码仓库示例中放在 config.json 是为了演示清晰生产环境建议改用环境变量或权限为 600 的独立配置文件。如果 Webhook 平台支持密钥签名务必开启并在发送逻辑中补充签名参数。如果你后续扩展了 SSH 远程采集能力同样要遵守最小权限原则优先使用专用账号和公钥认证不要直接用 root 口令。3. 群晖助手 2.0 核心设计拆解3.1 整体工作流程群晖助手 2.0 的运行逻辑并不复杂核心流程可以拆成六步读取 config.json加载检查项、阈值、通知地址等配置。执行各检查模块例如磁盘检查、内存检查、温度检查。汇总检查结果并和配置中的阈值进行对比。如果存在异常进入告警判断逻辑检查是否处于冷却期内。发送 Webhook 推送将巡检报告发送到钉钉、企业微信或 Bark 等平台。写入本地日志方便后续回溯。这个流程把“检查”和“通知”解耦开。检查模块只负责取数据、算状态通知模块只负责把消息推出去。这样后续新增一个检查项时不需要动任何通知代码。3.2 检查器接口设计为了避免检查逻辑越堆越乱2.0 版本建议每个检查项都以一个类或函数为单位统一返回结构化结果。下面是一个简化版接口示意class BaseChecker: name base def check(self, config): 返回结构示例 { field: disk:/volume1, value: 82, threshold: 90, ok: true, message: /volume1 使用率 82% } raise NotImplementedError每个检查器返回的字段包含检查项名称、当前值、阈值、是否正常和人类可读的描述。主程序只关心这些字段不需要关心具体的采集命令。这种设计让整个工具具备很强的可扩展性。3.3 告警阈值与冷却策略告警最怕的是“轰炸式通知”。比如磁盘使用率持续在 95%如果脚本每 5 分钟跑一次群里就会每 5 分钟收到一条告警最后大家都把群消息屏蔽了真正的问题反而被淹没。因此 2.0 版本引入了告警冷却机制在第一次推送异常后记录一个时间戳冷却时间内即使脚本再次检测到异常也不再重复推送只记录到本地日志。等冷却时间过后如果异常仍未恢复再推送一次。代码层面用简单的 JSON 文件保存上次告警时间即可不需要引入额外数据库。3.4 通知通道抽象通知模块可以抽象成一个通用 Webhook 发送函数。不同平台的消息格式略有差异比如钉钉机器人要求msgtype字段企业微信机器人有时需要markdown字段Bark 则直接提供title和body。但从脚本角度来说最后都会转化为一次 HTTPS POST 请求。因此主程序只需要暴露一个send_webhook(url, content)函数具体平台差异放在生成消息正文的地方处理。示例为了通用采用最简单的文本消息结构如果你用的是钉钉可以在消息生成函数中补充msgtype字段。4. 完整实战从零部署群晖助手 2.04.1 创建项目目录首先通过 SSH 登录群晖或者直接使用群晖的“控制面板-终端机和 SNMP”开启 SSH 功能后登录。然后在合适的存储卷下创建项目目录mkdir -p /volume1/tools/assistant/logs cd /volume1/tools/assistant如果后续还要放置其他工具可以在/volume1/tools下按工具名分目录保持整洁。4.2 编写配置文件在项目目录下创建 config.json。下面是一个完整示例配置文件{ nas_name: MyNAS, checks: [disk, memory, temperature], volumes: [/volume1], thresholds: { disk_usage: 90, memory_usage: 85, temperature: 70 }, cooldown_minutes: 30, notify: { webhook_url: https://example.com/hook/your-token } }配置项说明nas_name告警消息里显示的名称方便多台设备时区分。checks启用哪些检查项目前支持 disk、memory、temperature。volumes需要检查磁盘使用率的存储卷列表如果你的 NAS 有多个 volume按需添加。thresholds各类指标的告警阈值。cooldown_minutes同一类异常推送的最小间隔建议 30 分钟以上。notify.webhook_url接收告警的 Webhook 地址实际使用时应填入你所用平台的地址。4.3 编写主程序 assistant.py接下来是核心脚本。为了减少群晖上安装第三方依赖的麻烦这里只使用 Python 标准库不依赖 requests 等包。脚本可以直接运行。#!/usr/bin/env python3 # -*- coding: utf-8 -*- 群晖助手 2.0 - 自动巡检示例 使用 Python 标准库实现不依赖第三方包。 import json import os import shutil import logging import time import urllib.request from datetime import datetime BASE_DIR os.path.dirname(os.path.abspath(__file__)) CONFIG_PATH os.path.join(BASE_DIR, config.json) LOG_PATH os.path.join(BASE_DIR, logs, assistant.log) LAST_ALERT_PATH os.path.join(BASE_DIR, logs, last_alert.json) logging.basicConfig( filenameLOG_PATH, levellogging.INFO, format%(asctime)s [%(levelname)s] %(message)s, datefmt%Y-%m-%d %H:%M:%S ) def load_config(): with open(CONFIG_PATH, r, encodingutf-8) as f: return json.load(f) def check_disk(config): 检查指定 volume 的磁盘使用率。 results [] threshold config[thresholds][disk_usage] for vol in config.get(volumes, [/volume1]): try: usage shutil.disk_usage(vol) percent round(usage.used / usage.total * 100, 2) results.append({ field: fdisk:{vol}, value: percent, threshold: threshold, ok: percent threshold, message: f{vol} 使用率 {percent}% }) except Exception as e: results.append({ field: fdisk:{vol}, value: -1, threshold: threshold, ok: False, message: f{vol} 检查失败: {e} }) return results def check_memory(config): 检查内存使用率读取 /proc/meminfo。 try: with open(/proc/meminfo, r, encodingutf-8) as f: lines f.readlines() meminfo {} for line in lines: parts line.split(:) if len(parts) 2: continue key parts[0] value parts[1].strip().split( )[0] meminfo[key] int(value) total meminfo.get(MemTotal, 0) available meminfo.get(MemAvailable, 0) if total 0: return [] percent round((total - available) / total * 100, 2) threshold config[thresholds][memory_usage] return [{ field: memory, value: percent, threshold: threshold, ok: percent threshold, message: f内存使用率 {percent}% }] except Exception as e: return [{ field: memory, value: -1, threshold: 0, ok: False, message: f内存检查失败: {e} }] def check_temperature(config): 尝试读取 CPU/主板温度部分群晖机型支持失败则跳过。 results [] thermal_root /sys/class/thermal try: for zone in os.listdir(thermal_root): if not zone.startswith(thermal_zone): continue temp_path os.path.join(thermal_root, zone, temp) if not os.path.exists(temp_path): continue with open(temp_path, r, encodingutf-8) as f: raw f.read().strip() temp int(raw) / 1000.0 threshold config[thresholds].get(temperature, 70) results.append({ field: zone, value: temp, threshold: threshold, ok: temp threshold, message: f{zone} 温度 {temp}°C }) except Exception as e: logging.warning(温度检查失败已忽略: %s, e) return results def should_send_alert(config): 告警冷却再次推送前需要等待足够时间。 cooldown config.get(cooldown_minutes, 30) if not os.path.exists(LAST_ALERT_PATH): return True try: with open(LAST_ALERT_PATH, r, encodingutf-8) as f: last json.load(f) elapsed time.time() - last.get(timestamp, 0) return elapsed cooldown * 60 except Exception: return True def save_alert_time(): 记录最近一次告警推送时间。 with open(LAST_ALERT_PATH, w, encodingutf-8) as f: json.dump({timestamp: time.time()}, f) def send_webhook(url, content): 通用 Webhook 推送使用标准库 urllib。 payload {text: content} data json.dumps(payload).encode(utf-8) req urllib.request.Request( url, datadata, headers{Content-Type: application/json}, methodPOST ) with urllib.request.urlopen(req, timeout10) as resp: body resp.read().decode(utf-8) logging.info(Webhook 响应: %s, body) return body def format_report(config, results): 将巡检结果格式化为可读文本。 lines [f【{config.get(nas_name, 群晖助手)} 巡检报告】] has_error False for item in results: if not item[ok]: has_error True status 异常 if not item[ok] else 正常 lines.append(f- [{status}] {item[message]}) lines.append(f- 巡检时间{datetime.now().strftime(%Y-%m-%d %H:%M:%S)}) return \n.join(lines), has_error def main(): config load_config() logging.info(群晖助手 2.0 开始执行) results [] if disk in config.get(checks, []): results.extend(check_disk(config)) if memory in config.get(checks, []): results.extend(check_memory(config)) if temperature in config.get(checks, []): results.extend(check_temperature(config)) report, has_error format_report(config, results) print(report) logging.info(\n%s, report) if has_error and should_send_alert(config): webhook config.get(notify, {}).get(webhook_url) if webhook: try: send_webhook(webhook, report) save_alert_time() logging.info(告警推送完成) except Exception as e: logging.error(告警推送失败: %s, e) else: logging.warning(检测到异常但未配置 webhook_url) logging.info(群晖助手执行结束) if __name__ __main__: main()代码虽然不长但已经具备完整的巡检闭环。下面解释几个关键点磁盘检查使用shutil.disk_usage可以获取指定目录所在文件系统的总容量、已用容量和剩余容量兼容性比解析df命令输出更好。内存检查直接读取/proc/meminfo这是 Linux 系统下的通用做法群晖系统同样适用。温度检查会遍历/sys/class/thermal下的 thermal_zone如果机型不支持会记录 warning 并继续执行不会因为温度异常导致整个脚本中断。告警冷却通过last_alert.json保存时间戳避免短时间内重复推送。Webhook 发送使用urllib.request不依赖第三方包部署成本低。脚本中使用了logging模块所有关键运行信息都会写入logs/assistant.log方便后续定位问题。4.4 在群晖任务计划中添加定时任务脚本需要定时运行最简单的方式是使用群晖自带的“任务计划”。操作步骤如下登录 DSM打开“控制面板”。找到“任务计划”点击“新增”。选择“计划的任务”任务类型选择“用户自定义脚本”。在“常规”标签页填写任务名称例如“群晖助手巡检”运行用户建议选择有脚本执行权限的管理员账号。在“计划”标签页设置执行频率例如每天每小时执行一次。在“任务设置”标签页的“运行命令”中填入python3 /volume1/tools/assistant/assistant.py点击“确定”保存任务。这里要特别注意任务计划里的运行环境可能不会自动加载用户环境中配置的 PATH所以如果遇到“python3 找不到”的问题可以先执行which python3找到 Python 解释器绝对路径然后在运行命令中使用绝对路径例如/usr/bin/python3 /volume1/tools/assistant/assistant.py4.5 手动运行与验证配置好任务计划之前建议先手动执行一次脚本确认代码逻辑和配置都没问题。在 SSH 终端中运行cd /volume1/tools/assistant python3 assistant.py正常情况下会看到类似下面的输出【MyNAS 巡检报告】 - [正常] /volume1 使用率 67.32% - [正常] 内存使用率 42.18% - [正常] thermal_zone0 温度 45.0°C - 巡检时间2024-12-01 08:00:00日志文件logs/assistant.log中也会写入对应的记录。验证告警推送时可以临时把 config.json 中的阈值调低例如把disk_usage改成 50再运行一次脚本。此时磁盘使用率大概率超过阈值脚本会触发 Webhook 推送。确认收到消息后把阈值改回正常值即可。5. 常见问题与排查思路问题现象常见原因解决思路任务计划没有执行运行用户权限不足或脚本路径错误先手动执行脚本排除代码问题再检查任务设置提示 python3 找不到任务计划未加载 PATH 环境变量使用which python3查找绝对路径配置中写绝对路径Webhook 推送失败URL 配置错误、网络不通或平台要求签名先用 curl 测试 Webhook再看 assistant.log 的错误信息温度一直读不到机型未暴露 thermal zone 或权限受限关闭 temperature 检查项或采用其他温度获取方式日志中没有内容logs 目录不存在或脚本没有写权限手动创建 logs 目录检查目录属主和权限磁盘检查失败配置的 volume 路径不存在确认 config.json 中 volumes 是否真实存在下面展开几个高频问题的排查过程。如果你发现任务计划一直不执行第一步应该确认手动执行是否正常。手动执行可以排除脚本本身的问题如果手动执行正常那么问题基本出在任务计划的用户权限或路径配置上。建议把运行用户设置为管理员账号并使用脚本绝对路径。如果提示 python3 找不到大概率是环境变量问题。解决方法很简单在 SSH 中执行which python3拿到 Python 解释器的绝对路径比如/usr/bin/python3然后在任务计划里写成完整路径命令。另外脚本权限也要确保有执行权限建议执行chmod 755 /volume1/tools/assistant/assistant.py。Webhook 推送失败时日志中通常会记录异常信息。可以先在本地终端用 curl 模拟一次推送确定 Webhook 地址本身是否可用再检查脚本代码中的消息格式是否满足平台要求。例如部分平台要求在请求头中加入Content-Type: application/json脚本里已经处理如果还失败看响应内容会提示具体原因。温度读不到并不影响整体使用。群晖不同机型的硬件传感器路径差异较大部分机型没有在/sys/class/thermal下暴露温度信息。遇到这种情况可以暂时从 checks 中移除 temperature后续再通过 S.M.A.R.T 命令或外部温度计设备补充。6. 最佳实践与工程建议6.1 权限、密钥与安全边界脚本默认使用管理员账号运行这本身是高风险操作。建议为巡检脚本单独创建一个专用系统账号只授予执行脚本和读取状态所需的最小权限。如果脚本未来需要执行更多系统命令要通过 sudo 配置白名单而不是直接使用 root 账号。Webhook 地址和任何 Token 都要避免出现在代码仓库里。示例为了方便演示将 Webhook 放在 config.json 中真实环境中建议通过环境变量或单独的文件保存并把配置文件权限设置为 600。日志中也不要打印完整的 Token 或密钥信息避免泄露。6.2 脚本健壮性与告警策略巡检脚本的健壮性比功能数量更重要。单个检查项失败不应该导致整个巡检中断这也是为什么每个检查函数内部都有 try/except 的原因。告警策略上冷却机制不能省略否则会有频繁通知的问题。在实际项目中可以进一步优化告警策略连续 N 次检查都异常才推送告警避免瞬时抖动造成误报。告警恢复后发送一条“已恢复”消息形成闭环。不同检查项使用不同冷却时间例如磁盘告警冷却 60 分钟内存告警冷却 10 分钟。这些优化可以逐步迭代不用第一次就全部做完。6.3 发布与升级建议群晖助手 2.0 的升级建议遵循“配置与代码分离”的原则。升级 assistant.py 时config.json 保持不变升级前先到测试机或测试目录运行一遍确认没有语法错误和破坏性变更再替换生产环境的文件。如果有多台群晖 NAS可以使用 Git 管理脚本和配置模板。配置模板中只保留占位符不同设备的实际值通过
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门