容器内的Nodejs应用如何获取宿主机的基础信息-系统、内存、cpu、启动时间,以及一个df -h的坑

简介: 本文介绍了如何在Docker容器内的Node.js应用中获取宿主机的基础信息,包括系统信息、内存使用情况、磁盘空间和启动时间等。核心思路是将宿主机的根目录挂载到容器,但需注意权限和安全问题。文章还提到了使用`df -P`替代`df -h`以获得一致性输出,避免解析错误。

 在现代应用部署时中,Docker容器化技术被广泛应用。Node.js应用在容器中运行时,有时需要获取宿主机的基础信息,如系统信息、内存使用情况、磁盘空间和启动时间等。本文将介绍如何在Docker容器内的Node.js应用中获取这些信息,以及可能遇到的坑*。

核心思路:将宿主机的根目录挂载到容器

避坑总结:容器内执行 df -h /xxx, 如果xxx是挂载到容器的宿主机目录,那么它的输出格式可能会异常:第二行第一列直接换行,导致常规数值解析失败

前提条件

  1. 确保已安装Docker。
  2. Node.js已在Docker容器中运行。

1. 获取宿主机的基础信息

1.1 系统信息

要获取宿主机的系统信息,可以通过读取 /etc/os-release +文件来实现。这是Linux系统提供的标准文件,包含了操作系统的名称和版本等信息。

