当前位置: 首页 > news >正文

容器内的Nodejs应用如何获取宿主机的基础信息-系统、内存、cpu、启动时间,以及一个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;}
}

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;}
}

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;}
}

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;}
}

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,};}
}

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

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

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

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

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

环境信息:

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具有一致性

3. 综合示例

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

docker run --name voi-nodejs-app --restart unless-stopped 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

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)

4. 注意事项

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

总结

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

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

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 【计网】从零开始使用TCP进行socket编程 ---服务端业务模拟Xshell
  • 变脸大师:基于OpenCV与Dlib的人脸换脸技术实现
  • 掌握Spring Boot数据库集成:用JPA和Hibernate构建高效数据交互与版本控制
  • 【学习笔记】数据结构(六 ②)
  • 如何切换淘宝最新镜像源npm
  • 数字化转型中的企业蓝图构建:基于业务能力建模的全面解读与战略实施指南
  • 基于C语言+SQL Server2008实现(控制台)图书管理系统
  • 编程技巧:SQL 处理超大查询
  • 【machine learning-十-梯度下降-学习率】
  • node.js+Koa框架+MySQL实现注册登录
  • “科学突破奖”获得者连续两篇Nature,成功绘制人类主要激酶底物特异性图谱
  • 字符串函数的使用与模拟(2)——C语言内存函数
  • Leetcode 165. 比较版本号(Medium)
  • 【C++】——多态详解
  • 新电脑工作流搭建记录-前端篇
  • Docker下部署自己的LNMP工作环境
  • HTTP那些事
  • npx命令介绍
  • Python进阶细节
  • springboot_database项目介绍
  • vue的全局变量和全局拦截请求器
  • vue和cordova项目整合打包,并实现vue调用android的相机的demo
  • Vue全家桶实现一个Web App
  • 分享几个不错的工具
  • 高性能JavaScript阅读简记(三)
  • 个人博客开发系列:评论功能之GitHub账号OAuth授权
  • 观察者模式实现非直接耦合
  • 配置 PM2 实现代码自动发布
  • 如何用Ubuntu和Xen来设置Kubernetes?
  • 微信开放平台全网发布【失败】的几点排查方法
  • No resource identifier found for attribute,RxJava之zip操作符
  • 积累各种好的链接
  • ​ 轻量应用服务器:亚马逊云科技打造全球领先的云计算解决方案
  • ​520就是要宠粉,你的心头书我买单
  • ​VRRP 虚拟路由冗余协议(华为)
  • ​什么是bug?bug的源头在哪里?
  • ‌JavaScript 数据类型转换
  • # 利刃出鞘_Tomcat 核心原理解析(七)
  • #define MODIFY_REG(REG, CLEARMASK, SETMASK)
  • (06)Hive——正则表达式
  • (19)夹钳(用于送货)
  • (BFS)hdoj2377-Bus Pass
  • (pojstep1.1.1)poj 1298(直叙式模拟)
  • (Redis使用系列) Springboot 实现Redis 同数据源动态切换db 八
  • (顶刊)一个基于分类代理模型的超多目标优化算法
  • (二)基于wpr_simulation 的Ros机器人运动控制,gazebo仿真
  • (附源码)计算机毕业设计SSM疫情居家隔离服务系统
  • (一)SvelteKit教程:hello world
  • (原創) 未来三学期想要修的课 (日記)
  • (转)http协议
  • (转)Linux下编译安装log4cxx
  • (转)大型网站架构演变和知识体系
  • ******之网络***——物理***
  • .NET “底层”异步编程模式——异步编程模型(Asynchronous Programming Model,APM)...
  • .NET CF命令行调试器MDbg入门(四) Attaching to Processes