Maîtriser Docker et Kubernetes : Déploiement et Scalabilité d'Applications Modernes
Maîtriser Docker et Kubernetes : Déploiement et Scalabilité d'Applications Modernes

Docker Compose pour les Applications Multi-Conteneurs

Bienvenue à cette leçon fondamentale du cours "Maîtriser Docker et Kubernetes : Déploiement et Scalabilité d'Applications Modernes". Aujourd'hui, nous allons explorer Docker Compose, un outil indispensable pour gérer des applications complexes composées de multiples conteneurs. Alors que Docker vous permet de gérer des conteneurs individuels, Docker Compose excelle dans l'orchestration de groupes de conteneurs qui fonctionnent ensemble pour former une application complète.


Introduction : Au-delà du Conteneur Unique

Dans le monde du développement moderne, les applications sont rarement monolithiques. Elles sont souvent décomposées en microservices, ou du moins en composants distincts qui communiquent entre eux : un serveur web, une base de données, un cache, une file d'attente de messages, etc. Gérer chacun de ces services comme un conteneur Docker séparé peut devenir fastidieux. Vous devriez démarrer chaque conteneur individuellement, gérer leurs réseaux, leurs volumes, et leurs dépendances. C'est là que Docker Compose entre en jeu.

Docker Compose est un outil qui simplifie la définition et l'exécution d'applications Docker multi-conteneurs. Avec un simple fichier YAML, vous pouvez configurer l'ensemble des services de votre application, spécifier leurs dépendances, leurs ports, leurs volumes et leurs réseaux, puis les démarrer tous avec une seule commande. C'est la solution idéale pour le développement local, les tests automatisés et les environnements de staging simples.


Qu'est-ce que Docker Compose ?

Docker Compose est un outil développé par Docker pour définir et exécuter des applications Docker multi-conteneurs. Il utilise un fichier de configuration au format YAML pour spécifier tous les services qui composent votre application. Chaque service est un conteneur qui peut être démarré, arrêté, lié à d'autres services, etc.

En substance, Docker Compose vous permet de :

  • Définir votre application en un seul fichier (généralement docker-compose.yml).
  • Isoler les environnements pour chaque service.
  • Simplifier le démarrage et l'arrêt de l'ensemble de l'application.
  • Faciliter la collaboration en assurant que tous les développeurs travaillent avec le même environnement configuré.

Pourquoi utiliser Docker Compose ?

