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.path — pathlib 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 lesKeyboardInterruptetSystemExit. Toujours spécifier le type d’exception- Oublier
newline=""danscsv.writer— produit des lignes vides sur Windows yaml.load()au lieu deyaml.safe_load()— faille de sécurité par exécution de code arbitraire- Chemins en dur (
"/home/ubuntu/config.yaml") — utilisePath.home()ou des variables d’environnement - Ignorer les erreurs (
except: pass) — le pire anti-pattern. Log au minimum
🎯 Ce qu’il faut retenir :
try/exceptavec des exceptions spécifiques, jamais unexceptnuwith open()pour garantir la fermeture des fichiers (context manager)pathlib.Pathpour 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.
Contenu réservé aux abonnés
Ce chapitre fait partie de la formation complète. Abonne-toi pour débloquer tous les contenus.
Débloquer pour 29 CHF/moisLe chapitre 1 de chaque formation est gratuit.
Série pas encore débloquée
Termine la série prérequise d'abord pour accéder à ce contenu.
Aller à la série prérequiseSérie : Python DevOps
4 / 8Sur cette page
Articles liés
Structures de contrôle et fonctions
Conditions, boucles for/while, fonctions, et compréhensions de listes en Python. Tout pour écrire tes premiers scripts DevOps.
Python : les bases du langage
Premiers pas en Python pour le DevOps : variables, types de données, opérateurs, strings, listes, dictionnaires, tuples et sets.
Python avancé : classes et modules
Programmation orientée objet en Python : classes, héritage, méthodes spéciales, et organisation du code avec les modules et packages.