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

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
简介: 本文介绍了如何在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 前端开发 API
探索后端技术:Node.js的优势和实际应用
【10月更文挑战第6天】 在当今数字化时代,后端开发是任何成功软件应用的关键组成部分。本文将深入探讨一种流行的后端技术——Node.js,通过分析其核心优势和实际应用案例,揭示其在现代软件开发中的重要性和潜力。
49 2
|
5天前
|
存储 弹性计算 算法
前端大模型应用笔记(四):如何在资源受限例如1核和1G内存的端侧或ECS上运行一个合适的向量存储库及如何优化
本文探讨了在资源受限的嵌入式设备(如1核处理器和1GB内存)上实现高效向量存储和检索的方法,旨在支持端侧大模型应用。文章分析了Annoy、HNSWLib、NMSLib、FLANN、VP-Trees和Lshbox等向量存储库的特点与适用场景,推荐Annoy作为多数情况下的首选方案,并提出了数据预处理、索引优化、查询优化等策略以提升性能。通过这些方法,即使在资源受限的环境中也能实现高效的向量检索。
|
6天前
|
编解码 Android开发 UED
构建高效Android应用:从内存优化到用户体验
【10月更文挑战第11天】本文探讨了如何通过内存优化和用户体验改进来构建高效的Android应用。介绍了使用弱引用来减少内存占用、懒加载资源以降低启动时内存消耗、利用Kotlin协程进行异步处理以保持UI流畅,以及采用响应式设计适配不同屏幕尺寸等具体技术手段。
22 2
|
15天前
|
JavaScript NoSQL 前端开发
使用 Node.js 和 MongoDB 构建实时聊天应用
【10月更文挑战第2天】使用 Node.js 和 MongoDB 构建实时聊天应用
|
Java Shell JavaScript
|
1月前
|
JavaScript
NodeJs的安装
文章介绍了Node.js的安装步骤和如何创建第一个Node.js应用。包括从官网下载安装包、安装过程、验证安装是否成功,以及使用Node.js监听端口构建简单服务器的示例代码。
NodeJs的安装
|
15天前
|
JavaScript 开发工具 git
已安装nodejs但是安装hexo报错
已安装nodejs但是安装hexo报错
19 2
|
29天前
|
存储 JavaScript 前端开发
Node 版本控制工具 NVM 的安装和使用(Windows)
本文介绍了NVM(Node Version Manager)的Windows版本——NVM for Windows的安装和使用方法,包括如何安装Node.js的特定版本、列出已安装版本、切换使用不同版本的Node.js,以及其他常用命令,以实现在Windows系统上对Node.js版本的便捷管理。
Node 版本控制工具 NVM 的安装和使用(Windows)
|
9天前
|
Web App开发 JavaScript 前端开发
JavaWeb 22.Node.js_简介和安装
JavaWeb 22.Node.js_简介和安装
|
1月前
|
SQL JavaScript 数据库
sqlite在Windows环境下安装、使用、node.js连接
sqlite在Windows环境下安装、使用、node.js连接