15 août 2026 • Ibexcode
Du notebook à la production : construire un pipeline ML robuste avec Airflow et CI/CD

Sommaire
- 1. Le notebook n’est pas l’unité de production
- 2. Décomposer le pipeline en responsabilités
- 3. Airflow et les DAG : orchestrer sans absorber la logique métier
- 4. Dépendre d’un état, pas seulement d’un horaire
- 5. L’idempotence comme propriété du pipeline
- 6. Retry ne signifie pas masquer les erreurs
- 7. Valider les données avant l’inférence
- 8. Du contrôle de schéma au contrôle de distribution
- 9. Les artefacts ML sont versionnés comme un ensemble cohérent
- 10. CI : construire et valider
- 11. CD : déployer un état reproductible
- 12. Pourquoi les modifications manuelles deviennent dangereuses
- 13. La conteneurisation ne suffit pas à garantir la reproductibilité
- 14. Observabilité : savoir ce qui s’est réellement passé
- 15. Découpler le pipeline ML du produit
- 16. Concevoir pour l’incident plutôt que pour le parcours nominal
- 17. Les invariants opérationnels du pipeline
- 18. Conclusion
Un modèle Machine Learning peut fonctionner parfaitement dans un notebook et rester très loin d’un système exploitable en production.
Le notebook répond principalement à une question :
Le modèle peut-il apprendre quelque chose d’utile à partir des données disponibles ?
La production en pose beaucoup d’autres.
Que se passe-t-il si les données arrivent en retard ? Si leur schéma évolue ? Si une étape échoue après avoir partiellement écrit ses résultats ? Si une nouvelle version du code est incompatible avec un ancien modèle ? Si une inférence doit être rejouée ? Si le serveur redémarre pendant un traitement ? Si une dépendance externe devient temporairement indisponible ?
Sur un système ML exécuté quotidiennement, ces situations ne sont pas exceptionnelles. Elles font partie du fonctionnement normal de la plateforme.
Le pipeline présenté ici a donc progressivement été structuré autour d’un principe : l’inférence n’est pas un script à exécuter, mais une chaîne de traitements dont chaque étape possède des entrées, des sorties, des dépendances et des conditions de validité explicites.
L’objectif de cet article n’est pas de détailler le modèle lui-même, mais l’infrastructure qui permet de l’exécuter de manière reproductible : ingestion, validation des données, orchestration avec Apache Airflow, isolation des environnements, CI/CD, déploiement et observabilité.
1. Le notebook n’est pas l’unité de production
Pendant la phase de recherche, un workflow ML peut être relativement linéaire : charger les données → calculer les features → charger le modèle → prédire → analyser le résultat
Dans cet environnement, l’opérateur, le développeur, fait implicitement partie du système. Il vérifie que les données existent, relance une cellule en cas d’erreur, inspecte une distribution suspecte, supprime un fichier intermédiaire incohérent ou décide qu’une exécution doit être recommencée.
Aucune de ces hypothèses n’est acceptable en production.
Le pipeline doit pouvoir déterminer lui-même :
- si ses entrées sont disponibles
- si elles sont valides
- quelles étapes doivent être exécutées
- lesquelles peuvent être rejouées
- si une sortie existante peut être réutilisée
- si une erreur est transitoire ou bloquante
- si les résultats peuvent être publiés
Le passage en production consiste donc moins à « automatiser le notebook » qu’à rendre explicites toutes les décisions qui étaient auparavant prises implicitement autour de lui.
2. Décomposer le pipeline en responsabilités
La première étape a été de séparer les différentes responsabilités du traitement.
Une représentation simplifiée du pipeline est la suivante :

