Aller au contenu principal
PythonDevOpsFormationProgrammation

Gestion d'erreurs et fichiers

30 min de lecture Python DevOps — Chapitre 4

Try/except, manipulation de fichiers (JSON, YAML, CSV), environnements virtuels avec venv/pip, et pattern d'un script DevOps propre.

Un script DevOps qui ne gère pas les erreurs, c’est une bombe à retardement. Le fichier de config manque, l’API timeout, le YAML est mal formaté — et ton pipeline CI/CD s’effondre à 3h du matin sans la moindre explication utile dans les logs. Ce chapitre t’apprend deux compétences fondamentales : gérer les erreurs proprement avec try/except, et manipuler les fichiers de configuration (JSON, YAML, CSV) que tu croiseras quotidiennement en DevOps.

Try/except : la base de la résilience

En production, tout peut casser. Le réseau tombe, le disque est plein, les permissions changent entre deux déploiements. Sans gestion d’erreurs, ton script affiche un traceback incompréhensible et s’arrête. Avec try/except, tu interceptes l’erreur, tu logs un message utile, et tu décides quoi faire — retry, fallback, ou exit propre.

Syntaxe et bonnes pratiques

def read_config(filepath):
    """Lit un fichier de configuration avec gestion d'erreurs."""
    try:
        with open(filepath, "r") as f:
            content = f.read()
        return content
    except FileNotFoundError:
        print(f"❌ Fichier non trouvé : {filepath}")
        return None
    except PermissionError:
        print(f"❌ Permission refusée : {filepath}")
        return None
    except Exception as e:
        print(f"❌ Erreur inattendue : {e}")
        return None

🔥 Règle d’or : toujours capturer les exceptions spécifiques avant le Exception générique. Un except Exception seul masque les vrais problèmes.

finally et else

Le bloc else s’exécute uniquement si aucune erreur n’est levée. Le bloc finally s’exécute toujours — parfait pour le nettoyage.

def connect_database(host, port):
    conn = None
    try:
        conn = create_connection(host, port)
    except ConnectionError as e:
        print(f"❌ Connexion échouée : {e}")
    else:
        print(f"✅ Connecté à {host}:{port}")
        return conn
    finally:
        print("🔄 Nettoyage terminé")

💡 Astuce : finally est idéal pour fermer des connexions, supprimer des fichiers temporaires ou libérer des locks — même si le code plante.

Exceptions personnalisées et pattern retry

En DevOps, les erreurs réseau sont la norme. Un bon script ne plante pas au premier timeout — il réessaie avec un backoff intelligent.

import time

class DeploymentError(Exception):
    """Erreur spécifique au déploiement."""
    def __init__(self, service, env, message):
        self.service = service
        self.env = env
        super().__init__(f"Deploy {service} ({env}) : {message}")

def retry(func, max_attempts=3, delay=1):
    """Exécute une fonction avec retry et backoff exponentiel."""
    for attempt in range(1, max_attempts + 1):
        try:
            return func()
        except Exception as e:
            print(f"  ⚠️ Tentative {attempt}/{max_attempts} : {e}")
            if attempt == max_attempts:
                raise
            time.sleep(delay * attempt)

🎯 En entreprise : ce pattern retry est partout — appels API, connexions base de données, health checks. AWS, Azure et GCP l’utilisent dans tous leurs SDK. Tu le retrouveras sous le nom de “exponential backoff with jitter”.

Manipulation de fichiers avec Pathlib

Oublie os.pathpathlib est l’approche moderne et lisible pour gérer les chemins en Python 3.

from pathlib import Path

# Créer des chemins proprement
log_dir = Path("/var/log/myapp")
config = Path.home() / ".config" / "myapp" / "settings.yaml"

# Vérifications
if not config.exists():
    print(f"⚠️ Config absente : {config}")

# Créer un répertoire (parents inclus)
log_dir.mkdir(parents=True, exist_ok=True)

# Lire / écrire en une ligne
content = config.read_text()
Path("output.txt").write_text("Hello DevOps\n")

# Lister les fichiers log
for f in Path("/var/log").glob("*.log"):
    size_mb = f.stat().st_size / 1024 / 1024
    print(f"{f.name}{size_mb:.1f} MB")

⚠️ Piège classique : ne jamais construire des chemins avec des + ou des f-strings (f"/var/log/{name}.log"). Utilise / avec Pathlib — c’est cross-platform et ça évite les bugs de séparateurs Windows/Linux.

JSON, YAML, CSV : les trois formats DevOps

Ces trois formats couvrent 95% des besoins DevOps : configs Kubernetes en YAML, réponses API en JSON, inventaires en CSV.

JSON — le standard des API

import json

# Écrire un fichier JSON
config = {
    "app": "payment-service",
    "replicas": 3,
    "database": {"host": "db.prod.internal", "port": 5432}
}

