Module 05 · Docker DevOps · Chapitre 1

1 Architecture Docker, isolation et microservices

Comprendre pourquoi les conteneurs existent, comment Docker fonctionne et quels problèmes résolvent les principaux patterns d’architecture.

Position dans le parcours : ce chapitre explique les concepts.
Les installations, commandes et manipulations sont traitées dans les TP guidés.

1.1 Pourquoi isoler une application ?

Une application dépend de son code, de bibliothèques, de paramètres et de ressources du système.

Problèmes potentiels :

  1. Lorsque plusieurs applications partagent une machine, leurs dépendances peuvent entrer en conflit.
  2. Le passage du poste de développement au serveur peut également révéler des différences d’environnement.

L’isolation vise à limiter ces interactions, et bien qu’elle rende le déploiement plus prévisible, elle ne garantit pas à elle seule la sécurité, la disponibilité ou l’absence de conflits.

Question à se poser : qu’est-ce qui doit être commun entre les applications, et qu’est-ce qui doit rester indépendant ?

Pourquoi utiliser Docker ?

Docker permet d’empaqueter une application avec les dépendances nécessaires à son exécution dans une image, puis de la lancer dans un conteneur isolé.
On obtient ainsi un environnement plus reproductible entre le développement, les tests et la production, tout en facilitant le déploiement et la gestion des applications.

1.2 Monolithe et microservices

  • Le monolithe regroupe plusieurs fonctions métier dans une application déployée comme un ensemble.
  • Les microservices répartissent les responsabilités entre des services autonomes qui communiquent au moyen d’interfaces définies, souvent des API.
Critère Monolithe Microservices
Déploiement Généralement coordonné pour l’ensemble Potentiellement indépendant pour chaque service
Communication interne Souvent appels au sein du même processus Appels réseau, événements ou messages
Évolution Architecture souvent plus simple au départ Évolution ciblée, avec davantage de coordination
Exploitation Moins de composants distribués Surveillance, réseau et diagnostic distribués

Les microservices offrent une liberté de choix technologique et la possibilité de déployer indépendamment certains composants.

En contrepartie, ils complexifient les échanges, la cohérence des données, l’observabilité et les opérations.

Docker n’impose pas les microservices : un monolithe peut être conteneurisé, et un microservice peut s’exécuter sans Docker.

1.3 Virtualisation et conteneurisation

En bref :

  • Une machine virtuelle (VM) exécute son propre système d’exploitation installé au-dessus d’une couche de virtualisation.
  • Un conteneur Linux est constitué de processus isolés qui partagent le noyau Linux du système hôte avec les autres conteneurs fonctionnant sur ce même noyau.

Ce partage du noyau est une différence fondamentale avec la virtualisation classique.
Il faut le garder à l’esprit pour la sécurité : une isolation entre conteneurs ne signifie pas que chacun possède son propre noyau.

Machine virtuelle                  Conteneurs Linux
┌──────────────────────┐           ┌──────────────────────┐
│ Application          │           │ App A   │ App B      │
│ Bibliothèques        │           │ Dépend. │ Dépend.    │
│ OS invité + noyau    │           ├─────────┴────────────┤
├──────────────────────┤           │ Docker Engine        │
│ Hyperviseur          │           ├──────────────────────┤
│ Hôte / matériel      │           │ Noyau Linux de l’hôte│
└──────────────────────┘           └──────────────────────┘
          

La comparaison décrit les principes, pas toutes les implémentations.

Bonus — Windows : Sur Windows, des conteneurs Linux peuvent s’exécuter grâce à un environnement Linux virtualisé, notamment WSL 2.

1.4 Comment Linux isole les conteneurs

1.4.1 Contexte Linux

Quand une application fonctionne, Linux exécute un ou plusieurs processus : ce sont ses programmes en cours d’exécution.
Le noyau est la partie du système qui leur donne accès au processeur, à la mémoire, aux fichiers et au réseau.