L'utilisation de Docker Compose apporte de nombreux avantages, en particulier pour les développeurs et les équipes travaillant sur des applications distribuées :

  1. Environnements Cohérents et Reproductibles : Fini le "ça marche sur ma machine !". Docker Compose garantit que chaque membre de l'équipe utilise exactement le même environnement de développement que celui défini dans le fichier docker-compose.yml. C'est un pas géant vers la reproductibilité.
  2. Démarrage Facilité : Au lieu de multiples commandes docker run, une simple commande docker-compose up suffit pour démarrer l'intégralité de votre application multi-services, y compris la création des réseaux et des volumes nécessaires.
  3. Gestion Simplifiée des Dépendances : Vous pouvez spécifier l'ordre de démarrage des services et les relations entre eux (par exemple, le service web dépend de la base de données).
  4. Isolation des Services : Chaque service est encapsulé dans son propre conteneur, ce qui garantit l'isolation des dépendances logicielles et réduit les conflits.
  5. Configuration Centralisée : Toutes les configurations (ports, volumes, variables d'environnement, réseaux) sont définies dans un seul fichier facile à lire et à versionner.
  6. Idéal pour le Développement et le Staging : Bien que ce ne soit pas un orchestrateur de production à grande échelle comme Kubernetes, Docker Compose est parfait pour simuler un environnement de production sur une machine locale ou un serveur de staging.

Concepts Clés de Docker Compose

Pour comprendre Docker Compose, il est essentiel de maîtriser quelques concepts fondamentaux qui sont définis dans le fichier docker-compose.yml.

Services

Un service dans Docker Compose est un conteneur (ou un groupe de conteneurs d'une même image) qui exécute une partie de votre application. Chaque service est défini avec une image Docker spécifique (ou un Dockerfile à construire), des ports exposés, des volumes montés, des variables d'environnement, etc.

  • Exemple : Votre application web, votre base de données, un cache Redis sont tous des services distincts.

Réseaux

Docker Compose crée par défaut un réseau par défaut pour votre application, permettant à tous les services de communiquer entre eux via leurs noms de service. Par exemple, si vous avez un service nommé db, votre service web peut se connecter à la base de données en utilisant l'hôte db.

Vous pouvez aussi définir des réseaux personnalisés pour organiser la communication de manière plus granulaire ou pour connecter des services entre différentes applications Compose.

  • Avantage : Simplifie la découverte de service (pas besoin de connaître les adresses IP).

Volumes

Les volumes sont utilisés pour persister les données générées et utilisées par les conteneurs. Les données stockées dans des volumes survivent à l'arrêt, au redémarrage ou à la suppression des conteneurs.

  • Types de volumes :

    • Volumes nommés (Named Volumes) : Gérés par Docker, ils sont le moyen préféré pour persister les données.
    • Montages de bind (Bind Mounts) : Permettent de mapper un chemin du système de fichiers de l'hôte directement dans le conteneur. Idéal pour le développement, car les modifications de code sur l'hôte sont immédiatement reflétées dans le conteneur.
  • Exemple : Persister les données de votre base de données, ou monter votre code source dans le conteneur de votre application web pour un rechargement à chaud.


Le Fichier docker-compose.yml

Le cœur de Docker Compose est son fichier de configuration, docker-compose.yml (ou docker-compose.yaml). Ce fichier est écrit en YAML et définit la structure de votre application multi-conteneurs.

Voici la structure de base des sections les plus courantes :

version: '3.8' # La version de la syntaxe Docker Compose. Toujours utiliser la dernière stable.
services: # La section où vous définissez tous vos conteneurs/services.
  <nom_du_service_1>:
    image: <nom_image>:<tag> # Ou 'build: .' pour construire à partir d'un Dockerfile local.
    ports: # Mapper les ports de l'hôte vers le conteneur.
      - "8000:80" # Hôte:Conteneur
    volumes: # Monter des volumes ou des bind mounts.
      - ./app:/usr/src/app # Hôte:Conteneur (bind mount)
      - db_data:/var/lib/postgresql/data # Volume nommé:Conteneur
    environment: # Variables d'environnement passées au conteneur.
      - DATABASE_URL=postgres://user:password@db:5432/mydb
    depends_on: # Définir les dépendances de démarrage (le service 'db' doit démarrer avant celui-ci).
      - <nom_du_service_dependant>
    networks: # Attacher le service à des réseaux spécifiques.
      - <nom_du_reseau>
  <nom_du_service_2>:
    # ... autres configurations
volumes: # Section pour définir les volumes nommés.
  db_data:
networks: # Section pour définir les réseaux personnalisés.
  app_network:
    driver: bridge

Explications des Directives Clés :

  • version: Indique la version du fichier Compose que vous utilisez. La version 3.8 (ou la plus récente) est recommandée pour bénéficier des dernières fonctionnalités.
  • services: C'est ici que vous définissez chaque composant de votre application. Chaque clé sous services est le nom d'un service (ex: web, db, redis).
    • image ou build:
      • image: Spécifie l'image Docker à utiliser pour le service (ex: nginx:latest, postgres:13). Compose tentera de la télécharger si elle n'est pas déjà présente.
      • build: Indique à Compose de construire une image à partir d'un Dockerfile spécifié. build: . signifie que le Dockerfile se trouve dans le répertoire courant. Vous pouvez aussi spécifier context: ./path/to/dir et dockerfile: Dockerfile.prod.
    • ports: Mappe les ports du conteneur aux ports de la machine hôte. "<port_hôte>:<port_conteneur>". Le port hôte peut être omis ("80") pour un port dynamique.
    • volumes: Monte des volumes persistants ou des bind mounts.
      • ./app:/usr/src/app: Un bind mount. Le répertoire ./app de votre machine hôte est monté dans /usr/src/app du conteneur. Utile pour le code source.
      • db_data:/var/lib/postgresql/data: Un volume nommé. db_data est le nom du volume défini dans la section volumes plus bas.
    • environment: Définit des variables d'environnement passées au conteneur. Utile pour les identifiants de base de données, les clés API, etc.
    • depends_on: Spécifie les dépendances entre les services. Par exemple, le service web dépend du service db. Docker Compose démarrera db avant web. Attention : depends_on garantit uniquement que le conteneur du service dépendant est démarré, pas que l'application à l'intérieur de ce conteneur est entièrement prête et opérationnelle (par exemple, une base de données peut prendre du temps à initialiser). Pour une véritable "attente de service", vous devrez utiliser des scripts de santé ou des outils comme wait-for-it.sh à l'intérieur de votre conteneur.
    • networks: Connecte un service à un ou plusieurs réseaux définis dans la section networks.
  • volumes: Définit les volumes nommés qui peuvent être réutilisés par différents services. Docker gérera le stockage de ces volumes.
  • networks: Définit les réseaux personnalisés. Par défaut, Docker Compose crée un réseau bridge pour l'application. Vous pouvez en définir d'autres si nécessaire.

Exemple Pratique : Application Web avec Base de Données

Déployons une application web simple avec une base de données PostgreSQL en utilisant Docker Compose. Notre application sera une petite API Flask en Python.

Scénario :

  • Service web : Une application Flask (Python) qui interagit avec la base de données.
  • Service db : Une base de données PostgreSQL.

Structure du projet :

.
├── docker-compose.yml
├── web/
│   ├── Dockerfile
│   ├── app.py
│   └── requirements.txt
└── .env

1. Fichiers de l'Application Web (web/)

web/Dockerfile : Pour construire l'image de notre application Flask.

# web/Dockerfile
FROM python:3.9-slim-buster

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

CMD ["python", "app.py"]

web/requirements.txt : Dépendances Python.

# web/requirements.txt
Flask
psycopg2-binary

web/app.py : L'application Flask simple.

# web/app.py
import os
from flask import Flask
import psycopg2

app = Flask(__name__)

# Récupérer les variables d'environnement
DB_HOST = os.getenv('DB_HOST', 'db') # 'db' est le nom du service PostgreSQL dans docker-compose.yml
DB_NAME = os.getenv('DB_NAME', 'mydatabase')
DB_USER = os.getenv('DB_USER', 'myuser')
DB_PASSWORD = os.getenv('DB_PASSWORD', 'mypassword')

def get_db_connection():
    conn = None
    try:
        conn = psycopg2.connect(
            host=DB_HOST,
            database=DB_NAME,
            user=DB_USER,
            password=DB_PASSWORD
        )
        return conn
    except Exception as e:
        print(f"Erreur de connexion à la base de données : {e}")
        return None

@app.route('/')
def hello():
    return "Bonjour depuis l'application Flask !"

@app.route('/test-db')
def test_db():
    conn = get_db_connection()
    if conn:
        cursor = conn.cursor()
        cursor.execute('SELECT 1;')
        result = cursor.fetchone()
        conn.close()
        return f"Connexion à la base de données réussie : {result}"
    else:
        return "Échec de la connexion à la base de données."

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

2. Variables d'Environnement (.env)

.env : Pour définir les variables d'environnement sensibles ou spécifiques à l'environnement.

# .env
POSTGRES_DB=mydatabase
POSTGRES_USER=myuser
POSTGRES_PASSWORD=mypassword

Docker Compose lira automatiquement ce fichier .env si il est présent dans le même répertoire que docker-compose.yml.

3. Le Fichier docker-compose.yml

C'est le fichier clé qui orchestre nos deux services.

# docker-compose.yml
version: '3.8'

services:
  web:
    build: ./web # Construire l'image à partir du Dockerfile dans le répertoire './web'
    ports:
      - "5000:5000" # Mapper le port 5000 de l'hôte au port 5000 du conteneur web
    volumes:
      - ./web:/app # Monter le répertoire local 'web' dans '/app' du conteneur pour le développement
    environment:
      # Passer les variables d'environnement de .env au service web
      DB_HOST: db # Le nom du service de la base de données est 'db'
      DB_NAME: ${POSTGRES_DB}
      DB_USER: ${POSTGRES_USER}
      DB_PASSWORD: ${POSTGRES_PASSWORD}
    depends_on:
      - db # S'assurer que le service 'db' est démarré avant 'web'

  db:
    image: postgres:13 # Utiliser l'image officielle PostgreSQL version 13
    environment:
      # Utiliser les variables d'environnement du fichier .env
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - db_data:/var/lib/postgresql/data # Persister les données de la base de données dans un volume nommé

volumes:
  db_data: # Définition du volume nommé 'db_data'

Explication Détaillée du docker-compose.yml :

  1. version: '3.8': Nous utilisons la version 3.8 de la spécification Compose.
  2. services::
    • web::
      • build: ./web: Compose va construire une image Docker pour ce service en utilisant le Dockerfile situé dans le sous-répertoire web.
      • ports: - "5000:5000": Le port 5000 de notre machine hôte est mappé au port 5000 du conteneur web. Ainsi, vous pourrez accéder à l'application via http://localhost:5000.
      • volumes: - ./web:/app: C'est un bind mount. Le répertoire web de notre projet local est monté dans /app à l'intérieur du conteneur. Cela signifie que si vous modifiez app.py sur votre machine, la modification sera instantanément visible dans le conteneur (si votre application est configurée pour recharger à chaud, comme Flask en mode debug).
      • environment:: Définit les variables d'environnement que l'application Flask utilisera pour se connecter à la base de données. Remarquez que DB_HOST est db, qui est le nom du service de base de données dans notre fichier Compose. Compose gère la résolution DNS des noms de service au sein de son réseau interne. Les autres variables (DB_NAME, DB_USER, DB_PASSWORD) sont lues à partir du fichier .env grâce à la syntaxe ${VARIABLE_NAME}.
      • depends_on: - db: Indique à Compose de démarrer le service db avant le service web.
    • db::
      • image: postgres:13: Utilise l'image officielle de PostgreSQL version 13 depuis Docker Hub. Compose la téléchargera si elle n'est pas déjà présente.
      • environment:: Définit les variables d'environnement spécifiques à PostgreSQL pour initialiser la base de données. Celles-ci sont également lues depuis le fichier .env.
      • volumes: - db_data:/var/lib/postgresql/data: Monte le volume nommé db_data dans le chemin par défaut où PostgreSQL stocke ses données. Cela assure que les données de votre base de données persistent même si le conteneur db est supprimé et recréé.
  3. volumes::
    • db_data:: Déclare un volume nommé appelé db_data. Docker créera et gérera ce volume pour nous, garantissant la persistance des données.

Commandes Docker Compose Essentielles

Maintenant que vous avez un fichier docker-compose.yml, voyons comment interagir avec votre application.

Pour exécuter ces commandes, assurez-vous d'être dans le même répertoire que votre fichier docker-compose.yml.

  1. docker-compose up

    • Description : Construit (si nécessaire), crée, démarre et attache les conteneurs définis dans votre docker-compose.yml. C'est la commande la plus utilisée pour lancer votre application.
    • Variantes :
      • docker-compose up: Démarre les services en arrière-plan (mode détaché).
      • docker-compose up --build: Force la reconstruction des images des services avant de les démarrer. Utile après des modifications du Dockerfile.
      • docker-compose up -d: Démarre les services en mode détaché (en arrière-plan), ce qui libère votre terminal.
    # Démarre les services en mode détaché
    docker-compose up -d
    
  2. docker-compose ps

    • Description : Liste tous les services en cours d'exécution de votre application Compose, avec leur statut et les ports mappés.
    docker-compose ps
    
  3. docker-compose logs [service_name]

    • Description : Affiche les logs des services. Vous pouvez spécifier un nom de service pour voir les logs d'un conteneur spécifique.
    • Variantes :
      • docker-compose logs: Affiche les logs de tous les services.
      • docker-compose logs -f [service_name]: Suit les logs en temps réel (comme tail -f).
    # Affiche les logs du service web
    docker-compose logs web
    
    # Suit les logs de tous les services en temps réel
    docker-compose logs -f
    
  4. docker-compose exec [service_name] [command]

    • Description : Exécute une commande à l'intérieur d'un conteneur de service en cours d'exécution. Utile pour le débogage ou pour interagir avec un service.
    # Exécute un shell bash dans le conteneur web
    docker-compose exec web bash
    
    # Connecte au terminal PostgreSQL via psql
    docker-compose exec db psql -U myuser mydatabase
    
  5. docker-compose stop [service_name]

    • Description : Arrête les conteneurs d'un ou de tous les services sans les supprimer. Les conteneurs peuvent être redémarrés plus tard avec docker-compose start.
    # Arrête le service web
    docker-compose stop web
    
  6. docker-compose start [service_name]

    • Description : Démarre les conteneurs précédemment arrêtés.
    # Démarre le service web
    docker-compose start web
    
  7. docker-compose down

    • Description : Arrête et supprime les conteneurs, les réseaux et les volumes par défaut créés par docker-compose up.
    • Variantes :
      • docker-compose down: Supprime les conteneurs et les réseaux.
      • docker-compose down --volumes (ou -v): Supprime également les volumes nommés (ceux déclarés dans la section volumes: du fichier Compose). Attention : Cela supprimera toutes les données persistantes, utilisez avec prudence.
    # Arrête et supprime les services, les réseaux. Conserve les volumes nommés.
    docker-compose down
    
    # Arrête et supprime TOUT, y compris les volumes (attention aux données !)
    docker-compose down -v
    
  8. docker-compose build [service_name]

    • Description : Reconstruit les images pour les services qui ont une directive build. Utile si vous avez modifié un Dockerfile ou les fichiers de contexte de construction.
    # Reconstruit l'image du service web
    docker-compose build web
    

Bonnes Pratiques et Astuces

  • Utilisez les versions de services spécifiques : Toujours spécifier la version exacte des images (ex: postgres:13 au lieu de postgres:latest) pour assurer la reproductibilité.
  • Fichiers .env pour les secrets : N'incorporez jamais d'informations sensibles (mots de passe, clés API) directement dans docker-compose.yml. Utilisez un fichier .env ou des variables d'environnement du système d'exploitation. Docker Compose chargera automatiquement les variables d'un fichier .env situé dans le même répertoire que le docker-compose.yml.
  • Volumes nommés pour la persistance : Privilégiez les volumes nommés (volumes: db_data:) plutôt que les bind mounts pour les données de production/staging, car ils sont gérés par Docker et plus performants.
  • Bind mounts pour le développement : Les bind mounts (volumes: ./src:/app) sont excellents en développement car ils reflètent immédiatement les changements de code de l'hôte dans le conteneur.
  • Environnements multiples avec extends ou plusieurs fichiers :
    • Utilisez plusieurs fichiers docker-compose.yml (ex: docker-compose.yml pour la base, docker-compose.override.yml pour les spécificités de développement) et la commande docker-compose -f file1.yml -f file2.yml up. Docker Compose fusionne automatiquement docker-compose.override.yml.
    • Utilisez la directive extends pour réutiliser des configurations de services.
  • Santé des conteneurs : Utilisez healthcheck dans vos définitions de service pour vérifier que l'application à l'intérieur du conteneur est réellement prête, pas seulement que le conteneur est démarré. Cela aide depends_on (même si ce dernier ne l'attend pas explicitement).

Limitations et Quand Passer à l'Orchestration (Kubernetes)

Bien que Docker Compose soit un outil puissant et suffisant pour de nombreux cas d'utilisation, il a ses limites :

  • Orchestration sur un seul hôte : Docker Compose est conçu pour gérer des applications multi-conteneurs sur une seule machine ou un seul nœud Docker. Il ne fournit pas de fonctionnalités natives pour la distribution de services sur plusieurs serveurs, la haute disponibilité ou la gestion automatique de la charge.
  • Pas de haute disponibilité automatique : Si un conteneur tombe, Compose ne le redémarrera pas automatiquement sur un autre nœud.
  • Mise à l'échelle limitée : Bien que vous puissiez augmenter le nombre d'instances d'un service avec docker-compose up --scale service=N, cela se fait sur un seul hôte et ne gère pas la répartition de charge avancée ou la détection de défaillance.
  • Pas de rolling updates : La mise à jour de votre application implique souvent un temps d'arrêt.

Pour les applications en production nécessitant une scalabilité horizontale, une haute disponibilité, des mises à jour sans interruption (rolling updates), une résilience et une gestion automatisée de l'infrastructure, vous devrez vous tourner vers des orchestrateurs de conteneurs plus avancés comme Docker Swarm ou, plus communément, Kubernetes.

Kubernetes est le standard de l'industrie pour l'orchestration de conteneurs à grande échelle et sera le sujet de nos prochaines leçons. Docker Compose sert souvent de tremplin, permettant de prototyper et de tester des architectures de microservices avant de les déployer dans un cluster Kubernetes.


Conclusion

Docker Compose est un outil essentiel pour tout développeur travaillant avec Docker. Il transforme la tâche complexe de la gestion d'applications multi-conteneurs en une expérience simple et reproductible grâce à un fichier docker-compose.yml intuitif.

Nous avons appris :

  • Ce qu'est Docker Compose et pourquoi il est crucial pour les applications modernes.
  • Les concepts fondamentaux comme les services, les réseaux et les volumes.
  • La structure détaillée d'un fichier docker-compose.yml et ses directives clés.
  • Un exemple pratique complet avec une application web Flask et une base de données PostgreSQL.
  • Les commandes Docker Compose les plus utilisées pour gérer votre application.
  • Les bonnes pratiques et les limitations de l'outil, soulignant son rôle en développement et le besoin d'orchestrateurs comme Kubernetes pour la production à grande échelle.

Maîtriser Docker Compose vous donnera une base solide pour développer et tester des architectures distribuées, et vous préparera parfaitement aux défis de l'orchestration avancée avec Kubernetes.