with open("config.json", "w") as f:
    json.dump(config, f, indent=2)

# Lire un fichier JSON
with open("config.json", "r") as f:
    data = json.load(f)

print(data["database"]["host"])  # db.prod.internal

YAML — le roi de l’infra-as-code

import yaml  # pip install pyyaml

# Lire un manifest Kubernetes
with open("deployment.yaml", "r") as f:
    manifest = yaml.safe_load(f)

print(f"Replicas: {manifest['spec']['replicas']}")

# Écrire du YAML propre
with open("output.yaml", "w") as f:
    yaml.dump(config, f, default_flow_style=False, sort_keys=False)

# Multi-documents (séparés par ---)
with open("multi.yaml", "r") as f:
    for doc in yaml.safe_load_all(f):
        print(doc["kind"])

💡 Toujours utiliser safe_load au lieu de load. La version non-safe peut exécuter du code arbitraire — une faille de sécurité critique si tu charges du YAML non vérifié.

CSV — inventaires et rapports

import csv

# Écrire un inventaire serveurs
servers = [
    {"hostname": "web-01", "ip": "10.0.1.1", "status": "running"},
    {"hostname": "web-02", "ip": "10.0.1.2", "status": "stopped"},
    {"hostname": "db-01", "ip": "10.0.1.10", "status": "running"},
]

with open("servers.csv", "w", newline="") as f:
    writer = csv.DictWriter(f, fieldnames=["hostname", "ip", "status"])
    writer.writeheader()
    writer.writerows(servers)

# Lire et filtrer
with open("servers.csv", "r") as f:
    for row in csv.DictReader(f):
        if row["status"] == "stopped":
            print(f"⚠️ {row['hostname']} est down !")

Cas entreprise : health checker avec gestion d’erreurs

Voici un script complet qui combine tout — erreurs, fichiers, YAML, JSON — dans un cas réel de monitoring.

#!/usr/bin/env python3
"""Health checker — lit une config YAML, vérifie les services, écrit un rapport JSON."""

import json
import logging
import sys
from pathlib import Path

import requests
import yaml

logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)

def load_config(path):
    """Charge la config YAML avec gestion d'erreurs complète."""
    try:
        return yaml.safe_load(Path(path).read_text())
    except FileNotFoundError:
        logger.error(f"Config introuvable : {path}")
        sys.exit(1)
    except yaml.YAMLError as e:
        logger.error(f"YAML invalide : {e}")
        sys.exit(1)

def check_service(name, url, timeout=5):
    """Vérifie un service HTTP."""
    try:
        r = requests.get(url, timeout=timeout)
        status = "healthy" if r.status_code == 200 else "degraded"
    except requests.ConnectionError:
        status = "unreachable"
    except requests.Timeout:
        status = "timeout"
    icon = {"healthy": "", "degraded": "⚠️"}.get(status, "")
    logger.info(f"{icon} {name}: {status}")
    return {"name": name, "url": url, "status": status}

def main():
    config = load_config("services.yaml")
    results = [check_service(s["name"], s["url"]) for s in config["services"]]
    Path("health_report.json").write_text(json.dumps(results, indent=2))
    logger.info(f"Rapport : {len(results)} services vérifiés")

if __name__ == "__main__":
    main()

🔥 Ce pattern est réutilisable : remplace requests.get par n’importe quel check (port TCP, requête SQL, commande SSH) et tu as un monitoring custom en 30 lignes.

Pièges fréquents et résumé

⚠️ Les erreurs qui font perdre des heures :

  • except: sans type — attrape même les KeyboardInterrupt et SystemExit. Toujours spécifier le type d’exception
  • Oublier newline="" dans csv.writer — produit des lignes vides sur Windows
  • yaml.load() au lieu de yaml.safe_load() — faille de sécurité par exécution de code arbitraire
  • Chemins en dur ("/home/ubuntu/config.yaml") — utilise Path.home() ou des variables d’environnement
  • Ignorer les erreurs (except: pass) — le pire anti-pattern. Log au minimum

🎯 Ce qu’il faut retenir :

  • try/except avec des exceptions spécifiques, jamais un except nu
  • with open() pour garantir la fermeture des fichiers (context manager)
  • pathlib.Path pour tous les chemins — cross-platform et lisible
  • JSON pour les API, YAML pour l’infra, CSV pour les rapports
  • Le pattern retry avec backoff exponentiel pour tout appel réseau

💡 Pour aller plus loin : explore le module logging de Python pour remplacer les print() par de vrais logs structurés avec niveaux (DEBUG, INFO, WARNING, ERROR). En production, c’est indispensable pour le debugging.

Dans le prochain chapitre, on passe au scripting DevOps concret : subprocess, appels HTTP, SSH avec Paramiko et scripts d’automatisation prêts à l’emploi.

Articles liés