fix: remplacer nsenter par lecture directe /proc pour les métriques monitoring
This commit is contained in:
parent
3c3708e1cb
commit
c534a87e4c
|
|
@ -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,80 +176,32 @@ 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;
|
||||
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();
|
||||
|
||||
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)
|
||||
// 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 {
|
||||
const meminfo = fs.readFileSync('/proc/meminfo', 'utf8');
|
||||
const getVal = (key) => {
|
||||
|
|
@ -259,52 +210,38 @@ function getServerMetrics() {
|
|||
};
|
||||
memTotal = getVal('MemTotal');
|
||||
memFree = getVal('MemFree');
|
||||
memAvailable = getVal('MemAvailable') || memFree;
|
||||
memAvailable = getVal('MemAvailable');
|
||||
memUsed = memTotal - memAvailable;
|
||||
} catch (e2) {}
|
||||
}
|
||||
} catch(e) {}
|
||||
|
||||
// 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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue