From c534a87e4c33576ac4971f65a7304ea3f7698122 Mon Sep 17 00:00:00 2001 From: manus-admin Date: Mon, 1 Jun 2026 01:14:33 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20remplacer=20nsenter=20par=20lecture=20di?= =?UTF-8?q?recte=20/proc=20pour=20les=20m=C3=A9triques=20monitoring?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/src/docker.js | 165 ++++++++++++-------------------------- 1 file changed, 50 insertions(+), 115 deletions(-) diff --git a/src/backend/src/docker.js b/src/backend/src/docker.js index 9cb71cc..3d93707 100644 --- a/src/backend/src/docker.js +++ b/src/backend/src/docker.js @@ -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,