Redémarrage de services IA et orchestration
Du processus au cluster : construire pas à pas la reprise d'un service IA
Un service d'IA devient résistant aux redémarrages par paliers : d'abord un état sauvegardé et un travail reprenable, puis un processus relancé automatiquement (systemd), puis un conteneur, puis Kubernetes avec ses sondes et ses déploiements sans coupure, et enfin des tests de reprise qui prouvent que tout cela fonctionne.
Objectifs
- Monter, palier par palier, la reprise automatique d'un service d'IA : état, processus, conteneur, orchestrateur, déploiement, tests.
- Rendre un entraînement et un pipeline de traitement reprenables après un redémarrage (sauvegarde d'état, idempotence).
- Comprendre ce qu'un redémarrage fait à une session GPU et comment le vérifier.
- Configurer et calculer le comportement des relances automatiques : systemd, Docker, Kubernetes.
- Choisir entre rolling update et blue/green, puis écrire des tests de reprise mesurés (RTO, RPO).
1. La feuille de route : six paliers
On ne passe pas directement d'un script lancé à la main à un cluster Kubernetes. Chaque palier règle un problème précis et s'appuie sur le précédent :
flowchart LR P0[0 Lancement manuel] --> P1[1 Etat sauvegarde] P1 --> P2[2 Relance par systemd] P2 --> P3[3 Conteneur] P3 --> P4[4 Kubernetes et sondes] P4 --> P5[5 Deploiement sans coupure] P5 --> P6[6 Tests de reprise]
| Palier | Problème réglé | Outil |
|---|---|---|
| 1. État sauvegardé | Le travail en cours est perdu au redémarrage | Checkpoints, journal d'avancement |
| 2. Relance du processus | Personne ne relance le service après un plantage | systemd (Restart=) |
| 3. Conteneur | L'environnement (bibliothèques, CUDA) diffère d'une machine à l'autre | Docker (--restart, HEALTHCHECK) |
| 4. Orchestrateur | Une seule machine, et un service bloqué n'est pas détecté | Kubernetes (Deployment, sondes, Job) |
| 5. Déploiement sans coupure | Une mise à jour arrête le service | Rolling update, blue/green |
| 6. Tests de reprise | On croit que la reprise marche sans l'avoir prouvé | Pannes provoquées et mesurées |
2. Palier 1 : rendre le travail reprenable
2.1 Ce qui disparaît au redémarrage d'un processus d'IA
- La session GPU : chaque processus qui utilise le GPU possède un contexte CUDA. Quand le processus se termine, le pilote libère ce contexte et toute la mémoire GPU associée (poids du modèle, cache, lots en cours). Au redémarrage, il faut tout recharger.
- L'état d'entraînement : poids du modèle, état de l'optimiseur (par exemple les moments d'Adam), planificateur du taux d'apprentissage, numéro d'époque et de pas, état des générateurs aléatoires, position dans les données.
- Le travail d'un pipeline : quels fichiers ont déjà été traités, quels résultats sont déjà écrits.
- Les requêtes en cours d'un service d'inférence : elles échouent si le processus est tué au milieu.
2.2 Deux règles pour reprendre sans erreur
- Sauvegarde complète et atomique : un checkpoint contient tout l'état listé ci-dessus (en PyTorch,
model.state_dict(),optimizer.state_dict(),scheduler.state_dict(), le pas courant). On l'écrit d'abord dans un fichier temporaire, puis on le renomme avecos.replace(tmp, chemin): sur un même système de fichiers, le renommage est atomique, donc une coupure pendant l'écriture ne laisse jamais un checkpoint à moitié écrit à la place du bon. - Idempotence : refaire une étape déjà faite doit donner le même résultat, sans doublon. Par exemple, chaque résultat est écrit sous un nom déterminé par son entrée (
facture_0042.jsonpourfacture_0042.jpg) : le retraiter écrase le fichier au lieu d'en ajouter un second.
Pourquoi écrit-on le checkpoint dans un fichier temporaire avant de le renommer ?
3. Paliers 2 et 3 : relancer le processus, puis le conteneur
3.1 Palier 2 : systemd
Sous Linux, on décrit le service dans un fichier d'unité, par exemple /etc/systemd/system/factures.service :
[Unit] Description=Service IA de lecture de factures StartLimitIntervalSec=120 StartLimitBurst=4 [Service] ExecStart=/opt/factures/venv/bin/uvicorn app:app --host 0.0.0.0 --port 8000 Restart=on-failure RestartSec=5 TimeoutStopSec=30 [Install] WantedBy=multi-user.target
Restart=on-failure: systemd relance le service s'il s'arrête en erreur (code de sortie non nul, signal, dépassement de délai).RestartSec=5: attente de 5 s avant chaque relance.StartLimitIntervalSec=120etStartLimitBurst=4: au plus 4 démarrages sur 120 s ; au-delà, systemd abandonne et met l'unité en échec (sinon un service cassé tournerait en boucle). Après correction,sudo systemctl reset-failed factures.serviceremet le compteur à zéro.TimeoutStopSec=30: à l'arrêt, systemd envoie SIGTERM et laisse 30 s au service pour finir ses requêtes, puis envoie SIGKILL.
On active le tout avec sudo systemctl daemon-reload puis sudo systemctl enable --now factures.service ; journalctl -u factures.service affiche son journal.
3.2 Palier 3 : le conteneur Docker
Le conteneur emporte avec lui Python, les bibliothèques et le modèle : il démarre de la même façon sur toutes les machines. Docker a ses propres politiques de relance, données par --restart : no (par défaut), on-failure (avec un maximum facultatif, par exemple on-failure:5), always et unless-stopped (toujours, sauf si on l'a arrêté soi-même).
docker run -d --name factures --gpus all --restart unless-stopped -p 8000:8000 factures:2.1
Dans le Dockerfile, une vérification de santé décrit comment savoir si le service répond :
HEALTHCHECK --interval=30s --timeout=5s --retries=3 CMD curl -f http://localhost:8000/health || exit 1
Le service a planté, l'administrateur l'a ensuite arrêté avec docker stop, puis la machine redémarre. Avec --restart unless-stopped, que se passe-t-il ?
4. Palier 4 : Kubernetes, sondes et Jobs
Kubernetes fait tourner les conteneurs dans des pods, répartis sur plusieurs machines (les nœuds). Pour un service d'inférence permanent, on utilise un Deployment qui maintient un nombre voulu de pods (replicas). Pour un travail qui doit se terminer (entraînement, pipeline par lots), on utilise un Job.
4.1 Les trois sondes
startupProbe: laisse au conteneur le temps de démarrer (chargement du modèle sur le GPU). Tant qu'elle n'a pas réussi, les deux autres sondes ne sont pas exécutées.readinessProbe: le pod ne reçoit du trafic que lorsqu'elle réussit. On la fait réussir seulement après le préchauffage du modèle (une première inférence, souvent plus lente).livenessProbe: si elle échoue plusieurs fois de suite, Kubernetes redémarre le conteneur. C'est elle qui règle le cas du modèle bloqué mais vivant.
Un GPU se demande dans les ressources du conteneur, par exemple nvidia.com/gpu: 1 dans resources.limits (avec le plugin GPU de NVIDIA installé sur le cluster).
4.2 Quand un conteneur plante en boucle
Si un conteneur plante sans arrêt, Kubernetes le relance avec un délai qui double à chaque fois (valeurs par défaut : 10 s, 20 s, 40 s… plafonné à 5 minutes). Le pod affiche alors l'état CrashLoopBackOff. On en cherche la cause avec kubectl describe pod nom-du-pod et kubectl logs nom-du-pod --previous (journal du conteneur précédent, celui qui a planté).
4.3 Les Jobs pour les pipelines et les entraînements
Un Job relance son pod en cas d'échec, au plus backoffLimit fois (6 par défaut), avec restartPolicy: OnFailure ou Never. Cette relance n'est utile que si le travail est reprenable (palier 1) : le pod relancé doit relire le dernier checkpoint ou le journal d'avancement, rangé sur un volume persistant et pas dans le conteneur.
Le modèle met 90 s à se charger. Quelle sonde doit absorber ce délai ?
5. Palier 5 : mettre à jour sans couper
- Rolling update (stratégie par défaut d'un Deployment) : les anciens pods sont remplacés progressivement par des nouveaux ;
kubectl rollout status deployment/facturessuit l'avancement etkubectl rollout undo deployment/facturesrevient à la version précédente. Avec des GPU rares, un nouveau pod peut rester en attente (Pending) faute de GPU libre : on règle alorsmaxSurge: 0etmaxUnavailable: 1pour libérer un GPU avant d'en occuper un autre. - Blue/green : la version actuelle (bleue) continue de servir pendant qu'une version complète (verte) démarre et est testée. On bascule ensuite le Service en changeant son sélecteur, par exemple
kubectl patch service factures -p '{"spec":{"selector":{"app":"factures","version":"vert"}}}'. Le retour arrière consiste à remettre le sélecteur sur la version bleue, gardée en attente.
Étape 1 : la version verte démarre à côté de la bleue, qui sert encore tout le trafic ; il faut des GPU libres pour elle.
Étape 2 : on teste la version verte (sondes prêtes, quelques requêtes de contrôle) sans qu'aucun client ne la voie.
Étape 3 : on change le sélecteur du Service ; tout le trafic passe au vert, et le bleu reste disponible pour un retour arrière immédiat.
6. Palier 6 : prouver la reprise par des tests
Deux mesures guident les tests :
- RTO (Recovery Time Objective) : durée maximale acceptable entre la panne et le retour du service.
- RPO (Recovery Point Objective) : quantité maximale de travail ou de données qu'on accepte de perdre, exprimée en temps (par exemple « au plus 10 minutes d'entraînement »).
Un plan de test de reprise, pas à pas :
- Noter l'état de départ : nombre de pods prêts (
kubectl get pods), dernier checkpoint, mémoire GPU (nvidia-smi --query-gpu=memory.used,memory.total --format=csv). - Provoquer la panne :
kubectl delete pod nom-du-pod(arrêt propre),kill -9du processus (arrêt brutal), redémarrage d'un nœud aprèskubectl drain. - Observer :
kubectl get pods -wet chronométrer le temps jusqu'au retour à l'état prêt. - Vérifier le fond, pas seulement la forme : le service répond-il juste ? L'entraînement est-il reparti du dernier checkpoint ? La mémoire GPU a-t-elle bien été libérée (
nvidia-smi --query-compute-apps=pid,used_memory --format=csvne doit plus montrer l'ancien processus) ? - Comparer au RTO et au RPO, puis corriger et recommencer.
7. Exemples résolus
Exemple 1 : au bout de combien de temps systemd abandonne-t-il ?
Cherche d'abord : calcule la durée d'un cycle « démarrage, plantage, attente ».
systemctl status factures.service l'affiche, systemctl --failed la liste).journalctl -u factures.service, on revient à la version précédente, puis sudo systemctl reset-failed factures.service et sudo systemctl start factures.service.Exemple 2 : reprendre un pipeline par lots
Cherche d'abord, puis ouvre la solution.
Voir la solution
Nombre de lots :
\[ \frac{12\,000}{500} = 24 \text{ lots} \]Le pod relancé reprend au lot 15, le premier qui n'est pas marqué terminé. Il reste :
\[ 24 - 14 = 10 \text{ lots, soit } 10 \times 500 = 5\,000 \text{ photos} \]Les 200 photos déjà faites du lot 15 sont traitées une seconde fois. Grâce à l'idempotence (un résultat par nom de fichier, écrasé s'il existe), cela ne crée aucun doublon. Durée de la fin du travail :
\[ \frac{5\,000}{2} = 2\,500 \text{ s} \approx 41{,}7 \text{ min} \]Le travail perdu se limite à ces 200 photos, soit \( \frac{200}{2} = 100 \) s de calcul : c'est le RPO réel de ce pipeline, au plus un lot (\( \frac{500}{2} = 250 \) s).
Exemple 3 : combien de temps en CrashLoopBackOff ?
Cherche d'abord.
Voir la solution
Les délais avant les relances successives sont 10, 20, 40, 80, 160 puis 300 s (320 dépasserait le plafond). La sixième relance a donc lieu après :
\[ 10 + 20 + 40 + 80 + 160 + 300 = 610 \text{ s} \approx 10 \text{ min} \]Leçon : attendre qu'un pod en CrashLoopBackOff « finisse par repartir » fait perdre de longues minutes ; il faut lire kubectl logs nom-du-pod --previous dès les premiers plantages.
Dans l'exemple 2, si le journal d'avancement était gardé dans le système de fichiers du conteneur au lieu d'un volume persistant, que se passerait-il après la relance du pod ?
8. Erreurs fréquentes
- Automatiser la relance d'un travail qui n'est pas reprenable : systemd, Docker ou un Job relancent le processus, mais il recommence tout depuis le début.
- Checkpoint incomplet : poids sans état de l'optimiseur, du planificateur ou du pas courant.
- Écrire le checkpoint directement à sa place définitive : une coupure pendant l'écriture détruit le dernier état valide.
- Croire que Docker relance un conteneur « unhealthy » : seul l'arrêt du processus déclenche la politique
--restart. - Confier le chargement du modèle à la
livenessProbeau lieu de lastartupProbe. - Prévoir un blue/green sans GPU de réserve : la version verte reste en
Pending. - Ne tester la reprise que sur un arrêt propre : il faut aussi tester l'arrêt brutal (
kill -9, redémarrage de nœud).
9. Récapitulatif
- Palier 1 : un état sauvegardé complet et atomique, et un travail idempotent.
- Palier 2 : systemd relance le processus (
Restart=on-failure) et abandonne au-delà deStartLimitBurstdémarrages parStartLimitIntervalSec. - Palier 3 : le conteneur rend l'environnement identique partout ;
--restartne réagit qu'à l'arrêt du processus. - Palier 4 : Kubernetes détecte aussi les blocages grâce aux sondes ; un Job relance un travail par lots au plus
backoffLimitfois. - Palier 5 : rolling update (économe) ou blue/green (bascule instantanée, double capacité).
- Palier 6 : des pannes provoquées et chronométrées, comparées au RTO et au RPO.
Teste tes connaissances sur ce cours
Crée ton compte gratuitement pour accéder aux quiz associés et suivre ta progression.