Deux applications lancées sur le même Linux utilisent donc le même noyau et les ressources de la même machine.
Un conteneur Linux ne démarre pas un nouveau noyau comme une machine virtuelle.

Pour séparer ces applications, deux questions se posent :

  • Quelles ressources du système chaque application peut-elle voir ?
    → Les namespaces séparent les vues des ressources.
  • Quelle quantité de ressources chaque application peut-elle consommer ?
    → Les cgroups participent au suivi et au contrôle de cette consommation.

1.4.2 Les namespaces

Pourquoi séparer ce que voient les processus ?

Sur une machine Linux, plusieurs applications utilisent le même noyau. Pour faire fonctionner une application dans un conteneur, on veut lui donner son propre environnement : elle ne doit pas voir directement tous les processus ou toutes les interfaces réseau de la machine.

Un namespace Linux est un mécanisme du noyau qui donne à un groupe de processus une vue séparée de certaines ressources du système.

Les ressources ne sont pas toutes copiées dans une nouvelle machine. C’est leur visibilité qui est organisée : un processus voit les ressources accessibles dans ses namespaces.

Un exemple : le même processus, deux numéros

Le numéro d’un processus est appelé PID. Avec un namespace de processus, le processus principal d’un conteneur peut avoir le PID 1 dans le conteneur et un autre PID sur l’hôte.

Deux conteneurs partagent le noyau Linux avec des vues séparées des processusL’hôte voit les processus 2401 et 3108. Dans leurs namespaces respectifs, chacun est vu comme le processus 1. Ces numéros sont des exemples.Hôte Linux — vue des processus depuis l’hôteConteneur ANamespace de processus ADans A : PID 1Sur l’hôte : PID 2401Un seul et même processusConteneur BNamespace de processus BDans B : PID 1Sur l’hôte : PID 3108Un seul et même processusUn noyau Linux commun
Exemple avec des PID fictifs et des conteneurs ayant chacun leur namespace de processus. L’hôte voit les deux ; chaque conteneur voit ses propres processus.

Le PID 1 de A et le PID 1 de B ne se confondent pas : ils appartiennent à deux namespaces différents. Le processus de A ne voit pas les processus de B dans sa liste de processus.

Docker combine plusieurs namespaces

Le mécanisme existe aussi pour d’autres ressources. Docker en combine plusieurs pour construire l’environnement d’un conteneur.

TypeCe qui possède une vue séparée
PID — processusLa liste des processus et leurs numéros.
Network — réseauLes interfaces, les adresses IP, les routes et les ports.
Mount — montagesLes points de montage visibles dans le système de fichiers.
UTS — nom de machineLe nom de machine visible par les processus.

Ce tableau présente les principaux exemples. La séparation dépend de la configuration : Docker permet aussi de partager certains namespaces.

À retenir

Un namespace définit ce qu’un processus voit d’une catégorie de ressources. Un conteneur combine plusieurs de ces vues séparées tout en partageant le noyau Linux de l’hôte.

Les cgroups servent, eux, à comptabiliser et limiter les ressources consommées, comme la mémoire et le CPU.

1.4.3 Le cgroup

Pourquoi contrôler les ressources ?

Les conteneurs partagent le processeur et la mémoire de la machine. Une application qui consomme trop de mémoire ou sollicite fortement le processeur peut donc perturber les autres applications.

Les namespaces séparent les vues des ressources, mais cette séparation ne fixe pas à elle seule une limite de mémoire ou de CPU. Pour contrôler la consommation, Linux dispose d’un autre mécanisme : les cgroups.

Un cgroup (control group, ou groupe de contrôle) regroupe des processus pour mesurer et contrôler leur consommation de ressources, notamment la mémoire et le CPU.

Docker utilise ce mécanisme pour appliquer les limites configurées à un conteneur. Ces limites concernent l’ensemble de ses processus, pas chacun séparément.