Cette séparation permet notamment d’éviter qu’un problème d’ingestion soit interprété comme une erreur d’inférence ou qu’un résultat incomplet soit publié simplement parce que le processus Python s’est terminé sans exception.
Chaque étape possède son propre contrat.
L’inférence, par exemple, ne doit pas avoir à déterminer si les données brutes sont suffisamment complètes. Elle doit recevoir des données dont cette propriété a déjà été vérifiée.
De même, la publication ne doit pas décider si une prédiction semble valide. Elle doit recevoir un artefact ayant déjà franchi les contrôles correspondants.
Cette organisation introduit une propriété importante : une étape ne doit pas compenser silencieusement les garanties manquantes de l’étape précédente.
3. Airflow et les DAG : orchestrer sans absorber la logique métier
Apache Airflow organise les traitements sous forme de DAG (Directed Acyclic Graph, ou graphe orienté acyclique).
Un DAG décrit un workflow : il représente les différentes tâches à exécuter ainsi que les dépendances qui déterminent leur ordre.
Par exemple :
acquisition → validation → préparation
La tâche validation ne peut s’exécuter qu’après acquisition, et préparation dépend à son tour de la réussite de validation.
Le terme acyclique signifie simplement que ces dépendances ne peuvent pas former une boucle : une tâche située en amont ne peut pas finir par dépendre d’une tâche située en aval.
Un DAG ne désigne donc pas le traitement métier lui-même. Il décrit principalement quelles tâches doivent s’exécuter, leurs dépendances et les conditions qui gouvernent leur exécution. Airflow se charge alors de leur planification et de leur orchestration.
Il peut notamment gérer :
- les dépendances
- l’ordre d’exécution
- les conditions de déclenchement
- les retries
- les timeouts
- les ressources nécessaires
- les états d’exécution
La logique métier reste autant que possible dans des composants Python indépendants.
Cette distinction est importante. Un pipeline fortement couplé à Airflow devient rapidement difficile à tester ou à exécuter en dehors de l’orchestrateur. À l’inverse, lorsque les tâches Airflow appellent des composants disposant d’interfaces explicites, ces derniers peuvent être exécutés et testés indépendamment.
Des DAG correspondant à des frontières opérationnelles
L’ensemble du système n’a pas nécessairement à être représenté par un unique DAG.
Lorsque plusieurs chaînes possèdent des responsabilités distinctes et peuvent être exécutées ou rejouées indépendamment, leur séparation peut devenir une propriété utile de l’architecture.
Une représentation simplifiée est la suivante :

