Crafteo - Docker Training
Bienvenue sur les exercices de la formation Docker ! Suivre les liens du menu pour y accéder aux exercices.
Plus d'info sur crafteo.io et blog.crafteo.io
Docker - basics
docker --help|-h
docker [COMMAND] --help
docker run --help
docker image -h
# Run a container
docker run [OPTIONS] IMAGE
# List existing containers
> docker ps [-a]
# Show container logs
> docker logs [OPTIONS] CONTAINER
# start, stop, restart containers
> docker start|stop|restart CONTNER
Exercices
Lancer un container utilisant l'image httpd:alpine (image officielle Apache HTTP server) en mode détaché (daemon) et le nommer myapache
docker rundevra rendre la main et afficher un ID de container
Vérifier que myapache existe et est actif
- Le container doit être au status
Up
Afficher les 2 dernières lignes de logs de myapache et suivre les changements
Arrêter puis redémarrer le container myapache
- Plusieurs solutions possible utilisant le même set de commandes
Supprimer le container myapache
- Des actions préalables peuvent être requises
Docker - exposing container ports
# expose host port 8080 on container port 80
docker run -d --name myport -p 8080:80 httpd:alpine
# try it out!
curl localhost:8080
# help may always be useful
docker run -h
Exercices
Lancer 2 containers httpd:alpine exposant chacun leur port 80 sur des ports différents (tel que 8081 et 8082) et vérifier le fonctionnement
- l'option
-dpeut être utile pour lancer le container en background - curl
localhost:8081etlocalhost:8082permettront de tester avec l'un et l'autre - possible aussi de tester avec votre navigateur web
Lancer un container httpd:alpine utilisant le réseau hôte directement et tester
- Apache se lancera directement sur le réseau de la machine hôte en se bindant au port
80 - Si le port
80de la machine hote est déjà utilisé par un autre processus il y aura un conflit de port - Pour voir les process utilisant le port
80sur la machine hote:sudo netstat -lnap | grep -e ":80\s"
Docker - misc commands
Exercices
Puller l’image httpd taggée 2.4.41-alpine et lancer un nouveau container en mode détaché sans le nommer
docker pull -h, si jamais...- penser à récupérer l'identifiant unique de votre container donné en sortie de
docker run
Renommer le container lancé précédemment en myalpine en utilisant son identifiant unique (pas son nom)
- l'occasion de découvrir une nouvelle commande?
Afficher l’ensemble des processus du container myalpine
- équivalent de Linux
topoupspour un container Docker
Inspecter le container myalpine et trouver la commande lancée au démarrage
- il s'agit du processus qui sera lancé au démarrage du container
Obtenir les statistiques d’usage ressources (CPU, RAM...) du container
- pratique pour débugger dans certain cas
Mettre à jour la configuration du container myalpine pour le redémarrer automatiquement lors du boot de la machine
Docker - exec and co.
# Execute command inside a container
docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
# delete /tmp/somefile in container
docker exec mycontainer rm /tmp/somefile
# need help?
docker exec -h
Exercices
Lancer un container httpd:alpine exposant 8083:80 et y
ouvrir une session shell interactive avec pseudo-TTY et explorer le système de fichier (i.e. filesystem ou FS)
- le résultat est équivalent à ouvrir une session SSH sur une machine virtuelle... même si le fonctionnement n'a absolument rien à voir
- le FS de votre container est-il le même que celui de votre machine?
Modifier le fichier htdocs/index.html dans le container et vérifier ce que renvoi localhost:8083
- Utiliser une commande
echo "Test content" > index.htmlouvi - Que se passe-t-il si on lance un autre container utilisant la même image? Lancer un autre container
httpd:alpineexposant8084:80et comparer!
Lister les process du container lancé précédemment depuis le container lui-même. Vérifier si ce même processus est visible depuis la machine hôte.
ps -efpermet d'afficher les processus machine et/ou du containerpstree -ppermet d'afficher l'ensemble des process sous forme d'arbre parent/enfant
Docker - File System
Les fichiers de nos containers et images ne sortent pas de n'importe ou!
Exercices
Lancer un container httpd:alpine nommé filesystem et y créer un fichier. Trouver l'emplacement des fichiers du container sur la machine hôte.
docker exec -it filesystem bashavectouch somefilepour créer un fichierdocker exec filesystem sh -c 'touch somefile'pour les pros ;)docker -hest toujous notre ami!
Trouver l'emplacement des fichiers de l'image httpd:alpine sur la machine hôte.
- il est conseillé de ne toucher à ces fichiers qu'avec les yeux...
Docker - bind mount
# mount file (or folder) from ./some-data in container at /example/mydir
# short syntax: -v SOURCE:DEST
docker run -v ./some-data:/example/mydir -d httpd:alpine
# long syntax, equivalent of short syntax
# recommended by Docker but both work just as fine
docker run --mount type=bind,source=/tmp/myfile,target=/myfile -d httpd:alpine
# on oublie pas...
docker run -h
Exercices
Créer un dossier apache/ avec un fichier apache/index.html contenant "Hello Docker!"
# Example
mkdir apache
echo 'Hello Docker!' > ./apache/index.html
Lancer un container httpd:alpine montant le fichier ./apache/index.html sur htdocs/index.html du container (et exposant 8085:80)
- le chemin complet de destination sera peut-être nécéssaire
- tester avec
curl localhost:8085ou votre navigateur - lancer une session shell dans le container et modifier le fichier
index.html, re-testerlocalhost:8085et constater les changements# rappel: lancer une session shell docker exec -it mycontainer sh - modifier le fichier
apache/index.htmldirectement depuis la machine locale, re-testerlocalhost:8085et constater les changements
Même exercice mais monter le dossier complet ./apache/ dans le container afin de pouvoir écraser l'index.html du container et obtenir le même résultat que précédemment
Docker - Volumes
docker volume -h
# should be sufficient for this one!
Exercices
Créer un volume Docker nommé httpd_vol
- cette étape est optionnelle pour la suite, mais il serait intéréssant de comprendre pourquoi
Lancer un container httpd:alpine nommé containervol montant le volume Docker htdocs_vol sur /usr/local/apache2/htdocs. Vérifier le contenu du dossier htdocs dans le container une fois démarré.
- que se passe-t-il si le Volume n'existe pas au préalable?
- afficher les montages avec
df -houmountdonne des infos intéréssantes
Modifier le contenu de htodcs/index.html dans le container containervol puis stoppez et supprimer le container containervol. Relancer un nouveau container nommé containervol2 montant httpd_vol
- plutôt pratique pour backuper et manipuler des données lorsqu'une base de donnée type Mongo ou MySQL tourne en containerisée
Lancer un container basé sur busybox montant httpd_vol en parallèle
de containervol2 et faites des manipulations sur le fichier dans chacun des containers
Docker - build
# Build an image with a Dockerfile
docker build [OPTIONS] CONTEXT_PATH
# Use Dockerfile by default with context from current directory (.)
docker build .
# did you miss me?
docker build -h
# Example Dockerfile content to build a custom Apache image
# Image used as base to create our new image
FROM alpine:3.20
# Run some commands to install apache
RUN apk add apache2
# define commands which will be run on startup
CMD ["-DFOREGROUND"]
ENTRYPOINT ["httpd"]
Exercices
Créer un dossier mybuild et y créer un fichier Dockerfile avec le contenu de l'exemple ci-dessus. Builder une image Docker à partir du Dockerfile et la tagger myapache:1.0
- votre image doit apparaitre via
docker images
Lancer un container utilisant votre image myapache et exposer 8089:80, vérifier le fonctionnement
- rappel:
curl localhost:8089ou navigateur - essayer de monter des volumes pour obtenir un fichier
index.htmlcustomisé!
Créez un fichier index.html avec un contenu customisé, par exemple echo "Hello from file" > index.html.
Modifier votre Dockerfile pour y ajouter une instruction COPY permettant de copier index.html à l'emplacement /var/www/localhost/htdocs/ (emplacement par défaut du package Alpine apache2, différent de l'image officiel httpd).
Testez le fonctionnement de votre image en lançant un container.
Docker Compose - basics
Nous utiliserons l'application Example Voting App pour l'exercice. Elle se trouve dans le dossier ~/example-voting-app.
Le fichier docker-compose.yml décrit l'ensemble des services définissant notre stack Docker Compose:
- Vote: permet de voter pour Chien ou Chat
- Result: permet de voir les résultats des votes
- Worker: Traite les votes pour les insérer en base de donnée
- Redis: gère les votes en cours via un système de queue
- DB (Postgres): stocke les résultats
Nous utiliserons cette application pour illustrer nos exemples et exercices.
Documentation de référence:
- Compose file specs
- Docker Compose CLI
- Par ex,
serviceet les sous-éléments sont documentés dans Services top-level elements
- Par ex,
🔖 Conseil: Ajoutez des liens à fos favoris, ils seront utiles !
Note: la CLI docker-compose standalone a été dépréciée en faveur de docker compose
Exercices
Lancement d'une stack Docker Compose
- Utiliser la CLI
docker composepour lancer la stack en mode détachéedocker compose --help
Les services Vote et Result expose une interface web accessible depuis la machine locale une fois lancés. Trouver dans le fichier docker-compose.yml les bindings de ports utilisés pour y accéder via votre navigateur.
Configuration de services Docker Compose
Modifier le service Redis pour:
- Utiliser l'image
redis:7.2.1 - Nommer le container
my_redisau démarrage - Ajouter une variable d'environnement
FOO=BAR
Fichier .env et variables d'environnement
Il est possible de référencer des variables d'environnements dans notre fichier docker-compose.yml et de spécifier un fichier .env contenant des variables d'environnements par défaut. Par exemple:
# .env example
DOTENV_POSTGRES_USER: "postgres"
DOTENV_POSTGRES_PASSWORD: "postgres"
Créer un fichier .env et modifier le service db pour utiliser les valeurs de ce fichier plutôt que des valeurs hardcodées.
Healthcheck & depends on
Faisons en sorte de démarrer le service Worker après nous êtes assuré que la base de donnée Postgres soit disponible:
Ajouter une instruction healthcheck au service db:
- Faire un bind mount du script
resources/healthchecks/postgres.shdans le container - Configurer
healthcheckpour lancer le script toutes les 5s afin de vérifier la "healthiness" du service
Build avec Docker Compose
docker compose build permet de lancer des builds d'images Docker en spécifiant le dossier de contexte, le Dockerfile, etc. de façon similaire à docker build.
Exercices
Les Dockerfile des services result et worker sont présents dans des sous-dossiers correspondants à leurs noms (i.e. result/Dockerfile) mais le build des images n'est pas managé par Docker Compose avec la configuration actuelle.
Modifier la configuration des services result et worker dans docker-compose.yml pour indiquer l'emplacement de build à Docker Compose.
Docker Compose CLI
La plupart des commandes docker ont leurs équivalents avec docker compose, mais leurs fonctionnalités sont adaptées à la gestion de stack multi-container et les fonctionnalités ne sont pas toujours équivalentes.
# Rappel
docker compose --help
Exercices
Utiliser une commande permettant de puller l'ensemble des images de la stack en parallèle.
- Permet de gagner du temps lors du pull de nombreuses images
Lancer la stack Compose avec les options suivantes:
- Mode détachée (Tout comme
docker,docker composelance les containers en mode interactif par défaut) - Forcer la récréation des containers déjà existants
Lancer la stack puis modifier le fichier docker-compose.yml pour changer le port exposé de result pour 5002. Appliquer les changements à la stack avec une commande docker compose.
Quelques manipulations:
- Arrêter, démarrer puis re-démarrer la stack
- Lister les images utilisées par la stack
- Lister les containers de la stack
- Afficher les logs de l'ensemble des containers de la stack
- Afficher les logs d'un container de la stack et suivre les changements
- Executer une session shell interactive dans le container
vote - Arrêter et supprimer la stack, puis ne lancer que le service
vote - Arrêter et supprimer la stack
Ces commandes seraient possibles directement avec docker en y spécifiant les options requises. docker compose intéragi avec le Daemon Docker tout comme docker.
Plusieurs stacks peuvent coéxister en s'assurant qu'il n'y a pas de conflits de ports ou autre.
Copier le fichier docker-compose.yml et nommer cette copie docker-compose-bis.yml puis lancer 2 stacks en parallèle
- Celle utilisant
docker-compose.ymldoit être nomméapp - Celle utilisant
docker-compose-bis.ymldoit être nomméeapp-bis - Attention aux conflits de nom de container et ports: le nom des containers doivent être uniques ainsi que les ports exposées sur la machine
Docker Compose - ateliers avancés
Volumes
Configuration de Volume: configurer un volume persistant pour le service postgres. Il doit être conservé même en cas de destruction du container ou de la stack Compose.
- Ajouter un volume
db-data - Configurer le service
dbpour utiliser le volumedb-dataà l'emplacement/var/lib/postgresql/data
Réseaux
Configuration de Réseaux:
- Ajouter un réseau
app-network - Configurer l'ensemble des services pour que les containers soient associés au réseau
app-networket relancer la stack
Variables d'environnement
Docker Compose permet de définir un fichier de variable d'environnement global qui seront affectées à chaque container.
- Trouver dans la documentation la fonctionnalité correspondante
- Remplacer les variables d'environnement du service
dbdansdocker-compose.ymlet par le fichier de variable d'environnement utilisé par Compose
Docker Compose permet de définir des variables qui seront substituées aux variables d'environnement.
- Modifier
docker-compose.ymlpour définir la version du serviceredispour qu'elle utilise la variable d'environnementREDIS_VERSIONsi possible, etalpinepar défaut: - Définir la variable d'environnement
REDIS_VERSION=5.0.7-alpineet relancer la stack. Vérifier que le containerredisutilise l'imageredis:5.0.7-alpine
Override docker-compose.yml
Docker Compose permet d'utiliser plusieurs fichiers de configuration .yml pour étendre une configuration de base.
- Trouver dans la documentation la référence à ce méchanisme d'extention des configurations
- Créer un fichier
docker-compose.dev.ymlétendantdocker-compose.ymltel que:- le service
votemonte le code sourcevote/à l'emplacement/app - le service
voteutilise la commandepython -m flask run -h 0.0.0.0 -p 80 - le service
votedispose des variables d'environnements:FLASK_APP=app.app FLASK_ENV=development
- le service
- Relancer la stack en combinant
docker-compose.ymletdocker-compose.dev.yml - Renommer
docker-compose.dev.ymlde façon à pouvoir lancer la stack avecdocker-compose.ymlet le fichier d'override avec uniquement la commandedocker compose up -dsans aucune autre option
Cette configuration permet de monter le code source de l'application Vote (Python) directement dans le container et de relancer l'application dynamiquement au sein du container sans avoir à re-builder ou redémarrer le container.
Cette méthode utilise les fonctionnalités de Flask (serveur web Python) mais la plupart des frameworks permettent un setup similaire pour les environnement de développement.
Il est ainsi possible de définir une configuration de base Docker Compose avec des overrides selon différent contextes (dev, CI, production, etc.)
Bridge networking
Quelques exercices sur Bridge networking avec Docker.
Exercices
Lancer la stack docker-compose.yml. Par défaut, un réseau est créé et attaché à chaque container.
- Identifier le réseau
bridgecréé et utilisé par la stack par défaut- Utiliser la CLI Docker:
docker network --help
- Utiliser la CLI Docker:
- Identifier l'ensemble des réseaux actuellement existant et le Driver utilisé par chacun
- Inspecter le réseau utilisé par la stack Compose et:
- Trouver la liste des containers associés au réseau
- Identifier le subnet utilisé par le réseau
Quid de l'isolation des réseaux Bridge? Par défaut, les containers sur un même réseau sont joignables par leur nom (i.e. le container vote est joignable via le hostname vote). Docker effectue une résolution DNS interne.
Isolons les containers de notre stack:
- Configurer les réseaux
vote-netetresult-net - Isoler
vote,redis,workerdans le réseauvote-net - Isoler
worker,dbetresultdans le réseauresult-net - Note: le container
workersera dans 2 réseaux:vote-netetresult-net
Seul worker pourra communiquer avec chaque container. vote / redis et db / result seront mutuellement isolés dans leurs réseaux respectifs.
Vérifier la non-connectivité entre le container vote et result
- Lancer une session shell sur
voteet essayer de joindreresultdocker exec -it vote sh # Need curl and/or ping ? # Use 'apk add curl iputils-ping' # or 'apt update && apt install -y curl iputils-ping' # Try to connect $ curl result # Check DNS resolution $ getent hosts result $ getent hosts worker
Docker peut aussi connecter et déconnecter des containers d'un réseau à la volée (sans besoin de redémarrer ou recréer un container).
- Connecter le container
votemanuellement au réseauresult-net - Vérifier à nouveau la connectivité
- Déconnecter le container
votederesult-net
Configuration réseau customisée avec Docker Compose
- Ajouter un réseau bridge
my-bridgedansdocker-compose.ymlet configurer chaque container pour utiliser ce réseau - Modifier la configuration de
my-bridgepour:- Forcer l'utilisation du driver réseau
bridge - Affecter un nom spécifique
named-bridge - Utiliser le subnet
172.42.0.0/16
- Forcer l'utilisation du driver réseau
- Appliquer les configurations et inspecter le réseau
named-bridgepour vérifier les configurations
Il est aussi possible d'utiliser un réseau déjà existant par ailleurs:
- Créer un réseau Bridge nommé
my-external-networkdocker network --help
- Configurer les services pour utiliser ce réseau déjà existant et appliquer les configurations
Bridge networking - advanced
Recherchons comment Docker intéragi avec le système Linux pour manager les réseaux et interfaces:
- Lancer la stack Compose
- Trouver l'interface réseau Linux sous-jacente du réseau Docker utilisé par la stack
- Les interfaces réseaux sont les devices Linux tels que
eth0,lo(loop local), etc. - Les commandes
nmcli device statusouifconfigpermettent d'afficher les interfaces réseaux
- Les interfaces réseaux sont les devices Linux tels que
- Trouver l'entrée de la table de routage permettant de rediriger les packets réseaux vers cette interface réseau
- Utiliser
ip routeounetstat -rn
- Utiliser
Host networking
Configurer docker-compose.yml pour:
- Utiliser le network
hostpour chacun des services - Lancer la stack et vérifier que chacun des services est
Up - Se connecter au service Vote et Worker pour vérifier leur fonctionnement
- Trouver l'adresse IP du container
worker- si elle existe
- Identifier le réseau Docker utilisant le driver
host - Essayer de créer un autre réseau Docker utilisant le driver
host
Bind Mount avancé
Exercices
Besoin: afin de conserver les données PostgreSQL sur la machine locale même si le container est supprimé et pour faciliter le processus de backup, vous cherchez une solution pour que les données de la BDD soient persistées. Un Bind Mount vous parait une bonne solution.
- Configurer le service
dbpour monter le dossier local/home/docker/db-dataà l'emplacement/var/lib/postgresql/data - Redémarrer le container de la stack pour prendre en compte les configurations
- Observer le contenu du dossier
/home/docker/db-data
Besoin: vous devez configurer plus finement la base de donnée via un fichier de configuration postgresql.conf fourni par votre administrateur système et qui doit être utilisé par la BDD:
# Full content of postgresql.conf to be mounted in container
listen_addresses = '*'
temp_file_limit = 1000
- Trouver l'emplacement auquel le fichier doit être monté dans le container
dbbasé sur l'imagepostgres- La documentation des images Docker disponible sur Docker Hub indique généralement ces use-cases typiques
- Pour
postgresil faudra passer un flag de configuration au binaire lancé au démarrage du container
- Monter le fichier de configuration via un Bind Mount avec les contraintes:
- le fichier de configuration doit être monté en read-only (lecture seule)
- Définir l'option Bind Propagation à
rprivate
- Appliquer les modifications sans redémarrer le container, à la place envoyer un signal
SIGHUPau container pour reloader la configuration
De nombreux applicatifs utilisent SIGHUP pour recharger une configuration sans avoir à faire un redémarrage complet, cette feature n'est pas spécifique à Docker mais néanmoins très pratique.
Besoin: vous souhaitez configurer une procedure de backup des données via un au container postgres qui fera un dump régulier de la BDD
Lancer un container utilisant permettant d'effectuer un backup:
- utiliser la même image que le service
db - monter un dossier spécifique permettant de persister les backups (par ex. un bind mount
$PWD/backup:/backup) - supprimer automatiquement le container lorsqu'il s'arrêtera
- lancer directement la commande de backup, par exemple:
PGPASSWORD=postgres pg_dumpall -h db -c -U postgres -w > /backup/my-backup.sql- Attention: pour résoudre le nom d'hôte
dble container de backup doit se trouver sur le même réseau.
- Attention: pour résoudre le nom d'hôte
Il est possible de monter le même dossier/fichier sur plusieurs containers, pratique dans divers situations comme le backup de données
Bonus: configurer une tâche cron qui lancera toutes les heures notre backup. Pour créer une tâche cron:
# run cron editor for user
crontab -e
# specify cronjob to run every hour
0 * * * * COMMAND
Docker Volumes
Exercices sur les volumes Docker. Quelques rappels:
# as usual
docker volume --help
docker volume create --help
# create a volume named myvolume
docker volume create myvolume
# run a container and attach a volume
docker run -v myvolume:/data some_image
Exercices
Besoin: vous souhaitez gérer les données Postgres via un volume plutôt qu'un bind mount afin de limiter l'accès aux données depuis la machine locale et faciliter les procédures de backup
Modifier docker-compose.yml pour:
- configurer un volume
psqsl-dataet - monter ce volume à l'emplacement
/var/lib/postgresql/datadu servicedb - redémarrer le service
dbpour prendre en compte les modifications
Une fois effectué:
- Lister les volumes existants et trouver le volume créé par Docker Compose
- Trouver l'emplacement des données du volume sur le disque local
Cleanup: supprimer la stack Docker Compose et les volumes utilisés par Postgres
Montage tmpfs
Exercices sur le montage de volume de type tmpfs*
Exercices
Contexte: les données Redis sont dans notre cas considérées comme éphémères et ne doivent pas être persistées sur le disque ou dans le writable layer du container. De plus, pour optimiser les performances les données Redis doivent être stockées en mémoire directement.
Modifier docker-compose.yml pour:
- configurer un volume de type
tmpfssur le serviceredisà l'emplacement/data - limiter la taille du volume à
500Mo - redémarrer le service pour prendre en compte les modifications
Lancer une session shell dans le container redis et:
- créer un fichier test
echo test > /data/test - redémarrer le container
- vérifier l'éxistence du fichier précédemment créé
Container Layer data
Quelques exercices de manipulation des données d'un container
Exercices
Lancer une session shell dans le container vote (docker exec -it vote sh) et:
- Créer un fichier
/test.txtavec le contenutestecho test > /test.txt - Editer le fichier
app.pyet remplacer les options A et B Cat et Dog par Windows et Mac- installer un editeur avec
apt update && apt install nanosi besoin (ctrl+O: sauvegarder etctrl+X: exit)
- installer un editeur avec
- Quitter et redémarrer le container
- Se connecter à
localhost:5000et constater les changements
Il est fréquent d'avoir à éditer des fichiers au sein d'un container dans des environnements de dev ou test, et d'y installer des utilitaires "on the fly" - c'est cependant déconseillé en production: les changements seraient perdus en cas de re-création du container et pourrait modifier le comportement du container.
Inspecter le container vote et trouver l'emplacement des données du layer container sur le disque local.
Il est possible de modifier directement les données depuis le disque... mais c'est très fortement déconseillé et risque la corruption de l'installation Docker!
Supprimer le container vote et le récréer puis:
- Lancer une session shell dans le container et vérifier si l'état des fichiers
/test.txtetapp.py - Vérifier l'existence des données du container layer sur le disque
Copy
CLI Docker: images
# list images
docker images # mind the plural!
docker image ls
# S.O.S
docker image -h
# pull an image
docker images pull redis:alpine
docker pull redis:alpine
Exercices
Inspecter l'image vote de votre stack Example Voting App et trouver l'emplacement sur le disque des fichiers de l'image.
Tagger l'image vote de votre stack en vote:newtag et configurer votre stack pour l'utiliser
Afficher et comparer l'historique de build de l'image vote de base et vote:newtag
Import/export d'image
Une image Docker n'est rien d'autre qu'une archive contenant des fichiers. Exportons notre image sous forme d'archive avant de la ré-importer comme image Docker.
- Exporter l'image
vote:newtagcomme archive- Trouver la commande adapée via
docker image --help - Cette action peut prendre quelques secondes...
- Trouver la commande adapée via
- Supprimer l'image Vote de votre système local
- L'image ne doit plus apparaitre avec
docker images - Si besoin, il sera possible de la re-builder from scratch
- L'image ne doit plus apparaitre avec
- Re-importer l'image Vote depuis l'archive créée précédemment
docker imagesdoit affiche l'image
Lancer un container basé sur vote:newtag et ouvrez une session bash dans le container (docker exec ...) pour modifier le contenu du fichier /app/app.py afin de modifier les options Cat/Dog pour Windows/Mac (les variables options_a|b) Redémarer le container pour constater les changements.
Il est possible de créer une image Docker directement à partir d'un container (sans passer par docker build). Ce principe est équivalent à créer une image de VM via un snapshot de VM existante afin de lancer des clones de la VM d'origine.
Créer une nouvelle image à partir du container modifié précédemment sans passer par docker build, tagger cette image voting-app:from-container puis démarrer un container à partir de celle-ci et tester.
Voting App: Build Python image
L'appplication Voting App dispose de plusieurs services:
- Worker: récupère les votes et les stockes en base de donnée
- Vote: application web permettant de voter
- Result : permet d'afficher les résultats
Objectif de l'exercice: écrire un Dockerfile pour l'application Vote.
Lien utile: Dockerfile reference - l'ensemble des instructions utilisables dans un Dockerfile
Exercice - Dockerfile pour le service Vote
Le code du service Vote se trouve dans vote/:
app.pyest le fichier applicatif permettant de lancer l'applicationrequirements.txtcontiens les dépendences de l'application
Pour l'instant le service utilise une image Docker déjà buildée:
services:
vote:
image: crafteo/example-voting-app-vote
Nous allons faire en sorte de builder notre propre service Vote selon les contraintes suivantes:
-
Utiliser Python 3.9 ou plus récente
- Chercher sur Docker Hub une image Python correspondante
-
L'ensemble du code source du service (fichiers dans
/vote) doit être copiée dans l'image Docker -
L'image Docker doit contenir l'ensemble du service. Pour installer les dépendences, utiliser la commande
pip install -r requirements.txt -
La commande qui doit être lancée au démarrage du container est:
gunicorn app:app -b 0.0.0.0:80et doit être éxecutée depuis le dossier contenant l'ensemble du code source du service (le service exposera le port 80 par défaut une fois lancé)
Exercices:
- Ecrire un fichier
vote/Dockerfilepermettant de builder l'image du service Vote - Builder votre image Vote et la tagger
vote:local(mettre à jour ladocker-compose.ymlen conséquence) - Lancer la stack avec votre nouvelle image
vote
Optimization de build
Problèmes typiques de build:
- temps de build (notamment download des dépendances)
- taille finale de l'image (temps de push/pull)
- contenu de l'image: ne pas y copier des fichiers par accident (secret, token, etc.)
Documentation de référence:
Exercices
- Ajouter un fichier
.dockerignorepermettant de filtrer Dockerfile du contexte - Changer les instructions de build pour n'invalider le cache de
pip installqu'en cas de modification derequirements.txt - Utiliser une image
alpinepour réduire la taille finale de l'image
ENTRYPOINT vs CMD
Tableau de correspondance ENTRYPOINT vs. CMD: Voir la documentation Docker
Exercice: définir ENTRYPOINT et CMD
Définir ENTRYPOINT et CMD afin d'avoir une commande lancée par Docker dans le container répondant à divers contraintes.
Exemple
# Le container sera lancé avec la commande
# gunicorn app:app -b 0.0.0.0:80
CMD [ "gunicorn", "app:app", "-b", "0.0.0.0:80" ]
Pour tester:
# Update vote Dockerfile and build
docker compose build vote
# Run vote container
# COMMAND and ARG will override default COMMAND (CMD)
docker run -d --name test_entrypoint --rm vote:local [COMMAND] [ARG...]
# or
docker compose run vote [COMMAND] [ARG...]
# Check running processus
docker top test_entrypoint -o pid,command
# or
docker compose top vote -o pid,command
# Output like
# PID COMMAND
# 7767 /usr/local/bin/python /usr/local/bin/gunicorn app:app -b 0.0.0.0:80
# stop and remove container
docker stop test_entrypoint
# or
docker compose down vote
Cas 1
La commande lancée au démarrage par défaut doit être:
gunicorn app:app -b 0.0.0.0:80
Il doit être possible d'overrider les options de gunicorn définies par défaut (i.e. overrider l'usage de -b 0.0.0.0:80)
Résultat attendu:
#
# Sans argument supplémentaire
#
docker compose run vote
#
# gunicorn app:app -b 0.0.0.0:80
#
#
# Arguments: --log-level DEBUG
#
docker compose run vote --log-level DEBUG
#
# gunicorn app:app --log-level DEBUG
#
Cette configuration peut-être utilisée pour fournir une image lançant notre serveur Vote en permettant à l'utilisateur final de passer à gunicorn des options différentes (comme un port différent ou un niveau de debug plus élévé)
Cas 2
La commande lancée au démarrage par défaut doit être:
gunicorn app:app -b 0.0.0.0:80
Il doit être possible de passer des options supplémentaires à gunicorn tout en conservant l'usage de -b 0.0.0.0:80 par défaut
Résultat attendu:
#
# Sans argument supplémentaire
#
docker compose run vote
#
# gunicorn app:app -b 0.0.0.0:80
#
#
# Arguments: --log-level DEBUG
#
docker compose run vote --log-level DEBUG
#
# gunicorn app:app -b 0.0.0.0:80 --log-level DEBUG
#
Cette configuration peut-être utilisée pour fournir une image lançant notre serveur Vote en permettant à l'utilisateur final de passer à gunicorn des options différentes (comme niveau de debug plus élévé) tout en semi-forçant l'utilisation de certaines options (dans notre cas, l'utilisation du port 80)
Cas 3
La commande lancée au démarrage par défaut doit être:
gunicorn app:app -b 0.0.0.0:80
Passer un argument au run du container doit overrider intégralement la commande lancée par le container par celle en paramètre.
Résultat attendu:
#
# Sans argument supplémentaire
#
docker compose run vote
#
# gunicorn app:app -b 0.0.0.0:80
#
#
# Arguments: --log-level DEBUG
#
docker compose run vote sh
#
# Lance une session shell interactive
#
Cette configuration peut-être utilisée pour fournir une image lançant notre serveur Vote en permettant à l'utilisateur final de lancer un binaire ou une commande différent au lancement du container.
Instruction de build Dockerfile avancées
De nombreuses instructions de build existent pour Dockerfile.
La documentation officielle Dockerfile reference référence l'ensemble des instructions disponibles
Rappel: commandes de build
# builder l'image vote
docker build -t vote:builder vote
# lancer l'image vote
docker run -d --name builder1 vote:builder
# lancer une session shell dans le container
# sera utile pour tester vos manipulations!
docker exec -it builder1 bash
# Supprimer un container et une image
docker stop builder1
docker rm builder1
docker rmi vote:builder
Exercice
Configurer l'image pour que l'utilisateur crafteo soit utilisé pour lancer le processus principal
- Pour créer un utilisateur
crafteosous Linux Alpine:adduser -S crafteo
Il sera nécéssaire d'ajouter le chemin /home/crafteo/.local/bin au PATH du user crafteo pour éxecuter les binaires installés par Python.
- Ajouter une instruction au Dockerfile permettant de définir la variable
PATHtel que:PATH=$PATH:/home/crafteo/.local/bin
Attention: il n'est pas possible de binder un port <1024 avec un utilisateur non-root avec Linux, il sera nécéssaire d'utiliser un port plus élevé. Modifier votre Dockerfile pour lancer l'application en utilisant le port 8080.
Ajouter un healthcheck permettant de vérifier le fonctionnement de l'image avec curl localhost:80. Ce healthcheck permettra de vérifier que l'image est bien active si une réponse est renvoyée lors d'un appel à localhost:80
- Il peut-être nécéssaire d'installer
curlou d'utiliser une image de base ou il l'est déjà - Pour installer
curlsous Linux Alpine:apk add curl
Quel est le comportement d'un container Docker en cas de failure du healthcheck?
Ajouter un argument utilisable au build de l'image Vote permettant de spécifier la version de l'image Python de base à utiliser. Utiliser la version 3.7-alpine par défaut.
Par exemple, il devra être possible de lancer les commandes:
# utiliser Python 3.8
docker build -t vote:builder --build-arg PYTHON_VERSION=3.8-alpine vote
# utiliser Python 3.7 par défaut
docker build -t vote:builder vote
Harbor
Harbor est un outil de registry Docker. Nous allons créer un compte sur https://demo.goharbor.io pour expérimenter les fonctionnalités.
Exercices
Après le build locale nos images Example Voting App, pushons les sur une Registry pour les partager !
Aller sur demo.goharbor.io et créez-vous un compte (gratuit).
- Créer un projet
example-voting-app - Pusher les images Example Voting App dans la registry
demo.goharbor.io. Vous devrez:- Vous authentifier avec
docker login - Modifier
docker-compose.ymlpour spécifier l'URL de Harbor demo pour chaque image - Rebuilder les images avec le tag pointant vers Harbor demo
- Pusher vos images avec
docker compose pushoudocker push
- Vous authentifier avec
Docker Hub - Registry Docker officielle
Cette série d'exercice démontrera l'usage de Docker Hub, la registry officielle. Nous allons créer un compte, s'authentifier avec la CLI docker et pusher nos imagees buildées localement directement sur la registry.
Exercices
Après le build locale nos images Example Voting App, poussons les sur la Registry!
Aller sur hub.docker.com et créez vous un compte (gratuit).
- Avec votre compte, créer un repository sur Docker Hub nommé
voting-app-votequi sera utilisé pour héberger l'imagevote - Pour pouvoir pusher sur la registry, vous aurez besoin de vous authentifier avec
docker loginaprès avoir généré un token- Sur votre profil > Account settings > Security générer un token
- Utiliser
docker loginavec votre ID et token pour vous authentifier
Nous avons à présent accès au Docker Hub depuis notre machine
- Modifier
docker-compose.ymlpour que l'imagevote,workeretresultbuildée soit nommée selon votre registry - Utiliser
docker composepour pusher l'imagevotedans votre domaine Docker Hub - Utiliser
docker composepour pusher l'imageworkerdans votre domaine Docker Hub - pour lequel vous n'avez pas créé de repository pour l'instant - Utiliser la CLI
dockerpour pusher l'imageresult - Modifier
docker-compose.ymlpour tagger les images1.0et pusher toutes les images en une seule commande
Les images pushées sur la registry peuvent maintenant être utilisée publiquement. Il est aussi possible de les rendre privées, auquel cas il sera obligatoire de s'authentifier sur la registry avant de pouvoir les puller.
- Essayer de puller les images construires et pushées depuis la registry de votre voisin
- Essayer de pusher une image construite par vous-même sur la registry de votre voisin
Self-hosted Docker Registry
Cette série d'exercice nous permettra de déployer notre propre registry Docker.
Le Registry Docker se déploie sous forme d'un container Docker, par exemple pour déployer une registry sur votre machine écoutant sur le port 8080:
docker run -d -p 5010:5000 --name registry registry:2
Taggons une image pour la pusher sur notre registry locale:
docker pull ubuntu:latest
docker tag ubuntu localhost:5010/ubuntu
docker push localhost:5010/ubuntu
Pour plus de détails, voir la documentation officielle Docker
Dockerizer une application depuis le code source
Considérons le contexte suivant:
Vous êtes un opérateur travaillant avec une équipe de développeur. L'équipe de dev viens de vous livrer le code source d'une application web que vous devez déployer sous Docker.
L'application est codé en NodeJS et a pour but d'afficher un message à partir d'un fichier de configuration. Le code source de l'application se trouve à l'emplacement suivant: NodeJS app
Les développeurs vous donnent les instructions suivantes:
- L'application peut se lancer avec NodeJS 15+
- Pour fonctionner, elle va charger au démarrage le fichier de configuration
./config.yamlqui doit lui être mis à disposition. Les devs vous fournissent le fichier de config testé en développement:# Message to print when app is accessed via a web browser message: "Test message from dev" # Host and port on which to bind server hostname: 127.0.0.1 port: 8080 - Instructions d'utilisation de l'application:
# Install Node dependencies # Require package.json and package-lock.json npm install # Run node app node app.js # App should not respond to web request # For example, localhost:8080
Vous devez dockeriser l'application:
- Ecrire un
Dockerfilepermettant de builder l'application - Ecrire un
docker-compose.ymlpermettant de lancer l'application:- Assurez-vous de monter un fichier
config.yamldans le container
- Assurez-vous de monter un fichier
- Lancer l'application et vérifier qu'elle soit joignable
Note: ⚠️ attention avec l'image node: lancer npm install à la racine du système de fichier (dossier /) provoque un bug du type "idealTree" already exists. Cf. ce post Stack Overflow. Penser à utiliser un WORKDIR au préalable.
HTTPS & Reverse Proxying avec Traefik
Ajoutons des configurations TLS (HTTPS) et un reverse proxy (Traefik).
- Explorer le contenu de
resources/traefik.yml - Créer un fichier
.env(remplacer<you>par votre nom):VOTE_URL=vote.<you>.training.crafteo.io RESULT_URL=result.<you>.training.crafteo.io - Lancer la stack avec les overrides Traefik:
# make traefik docker compose -f docker-compose.yml -f resources/traefik.yml up -d
vote et result sont maintenant exposés via:
https://vote.<you>.training.crafteo.iohttps://result.<you>.training.crafteo.io
Note: le certificat TLS ne sera pas reconnu par votre navigateur, ils sont fournis par (STAGING) Let's Encrypt, une version de test des certificats Let's Encrypt
Docker: quelques bonnes pratiques!
Quelques exercices sur les bonnes pratiques avec Docker: limitation de ressources, healthcheck, logging...
Les exercices utiliserons le docker-compose.yml suivant:
version: "3.7"
services:
db:
container_name: db
image: postgres:9.4
environment:
POSTGRES_USER: "postgres"
POSTGRES_PASSWORD: "postgres"
redis:
container_name: redis
image: redis:alpine
result:
container_name: result
image: crafteo/example-voting-app-result
ports:
- "5001:80"
- "5858:5858"
vote:
container_name: vote
image: crafteo/example-voting-app-vote
ports:
- "5000:80"
worker:
container_name: worker
image: crafteo/example-voting-app-worker
Exercices
Se documenter sur les méthodes de limitation de ressources pour les containers Docker puis modifier le docker-compose.yml pour limiter la consommation de chaque service:
- Maximum de 0.2 CPU et 256Mo RAM par service
- Réservation de 0.1 CPU 128Mo de RAM par service
Note: Docker Compose v3 ignore par défaut les paramètres deploy.resources qui sont réservés à Docker Swarm. Pour les prendre en compte, utiliser --compatibility ou un fichier compose version: "2"
Docker permet de configurer des drivers de Logging. Faire quelques recherches sur les configuration de logging possible avec Docker puis:
- Lancer un container
fluent/fluentdécoutant montant le volume/tmp/docker-logs:/fluentd/logdocker run -d --rm --name fluentd -v /tmp/docker-logs:/fluentd/log fluent/fluentd - Configurer le service
dbpour utiliser le driverfluentdafin d'envoyer les logs vers le containerfluentd- Utiliser
docker inspectpour obtenir l'IP du containerfluentd - Ne pas utiliser le nom du container directement qui ne sera pas reconnu
- Utiliser
- Lancer la stack ainsi configurée
Vérifier l'existence des logs dans /tmp/docker-logs
Logging with Docker example: ELK Stack
Déployons une stack ELK (Logstash, Elasticearch, Kibana) que nous pourrons utiliser pour obtenir les logs de nos containers:
resources/elk-stack.yml contiens les configurations d'une stack ELK. La déployer avec:
# make elk
docker compose -f resources/elk-stack.yml up -d
- L'interface Kibana est accessible via
http://<host>:8082 - Explorer le contenu de
resources/elk-stack.ymlpour y trouver les configurations des différents services
Une fois déployée, lancer une stack de containers avec le logging driver Docker adapté. Utiliser le fichier resources/logging.yml:
# make logging
docker compose -f docker-compose.yml -f resources/logging.yml up -d
Aller sur Kibana (<host>:8082) et:
- Cliquer sur Discover (bouton avec la boussole)
- Vous serez redirigé sur la page Create index pattern. Entrez
logstash-*et cliquer sur Next step - Choisissez
@timestampcomme Time filter field et confirmer avec Create index pattern - Cliquer à nouveau sur Discover pour accéder aux logs
Monitoring example: Prometheus + Grafana stack
Déployons une stack Prometheus + Grafana que nous pourrons utiliser pour obtenir des metrics sur nos containers (CPU, RAM, etc.) et configurer des alertes.
Cloner le repository Git Prometheus:
git clone https://github.com/PierreBeucher/prometheus.git
Lancer la stack Docker Compose:
cd prometheus
docker compose up -d
- L'interface Grafana est accessible via
http://<host>:3000(login:admin, password:Prometheus2023) - Explorer le contenu de
docker-compose.ymlpour y trouver les configurations des différents services
Configuration avancées du Docker Daemon
Suite à l'installation de Docker sur Linux:
- Docker est installé comme service Linux (
systemd) - La CLI
dockerdest utilisé pour lancer le Docker daemon - Il est possible d'overrider les configurations du daemon via des options CLI au lancement du service ou via un fichier
/etc/docker/daemon.jsonlu pardockerdpar défaut
Quelques infos et rappels:
- Le Docker daemon est aussi appelé Docker host ou Docker server
- Le Docker client (CLI
docker) communique avec le client via API REST en utilisant la socket/var/run/docker.sockpar défaut. Le client peut aussi contacter directement une adresse tel quetcp://127.0.0.1:2375outcp://198.265.78.1:2376en fonction des configurations du Daemon - Memo de commandes
systemctlutile pour manipuler les services Linux:# restart, stop, get status of docker sudo systemctl [restart|stop|status|...] docker # edit systemd file override # or edit file directly at /lib/systemd/system/docker.service sudo systemctl edit docker # reload after edit sudo systemctl daemon-reload # see newest service logs sudo journalctl -u docker -r
Exercices
Créer un fichier etc/docker/daemon.json et y configuer les options suivantes pour le Docker daemon:
- Activer le mode debug
- Configurer le daemon Docker pour écouter sur
127.0.0.1:2375 - Configurer le port binding pour écouter sur
127.0.0.1par défaut- sinon, un port binding avec
docker run -p 80:80 ...écoutera sur0.0.0.0:80par défaut, ce qui peut représenter un risque de sécurité
- sinon, un port binding avec
- Ajouter le DNS
8.8.8.8
Votre Docker daemon écoute maintenant sur 127.0.0.1:2375 et n'utilisera plus la socket /var/run/docker.sock. La CLI Docker utilisant cette socket par défaut, des configurations supplémentaires seront requises en utilisant la CLI docker.
Redémarrer le service Docker et tester:
- Lancer un container exposant un port et vérifier que l'adresse d'écoute est bien
127.0.0.1 - Au sein du container, vérifier que le DNS
8.8.8.8est bien utilisé - Afficher les logs du Daemon
Certains déploiements mettent à disposition un daemon Docker accessible à distance. (i.e. le daemon Docker est installé sur une machine différente du client Docker)
Cela peut-être utile dans certaines situations, par exemple pour partager un daemon avec une équipe de développeur ou un système de CI et accéler le build d'image: le cache de build pourra être réutilisé facilement et ainsi réduire le temps de build (pratique dans certains cas ou un build sans cache dure 20+ min et avec cache seulement quelques secondes!)
Un tel déploiement demande de sécuriser les communications entre le client Docker et le daemon. Utilisant HTTP par défaut, il faut y configurer TLS pour passer en HTTPS.
Configurer le daemon Docker pour:
- écouter sur
127.0.0.1:2376 - Configurer l'authentification du serveur en TLS - un client pourra ainsi authentifier le serveur lors de son utilisation (similaire à la connection à un site web en HTTPS)
- (Optionnel) Configurer l'authentification du client en TLS - le client devra s'authentifier auprès du serveur avec un certificate pour pouvoir utiliser le daemon
Ce setup s'appelle une double-authentification TLS. Pour ce besoin il faut un certificat et une clé pour être utilisé par le Daemon (idem pour le client):
- Le client va "truster" un CA (certificate Authority) et le daemon fournira un certificat signé par ce CA pour s'authentifier (c'est exactement le même processus en se connectant à un site web en HTTPS)
- Le Daemon va "truster" un CA (généralement le même), et le client devra fournir un certificat signé par ce CA pour être autorisé par le daemon
Dans le cadre de l'exercice nous pourrons générer nos propre CA et certificats. Pour générer une clé et un certificat autosigné:
# generate CA cert and key
openssl genrsa -out ca.key 4096
openssl req -nodes -new -x509 -keyout ca.key -out ca.cert
# Generate key for daemon
openssl genrsa -out daemon.key 4096
# Generate CSR for daemon
openssl req -new -sha256 -key daemon.key -out daemon.csr
# Generate cert for daemon signed by CA
openssl x509 -req -in daemon.csr -CA ca.cert -CAkey ca.key -CAcreateserial -out daemon.cert -days 500
# same for client: key, csr and cert
Points d'attention:
- Par convention, le port d'écoute de Docker est
2375sans TLS et2376avec TLS. - Le client aura besoin de configuration supplémentaire pour activer TLS et vérifier l'authenticité du serveur