Un exemple : trois processus, une limite commune

Un conteneur possède trois processus et une limite mémoire de 512 Mio. Les consommations comptabilisées dans son cgroup s’additionnent : chaque processus ne dispose pas de 512 Mio pour lui seul.

Trois processus partagent une limite mémoireExemple simplifié : les consommations de 120, 180 et 60 Mio totalisent 360 Mio dans un groupe limité à 512 Mio. Conteneur — processus regroupés dans un cgroup Processus A120 MioProcessus B180 MioProcessus C60 Mio Consommation commune : 360 Mio sur une limite de 512 Mio 120 + 180 + 60 = 360 MioLa limite s’applique au groupe, pas à chaque processus.
Illustration simplifiée : le cgroup comptabilise aussi d’autres consommations mémoire, comme certains caches. Les valeurs servent ici à comprendre la limite commune.

Que se passe-t-il lorsqu’une limite est atteinte ?

RessourceEffet de la limite
CPUAvec un quota CPU, les processus sont temporairement ralentis : une fois le quota consommé, ils attendent la période suivante pour reprendre. Ils ne sont pas tués pour ce seul dépassement.
MémoireLinux tente de récupérer de la mémoire. Si cela ne suffit pas à respecter la limite, il peut tuer un ou plusieurs processus du groupe : c’est une situation de manque de mémoire, appelée OOM (Out Of Memory).

Une limite n’est pas une réservation : fixer un plafond de 512 Mio ne signifie pas que 512 Mio sont immédiatement occupés ou garantis au conteneur. Le comportement dépend aussi des réglages, notamment de l’utilisation du swap.

À retenir

Un cgroup permet de comptabiliser et contrôler les ressources consommées par un groupe de processus. Docker s’en sert notamment pour appliquer les limites de mémoire et de CPU d’un conteneur.

1.4.4 Pourquoi Docker utilise les deux

Un besoin : faire cohabiter plusieurs applications

Imaginons deux sites web, chacun exécuté par NGINX dans son conteneur. Ils tournent sur la même machine Linux et partagent son noyau, son processeur et sa mémoire.

Pour les faire cohabiter, il faut répondre à deux questions : quel environnement chaque application voit-elle ? et combien de ressources peut-elle consommer ?

Deux mécanismes pour deux besoins

  1. Les namespaces construisent des vues séparées. Dans cet exemple, chaque conteneur possède son namespace de processus et son namespace réseau. Chacun peut donc avoir un PID 1 et écouter sur son port interne 80 sans se confondre avec l’autre.
  2. Les cgroups contrôlent la consommation. Nous configurons une limite mémoire de 512 Mio pour le conteneur A et de 256 Mio pour le conteneur B. Chaque limite s’applique à l’ensemble des processus de son conteneur.
Docker combine namespaces et cgroupsDeux conteneurs ont chacun leur vue des processus et du réseau, ainsi qu’une limite mémoire propre. Ils partagent le même noyau Linux. Une même machine Linux, deux conteneurs Conteneur A — NGINXVue des processus A : PID 1…Vue réseau A : port interne 80Cgroup A : limite de 512 MioPour l’ensemble des processus de A Conteneur B — NGINXVue des processus B : PID 1…Vue réseau B : port interne 80Cgroup B : limite de 256 MioPour l’ensemble des processus de B Un noyau Linux commun applique les deux mécanismes
Exemple avec des namespaces séparés et des limites mémoire explicitement configurées. Les valeurs sont illustratives.

Si A consomme trop de mémoire, sa limite permet au noyau d’agir sur les processus de son groupe. C’est le cgroup qui intervient ; la séparation de ses processus et de son réseau provient des namespaces.

Pour accéder aux sites depuis l’extérieur, il faudra aussi configurer leur accès réseau, par exemple en publiant leurs ports sur des ports différents de l’hôte. Deux ports internes 80 ne signifient pas deux publications sur le même port de l’hôte.