Chaque DAG conserve ainsi une responsabilité identifiable et produit un état exploitable par le composant suivant. Cette séparation permet également de raisonner plus facilement sur les reprises.
Une nouvelle inférence ne nécessite pas nécessairement de refaire l’ingestion si un dataset déjà validé est disponible. De la même manière, une publication peut être rejouée à partir de prédictions déjà validées sans recalculer l’ensemble de la chaîne ML.
Les erreurs, retries et conditions de réussite peuvent ainsi être gérés au niveau correspondant réellement au traitement concerné.
Des DAG légers, des composants indépendants
Séparer plusieurs responsabilités ne signifie pas pour autant déplacer leur implémentation dans les fichiers Airflow.
Un DAG d’inférence peut par exemple définir les dépendances entre plusieurs tâches :
préparation → inférence → validation
mais les traitements correspondants restent implémentés dans des composants Python dédiés.
- Le DAG décrit l’orchestration.
- Le composant Python réalise le traitement.
Cette séparation permet notamment de tester une inférence ou une validation sans avoir à démarrer Airflow, et évite que l’orchestrateur devienne progressivement le conteneur de toute la logique applicative.
L’objectif n’est pas non plus de multiplier artificiellement les DAG.
Deux traitements n’ont pas besoin d’être séparés simplement parce qu’ils pourraient l’être. Une frontière devient pertinente lorsqu’elle correspond à une responsabilité opérationnelle réelle : cycle d’exécution différent, possibilité de rejeu indépendant, état intermédiaire réutilisable ou conditions de réussite distinctes.
Le découpage cherche donc un équilibre entre deux extrêmes :
- un DAG monolithique, absorbant toute la logique métier
- une multitude de DAG, sans véritable frontière opérationnelle
Dans cette architecture, Airflow contrôle quand, dans quel ordre et dans quelles conditions les traitements s’exécutent.
Le code applicatif définit ce que ces traitements font.
4. Dépendre d’un état, pas seulement d’un horaire
Une planification basée uniquement sur l’heure est souvent insuffisante pour un pipeline de données.
Il peut par exemple être tentant d’organiser les traitements ainsi :
05:00 → ingestion
06:00 → inférence
06:30 → publication
Cette organisation contient pourtant une hypothèse implicite : chaque traitement précédent aura terminé correctement dans le délai prévu.
Le fait qu’il soit 6h ne garantit pas que les données nécessaires à l’inférence soient disponibles.
Une ingestion peut avoir pris plus de temps que prévu, une validation peut avoir échoué ou les données attendues peuvent simplement ne pas encore être disponibles.
La dépendance réelle n’est donc pas il est 6h, mais plutôt dataset validé et disponible
L’orchestration doit représenter ces dépendances explicitement. Une tâche aval devient alors exécutable lorsque l’état dont elle dépend existe réellement et satisfait les conditions attendues, et non simplement parce qu’une heure théorique a été atteinte.
Cette distinction devient particulièrement importante lorsque plusieurs DAG coopèrent.
Le DAG d’inférence ne dépend pas uniquement de la fin théorique du DAG d’ingestion : il dépend de l’existence d’un dataset ayant effectivement franchi les contrôles nécessaires.
De la même manière, la publication dépend de prédictions validées, et non simplement du fait qu’une tâche d’inférence se soit terminée.
L’horaire conserve évidemment son utilité pour déterminer quand vérifier ou déclencher un traitement. Il ne remplace simplement pas les garanties nécessaires à son exécution.
Airflow permet ainsi de raisonner sur des états et des dépendances réels du système, plutôt que sur une succession de scripts supposés avoir terminé à heures fixes.
5. L’idempotence comme propriété du pipeline
Un pipeline de production doit pouvoir être rejoué. Une tâche peut échouer après avoir :
- téléchargé les données
- créé une partie de ses fichiers
- écrit quelques enregistrements
- calculé certaines features
- commencé une publication
Si sa relance suppose que rien n’a été exécuté auparavant, chaque incident nécessite une intervention manuelle.
Les traitements sont donc conçus autant que possible pour être idempotents :
run(x)
run(x)
run(x)
doit converger vers le même état final qu’une seule exécution correcte.
Cela peut impliquer selon les composants :
- des écritures déterministes
- des identifiants stables
- le remplacement contrôlé d’artefacts
- des transactions
- des fichiers temporaires suivis d’un déplacement atomique
- des opérations d’upsert
- la vérification de l’état existant avant modification
L’idempotence transforme profondément la gestion des incidents. Un retry cesse d’être une opération potentiellement dangereuse et devient un mécanisme normal de reprise.
6. Retry ne signifie pas masquer les erreurs
Toutes les erreurs ne doivent cependant pas être rejouées de la même manière.
Une indisponibilité réseau temporaire et une incompatibilité de schéma ne représentent pas le même type d’incident. Le premier peut légitimement être retenté, le second doit généralement interrompre le pipeline.
Retenter automatiquement pendant plusieurs heures une donnée structurellement invalide ne rend pas le système plus robuste. Cela retarde simplement le diagnostic.
La politique de reprise distingue donc les erreurs transitoires des erreurs structurelles.
Cette distinction permet également de produire des alertes plus utiles : une erreur qui sera probablement résolue au prochain retry n’a pas la même priorité qu’une rupture de contrat sur les données d’entrée.
7. Valider les données avant l’inférence
Un service externe peut répondre avec succès tout en fournissant des données inutilisables. Le monitoring HTTP ne suffit donc pas.
Avant d’autoriser la suite du pipeline, plusieurs propriétés peuvent être vérifiées :
- présence des champs attendus
- types
- valeurs obligatoires
- cardinalités
- catégories inconnues
- volumes
- distributions
- valeurs extrêmes
- cohérence entre plusieurs objets
Cette distinction est fondamentale, une évolution silencieuse des données peut être plus dangereuse qu’une panne franche. Lorsqu’une source ne répond plus, le pipeline s’arrête.
Lorsqu’elle continue à répondre avec une distribution modifiée ou un champ dont la sémantique a changé, l’inférence peut continuer à produire des résultats techniquement valides mais statistiquement incohérents.
8. Du contrôle de schéma au contrôle de distribution
La validation ne s’arrête donc pas au schéma. Deux datasets peuvent posséder exactement les mêmes colonnes et pourtant représenter des populations très différentes.
Le pipeline surveille également certaines propriétés statistiques des entrées et peut les comparer à des références historiques.
Conceptuellement :

