前言
终于等来了国庆长假!宅在家折腾自己的 HomeLab、 K8s、 NAS 服务器集群的感觉真爽!这次长假真的是折腾了好多东西,感兴趣的读者请期待后续的爆更,等不及的话也可以直接看我的 repo - east4ming/homelab2 的 PR 和 Commits。
homelab2 里有一台 QNAP NAS,跑了好几年,一直是块心病:它不在监控里。
集群里的 node、pod、中间件、URL 监控都齐了,偏偏这台 NAS 是个黑盒。它上面躺着我的照片、备份、电视、电影,盘什么时候开始报错、卷什么时候快满了,全靠我偶尔登 Web 控制台瞄一眼。典型的「防御式运维」,出事了才知道。
这件事放在待办清单里至少快2年了,趁国庆把这套补上。折腾完之后发现,坑比我想的多得多,尤其是那个**「面板有曲线但数据是假的」**的静默失效,说实话有点费劲。
先交代下对象:
| 项目 | 值 |
|---|---|
| 型号 | QNAP TS-453Bmini(NAS33657A) |
| 系统 | QTS 5.2.10 / kernel 5.10.60-qnap / x86_64 |
| 内存 | 16GB |
| LAN | 192.168.3.216 |
| 盘位 | 4 个内置 + 1 个 USB 外接盘 |
| 存储卷 | 5 个 |
最终效果图
先放几张最终效果图:
方案:Container Station 跑两个只读 exporter
NAS 上没法装 K8s agent,但 QNAP 有 Container Station(本质是个 docker)。所以思路很直接:
- NAS 侧跑两个 exporter,走 host 网络,让集群直接按 IP:Port 抓;
- 集群侧在 Prometheus 里加两个静态 job;
- 告警和面板都在集群侧统一管理。
yaml
# metal/nas/docker-compose.yml
services:
nas-node-exporter:
image: quay.io/prometheus/node-exporter:v1.8.2
container_name: nas-node-exporter
network_mode: host
restart: unless-stopped
pid: host
privileged: true
command:
- --path.rootfs=/host
- --collector.filesystem.mount-points-exclude=^/(dev|proc|sys|run|mnt/snapshot|share/NFSv=4)($|/)
volumes:
- /:/host:ro,rslave
nas-smartctl-exporter:
image: docker.io/prometheuscommunity/smartctl-exporter:v0.14.0
container_name: nas-smartctl-exporter
network_mode: host
restart: unless-stopped
privileged: true
user: "0:0"
command:
- --smartctl.device=/dev/sda;sat
- --smartctl.device=/dev/sdb;sat
- --smartctl.device=/dev/sdc;sat
- --smartctl.device=/dev/sdd;sat
- --smartctl.device=/dev/sde;sat
📝Notes :
restart: unless-stopped是必须的,NAS 断电重启后要能自动恢复采集,不然又是一次静默盲区。
实测开销:node-exporter 3101 条序列 / 19.89MiB,镜像 23.3MB;smartctl-exporter 496 条序列 / 6.449MiB,镜像 32.2MB。对 16GB 内存的 NAS 来说基本无感。
🐾 注意: 镜像源要挑。实测
quay.io/prometheus-community/smartctl-exporter返回 unauthorized ,docker.io/prometheuscommunity/smartctl-exporter才是可用的。node-exporter 走 quay.io 没问题。
坑一:温度恒为 0 的静默失效 😱
这是最阴的一个。
第一版 compose 里,我偷懒没指定设备,让 exporter 自己扫:
bash
smartctl --scan-open
# /dev/sda -d scsi
# /dev/sdb -d scsi
# /dev/sdc -d scsi
# /dev/sdd -d scsi
4 块 SATA 机械盘,全被识别成 SCSI。后果是什么?
smartctl_device_temperature恒为 0;- ATA 属性(通电时间、重映射扇区、SMART 各项原始值)全部缺失;
- 但 exporter 照常输出、Grafana 面板照常画曲线。😑
这就叫静默失效,比没数据危险得多。没数据你会去查,有数据你会信。要是哪天盘真过热了,我的告警规则永远触发不了。
修法:强制 SAT 类型。
bash
--smartctl.device=/dev/sda;sat
📝Notes :
<device>;<type>这个语法要求镜像 ≥ v0.14.0 。我先用 v0.12.0 试的,直接报No such device,白折腾半小时。
改完之后温度才正常:sda 46°C、sdb 49°C、sdc 45°C、sdd 44°C、sde 44°C,5 块盘 smart_status 全为 1。
坑二:设备不可见 + 容器跑在 nobody
强制指定 /dev/sdX 之后,第二个坑立刻撞上来:/dev/sd* 在普通容器里根本不存在。
加上 privileged: true 才出现。但出现之后还打不开------容器默认 UID 是 nobody,而 /dev/sda 的权限是 brw------- root:root。
所以这两行必须同时存在:
yaml
privileged: true
user: "0:0"
📝Notes : 这是我个人比较不喜欢的那种组合:privileged + root。但 SMART 读取本质上就是特权操作,绕不过去。缓解措施是容器只读 挂载宿主
/,不挂 docker.sock,不监听公网,端口只开在 LAN IP 上。
坑三:挂载点噪声
node-exporter 挂宿主根目录,会带出一堆垃圾:
- 25 个
/mnt/snapshot/<pool>/<snap>快照视图 /share/NFSv=4/*重导出/share/CACHEDEV*_DATA/.qpkg/*QPKG 沙箱- 宿主自己的
/sys、/proc
不排除的话,容量面板能给你刷出几十行,全是假的。
排除之后 node_filesystem_size_bytes 恰好剩 10 个挂载点 ,其中真实存储卷 6 个:/share/CACHEDEV1..5_DATA 加上 /share/external/DEV3303_1。
bash
- --collector.filesystem.mount-points-exclude=^/(dev|proc|sys|run|mnt/snapshot|share/NFSv=4)($|/)
- --collector.filesystem.fs-types-exclude=^(autofs|binfmt_misc|cgroup|configfs|debugfs|devpts|devtmpfs|fusectl|hugetlbfs|mqueue|nsfs|overlay|proc|procfs|pstore|rpc_pipefs|securityfs|selinuxfs|squashfs|sysfs|tracefs)$
盘位标签:为什么要硬编码
磁盘告警只说 /dev/sdc 没用,我要的是「bay1 那块盘」。盘位映射依据实测:
bash
qcli_storage -d
# sdc → bay1
# sdd → bay2
# sda → bay3
# sdb → bay4
在 metric_relabel_configs 里逐条静态映射:
yaml
metric_relabel_configs:
- source_labels: [device]
regex: /dev/sdc
target_label: disk
replacement: bay1
# ... 其余三块同理
🤔 有同学会问:为什么不用 relabel_configs 的 labelmap 批量映射?
因为 separator 是全局参数,批量映射出来是 /dev/sdc;bay1 这种,拿不到干净的 disk=bay3。这种小场景,老老实实写四行反而最省事。
告警:两处「上线即永久误报」
6 条规则:卷容量两级阈值、卷被重挂只读、RAID 失去冗余、SMART 健康失败、磁盘高温。抓取中断直接复用既有的 TargetDown,不重复定义。
但写完跑 promtool check rules 通过,不代表规则能上线。用真实环境样本一评估,两条规则是永久误报:
误报一:/mnt/ext
它是 436MB 的固件 DOM(/dev/md13),实测可用只剩 7.69%。这是 QNAP 的固定布局,不可配置。卷容量规则必须限定排除掉固件 DOM:
yaml
expr: |
node_filesystem_avail_bytes{mountpoint=~"/share/.+"}
/ node_filesystem_size_bytes{mountpoint=~"/share/.+"} < 0.15
误报二:md9 / md13
这俩是 32 槽位的固件 DOM 镜像,实际只用 4 槽,所以 node_md_disks{state="active"} < node_md_disks_required 恒成立 。RAID 规则必须硬编码范围,并且用 on (device) 做向量匹配:
yaml
expr: |
node_md_disks{state="active", device=~"md1|md2|md256|md322"}
< on (device) node_md_disks_required
修正后,6 条规则在当前环境全部 inactive(数据卷可用 29%--90%,SMART 全为 1,最高盘温 49°C)。
📝Notes : 这两条属于领域约束,不是 bug。你不能改 QNAP 的固件 DOM 设计,只能把范围限制硬编码进规则。写告警的时候,光看语法过没过是远远不够的,必须拿真实样本逐条评估。
GitOps 的边界:ArgoCD 管不了集群外的容器
集群侧所有资源(scrape config、rules、dashboard)全部走 ArgoCD,这个没争议。争议在 NAS 侧。
我逐条否决了几条路:
| 方案 | 否决原因 |
|---|---|
| NAS 上装 k3s 当节点 | 4 盘位家用 NAS,为了跑两个 exporter 装个 K8s,杀鸡用牛刀 |
| ArgoCD ConfigManagementPlugin | 还是要落容器到 NAS,问题没解决 |
| 自建 webhook 控制器 | 维护成本远超收益 |
| Entware 非容器安装 | 无版本锁定、无回滚、污染 QTS 系统 |
最终方案:compose 入库 + Container Station 人工导入。
好处是配置有版本、可追溯、可 review;代价是导入这一步得手动------改了 compose,得去 Container Station 重建应用。
说实话这个代价我认了。ArgoCD 的管辖边界就是「集群内」,硬要把它伸到集群外的设备上,只会搞出一堆更脆的胶水。NAS 不是集群节点,也不该被塞进 metal/inventories/prod.yml。所以 NAS 的采集定义单独放 metal/nas/,各归各的。
没采到的指标,也交代一下
- 机箱温度、风扇转速 :QNAP 控制器不通过 hwmon 暴露,
/sys/class/hwmon下只有 CPUcoretemp。所以温度监控只覆盖硬盘。 qcli_hdd:需要交互式 QCLI 登录,会引入管理员凭据,否决。get_hd_smartinfo:实测无输出,依赖私有库,否决。- NAS 上不存在
smartctl和snmpd。
参考了两个仓库(zottelbeyer/QNAP-collectd-influxdb-grafana 走 collectd 推送 InfluxDB,sandrotosi/qnap-dashboards 依赖 SNMP),跟本仓库的 Prometheus 拉取模型都不兼容,只作指标维度参考,没采用。
运维要点
- 增删硬盘 :要更新 compose 里的
--smartctl.device列表并在 Container Station 重建应用。显式指定设备后,exporter 不再自动重扫。 - QTS 升级导致盘符变化 :按
qcli_storage -d复核盘位。 - NAS 换 IP :更新
values.yaml里的两个静态目标。 - 硬盘休眠:SMART 读取可能把盘唤醒,介意的话要权衡采集频率。
总结
把 NAS 接进监控只是表面,真正让我长记性的是那些静默失效。
面板上有曲线、指标有输出,不等于数据是对的。SCSI 误判、UID 打不开设备、挂载点噪声、固件 DOM 的永久误报,每一个都不会报错,只会安安静静地骗你。
纸上得来终觉浅,绝知此事要躬行。规则语法过了、promtool 过了,都只是第一步;真正让规则可信的,是拿实测样本一条条去跑、去对、去证伪。
现在这套跑在 homelab2 上:13 个面板的 nas.json,6 条告警规则全 inactive,5 块盘温度 44--49°C,数据卷可用 29%--90%。终于不用再登 Web 控制台了。🎉
以上。