À retenir

Docker combine les namespaces pour séparer les vues des ressources et les cgroups pour comptabiliser et contrôler leur consommation. Les limites de cet exemple doivent être configurées : créer un conteneur ne lui attribue pas automatiquement ces plafonds.

Les permissions et les mécanismes de sécurité complètent cette isolation. Le noyau reste partagé : un conteneur ne possède pas son propre noyau comme une machine virtuelle.

1.4.5 Vérifier ce que l’on a compris

Répondez à chaque question avec vos propres mots, puis ouvrez la réponse pour vérifier votre compréhension.

1. À quoi sert un namespace Linux ?

Afficher la réponse

Il donne à un groupe de processus une vue séparée de certaines ressources du système. Par exemple, un namespace de processus permet à un conteneur de voir ses propres processus sans voir ceux des autres conteneurs.

2. Deux conteneurs affichent chacun un processus PID 1. Est-ce le même processus ?

Afficher la réponse

Non. Chaque namespace de processus possède sa propre numérotation. Deux processus différents peuvent donc avoir le PID 1 dans leurs conteneurs respectifs, tout en ayant des PID différents sur l’hôte.

3. Trois processus d’un conteneur consomment 100, 150 et 50 Mio. Le conteneur est limité à 512 Mio : combien consomment-ils ensemble, et chacun dispose-t-il de 512 Mio ?

Afficher la réponse

Ils consomment ensemble 300 Mio dans cet exemple simplifié. Les 512 Mio constituent une limite commune, appliquée par le cgroup à l’ensemble du groupe, et non une limite accordée à chaque processus.

4. Un conteneur possède son propre réseau. Cela empêche-t-il son application de consommer trop de mémoire ?

Afficher la réponse

Non. Le namespace réseau sépare la vue du réseau ; il ne fixe pas de plafond mémoire. Il faut configurer une limite mémoire, que Linux applique à l’aide des cgroups.

5. Que peut-il se passer lorsqu’un conteneur atteint son quota CPU ou sa limite mémoire ?

Afficher la réponse

Lorsque le quota CPU est consommé, les processus attendent avant de pouvoir reprendre. Pour la mémoire, Linux tente d’en récupérer ; si cela ne suffit pas à respecter la limite, il peut tuer un ou plusieurs processus du groupe.

6. Pourquoi Docker combine-t-il namespaces et cgroups ? Crée-t-il un noyau Linux pour chaque conteneur ?

Afficher la réponse

Les namespaces séparent les vues des ressources ; les cgroups comptabilisent et contrôlent leur consommation. Ces fonctions se complètent. Les conteneurs Linux continuent de partager le noyau Linux de leur hôte.

1.5 Les composants de Docker

Docker adopte une architecture client/serveur :

  • Le client transmet des demandes au daemon Docker ( dockerd ), qui gère les images, conteneurs, réseaux et volumes.
  • Les images peuvent provenir d’un registre (public ou privé).
Utilisateur
    │
    ▼
Client Docker (CLI, interface, SDK)
    │   API Docker / socket ou connexion distante
    ▼
Docker Engine — daemon dockerd
    ├── Images ──────────────── Registre d’images
    ├── Conteneurs
    ├── Réseaux
    └── Volumes
          

Une image sert de modèle en lecture seule.
Un conteneur est une instance d’une image.
Un registre distribue des images, mais ne conserve pas les données produites par les conteneurs pendant leur exécution : celles-ci relèvent du stockage des conteneurs et des volumes.

Les chapitres 3 à 6 détailleront ces notions.

1.6 Patterns : service principal, sidecar et initialisation

Dans une application distribuée, certains composants ont un rôle métier et d’autres remplissent une fonction auxiliaire ou préparatoire.

Ces patterns d’architecture aident à reconnaître les responsabilités des conteneurs.