async function getHostOSInfo(hostRoot: string) {
  try {
    // 尝试读取 /etc/os-release 文件
    const osReleasePath = path.join(hostRoot, "/etc/os-release");
    const osReleaseContent: string = await fs.readFile(osReleasePath, "utf8");
    const osInfo = {} as any;
    osReleaseContent.split("\n").forEach((line) => {
      const [key, value] = line.split("=");
      if (key && value) {
        osInfo[key] = value.replace(/"/g, "");
      }
    });
    // 尝试读取 /etc/lsb-release 文件(用于某些 Ubuntu 版本)
    let lsbReleaseContent = "";
    try {
      lsbReleaseContent = await fs.readFile(
        path.join(hostRoot, "/etc/lsb-release"),
        "utf8"
      );
      lsbReleaseContent.split("\n").forEach((line) => {
        const [key, value] = line.split("=");
        if (key && value) {
          osInfo[key] = value.replace(/"/g, "");
        }
      });
    } catch (error) {
      // 如果文件不存在,就忽略这个错误
    }
    // 尝试读取内核版本
    const kernelVersion = await fs.readFile(
      path.join(hostRoot, "proc/version"),
      "utf8"
    );
    return {
      arch: osInfo.NAME || osInfo.DISTRIB_ID || "Unknown",
      osVersion: osInfo.VERSION_ID || osInfo.DISTRIB_RELEASE || "Unknown",
      osDescription:
        osInfo.PRETTY_NAME ||
        `${osInfo.DISTRIB_ID} ${osInfo.DISTRIB_RELEASE}` ||
        "Unknown",
      kernelVersion: kernelVersion.split(" ")[2] || "Unknown",
      firewallStatus: getFirewallStatus(hostRoot),
    };
  } catch (error) {
    console.error("Error reading host OS information:", error);
    return null;
  }
}

image.gif

1.2 内存信息

内存使用情况可以通过读取 /proc/meminfo 文件来获取。该文件提供了系统内存的详细信息。

async function getHostMemInfo(hostRoot: string) {
  try {
    const filePath = path.join(hostRoot, "/proc/meminfo");
    const meminfo = await fs.readFile(filePath, "utf8");
    const memTotal = parseInt(meminfo.match(/MemTotal:\s+(\d+)/)[1]) * 1024;
    const memFree = parseInt(meminfo.match(/MemFree:\s+(\d+)/)[1]) * 1024;
    const memUsed = memTotal - memFree;
    return {
      total: memTotal,
      free: memFree,
      used: memUsed,
    };
  } catch (error) {
    console.error("Error reading memory information:", error);
    return null;
  }
}

image.gif

1.3 CPU信息

可以通过读取 /proc/stat文件来获取cpu信息,隔段时间读取两次就可以计算出使用率。

async function readHostCpuInfo(hostRoot: string) {
  const filePath = path.join(hostRoot, "/proc/cpuinfo");
  const cpuinfo = await fs.readFile(filePath, "utf8");
  const cpus = cpuinfo.match(/processor/g) || [];
  return cpus;
}
async function readHostCpuUsage(hostRoot: string) {
  const filePath = path.join(hostRoot, "/proc/stat");
  const stat = await fs.readFile(filePath, "utf8");
  const cpuLine = stat.split("\n")[0];
  const cpuTimes = cpuLine.split(/\s+/).slice(1).map(Number);
  const totalTime = cpuTimes.reduce((a: number, b: number) => a + b, 0);
  const idleTime = cpuTimes[3];
  return { totalTime, idleTime };
}
async function getHostCpuInfo(hostRoot: string) {
  try {
    const cpus = await readHostCpuInfo(hostRoot);
    const usage1 = await readHostCpuUsage(hostRoot);
    // 等待一小段时间再次读取,以计算使用率
    await new Promise((resolve) => setTimeout(resolve, 1000));
    const usage2 = await readHostCpuUsage(hostRoot);
    const totalDiff = usage2.totalTime - usage1.totalTime;
    const idleDiff = usage2.idleTime - usage1.idleTime;
    const usagePercent = (1 - idleDiff / totalDiff) * 100;
    return {
      cpus,
      cpuUsage: usagePercent.toFixed(2),
    };
  } catch (error) {
    console.error("Error reading CPU information:", error);
    return null;
  }
}

image.gif

1.4 启动时间

启动时间可以通过读取 /proc/stat 文件中的 btime 字段来获取,表示自Unix时间(1970年1月1日)以来的秒数。

async function getHostUptime(hostRoot: string) {
  try {
    const filePath = path.join(hostRoot, "/proc/uptime");
    const uptime = await fs.readFile(filePath, "utf8");
    const uptimeSeconds = parseFloat(uptime.split()[0]);
    return uptimeSeconds;
  } catch (error) {
    console.error("Error reading uptime information:", error);
    return null;
  }
}

image.gif

2. 获取磁盘使用

2.1. 使用df -h 失败

function getDiskUsage(hostRoot= "/") {
  try {
    const output = execSync(`df -h ${hostRoot}`).toString();
    const lines = output.trim().split("\n");
    const data = lines[1].split(/\s+/);
    const [filesystem, size, used, available, percentUsed, mounted] = data;
    return {
      total:size,
      free: available,
      used: used,
      percentUsed: parseInt(percentUsed),
    };
  } catch (error: any) {
    console.error("Error checking disk usage:", error.message);
    return {
      total: 0,
      free: 0,
      used: 0,
      percentUsed: 0,
      error: error.message,
    };
  }
}

image.gif

2.2 使用df -h 的诡异结果 - 外挂磁盘输出格式不一致

经过测试,前端拿到的磁盘信息全为空,百思不得其解,而且进入到容器,测试df -h /也是对的,这里犯了个错,我想当然因为 df -h **的返回格式一样

下图为我在磁盘里,随便找了个非/路径的磁盘信息,正常!我也是按照这个方式解析的

image.gif 编辑

但是还是不明白,为啥有问题呢?为了复原情况,我还是敲出了映射路径,结果如下:

image.gif 编辑

看到没有,多了一个空行,在第二行就显示了第一列,后面就换行了。

环境信息:

Docker version 18.09.6, build 481bc77

基础镜像 node:20.11.1-alpine

宿主机 centos 7 / ubuntu

磁盘映射 / -> /home_dev:ro

2.3 用df -P 替代以获得一致性输出

因为df -h使用比较多,我试了一些方法,发现df -P具有一致性

image.gif 编辑

3. 综合示例

3.1 将宿主机根目录以只读模式挂载到容器内

docker run --name voi-nodejs-app --restart unless-stopped -e DATABASE_URL=file:/home/nodejs/app/dev.db  -e HOST_MOUNT_POINT=/home_dev/  --network host -v /root/test/db/dev.db:/home/nodejs/app/dev.db -v /:/home_dev:ro  nodeapp:latest

image.gif

3.2 从环境变量读取宿主机根目录挂载位置获取相关信息

const hostRoot = process.env["HOST_MOUNT_POINT"]
memo = await getHostMemInfo(hostRoot)
uptime = await getHostOUptime(hostRoot)
cpu = await getHostCpuInfo(hostRoot)
os = await getHostOSInfo(hostRoot)

image.gif

4. 注意事项

  1. 权限问题:在某些Docker环境中,可能需要提升容器的权限(如使用 --privileged--cap-add=SYS_ADMIN)才能访问某些文件。
  2. 安全性:暴露宿主机的详细信息可能会带来安全风险,请确保在受信任的环境中使用。

总结

通过读取特定的系统文件和执行命令,您可以获取有关操作系统、内存、磁盘和启动时间的详细信息。这些信息对于监控、性能分析和故障排查都是非常有用的。希望这篇文章能为您提供帮助!

实事求是才是解决问题的真理,不能想当然,很多看似一样的输入却在某些特殊情况下会输出不一样的结果。例如我就把df -h 的结果想象成一样了。

相关文章
|
11月前
|
JavaScript 前端开发
如何减少Node.js应用中的全局变量?
如何减少Node.js应用中的全局变量?
662 165
|
8月前
|
存储 监控 JavaScript
基于布隆过滤器的 Node.js 算法在局域网电脑桌面监控设备快速校验中的应用研究
本文探讨了布隆过滤器在局域网电脑桌面监控中的应用,分析其高效空间利用率、快速查询性能及动态扩容优势,并设计了基于MAC地址的校验模型,提供Node.js实现代码,适用于设备准入控制与重复数据过滤场景。
301 0
|
7月前
|
运维 监控 JavaScript
基于 Node.js 图结构的局域网设备拓扑分析算法在局域网内监控软件中的应用研究
本文探讨图结构在局域网监控系统中的应用,通过Node.js实现设备拓扑建模、路径分析与故障定位,提升网络可视化、可追溯性与运维效率,结合模拟实验验证其高效性与准确性。
412 3
|
9月前
|
SQL 缓存 安全
深度理解 Java 内存模型:从并发基石到实践应用
本文深入解析 Java 内存模型(JMM),涵盖其在并发编程中的核心作用与实践应用。内容包括 JMM 解决的可见性、原子性和有序性问题,线程与内存的交互机制,volatile、synchronized 和 happens-before 等关键机制的使用,以及在单例模式、线程通信等场景中的实战案例。同时,还介绍了常见并发 Bug 的排查与解决方案,帮助开发者写出高效、线程安全的 Java 程序。
494 0
|
11月前
|
监控 算法 JavaScript
公司局域网管理视域下 Node.js 图算法的深度应用研究:拓扑结构建模与流量优化策略探析
本文探讨了图论算法在公司局域网管理中的应用,针对设备互联复杂、流量调度低效及安全监控困难等问题,提出基于图论的解决方案。通过节点与边建模局域网拓扑结构,利用DFS/BFS实现设备快速发现,Dijkstra算法优化流量路径,社区检测算法识别安全风险。结合WorkWin软件实例,展示了算法在设备管理、流量调度与安全监控中的价值,为智能化局域网管理提供了理论与实践指导。
291 3
|
存储 缓存 Linux
Linux系统中如何查看CPU信息
本文介绍了查看CPU核心信息的方法,包括使用`lscpu`命令和读取`/proc/cpuinfo`文件。`lscpu`能快速提供逻辑CPU数量、物理核心数、插槽数等基本信息;而`/proc/cpuinfo`则包含更详细的配置数据,如核心ID和处理器编号。此外,还介绍了如何通过`lscpu`和`dmidecode`命令获取CPU型号、制造商及序列号,并解释了CPU频率与缓存大小的相关信息。最后,详细解析了`lscpu`命令输出的各项参数含义,帮助用户更好地理解CPU的具体配置。
1569 8
|
缓存 安全 Linux
Linux系统查看操作系统版本信息、CPU信息、模块信息
在Linux系统中,常用命令可帮助用户查看操作系统版本、CPU信息和模块信息
3289 23
|
开发框架 .NET PHP
网站应用项目如何选择阿里云服务器实例规格+内存+CPU+带宽+操作系统等配置
对于使用阿里云服务器的搭建网站的用户来说,面对众多可选的实例规格和配置选项,我们应该如何做出最佳选择,以最大化业务效益并控制成本,成为大家比较关注的问题,如果实例、内存、CPU、带宽等配置选择不合适,可能会影响到自己业务在云服务器上的计算性能及后期运营状况,本文将详细解析企业在搭建网站应用项目时选购阿里云服务器应考虑的一些因素,以供参考。
|
7月前
|
缓存 人工智能 算法
不同业务怎么选服务器?CPU / 内存 / 带宽配置表
本文详解了服务器三大核心配置——CPU、内存、带宽,帮助读者快速理解服务器性能原理。结合不同业务场景,如个人博客、电商、数据库、直播等,提供配置选择建议,并强调合理搭配的重要性,避免资源浪费或瓶颈限制。内容实用,适合初学者和业务选型参考。
1095 0
下一篇
开通oss服务