diff --git a/.requirement-agent b/.requirement-agent new file mode 100644 index 0000000..0c08740 --- /dev/null +++ b/.requirement-agent @@ -0,0 +1,2 @@ +psutil +flask diff --git a/.requirement-collector b/.requirement-collector new file mode 100644 index 0000000..81e1c04 --- /dev/null +++ b/.requirement-collector @@ -0,0 +1,2 @@ + +dotenv \ No newline at end of file diff --git a/README.md b/README.md index ef946f8..be3b0c2 100644 --- a/README.md +++ b/README.md @@ -1 +1,72 @@ -# monitoring \ No newline at end of file +# monitoring + +# Supervision et Monitoring d'Infrastructure Linux + +Ce projet fournit une solution simple et efficace pour superviser une infrastructure Linux, en collectant des métriques système via des agents Python et en sauvegardant ces données pour analyse. + +--- + +## 🛠️ Fonctionnalités + +### 1. Serveur de Monitoring +- **Endpoints :** + - `/healthcheck` : Vérifie si l'agent est opérationnel. + - `/getmetrics` : Récupère les métriques système (CPU, mémoire, disque, etc.). +- Collecte de métriques en temps réel grâce à `psutil`. + +### 2. Script de Supervision +- Collecte des métriques de plusieurs agents en parallèle. +- Gestion dynamique des listes blanche et noire : + - **Liste blanche** : Agents à surveiller. + - **Liste noire** : Agents inaccessibles ou hors service. +- Sauvegarde des métriques dans un fichier JSON (`metrics_data.json`). + +--- + +## 🖥️ Installation + +### Prérequis +1. **Python 3.7+** installé. +2. Bibliothèques Python nécessaires : + - `psutil` + - `flask` + - `requests` + - `python-dotenv` + +### Installation des dépendances +```bash +pip install -r requirements-agent +pip install -r requirements-collector +``` + +### Configuration + + Créez un fichier .env dans le répertoire principal avec le contenu suivant : + +METRICS_FILE=metrics_data.json +INTERVAL_HEALTH_CHECK=3 +INTERVAL_METRICS_CHECK=1 +WHITELIST_AGENT=127.0.0.1,192.168.1.100 +HEALTHROOT=healthcheck +METRICSROOT=getmetrics + +### Paramètres principaux : + + METRICS_FILE : Nom du fichier JSON où les métriques seront sauvegardées. + INTERVAL_HEALTH_CHECK : Intervalle (en secondes) entre les vérifications de santé. + INTERVAL_METRICS_CHECK : Intervalle (en secondes) entre les collectes de métriques. + WHITELIST_AGENT : Liste des adresses IP des agents autorisés, séparées par des virgules. + + +### arborescence +├── agent.py # Script Flask pour l'agent +├── supervisor.py # Script de supervision +├── metrics_data.json # Fichier JSON contenant les métriques collectées +├── .env # Variables d'environnement pour la configuration +├── requirements.txt # Liste des dépendances Python +├── README.md # Documentation du projet + +### auteur +josselin +heidi +antoine \ No newline at end of file diff --git a/agent.py b/agent.py new file mode 100644 index 0000000..74c1559 --- /dev/null +++ b/agent.py @@ -0,0 +1,89 @@ +#!/usr/bin/python3 +""" +Ce script expose un serveur Flask qui collecte des métriques système et les rend disponibles via un endpoint `/getmetrics`. +Les métriques incluent l'utilisation du CPU, de la mémoire, du disque, le nom du système d'exploitation, +le nom de l'hôte, et l'heure. + +Le port d'exécution du serveur doit etre choisis par l'ingénieur sinon le port par defaut est de 5000 + +Les métriques sont collectées en temps réel à l'aide de la bibliothèque `psutil`. +""" + +import psutil +from flask import Flask, jsonify +import platform +from datetime import datetime + +# Initialisation de l'application Flask +app = Flask(__name__) + +def collect_metrics(): + """ + Collecte les métriques système, incluant l'utilisation du CPU, de la mémoire, et du disque, + ainsi que des informations sur le système d'exploitation et le nom de l'hôte. + + Returns: + - dict: Un dictionnaire contenant les métriques collectées : + - cpu_usage (str): Pourcentage d'utilisation du CPU. + - memory_usage (str): Pourcentage d'utilisation de la mémoire. + - disk_usage (str): Pourcentage d'utilisation du disque principal. + - os (str): Nom du système d'exploitation. + - hostname (str): Nom de l'hôte de la machine. + - timestamp (str): Horodatage actuel au format ISO 8601. + """ + # Collecte des métriques système + cpu_usage = psutil.cpu_percent(interval=1) # Utilisation CPU en pourcentage + memory = psutil.virtual_memory().percent # Utilisation mémoire en pourcentage + disk = psutil.disk_usage('/').percent # Utilisation disque en pourcentage + os_name = platform.system() # Nom du système d'exploitation + hostname = platform.node() # Nom de la machine (hôte) + timestamp = datetime.now().isoformat() # Horodatage actuel + + # Création du dictionnaire contenant les métriques + metrics = { + "cpu_usage": f"{cpu_usage}%", + "memory_usage": f"{memory}%", + "disk_usage": f"{disk}%", + "os": os_name, + "hostname": hostname, + "timestamp": timestamp + } + return metrics + +@app.route('/healthcheck', methods=['GET']) +def provide_health(): + """ + Endpoint Flask pour fournir un healthcheck. + Lorsqu'une requête POST est reçue sur `/healthcheck`, les métriques système sont collectées et renvoyées en JSON. + + Returns: + - tuple: Un tuple contenant : + - Response: La réponse JSON avec les métriques. + - int: Le code HTTP 200 (succès). + """ + return jsonify('OK'),200 + +@app.route('/getmetrics', methods=['GET']) +def provide_metrics(): + """ + Endpoint Flask pour fournir les métriques collectées. + Lorsqu'une requête POST est reçue sur `/getmetrics`, les métriques système sont collectées et renvoyées en JSON. + + Returns: + - tuple: Un tuple contenant : + - Response: La réponse JSON avec les métriques. + - int: Le code HTTP 200 (succès). + """ + # Collecte des métriques système + metrics = collect_metrics() + # Renvoi des métriques en réponse à la requête + return jsonify(metrics), 200 + +if __name__ == '__main__': + """ + Point d'entrée principal du script. + Configure et lance le serveur Flask sur l'adresse `0.0.0.0` et le port 5000 par defaut + """ + # Lancement de l'application Flask + app.run(host='0.0.0.0', port=5000) + diff --git a/collector.py b/collector.py index 1477841..01a6d04 100644 --- a/collector.py +++ b/collector.py @@ -1,55 +1,179 @@ -from flask import Flask, request, jsonify -import json -import requests +#!/usr/bin/python3 + +""" +Ce script exécute des vérifications de santé et des récupérations de métriques pour une liste d'agents, +et enregistre ces données dans un fichier JSON. Les vérifications sont effectuées à intervalles réguliers +avec un mécanisme asynchrone pour gérer plusieurs agents simultanément. + +Le script vérifie la santé des agents via l'endpoint /healthcheck et récupère les métriques via l'endpoint /metrics. +Les agents sont spécifiés dans un fichier d'environnement (.env) et sont gérés par des listes blanches et noires dynamiques. + +Les données collectées sont enregistrées dans un fichier JSON, et des notifications sont émises en cas de problème de connectivité. +""" + from datetime import datetime +import asyncio +import requests +import json +import os +from dotenv import load_dotenv + +# Charger les variables d'environnement depuis le fichier .env +load_dotenv() -app = Flask(__name__) +# Fonction pour vérifier si une valeur est positive +def check_positive(value: int) -> bool: + """ + Vérifie si une valeur est positive. -# Fichier où les métriques seront sauvegardées -METRICS_FILE = 'metrics_data.json' -AGENT_URL = 'http://172.21.31.251:5001/metrics' + Args: + - value (int): La valeur à vérifier. -def load_metrics(): + Returns: + - bool: True si la valeur est positive, sinon False. """ - Charge les métriques sauvegardées depuis un fichier. + try: + return value > 0 + except ValueError: + return False + +# Lecture des variables d'environnement +METRICS_FILE:str = os.getenv('METRICS_FILE', 'metrics_data.json') # Fichier où les métriques sont sauvegardées +INTERVAL_HEALTH_CHECK:int = int(os.getenv('INTERVAL_HEALTH_CHECK', 3)) # Intervalle pour les vérifications de santé (en secondes) +INTERVAL_METRICS_CHECK:int = int(os.getenv('INTERVAL_METRICS_CHECK', 1)) # Intervalle pour la récupération des métriques (en secondes) +HEALTHROOT:str = os.getenv('HEALTHROOT', 'healthcheck') # Chemin de l'endpoint de vérification de santé +METRICSROOT:str = os.getenv('METRICSROOT', 'metrics') # Chemin de l'endpoint des métriques + +# Vérification que les intervalles sont des entiers positifs +if not check_positive(INTERVAL_HEALTH_CHECK) or not check_positive(INTERVAL_METRICS_CHECK): + raise ValueError("INTERVAL_HEALTH_CHECK et INTERVAL_METRICS_CHECK doivent être des entiers positifs") + +# Lecture de la variable WHITELIST_AGENT et conversion en un ensemble +whitelist_agent_str:str = os.getenv('WHITELIST_AGENT', '') +if whitelist_agent_str: + WHITELIST_AGENT = set(whitelist_agent_str.split(',')) +else: + raise ValueError("WHITELIST_AGENT est vide") + +# BLACKLIST_AGENT reste un ensemble vide par défaut +BLACKLIST_AGENT = set() +TIMESTAMP = datetime.now().isoformat() # Date et heure actuelles au format ISO + +# Fonction pour sauvegarder les données dans un fichier +def save_metrics(ip, info): + """ + Sauvegarde les métriques pour un agent dans le fichier JSON. + + Args: + - ip (str): L'adresse IP de l'agent. + - info (dict): Les informations (métriques) à sauvegarder. """ try: with open(METRICS_FILE, 'r') as f: - return json.load(f) - except (FileNotFoundError, json.JSONDecodeError): - return {} + metrics = json.load(f) + except FileNotFoundError: + print(f'fichier {METRICS_FILE} non existant') + metrics = {} + except json.JSONDecodeError: + print(f"Le fichier {METRICS_FILE} est vide ou corrompu. Réinitialisation.") + metrics = {} -def save_metrics(metrics): + # Ajouter l'IP si elle n'existe pas encore dans le fichier + if ip not in metrics: + metrics[ip] = [] + + # Ajouter les nouvelles informations pour l'IP + metrics[ip].append(info) + + # Sauvegarder les nouvelles données dans le fichier + try: + with open(METRICS_FILE, 'w') as f: + json.dump(metrics, f, indent=4) + except FileNotFoundError: + print(f'fichier {METRICS_FILE} non existant') + except json.JSONDecodeError: + print(f"Le fichier {METRICS_FILE} est vide ou corrompu. Impossible de sauvegarder") + +# Fonction pour envoyer une requête de vérification de santé à un agent +def get_healthcheck(agent_url: str)->int: """ - Sauvegarde les métriques dans un fichier JSON. + Envoie une requête GET à l'agent pour vérifier sa santé. + + Args: + - agent_url (str): L'URL de l'agent à interroger. + + Returns: + - int: Le code de statut HTTP retourné par l'agent. """ - with open(METRICS_FILE, 'w') as f: - json.dump(metrics, f, indent=4) + try: + res = requests.get(f'http://{agent_url}/{HEALTHROOT}', timeout=5) # Timeout de 5 secondes + print(f'health_check status code: {res.status_code} from agent: {agent_url}') + return res.status_code + except requests.exceptions.RequestException as e: + print(f"Erreur lors de la requête de santé pour {agent_url}: {str(e)}") + return 500 -@app.route('/getmet', methods=['GET']) -def get_metrics(): +# Fonction pour récupérer les métriques d'un agent +def get_metrics(agent_url): """ - Envoie une requête à l'agent pour récupérer les métriques. + Récupère les métriques d'un agent et les sauvegarde dans le fichier JSON. + + Args: + - agent_url (str): L'URL de l'agent à interroger. """ try: - response = requests.get(AGENT_URL) + response = requests.get(f'http://{agent_url}/{METRICSROOT}') if response.status_code == 200: - data = response.json() - - # Ajoute un horodatage aux métriques - timestamp = datetime.now().isoformat() - metrics = load_metrics() - metrics[timestamp] = data - save_metrics(metrics) - - return jsonify({"status": "success", "metrics": data, "timestamp": timestamp}), 200 - else: - return jsonify({"error": f"Échec de la requête à l'agent. Code HTTP : {response.status_code}"}), 500 - except requests.RequestException as e: - return jsonify({"error": f"Erreur lors de la requête à l'agent : {str(e)}"}), 500 -@app.route('/') -def index(): - return "Collecteur de métriques en ligne!" + if not response.json(): + return print("Error, No data received", 400) + else: + save_metrics(agent_url, response.json()) + return print(f"get metrics success, data received and saved from agent: {agent_url}", 200) + except requests.exceptions.RequestException as e: + return print({"error": str(e)}), 500 + +# Fonction pour vérifier la santé de tous les agents dans la liste blanche +def health_check(): + """ + Effectue une vérification de santé pour chaque agent dans la liste blanche (WHITELIST_AGENT). + Si un agent échoue, il est ajouté à la liste noire (BLACKLIST_AGENT). + """ + for agent in WHITELIST_AGENT: + res = get_healthcheck(str(agent)) + log = {"Health_check_status": res, "timestamp": TIMESTAMP.split('.')[0]} + if res != 200 and agent not in BLACKLIST_AGENT: + BLACKLIST_AGENT.add(agent) + elif res == 200 and agent in BLACKLIST_AGENT: + BLACKLIST_AGENT.remove(agent) + + save_metrics(agent, log) + +# Fonction pour récupérer les métriques de tous les agents dans la liste blanche +def metrics_check(): + """ + Récupère les métriques pour chaque agent dans la liste blanche (WHITELIST_AGENT), + à condition que l'agent ne soit pas dans la liste noire. + """ + for agent in WHITELIST_AGENT: + if agent not in BLACKLIST_AGENT: + get_metrics(agent) + +# Tâche asynchrone pour effectuer des vérifications de santé à intervalles réguliers +async def interval_health_check(): + while True: + health_check() + await asyncio.sleep(INTERVAL_HEALTH_CHECK) # Attente avant la prochaine vérification + +# Tâche asynchrone pour récupérer les métriques à intervalles réguliers +async def interval_metrics_check(): + while True: + metrics_check() + await asyncio.sleep(INTERVAL_METRICS_CHECK) # Attente avant la prochaine récupération + +# Fonction principale asynchrone qui exécute les deux tâches en parallèle +async def main(): + await asyncio.gather(interval_health_check(), interval_metrics_check()) +# Exécution du programme if __name__ == '__main__': - app.run(host='0.0.0.0', port=5000, debug=True) + asyncio.run(main()) diff --git a/metrics_data.json b/metrics_data.json new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/metrics_data.json @@ -0,0 +1 @@ +