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

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: 本文介绍了如何在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 的结果想象成一样了。

目录
打赏
0
0
0
0
58
分享
相关文章
ACK 容器监控存储全面更新:让您的应用运行更稳定、更透明
针对本地存储和 PVC 这两种容器存储使用方式,我们对 ACK 的容器存储监控功能进行了全新升级。此次更新完善了对集群中不同存储类型的监控能力,不仅对之前已有的监控大盘进行了优化,还针对不同的云存储类型,上线了全新的监控大盘,确保用户能够更好地理解和管理容器业务应用的存储资源。
391 187
zabbix7.0.9安装-以宝塔安装形式-非docker容器安装方法-系统采用AlmaLinux9系统-最佳匹配操作系统提供稳定运行环境-安装教程完整版本-优雅草卓伊凡
zabbix7.0.9安装-以宝塔安装形式-非docker容器安装方法-系统采用AlmaLinux9系统-最佳匹配操作系统提供稳定运行环境-安装教程完整版本-优雅草卓伊凡
124 30
【01】Cocos游戏开发引擎从0开发一款游戏-cocos环境搭建以及配置-Cocos Creator软件系统下载安装-node环境-优雅草卓伊凡
【01】Cocos游戏开发引擎从0开发一款游戏-cocos环境搭建以及配置-Cocos Creator软件系统下载安装-node环境-优雅草卓伊凡
62 2
【01】Cocos游戏开发引擎从0开发一款游戏-cocos环境搭建以及配置-Cocos Creator软件系统下载安装-node环境-优雅草卓伊凡
容器化浪潮下的AI赋能:智能化运维与创新应用
近年来,容器技术以其轻量、高效、可移植的特性成为云原生时代的基石,推动应用开发和部署方式革新。随着容器化应用规模扩大,传统运维手段逐渐力不从心。AI技术的引入为容器化生态带来新活力,实现智能监控、自动化故障诊断与修复及智能资源调度,提升运维效率和可靠性。同时,AI驱动容器化创新应用,如模型训练、边缘计算和Serverless AI服务,带来更多可能性。未来,AI与容器技术的融合将更加紧密,推动更智能、高效的运维平台和丰富的创新应用场景,助力数字化转型。
ACK 容器监控存储全面更新:让您的应用运行更稳定、更透明
ACK 容器监控存储全面更新:让您的应用运行更稳定、更透明
ACK 容器监控存储全面更新:让您的应用运行更稳定、更透明
ACK 容器监控存储全面更新:让您的应用运行更稳定、更透明
ACK容器监控存储全面更新:让您的应用运行更稳定、更透明
介绍升级之后的ACK容器监控体系,包括各大盘界面展示和概要介绍。
快速定位并优化CPU 与 JVM 内存性能瓶颈
本文介绍了 Java 应用常见的 CPU & JVM 内存热点原因及优化思路。
717 166
如何快速定位并优化CPU 与 JVM 内存性能瓶颈?
如何快速定位并优化CPU 与 JVM 内存性能瓶颈?
2025年阿里云服务器配置选择全攻略:CPU、内存、带宽与系统盘详解
在2025年,阿里云服务器以高性能、灵活扩展和稳定服务助力数字化转型,提供轻量应用服务器、通用型g8i实例等多样化配置,满足个人博客至企业级业务需求。针对不同场景(如计算密集型、内存密集型),推荐相应实例类型与带宽规划,强调成本优化策略,包括包年包月节省成本、ESSD云盘选择及地域部署建议。文中还提及安全设置、监控备份的重要性,并指出未来可关注第九代实例g9i支持的新技术。整体而言,阿里云致力于帮助用户实现性能与成本的最优平衡。 以上简介共计238个字符。
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等