在现代应用部署时中,Docker容器化技术被广泛应用。Node.js应用在容器中运行时,有时需要获取宿主机的基础信息,如系统信息、内存使用情况、磁盘空间和启动时间等。本文将介绍如何在Docker容器内的Node.js应用中获取这些信息,以及可能遇到的坑*。
核心思路:将宿主机的根目录挂载到容器
避坑总结:容器内执行 df -h /xxx, 如果xxx是挂载到容器的宿主机目录,那么它的输出格式可能会异常:第二行第一列直接换行,导致常规数值解析失败
前提条件
- 确保已安装Docker。
- 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 -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
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. 注意事项
- 权限问题:在某些Docker环境中,可能需要提升容器的权限(如使用
--privileged
或--cap-add=SYS_ADMIN
)才能访问某些文件。 - 安全性:暴露宿主机的详细信息可能会带来安全风险,请确保在受信任的环境中使用。
总结
通过读取特定的系统文件和执行命令,您可以获取有关操作系统、内存、磁盘和启动时间的详细信息。这些信息对于监控、性能分析和故障排查都是非常有用的。希望这篇文章能为您提供帮助!
实事求是才是解决问题的真理,不能想当然,很多看似一样的输入却在某些特殊情况下会输出不一样的结果。例如我就把df -h 的结果想象成一样了。