自己在搭服务器监控面板的时候,是不是经常遇到这种情况:SSH 登上去敲个 free -h 看到的内存占用,和面板上显示的数值总是对不上?要么高得离谱,要么低得吓人,强迫症瞬间犯了。

最近在用轻量级的 Komari 监控面板时就遇到了这个坑。它的被控端 komari-agent 默认用的内存计算逻辑,跟大家习惯的 free -h 完全是两个套路。本来以为改个配置参数就能解决,结果折腾一圈发现是个硬伤,最后祭出一套“骗过 Agent”的黑科技方案才搞定。

今天就复盘一下这个排查过程和最终方案,不仅适用于 Komari,这种思路在其他监控工具对齐数据时也能借鉴。

1. 为什么数据对不上?

首先得搞清楚两边是怎么算内存的。这不仅是数学问题,更涉及到 Linux 内核怎么上报内存数据。

通常我们看 free -h,它显示的“已用内存”公式大概是这样的:

Used = Total - MemAvailable

MemAvailable 是内核综合考虑了缓存回收后给出的“可用内存”,这是目前业界的标准,也是我们感知最准确的数值。

但是,komari-agent 并没有乖巧地使用这个标准值。经过翻代码和实测发现,它虽然内置了三种计算逻辑(htoplikegopsutilcallFree),但在上报给面板时死板地只用了 htoplike 这一种。它的计算公式非常复古:

Linux 终端执行 free -h 命令的输出示例

图:终端执行 free -h 命令显示的内存使用情况

htoplike = Total - Free - Buffers - (Cached + SReclaimable - Shmem)

简单说,它对缓存的处理方式很死板。这就导致在系统运行了一段时间,缓存占用量变大后,Agent 算出来的“已用”和 free -h 算出来的“已用”差距会越来越大。

2. 常规路子走不通

发现算法不一样,第一反应肯定是:找个参数切一下。

我翻遍了文档和源码,发现 Agent 确实带了几个开关,比如 --memory-include-cache--memory-exclude-bcf。本来以为这就是切换算法的金钥匙,结果实测只是微调默认公式,根本不能切换到我们想要的 free -h 模式。

还有一个路子是改安装脚本 install.sh,但因为计算逻辑是写死在 Go 语言编译好的二进制文件里的,不重新编译源码,脚本层面根本动不了它。

Systemd 私有挂载命名空间隔离原理示意图

图:利用 Systemd BindReadOnlyPaths 实现挂载命名空间隔离

3. 终极方案:骗它的眼睛

既然改不了 Agent 的脑子(算法),那就改它看到的世界(数据)。

Agent 算内存时,本质上是去读 /proc/meminfo 这个系统文件里的数值。如果能在这个环节动动手脚,给它一份“伪造”的数值,让它按自己的公式算出来的结果,正好等于 free -h 的结果,问题不就解决了吗?

这个思路的核心难点在于:不能真的修改系统的 /proc/meminfo,否则全系统都要乱套。我们只能想办法让 Agent 这个进程看到的是假文件,而系统其他进程(包括我们自己敲的 free -h)看到的是真文件。

4. 实现原理:Systemd 私有挂载

这里就用到了 Linux 容器技术里常用的 Mount Namespace(挂载命名空间)隔离

我们可以利用 Systemd 的 BindReadOnlyPaths 特性,给 komari-agent 单独创建一个私有的挂载空间。具体操作如下:

  1. 计算假数据:写一个后台脚本,每秒读取真实系统的 /proc/meminfo,拿到真实的 TotalMemAvailable
  2. 反推逻辑:我们知道 Agent 的公式是 htoplike,我们也知道我们想要的结果是 free -h 的结果。通过数学反推,我们可以算出一个“假的 MemFree 和 MemAvailable”。
  3. 定向投喂:把算出来的假数据写入一个临时文件,然后通过 Systemd 的配置,把这个临时文件绑定挂载到 Agent 私有命名空间里的 /proc/meminfo

这样一来,Agent 以为自己读的是系统标准文件,其实读的是我们精心调教过的数据。而你在 SSH 里敲 free -h,读的依然是真实的系统文件,互不干扰。

5. 一键部署(懒人版)

这个原理听起来有点绕,但操作上我已经封装成了一键脚本。它会自动检测你的 systemd 版本、Python3 环境,并自动识别 komari-agent 的服务名。

执行以下任意一条命令即可:

curl -fsSL https://gist.githubusercontent.com/cmooou/d3916c2b9b6b3142dd2b1f5d00294c6d/raw/8f161ff9fe30436bab34d9cf1b2e9af33975d13f/komari-fix-memory.sh | bash

或者

wget -qO- https://gist.githubusercontent.com/cmooou/d3916c2b9b6b3142dd2b1f5d00294c6d/raw/8f161ff9fe30436bab34d9cf1b2e9af33975d13f/komari-fix-memory.sh | bash

脚本跑完重启服务后,你会发现面板上的内存图形终于和终端里对齐了,误差通常控制在个位数 MB 以内(采样时间差导致)。

6. 注意事项与回滚

n 这套方案虽然骚操作,但也有一些限制:

  • Systemd 版本:需要 systemd ≥ 233,因为依赖 BindReadOnlyPaths 特性。现在的主流发行版基本都满足。
  • Python 依赖:脚本需要 Python3 来跑计算逻辑,脚本会尝试用 apt/yum/apk 自动安装,不用操心。
  • 服务名:如果你的服务名不是标准的 komari-agent(比如自己改过),脚本可能识别不到,需要手动去改一下配置。

如果想回滚,非常简单,删掉配置重载一下服务就行:

rm -f /etc/systemd/system/komari-agent.service.d/override.conf && systemctl daemon-reload && systemctl restart komari-agent

通过这次折腾,也算是重新复习了一下 Linux 的 Namespace 隔离机制。有时候解决兼容性问题,不一定非要硬刚代码,换个思路,“欺骗”一下进程也是一种优雅的解决方案。

标签: none

AI Skills Smart Station on Nick Launches

评论已关闭