Tous les écarts ne doivent évidemment pas interrompre la production, une variation peut être parfaitement légitime.
L’objectif est de distinguer :
- les invariants structurels, dont la violation doit bloquer le pipeline
- les anomalies fortes, qui nécessitent une inspection
- les variations statistiques normales, qui doivent simplement être enregistrées
Cette hiérarchie évite deux extrêmes : ne rien contrôler ou construire un système si sensible qu’il s’arrête au moindre changement naturel des données.
9. Les artefacts ML sont versionnés comme un ensemble cohérent
Un modèle de production n’est pas uniquement un fichier de poids. Son comportement dépend également de tout ce qui transforme les données avant et après son exécution.
Un artefact déployable peut conceptuellement regrouper :
model/
├── model
├── configuration
├── feature definition
├── categorical vocabulary
├── preprocessing metadata
├── calibration
└── evaluation metadata
L’objectif est d’empêcher une situation dans laquelle :
- le modèle appartient à une version
- le vocabulaire catégoriel à une autre
- le preprocessing au code actuellement présent sur le serveur
- la calibration à une expérience encore différente
Ces combinaisons peuvent être techniquement exécutables tout en produisant des résultats incorrects. Le déploiement doit donc préserver la compatibilité entre code, données et artefacts ML.
10. CI : construire et valider
La chaîne CI vérifie les propriétés qui peuvent l’être avant le déploiement. Selon le composant concerné, cela inclut notamment :
- tests unitaires
- tests d’intégration
- validation de configuration
- contrôle des dépendances
- construction des images
- vérification des composants du pipeline
- publication des artefacts nécessaires au déploiement
L’objectif est d’arrêter une modification le plus tôt possible.

Une image qui ne peut pas être construite n’a aucune raison d’atteindre le serveur. Une configuration invalide n’a aucune raison d’attendre le démarrage d’Airflow pour être détectée.
Ce principe réduit le nombre d’erreurs découvertes dans l’environnement le plus coûteux : la production.
11. CD : déployer un état reproductible
Le déploiement ne doit pas seulement amener une nouvelle version en production. Il doit garantir que l’état déployé est identifiable, versionné et reproductible.
Une production dont l’état dépend de modifications manuelles ou d’éléments présents uniquement sur le serveur finit rapidement par diverger de ses sources de vérité. Le système s’appuie donc sur des artefacts identifiables et des procédures automatisées.
Le principe général est :

