前言:飞牛自带的监控,为什么不够用

飞牛 NAS 自带的资源监控只有一个百分比数字:CPU 多少、内存多少,没了。但实际运维中真正要回答的问题是:是哪个进程在吃 CPU?内存被谁占了?CPU 烧到多少度?风扇转不转?这台机器的配置到底顶不顶得住?哪块盘快坏了?哪个端口被谁占着?

于是我写了 fnos-resmon(飞牛资源管理器):一个单文件 Flask + psutil 的轻量监控面板,专为飞牛 NAS 设计,一条 docker 命令启动,浏览器打开 http://NAS-IP:7031 就能看,手机也能看。

开源地址:https://gitea.88531.cn/jinghui/fnos-resmon

一、它能做什么

二、实现原理:容器里怎么读到宿主真实数据

监控跑在 Docker 里,默认只能看到容器自己的 namespace。要读宿主真实数据,靠三招:

services:
  fnos-resmon:
    image: jpccr.ccs.tencentyun.com/jinghui/fnos-resmon:latest
    network_mode: host
    pid: host          # 与宿主共享 PID 命名空间
    privileged: true
    volumes:
      - /proc:/host/proc:rw   # 宿主 proc
      - /sys:/host/sys:ro     # 宿主 sysfs(温度/风扇/网卡从这读)

1. CPU / 内存 / 进程:psutil 重定向 PROCFS。启动时把 psutil.PROCFS_PATH 指向 /host/proc,之后所有 psutil.process_iter / cpu_percent / virtual_memory 读到的都是宿主数据:

PROCFS = os.environ.get("PROCFS_PATH", "/host/proc")
if os.path.isdir(PROCFS):
    psutil.PROCFS_PATH = PROCFS

2. 网络:nsenter 进宿主 netns。容器即使 bridged 模式,直读 /proc/net 也只能看到容器自身。网络类数据统一经 nsenter --target 1 --mount --net 在宿主(PID 1)netns 里执行 cat /proc/net/dev 和 ss -tunHn state all,失败才回退到 psutil 容器视角:

NSENTER = ["nsenter", "--target", "1", "--mount", "--net"]

def _host_ss():
    """宿主 tcp/udp 连接+监听,经 nsenter 在宿主 netns 执行 ss"""
    out = _nsenter_run(["ss", "-tunHn", "state", "all"])

ss 行尾的 ("进程名",pid=1234,...) 会被正则抠出来,再用进程表按 PID 补齐进程名,前 80 条连接 + 前 80 条监听返回前端。

3. 后台采样线程 + 2 秒缓存。进程 CPU 统计需要两次采样算差值,所以 sample_procs() 先全员 cpu_percent(None) 打点,sleep 1 秒后再读数,按 CPU 倒序,前端每 2 秒拿到的是缓存,不会每次请求都卡 1 秒:

def sampler_loop():
    while True:
        try:
            sample_procs()
        except Exception:
            pass
        time.sleep(2)

threading.Thread(target=sampler_loop, daemon=True).start()

每个进程采集的字段:cpu / mem / rss / uss / swap / 线程数 / 用户 / 状态 / IO 读写 / 连接数。容器内 /etc/passwd 没有宿主用户,纯数字 uid 会经 /host/etc/passwd 还原用户名(5 分钟缓存)。

三、两个最实用的接口:负载诊断与一键优化

GET /api/cpu/advise —— 配置顶不顶得住,一句话结论。公式很简单:单核负载 = 1分钟load ÷ 逻辑核数,分四档给结论,并点名最吃 CPU 的进程、给出扩容 / 杀进程 / 属正常的建议:

@app.route("/api/cpu/advise")
def api_cpu_advise():
    load = list(os.getloadavg())       # 1/5/15 分钟
    per_core = load[0] / logical       # 单核负载
    # 四档:<0.7 轻松 / <1.0 饱和 / <2.0 吃力 / 更高 过载

POST /api/sys/optimize —— 一键优化。依次做三件事:sync 数据落盘 → 写 /host/proc/sys/vm/drop_caches 回收 pagecache → 写 /host/proc/sys/vm/compact_memory 整理碎片。内存面板另有 POST /api/mem/drop 单独回收。零侵入:只调内核已有接口,不改系统文件。

GET /api/sensors —— 温度风扇。优先读 /host/sys/class/hwmon,回退 /sys/class/hwmon,temp*_input ÷ 1000 得摄氏度,fan*_input 即 RPM,label 优先用 temp*_label。超温阈值前端变色预警,风扇节点不存在(如纯 NAS 小主机无风扇上报)的机器返回空 + 友好提示,不阻塞主流程。

四、API 一览

方法 路径 作用
GET /api/cpu CPU 信息 + 每核占用 + 进程排行 + load
GET /api/mem 内存占用 + RSS/USS/SWAP 进程明细 + 槽位硬件
GET /api/disk 挂载用量 + SMART/温度/通电时间 + 阵列 + IO 排行
GET /api/net 网卡速率 + TCP/监听明细 + 硬件(驱动/MTU/双工)
GET /api/sensors 11 路温度 + 风扇 RPM
GET /api/cpu/advise 负载诊断:四档结论 + 头号进程 + 建议
POST /api/sys/optimize 一键优化:落盘 + 回收缓存 + 内存整理
POST /api/mem/drop 一键内存回收
POST /api/kill 结束异常进程(限宿主 PID)
GET /api/health 健康检查

五、部署:一条命令

docker run -d --name fnos-resmon -p 7031:7031 --privileged \
  -v /proc:/host/proc:rw -v /sys:/host/sys:ro \
  jpccr.ccs.tencentyun.com/jinghui/fnos-resmon:latest

浏览器打开 http://NAS-IP:7031 即可。前端是单个静态页(改前端必须重建镜像),后端一个 app.py 约 1100 行,无数据库,开箱即用。

六、实测截图(v1.3.4 当前版本)

CPU 面板:63°C 红色预警、风扇 1200 转 RPM、负载诊断四档结论、一键优化按钮:

CPU面板

内存面板:RSS/USS/SWAP 三列细分 + 🧹 一键回收大按钮:

内存面板

磁盘面板:3 盘 SMART 全 PASSED + 温度通电时间:

磁盘面板

网络面板:qbittorrent 482 连接一眼揪出吃带宽进程:

网络面板

七、总结

飞牛自带监控解决"有没有",fnos-resmon 解决"是谁、多少度、顶不顶得住、能不能一键收拾"。核心就三招:psutil 重定向宿主 proc、nsenter 进宿主 netns 读网络、后台线程 2 秒缓存进程快照,剩下的都是把数据摆整齐。如果你也在飞牛上跑了很多 Docker 服务,装一个看看头号吃 CPU 的到底是谁 —— 八成有惊喜。

镜像:jpccr.ccs.tencentyun.com/jinghui/fnos-resmon:latest | 开源:https://gitea.88531.cn/jinghui/fnos-resmon | 端口 7031,欢迎提 issue。