7.1 a - Présentation
Docker Compose est un outil qui permet d'exécuter un ensemble de conteneurs Docker en même temps. Si une application doit utiliser plusieurs conteneurs, lancer tous ces conteneurs d'un coup est très pratique. Une des utilisations courantes de Docker Compose est de créer des environnements de test : par exemple, si l'on doit tester un script qui doit interagir avec une base de données, on peut lancer un ensemble de conteneurs qui constitueront l'ensemble de test. En particulier, Docker Compose n'est pas utilisé pour créer un ensemble de conteneurs sur différentes machines, mais sur une seule machine. Docker Compose utilise un fichier appelé, par défaut, docker-compose.yaml afin de décrire l'ensemble des éléments à déployer.
7.2 b - Comprendre les mots-clés dans un fichier docker-compose.yaml
Le fichier docker-compose.yaml est utilisé pour décrire une application multi-conteneurs dans Docker Compose. Il permet de spécifier les services, les volumes, les réseaux et diverses configurations nécessaires à l'orchestration des conteneurs.
Docker Compose utilise un fichier YAML (docker-compose.yml) pour définir les services, les réseaux et les volumes qui composent votre application. Sa structure est facile à comprendre et hautement configurable, vous permettant de gérer plusieurs conteneurs avec un seul fichier.
Voici un aperçu des composants de base d'un fichier docker-compose.yml :
services : La section services vous permet de définir chaque conteneur qui fera partie de votre application. Chaque service est un conteneur que vous configurez avec des paramètres tels que l'image, les options de build, les variables d'environnement, les ports, etc.
Un exemple de définition d'un service Web de base :
services:
web:
image: nginx:latest
ports:
- "8080:80"
Dans ce cas, nous utilisons une image existante (nginx) de Docker Hub et exposons le port 80 du conteneur au port 8080 sur la machine hôte.
networks : Par défaut, Docker Compose crée un réseau de type bridge pour la communication entre tous les services. Cependant, vous pouvez définir des réseaux personnalisés pour mieux contrôler la communication entre les services.
networks:
my_network:
driver: bridge
Une fois qu'un réseau est défini, vous pouvez attribuer des services à ce réseau pour une meilleure isolation et un meilleur contrôle.
Volumes : La section « volumes » permet de créer et de gérer un stockage persistant indépendant du cycle de vie du conteneur. Ceci est utile lorsque vous devez conserver des données après plusieurs redémarrages du conteneur.
volumes:
my_volume:
Vous pouvez ensuite attacher ce volume à un répertoire au sein d'un conteneur pour conserver les données :
services:
db:
image: postgres
volumes:
- my_volume:/var/lib/postgresql/data
Dans cet exemple, les données de la base de données Postgres sont stockées dans le volume my_volume, garantissant que les données ne sont pas perdues lorsque le conteneur s'arrête ou redémarre.
Grâce à ces composants de base, vous pouvez déjà commencer à définir une application multi-conteneurs. La flexibilité de Docker Compose facilite la mise à l'échelle et la gestion des services à mesure que votre projet se développe.
Chaque service est défini dans la section services correspondante du fichier docker-compose.yml. La configuration la plus basique d'un service comprend la spécification d'une image ou d'une option de build, les ports à exposer et toute configuration supplémentaire spécifique au service, comme les volumes, les réseaux ou les variables d'environnement.
Nous avons ici un exemple de configuration simple avec un serveur Web et un service de base de données :
services:
web:
image: nginx:1.18.2
ports:
- "8080:80"
networks:
- app_network
db:
image: postgres:13
environment:
POSTGRES_USER: liora
POSTGRES_PASSWORD: secret
volumes:
- db_data:/var/lib/postgresql/data
networks:
- app_network
Options de configuration :
Nom du service: Le nom que vous attribuez à un service (par exemple, web, db) est important, car Docker Compose l'utilise pour la résolution DNS automatique entre les conteneurs. Les services peuvent communiquer entre eux en utilisant leurs noms comme noms d'hôte, sans avoir besoin d'adresses IP.Images: Vous pouvez utiliser des images prédéfinies de Docker Hub ou de tout autre registre en spécifiant l'option « image ». Dans l'exemple ci-dessus, le service web utilise l'imagenginxet la base de données utilise l'imagepostgres.
image: nginx:latest
Ports: L'option « Ports » expose les ports du conteneur à la machine hôte. Ceci est utile pour les services tels que les serveurs web ou les API qui doivent être accessibles en dehors du réseau Docker.
ports:
- "8080:80"
Dans ce cas, le port 80 à l'intérieur du conteneur est mappé (connecté) au port 8080 sur la machine hôte.
- Variables d'environnement : De nombreux services nécessitent des variables d'environnement pour leur configuration. Dans Docker Compose, vous pouvez facilement définir des variables d'environnement dans la section « environment ». Ceci est particulièrement utile pour configurer des bases de données ou tout autre service nécessitant une configuration externe.
environment:
POSTGRES_USER: admin
POSTGRES_PASSWORD: secret
Volumes: Les volumes permettent de conserver les données entre les redémarrages du conteneur. Dans l'exemple ci-dessus, un volume est monté pour la base de données Postgres afin de garantir la conservation des données lors de l'arrêt ou de la suppression du conteneur.
volumes:
- db_data:/var/lib/postgresql/data
networks: Les services sont attribués aux réseaux afin de gérer leurs communications. En plaçant les services Web et de base de données sur le même réseau, nous leur permettons de communiquer en utilisant les noms de service (Web, base de données) comme noms d'hôtes.
networks:
- app_network
Dépendances de service : Parfois, un service dépend d'un autre. Par exemple, dans une application web, le serveur web peut dépendre de la disponibilité de la base de données. Docker Compose permet de définir des dépendances entre services grâce à l'option « depends_on ».
services:
web:
image: nginx:latest
depends_on:
- db
ports:
- "8080:80"
db:
image: postgres:13
environment:
POSTGRES_USER: admin
POSTGRES_PASSWORD: secret
Dans cette configuration, Docker Compose garantit que le service de base de données démarre avant le service Web.
Grâce à ces options de configuration de service, vous disposez des éléments de base pour définir l'architecture de votre application. Docker Compose simplifie la gestion du cycle de vie de chaque service et de ses relations avec les autres services de la pile.
Utilisation d'un fichier .env :
Une pratique plus courante consiste à séparer les variables d'environnement du fichier docker-compose.yml à l'aide d'un fichier .env. Ce fichier contient des paires clé-valeur et permet une gestion plus claire des variables d'environnement. Docker Compose chargera automatiquement le fichier .env s'il se trouve dans le même répertoire que le fichier docker-compose.yml.
Exemple de fichier .env :
APP_ENV=production
APP_DEBUG=false
DATABASE_URL=postgres://admin:secret@db:5432/mydb
Dans le fichier docker-compose.yml, vous pouvez référencer ces variables de la façon suivante :
services:
app:
image: myapp:latest
environment:
- APP_ENV
- APP_DEBUG
- DATABASE_URL
Docker Compose remplacera automatiquement les valeurs du fichier .env lors du démarrage des services.
Utilisation des fichiers d'environnement avec l'option « env_file »_ :
Vous pouvez également charger explicitement des variables d'environnement depuis un fichier en utilisant l'option « envfile » correspondante dans le fichier docker-compose.yml. Ceci est utile lorsque vous souhaitez charger des variables depuis plusieurs fichiers ou disposer de fichiers distincts
pour différents environnements (par exemple, .env.development, .env.production).
services:
app:
image: myapp:latest
env_file:
- .env
Vous pouvez également spécifier plusieurs fichiers d'environnement :
services:
app:
image: myapp:latest
env_file:
- .env
- .env.liora
Remplacement des variables d'environnement :
Si vous définissez des variables d'environnement dans le fichier docker-compose.yml et dans le fichier .env, les variables du fichier docker-compose.yml seront prioritaires. Cela vous permet d'avoir des valeurs par défaut dans votre fichier .env tout en les remplaçant service par service.
services:
app:
image: myapp:latest
environment:
- APP_ENV=development # Overrides the value from .env
Exemple : configuration d'un service de base de données avec des variables d'environnement Voici un exemple de configuration de service de base de données utilisant des variables d'environnement :
services:
db:
image: postgres:13
environment:
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=${POSTGRES_DB}
Dans le fichier .env :
POSTGRES_USER=admin
POSTGRES_PASSWORD=secret
POSTGRES_DB=my_database
Dans cette configuration, le service Postgres utilisera les identifiants définis dans le fichier .env. Cette configuration garantit que les informations sensibles, comme les mots de passe, ne sont pas codées en dur dans le fichier docker-compose.yml.
7.2.1 Définition des réseaux personnalisés :
Bien que le réseau par défaut fonctionne dans la plupart des cas, vous pourriez souhaiter avoir davantage de contrôle sur la communication de vos services, notamment dans les configurations plus vastes ou complexes. Vous pouvez créer des réseaux personnalisés pour organiser la communication entre vos services ou pour isoler certains services des autres.
Pour définir un réseau personnalisé, utilisez la clé networks dans votre fichier docker-compose.yml et attribuez ensuite des services à ces réseaux de la façon suivante :
services:
web:
image: nginx
networks:
- frontend_network
- backend_network
db:
image: postgres
networks:
- backend_network
networks:
frontend_network:
backend_network:
Dans cet exemple, le service web est rattaché à frontend_network et backend_network, ce qui lui permet de communiquer avec les services frontend et backend. Le service de base de données est rattaché uniquement à backend_network, ce qui limite son exposition aux services internes.
7.2.2 Exposer les ports
Docker Compose permet d'exposer les ports des conteneurs à la machine hôte, rendant ainsi les services accessibles depuis l'extérieur du réseau Docker. Cela s'effectue généralement via l'option ports :
services:
web:
image: nginx
ports:
- "8080:80"
Dans cet exemple, le port 80 du conteneur Web est mappé au port 8080 de l'hôte. Le service Web est ainsi accessible depuis http://localhost:8080 sur votre machine.
7.2.3 Définition des volumes dans Docker Compose :
Vous pouvez définir des volumes dans le fichier docker-compose.yml sous la section volumes. Les volumes peuvent être nommés ou anonymes. Les volumes nommés ont des noms explicites et peuvent être réutilisés dans plusieurs services, tandis que les volumes anonymes sont générés automatiquement par Docker et n'ont pas de nom spécifique.
Voici comment définir un volume nommé :
volumes:
db_data:
Dans cet exemple, nous avons défini un volume nommé db_data qui peut être partagé entre les services.
7.2.4 Attacher des volumes aux services
Une fois un volume défini, vous pouvez l'attacher à un service grâce à l'option « volumes » sous ce service. Cela mappe un répertoire de l'hôte à un répertoire du conteneur.
Voici un exemple de connexion du volume db_data à un service PostgreSQL pour conserver les données de la base de données :
services:
db:
image: postgres:13
volumes:
- db_data:/var/lib/postgresql/data
Dans ce cas, le volume db_data est mappé à /var/lib/postgresql/data à l'intérieur du conteneur, garantissant que toutes les données stockées par PostgreSQL sont enregistrées en dehors du conteneur.
7.2.5 Lier les montages et les volumes
Il existe deux façons de conserver des données dans Docker : les volumes et les montages liés. Bien que les volumes soient gérés par Docker et constituent l'approche recommandée, les montages liés vous permettent de mapper directement les répertoires de votre machine hôte aux répertoires du conteneur.
Voici comment utiliser un montage lié dans Docker Compose :
services:
app:
image: myapp:latest
volumes:
- ./app:/usr/src/app
Dans cet exemple, le répertoire local ./app de l'hôte est monté dans /usr/src/app à l'intérieur du conteneur. Ceci est particulièrement utile dans les environnements de développement où vous souhaitez refléter les modifications en temps réel.
7.2.6 Partage des volumes entre les services
Parfois, plusieurs services ont besoin d'accéder aux mêmes données. Docker Compose permet de partager des volumes entre services, leur permettant ainsi de collaborer sur les mêmes fichiers ou ensembles de données.
Par exemple, voici comment vous pouvez partager un volume entre un service Web et un service de travail en arrière-plan :
services:
web:
image: nginx:latest
volumes:
- shared_data:/usr/share/nginx/html
worker:
image: myworker:latest
volumes:
- shared_data:/usr/src/app/data
volumes:
shared_data:
Dans cette configuration, les services Web et Worker ont tous deux accès au volume shared_data. Le service Web stocke son contenu statique dans le volume, tandis que le service Worker lit ou traite les mêmes données.
7.2.7 Persistance des données pour les bases de données
Pour les bases de données, l'utilisation de volumes est essentielle pour garantir la conservation des données lors de l'arrêt ou de la suppression des conteneurs. Voici un exemple de configuration Docker Compose pour un service MySQL qui conserve les données à l'aide d'un volume nommé :
services:
mysql:
image: mysql:8
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_DATABASE: mydatabase
volumes:
- mysql_data:/var/lib/mysql
volumes:
mysql_data:
Dans cette configuration, le volume mysql_data est utilisé pour conserver les données MySQL dans /var/lib/mysql. Cela garantit que même si le conteneur MySQL est arrêté ou recréé, les données de la base de données restent intactes.
7.2.8 Mise à l'échelle des services avec Docker Compose
L'une des fonctionnalités les plus utiles de Docker Compose est la possibilité de faire évoluer vos services horizontalement. Cela signifie que vous pouvez exécuter plusieurs instances d'un service pour gérer davantage de charge ou assurer la redondance. La mise à l'échelle est particulièrement avantageuse pour les services sans état, comme les serveurs web ou les processus de travail.
Vous pouvez faire évoluer les services en utilisant l'option --scale avec la commande docker compose up :
docker compose up --scale web=3
Cette commande démarrera trois instances du service Web. Pour assurer un équilibrage de charge correct entre les services mis à l'échelle, vous devrez peut-être configurer un équilibreur de charge (comme NGINX) ou utiliser la résolution DNS interne de type round-robin de Docker.
Alternativement, vous pouvez définir des répliques de service dans votre docker-compose.yml :
services:
web:
image: nginx:latest
deploy:
replicas: 3
7.2.9 Dépendances de service avec depends_on
Dans de nombreuses applications, certains services dépendent de la disponibilité d'autres services avant de pouvoir démarrer. Docker Compose propose l'option depends_on pour exprimer cette relation. Cela garantit que Docker démarre les services dépendants dans le bon ordre.
Voici un exemple de service Web qui dépend d'un service de base de données :
services:
web:
image: nginx
depends_on:
- db
db:
image: postgres
Notez toutefois que depends_on contrôle uniquement l'ordre de démarrage ; il n'attend pas que le service dépendant soit prêt (par exemple, que la base de données accepte les connexions). Pour une gestion plus robuste des dépendances, envisagez d'utiliser des contrôles de santé (abordés ci-dessous) ou une logique de nouvelle tentative personnalisée dans votre application.
7.2.10 Contrôles de santé pour garantir la disponibilité du service
Vous pouvez également ajouter des contrôles de santé pour vous assurer que vos services sont bien disponibles avant de permettre à Docker de les utiliser ou de les mettre à l'échelle. Les contrôles de santé permettent à Docker de surveiller la santé de vos services et de garantir qu'ils sont dans un état fonctionnel avant d'être considérés comme prêts.
Voici un exemple d'ajout d'un contrôle de santé pour une base de données MySQL dans Docker Compose :
services:
db:
image: mysql:8
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_DATABASE: mydatabase
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 30s
retries: 3
start_period: 10s
timeout: 5s
Dans cet exemple, le contrôle de santé utilise la commande mysqladmin ping pour vérifier que la base de données MySQL est opérationnelle. Docker attendra que la base de données soit prête avant de marquer le service comme « sain ».
7.2.11 Gestion des contraintes de ressources
Docker Compose permet de contrôler les ressources (CPU et mémoire) allouées à chaque service. Cela est particulièrement important lors de l'exécution de plusieurs conteneurs sur le même hôte, car cela permet d'éviter les conflits de ressources.
Voici comment définir les limites de ressources pour un service :
services:
web:
image: nginx
deploy:
resources:
limits:
cpus: "0.5"
memory: "512M"
Dans cet exemple, le service Web est limité à 50 % du processeur (0.5 CPU) et à 512 Mo de mémoire. Vous pouvez également définir des valeurs de réservation pour garantir une certaine quantité de ressources pour un conteneur, ce qui permet de mieux contrôler la gestion des ressources.
7.2.12 Utilisation des politiques de redémarrage pour la résilience des services
Pour garantir le redémarrage automatique de vos services en cas de panne, vous pouvez définir une politique de redémarrage. Docker Compose propose plusieurs options pour gérer le redémarrage des conteneurs :
no: ne redémarre pas automatiquement le conteneur (comportement par défaut).always: redémarre toujours le conteneur s'il s'arrête.on-failure: redémarre uniquement si le conteneur se termine avec un code d'erreur (code différent de zéro).unless-stopped: redémarre à moins que le conteneur ne soit explicitement arrêté.
Voici un exemple d'utilisation d'une politique de redémarrage pour un service Web :
services:
web:
image: nginx
restart: always
Dans ce cas, le service Web sera toujours redémarré s'il plante ou s'il est arrêté involontairement.
7.3 c - Mise en pratique
Docker Compose a été installé sur la machine virtuelle. Pour créer une application Docker Compose, on doit éditer un fichier yaml.
Dans un fichier
docker-compose.yml, collez les lignes suivantes:
services:
jupyter:
image: jupyter/minimal-notebook:ubuntu-18.04
elasticsearch:
image: elasticsearch:7.2.0
Sauvegardez le fichier, quittez-le et exécutez la commande suivante
docker compose up
Dans une autre console, listez les containers en fonctionnement
On peut voir que nous avons pu créer un container: une instance de jupyter/minimal-notebook. Dans le fichier docker-compose.yml, on peut voir deux services jupyter et elasticsearch: ce fichier est en réalité censé lancer deux containers mais comme nous l'avons vu précédemment, les images d'ElasticSearch nécessitent que l'on définissent une variable d'environnement. On peut préciser des variables d'environnement en utilisant le mot-clef environment.
On peut arrêter les containers lancés par Docker Compose en utilisant la ctrl + C dans la console dans laquelle docker compose up a été effectuée ou en tapant docker compose down dans une autre console.
Arrêtez les containers lancés par Docker Compose
Remplacez le contenu du fichier
docker-compose.ymlpar les lignes suivantes
services:
jupyter:
image: jupyter/minimal-notebook:ubuntu-18.04
elasticsearch:
image: elasticsearch:7.2.0
environment:
discovery.type: single-node
Quittez ce fichier, exécutez la commande
docker compose uppuis listez les containers en fonctionnement
Cette fois-ci on peut voir que les containers sont bien tous les deux en fonctionnement. Pour pouvoir utiliser jupyter, on doit faire suivre le port 8888 du container vers un des ports de la machine hôte.
Remplacez le contenu du fichier
docker-compose.ymlpar ces lignes, puis relancez les services Docker Compose
services:
jupyter:
image: jupyter/minimal-notebook:ubuntu-18.04
ports:
- target: 8888
published: 4444
protocol: tcp
mode: host
elasticsearch:
image: elasticsearch:7.2.0
environment:
discovery.type: single-node
Ici on fait suivre le port 8888 vers le port 4444 de la machine hôte. Vous pouvez ouvrir ce port pour arriver sur l'interface graphique de jupyter. On peut noter qu'il existe une autre manière de faire suivre des ports. Par exemple, si on veut faire suivre les ports 9200 et 9300 du container elasticsearch vers ceux de la machine hôte, on peut faire:
services:
jupyter:
image: jupyter/minimal-notebook:ubuntu-18.04
ports:
- target: 8888
published: 4444
protocol: tcp
mode: host
elasticsearch:
image: elasticsearch:7.2.0
environment:
discovery.type: single-node
ports:
- "9200:9200"
- "9300:9300"
On peut aussi spécifier les networks ou donner un nom aux containers. Dans le code suivant, on place les containers sur le réseau my_network_from_compose, on les renomme my_jupyter_from_compose et my_es_from_compose. Enfin, on précise que le mot de passe à utiliser pour utiliser jupyter est bonjour.
services:
jupyter:
image: jupyter/minimal-notebook:ubuntu-18.04
container_name: my_jupyter_from_compose
networks:
- my_network_from_compose
ports:
- target: 8888
published: 4444
protocol: tcp
mode: host
environment:
JUPYTER_TOKEN: "bonjour"
elasticsearch:
image: elasticsearch:7.2.0
container_name: my_es_from_compose
networks:
- my_network_from_compose
ports:
- "9200:9200"
- "9300:9300"
environment:
discovery.type: single-node
networks:
my_network_from_compose:
Mettez à jour le contenu du fichier
docker-compose.ymlavec ces lignes et relancez ces services
Une fois que les services sont lancés, allez sur le navigateur à l'adresse IP de votre machine virtuelle et le port 4444, vous devrez avoir l'interface jupyter.
Dans un nouveau notebook, exécutez le code suivant:
import requests
import pprint
import json
content = json.loads(requests.get('http://my_es_from_compose:9200').content)
pprint.pprint(content)
Enfin, on peut préciser créer des points de monture en précisant volumes. Par exemple, dans le code suivant, on peut lier le dossier work du container vers le dossier local:
services:
jupyter:
image: jupyter/minimal-notebook:ubuntu-18.04
container_name: my_jupyter_from_compose
networks:
- my_network_from_compose
volumes:
- .:/home/jovyan/work
ports:
- target: 8888
published: 4444
protocol: tcp
mode: host
environment:
JUPYTER_TOKEN: "bonjour"
elasticsearch:
image: elasticsearch:7.2.0
container_name: my_es_from_compose
networks:
- my_network_from_compose
ports:
- "9200:9200"
- "9300:9300"
environment:
discovery.type: single-node
networks:
my_network_from_compose:
Mettez à jour le fichier, relancez les services et vérifier le contenu du dossier
work
Docker Compose permet donc de lancer facilement un ensemble de containers. Nous avons vu ici les principaux arguments de Docker Compose. Nous n'irons pas plus loin car ce n'est pas l'objet de ce cours. Notez que si nous souhaitons lancer des containers sur un système distribué, on utilisera des outils comme Docker Swarm ou Kubernetes.