L’infrastructure et la configuration des services sont elles aussi décrites de manière déclarative autant que possible. Le serveur n’est pas considéré comme la source de vérité. Il est une cible sur laquelle un état défini ailleurs doit pouvoir être reproduit.
12. Pourquoi les modifications manuelles deviennent dangereuses
Une correction directe sur un serveur peut sembler efficace lorsqu’un problème doit être résolu rapidement.
Mais si cette modification n’existe que sur le serveur, l’état réel de la production commence à diverger de l’état décrit par les sources de vérité.
Cette modification disparaîtra au prochain déploiement, lorsque l’état défini dans les sources de vérité sera de nouveau appliqué.
Pendant cette divergence, l’état réellement exécuté en production ne peut plus être reconstruit uniquement à partir d’éléments identifiés et versionnés.
Toute modification durable doit être effectuée dans le code, la configuration ou l’infrastructure déclarative, puis propagée vers la production par le processus de déploiement.
La production doit rester la conséquence de ces sources de vérité, et non devenir une source de configuration parallèle.
13. La conteneurisation ne suffit pas à garantir la reproductibilité
Docker permet d’encapsuler un service avec son environnement d’exécution :
- version de Python
- bibliothèques
- dépendances système
- application
- configuration d’exécution
Cette frontière permet à une même version du service d’être construite, testée et exécutée avec les mêmes dépendances système et logicielles.
Mais cette reproductibilité reste limitée à ce que le conteneur encapsule. Un conteneur ne garantit ni la version des données utilisées, ni la compatibilité des artefacts ML, ni celle des transformations dont dépend le modèle.
Une image Docker parfaitement versionnée exécutant un modèle incompatible avec son preprocessing reste un système incorrect.
La conteneurisation apporte donc une garantie importante de reproductibilité, mais elle ne suffit pas à rendre l’ensemble d’un système ML reproductible.
14. Observabilité : savoir ce qui s’est réellement passé
L’automatisation d’un pipeline ne garantit pas à elle seule que son fonctionnement soit observable. Savoir qu’une tâche s’est exécutée ne suffit pas toujours à déterminer si le traitement a réellement produit le résultat attendu.
L’orchestration fournit une première couche d’observabilité :
- état des tâches
- durée
- retries
- dépendances
- logs
- historique des exécutions
Mais le statut technique d’une tâche ne suffit pas toujours. Une tâche peut réussir et produire zéro résultat. Une ingestion peut terminer sans exception avec un volume anormalement faible. Une inférence peut générer des scores contenant une distribution inhabituelle.
L’observabilité doit donc couvrir plusieurs niveaux et répondre aux questions associées:
- Infrastructure : Le serveur et les services nécessaires sont-ils disponibles ?
- Processus : Les composants s’exécutent-ils correctement ?
- Pipeline : Toutes les étapes attendues ont-elles terminé ?
- Données : Les entrées sont-elles complètes et cohérentes ?
- ML : Les sorties restent-elles compatibles avec le comportement attendu ?
Cette hiérarchie évite de considérer qu’un processus terminé sans erreur constitue à lui seul une preuve du bon fonctionnement du système.
15. Découpler le pipeline ML du produit
Les résultats de l’inférence ne sont pas exposés directement au client final. Seul un état ayant franchi les contrôles du pipeline est publié vers une couche backend dédiée.
Cette couche constitue l’interface entre deux systèmes ayant des responsabilités et des cycles de vie différents :

