Application multi-conteneur - Docker-compose¶
Support de présentation : https://soleil-docker.jrobert-orleans.fr/04-docker_compose_et_bp_decouverte
Applications multi-containers¶
Besoin¶
Pour concevoir et déployer des applications fondées sur plusieurs micro-services :
BD
NoSQL (Mongo etc.)
Applications
APIs
de nouveaux besoins apparaissent :
nécessité de communiquer entre containers
possibilité de créer des réseaux ad-hoc mais pas très facile à manipuler
besoin de pouvoir décrire dans une syntaxe simplifiée un système de containers - avec des images prédéfinies - ou spécifiées dans de Dockerfiles - communicant naturellement entre elles
Solution : docker-compose¶
La commande docker-compose repose sur un fichier docker-compose.yml, écrit au format YAML.
Format YAML¶
json en moins verbeux
plus lisible, avec indentation (2 caractères décalage suffisent)
bcp de fichiers de conf utilisent ce format
cf exemples
docker ou docker-compose ?¶
Créons un conteneur nginx avec la commande Docker :
docker run --name web -d -p 8000:80 nginx:alpine
Puis allez visiter http://localhost:8000 …
On peut vouloir monter un volume dans notre container pour publier une page de notre cru via nginx :
créer un dossier de base nommé compose-nginx
créer dedans dossier app contenant un fichier index.html :
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Nginx Docker</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
</head>
<body>
<section class="section">
<div class="container">
<h1 class="Nginx via Docker">
Hello World
</h1>
<p class="subtitle">
Nginx à l'intérieur d'un container <strong>Docker</strong>!
</p>
</div>
</section>
</body>
</html>
Puis relançons la commande en montant app au bon endroit :
docker run --name web -itd -p 8000:80 -v $(pwd)/app:/usr/share/nginx/html nginx:alpine
Nginx avec docker-compose¶
Faisons plus simple avec une description en yaml :
services:
web:
image: nginx:alpine
ports:
- "8000:80"
volumes:
- ./app:/usr/share/nginx/html
Explications¶
top level : services
ici un seul service : web, assuré par nginx
le volume local app sera visible dans le container, à l’emplacement /usr/share/nginx/html
le port 8000 de l’hôte sera redirigé vers le port 80 du container
Lancement : docker compose up -d en mode detached
Questions¶
Peut-on changer en direct le contenu du fichier index.html du dossier app pendant que le conteneur tourne ?
comment lister ce qui tourne ?
Quels volumes sont montés ?
Comment tout arrêter ?
Services¶
On peut évidemment placer de multiples services dans un docker-compose.
les différents services sont décrits avec le même niveau d’indentation.
les services peuvent se décrire avec une image préfabriquée (Dockerhub) * avec d’éventuels fichiers de configuration * avec d’éventuels paramètres
les services peuvent également faire référence à des images fabriquées avec des Dockerfile spécifiques
principales commandes docker-compose¶
Commandes |
Utilisation |
|---|---|
|
build |
|
lancer l’app |
|
lancer en arrière plan |
|
lister les containers de l’app |
|
visualiser les logs du container nginx |
|
faire une pause en gardant les containers en l’état |
|
arrêter la pause |
|
arrêter l’application en gardant les données associées |
|
arrêter l’application en enlevant les containers, réseaux et volumes associés |
|
grand ménage ! (Attention données, etc.) |
Testez-les !
Portainer¶
Portainer est un outil web qui permet de gérer les conteneurs Docker et Docker Compose. Il permet de gérer les conteneurs, les images, les volumes, les réseaux, les stacks etc. Il est disponible sous forme d’image Docker et peut donc s’installer via Docker ! On peut par exemple le configurer et le lancer à partir du docker-compose suivant (linux ou mac) :
services:
portainer:
image: portainer/portainer-ce:latest
container_name: portainer
restart: unless-stopped
volumes:
- /etc/localtime:/etc/localtime:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./portainer-data:/data
ports:
- 9000:9000
On lance la commande suivante pour lancer le service portainer :
$ docker compose up -d
L’interface web est accessible via l’adresse suivante : http://localhost:9000. Lors du premier lancement, un assistant de configuration est lancé pour créer un compte administrateur, puis choisir l’option « local » pour la connexion à Docker. Il faut nommer le container et puis cliquer sur « connect ». Vous pouvez ensuite visualiser les conteneurs Docker et Docker Compose lancés sur la machine ainsi que leurs environments, volumes, ports, etc. La documentation est disponible ici : https://docs.portainer.io
Essayez le :
Visualisez les logs de conteneurs
Créez une application à l’aide d’un docker-compose que vous déposerez dans la section « stacks ».
Définissez des variables d’environnement pour votre stack.
Postgresql avec adminer¶
Une première application muti-container où l’on se contente d’images prédéfinies.
Le docker-compose¶
# Use postgres/example user/password credentials
services:
db:
image: postgres
restart: always
environment:
POSTGRES_PASSWORD: example
adminer:
image: adminer
restart: always
ports:
- 8080:8080
Faites un schéma représentant ce docker compose (un simple rectangle pour chaque service, le tout dans un rectangle représentant l’hôte ; représentez aussi les ports, etc.)
Placez le dans un répertoire séparé et testez ! (L’utilisateur dans l’interface adminer sera “postgres” et le mot de passe “example”)
Quels sont les services ?
A quoi correspondent les directives restart: always et environment ?
Exemple multi-containers avec Dockerfile : Flask et Redis¶
Exemple classique
Redis est un système clef/valeur efficace dans le Cloud
Flask est un micro-framework Python pour développer simplement des app Web
Tout le code de Flask est dans un seul fichier run.py
Créer un répertoire compose-flask-redis contenant le fichier suivant :
Le fichier run.py¶
from flask import Flask
from redis import Redis
app = Flask(__name__)
redis = Redis(host='redis', port=6379)
@app.route('/')
def hello():
count = redis.incr('hits')
return 'Hello for the {} time !\n'.format(count)
if __name__ == "__main__":
app.run(host="0.0.0.0", debug = True)
Conteneuriser ce service¶
installer les dépendances
lancer l’application automatiquement
Les dépendances sont gérées en Python à l’aide d’un fichier requirements.txt qui peut être utilisé pour créer un virtualenv ou un conteneur.
A minima :
flask
redis
(on peut préciser des versions aussi !)
On installe les requirements avec la commande : pip install -r requirements.txt
Écrire le Dockerfile pour le service Flask en partant de l’image python:3.11-slim
Ajouter le contenu du dossier courant au dossier /app du conteneur :
ADD . /apppuis choisir /app comme répertoire de travail
installer les dépendances
exposer le port 5000
lancer run.py
testez !
le docker compose avec le service Redis¶
Ajouter à présent un docker-compose.yml dans votre dossier dont voici les éléments :
Redis étant une image standard on peut directement l’invoquer dans le docker-compose contenant deux services :
redis:
image: "redis"
le service Flask étant décrit par
web:
build: .
ports:
- "4000:5000"
Ecrivez le docker-compose correspondant
puis build et lancement
docker compose builddocker compose up -d
Visitez : http://127.0.0.1:4000
Observation de l’app avec les outils Docker¶
docker compose psdocker compose logs ...Quelles sont les tailles des images utilisées ?
Lister les réseaux ? Les volumes ?
Comment lancer un terminal interactif sur le container Flask ?
Comment arrêter tout ?
Faire le grand ménage ?
Améliorations du Dockerfile¶
On peut définir des variables d’environnement dans FLASK : FLASK_DEBUG à la valeur True si on souhaite être en mode DEBUG et FLASK_APP avec le nom de l’app à lancer.
puis lancer l’app avec la directive : flask run
Effectuez les petites modifs correspondantes. Tester !
Réduction des images¶
essayez les images python:3.11-alpine et python:3.11
comparez les tailles et les temps de build et de lancement
Fichier .env¶
De manière à ne pas versionner les données sensibles, on peut créer un fichier .env dans le dossier du docker-compose. Ce fichier a la forme suivante :
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
...
On accède dans le docker-compose.yml avec la syntaxe : ${POSTGRES_USER} .
Par exemple :
services:
db:
image: postgres
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
Portainer¶
Déployez le docker compose précédent en passant par portainer.
Outils complémentaires pour Docker¶
yamllint¶
Vérifiez la syntaxe de votre fichier YAML avec la commande suivante :
# Si yamllint est installé sur votre système:
# yamllint docker-compose.yml
# Sinon :
docker run --rm -v $(pwd):/data cytopia/yamllint /data/docker-compose.yml
La documentation est ici : https://yamllint.readthedocs.io/en/stable/
Hadolint¶
Hadolint vérifie la validité de votre Dockerfile et vous donne des indications pour améliorer la qualité.
docker run --rm -i hadolint/hadolint < Dockerfile
Essayez le sur les dockerfiles que vous avez. Discutez les recommendations fournies.
Bonnes pratiques¶
Dans cette section, nous allons aborder quelques bonnes pratiques à suivre lors de la création de conteneurs Docker.
.dockerignore¶
Ca ne vous a peut être pas échappé, au moment de lancer un build docker affiche « Uploading build context » . Le client docker à ce moment envoie l’intégralité du répertoire courant au serveur docker. Si ce répertoire est volumineux, cela peut prendre du temps.
Une bonne pratique consiste à écrire un fichier .dockerignore contenant des expressions régulières de fichiers à exclure du contexte de build.
Bonnes pratiques diverses¶
Ces bonnes pratiques sont tirées de la documentation : https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
Each container should have only one responsibility.
Containers should be immutable, lightweight, and fast.
Don’t store data in your container. Use a shared data store instead.
Containers should be easy to destroy and rebuild.
Use a small base image (such as Linux Alpine). Smaller images are easier to distribute.
Avoid installing unnecessary packages. This keeps the image clean and safe.
Avoid cache hits when building.
Auto-scan your image before deploying to avoid pushing vulnerable containers to production.
Scan your images daily both during development and production for vulnerabilities Based on that, automate the rebuild of images if necessary.
apt-get : privilégiez l’installation de paquets sous la forme suivante :
RUN apt-get update && apt-get install -y \
package-bar \
package-baz \
package-foo \
&& rm -rf /var/lib/apt/lists/*
Exercice¶
Dans l’exemple suivant la personne qui fournit le Dockerfile a pour intention de créer une application qui qui exécute un serveur web Nginx et un serveur SSH pour qu’un utilisateur “user” puisse se connecter en SSH et modifier le contenu du serveur web. Cependant, ce qu’il propose ne respecte pas les bonnes pratiques énoncées ci-dessus.
Essayez de proposer des améliorations.
.
├── Dockerfile
└── index.html
FROM ubuntu:latest
MAINTAINER John Doe <ohn.doe@example.com>
RUN apt-get update
RUN apt-get install -y nginx vim git openssh-server
COPY index.html /var/www/html/index.html
RUN mkdir /run/sshd
RUN useradd -m -s /bin/bash user
RUN echo 'user:password' | chpasswd
EXPOSE 80
EXPOSE 22
ENTRYPOINT ["bash", "-c"]
CMD ["/sbin/sshd && nginx && tail -f /var/log/nginx/access.log"]
version: '3'
services:
web:
build: .
ports:
- "80:80"
- "22:22"
volumes:
- ./index.html:/var/www/html/index.html