Conteneurisation (Containerization)
La conteneurisation est une méthode de virtualisation au niveau du système d’exploitation qui consiste à packager une application avec toutes ses dépendances (code, bibliothèques, configuration, runtime) dans une unité isolée et portable appelée conteneur, capable de s’exécuter de manière identique sur n’importe quel environnement.
En une phrase : la conteneurisation élimine le problème « ça marche sur ma machine ». Ce qui tourne en développement tourne de manière identique en production, sur n’importe quel cloud, sur n’importe quel serveur. C’est le fondement technique du DevOps moderne, du MLOps et du déploiement d’applications IA en production.
- Catégorie
- Technologie d’infrastructure / Virtualisation
- Principe
- Virtualisation OS-level (partage du noyau hôte)
- Outil principal
- Docker (build/run), Kubernetes (orchestration)
- Standard
- OCI (Open Container Initiative)
- Alternatives
- Podman, containerd, CRI-O, LXC/LXD
- Usage IA
- Packaging de modèles ML, pipelines d’inférence, reproductibilité
Comment fonctionne la conteneurisation
Un conteneur est un processus isolé qui partage le noyau (kernel) du système d’exploitation hôte tout en disposant de son propre système de fichiers, réseau et espace de processus. Contrairement à une machine virtuelle (VM) qui embarque un OS invité complet, un conteneur ne virtualise que la couche applicative. C’est cette architecture qui le rend léger (quelques Mo contre quelques Go pour une VM) et rapide au démarrage (quelques secondes contre quelques minutes).
Sous Linux, la conteneurisation s’appuie sur trois mécanismes du noyau :
Namespaces : isolent les processus, le réseau, le système de fichiers et les utilisateurs. Chaque conteneur « croit » qu’il est seul sur le système.
Cgroups (Control Groups) : limitent les ressources (CPU, mémoire, I/O) qu’un conteneur peut consommer. Empêchent un conteneur de monopoliser les ressources de l’hôte.
Union filesystems : empilent des couches (layers) de fichiers en lecture seule, avec une couche d’écriture au-dessus. C’est ce qui permet aux images de conteneurs d’être légères et de partager des couches communes.
Docker a unifié ces mécanismes noyau sous une CLI simple, un format d’image standardisé et un système de registres, rendant la conteneurisation accessible aux développeurs sans expertise kernel.
Conteneurs vs machines virtuelles
| Aspect | Conteneur | Machine virtuelle (VM) |
|---|---|---|
| Isolation | Processus isolé, noyau partagé | OS invité complet, hyperviseur |
| Taille | ~10-100 Mo typique | ~1-20 Go typique |
| Démarrage | Secondes | Minutes |
| Overhead | Minimal (pas d’OS invité) | Significatif (OS complet par VM) |
| Portabilité | Très élevée (OCI standard) | Limitée (format spécifique au hyperviseur) |
| Densité | Dizaines à centaines par hôte | Quelques unités à dizaines par hôte |
| Sécurité | Isolation processus (plus légère) | Isolation matérielle (plus forte) |
| Usage typique | Microservices, CI/CD, ML serving | Legacy apps, isolation forte requise |
Les conteneurs ne remplacent pas les VM. Ils les complètent. En pratique, de nombreux environnements de production exécutent des conteneurs à l’intérieur de VM pour combiner l’isolation matérielle des VM avec la densité et la portabilité des conteneurs.
Les concepts clés
Image de conteneur
Une image est un template immuable en lecture seule qui contient tout ce qui est nécessaire pour exécuter une application : le code, le runtime, les bibliothèques, les variables d’environnement et la configuration. Une image est construite à partir d’un fichier de description (Dockerfile pour Docker) qui spécifie les couches (layers) à empiler.
Les images sont stockées dans des registres (Docker Hub, GitHub Container Registry, AWS ECR, Google GCR, Azure ACR, Harbor pour le self-hosted). Le format standard est OCI (Open Container Initiative), ce qui garantit l’interopérabilité entre les outils.
Conteneur
Un conteneur est une instance en cours d’exécution d’une image. Vous pouvez lancer plusieurs conteneurs à partir de la même image, chacun isolé des autres. Les conteneurs sont éphémères par défaut : quand un conteneur s’arrête, ses modifications sont perdues (sauf si vous utilisez des volumes persistants).
Dockerfile
Le Dockerfile est la « recette de construction » d’une image. Chaque instruction crée une couche dans l’image finale. Voici un exemple typique pour une application Python :
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN useradd -m appuser
USER appuser
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]Points importants : utilisez des images de base légères (-slim ou -alpine), copiez le fichier de dépendances avant le code (pour exploiter le cache de build), et exécutez l’application en tant qu’utilisateur non-root (sécurité).
Orchestration
L’orchestration automatise le déploiement, le scaling, le networking et la gestion des conteneurs à grande échelle. Kubernetes est le standard de facto pour l’orchestration, un projet open source de la CNCF avec plus de 88 000 contributeurs. Il gère l’auto-scaling, le load balancing, les rollouts, le self-healing (redémarrage automatique des conteneurs en échec), et la gestion du stockage.
Docker Compose est l’outil d’orchestration locale pour le développement : un fichier YAML qui définit et lance plusieurs conteneurs ensemble.
L’écosystème de conteneurisation
| Catégorie | Outils | Rôle |
|---|---|---|
| Build & Run | Docker Engine, Podman, containerd | Construire et exécuter des conteneurs |
| Build uniquement | BuildKit/Buildx, Buildah, Kaniko | Construire des images (CI/CD, sans daemon) |
| Runtime bas niveau | runc, crun, Kata Containers | Exécuter les conteneurs au niveau kernel |
| Orchestration | Kubernetes, Docker Swarm, Nomad | Gérer des clusters de conteneurs |
| Registres | Docker Hub, GHCR, ECR, GCR, ACR, Harbor | Stocker, signer et distribuer les images |
| Sécurité | Snyk, Trivy, Falco | Scanner les vulnérabilités, surveiller le runtime |
La conteneurisation dans l’IA et le ML
La conteneurisation est devenue une pratique standard en MLOps. Chaque modèle ML en production est typiquement packagé dans un conteneur. Voici pourquoi c’est fondamental :
Reproductibilité
Un modèle ML dépend de dizaines de bibliothèques (PyTorch, TensorFlow, NumPy, Pandas, scikit-learn) avec des versions spécifiques. Un changement de version, même mineur, peut modifier les résultats d’inférence. Le conteneur fige l’environnement exact : même Python, mêmes bibliothèques, même configuration. Ce qui tourne sur la machine du data scientist tourne de manière identique en production.
Packaging de modèles
Les runtimes de model serving (BentoML, TorchServe, Triton) produisent des images Docker optimisées pour l’inférence. L’image contient le modèle sérialisé, le code de pre/post-processing, le runtime d’inférence, et une API REST/gRPC. KServe déploie ces images sur Kubernetes avec auto-scaling et load balancing.
Conteneurs GPU
Les conteneurs peuvent accéder aux GPU de l’hôte via le NVIDIA Container Toolkit (anciennement nvidia-docker). Cela permet de servir des modèles de deep learning et des LLM sur GPU tout en conservant les avantages de la conteneurisation (isolation, portabilité, scaling). Les images NVIDIA NGC fournissent des environnements pré-configurés avec CUDA, cuDNN et les frameworks ML.
# Lancer un conteneur avec accès GPU
docker run --gpus all -p 8000:8000 mon-modele-llm:v1.0Pipelines ML conteneurisés
Les plateformes comme Kubeflow exécutent chaque étape du pipeline ML (ingestion, preprocessing, entraînement, évaluation, déploiement) dans un conteneur séparé. Chaque étape a ses propres dépendances, son propre runtime, et peut être développée et mise à jour indépendamment. C’est l’application du pattern microservices aux pipelines ML.
Bonnes pratiques de conteneurisation
Garder les images légères
Une image plus légère se télécharge plus vite, démarre plus vite, et a moins de surface d’attaque. Utilisez des images de base minimales (python:3.12-slim au lieu de python:3.12), supprimez les fichiers inutiles, et utilisez des multi-stage builds pour séparer l’environnement de build de l’environnement d’exécution.
# Multi-stage build : phase de build séparée de l'image finale
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --target=/install -r requirements.txt
FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /install /usr/local/lib/python3.12/site-packages
COPY . .
USER 1000
CMD ["python", "serve.py"]Sécurité
N’exécutez jamais en root. Créez un utilisateur non-root dans votre Dockerfile et utilisez USER pour basculer. C’est l’une des mesures de sécurité les plus simples et les plus impactantes.
Scannez vos images. Les images de base contiennent fréquemment des vulnérabilités (CVE) dans les paquets système. Utilisez Trivy, Snyk ou les scanners intégrés des registres cloud pour détecter et corriger les vulnérabilités avant le déploiement.
Épinglez les versions. Utilisez des tags de version spécifiques (python:3.12.3-slim) plutôt que latest ou python:3.12-slim. Cela garantit la reproductibilité des builds et évite les surprises lors des mises à jour.
Ne stockez pas de secrets dans l’image. Les variables d’environnement, clés API et certificats doivent être injectés au runtime (via Kubernetes Secrets, Docker secrets, ou un gestionnaire de secrets comme Vault).
Exploiter le cache de build
Docker cache chaque couche du Dockerfile. Si une couche n’a pas changé, elle est réutilisée. Ordonnez vos instructions du moins fréquemment modifié au plus fréquemment modifié : les dépendances (qui changent rarement) avant le code source (qui change souvent). Cela accélère considérablement les builds itératifs.
Health checks
Ajoutez un HEALTHCHECK dans votre Dockerfile ou configurez des readiness/liveness probes dans Kubernetes. Sans health check, l’orchestrateur ne peut pas distinguer un conteneur sain d’un conteneur en échec.
Conteneurisation vs serverless
Le serverless (AWS Lambda, Google Cloud Functions) abstrait encore plus l’infrastructure que les conteneurs. Vous ne gérez ni les conteneurs ni les serveurs, juste du code et de la configuration.
Cependant, le serverless a des limites pour le ML : contraintes de taille de déploiement (Lambda est limité à 10 Go), cold start important, timeout d’exécution (15 minutes max sur Lambda), et pas d’accès GPU natif. Pour le serving de modèles en production, les conteneurs sur Kubernetes restent la solution dominante, surtout pour les workloads GPU et les LLM.
Docker Compose : orchestration locale
Docker Compose est l’outil standard pour définir et gérer des applications multi-conteneurs en local. Un fichier docker-compose.yml décrit l’ensemble des services, réseaux et volumes nécessaires. C’est l’outil idéal pour le développement local d’applications complexes (API + base de données + cache + modèle ML).
services:
api:
build: ./api
ports:
- "8000:8000"
depends_on:
- db
- redis
environment:
- DATABASE_URL=postgresql://user:pass@db:5432/mydb
ml-model:
build: ./model-server
ports:
- "8001:8001"
deploy:
resources:
reservations:
devices:
- capabilities: [gpu]
db:
image: postgres:16-alpine
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:7-alpine
volumes:
pgdata:Une seule commande (docker compose up) lance l’ensemble de la stack. C’est le pont entre le développement local et le déploiement Kubernetes : la logique de services est la même, seul l’orchestrateur change.
Le standard OCI
L’Open Container Initiative (OCI), hébergée par la Linux Foundation, définit les standards ouverts pour les conteneurs : le format d’image (OCI Image Spec), le runtime (OCI Runtime Spec) et la distribution (OCI Distribution Spec). Tous les outils majeurs (Docker, Podman, containerd, Kubernetes) respectent ces standards, ce qui garantit l’interopérabilité : une image construite avec Docker tourne sur Podman, et vice versa.
Ce standard est particulièrement important pour le ML : les images de modèles produites par BentoML ou d’autres runtimes de serving sont OCI-conformes, ce qui signifie qu’elles peuvent être stockées dans n’importe quel registre OCI et déployées sur n’importe quelle plateforme compatible.
Tendances 2026
WebAssembly (Wasm) comme complément. WebAssembly émerge comme alternative aux conteneurs pour les workloads ultra-légers nécessitant un démarrage en millisecondes. Docker intègre un support Wasm expérimental. Le consensus en 2026 : Wasm excelle pour les workloads légers et rapides, les conteneurs restent dominants pour le packaging applicatif général.
Conteneurs pour l’IA/ML. Les extensions Kubernetes comme Kubeflow, Ray et KServe standardisent la gestion des pipelines ML et l’inférence dans des conteneurs. Le scheduling GPU (via le NVIDIA GPU Operator) et les conteneurs multi-GPU sont des pratiques établies.
Supply chain security. La sécurité de la chaîne d’approvisionnement des images (signature des images, SBOM, attestations de provenance) devient une exigence enterprise. Les outils comme Sigstore, Cosign et les politiques OPA/Kyverno vérifient l’intégrité des images avant deployment.
Erreurs courantes
Images de plusieurs Go. Une image Python avec TensorFlow peut facilement dépasser 5 Go. Utilisez des multi-stage builds, des images slim, et ne copiez que les fichiers strictement nécessaires. Un .dockerignore bien configuré (excluant .git, __pycache__, venv, .env) réduit significativement la taille.
Exécuter en root. Par paresse ou par ignorance, beaucoup de conteneurs tournent en root. C’est un risque de sécurité majeur : une faille dans l’application donne un accès root au conteneur.
Pas de .dockerignore. Sans ce fichier, Docker copie tout le répertoire de build dans l’image, y compris les fichiers inutiles (historique Git, virtualenvs, logs, fichiers secrets).
Utiliser latest comme tag. Le tag latest n’a pas de sémantique de version. Il pointe vers la dernière image poussée, qui peut changer à tout moment. Utilisez des tags de version explicites pour garantir la reproductibilité.
Stocker des données dans le conteneur. Les conteneurs sont éphémères. Les données doivent être sur des volumes persistants (Kubernetes PersistentVolume, Docker volumes) ou dans des services externes (base de données, object storage).
docker run ne garantit pas qu’il fonctionnera sur Kubernetes. Les différences de réseau (DNS, service discovery), de stockage (volumes éphémères vs persistants) et de permissions (SecurityContext, PodSecurityStandards) peuvent casser le comportement. Testez toujours dans un environnement qui reproduit la production (Kind, Minikube, ou un cluster de staging).
Questions fréquentes sur la conteneurisation
Quelle est la différence entre un conteneur et une machine virtuelle ?
Un conteneur virtualise la couche applicative et partage le noyau de l’OS hôte. Une VM virtualise le matériel complet et embarque un OS invité entier. Les conteneurs sont plus légers (Mo vs Go), démarrent en secondes (vs minutes), et offrent une densité supérieure (dizaines à centaines par hôte vs quelques unités). Les VM offrent une isolation plus forte (isolation matérielle via hyperviseur). En pratique, les conteneurs sont le choix par défaut pour les applications modernes, les VM restent pertinentes pour les applications legacy ou quand une isolation renforcée est requise.
Quelle est la différence entre Docker et Kubernetes ?
Docker construit et exécute des conteneurs individuels. Kubernetes orchestre des clusters de conteneurs à grande échelle (auto-scaling, load balancing, rollouts, self-healing). Docker est l’outil de build et de test local. Kubernetes est l’outil de déploiement production. Vous utilisez Docker pour créer vos images, Kubernetes pour les déployer et les gérer en production. Les deux sont complémentaires, pas concurrents.
La conteneurisation est-elle nécessaire pour le machine learning ?
Pas strictement obligatoire, mais fortement recommandée dès que vous passez en production. La conteneurisation résout les problèmes de reproductibilité (mêmes dépendances en dev et prod), de portabilité (déployez sur n’importe quel cloud), et de scaling (orchestration via Kubernetes). Les runtimes de model serving (BentoML, Triton, vLLM) produisent des conteneurs Docker prêts à déployer. En MLOps, la conteneurisation est considérée comme une pratique de base.
Comment conteneuriser une application Python pour le ML ?
Créez un Dockerfile qui part d’une image Python slim (python:3.12-slim), copie et installe vos dépendances (requirements.txt ou pyproject.toml), copie votre code et votre modèle sérialisé, crée un utilisateur non-root, et définit la commande de démarrage. Pour les modèles ML, ajoutez un .dockerignore pour exclure les fichiers lourds inutiles (datasets d’entraînement, checkpoints intermédiaires). Pour les modèles GPU, partez d’une image NVIDIA CUDA de base et utilisez --gpus all au lancement.
Quelles sont les limites de la conteneurisation ?
L’isolation des conteneurs est plus légère que celle des VM (noyau partagé, surface d’attaque plus large). Les applications legacy avec des dépendances OS profondes peuvent être difficiles à conteneuriser. La gestion du stockage persistant ajoute de la complexité. Les conteneurs GPU nécessitent un toolkit spécifique (NVIDIA Container Toolkit). Et la sécurité des images nécessite un scanning régulier des vulnérabilités dans les couches de base. Malgré ces limites, les bénéfices (portabilité, reproductibilité, densité, vitesse de déploiement) sont quasi-universellement reconnus.