Le pipeline ML peut ainsi être en cours de recalcul, temporairement indisponible ou redéployé sans que l’application perde nécessairement le dernier état valide publié.
Inversement, l’évolution du produit n’impose pas de modifier le pipeline d’inférence.
Cette séparation permet donc de faire évoluer et d’exploiter indépendamment le calcul ML et les services qui exposent ses résultats aux utilisateurs.
16. Concevoir pour l’incident plutôt que pour le parcours nominal
Le parcours nominal d’un pipeline est généralement simple à décrire : les données arrivent, les traitements s’exécutent et les résultats sont publiés.
La robustesse se joue davantage dans les chemins qui s’écartent de ce scénario.
Pour chaque étape, il faut donc définir non seulement ce qui doit se produire lorsqu’elle réussit, mais également l’état dans lequel le système doit rester lorsqu’elle échoue. Une interruption ne doit pas laisser cette réponse au hasard.
Selon l’étape concernée, le comportement attendu peut être différent :
- rejouer automatiquement une opération idempotente
- interrompre la chaîne lorsqu’un contrat n’est plus respecté
- empêcher les traitements dépendants de démarrer
- écarter une sortie partielle
- conserver le dernier état valide
- produire suffisamment d’informations pour diagnostiquer l’incident
L’objectif n’est donc pas de construire un pipeline qui ne tombe jamais en erreur. Il est de faire en sorte que l’échec d’une étape conduise à un état connu, observable et maîtrisé.
Cette capacité à maîtriser les états d’échec est souvent plus importante pour la robustesse du système que l’optimisation de quelques secondes sur son parcours nominal.
Cette recherche de robustesse ne consiste pas pour autant à multiplier les composants d’infrastructure. Chaque composant introduit doit répondre à un problème opérationnel identifiable et supprimer davantage de complexité qu’il n’en ajoute.
17. Les invariants opérationnels du pipeline
L’architecture peut finalement être résumée par plusieurs invariants.
1. Une tâche aval ne compense pas une entrée invalide. Les données franchissent explicitement les contrôles nécessaires avant d’être consommées par les étapes suivantes.
2. Une tâche rejouée doit converger vers un état cohérent. Les retries font partie du fonctionnement normal du système et ne doivent pas nécessiter un nettoyage manuel systématique.
3. Une réussite technique ne garantit pas une réussite fonctionnelle. Le code de sortie d’un processus est complété par des contrôles sur les données et les artefacts produits.
4. Un modèle est déployé avec les éléments nécessaires à son exécution. Poids, configuration, preprocessing, vocabulaire et calibration appartiennent à une même version logique.
5. La publication intervient après validation. Une sortie partielle ou incohérente ne remplace pas automatiquement le dernier état valide.
6. La production n’est pas une source de vérité. Les modifications durables doivent être reproductibles depuis le code, la configuration et l’automatisation.
7. L’observabilité couvre l’ensemble de la chaîne. Infrastructure, orchestration, données et sorties ML sont des niveaux de contrôle distincts.
8. Les dépendances sont représentées explicitement. Un horaire ne remplace pas un contrat de disponibilité entre deux traitements.
9. L’orchestrateur ne porte pas la logique métier. Les DAG coordonnent les traitements et leurs dépendances, leur implémentation reste dans des composants pouvant être exécutés et testés indépendamment.
10. Le produit est découplé du calcul ML. L’indisponibilité temporaire du pipeline ne doit pas nécessairement devenir une indisponibilité du service consommateur.
Ces invariants permettent de raisonner sur le pipeline indépendamment de l’algorithme actuellement utilisé.
18. Conclusion
Passer d’un notebook à un système ML en production ne consiste pas simplement à placer un script Python dans un cron ou un conteneur.
Le problème change de nature. Le modèle doit désormais fonctionner au sein d’une chaîne capable de gérer des données imparfaites, des dépendances externes, des traitements partiels, des retries, plusieurs versions d’artefacts et des déploiements successifs.
La robustesse ne repose alors plus uniquement sur le bon fonctionnement du modèle. Elle dépend aussi de propriétés beaucoup plus opérationnelles :
- les traitements ne s’exécutent que lorsque leurs dépendances sont réellement satisfaites
- les erreurs et les reprises conduisent à des états connus et maîtrisés
- code, configuration et artefacts permettent de reproduire l’état effectivement déployé
- les résultats sont contrôlés avant d’être exposés au produit, qui reste découplé du calcul ML
À partir de ce niveau de maturité, la question n’est plus seulement :
« Le modèle produit-il de bonnes prédictions ? »
Elle devient :
« Peut-on expliquer, reproduire et contrôler le chemin qui a produit chaque résultat ? »
C’est cette capacité à rendre les dépendances, les états et les conditions de validité explicites qui transforme une chaîne de traitements ML en pipeline réellement exploitable en production.