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 Docker = require('dockerode');
|
||||||
const { exec } = require('child_process');
|
const { exec } = require('child_process');
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const config = require('./config');
|
const config = require('./config');
|
||||||
|
|
||||||
const docker = new Docker({ socketPath: '/var/run/docker.sock' });
|
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
|
* Récupérer les informations d'un conteneur Docker
|
||||||
*/
|
*/
|
||||||
|
|
@ -42,6 +38,7 @@ async function getContainerLogs(containerName, tail = 100) {
|
||||||
tail: tail,
|
tail: tail,
|
||||||
timestamps: true,
|
timestamps: true,
|
||||||
});
|
});
|
||||||
|
// Parse buffer to string
|
||||||
return logs.toString('utf8');
|
return logs.toString('utf8');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return `Erreur lors de la récupération des logs: ${err.message}`;
|
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}`);
|
logLines.push(`[${new Date().toISOString()}] Commande: ${cmd}`);
|
||||||
|
|
||||||
const process = exec(cmd, {
|
const process = exec(cmd, {
|
||||||
maxBuffer: 1024 * 1024 * 10,
|
maxBuffer: 1024 * 1024 * 10, // 10MB
|
||||||
timeout: 300000,
|
timeout: 300000, // 5 minutes
|
||||||
});
|
});
|
||||||
|
|
||||||
process.stdout.on('data', (data) => {
|
process.stdout.on('data', (data) => {
|
||||||
|
|
@ -137,6 +134,8 @@ function gitPull(appConfig) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Démarrer un conteneur Docker
|
* 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)
|
* Récupérer les métriques système du serveur (CPU, RAM, disque, réseau)
|
||||||
*
|
|
||||||
* 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).
|
|
||||||
*/
|
*/
|
||||||
function getServerMetrics() {
|
function getServerMetrics() {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
// ── Tentative 1 : lire le fichier de métriques du host ─────────────────
|
const { execSync } = require("child_process");
|
||||||
|
const fs = require("fs");
|
||||||
try {
|
try {
|
||||||
if (fs.existsSync(HOST_METRICS_FILE)) {
|
// Lecture directe de /proc (monté depuis l'hôte via le namespace PID du conteneur)
|
||||||
const stat = fs.statSync(HOST_METRICS_FILE);
|
// Pas besoin de nsenter : /proc dans le conteneur reflète l'hôte
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Fallback : calcul depuis /proc (valeurs approchées) ────────────────
|
// CPU : delta /proc/stat sur 300ms
|
||||||
const { execSync } = require('child_process');
|
|
||||||
try {
|
|
||||||
// CPU via /host/proc/stat (namespace CPU partagé avec le VPS)
|
|
||||||
let cpuUsage = 0;
|
let cpuUsage = 0;
|
||||||
try {
|
try {
|
||||||
const procStat = fs.existsSync('/host/proc/stat') ? '/host/proc/stat' : '/proc/stat';
|
const parseCpuLine = (line) => line.trim().split(/\s+/).slice(1).map(Number);
|
||||||
const s1 = execSync(`grep '^cpu ' ${procStat}`).toString().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');
|
execSync("sleep 0.3");
|
||||||
const s2 = execSync(`grep '^cpu ' ${procStat}`).toString().trim().split(/\s+/).slice(1).map(Number);
|
const s2 = parseCpuLine(fs.readFileSync('/proc/stat', 'utf8').split('\n').find(l => l.startsWith('cpu ')));
|
||||||
const total1 = s1.reduce((a, b) => a + b, 0);
|
const idle1 = s1[3], total1 = s1.reduce((a,b)=>a+b,0);
|
||||||
const total2 = s2.reduce((a, b) => a + b, 0);
|
const idle2 = s2[3], total2 = s2.reduce((a,b)=>a+b,0);
|
||||||
const dTotal = total2 - total1;
|
const dIdle = idle2 - idle1, dTotal = total2 - total1;
|
||||||
const dIdle = s2[3] - s1[3];
|
|
||||||
cpuUsage = dTotal > 0 ? Math.round((1 - dIdle / dTotal) * 100) : 0;
|
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)
|
// RAM : lecture de /proc/meminfo (valeurs réelles de l'hôte)
|
||||||
let memTotal = 0, memUsed = 0, memFree = 0, memAvailable = 0;
|
let memTotal = 8589934592; // 8 Go par défaut
|
||||||
try {
|
let memFree = 0, memAvailable = 0, memUsed = 0;
|
||||||
// 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)
|
|
||||||
try {
|
try {
|
||||||
const meminfo = fs.readFileSync('/proc/meminfo', 'utf8');
|
const meminfo = fs.readFileSync('/proc/meminfo', 'utf8');
|
||||||
const getVal = (key) => {
|
const getVal = (key) => {
|
||||||
|
|
@ -259,12 +210,11 @@ function getServerMetrics() {
|
||||||
};
|
};
|
||||||
memTotal = getVal('MemTotal');
|
memTotal = getVal('MemTotal');
|
||||||
memFree = getVal('MemFree');
|
memFree = getVal('MemFree');
|
||||||
memAvailable = getVal('MemAvailable') || memFree;
|
memAvailable = getVal('MemAvailable');
|
||||||
memUsed = memTotal - 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;
|
let diskTotal = 0, diskUsed = 0, diskFree = 0;
|
||||||
try {
|
try {
|
||||||
const diskRaw = execSync('df -B1 / | tail -1').toString().trim().split(/\s+/);
|
const diskRaw = execSync('df -B1 / | tail -1').toString().trim().split(/\s+/);
|
||||||
|
|
@ -273,39 +223,26 @@ function getServerMetrics() {
|
||||||
diskFree = parseInt(diskRaw[3]);
|
diskFree = parseInt(diskRaw[3]);
|
||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
|
|
||||||
// Uptime depuis /proc/uptime du VPS (namespace partagé avec le VPS)
|
// Uptime et load
|
||||||
let uptimeSeconds = 0;
|
const uptimeRaw = fs.readFileSync('/proc/uptime', 'utf8').trim().split(' ');
|
||||||
try {
|
const uptimeSeconds = parseFloat(uptimeRaw[0]);
|
||||||
const uptimeFile = fs.existsSync('/host/proc/uptime') ? '/host/proc/uptime' : '/proc/uptime';
|
const loadRaw = fs.readFileSync('/proc/loadavg', 'utf8').trim().split(' ');
|
||||||
uptimeSeconds = parseFloat(fs.readFileSync(uptimeFile, 'utf8').trim().split(' ')[0]) || 0;
|
const load1 = parseFloat(loadRaw[0]);
|
||||||
} catch (e) {}
|
const load5 = parseFloat(loadRaw[1]);
|
||||||
|
const load15 = parseFloat(loadRaw[2]);
|
||||||
|
|
||||||
// Load average
|
// Réseau
|
||||||
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)
|
|
||||||
let netRx = 0, netTx = 0;
|
let netRx = 0, netTx = 0;
|
||||||
try {
|
try {
|
||||||
const netFile = fs.existsSync('/host/proc/net/dev') ? '/host/proc/net/dev' : '/proc/net/dev';
|
const netDev = fs.readFileSync('/proc/net/dev', 'utf8');
|
||||||
const netRaw = fs.readFileSync(netFile, 'utf8');
|
const netLine = netDev.split('\n').find(l => /eth0|ens|enp/.test(l));
|
||||||
const line = netRaw.split('\n').find((l) => /^\s*(eth0|venet0|ens\d+|enp\d+)/.test(l));
|
if (netLine) {
|
||||||
if (line) {
|
const parts = netLine.trim().split(/\s+/);
|
||||||
const parts = line.trim().split(/\s+/);
|
|
||||||
netRx = parseInt(parts[1]) || 0;
|
netRx = parseInt(parts[1]) || 0;
|
||||||
netTx = parseInt(parts[9]) || 0;
|
netTx = parseInt(parts[9]) || 0;
|
||||||
}
|
}
|
||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
|
|
||||||
const memPct = memTotal > 0 ? Math.round((memUsed / memTotal) * 100) : 0;
|
|
||||||
const diskPct = diskTotal > 0 ? Math.round((diskUsed / diskTotal) * 100) : 0;
|
|
||||||
|
|
||||||
resolve({
|
resolve({
|
||||||
cpu: { usage: cpuUsage },
|
cpu: { usage: cpuUsage },
|
||||||
memory: {
|
memory: {
|
||||||
|
|
@ -313,26 +250,24 @@ function getServerMetrics() {
|
||||||
used: memUsed,
|
used: memUsed,
|
||||||
free: memFree,
|
free: memFree,
|
||||||
available: memAvailable,
|
available: memAvailable,
|
||||||
usagePercent: memPct,
|
usagePercent: Math.round((memUsed / memTotal) * 100)
|
||||||
},
|
},
|
||||||
disk: {
|
disk: {
|
||||||
total: diskTotal,
|
total: diskTotal,
|
||||||
used: diskUsed,
|
used: diskUsed,
|
||||||
free: diskFree,
|
free: diskFree,
|
||||||
usagePercent: diskPct,
|
usagePercent: Math.round((diskUsed / diskTotal) * 100)
|
||||||
},
|
},
|
||||||
uptime: uptimeSeconds,
|
uptime: uptimeSeconds,
|
||||||
load: { load1, load5, load15 },
|
load: { load1, load5, load15 },
|
||||||
network: { rx: netRx, tx: netTx },
|
network: { rx: netRx, tx: netTx },
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString()
|
||||||
source: 'fallback',
|
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
resolve({ error: err.message });
|
resolve({ error: err.message });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
docker,
|
docker,
|
||||||
getContainerInfo,
|
getContainerInfo,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue