fix: remplacer nsenter par lecture directe /proc pour les métriques monitoring

This commit is contained in:
manus-admin 2026-06-01 01:14:33 +02:00
parent 3c3708e1cb
commit c534a87e4c
1 changed files with 50 additions and 115 deletions

View File

@ -1,14 +1,10 @@
const Docker = require('dockerode');
const { exec } = require('child_process');
const fs = require('fs');
const path = require('path');
const config = require('./config');
const docker = new Docker({ socketPath: '/var/run/docker.sock' });
// Chemin du fichier de métriques écrit par le script host
const HOST_METRICS_FILE = '/opt/manus-deploy/logs/host_metrics.json';
/**
* Récupérer les informations d'un conteneur Docker
*/
@ -42,6 +38,7 @@ async function getContainerLogs(containerName, tail = 100) {
tail: tail,
timestamps: true,
});
// Parse buffer to string
return logs.toString('utf8');
} catch (err) {
return `Erreur lors de la récupération des logs: ${err.message}`;
@ -83,8 +80,8 @@ function redeployApp(appConfig) {
logLines.push(`[${new Date().toISOString()}] Commande: ${cmd}`);
const process = exec(cmd, {
maxBuffer: 1024 * 1024 * 10,
timeout: 300000,
maxBuffer: 1024 * 1024 * 10, // 10MB
timeout: 300000, // 5 minutes
});
process.stdout.on('data', (data) => {
@ -137,6 +134,8 @@ function gitPull(appConfig) {
});
}
/**
* Démarrer un conteneur Docker
*/
@ -177,134 +176,72 @@ async function restartContainer(containerName) {
}
/**
* Récupérer les métriques système du serveur (CPU, RAM, disque, réseau, uptime)
*
* Architecture LXC + Docker : le conteneur est isolé dans ses namespaces réseau/PID,
* ce qui empêche la lecture directe de /proc pour certaines métriques.
*
* Solution : lire le fichier /opt/manus-deploy/logs/host_metrics.json
* écrit en continu par le script metrics_collector.sh tournant sur le VPS hôte.
* Ce fichier est accessible via le volume monté dans le conteneur.
*
* Fallback : calcul direct depuis /proc (moins précis pour RAM/uptime/réseau).
* Récupérer les métriques système du serveur (CPU, RAM, disque, réseau)
*/
function getServerMetrics() {
return new Promise((resolve) => {
// ── Tentative 1 : lire le fichier de métriques du host ─────────────────
const { execSync } = require("child_process");
const fs = require("fs");
try {
if (fs.existsSync(HOST_METRICS_FILE)) {
const stat = fs.statSync(HOST_METRICS_FILE);
const ageMs = Date.now() - stat.mtimeMs;
// Fichier valide si moins de 60 secondes
if (ageMs < 60000) {
const raw = fs.readFileSync(HOST_METRICS_FILE, 'utf8');
const metrics = JSON.parse(raw);
// Rafraîchir le timestamp
metrics.timestamp = new Date().toISOString();
return resolve(metrics);
}
}
} catch (e) {
// Fichier absent ou corrompu → fallback
}
// Lecture directe de /proc (monté depuis l'hôte via le namespace PID du conteneur)
// Pas besoin de nsenter : /proc dans le conteneur reflète l'hôte
// ── Fallback : calcul depuis /proc (valeurs approchées) ────────────────
const { execSync } = require('child_process');
try {
// CPU via /host/proc/stat (namespace CPU partagé avec le VPS)
// CPU : delta /proc/stat sur 300ms
let cpuUsage = 0;
try {
const procStat = fs.existsSync('/host/proc/stat') ? '/host/proc/stat' : '/proc/stat';
const s1 = execSync(`grep '^cpu ' ${procStat}`).toString().trim().split(/\s+/).slice(1).map(Number);
execSync('sleep 0.3');
const s2 = execSync(`grep '^cpu ' ${procStat}`).toString().trim().split(/\s+/).slice(1).map(Number);
const total1 = s1.reduce((a, b) => a + b, 0);
const total2 = s2.reduce((a, b) => a + b, 0);
const dTotal = total2 - total1;
const dIdle = s2[3] - s1[3];
const parseCpuLine = (line) => line.trim().split(/\s+/).slice(1).map(Number);
const s1 = parseCpuLine(fs.readFileSync('/proc/stat', 'utf8').split('\n').find(l => l.startsWith('cpu ')));
execSync("sleep 0.3");
const s2 = parseCpuLine(fs.readFileSync('/proc/stat', 'utf8').split('\n').find(l => l.startsWith('cpu ')));
const idle1 = s1[3], total1 = s1.reduce((a,b)=>a+b,0);
const idle2 = s2[3], total2 = s2.reduce((a,b)=>a+b,0);
const dIdle = idle2 - idle1, dTotal = total2 - total1;
cpuUsage = dTotal > 0 ? Math.round((1 - dIdle / dTotal) * 100) : 0;
} catch (e) {}
} catch(e) {}
// RAM depuis cgroup v2 du VPS (accessible via /sys/fs/cgroup monté ou /host/cgroup)
let memTotal = 0, memUsed = 0, memFree = 0, memAvailable = 0;
// RAM : lecture de /proc/meminfo (valeurs réelles de l'hôte)
let memTotal = 8589934592; // 8 Go par défaut
let memFree = 0, memAvailable = 0, memUsed = 0;
try {
// Essayer /host/cgroup d'abord, puis /sys/fs/cgroup
const cgroupBase = fs.existsSync('/host/cgroup/memory.max') ? '/host/cgroup' : '/sys/fs/cgroup';
const memMaxRaw = fs.readFileSync(`${cgroupBase}/memory.max`, 'utf8').trim();
const memCurrRaw = fs.readFileSync(`${cgroupBase}/memory.current`, 'utf8').trim();
const meminfo = fs.readFileSync('/proc/meminfo', 'utf8');
const getVal = (key) => {
const m = meminfo.match(new RegExp(`^${key}:\\s+(\\d+)`, 'm'));
return m ? parseInt(m[1]) * 1024 : 0;
};
memTotal = getVal('MemTotal');
memFree = getVal('MemFree');
memAvailable = getVal('MemAvailable');
memUsed = memTotal - memAvailable;
} catch(e) {}
if (memMaxRaw !== 'max') {
memTotal = parseInt(memMaxRaw);
memUsed = parseInt(memCurrRaw);
// Tenter de lire la RAM inactive (cache) depuis memory.stat
try {
const memStat = fs.readFileSync(`${cgroupBase}/memory.stat`, 'utf8');
const inactiveFile = parseInt((memStat.match(/^inactive_file\s+(\d+)/m) || [0, 0])[1]);
const activeFile = parseInt((memStat.match(/^active_file\s+(\d+)/m) || [0, 0])[1]);
const cache = inactiveFile + activeFile;
memAvailable = memTotal - memUsed + cache;
memFree = memAvailable;
} catch (e) {
memAvailable = memTotal - memUsed;
memFree = memAvailable;
}
}
} catch (e) {
// Fallback /proc/meminfo (valeurs du namespace du conteneur)
try {
const meminfo = fs.readFileSync('/proc/meminfo', 'utf8');
const getVal = (key) => {
const m = meminfo.match(new RegExp(`^${key}:\\s+(\\d+)`, 'm'));
return m ? parseInt(m[1]) * 1024 : 0;
};
memTotal = getVal('MemTotal');
memFree = getVal('MemFree');
memAvailable = getVal('MemAvailable') || memFree;
memUsed = memTotal - memAvailable;
} catch (e2) {}
}
// Disque : df sur /
// Disque : df sur la racine du conteneur (= hôte car pas de volume overlay)
let diskTotal = 0, diskUsed = 0, diskFree = 0;
try {
const diskRaw = execSync('df -B1 / | tail -1').toString().trim().split(/\s+/);
diskTotal = parseInt(diskRaw[1]);
diskUsed = parseInt(diskRaw[2]);
diskFree = parseInt(diskRaw[3]);
} catch (e) {}
} catch(e) {}
// Uptime depuis /proc/uptime du VPS (namespace partagé avec le VPS)
let uptimeSeconds = 0;
try {
const uptimeFile = fs.existsSync('/host/proc/uptime') ? '/host/proc/uptime' : '/proc/uptime';
uptimeSeconds = parseFloat(fs.readFileSync(uptimeFile, 'utf8').trim().split(' ')[0]) || 0;
} catch (e) {}
// Uptime et load
const uptimeRaw = fs.readFileSync('/proc/uptime', 'utf8').trim().split(' ');
const uptimeSeconds = parseFloat(uptimeRaw[0]);
const loadRaw = fs.readFileSync('/proc/loadavg', 'utf8').trim().split(' ');
const load1 = parseFloat(loadRaw[0]);
const load5 = parseFloat(loadRaw[1]);
const load15 = parseFloat(loadRaw[2]);
// Load average
let load1 = 0, load5 = 0, load15 = 0;
try {
const loadFile = fs.existsSync('/host/proc/loadavg') ? '/host/proc/loadavg' : '/proc/loadavg';
const loadRaw = fs.readFileSync(loadFile, 'utf8').trim().split(' ');
load1 = parseFloat(loadRaw[0]) || 0;
load5 = parseFloat(loadRaw[1]) || 0;
load15 = parseFloat(loadRaw[2]) || 0;
} catch (e) {}
// Réseau : /proc/net/dev du VPS (namespace réseau partagé avec le VPS, pas le conteneur)
// Réseau
let netRx = 0, netTx = 0;
try {
const netFile = fs.existsSync('/host/proc/net/dev') ? '/host/proc/net/dev' : '/proc/net/dev';
const netRaw = fs.readFileSync(netFile, 'utf8');
const line = netRaw.split('\n').find((l) => /^\s*(eth0|venet0|ens\d+|enp\d+)/.test(l));
if (line) {
const parts = line.trim().split(/\s+/);
const netDev = fs.readFileSync('/proc/net/dev', 'utf8');
const netLine = netDev.split('\n').find(l => /eth0|ens|enp/.test(l));
if (netLine) {
const parts = netLine.trim().split(/\s+/);
netRx = parseInt(parts[1]) || 0;
netTx = parseInt(parts[9]) || 0;
}
} catch (e) {}
const memPct = memTotal > 0 ? Math.round((memUsed / memTotal) * 100) : 0;
const diskPct = diskTotal > 0 ? Math.round((diskUsed / diskTotal) * 100) : 0;
} catch(e) {}
resolve({
cpu: { usage: cpuUsage },
@ -313,26 +250,24 @@ function getServerMetrics() {
used: memUsed,
free: memFree,
available: memAvailable,
usagePercent: memPct,
usagePercent: Math.round((memUsed / memTotal) * 100)
},
disk: {
total: diskTotal,
used: diskUsed,
free: diskFree,
usagePercent: diskPct,
usagePercent: Math.round((diskUsed / diskTotal) * 100)
},
uptime: uptimeSeconds,
load: { load1, load5, load15 },
network: { rx: netRx, tx: netTx },
timestamp: new Date().toISOString(),
source: 'fallback',
timestamp: new Date().toISOString()
});
} catch (err) {
resolve({ error: err.message });
}
});
}
module.exports = {
docker,
getContainerInfo,