Pattern Intention Exemple
Conteneur applicatif Exécuter la fonction principale Serveur web ou API métier
Sidecar Accompagner l’application pendant son fonctionnement Collecte de logs, proxy local ou adaptation de protocole
Init container Préparer l’environnement avant le démarrage du service principal Génération de configuration, attente d’un prérequis ou initialisation de fichiers
      Phase de préparation
       [ Init container ]
                │ fin réussie
                ▼
        ┌──────────────────┐
        │ Application      │◄──►[ Sidecar ]
        └──────────────────┘
           fonctionnement
          

Un sidecar est un rôle architectural : il accompagne une application en assurant une fonction auxiliaire, comme la collecte de logs ou un proxy. Son fonctionnement dépend des besoins de l’application et des mécanismes offerts par l’environnement d’exécution.

Un init container est un conteneur chargé de préparer ou de vérifier les conditions nécessaires au démarrage d’une application. Ce modèle est notamment utilisé dans Kubernetes.

Lien avec Docker Compose

Un service de préparation peut exécuter une tâche avant le lancement de l’application. Compose peut attendre sa réussite avec depends_on et la condition service_completed_successfully, ou attendre qu’un service soit prêt grâce à service_healthy et un healthcheck.
On retrouve ainsi l’objectif d’un init container, sans que Compose fournisse exactement le même objet ni les mêmes garanties de cycle de vie que Kubernetes.

Pourquoi apprendre ces architectures ?

Parce que tu peux les rencontrer dans une application existante, ou en avoir besoin pour séparer les responsabilités.

Ces principes se retrouvent dans divers environnements de conteneurs et services cloud, notamment Azure et AWS.

Les outils et garanties d’exécution varient : l’objectif ici est de reconnaître le besoin architectural, pas d’apprendre Kubernetes.

1.7 Exemple : une application web et ses services

Imaginons une application de réservation comprenant :

  • Un service web : reçoit les requêtes HTTP ;
  • Une API : applique les règles métier ;
  • Une base de données : conserve les réservations ;
  • Un composant de collecte : transmet les journaux à un système de supervision.
Schéma du chapitre 1
Quatre conteneurs : Web, API, base de données et monitoring. Les flèches bleues représentent les échanges applicatifs ; les flèches pointillées représentent la collecte des logs.

1.7.1 Expliquons ce qui se passe dans notre architecture

  1. Le navigateur reçoit une page HTML du service Web.
  2. Le service Web échange des données JSON avec l’API.
  3. L’API interroge la base de données pour lire ou enregistrer les réservations.
  4. Le composant de monitoring collecte les journaux de l’API et de la base de données.
    Il ne participe pas directement au traitement d’une réservation.
Le schéma illustre les responsabilités logiques : la collecte réelle des logs dépendra de la configuration et des mécanismes retenus.

1.7.2 Les besoins techniques de notre architecture Docker

Pour faire fonctionner cette architecture, plusieurs éléments sont à prendre en compte :

  • Le service web et l’API peuvent être déployés dans des conteneurs distincts.
  • La base de données a besoin d’un stockage persistant.
  • Le composant de collecte peut jouer un rôle auxiliaire de type sidecar selon l’architecture retenue.

Cette séparation rend les responsabilités visibles, mais elle implique des communications réseau, une gestion des accès et une stratégie de diagnostic.

Le TP de déploiement multicon­teneurs mettra ces mécanismes en pratique après l’étude de Compose.

1.8 Ce qu’il faut retenir

À retenir :
  • La conteneurisation isole des processus et leurs dépendances, tout en partageant le noyau Linux ;
  • Monolithe et microservices sont des choix d’architecture indépendants de Docker ;
  • Docker Engine gère les ressources, tandis que les clients lui adressent des demandes ;
  • Sidecar et initialisation répondent à des besoins d’accompagnement et de préparation ;
  • La séparation des services facilite certains déploiements, mais ajoute des enjeux réseau, sécurité et exploitation.