Pour naviguer, utilisez les flèches en bas à droite (ou celles de votre clavier)
Gauche/Droite: changer de chapitre
Haut/Bas: naviguer dans un chapitre
Pour avoir une vue globale : utiliser la touche "o" (pour "Overview")
2024/2025
Présentation disponible à l’adresse: https://gounthar.github.io/gounthar/cours-devops-docker/main
Version PDF de la présentation : Cliquez ici
Contenu sous licence Creative Commons Attribution 4.0 International License
Code source de la présentation: https://github.com/gounthar/gounthar/cours-devops-docker
Pour naviguer, utilisez les flèches en bas à droite (ou celles de votre clavier)
Gauche/Droite: changer de chapitre
Haut/Bas: naviguer dans un chapitre
Pour avoir une vue globale : utiliser la touche "o" (pour "Overview")
Bruno VERACHTEN
Première itération d’une découverte Docker dans le cadre du DevOps
Contenu entièrement libre et open-source
Méchamment basé sur le travail de Damien Duportal et Amaury Willemant
N’hésitez pas ouvrir des Pull Request si vous voyez des améliorations ou problèmes: sur cette page (😉 wink wink)
🚧 Work in progress… 🚧
26 septembre après-midi
24 octobre après-midi
07 novembre après-midi
…
Pourquoi ? s’assurer que vous avez acquis un minimum de concepts
Quoi ? Sans doute une note sur 20, basée sur une liste de critères exhaustifs
Comment ? Un projet Gitlab (probable) ou GitHub (peu probable) à me rendre (timing à déterminer ensemble)
Intro
Base
Containers
Images
Fichiers, nommage, inspect
Volumes
Réseaux
Docker Compose
Bonus
"La Base"
🤔 Quel est le problème ?
🤔 Commençons plutôt par une définition:
Docker c’est …
🤔 Définition quelque peu datée (2014):
Docker is …
🤔 Définition quelque peu datée (2014):
Docker is a toolset for Linux containers designed to ‘build, ship and run’ distributed applications.
🤔 Nous voilà bien…
C’est quoi un container?
🤔 Nous voilà bien…
C’est quoi un container?
10 ans déjà…
Mais moi aussi…
Aucune raison que ça soit réservé aux puissants! La révolution vaincra!
Linux everywhere… And then Docker to follow.
Histoire vraie.
|
Histoire vraie.
| |
cc |
Histoire vraie.
| |
cc | |
|
Histoire vraie.
| |
cc | |
| |
nan |
Histoire vraie.
| |
cc | |
| |
nan | |
|
Histoire vraie.
| |
cc | |
| |
nan | |
| |
pas standard dsl |
Histoire vraie.
| |
cc | |
| |
nan | |
| |
pas standard dsl |
Problème de temps exponentiel
L’IT n’est pas la seule industrie à résoudre des problèmes…
"Separation of Concerns"
"Virtualisation Légère"
Virtualisation
Virtualisation
"Virtualisation Légère"
Légère, vraiment?
Idée erronée mais citée trop fréquemment !
"Separation of concerns": 1 "tâche" par conteneur
Non exclusifs mutuellement
Docker sur Windows pour exécuter des conteneurs Linux :
Hyper-V : Sous Windows, Docker utilise souvent Hyper-V pour exécuter des machines virtuelles légères (VM) Linux.
Performance : Docker sur Windows pour les conteneurs Linux peut être légèrement moins performant que Docker sur Linux natif.
Isolation : Isolation différente de celle des conteneurs Linux natifs.
Docker sur Linux pour exécuter des conteneurs Linux :
Native : Docker fonctionne nativement car il partage le même noyau Linux.
Efficacité : Il est très efficace en termes de consommation de ressources.
Isolation : Isolation basée sur les cgroups et les namespaces du noyau Linux.
Le bac à sable
Le bac à sable
Un environnement tout propre tout neuf !
Le bac à sable
Un environnement tout propre tout neuf !
Le poste de travail n’est pas impacté
Le bac à sable
Un environnement tout propre tout neuf !
Le poste de travail n’est pas impacté
La configuration reste également isolée sans effet sur le poste de travail
La machine de dév
La machine de dév
Avoir un environnement reproductible pour rendre homogène le développement et les tests.
La machine de dév
La machine de dév
Déploiement en production
Déploiement en production
Avec un container, si ca fonctionne en local, ca fonctionne en prod !
Outillage jetable
Outillage jetable
On instancie des applications sans les installer
Outillage jetable
On instancie des applications sans les installer
On crée le container, on l’utilise puis on le jette
On a la base, remplissons là maintenant de containers!
Un navigateur web récent (et décent)
Un compte sur GitHub
GitPod.io : Environnement de développement dans le ☁️ "nuage"
But: reproductible
Puissance de calcul sur un serveur distant
Éditeur de code VSCode dans le navigateur
Gratuit pour 50h par mois (⚠️) |
Ça, c’était avant (⚠️) |
Gratuit pour 10h par mois (⚠️) |
Gratuit pour 50h par mois si compte lié (⚠️) |
Rendez vous sur https://gitpod.io
Authentifiez vous en utilisant votre compte GitHub:
Bouton "Login" en haut à droite
Puis choisissez le lien " Continue with GitHub"
Lors de votre première connexion, GitPod va vous demander l’accès (à accepter) à votre email public configuré dans GitHub :
GitPod vous demande votre numéro de téléphone mobile afin d’éviter les abus (service gratuit). Saisissez un numéro de téléphone valide pour recevoir par SMS un code de déblocage :
Choisissez l’éditeur "VSCode Browser" (la première tuile) :
Vous arrivez sur la page listant les "workspaces" GitPod :
Un workspace est une instance d’un environnement de travail virtuel (C’est un ordinateur distant)
⚠ Faites attention à réutiliser le même workspace tout au long de ce cours ⚠
Pour les besoins de ce cours, vous devez autoriser GitPod à pouvoir effectuer certaines modification dans vos dépôts GitHub
Rendez-vous sur la page des intégrations avec GitPod
Éditez les permissions de la ligne "GitHub" (les 3 petits points à droits) et sélectionnez uniquement :
user:email
public_repo
workflow
Cliquez sur le bouton ci-dessous pour démarrer un environnement GitPod personnalisé:
Après quelques secondes (minutes?), vous avez accès à l’environnement:
Gauche: navigateur de fichiers ("Workspace")
Haut: éditeur de texte ("Get Started")
Bas: Terminal interactif
À droite en bas: plein de popups à ignorer (ou pas?)
Vous devriez pouvoir taper la commande whoami
dans le terminal de GitPod:
Retour attendu: gitpod
Vous devriez pouvoir fermer le fichier "Get Started"…
… et ouvrir n’importe quel autre fichier…
Bien, on peut maintenant fermer ce workspace, il ne s’agirait pas de gaspiller vos 50 heures.
GitPod fonctionne aussi avec gitlab.com, mais pour les instances on premise, il faut l’installer, l’instancier, avoir un "inner cloud"
Bref, on ne l’a pas ici. ¯_(ツ)_/¯
Guide de survie
Communication Humain <→ Machine
Base commune de TOUS les outils
🇬🇧 CLI == "Command Line Interface"
🇫🇷 "Interface de Ligne de Commande"
Pour les théoriciens et curieux :
🇬🇧 REPL == "Read–eval–print loop"
ls --color=always -l /bin
Séparateur : l’espace
Premier élément (ls
) : c’est la commande
Les éléments commençant par un tiret -
sont des "options" et/ou drapeaux ("flags")
"Option" == "Optionnel"
Les autres éléments sont des arguments (/bin
)
Nécessaire (par opposition)
Afficher le manuel de <commande>
:
# Commande 'man' avec comme argument le nom de ladite commande
man <commande>
Navigation avec les flèches haut et bas
Tapez /
puis une chaîne de texte pour chercher
Touche n
pour sauter d’occurrence en occurrence
Touche q
pour quitter
🎓 Essayez avec ls
, chercher le mot color
💡 La majorité des commandes fournit également une option (--help
), un flag (-h
) ou un argument (help
)
Google c’est pratique aussi hein !
Dans un terminal Unix/Linux/WSL :
CTRL + C
: Annuler le process ou prompt en cours
CTRL + L
: Nettoyer le terminal
CTRL + A
: Positionner le curseur au début de la ligne
CTRL + E
: Positionner le curseur à la fin de la ligne
CTRL + R
: Rechercher dans l’historique de commandes
pwd
: Afficher le répertoire courant
🎓 Option -P
?
ls
: Lister le contenu du répertoire courant
🎓 Options -a
et -l
?
cd
: Changer de répertoire
🎓 Sans argument : que se passe t’il ?
cat
: Afficher le contenu d’un fichier
🎓 Essayez avec plusieurs arguments
mkdir
: créer un répertoire
🎓 Option -p
?
echo
: Afficher un (des) message(s)
rm
: Supprimer un fichier ou dossier
touch
: Créer un fichier
grep
: Chercher un motif de texte
Le système de fichier a une structure d’arbre
La racine du disque dur c’est /
: 🎓 ls -l /
Le séparateur c’est également /
: 🎓 ls -l /usr/bin
Deux types de chemins :
Absolu (depuis la racine): Commence par /
(Ex. /usr/bin
)
Sinon c’est relatif (e.g. depuis le dossier courant) (Ex ./bin
ou local/bin/
)
Le dossier "courant" c’est .
: 🎓 ls -l ./bin # Dans le dossier /usr
Le dossier "parent" c’est ..
: 🎓 ls -l ../ # Dans le dossier /usr
~
(tilde) c’est un raccourci vers le dossier de l’utilisateur courant : 🎓 ls -l ~
Sensible à la casse (majuscules/minuscules) et aux espaces : 🎓
ls -l /bin
ls -l /Bin
mkdir ~/"Accent tué"
ls -d ~/Accent\ tué
Variables interpolées avec le caractère "dollar" $
:
echo $MA_VARIABLE
echo "$MA_VARIABLE"
echo ${MA_VARIABLE}
# Recommendation
echo "${MA_VARIABLE}"
MA_VARIABLE="Salut tout le monde"
echo "${MA_VARIABLE}"
Sous commandes avec $(<command>)
:
echo ">> Contenu de /tmp :\n$(ls /tmp)"
Des if
, des for
et plein d’autres trucs (https://tldp.org/LDP/abs/html/)
Chaque exécution de commande renvoie un code de retour (🇬🇧 "exit code")
Nombre entier entre 0 et 255 (en POSIX)
Code accessible dans la variable éphémère $?
:
ls /tmp
echo $?
ls /do_not_exist
echo $?
# Une seconde fois. Que se passe-t'il ?
echo $?
ls -l /tmp
echo "Hello" > /tmp/hello.txt
ls -l /tmp
ls -l /tmp >/dev/null
ls -l /tmp 1>/dev/null
ls -l /do_not_exist
ls -l /do_not_exist 1>/dev/null
ls -l /do_not_exist 2>/dev/null
ls -l /tmp /do_not_exist
ls -l /tmp /do_not_exist 1>/dev/null 2>&1
Le caractère "pipe" |
permet de chaîner des commandes
Le "stdout" de la première commande est branchée sur le "stdin" de la seconde
Exemple : Afficher les fichiers/dossiers contenant le lettre d
dans le dossier /bin
:
ls -l /bin
ls -l /bin | grep "d" --color=auto
Les commandes sont des fichier binaires exécutables sur le système :
command -v cat # équivalent de "which cat"
ls -l "$(command -v cat)"
La variable d’environnement $PATH
liste les dossiers dans lesquels chercher les binaires
💡 Utiliser cette variable quand une commande fraîchement installée n’est pas trouvée
Exécution de scripts :
Soit appel direct avec l’interprétateur : sh ~/monscript.txt
Soit droit d’exécution avec un "shebang" (e.g. #!/bin/bash
)
$ chmod +x ./monscript.sh
$ head -n1 ./monscript.sh
#!/bin/bash
$ ./monscript.sh
# Exécution
Guide de survie
Support de communication
Humain → Machine
Humain <→ Humain
Besoins de traçabilité, de définition explicite et de gestion de conflits
Collaboration requise pour chaque changement (revue, responsabilités)
avec un VCS : 🇬🇧 Version Control System
Pour conserver une trace de tous les changements dans un historique
"Source unique de vérité" (🇬🇧 Single Source of Truth)
Pour collaborer efficacement sur un même référentiel
Nous allons utiliser Git
Git is a free and open source distributed version control system designed to handle everything from small to very large projects with speed and efficiency.
L’historique ("Version Database") : dossier .git
Dossier de votre projet ("Working Directory") - Commande
La zone d’index ("Staging Area")
Dans le terminal de votre environnement GitPod:
Créez un dossier vide nommé projet-vcs-1
dans le répertoire /workspace
, puis positionnez-vous dans ce dossier
mkdir -p /workspace/projet-vcs-1/
cd /workspace/projet-vcs-1/
Est-ce qu’il y a un dossier .git/
?
Essayez la commande git status
?
Initialisez le dépôt git avec git init
Est-ce qu’il y a un dossier .git/
?
Essayez la commande git status
?
mkdir -p /workspace/projet-vcs-1/
cd /workspace/projet-vcs-1/
ls -la # Pas de dossier .git
git status # Erreur "fatal: not a git repository"
git init ./
ls -la # On a un dossier .git
git status # Succès avec un message "On branch main No commits yet"
Créez un fichier README.md
dedans avec un titre et vos nom et prénoms
Essayez la commande git status
?
Ajoutez le fichier à la zone d’indexation à l’aide de la commande git add (…)
Essayez la commande git status
?
Créez un commit qui ajoute le fichier README.md
avec un message,
à l’aide de la commande git commit -m <message>
Essayez la commande git status
?
echo "# Read Me\n\nObi Wan" > ./README.md
git status # Message "Untracked file"
git add ./README.md
git status # Message "Changes to be committed"
git commit -m "Ajout du README au projet"
git status # Message "nothing to commit, working tree clean"
diff: un ensemble de lignes "changées" sur un fichier donné
changeset: un ensemble de "diff" (donc peut couvrir plusieurs fichiers)
commit: un changeset qui possède un (commit) parent, associé à un message
"HEAD": C’est le dernier commit dans l’historique
Afficher la liste des commits
Afficher le changeset associé à un commit
Modifier du contenu dans README.md
et afficher le diff
Annulez ce changement sur README.md
git log
git show # Show the "HEAD" commit
echo "# Read Me\n\nObi Wan Kenobi" > ./README.md
git diff
git status
git checkout -- README.md
git status
Abstraction d’une version "isolée" du code
Concrètement, une branche est un alias pointant vers un "commit"
Créer une branche nommée feature/html
Ajouter un nouveau commit contenant un nouveau fichier index.html
sur cette branche
Afficher le graphe correspondant à cette branche avec git log --graph
git switch --create feature/html
# Ou alors: git branch feature/html && git switch feature/html
echo '<h1>Hello</h1>' > ./index.html
git add ./index.html && git commit --message="Ajout d'une page HTML par défaut" # -m / --message
git log
git log --graph
git lg # cat ~/.gitconfig => regardez la section section [alias], cette commande est déjà définie!
On intègre une branche dans une autre en effectuant un merge
Un nouveau commit est créé, fruit de la combinaison de 2 autres commits
Merger la branche feature/html
dans la branche principale
⚠️ Pensez à utiliser l’option --no-ff
Afficher le graphe correspondant à cette branche avec git log --graph
git switch main
git merge --no-ff feature/html # Enregistrer puis fermer le fichier 'MERGE_MSG' qui a été ouvert
git log --graph
# git lg
"Infrastructure as Code" :
Besoins de traçabilité, de définition explicite et de gestion de conflits
Collaboration requise pour chaque changement (revue, responsabilités)
Code Civil:
git
est un des (plus populaires) de système de contrôle de versions
Cet outil vous permet:
D’avoir un historique auditable de votre code source
De collaborer efficacement sur le code source (conflit git
== "PARLEZ-VOUS")
⇒ C’est une ligne de commande (trop?) complète qui nécessite de pratiquer
C’est à vous (ouf) !
Allez dans Gitpod
Dans un terminal, tapez la commande suivante :
docker container run hello-world
# Equivalent de l'ancienne commande 'docker run'
Un service "Docker Engine" tourne en tâche de fond et publie une API REST
La commande docker container run …
a lancé un client docker qui a envoyé une requête POST
au service docker
Le service a téléchargé une Image Docker depuis le registre DockerHub,
Puis a exécuté un conteneur basé sur cette image
C’est à vous !
docker container ls --help
# ...
docker container ls
# ...
docker container ls --all
⇒ 🤔 comment comprenez vous les résultats des 2 dernières commandes ?
Le conteneur est toujours présent dans le "Docker Engine" même en étant arrêté
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
109a9cdd3ec8 hello-world "/hello" 33 seconds ago Exited (0) 17 seconds ago festive_faraday
Un conteneur == une commande "conteneurisée"
cf. colonne "COMMAND"
Quand la commande s’arrête : le conteneur s’arrête
cf. code de sortie dans la colonne "STATUS"
$ docker container run busybox echo hello world
$ docker container run busybox echo hello world
Le moteur Docker crée un container à partir de l’image "busybox".
$ docker container run busybox echo hello world
Le moteur Docker crée un container à partir de l’image "busybox".
Le moteur Docker démarre le container créé.
$ docker container run busybox echo hello world
Le moteur Docker crée un container à partir de l’image "busybox".
Le moteur Docker démarre le container créé.
La commande "echo hello world" est exécutée à l’intérieur du container.
$ docker container run busybox echo hello world
Le moteur Docker crée un container à partir de l’image "busybox".
Le moteur Docker démarre le container créé.
La commande "echo hello world" est exécutée à l’intérieur du container.
On obtient le résultat dans la sortie standard de la machine hôte.
$ docker container run busybox echo hello world
Le moteur Docker crée un container à partir de l’image "busybox".
Le moteur Docker démarre le container créé.
La commande "echo hello world" est exécutée à l’intérieur du container.
On obtient le résultat dans la sortie standard de la machine hôte.
Le container est stoppé.
Outillage jetable
Tester une version de Maven, de JDK, de NPM, …
Lancez un nouveau conteneur nommé bonjour
💡 docker container run --help
ou Documentation en ligne
Affichez les "logs" du conteneur (==traces d’exécution écrites sur le stdout + stderr de la commande conteneurisée)
💡 docker container logs --help
ou Documentation en ligne
Lancez le conteneur avec la commande docker container start
Regardez le résultat dans les logs
Supprimez le container avec la commande docker container rm
docker container run --name=bonjour hello-world
# Affiche le texte habituel
docker container logs bonjour
# Affiche le même texte : pratique si on a fermé le terminal
docker container start bonjour
# N'affiche pas le texte mais l'identifiant unique du conteneur 'bonjour'
docker container logs bonjour
# Le texte est affiché 2 fois !
docker container ls --all
# Le conteneur est présent
docker container rm bonjour
docker container ls --all
# Le conteneur n'est plus là : il a été supprimé ainsi que ses logs
docker container logs bonjour
# Error: No such container: bonjour
C’est une "image" de conteneur, c’est à dire un modèle (template) représentant une application auto-suffisante.
On peut voir ça comme un "paquetage" autonome
C’est un système de fichier complet:
Il y a au moins une racine /
Ne contient que ce qui est censé être nécessaire (dépendances, librairies, binaires, etc.)
https://hub.docker.com/ : C’est le registre d’images "par défaut"
Exemple : Image officielle de conteneur "Ubuntu"
🎓 Cherchez l’image hello-world
pour en voir la page de documentation
💡 pas besoin de créer de compte pour ça
Il existe d’autre "registres" en fonction des besoins (GitHub GHCR, Google GCR, etc.)
docker container run --interactive --tty alpine
docker container run --interactive --tty alpine
/ $
docker container run --interactive --tty alpine
/ $
On lance un container à partir de l’image "alpine"
docker container run --interactive --tty alpine
/ $
On lance un container à partir de l’image "alpine"
On lance un sh
dans ce container
docker container run --interactive --tty alpine
/ $
On lance un container à partir de l’image "alpine"
On lance un sh
dans ce container
On redirige l’entrée standard avec -i
docker container run --interactive --tty alpine
/ $
On lance un container à partir de l’image "alpine"
On lance un sh
dans ce container
On redirige l’entrée standard avec -i
On déclare un pseudo-terminal avec -t
Quelle distribution Linux est utilisée dans le terminal Gitpod ?
💡 Regardez le fichier /etc/os-release
Exécutez un conteneur interactif basé sur alpine:3.18.3
(une distribution Linux ultra-légère) et regardez le contenu du fichier au même emplacement
💡 docker container run --help
💡 Demandez un tty
à Docker
💡 Activez le mode interactif
Exécutez la même commande dans un conteneur basé sur la même image mais en NON interactif
💡 Comment surcharger la commande par défaut ?
$ cat /etc/os-release
# ... Ubuntu ....
$ docker container run --tty --interactive alpine:3.18.3
/ $ cat /etc/os-release
# ... Alpine ...
# Notez que le "prompt" du terminal est différent DANS le conteneur
/ $ exit
$ docker container ls --all
$ docker container run alpine:3.18.3 cat /etc/os-release
# ... Alpine ...
Revenons dans notre container interactif de tout à l’heure…
/ $ curl google.fr
/ $ curl google.fr
/bin/sh: curl: not found
/ $ curl google.fr
/bin/sh: curl: not found
cURL
n’est pas disponible par défaut sur Alpine. Il faut l’installer au préalable
/ $ curl google.fr
/bin/sh: curl: not found
cURL
n’est pas disponible par défaut sur Alpine. Il faut l’installer au préalable
/ $ apk update && apk add curl
/ $ curl google.fr
/bin/sh: curl: not found
cURL
n’est pas disponible par défaut sur Alpine. Il faut l’installer au préalable
/ $ apk update && apk add curl
fetch https://dl-cdn.alpinelinux.org/alpine/v3.18/main/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/v3.18/community/x86_64/APKINDEX.tar.gz
v3.18.3-169-gd5adf7d7d28 [https://dl-cdn.alpinelinux.org/alpine/v3.18/main]
v3.18.3-171-g503977088b4 [https://dl-cdn.alpinelinux.org/alpine/v3.18/community]
OK: 20063 distinct packages available
(1/7) Installing ca-certificates (20230506-r0)
(2/7) Installing brotli-libs (1.0.9-r14)
(3/7) Installing libunistring (1.1-r1)
(4/7) Installing libidn2 (2.3.4-r1)
(5/7) Installing nghttp2-libs (1.55.1-r0)
(6/7) Installing libcurl (8.2.1-r0)
(7/7) Installing curl (8.2.1-r0)
Executing busybox-1.36.1-r2.trigger
Executing ca-certificates-20230506-r0.trigger
OK: 12 MiB in 22 packages
/ $ curl google.fr
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>301 Moved</TITLE></HEAD><BODY>
<H1>301 Moved</H1>
The document has moved
<A HREF="http://www.google.fr/">here</A>.
</BODY></HTML>
C’est bon, on a cURL
🦱
On peut quitter sh
et revenir à la machine hôte !
/ $ exit
On peut quitter sh
et revenir à la machine hôte !
/ $ exit
Si on veut réutiliser cURL
sur Alpine, c’est simple, on relance le shell, non? 🤔
On peut quitter sh
et revenir à la machine hôte !
/ $ exit
Si on veut réutiliser cURL
sur Alpine, c’est simple, on relance le shell, non? 🤔
docker container run --interactive --tty alpine
On peut quitter sh
et revenir à la machine hôte !
/ $ exit
Si on veut réutiliser cURL
sur Alpine, c’est simple, on relance le shell, non? 🤔
docker container run --interactive --tty alpine
On relance cURL
:
/ $ curl google.fr
On peut quitter sh
et revenir à la machine hôte !
/ $ exit
Si on veut réutiliser cURL
sur Alpine, c’est simple, on relance le shell, non? 🤔
docker container run --interactive --tty alpine
On relance cURL
:
/ $ curl google.fr
/bin/sh: curl: not found
En fait, c’est logique !
docker container run
Cette commande instancie un "nouveau container à chaque fois" !
En fait, c’est logique !
docker container run
Cette commande instancie un "nouveau container à chaque fois" !
Chaque container est différent.
En fait, c’est logique !
docker container run
Cette commande instancie un "nouveau container à chaque fois" !
Chaque container est différent.
Aucun partage entre les containers à part le contenu de base de l’image.
" On m’a vendu un truc qui permet de lancer des tonnes de microservices… mais là, on télécharge nimps et s’amuse à le perdre…"
Lançons un container bien particulier…
docker container run --interactive --tty jpetazzo/clock
Lançons un container bien particulier…
docker container run --interactive --tty jpetazzo/clock
Mon Sep 25 10:33:51 UTC 2023
Mon Sep 25 10:33:52 UTC 2023
Mon Sep 25 10:33:53 UTC 2023
Mon Sep 25 10:33:54 UTC 2023
Mon Sep 25 10:33:55 UTC 2023
Mon Sep 25 10:33:56 UTC 2023
...
Lançons un container bien particulier…
docker container run --interactive --tty jpetazzo/clock
Mon Sep 25 10:33:51 UTC 2023
Mon Sep 25 10:33:52 UTC 2023
Mon Sep 25 10:33:53 UTC 2023
Mon Sep 25 10:33:54 UTC 2023
Mon Sep 25 10:33:55 UTC 2023
Mon Sep 25 10:33:56 UTC 2023
...
Ce container va tourner indéfiniment sauf si on le stoppe avec ⌨️ Ctrl+C…
Lançons un container bien particulier…
docker container run --interactive --tty jpetazzo/clock
Mon Sep 25 10:33:51 UTC 2023
Mon Sep 25 10:33:52 UTC 2023
Mon Sep 25 10:33:53 UTC 2023
Mon Sep 25 10:33:54 UTC 2023
Mon Sep 25 10:33:55 UTC 2023
Mon Sep 25 10:33:56 UTC 2023
...
Ce container va tourner indéfiniment sauf si on le stoppe avec ⌨️ Ctrl+C…
… mais ca va stopper le container !
Lançons un container bien particulier…
docker container run --interactive --tty jpetazzo/clock
Mon Sep 25 10:33:51 UTC 2023
Mon Sep 25 10:33:52 UTC 2023
Mon Sep 25 10:33:53 UTC 2023
Mon Sep 25 10:33:54 UTC 2023
Mon Sep 25 10:33:55 UTC 2023
Mon Sep 25 10:33:56 UTC 2023
...
Ce container va tourner indéfiniment sauf si on le stoppe avec ⌨️ Ctrl+C…
… mais ca va stopper le container !
Oui car quand on stoppe le processus principal d’un container, ce dernier n’a plus de raison d’exister et s’arrête naturellement.
La solution : le flag --detach
docker container run --detach jpetazzo/clock
La solution : le flag --detach
docker container run --detach jpetazzo/clock
399f3b23bc0585991afa80dfee854cf0a953d782b99153b4e2cbc74ab6b07770
Le retour de cette commande correspond à l’identifiant unique du container.
Cette fois-ci, le container tourne, mais en arrière plan !
" Okay, maintenant il n’écrit la date nulle part… mais toutes les secondes… à l’aide ! "
Le processus principal de ce container écrit dans la sortie standard… du container !
Comment retrouver le contenu de la sortie standard du container ?
Le processus principal de ce container écrit dans la sortie standard… du container !
Comment retrouver le contenu de la sortie standard du container ?
docker logs 399f3
Le processus principal de ce container écrit dans la sortie standard… du container !
Comment retrouver le contenu de la sortie standard du container ?
docker logs 399f3
Tue Sep 12 12:37:01 UTC 2023
Tue Sep 12 12:37:02 UTC 2023
Tue Sep 12 12:37:03 UTC 2023
Tue Sep 12 12:37:04 UTC 2023
Tue Sep 12 12:37:05 UTC 2023
Tue Sep 12 12:37:06 UTC 2023
Tue Sep 12 12:37:07 UTC 2023
Tue Sep 12 12:37:08 UTC 2023
Tue Sep 12 12:37:09 UTC 2023
Tue Sep 12 12:37:10 UTC 2023
Tue Sep 12 12:37:11 UTC 2023
Le processus principal de ce container écrit dans la sortie standard… du container !
Comment retrouver le contenu de la sortie standard du container ?
docker logs 399f3
Tue Sep 12 12:37:01 UTC 2023
Tue Sep 12 12:37:02 UTC 2023
Tue Sep 12 12:37:03 UTC 2023
Tue Sep 12 12:37:04 UTC 2023
Tue Sep 12 12:37:05 UTC 2023
Tue Sep 12 12:37:06 UTC 2023
Tue Sep 12 12:37:07 UTC 2023
Tue Sep 12 12:37:08 UTC 2023
Tue Sep 12 12:37:09 UTC 2023
Tue Sep 12 12:37:10 UTC 2023
Tue Sep 12 12:37:11 UTC 2023
Ouf ! On n’est pas obligé de saisir l’identifiant complet ! Il suffit de fournir le nombre suffisant de caractères pour que ce soit discriminant.
Comment savoir si j’ai des containers en cours d’exécution ?
💡 Commande vue un peu plus tôt…
Comment savoir si j’ai des containers en cours d’exécution ?
docker container ls
Comment savoir si j’ai des containers en cours d’exécution ?
docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
02cbf2fb3721 cours-devops-docker-serve "/sbin/tini -g gulp …" 3 hours ago Up 3 hours 0.0.0.0:8000->8000/tcp cours-devops-docker-serve-1
ebfbe695b2ec moby/buildkit:buildx-stable-1 "buildkitd" 2 months ago Up 3 hours buildx_buildkit_exciting_williams0
Comment savoir si j’ai des containers en cours d’exécution ?
docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
02cbf2fb3721 cours-devops-docker-serve "/sbin/tini -g gulp …" 3 hours ago Up 3 hours 0.0.0.0:8000->8000/tcp cours-devops-docker-serve-1
ebfbe695b2ec moby/buildkit:buildx-stable-1 "buildkitd" 2 months ago Up 3 hours buildx_buildkit_exciting_williams0
On obtient un tableau de tous les containers en cours d’exécution.
Il est possible de stopper un container.
docker container stop 399f3
Il est possible de stopper un container.
docker container stop 399f3
Pour redémarrer un container :
docker container start 399f3
Même les 💀.
Comment savoir si j’ai des containers stoppés ?
docker container ls --all
Comment savoir si j’ai des containers stoppés ?
docker container ls --all
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
90725f661d4e hello-world "/hello" 13 seconds ago Exited (0) 12 seconds ago hardcore_wescoff
9d0a6586b9e1 busybox "echo hello world" 22 seconds ago Exited (0) 21 seconds ago gracious_moser
368ed08a35e3 jpetazzo/clock "/bin/sh -c 'while d…" 6 minutes ago Up 5 minutes sweet_clarke
c036e57bbf05 jpetazzo/clock "/bin/sh -c 'while d…" 17 minutes ago Exited (130) 17 minutes ago cool_euclid
02cbf2fb3721 cours-devops-docker-serve "/sbin/tini -g gulp …" 3 hours ago Up 3 hours 0.0.0.0:8000->8000/tcp cours-devops-docker-serve-1
ebfbe695b2ec moby/buildkit:buildx-stable-1 "buildkitd" 2 months ago Up 4 hours buildx_buildkit_exciting_williams0
Comment savoir si j’ai des containers stoppés ?
docker container ls --all
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
90725f661d4e hello-world "/hello" 13 seconds ago Exited (0) 12 seconds ago hardcore_wescoff
9d0a6586b9e1 busybox "echo hello world" 22 seconds ago Exited (0) 21 seconds ago gracious_moser
368ed08a35e3 jpetazzo/clock "/bin/sh -c 'while d…" 6 minutes ago Up 5 minutes sweet_clarke
c036e57bbf05 jpetazzo/clock "/bin/sh -c 'while d…" 17 minutes ago Exited (130) 17 minutes ago cool_euclid
02cbf2fb3721 cours-devops-docker-serve "/sbin/tini -g gulp …" 3 hours ago Up 3 hours 0.0.0.0:8000->8000/tcp cours-devops-docker-serve-1
ebfbe695b2ec moby/buildkit:buildx-stable-1 "buildkitd" 2 months ago Up 4 hours buildx_buildkit_exciting_williams0
Avec le flag --all
, on obtient un tableau de tous les containers quel que soit leur état.
Tout container stoppé peut être supprimé.
Tout container stoppé peut être supprimé.
docker container rm 90725f661d4e
Tout container stoppé peut être supprimé.
docker container rm 90725f661d4e
90725f661d4e
Tout container stoppé peut être supprimé.
docker container rm 90725f661d4e
90725f661d4e
Container "auto-nettoyant" 🗑️
Tout container stoppé peut être supprimé.
docker container rm 90725f661d4e
90725f661d4e
Container "auto-nettoyant" 🗑️
docker container run --interactive --tty --rm jpetazzo/clock
Sur un container en arrière-plan
Sur un container en arrière-plan
Il est possible d’interagir avec un container en arrière-plan en cours d’exécution.
Sur un container en arrière-plan
Il est possible d’interagir avec un container en arrière-plan en cours d’exécution.
La commande suivante permet de lancer une commande à l’intérieur d’un container.
Sur un container en arrière-plan
Il est possible d’interagir avec un container en arrière-plan en cours d’exécution.
La commande suivante permet de lancer une commande à l’intérieur d’un container.
docker container exec <containerID> echo "hello"
Sur un container en arrière-plan
Il est possible d’interagir avec un container en arrière-plan en cours d’exécution.
La commande suivante permet de lancer une commande à l’intérieur d’un container.
docker container ls
Sur un container en arrière-plan
Il est possible d’interagir avec un container en arrière-plan en cours d’exécution.
La commande suivante permet de lancer une commande à l’intérieur d’un container.
docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
368ed08a35e3 jpetazzo/clock "/bin/sh -c 'while d…" 4 hours ago Up 4 hours sweet_clarke
02cbf2fb3721 cours-devops-docker-serve "/sbin/tini -g gulp …" 7 hours ago Up 7 hours 0.0.0.0:8000->8000/tcp cours-devops-docker-serve-1
ebfbe695b2ec moby/buildkit:buildx-stable-1 "buildkitd" 2 months ago Up 7 hours buildx_buildkit_exciting_williams0
Sur un container en arrière-plan
Il est possible d’interagir avec un container en arrière-plan en cours d’exécution.
La commande suivante permet de lancer une commande à l’intérieur d’un container.
docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
368ed08a35e3 jpetazzo/clock "/bin/sh -c 'while d…" 4 hours ago Up 4 hours sweet_clarke
02cbf2fb3721 cours-devops-docker-serve "/sbin/tini -g gulp …" 7 hours ago Up 7 hours 0.0.0.0:8000->8000/tcp cours-devops-docker-serve-1
ebfbe695b2ec moby/buildkit:buildx-stable-1 "buildkitd" 2 months ago Up 7 hours buildx_buildkit_exciting_williams0
docker container exec 368ed08a35e3 echo hello
hello
Sur un container en arrière-plan
docker container exec --interactive --tty <containerID> bash
Ca fonctionne aussi en interactif !
Étapes
Lancer un container "daemon" jpetazzo/clock
Utiliser l’équivalent de tail –f
pour lire la sortie standard du container 💡
Lancer un second container "daemon" 👹
Stocker l’identifiant de ce container dans une variable du shell, en une seule commande et en jouant avec docker container ls
Stopper le container avec cet identifiant
Afficher les containers lancés 🏃📦
Afficher les containers arrêtés 🛑📦
# Lancer un container "daemon" `jpetazzo/clock`
docker container run --detach --name first-clock jpetazzo/clock
# Utiliser l'équivalent de `tail –f` pour lire la sortie standard du container 💡
docker container logs -f first-clock
# * Lancer un second container "daemon" 👹
docker container run --detach --name second-clock jpetazzo/clock
# Stocker l'identifiant de ce container dans une variable du shell, en une seule commande et en jouant avec `docker container ls`
# --filter "name=second-clock" filters the list of containers to include only those with the name "second-clock."
container_id=$(docker container ls -q --filter "name=second-clock")
# Stopper le container avec cet identifiant
docker container stop "$container_id"
# Afficher les containers lancés 🏃📦
docker container ls
# Afficher les containers arrêtés 🛑📦
docker container ls --filter "status=exited"
Relancer un des containers arrêtés.
Exécuter un `ps –ef `dans ce container
Quel est le PID du process principal ?
Vérifier que le container "tourne" toujours
Supprimer l’image (tip : docker rmi
)
Supprimer les containers
Supprimer l’image (pour de vrai cette fois)
# Relancer un des containers arrêtés.
docker container start second-clock
# Exécuter un `ps –ef `dans ce container
docker container exec second-clock ps -ef
PID USER TIME COMMAND
1 root 0:00 /bin/sh -c while date; do sleep 1; done
56 root 0:00 sleep 1
57 root 0:00 ps -ef
# Quel est le PID du process principal ?
# 1
# Vérifier que le container "tourne" toujours
docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7d7085809ad2 cours-devops-docker-serve "/sbin/tini -g gulp …" 6 minutes ago Up 5 minutes 0.0.0.0:8000->8000/tcp cours-devops-docker-serve-1
edea0c46c6d8 jpetazzo/clock "/bin/sh -c 'while d…" 9 minutes ago Up About a minute second-clock
ebfbe695b2ec moby/buildkit:buildx-stable-1 "buildkitd" 2 months ago Up 11 hours buildx_buildkit_exciting_williams0
# Supprimer l'image (tip : `docker rmi`)
docker rmi jpetazzo/clock
Error response from daemon: conflict: unable to remove repository reference "jpetazzo/clock" (must force) - container edea0c46c6d8 is using its referenced image 7a8965d6553e
# Supprimer les containers
docker stop second-clock
second-clock
docker rm second-clockl
second-clock
# Supprimer l'image (pour de vrai cette fois)
docker rmi jpetazzo/clock
Untagged: jpetazzo/clock:latest
Untagged: jpetazzo/clock@sha256:dc06bbc3744f7200404bff0bbb2516925e7adea115e07de9da8b36bf15fe3dd3
Deleted: sha256:7a8965d6553ea2289485744ef20521e0dd0d12ab51773f271882913b79813750
Deleted: sha256:67f770da229bf16d0c280f232629b0c1f1243a884df09f6b940a1c7288535a6d
Exécutez un conteneur, basé sur l’image nginx
en tâche de fond ("Background"), nommé webserver-1
💡 On parle de processus "détaché" (ou bien "démonisé") 👹
⚠️ Pensez bien à docker container ls
Regardez le contenu du fichier /etc/os-release
dans ce conteneur
💡 docker container exec
Essayez d’arrêter, démarrer puis redémarrer le conteneur
⚠️ Pensez bien à docker container ls
à chaque fois
💡 stop
, start
, restart
docker container run --detach --name=webserver-1 nginx
# <ID du conteneur>
docker container ls
docker container ls --all
docker container exec webserver-1 cat /etc/os-release
# ... Debian ...
docker container stop webserver-1
docker container ls
docker container ls --all
docker container start webserver-1
docker container ls
docker container ls --all
docker container start webserver-1
docker container ls
Vous savez désormais:
Maîtriser le cycle de vie des containers
Interagir avec les containers existants
Docker essaye de résoudre le problème de l’empaquetage le plus "portable" possible
On n’en a pas encore vu les effets, ça arrive !
Vous avez vu qu’un containeur permet d’exécuter une commande dans un environnement "préparé"
Catalogue d’images Docker par défaut : Le Docker Hub
Vous avez vu qu’on peut exécuter des conteneurs selon 3 modes :
"One shot"
Interactif
En tâche de fond
⇒ 🤔 Mais comment ces images sont-elles fabriquées ? Quelle confiance leur accorder ?
Un conteneur est toujours exécuté depuis une image.
Une image de conteneur (ou "Image Docker") est un modèle ("template") d’application auto-suffisant.
⇒ Permet de fournir un livrable portable (ou presque).
C’est une collection de fichiers et de metadonnées.
C’est une collection de fichiers et de metadonnées.
C’est une suite de couches superposées.
C’est une collection de fichiers et de metadonnées.
C’est une suite de couches superposées.
C’est une collection de fichiers et de metadonnées.
C’est une suite de couches superposées.
Exemple d’une image Apache HTTPd "custom"
Exemple d’une image Apache HTTPd "custom"
Exemple d’une image Apache HTTPd "custom"
Exemple d’une image Apache HTTPd "custom"
Exemple d’une image Apache HTTPd "custom"
Exemple d’une image Apache HTTPd "custom"
"L’image est à la classe ce que le container est à l’objet"
🤔 Application Auto-Suffisante ?
Un simple fichier nommé "Dockerfile" (majuscule sur le D et pas d’extension).
Un simple fichier nommé "Dockerfile" (majuscule sur le D et pas d’extension).
C’est du texte, très pratique à stocker dans Git.
Un simple fichier nommé "Dockerfile" (majuscule sur le D et pas d’extension).
C’est du texte, très pratique à stocker dans Git.
Une suite de clef-valeur.
❗️ Problème :
cat /etc/os-release
# ...
git --version
# ...
# Même version de Linux que dans GitPod
docker container run --rm ubuntu:22.04 git --version
# docker: Error response from daemon: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "git": executable file not found in $PATH: unknown.
# En interactif ?
docker container run --rm --tty --interactive ubuntu:22.04 git --version
But : fabriquer une image Docker qui contient git
Dans votre workspace Gitpod, créez un dossier nommé docker-git/
Dans ce dossier, créer un fichier Dockerfile
avec le contenu ci-dessous :
FROM alpine:3.22.0
RUN apk update && apk add --no-cache git
Fabriquez votre image avec la commande docker image build --tag=docker-git chemin/vers/docker-git/
Testez l’image fraîchement fabriquée
💡 docker image ls
cat <<EOF >Dockerfile
FROM alpine:3.22.0
RUN apk update && apk add --no-cache git
EOF
docker image build --tag=docker-git ./
docker image ls | grep docker-git
# Doit fonctionner
docker container run --rm docker-git:latest git --version
Un peu de cuisine…
💪🏁 DÉFI
Créer une image alpine avec un JRE installé.
📖🍳 RECETTE
On part d’une image Alpine
On installe un JRE
FROM alpine:3.22.0
LABEL maintainer="Tony Stark"
RUN apk update
RUN apk add --no-cache openjdk17-jre-headless
docker image build -t myjava:1.42 .
Quand le build se termine, il se trouve dans le registre local.
docker images
REPOSITORY TAG IMAGE ID
myjava 1.42 d3017f59d5e2
docker container run --interactive --tty myjava:1.42 sh
/ $
/ $ java -version
openjdk version "17.0.8" 2023-07-18
OpenJDK Runtime Environment (build 17.0.8+7-alpine-r0)
OpenJDK 64-Bit Server VM (build 17.0.8+7-alpine-r0, mixed mode, sharing)
On les trouve où, ces images ?
En local, on l’a vu.
Dans les registres Docker.
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
jenkinsciinfra/jenkins-agent-ubuntu-22.04 latest c87afa001ba1 25 hours ago 6.89GB
cours-devops-docker-serve latest 6b7b6a3145fd 27 hours ago 427MB
cours-devops-docker-qrcode latest 43f91abb9cfa 27 hours ago 427MB
jenkins/jenkins 2.419-rhel-ubi8-jdk11 9e2076ee44fa 3 days ago 507MB
jenkins/jenkins 2.419-slim b93a74bd73b4 3 days ago 394MB
jenkins/jenkins 2.419 2193a96f254a 3 days ago 478MB
jenkins/jenkins 2.419-jdk11 2193a96f254a 3 days ago 478MB
jenkins/jenkins 2.419-alpine f700f6333bf2 3 days ago 249MB
jenkins/jenkins 2.419-rhel-ubi9-jdk17 0dbee3b2c2fc 3 days ago 485MB
jenkins/jenkins 2.419-slim-jdk17 d9a360e0a9bf 3 days ago 393MB
jenkins/jenkins 2.419-jdk17 1695080429f5 3 days ago 476MB
jenkins/jenkins 2.419-alpine-jdk17 2ea0017744c8 3 days ago 249MB
jenkins/jenkins 2.419-rhel-ubi9-jdk21-preview 66ee1f18309d 3 days ago 494MB
jenkins/jenkins 2.419-slim-jdk21-preview e31d85782d2b 3 days ago 414MB
jenkins/jenkins 2.419-jdk21 c1e6c123a3c7 3 days ago 485MB
jenkins/jenkins 2.419-alpine-jdk21-preview 97764348fde6 3 days ago 261MB
jdk21 latest ba476a3f2cd9 5 days ago 218MB
mycurl 1.0 292bf6b4a4df 6 days ago 13.3MB
myjava 1.42 05b6d3da385e 6 days ago 198MB
<none> <none> 7bc71c53e776 7 days ago 427MB
<none> <none> b45a84c7d06b 7 days ago 427MB
jenkins/jenkins latest-jdk21-preview 5b5828e392bf 8 days ago 485MB
moby/buildkit buildx-stable-1 9291fad3b41c 4 weeks ago 172MB
alpine latest 7e01a0d0a1dc 6 weeks ago 7.34MB
busybox latest a416a98b71e2 2 months ago 4.26MB
docker/volumes-backup-extension 1.1.4 6872a696b721 3 months ago 119MB
portainer/portainer-docker-extension 2.18.3 3d18fe6d6805 4 months ago 273MB
felipecruz/alpine-tar-zstd latest 31988344315d 12 months ago 6.99MB
justincormack/nsenter1 latest c81481184b1b 5 years ago 101kB
Ce sont des plates-formes qui hébergent les images.
Il est possible de créer ses propres registres (ex : registre privé d’entreprise)
Avant d’instancier un container, il faut récupérer l’image en local.
Méthode explicite :
docker image pull mysql
Méthode implicite :
docker container run -d mysql
$ docker image pull mysql
Using default tag: latest
latest: Pulling from library/mysql
5f70bf18a086: Pull complete
a734b0ff4ca6: Already exists
ec46eb0ce0a7: Pull complete
a74b383379bc: Pull complete
Digest: sha256:42dc1b67073f7ebab1...8c8d36c9031e408db0d
Status: Downloaded newer image for mysql:latest
Les couches déjà présentes en local ne sont pas téléchargées de nouveau !
Une même image peut avoir plusieurs noms et tags !
Le tag "latest" est régulièrement réaffecté sur les registres distants.
Plusieurs noms pour une signature
Une même image peut avoir plusieurs noms et tags !
Le tag "latest" est régulièrement réaffecté sur les registres distants.
Plusieurs noms pour une signature
Tags disponibles pour MySQL
Pour résumer…
[REGISTRY/][NAMESPACE/]NAME[:TAG|@DIGEST]
Pas de Registre ? Défaut: registry.docker.com
Pas de Namespace ? Défaut: library
Pas de tag ? Valeur par défaut: latest
⚠️ Friends don’t let friends use latest
👫
Digest: signature unique basée sur le contenu
ubuntu:22.04
⇒ registry.docker.com/library/ubuntu:22.04
dduportal/docker-asciidoctor
⇒ registry.docker.com/dduportal/docker-asciidoctor:latest
ghcr.io/dduportal/docker-asciidoctor:1.3.2@sha256:xxxx
Rappel : ⚠️ Friends don’t let friends use latest
👫
Il est temps de "taguer" votre première image !
docker image tag docker-git:latest docker-git:1.0.0
Testez le fonctionnement avec le nouveau tag
Comparez les 2 images dans la sortie de docker image ls
docker image tag docker-git:latest docker-git:1.0.0
# 2 lignes
docker image ls | grep docker-git
# 1 ligne
docker image ls | grep docker-git | grep latest
# 1 ligne
docker image ls | grep docker-git | grep '1.0.0'
# Doit fonctionner
docker container run --rm docker-git:1.0.0 git --version
Mettez à jour votre image en version 1.1.0
avec les changements suivants :
Ajoutez un LABEL
dont la clef est description
(et la valeur de votre choix)
Configurez git
pour utiliser une branche main
par défaut au lieu de master
(commande git config --global init.defaultBranch main
)
Indices :
💡 Commande docker image inspect <image name>
💡 Commande git config --get init.defaultBranch
(dans le conteneur)
💡 Ajoutez des lignes à la fin du Dockerfile
cat ./Dockerfile
FROM ubuntu:22.04
RUN apt-get update && apt-get install --yes --no-install-recommends git
LABEL description="Une image contenant git préconfiguré"
RUN git config --global init.defaultBranch main
docker image build -t docker-git:1.1.0 ./docker-git/
# Sending build context to Docker daemon 2.048kB
# Step 1/4 : FROM ubuntu:22.04
# ---> e40cf56b4be3
# Step 2/4 : RUN apt-get update && apt-get install --yes --no-install-recommends git
# ---> Using cache
# ---> 926b8d87f128
# Step 3/4 : LABEL description="Une image contenant git préconfiguré"
# ---> Running in 0695fc62ecc8
# Removing intermediate container 0695fc62ecc8
# ---> 68c7d4fb8c88
# Step 4/4 : RUN git config --global init.defaultBranch main
# ---> Running in 7fb54ecf4070
# Removing intermediate container 7fb54ecf4070
# ---> 2858ff394edb
Successfully built 2858ff394edb
Successfully tagged docker-git:1.1.0
docker container run --rm docker-git:1.0.0 git config --get init.defaultBranch
docker container run --rm docker-git:1.1.0 git config --get init.defaultBranch
# main
Step 2/4 : RUN apt-get update && apt-get install --yes --no-install-recommends git
---> Using cache
🤔 En fait, Docker n’a PAS exécuté cette commande la seconde fois ⇒ ça va beaucoup plus vite !
🎓 Essayez de voir les layers avec (dans Gitpod) dive <image>:<tag>
But : manipuler le cache d’images
Commencez par vérifier que le cache est utilisé : relancez la dernière commande docker image build
(plusieurs fois s’il le faut)
Invalidez le cache en ajoutant le paquet APT make
à installer en même temps que git
⚠️ Tag 1.2.0
Vérifiez que le cache est bien présent de nouveau
# Build one time
docker image build -t docker-git:1.1.0 ./docker-git/
# Second time is fully cached
docker image build -t docker-git:1.1.0 ./docker-git/
cat Dockerfile
# FROM ubuntu:22.04
# RUN apt-get update && apt-get install --yes --no-install-recommends git make
# LABEL description="Une image contenant git préconfiguré"
# RUN git config --global init.defaultBranch main
# Build one time
docker image build -t docker-git:1.2.0 ./docker-git/
# Second time is fully cached
docker image build -t docker-git:1.2.0 ./docker-git/
## Vérification
# Renvoie une erreur
docker run --rm docker-git:1.1.0 make --version
# Doit fonctionner
docker run --rm docker-git:1.2.0 make --version
Deux instructions permettent de définir la commande à lancer au démarrage du container.
Deux instructions permettent de définir la commande à lancer au démarrage du container.
Laquelle choisir ???
Cas d’usage : énoncé du besoin.
Besoin : je veux utiliser cURL
mais il n’est pas présent sur la machine hôte.
Facile !
docker container run --rm curlimages/curl curl -x http://prx:3128 -L --connect-timeout 60 "http://google.com"
Cas d’usage. On va s’outiller!
FROM alpine:3.22
LABEL maintainer="John Doe"
RUN apk update && apk add curl
# Si vous utilisez le proxy de l'Université
CMD ["curl","-x", "http://prx:3128", "-L", "--connect-timeout", "60", "http://google.com"]
# Si vous utilisez votre propre proxy docker
# CMD ["curl","-x", "http://host.docker.internal:3128", "-L", "--connect-timeout", "60", "http://google.com"]
docker image build -t my-curl:1.0 .
docker container run --rm my-curl:1.0
<!doctype html><html [...]
google blahblahblah [...]
</html>
Cas d’usage.
Cas d’usage.
Cas d’usage: un cran plus loin.
L’image actuelle, c’est bien mais pas hyper flexible !
Et si on la rendait paramétrable ?
docker container run --rm my-curl:2.0 http://google.com
docker container run --rm my-curl:2.0 http://facebook.com
docker container run --rm my-curl:2.0 http://twitter.com
Cas d’usage, un cran plus loin
Paramétrable, et hop!
FROM alpine:3.22
LABEL maintainer="John Doe"
RUN apk update && apk add curl
# Si vous utilisez le proxy de l'Université
ENTRYPOINT ["curl","-x", "http://prx:3128", "-L", "--connect-timeout", "60"]
# Si vous utilisez votre propre proxy docker
# CMD ["curl","-x", "http://host.docker.internal:3128", "-L", "--connect-timeout", "60"]
docker image build -t my-curl:2.0 .
docker container run --rm my-curl:2.0 http://google.com
<!doctype html><html [...]
google blahblahblah [...]
</html>
Cas d’usage, un cran plus loin
Paramétrable, et hop!
FROM alpine:3.22
LABEL maintainer="John Doe"
RUN apk update && apk add curl
# Si vous utilisez le proxy de l'Université
ENTRYPOINT ["curl","-x", "http://prx:3128", "-L", "--connect-timeout", "60"]
# Si vous utilisez votre propre proxy docker
# CMD ["curl","-x", "http://host.docker.internal:3128", "-L", "--connect-timeout", "60"]
docker image build -t my-curl:2.0 .
docker container run --rm my-curl:2.0 http://google.com
<!doctype html><html [...]
google blahblahblah [...]
</html>
Une image Docker fournit un environnement de système de fichier auto-suffisant (application, dépendances, binaries, etc.) comme modèle de base d’un conteneur
Les images Docker ont une convention de nommage permettant d’identifier les images très précisément
On peut spécifier une recette de fabrication d’image à l’aide d’un Dockerfile
et de la commande docker image build
⇒ 🤔 et si on utilisait Docker pour nous aider dans l’intégration continue ?
docker container run --interactive --tty httpd pwd /usr/local/apache2
Comment paramétrer le répertoire de travail de tous nos containers ?
FROM alpine:3.22
WORKDIR /repertoire/travail
RUN echo hello > ./world.txt
FROM alpine:3.22
WORKDIR /repertoire/travail
RUN echo hello > ./world.txt
FROM alpine:3.22
WORKDIR /repertoire/travail
RUN echo hello > ./world.txt
FROM alpine:3.22
WORKDIR /repertoire/travail
RUN echo hello > ./world.txt
docker image build --tag mon-img .
...
$ docker container run mon-img cat /repertoire/travail/world.txt
hello
$ docker container run mon-img pwd
/repertoire/travail
$ docker container run --workdir /home mon-img pwd
/home
Cas d’usage.
ls
app.js
$ docker container run --workdir /customdir node app.js
<error : app.js not found>
$ docker container run --workdir /customdir --entrypoint ls node
<0 fichiers présents !>
Comment fournir des fichiers à mes containers ?
Copie de fichiers.
FROM node:22.2.0-alpine3.18
WORKDIR /app
COPY ./* /app
docker image build --tag mon-node .
...
$ docker container run --workdir /app --entrypoint ls mon-node
app.js
$ docker container run mon-node app.js
Hello Mad Javascript World!
Le mot clé ADD
.
FROM image
ADD https://mon-nexus/foo/bar/1.0/package.tar /uncompressed/
Il permet d’ajouter des fichiers aux images, tout comme COPY
.
Il est capable de télécharger directement d’une URL
Il est capable de "untar" automatiquement.
On le retrouve dans d’anciens Dockerfile mais il tend à disparaître.
Le mot clé ADD
Le mot clé ADD
.
Oui, mais pas la terre entière !
docker image build --tag myjava:1.42 ./
Sending build context to Docker daemon 172.8MB
Oui, mais pas la terre entière !
Oui, mais pas la terre entière !
Oui, mais pas la terre entière !
Si un gros fichier traîne dans le dossier alors qu’il n’est même pas utilisé ni référencé dans le Dockerfile, il sera tout de même envoyé au Docker daemon.
Oui, mais pas la terre entière !
"M’en fous ! j’ai une machine de fou et un réseau de fou ! YOLO !"
Et si la CI fait plusieurs builds par minute ?
Et si un vieux Keystore traîne dans les sources ?
.git/
? .idea/
? vraiment ?
Attention à l’invalidation de cache d’un step de build à cause de l’ajout d’un fichier inutile !
.dockerignore
# ignore les dossiers .git et .cache
.git
.cache
# ignore tous les fichiers *.class dans tous les dossiers
**/*.class
# ignore les markdown sauf les README*.md (README-secret.md sera tout de même ignoré par contre)
*.md
!README*.md
README-secret.md
httpd:latest
sera taggué en fedora/httpd:version1.0
Le bilan
Vous savez désormais:
Rédiger un Dockerfile
Nommer vos images
Créer vos outils
Le bilan
Vous savez désormais:
Rédiger un Dockerfile
Nommer vos images
Créer vos outils
La construction d’une image doit être automatisée.
Le fait de déléguer la construction des images permet d’ajouter toute une chaîne de traitement, de contrôles des images pour s’assurer qu’elle respecte les règles RSSI.
patch management
droits sur le FS
user
diff --git a/src/Dockerfile b/src/Dockerfile
index c92c67cd48121705628f67a2af14b2a65e54cdfd..77919a581fffde7c57f43c8b7cbee2a8d84b628b 100644
--- a/src/Dockerfile
+++ b/src/Dockerfile
@@ -9,6 +9,10 @@ COPY start.sh /bin/start.sh
EXPOSE 4321
# This line opens port 4321 on the container. It's like installing a door in your house.
+RUN adduser --disabled-password --gecos "" --home /home/www www
+USER www
+WORKDIR /home/www
+
ENTRYPOINT ["/bin/start.sh"]
# This line sets the command that will be run when the container starts. It's like setting the alarm clock in your house.
diff --git a/src/start.sh b/src/start.sh
index 44006d6cd8414706a5fcf564dbfd1e9c38d4bc0b..a6ed60fdf88759d4962685e7ceb6cdbc21867448 100755
--- a/src/start.sh
+++ b/src/start.sh
@@ -4,7 +4,7 @@
set -eux
# These are shell options. 'e' exits on error, 'u' treats unset variables as an error, and 'x' prints each command to the terminal. It's like the script's personal assistant, making sure everything runs smoothly.
-touch /home/www/started.time
+touch /tmp/started.time
# This creates a file named 'started.time' in the '/home/www' directory. It's like the script's way of marking its territory.
if [ $? -ne 0 ]; then
@@ -12,7 +12,7 @@ if [ $? -ne 0 ]; then
fi
# This checks the exit status of the last command. If it's not zero, which means there was an error, it exits the script. It's like the script's way of saying, "If I can't do it right, I won't do it at all."
-date > /home/www/started.time
+date > /tmp/started.time
# This writes the current date and time to the 'started.time' file. It's like the script's way of keeping a diary.
exec "$@"
stages:
- "validator.sh"
- "docker scout"
variables:
PROXY: "http://cache-etu.univ-artois.fr:3128"
IMAGE: "devops-docker-tp05:latest"
validator-job:
image: cache-ili.univ-artois.fr/proxy_cache/library/docker
stage: "validator.sh"
before_script:
- export HTTP_PROXY="$PROXY"
- export HTTPS_PROXY="$PROXY"
- apk add -U bash
script:
- errors=$(./validator.sh)
- echo "$errors"
- exit $([ -z "$errors" ] && echo 0 || echo 1)
tags:
- docker2
# No login needed, using docker desktop with wsl
scout-job:
stage: "docker scout"
script:
- cd ./src
- docker build . -t "$IMAGE"
- docker scout cves --format only-packages --only-vuln-packages "$IMAGE"
- docker rmi "$IMAGE"
tags:
- myshell
stages:
- run_script
- docker_scan
run_script:
stage: run_script
script:
- chmod +x validator.sh src/start.sh && ./validator.sh # Makes the scripts executable and runs validator.sh
- |
if [ $? -eq 0 ]; then
echo "Script exited successfully."
else
echo "Script exited with an error."
exit 1 # Mark the job as a failure
fi
docker_scan:
stage: docker_scan
script:
- IMG=$(echo img$$)
- docker image build --tag $IMG ./src > /dev/null
- echo "Will scan $IMG"
- echo $DOCKER_TOKEN | docker login -u $DOCKER_USERNAME --password-stdin
- docker scout cves --format only-packages --only-vuln-packages $IMG # Runs docker scout to scan the image
- docker scout recommendations $IMG # View base image update recommendations
Ne peut-on pas trouver plus 'user-friendly'?
Humour de g33k
docker container run --detach --name my-web httpd
Nom du container explicite
docker container logs my-web
docker exec --interactive --tty my-web sh
Attention, spoiler pour les n00bs de DC Comics
docker container rename clark_kent superman
docker container inspect my-web
[
{
"Id": "0ac8cdf8d447c6d316a04bd1a7f74cd2677eea3478f11f0be5241c1bb2d4c7da",
"Created": "2023-10-24T19:40:11.84788911Z",
"Path": "httpd-foreground",
"Args": [],
"State": {
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 3275,
"ExitCode": 0,
"Error": "",
"StartedAt": "2023-10-24T19:40:12.363841649Z",
"FinishedAt": "0001-01-01T00:00:00Z"
},
...
]
JQ est le meilleur outil pour parser du JSON dans le shell
docker container inspect my-web | jq .
Les templates de GO peuvent être utilisés directement
docker container inspect --format '{{json .Created }}' my-web
Étapes:
Lancer un container HTTPd
Trouver la syntaxe permettant d’extraire le "CMD" qui a été passé au démarrage du container
Trouver une seconde méthode pour faire la même chose
On démarre un container nginx avec la commande
# Here we're using the 'docker container run' command to start a new Docker container.
# The '--detach' option tells Docker to run the container in the background and print the container ID.
# The '--name' option allows us to give our container a custom name, in this case 'my-web'.
# Finally, 'nginx' is the name of the Docker image we want to use to create the container.
# So, to sum up, this command will start a new Docker container in the background, using the 'nginx' image, and name it 'my-web'.
docker container run --detach --name my-web nginx
Une solution facile est de faire:
# In this command, we're using 'docker container inspect' to get detailed information about our 'my-web' container.
# The '--format' option allows us to specify the output format. Here, we're using the Go template '{{json .Config.Cmd }}' to get the command used to start the container in JSON format.
# We then pipe this JSON output to 'jq', a powerful command-line JSON processor.
# The '.' in 'jq .' tells 'jq' to take the input JSON and output it as is, effectively pretty-printing it.
# So, to sum up, this command will give us the command used to start the 'my-web' container, in a pretty-printed JSON format.
docker container inspect --format '{{json .Config.Cmd }}' my-web | jq .
Ça nous donne:
[
"nginx",
"-g",
"daemon off;"
]
[
"nginx",
"-g",
"daemon off;"
]
Qu’on pourrait nettoyer pour avoir nginx -g daemon off;
avec:
# This command is a symphony in three parts. First, we're using 'docker container inspect' to get detailed information about our 'my-web' container.
# The '--format' option allows us to specify the output format. Here, we're using the Go template '{{json .Config.Cmd }}' to get the command used to start the container in JSON format.
# We then pipe this JSON output to 'jq', a powerful command-line JSON processor. The '-r' option tells 'jq' to output raw strings instead of JSON-encoded strings, and '.[]' tells 'jq' to output the elements of the array.
# But we're not done yet. We then pipe this output to 'tr', which replaces the newlines with spaces, giving us a neat, single-line command.
# So, to sum up, this command will give us the command used to start the 'my-web' container, as a single line of text.
docker container inspect --format '{{json .Config.Cmd }}' my-web | jq -r '.[]' | tr '\n' ' '
qui donne nginx -g daemon off;
.
Une autre solution serait:
# This command is using 'docker container ls' to list our Docker containers. The '--filter' option allows us to only show containers with the name 'my-web', and '--no-trunc' prevents Docker from truncating the output.
# We then pipe this output to 'awk', a powerful text processing tool. 'NR==1 {cmd=index($0, "COMMAND"); created=index($0, "CREATED")}' tells 'awk' to find the positions of the 'COMMAND' and 'CREATED' headers in the first line of the output.
# 'NR>1 {print substr($0, cmd, created-cmd)}' then tells 'awk' to print the substring from the start of the 'COMMAND' field to the start of the 'CREATED' field for the rest of the lines.
# So, to sum up, this command will give us the command used to start the 'my-web' container, extracted from the output of 'docker container ls'.
docker container ls --filter "name=my-web" --no-trunc | awk 'NR==1 {cmd=index($0, "COMMAND"); created=index($0, "CREATED")} NR>1 {print substr($0, cmd, created-cmd)}'
# This command is using 'docker container ls' to list our Docker containers. The '--filter' option allows us to only show containers with the name 'my-web', and '--no-trunc' prevents Docker from truncating the output.
# We then pipe this output to 'awk', a powerful text processing tool. 'NR==1 {cmd=index($0, "COMMAND"); created=index($0, "CREATED")}' tells 'awk' to find the positions of the 'COMMAND' and 'CREATED' headers in the first line of the output.
# 'NR>1 {print substr($0, cmd, created-cmd)}' then tells 'awk' to print the substring from the start of the 'COMMAND' field to the start of the 'CREATED' field for the rest of the lines.
# So, to sum up, this command will give us the command used to start the 'my-web' container, extracted from the output of 'docker container ls'.
docker container ls --filter "name=my-web" --no-trunc | awk 'NR==1 {cmd=index($0, "COMMAND"); created=index($0, "CREATED")} NR>1 {print substr($0, cmd, created-cmd)}'
qui donnerait "/docker-entrypoint.sh nginx -g 'daemon off;'"
.
docker container run --detach nginx
bd12c4d7110d17ce80...`
docker container ls
CONTAINERID IMAGE COMMAND CREATED STATUS PORTS
bd12c4d71 nginx "nginx …" 35 s. ago Up 33 s. 80/tcp, 443/tcp
Ok, mon container expose un service sur les ports TCP 80
et 443
mais j’appelle comment mon Nginx ?
"Networks": {
"bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"NetworkID": "c0fcec43e3e8…eecd6558ac0870a468a3",
"EndpointID": "0ec8fb237a10e9227359b4…db23edc32",
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:11:00:02",
"DriverOpts": null
}
}
docker container inspect --format "{{.NetworkSettings.IPAddress}}" <ContainerID>
curl -I --noproxy '*' http://172.17.0.2:80
HTTP/1.1 200 OK
....
`Server: nginx/1.25 ...
`
Il est maintenant facile d’interagir avec notre serveur web !
curl -I --noproxy '*' http://172.17.0.2:80
curl -I --noproxy '*' http://172.17.0.2:80
HTTP/1.1 200 OK
Server: nginx/1.25.2
Date: Wed, 18 Oct 2023 11:54:49 GMT
Content-Type: text/html
Content-Length: 284237
Last-Modified: Tue, 10 Oct 2023 12:12:13 GMT
Connection: keep-alive
ETag: "65253f9d-4564d"
Accept-Ranges: bytes
$ curl -I --noproxy '*' http://localhost:8000`
HTTP/1.1 200 OK
Server: nginx/1.25.2
Date: Wed, 18 Oct 2023 11:57:25 GMT
Content-Type: text/html
Content-Length: 284237
Last-Modified: Tue, 10 Oct 2023 12:12:13 GMT
Connection: keep-alive
ETag: "65253f9d-4564d"
Accept-Ranges: bytes
Étapes:
Créer un fichier HTML et le distribuer à partir d’un container nginx
Récupérer un war sur Gitlab et le déployer dans un container wildfly (https://gitlab.univ-artois.fr/bruno.verachten/devops-docker-tp07/-/raw/master/sample.war)
Récupérer le code sur https://spring.io/guides/gs/spring-boot/ et faire tourner l’appli (sous répertoire complete
)
<html><head></head><body><header>
<title>http://info.cern.ch</title>
</header>
<h1>http://info.cern.ch - home of the first website</h1>
<p>From here you can:</p>
<ul>
<li><a href="http://info.cern.ch/hypertext/WWW/TheProject.html">Browse the first website</a></li>
<li><a href="http://line-mode.cern.ch/www/hypertext/WWW/TheProject.html">Browse the first website using the line-mode browser simulator</a></li>
<li><a href="http://home.web.cern.ch/topics/birth-web">Learn about the birth of the web</a></li>
<li><a href="http://home.web.cern.ch/about">Learn about CERN, the physics laboratory where the web was born</a></li>
</ul>
</body></html>
docker run --name my-nginx -v /fully/qualified/path/on/my/computer/:/usr/share/nginx/html:ro -d -p 8000:80 nginx
curl --noproxy '*' http://localhost:8000
<html><head></head><body><header>
<title>http://info.cern.ch</title>
</header>
<h1>http://info.cern.ch - home of the first website</h1>
<p>From here you can:</p>
<ul>
<li><a href="http://info.cern.ch/hypertext/WWW/TheProject.html">Browse the first website</a></li>
<li><a href="http://line-mode.cern.ch/www/hypertext/WWW/TheProject.html">Browse the first website using the line-mode browser simulator</a></li>
<li><a href="http://home.web.cern.ch/topics/birth-web">Learn about the birth of the web</a></li>
<li><a href="http://home.web.cern.ch/about">Learn about CERN, the physics laboratory where the web was born</a></li>
</ul>
</body></html>
wget https://gitlab.univ-artois.fr/bruno.verachten/devops-docker-tp07/-/raw/master/sample.war
# Used to run a Docker container with the name "wildfly-container" using the "jboss/wildfly" image. The container is
# configured to expose ports 8080 and 9990 on the host machine, which will be mapped to the corresponding ports inside
# the container. Additionally, a volume is mounted to the container, linking the
# "/fully/qualified/path/on/my/computer/sample.war" file to "/opt/jboss/wildfly/standalone/deployments/sample.war"
# path within the container. This allows the WildFly server running inside the container to access and deploy the
# "sample.war" file. The "-d" flag is used to run the container in detached mode, meaning it will run in the
# background.
docker run -d -p 8080:8080 -p 9990:9990 --name wildfly-container -v /fully/qualified/path/on/my/computer/sample.war:/opt/jboss/wildfly/standalone/deployments/sample.war jboss/wildfly
Visitez avec votre navigateur http://localhost:8080/sample/
.
git clone https://github.com/spring-guides/gs-spring-boot.git
C’est un début…
cd gs-spring-boot/complete
mvn package
java -jar target/spring-boot-complete-0.0.1-SNAPSHOT.jar
Ok, il y a de l’idée…
Assemblons tout ça dans un Dockerfile
.
# Use a Maven image for building the application
FROM maven:3.9.8-eclipse-temurin-22-alpine
WORKDIR /app
# Clone the Spring Boot application repository
RUN apk add git && git clone https://github.com/spring-guides/gs-spring-boot.git
# Change directory to the application's complete directory
WORKDIR /app/gs-spring-boot/complete
# Build the application using Maven
RUN mvn package
WORKDIR /app/gs-spring-boot/complete/target
# Command to run the Spring Boot application
CMD ["java", "-jar", "spring-boot-complete-0.0.1-SNAPSHOT.jar"]
Spoiler alert: le nom de fichier donne une indication…
docker build -t spring-boot-app --file Dockerfile.ugly .
Et logiquement…
docker run -d -p 8080:8080 --name spring-boot-container spring-boot-app
Ça fonctionne, mais c’est comme si je laissais la grue dans la chambre une fois que j’avais fini de construire ma maison !
On essaye un Dockerfile
moins BTP?
# Use a Maven image for building the application
FROM maven:3.9.8-eclipse-temurin-22-alpine AS build
WORKDIR /app
# Clone the Spring Boot application repository
RUN apk add git && git clone https://github.com/spring-guides/gs-spring-boot.git
# Change directory to the application's complete directory
WORKDIR /app/gs-spring-boot/complete
# Build the application using Maven
RUN mvn package
# Use a lightweight Java image for running the application
FROM eclipse-temurin:24.0.1_9-jre-alpine
WORKDIR /app
# Copy the built JAR file from the previous build stage
COPY --from=build /app/gs-spring-boot/complete/target/spring-boot-complete-0.0.1-SNAPSHOT.jar ./
# Command to run the Spring Boot application
CMD ["java", "-jar", "spring-boot-complete-0.0.1-SNAPSHOT.jar"]
Le multistage, c’est la classe à Arras. On en reparle après.
Avec un autre exemple… Une image pour construire l’appli:
FROM golang:1.24
WORKDIR /go/src/github.com/alexellis/href-counter/
COPY go.mod .
COPY go.sum .
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags "-s -w" -o app .
Une image pour faire tourner l’appli
FROM alpine:3.22.0
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY app .
CMD ["./app"]
Avec un autre exemple
#!/bin/sh
echo Building alexellis2/href-counter:build
docker image build --build-arg https_proxy=$https_proxy --build-arg http_proxy=$http_proxy \
-t alexellis2/href-counter:build . -f Dockerfile.build
docker container create --name extract alexellis2/href-counter:build
docker container cp extract:/go/src/github.com/alexellis/href-counter/app ./app
docker container rm -f extract
echo Building alexellis2/href-counter:latest
docker image build --no-cache -t alexellis2/href-counter:latest .
rm ./app
Avec un autre exemple
Avec un autre exemple
Avec un autre exemple
Avec un autre exemple
FROM golang:1.19
WORKDIR /go/src/github.com/alexellis/href-counter/
COPY go.mod .
COPY go.sum .
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags "-s -w" -o app .
FROM alpine:3.17.1
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=0 /go/src/github.com/alexellis/href-counter/app .
CMD ["./app"]
FROM golang:1.19
WORKDIR /go/src/github.com/alexellis/href-counter/
COPY go.mod .
COPY go.sum .
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags "-s -w" -o app .
FROM alpine:3.17.1
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=0 /go/src/github.com/alexellis/href-counter/app .
CMD ["./app"]
On copie les fichiers depuis le premier stage
Le bilan : achievement unlocked
Vous savez désormais:
Maîtriser le cycle de vie des containers
Interagir avec les containers existants
Nommer les containers
Inspecter les containers
Appeler les containers
Obtenir des containers légers
Partage d’un répertoire de votre système hôte avec un conteneur Docker.
Utile pour partager des fichiers ou des données de configuration avec un conteneur.
Données stockées sur le système hôte, mais accessibles depuis le conteneur.
docker container run -v /chemin/du/systeme/hote:/chemin/dans/le/conteneur
Stockage persistant ⇒ données en dehors du système de fichiers du conteneur.
Utile pour stocker des données qui doivent survivre à l’arrêt ou à la suppression d’un conteneur.
Volumes indépendants du cycle de vie du conteneur.
docker container run -v nom_du_volume:/chemin/dans/le/conteneur
Système de fichiers temporaire stocké en RAM.
Utile lorsque des données temporaires doivent être stockées en mémoire pour des performances élevées.
Les données n’existent que tant que le conteneur est en cours d’exécution.
docker container run --tmpfs /chemin/dans/le/conteneur
"Créer une image avec les sources de mon application web juste pour distribuer des ressources statiques via Apache ! Merci Docker !"
Un étudiant excédé
Partage de dossier
Partage de fichier
Le chemin dans la machine hôte doit être un chemin absolu ! |
L’exemple ci-dessus ne peut donc pas fonctionner |
Il est possible de créer des "espaces mémoire" que l’on peut mettre à disposition des containers.
docker volume create --name my-data
docker run -v my-data:/data busybox ls /data
my_data n’est plus un chemin absolu, mais juste le nom du volume |
Lister tous les volumes
docker volume ls
Supprimer un volume
docker volume rm my-data
Inspecter un volume
docker volume inspect my-data
FROM alpine:3.22
WORKDIR /repertoire/travail
VOLUME ["/data"]
RUN echo hello > ./world.txt
Création d’un volume anonyme mappé sur le répertoire /data des containers basés sur cette image
docker container run
-d
--read-only
--tmpfs /run/httpd
--tmpfs /tmp
httpd
Les données sont perdues à l’arrêt du container. |
Les données sont perdues à l’arrêt du container. |
Les données sont perdues à l’arrêt du container. |
Création d’images
Création des containers
cycle de vie
nommage
débug
réseau
volumes
Création d’images ✅
Création des containers ✅
cycle de vie ✅
nommage ✅
débug ✅
réseau ✅
volumes ✅
Lancer un container `nginx `et lui faire distribuer une page web externe au container.
En utilisant un montage de répertoire (Bind Mount) :
Utilisez un montage de répertoire pour mapper un répertoire de l’hôte vers le conteneur Nginx.
Placez vos fichiers HTML dans un répertoire sur votre machine hôte, puis mappez ce répertoire vers le conteneur Nginx.
docker container run -d -p 80:80 --name mon-nginx -v /chemin/vers/la/page/web/sur/lhote:/usr/share/nginx/html nginx
Remplacez /chemin/vers/la/page/web/sur/lhote
par le chemin réel vers le répertoire contenant vos fichiers de la page web sur l’hôte.
En utilisant un volume nommé (Named Volume) :
Créez un volume nommé et copiez vos fichiers de la page web dedans.
Montez ce volume nommé dans le conteneur Nginx.
Tout d’abord, créez un volume nommé :
docker volume create mon-nginx-html
Copiez vos fichiers de la page web dans ce volume :
docker container run --rm \
--volume mon-nginx-html:/cible \ # Montez le volume nommé "mon-nginx-html" dans le conteneur sous le répertoire "/cible".
--volume /chemin/vers/la/page/web/sur/lhote:/html \ # Montez le répertoire où se trouve le fichier html
--workdir /cible \ # Définissez le répertoire de travail à "/cible" dans le conteneur.
busybox cp -r /html . # Copiez récursivement le contenu depuis "/chemin/vers/la/page/web/sur/lhote" dans le répertoire actuel du conteneur.
Maintenant, lancez le conteneur Nginx et montez le volume nommé :
docker container run -d -p 80:80 --name mon-nginx -v mon-nginx-html:/usr/share/nginx/html nginx
Étapes:
Créer un volume nommé "data"
Démarrer un container attaché à ce volume
Lancer un processus qui écrit un fichier dans le volume
Démarrer un second container attaché à ce volume
Lire les données du volume depuis le second container
Supprimer le volume
Créer un volume nommé "data" :
docker volume create data
Démarrer un premier conteneur attaché à ce volume :
docker container run -it --name premier-container -v data:/donnees busybox
Dans le premier conteneur, lancez un processus qui écrit un fichier dans le volume (par exemple, un fichier texte) :
echo "Données du premier conteneur" > /donnees/mon-fichier.txt
Quittez le premier conteneur.
Démarrer un second conteneur attaché au même volume :
docker container run -it --name second-container -v data:/donnees busybox
Démarrer un second conteneur attaché au même volume :
docker container run -it --name second-container -v data:/donnees busybox
Dans le second conteneur, lisez les données du volume (le fichier texte) :
cat /donnees/mon-fichier.txt
Vous devriez voir le contenu du fichier affiché à l’écran.
Quittez le second conteneur.
Vous pouvez maintenant supprimer le volume "data" car vous n’en avez plus besoin :
docker volume rm data
Écrire un Dockerfile qui:
Ajoute des fichiers dans un dossier
Déclare ce dossier comme volume anonyme
Ajoute d’autres fichiers dans ce dossier
Que verra-ton dans ce dossier au sein de nos containers ?
Tout d’abord, créez un répertoire pour votre projet et placez-vous dedans.
Créez un Dockerfile avec le contenu suivant :
FROM alpine:3.22
# Ajoutez des fichiers dans un dossier
RUN mkdir /mon-dossier
RUN echo "Contenu du fichier 1" > /mon-dossier/fichier1.txt
RUN echo "Contenu du fichier 2" > /mon-dossier/fichier2.txt
# Déclarez ce dossier comme un volume anonyme
VOLUME ["/mon-dossier"]
# Ajoutez d'autres fichiers dans ce dossier
RUN echo "Nouveau contenu du fichier 3" > /mon-dossier/fichier3.txt
Ensuite, vous pouvez créer une image Docker à partir de ce Dockerfile en exécutant la commande suivante dans le même répertoire que le Dockerfile :
docker image build -t mon-image .
docker image build -t mon-image .
Assurez-vous que "mon-image" est le nom que vous souhaitez donner à votre image Docker. Après avoir créé l’image, vous pouvez exécuter un conteneur basé sur cette image :
docker container run -it mon-image
Lorsque vous êtes dans le conteneur, accédez au dossier /mon-dossier :
cd /mon-dossier
Vous verrez les fichiers "fichier1.txt", "fichier2.txt" et "fichier3.txt" dans ce dossier.
ls
fichier1.txt fichier2.txt fichier3.txt
cat *
Contenu du fichier 1
Contenu du fichier 2
Nouveau contenu du fichier 3
cat *
Contenu du fichier 1
Contenu du fichier 2
Nouveau contenu du fichier 3
Vous devriez voir la liste des fichiers et leurs contenus.
Cela montre que les fichiers que vous avez ajoutés dans le dossier, même après avoir déclaré ce dossier comme un volume anonyme, restent accessibles dans le conteneur.
C’est ainsi que vous pouvez ajouter des fichiers dans un dossier, le déclarer comme un volume anonyme, puis ajouter d’autres fichiers dans ce dossier tout en y ayant accès à l’intérieur du conteneur.
Interagir avec le Docker ENGINE
docker container run –d -p 8000:80 nginx
curl -I --noproxy '*' http://172.17.0.2:80
It works!
curl -I --noproxy '*' http://localhost:8000
It works too!
docker network ls
NETWORK ID NAME DRIVER SCOPE
11b7af7b16e4 bridge bridge local
1112832d205b cours-devops-docker_default bridge local
7ab15cc28199 docker_volumes-backup-extension-desktop-extension_default bridge local
34caf4674478 host host local
2a179c7be3b3 none null local
0c577a792771 portainer_portainer-docker-extension-desktop-extension_default bridge local
docker network ls
NETWORK ID NAME DRIVER SCOPE
11b7af7b16e4 bridge bridge local
1112832d205b cours-devops-docker_default bridge local
7ab15cc28199 docker_volumes-backup-extension-desktop-extension_default bridge local
34caf4674478 host host local
2a179c7be3b3 none null local
0c577a792771 portainer_portainer-docker-extension-desktop-extension_default bridge local
docker network inspect bridge
[
{
"Name": "bridge",
"Id": "11b7af7b16e4cf9fe42733aa1b6900ed876407e3b55e692c9dfe03505e2af19f",
"Created": "2023-10-31T15:08:44.054140179Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.17.0.0/16",
"Gateway": "172.17.0.1"
}
]
},
...
docker network create mon-reseau
docker network ls
NETWORK ID NAME DRIVER SCOPE
11b7af7b16e4 bridge bridge local
1112832d205b cours-devops-docker_default bridge local
7ab15cc28199 docker_volumes-backup-extension-desktop-extension_default bridge local
34caf4674478 host host local
46953dc9b3d5 mon-reseau bridge local
2a179c7be3b3 none null local
0c577a792771 portainer_portainer-docker-extension-desktop-extension_default bridge local
docker run –d --net=mon-reseau --name=app img
Il est opportun de nommer un container quand on l’attache à un réseau.
Docker fournit un DNS interne dans les réseaux custom. Les containers d’un même réseau peuvent se "voir" et "s’appeler" par leur noms.
docker network create mynet
docker container run -d --name web --net mynet nginx
docker container run --net mynet alpine ping web
PING web (172.21.0.2): 56 data bytes
64 bytes from 172.21.0.2: seq=0 ttl=64 time=0.442 ms
64 bytes from 172.21.0.2: seq=1 ttl=64 time=0.105 ms
64 bytes from 172.21.0.2: seq=2 ttl=64 time=0.099 ms
64 bytes from 172.21.0.2: seq=3 ttl=64 time=0.150 ms
64 bytes from 172.21.0.2: seq=4 ttl=64 time=0.114 ms
64 bytes from 172.21.0.2: seq=5 ttl=64 time=0.098 ms
64 bytes from 172.21.0.2: seq=6 ttl=64 time=0.099 ms
64 bytes from 172.21.0.2: seq=7 ttl=64 time=0.100 ms
64 bytes from 172.21.0.2: seq=8 ttl=64 time=0.114 ms
64 bytes from 172.21.0.2: seq=9 ttl=64 time=0.097 ms
^C
--- web ping statistics ---
10 packets transmitted, 10 packets received, 0% packet loss
round-trip min/avg/max = 0.097/0.141/0.442 ms
docker network connect mynet myapp
Cette commande permet d’attacher un container à un réseau après sa création.
Étapes:
Lister et inspecter les réseaux existants
Lancer un container `nginx `nommé "web1"
A quel réseau est-il attaché ?
Créer un réseau de type bridge et l’inspecter
Lancer un container nginx
nommé "web2" et l’attacher au réseau créé
L’inspection confirme-t-elle l’attachement ?
Lancer un bash dans le container "web1"
Est-ce que "web1" peut voir "web2" ?
Corriger pour que ce soit le cas
Étape 1 : Lister et inspecter les réseaux existants
# Liste tous les réseaux Docker existants
docker network ls
# Inspecte un réseau spécifique (par exemple, le réseau bridge, d'autres réseaux peuvent être inspectés)
docker network inspect bridge
Étape 2 : Lancer un container "nginx" nommé "web1"
# Lance un conteneur "nginx" nommé "web1"
docker container run --name web1 -d nginx
Étape 3 : Vérifier le réseau auquel "web1" est attaché
# Récupère l'identifiant de réseau complet pour le container "web1"
network_id=$(docker container inspect -f '{{.NetworkSettings.Networks.bridge.NetworkID}}' web1)
# Raccourcit l'identifiant du réseau aux premiers 12 caractères
shortened_network_id=$(echo $network_id | cut -c 1-12)
# Liste tous les réseaux docker, en filtrant par l'identifiant du réseau
docker network ls --format "table {{.ID}}\t{{.Name}}" | awk -v network_id="$shortened_network_id" '$1 == network_id {print $2}'
Étape 3 : Vérifier le réseau auquel "web1" est attaché
# Récupère l'identifiant de réseau complet pour le container "web1"
network_id=$(docker container inspect -f '{{.NetworkSettings.Networks.bridge.NetworkID}}' web1)
# Raccourcit l'identifiant du réseau aux premiers 12 caractères
shortened_network_id=$(echo $network_id | cut -c 1-12)
# Liste tous les réseaux docker, en filtrant par l'identifiant du réseau
docker network ls --format "table {{.ID}}\t{{.Name}}" | awk -v network_id="$shortened_network_id" '$1 == network_id {print $2}'
bridge
Étape 4 : Créer un réseau de type bridge et l’inspecter
# Crée un réseau Docker de type bridge nommé "mon-reseau"
docker network create mon-reseau
# Inspecte le réseau "mon-reseau"
docker network inspect mon-reseau
Étape 5 : Lancer un container "nginx" nommé "web2" et l’attacher au réseau créé
# Lance un conteneur "nginx" nommé "web2" et l'attache au réseau "mon-reseau"
docker container run --name web2 -d --network mon-reseau nginx
Étape 5 : Lancer un container "nginx" nommé "web2" et l’attacher au réseau créé
# Lance un conteneur "nginx" nommé "web2" et l'attache au réseau "mon-reseau"
docker container run --name web2 -d --network mon-reseau nginx
Étape 6 : Vérifier l’attachement du container "web2" au réseau
# Inspecte le conteneur "web2" pour vérifier le réseau auquel il est attaché
docker container inspect web2 | grep NetworkMode
Étape 7 : Lancer un bash dans le container "web1"
# Lance un shell interactif dans le conteneur "web1"
docker container exec -it web1 bash
Étape 8 : Vérifier si "web1" peut voir "web2"
# À l'intérieur du conteneur "web1," essayez de faire une requête HTTP vers "web2"
curl web2
curl: (6) Could not resolve host: web2
Étape 9 : Corriger pour permettre la communication entre "web1" et "web2"
# Sortez du shell du conteneur "web1" en tapant "exit"
# Attachez "web1" au même réseau "mon-reseau"
docker network connect mon-reseau web1
Après avoir suivi ces étapes, les conteneurs "web1" et "web2" devraient être attachés au même réseau "mon-reseau" et être capables de communiquer entre eux.
# Lance un shell interactif dans le conteneur "web1"
docker container exec -it web1 bash
# À l'intérieur du conteneur "web1," essayez de faire une requête HTTP vers "web2"
curl web2
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
[...]
Une application riche repose souvent sur plusieurs éléments techniques à coordonner ensemble.
(Apache, Tomcat, Node, MongoDB, ElasticSearch, Logstash, etc.)
Comment automatiser ces déploiements ?
#!/bin/sh
docker network create my-net
docker container run –d --net=my-net –p 3306:3306 mysql
docker container run –d --net=my-net –v /docs:/docs --name col1 httpd:2.4.58-bookworm
docker container run –d --net=my-net –v /docs:/docs --name col2 httpd:2.4.58-bookworm
Et tout lancer indépendamment ?
Et tout monitorer un par un ?
Peut mieux faire, non ?
services:
services:
apache:
middle:
db:
services:
apache:
image: "my-httpd:2.4"
middle:
db:
services:
apache:
image: "my-httpd:2.4"
ports:
- "80:80"
middle:
db:
services:
apache:
image: "my-httpd:2.4"
ports:
- "80:80"
environment:
- MIDDLE_HOST_1=middle
middle:
db:
services:
apache:
image: "my-httpd:2.4"
ports:
- "80:80"
environment:
- MIDDLE_HOST_1=middle
volumes:
- ./etc/httpd/workers.properties:/etc/httpd/conf/workers.properties
middle:
db:
services:
apache:
image: "my-httpd:2.4"
ports:
- "80:80"
environment:
- MIDDLE_HOST_1=middle
volumes:
- ./etc/httpd/workers.properties:/etc/httpd/conf/workers.properties
middle:
build: .
db:
services:
apache:
image: "my-httpd:2.4"
ports:
- "80:80"
environment:
- MIDDLE_HOST_1=middle
volumes:
- ./etc/httpd/workers.properties:/etc/httpd/conf/workers.properties
middle:
build: .
environment:
- DB_HOST=db
db:
services:
apache:
image: "my-httpd:2.4"
ports:
- "80:80"
environment:
- MIDDLE_HOST_1=middle
volumes:
- ./etc/httpd/workers.properties:/etc/httpd/conf/workers.properties
middle:
build: .
environment:
- DB_HOST=db
db:
image: "mysql:5.6"
services:
apache:
image: "my-httpd:2.4"
ports:
- "80:80"
environment:
- MIDDLE_HOST_1=middle
volumes:
- ./etc/httpd/workers.properties:/etc/httpd/conf/workers.properties
middle:
build: .
environment:
- DB_HOST=db
db:
image: "mysql:5.6"
ports:
- "3306:3306"
docker compose up -d
docker compose build
docker compose logs
docker compose stop
docker compose restart
docker compose down
L’ordre de démarrage fait référence à la séquence dans laquelle les services définis dans un fichier Docker Compose sont lancés.
Pourquoi est-ce important ?
Certains services peuvent dépendre d’autres services pour fonctionner correctement.
Par exemple, une application web peut avoir besoin qu’une base de données soit opérationnelle avant de pouvoir démarrer.
Comment Docker Compose gère-t-il l’ordre de démarrage ?
Par défaut, Docker Compose démarre les services dans l’ordre dans lequel ils sont définis dans le fichier Docker Compose.
Cependant, cela ne garantit pas que les services dépendants seront prêts à être utilisés lorsque les services qui en dépendent seront lancés.
Comment gérer les dépendances entre services ?
Docker Compose offre deux directives pour gérer les dépendances entre services : depends_on
et healthcheck
.
depends_on
: Cette directive peut être utilisée pour indiquer qu’un service dépend d’un autre service.
Cependant, cela ne garantit pas que le service dépendant sera prêt à être utilisé lorsque le service qui en dépend sera lancé.
healthcheck
: Cette directive peut être utilisée pour vérifier l’état de santé d’un service.
En combinaison avec depends_on
, elle peut aider à s’assurer qu’un service est prêt à être utilisé avant de lancer les services qui en dépendent.
Exemple
Voici un exemple de fichier Docker Compose qui utilise depends_on
et healthcheck
pour gérer l’ordre de démarrage des services :
version: '3'
services:
web:
build: .
depends_on:
db:
condition: service_healthy
db:
image: postgres
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 30s
timeout: 30s
retries: 3
Dans cet exemple, le service web dépend du service db. Le service db est vérifié toutes les 30 secondes pour s’assurer qu’il est prêt à être utilisé. Le service web ne sera lancé que lorsque le service db sera en bonne santé.
Rappel:
La directive depends_on dans un fichier Docker Compose est utilisée pour indiquer qu’un service dépend d’un autre service.
Cela signifie que le service dépendant ne sera pas démarré tant que les services dont il dépend n’auront pas été démarrés. |
En plus de la condition service_healthy, il existe d’autres conditions que vous pouvez utiliser avec depends_on
:
service_started
: Cette condition signifie que le service dépendant ne sera pas démarré tant que le service dont il dépend n’aura pas été démarré.
service_completed_successfully
: Cette condition indique qu’un service dépendant ne doit pas démarrer avant qu’un autre service ait terminé avec succès. Cela peut être utile dans des scénarios où un service doit effectuer une tâche unique qui doit être terminée avant que d’autres services puissent débuter.
Cependant, cela ne garantit pas que le service dont il dépend est prêt à être utilisé.
Voici un exemple de comment vous pourriez utiliser service_started
dans un fichier Docker Compose :
version: '3'
services:
web:
build: .
depends_on:
db:
condition: service_started
db:
image: postgres
Dans cet exemple, le service web
ne sera démarré que lorsque le service db
aura été démarré.
Rappel
healthcheck est une instruction dans un Dockerfile qui permet de vérifier l’état de santé d’un service.
Il peut être utilisé pour déterminer si un service est prêt à être utilisé ou non. |
Voici un exemple de healthcheck
dans un Dockerfile
:
FROM postgres
HEALTHCHECK --interval=5m --timeout=3s \
CMD pg_isready -U postgres || exit 1
Dans cet exemple, pg_isready -U postgres
est la commande utilisée pour vérifier l’état de santé du service.
Si cette commande réussit, le service est considéré comme sain.
Sinon, il est considéré comme malsain.
Rappel
depends_on est une directive dans un fichier Docker Compose qui indique qu’un service dépend d’un autre service.
Il peut être utilisé pour contrôler l’ordre de démarrage des services. |
Voici un exemple de depends_on dans un fichier Docker Compose :
version: '3'
services:
web:
build: .
depends_on:
db:
condition: service_healthy
db:
image: postgres
Dans cet exemple, le service web
dépend du service db
.
Le service web ne sera démarré que lorsque le service db sera en bonne santé.
En combinant healthcheck
et depends_on
, vous pouvez contrôler l’ordre de démarrage des services en fonction de leur état de santé.
Par exemple, vous pouvez vous assurer qu’un service de base de données est prêt à être utilisé avant de démarrer une application web qui en dépend.
L’ordre de démarrage fait référence à la séquence dans laquelle les services définis dans un fichier Docker Compose sont lancés. Par défaut, Docker Compose démarre les services dans l’ordre dans lequel ils sont définis dans le fichier Docker Compose.
Docker Compose offre deux directives pour gérer les dépendances entre services : depends_on
et healthcheck
.
depends_on
: Cette directive peut être utilisée pour indiquer qu’un service dépend d’un autre service. Elle peut être utilisée avec deux conditions : service_started
et service_healthy
.
service_started
: Cette condition signifie que le service dépendant ne sera pas démarré tant que le service dont il dépend n’aura pas été démarré. Cependant, cela ne garantit pas que le service dont il dépend est prêt à être utilisé.
service_healthy
: Cette condition est utilisée avec la directive healthcheck dans un Dockerfile pour vérifier l’état de santé d’un service. Si la commande healthcheck réussit, Docker considère le service comme sain. Sinon, il est considéré comme malsain.
Exemple Voici un exemple de fichier Docker Compose qui utilise depends_on et healthcheck pour gérer l’ordre de démarrage des services :
version: '3'
services:
web:
build: .
depends_on:
db:
condition: service_healthy
db:
image: postgres
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 30s
timeout: 30s
retries: 3
Un exemple de fichier docker-compose.yml utilisé dans Jenkins:
sidekick_service:
# Configuration for the sidekick service
image: ${DOCKERHUB_USERNAME}/jenkinsci-tutorials:sidekick_
stdin_open: true
tty: true
entrypoint: sh -c "/usr/local/bin/keygen.sh /ssh-dir" # Runs the keygen.sh script and specifies the output directory
volumes:
- agent-ssh-dir:/ssh-dir # Mounts the agent-ssh-dir volume to the /ssh-dir path inside the container
healthcheck:
test: [ "CMD-SHELL", "[ -f /ssh-dir/conductor_ok ] || exit 1" ] # Checks if the conductor_ok file exists in the /ssh-dir path
interval: 5s
timeout: 10s
retries: 5
Les options stdin_open: true
et tty: true
sont utilisées pour garder l’entrée standard du conteneur ouverte et pour allouer un pseudo-TTY au conteneur, respectivement.
C’est généralement fait lorsque vous voulez interagir avec le service en cours d’exécution.
sidekick_service:
# Configuration for the sidekick service
image: ${DOCKERHUB_USERNAME}/jenkinsci-tutorials:sidekick_
stdin_open: true
tty: true
entrypoint: sh -c "/usr/local/bin/keygen.sh /ssh-dir" # Runs the keygen.sh script and specifies the output directory
volumes:
- agent-ssh-dir:/ssh-dir # Mounts the agent-ssh-dir volume to the /ssh-dir path inside the container
healthcheck:
test: [ "CMD-SHELL", "[ -f /ssh-dir/conductor_ok ] || exit 1" ] # Checks if the conductor_ok file exists in the /ssh-dir path
interval: 5s
timeout: 10s
retries: 5
La directive entrypoint
est utilisée pour spécifier la commande qui sera exécutée lorsque le conteneur démarre.
Dans ce cas, elle exécute une commande shell qui exécute un script nommé keygen.sh
situé à /usr/local/bin/keygen.sh
.
Le script reçoit un argument /ssh-dir
, qui est le répertoire où le script effectuera ses opérations.
sidekick_service:
# Configuration for the sidekick service
image: ${DOCKERHUB_USERNAME}/jenkinsci-tutorials:sidekick_
stdin_open: true
tty: true
entrypoint: sh -c "/usr/local/bin/keygen.sh /ssh-dir" # Runs the keygen.sh script and specifies the output directory
volumes:
- agent-ssh-dir:/ssh-dir # Mounts the agent-ssh-dir volume to the /ssh-dir path inside the container
healthcheck:
test: [ "CMD-SHELL", "[ -f /ssh-dir/conductor_ok ] || exit 1" ] # Checks if the conductor_ok file exists in the /ssh-dir path
interval: 5s
timeout: 10s
retries: 5
La directive volumes est utilisée pour monter un volume nommé agent-ssh-dir
sur le chemin /ssh-dir
à l’intérieur du conteneur.
Cela permet de conserver les données entre les redémarrages du conteneur et peut également être utilisé pour partager des données entre les conteneurs.
sidekick_service:
# Configuration for the sidekick service
image: ${DOCKERHUB_USERNAME}/jenkinsci-tutorials:sidekick_
stdin_open: true
tty: true
entrypoint: sh -c "/usr/local/bin/keygen.sh /ssh-dir" # Runs the keygen.sh script and specifies the output directory
volumes:
- agent-ssh-dir:/ssh-dir # Mounts the agent-ssh-dir volume to the /ssh-dir path inside the container
healthcheck:
test: [ "CMD-SHELL", "[ -f /ssh-dir/conductor_ok ] || exit 1" ] # Checks if the conductor_ok file exists in the /ssh-dir path
interval: 5s
timeout: 10s
retries: 5
Enfin, un healthcheck est défini pour le service. Il s’agit d’une commande que Docker exécutera à l’intérieur du conteneur pour vérifier sa santé.
Dans ce cas, la commande vérifie si un fichier nommé conductor_ok
existe dans le répertoire /ssh-dir
.
Si le fichier existe, la commande réussit et Docker considère le service comme sain.
Si le fichier n’existe pas, la commande échoue et Docker considère le service comme malsain.
Docker exécutera cette vérification de santé toutes les 5 secondes (interval: 5s
), et si elle ne répond pas dans les 10 secondes (timeout: 10s
), Docker la considérera comme un échec. Docker réessaiera une vérification de santé échouée 5 fois (retries: 5
) avant de considérer le service comme malsain.
services:
sidekick_service: [...]
jenkins_controller: [...]
depends_on:
sidekick_service:
condition: service_completed_successfully # Depends on the successful completion of the sidekick_service
healthcheck:
test: [ "CMD-SHELL", "[ -f /ssh-dir/conductor_ok ] || exit 1" ] # Checks if the conductor_ok file exists in the /ssh-dir path
interval: 5s
timeout: 10s
retries: 5
default_agent: [...]
depends_on:
sidekick_service:
condition: service_completed_successfully # Depends on the successful completion of the sidekick_service
jenkins_controller:
condition: service_started
healthcheck:
test: [ "CMD-SHELL", "[ -f /home/jenkins/.ssh/authorized_keys ] || exit 1" ] # Checks if the authorized_keys file exists in the /home/jenkins/.ssh path
interval: 5s
timeout: 10s
retries: 5
volumes:
- agent-ssh-dir:/home/jenkins/.ssh:ro # Mounts the agent-ssh-dir volume to the /home/jenkins/.ssh path inside the container as read-only
Service jenkins_controller
Ce service dépend du sidekick_service
.
Il ne démarrera que lorsque le sidekick_service
aura terminé avec succès.
C’est ce que signifie la condition service_completed_successfully
.
services:
sidekick_service: [...]
jenkins_controller: [...]
depends_on:
sidekick_service:
condition: service_completed_successfully # Depends on the successful completion of the sidekick_service
healthcheck:
test: [ "CMD-SHELL", "[ -f /ssh-dir/conductor_ok ] || exit 1" ] # Checks if the conductor_ok file exists in the /ssh-dir path
interval: 5s
timeout: 10s
retries: 5
default_agent: [...]
depends_on:
sidekick_service:
condition: service_completed_successfully # Depends on the successful completion of the sidekick_service
jenkins_controller:
condition: service_started
healthcheck:
test: [ "CMD-SHELL", "[ -f /home/jenkins/.ssh/authorized_keys ] || exit 1" ] # Checks if the authorized_keys file exists in the /home/jenkins/.ssh path
interval: 5s
timeout: 10s
retries: 5
volumes:
- agent-ssh-dir:/home/jenkins/.ssh:ro # Mounts the agent-ssh-dir volume to the /home/jenkins/.ssh path inside the container as read-only
Un healthcheck
est également défini pour ce service.
Docker vérifie si un fichier nommé conductor_ok
existe dans le chemin /ssh-dir
.
Si le fichier existe, Docker considère le service comme sain.
Sinon, il est considéré comme malsain.
services:
sidekick_service: [...]
jenkins_controller: [...]
depends_on:
sidekick_service:
condition: service_completed_successfully # Depends on the successful completion of the sidekick_service
healthcheck:
test: [ "CMD-SHELL", "[ -f /ssh-dir/conductor_ok ] || exit 1" ] # Checks if the conductor_ok file exists in the /ssh-dir path
interval: 5s
timeout: 10s
retries: 5
default_agent: [...]
depends_on:
sidekick_service:
condition: service_completed_successfully # Depends on the successful completion of the sidekick_service
jenkins_controller:
condition: service_started
healthcheck:
test: [ "CMD-SHELL", "[ -f /home/jenkins/.ssh/authorized_keys ] || exit 1" ] # Checks if the authorized_keys file exists in the /home/jenkins/.ssh path
interval: 5s
timeout: 10s
retries: 5
volumes:
- agent-ssh-dir:/home/jenkins/.ssh:ro # Mounts the agent-ssh-dir volume to the /home/jenkins/.ssh path inside the container as read-only
Service default_agent
Ce service dépend également du sidekick_service
et du jenkins_controller
.
Il ne démarrera que lorsque le sidekick_service
aura terminé avec succès et que le jenkins_controller
aura démarré.
C’est ce que signifient les conditions service_completed_successfully
et service_started
.
services:
sidekick_service: [...]
jenkins_controller: [...]
depends_on:
sidekick_service:
condition: service_completed_successfully # Depends on the successful completion of the sidekick_service
healthcheck:
test: [ "CMD-SHELL", "[ -f /ssh-dir/conductor_ok ] || exit 1" ] # Checks if the conductor_ok file exists in the /ssh-dir path
interval: 5s
timeout: 10s
retries: 5
default_agent: [...]
depends_on:
sidekick_service:
condition: service_completed_successfully # Depends on the successful completion of the sidekick_service
jenkins_controller:
condition: service_started
healthcheck:
test: [ "CMD-SHELL", "[ -f /home/jenkins/.ssh/authorized_keys ] || exit 1" ] # Checks if the authorized_keys file exists in the /home/jenkins/.ssh path
interval: 5s
timeout: 10s
retries: 5
volumes:
- agent-ssh-dir:/home/jenkins/.ssh:ro # Mounts the agent-ssh-dir volume to the /home/jenkins/.ssh path inside the container as read-only
Un healthcheck
est également défini pour ce service.
Docker vérifie si un fichier nommé authorized_keys
existe dans le chemin /home/jenkins/.ssh
.
Si le fichier existe, Docker considère le service comme sain.
Sinon, il est considéré comme malsain.
services:
sidekick_service: [...]
jenkins_controller: [...]
depends_on:
sidekick_service:
condition: service_completed_successfully # Depends on the successful completion of the sidekick_service
healthcheck:
test: [ "CMD-SHELL", "[ -f /ssh-dir/conductor_ok ] || exit 1" ] # Checks if the conductor_ok file exists in the /ssh-dir path
interval: 5s
timeout: 10s
retries: 5
default_agent: [...]
depends_on:
sidekick_service:
condition: service_completed_successfully # Depends on the successful completion of the sidekick_service
jenkins_controller:
condition: service_started
healthcheck:
test: [ "CMD-SHELL", "[ -f /home/jenkins/.ssh/authorized_keys ] || exit 1" ] # Checks if the authorized_keys file exists in the /home/jenkins/.ssh path
interval: 5s
timeout: 10s
retries: 5
volumes:
- agent-ssh-dir:/home/jenkins/.ssh:ro # Mounts the agent-ssh-dir volume to the /home/jenkins/.ssh path inside the container as read-only
Enfin, un volume nommé agent-ssh-dir
est monté sur le chemin /home/jenkins/.ssh
à l’intérieur du conteneur en lecture seule.
À étudier au calme chez vous:
Deux approches différentes et complémentaires :
Ça vous dirait d’avoir votre propre forge Gitlab ?
😏 Non, et bien c’est parti quand même.
Créez un nouveau fichier appelé docker-compose.yml
.
Dans ce fichier, définissez trois services : gitlab
, gitlab-runner-1
et gitlab-runner-2
.
Pour le service gitlab
:
utilisez l’image gitlab/gitlab-ce:latest
(préfixée par le cache spécifique ILI)
redémarrez toujours le conteneur s’il s’arrête,
définissez le nom d’hôte sur localhost
,
définissez l’URL externe sur http://localhost
et exposez les ports 80
, 443
et 22
.
Pour les services gitlab-runner-1 et gitlab-runner-2:
utilisez l’image gitlab/gitlab-runner:latest
(préfixée par le cache spécifique ILI),
redémarrez toujours le conteneur s’il s’arrête,
dépendez du service gitlab et montez le socket Docker et le fichier de configuration de GitLab Runner.
Créez des volumes pour:
les fichiers de configuration,
les fichiers journaux
et les fichiers de données de GitLab,
ainsi que pour les fichiers de configuration de GitLab Runner.
Pas assez détaillé? 😨
Ouvrez votre éditeur de texte préféré et créez un nouveau fichier.
Nommez ce fichier docker-compose.yml
.
Commencez le fichier avec la ligne services:
pour commencer à définir les services de votre application.
Commencez à définir votre premier service en tapant gitlab:
sur une nouvelle ligne. Ce sera le service pour votre serveur GitLab.
Sous gitlab:
, ajoutez les détails de votre service.
Par exemple, image: 'gitlab/gitlab-ce:latest'
pour spécifier l’image Docker à utiliser pour ce service.
Sous gitlab:
, ajoutez les détails de votre service.
Par exemple, image: 'gitlab/gitlab-ce:latest'
pour spécifier l’image Docker à utiliser pour ce service.
Continuez à ajouter des détails pour le service gitlab, comme restart: always
pour toujours redémarrer le conteneur s’il s’arrête, et hostname: 'localhost'
pour définir le nom d’hôte du serveur GitLab.
Définissez l’URL externe de votre serveur GitLab en ajoutant environment:
et GITLAB_OMNIBUS_CONFIG: |
sur de nouvelles lignes,
puis external_url 'http://localhost'
sur la ligne suivante.
Exposez les ports nécessaires en ajoutant ports:
sur une nouvelle ligne,
puis - '80:80'
, - '443:443'
et - '22:22'
sur les lignes suivantes.
Montez les volumes nécessaires en ajoutant volumes:
sur une nouvelle ligne,
puis - 'gitlab_config:/etc/gitlab'
,
- 'gitlab_logs:/var/log/gitlab'
et - 'gitlab_data:/var/opt/gitlab'
sur les lignes suivantes.
Répétez ces étapes pour les services gitlab-runner-1 et gitlab-runner-2, en remplaçant gitlab
par gitlab-runner-1
et gitlab-runner-2
respectivement.
Pour les services gitlab-runner-1
et gitlab-runner-2
, remplacez l’URL externe par une dépendance au service gitlab en ajoutant depends_on:
sur une nouvelle ligne, puis - gitlab
sur la ligne suivante.
Montez le socket Docker et le fichier de configuration de GitLab Runner
en ajoutant - '/var/run/docker.sock:/var/run/docker.sock'
et - 'gitlab-runner-1-config:/etc/gitlab-runner'
(ou - 'gitlab-runner-2-config:/etc/gitlab-runner'
pour gitlab-runner-2
) sous volumes:
.
Enfin, définissez les volumes pour votre application en ajoutant volumes:
sur une nouvelle ligne à la fin de votre fichier, puis
- gitlab_config:
,
- gitlab_logs:
,
- gitlab_data:
,
- gitlab-runner-1-config:
et - gitlab-runner-2-config:
sur les lignes suivantes.
# This is a Docker Compose file for setting up a GitLab server and two GitLab runners.
services:
# The GitLab server service
gitlab:
# The Docker image to use for the GitLab server
image: 'gitlab/gitlab-ce:16.6.0-ce.0'
# Always restart the container if it stops
restart: always
# The hostname for the GitLab server
hostname: 'localhost'
# Environment variables for the GitLab server
environment:
# Configuration for the GitLab Omnibus package
GITLAB_OMNIBUS_CONFIG: |
# The external URL for the GitLab server
external_url 'http://localhost'
# The ports to expose from the GitLab server
ports:
- '80:80' # HTTP
- '443:443' # HTTPS
- '22:22' # SSH
# The volumes to mount for the GitLab server
volumes:
- 'gitlab_config:/etc/gitlab' # Configuration files
- 'gitlab_logs:/var/log/gitlab' # Log files
- 'gitlab_data:/var/opt/gitlab' # Data files
# The first GitLab runner service
gitlab-runner-1:
# The Docker image to use for the GitLab runner
image: 'gitlab/gitlab-runner:alpine3.16-v16.6.0'
# Always restart the container if it stops
restart: always
# The services this service depends on
depends_on:
- gitlab # Depends on the GitLab server
# The volumes to mount for the GitLab runner
volumes:
- '/var/run/docker.sock:/var/run/docker.sock' # Docker socket for running Docker commands
- 'gitlab-runner-1-config:/etc/gitlab-runner' # Configuration files
# The second GitLab runner service
gitlab-runner-2:
# The Docker image to use for the GitLab runner
image: 'gitlab/gitlab-runner:alpine3.16-v16.6.0'
# Always restart the container if it stops
restart: always
# The services this service depends on
depends_on:
- gitlab # Depends on the GitLab server
# The volumes to mount for the GitLab runner
volumes:
- '/var/run/docker.sock:/var/run/docker.sock' # Docker socket for running Docker commands
- 'gitlab-runner-2-config:/etc/gitlab-runner' # Configuration files
# The volumes to create for the services
volumes:
gitlab_config: # Volume for GitLab server configuration files
gitlab_logs: # Volume for GitLab server log files
gitlab_data: # Volume for GitLab server data files
gitlab-runner-1-config: # Volume for GitLab runner 1 configuration files
name: gitlab-runner-1-config
gitlab-runner-2-config: # Volume for GitLab runner 2 configuration files
name: gitlab-runner-2-config
Il va falloir se logguer, lier les runners au serveur, créer un projet, etc…
Pour trouver le mot de passe, il va falloir le demander gentiment à Gitlab:
docker compose -f tp12-bis.yml exec -it gitlab grep 'Password:' /etc/gitlab/initial_root_password
Password: qaPxXU+RqioolV3bAljvs2VYnyYa4jO/UYcis/UXLAk=
Ensuite, il va falloir restreindre l’accès:
Pour utiliser le runner GitLab dans GitLab, vous devez le configurer.
Pour une configuration correcte, nous aurons besoin d’un jeton copié depuis le portail.
Pire que ça, pas un jeton, mais carrément une commande à adapter à docker compose
.
Pour ce faire, allez à l’adresse : http://localhost/admin/runners et cliquez sur le bouton "New instance runner".
Choisissez "Linux".
"Run untagged jobs"
Donnez une description au runner.
Cliquez sur le bouton "Create runner"
Vous avez ensuite une commande à copier et modifier pour docker compose
.
docker compose -f tp12-bis.yml exec gitlab-runner-1 gitlab-runner register --url http://gitlab --token glrt-PF5rLbUKzku5g8BL2y7J
Runtime platform arch=amd64 os=linux pid=103 revision=853330f9 version=16.5.0
Running in system-mode.
Enter the GitLab instance URL (for example, https://gitlab.com/):
[http://gitlab]:
Verifying runner... is valid runner=PF5rLbUKz
Enter a name for the runner. This is stored only in the local config.toml file:
[3c2ee0d97d2b]: runner 1
Enter an executor: parallels, docker-autoscaler, docker+machine, custom, docker, docker-windows, shell, ssh, virtualbox, instance, kubernetes:
shell
Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!
Configuration (with the authentication token) was saved in "/etc/gitlab-runner/config.toml"
On a vu déjà l’instruction depends_on
dans un fichier Docker Compose.
Elle permet de définir des dépendances entre les services.
Mais que faire si on veut définir un lot de services qui fonctionnent ensemble, sans pour autant être en interdépendance ?
Les profils dans Docker Compose permettent de définir des groupes de services qui peuvent être activés ou désactivés ensemble.
Cela peut être utile pour gérer des environnements de développement, de test et de production différents dans le même fichier Docker Compose.
Pour définir un profil pour un service, vous pouvez ajouter la clé profiles
à la définition du service dans votre fichier Docker Compose.
Par exemple :
services:
mon_service:
image: mon_image
profiles:
- dev
Dans cet exemple, le service mon_service appartient au profil dev.
Pour démarrer seulement les services qui appartiennent à un certain profil, vous pouvez utiliser l’option --profile
avec la commande docker compose up
.
Par exemple :
docker compose up --profile dev
Cette commande démarrera seulement les services qui appartiennent au profil dev
.
Les profils peuvent rendre votre fichier Docker Compose plus organisé et flexible.
Ils vous permettent de définir différents environnements dans le même fichier et de choisir facilement quels services démarrer en fonction de vos besoins.
Jetons un coup d’œil à un exemple de fichier Docker Compose avec des profils :
services:
service_dev:
image: mon_image_dev
profiles:
- dev
service_prod:
image: mon_image_prod
profiles:
- prod
Dans cet exemple, nous avons deux services : service_dev
et service_prod
.
Chacun appartient à un profil différent.
services:
service_dev:
image: mon_image_dev
profiles:
- dev
service_prod:
image: mon_image_prod
profiles:
- prod
Nous pouvons choisir de démarrer seulement les services de développement avec docker compose up --profile dev
, ou uniquement les services de production avec docker compose up --profile prod
.
Les profils dans Docker Compose sont un outil puissant pour gérer différents environnements dans le même fichier Docker Compose.
Ils peuvent rendre votre développement et vos tests plus efficaces et organisés.
😏 Non, et bien, c’est parti quand même.
services:
# Le service sidekick est responsable de la génération des clés SSH et de la vérification de leur existence.
sidekick_service:
build: dockerfiles/sidekick/. # Le Dockerfile pour construire l'image du service sidekick.
stdin_open: true # Permet au service de garder STDIN ouvert même s'il n'est pas attaché.
tty: true # Alloue un pseudo-TTY pour le service.
entrypoint: sh -c "/usr/local/bin/keygen.sh /ssh-dir" # La commande que le service exécute lorsqu'il démarre.
volumes:
- agent-ssh-dir:/ssh-dir # Monte le volume agent-ssh-dir au chemin /ssh-dir à l'intérieur du conteneur.
healthcheck: # La commande de vérification de santé pour le service.
test: [ "CMD-SHELL", "[ -f /ssh-dir/conductor_ok ] || exit 1" ] # Vérifie si le fichier conductor_ok existe dans le chemin /ssh-dir.
interval: 5s # Le temps entre les vérifications de santé.
timeout: 10s # Le temps à attendre avant de considérer que la vérification a échoué.
retries: 5 # Le nombre d'échecs consécutifs nécessaires pour considérer un service comme malsain.
# Le service jenkins_controller est responsable de la gestion des jobs et des configurations Jenkins.
jenkins_controller:
build: dockerfiles/. # Le Dockerfile pour construire l'image du service jenkins_controller.
restart: on-failure # Le service sera redémarré s'il quitte en raison d'une erreur.
ports:
- "8080:8080" # Expose le port 8080 du service à l'hôte.
volumes:
- jenkins_home:/var/jenkins_home # Monte le volume jenkins_home au chemin /var/jenkins_home à l'intérieur du conteneur.
- agent-ssh-dir:/ssh-dir # Monte le volume agent-ssh-dir au chemin /ssh-dir à l'intérieur du conteneur.
depends_on: # Les services dont ce service dépend.
sidekick_service:
condition: service_completed_successfully # Le sidekick_service doit se terminer avec succès avant que ce service ne démarre.
healthcheck: # La commande de vérification de santé pour le service.
test: [ "CMD-SHELL", "[ -f /ssh-dir/conductor_ok ] || exit 1" ] # Vérifie si le fichier conductor_ok existe dans le chemin /ssh-dir.
interval: 5s # Le temps entre les vérifications de santé.
timeout: 10s # Le temps à attendre avant de considérer que la vérification a échoué.
retries: 5 # Le nombre d'échecs consécutifs nécessaires pour considérer un service comme malsain.
# Le service default_agent est un agent Jenkins avec un JDK installé.
default_agent:
image: jenkins/ssh-agent:5.5.0-jdk17 # L'image Docker pour le service.
container_name: desktop-jenkins_agent-1 # Le nom du conteneur.
depends_on: # Les services dont ce service dépend.
sidekick_service:
condition: service_completed_successfully # Le sidekick_service doit se terminer avec succès avant que ce service ne démarre.
jenkins_controller:
condition: service_started # Le service jenkins_controller doit démarrer avant que ce service ne démarre.
healthcheck: # La commande de vérification de santé pour le service.
test: [ "CMD-SHELL", "[ -f /home/jenkins/.ssh/authorized_keys ] || exit 1" ] # Vérifie si le fichier authorized_keys existe dans le chemin /home/jenkins/.ssh.
interval: 5s # Le temps entre les vérifications de santé.
timeout: 10s # Le temps à attendre avant de considérer que la vérification a échoué.
retries: 5 # Le nombre d'échecs consécutifs nécessaires pour considérer un service comme malsain.
volumes:
- agent-ssh-dir:/home/jenkins/.ssh:ro # Monte le volume agent-ssh-dir au chemin /home/jenkins/.ssh à l'intérieur du conteneur en lecture seule.
# Le service maven est un agent Jenkins avec Maven installé.
maven:
build: dockerfiles/maven/. # Le Dockerfile pour construire l'image du service Maven.
container_name: desktop-jenkins_agent-1 # Le nom du conteneur.
profiles:
- maven # Les profils à appliquer au service. Cela permet de personnaliser le comportement du service en fonction des besoins spécifiques.
depends_on: # Les services dont ce service dépend.
sidekick_service:
condition: service_completed_successfully # Le sidekick_service doit se terminer avec succès avant que ce service ne démarre.
jenkins_controller:
condition: service_started # Le service jenkins_controller doit démarrer avant que ce service ne démarre.
healthcheck: # La commande de vérification de santé pour le service.
test: [ "CMD-SHELL", "[ -f /home/jenkins/.ssh/authorized_keys ] || exit 1" ] # Vérifie si le fichier authorized_keys existe dans le chemin /home/jenkins/.ssh.
interval: 5s # Le temps entre les vérifications de santé.
timeout: 10s # Le temps à attendre avant de considérer que la vérification a échoué.
retries: 5 # Le nombre d'échecs consécutifs nécessaires pour considérer un service comme malsain.
volumes:
- agent-ssh-dir:/home/jenkins/.ssh:ro # Monte le volume agent-ssh-dir au chemin /home/jenkins/.ssh à l'intérieur du conteneur en lecture seule.
# Le service python est un agent Jenkins avec Python installé.
python:
build: dockerfiles/python/. # Le Dockerfile pour construire l'image du service Python.
container_name: desktop-jenkins_agent-1 # Le nom du conteneur.
profiles:
- python # Les profils à appliquer au service. Cela permet de personnaliser le comportement du service en fonction des besoins spécifiques.
depends_on: # Les services dont ce service dépend.
sidekick_service:
condition: service_completed_successfully # Le sidekick_service doit se terminer avec succès avant que ce service ne démarre.
jenkins_controller:
condition: service_started # Le service jenkins_controller doit démarrer avant que ce service ne démarre.
healthcheck: # La commande de vérification de santé pour le service.
test: [ "CMD-SHELL", "[ -f /home/jenkins/.ssh/authorized_keys ] || exit 1" ] # Vérifie si le fichier authorized_keys existe dans le chemin /home/jenkins/.ssh.
interval: 5s # Le temps entre les vérifications de santé.
timeout: 10s # Le temps à attendre avant de considérer que la vérification a échoué.
retries: 5 # Le nombre d'échecs consécutifs nécessaires pour considérer un service comme malsain.
volumes:
- agent-ssh-dir:/home/jenkins/.ssh:ro # Monte le volume agent-ssh-dir au chemin /home/jenkins/.ssh à l'intérieur du conteneur en lecture seule.
# Le service node est un agent Jenkins avec Node.js installé.
node:
build: dockerfiles/node/. # Le Dockerfile pour construire l'image du service Node.js.
environment:
- GITPOD_WORKSPACE_URL=${GITPOD_WORKSPACE_URL} # Les variables d'environnement pour le service.
container_name: desktop-jenkins_agent-1 # Le nom du conteneur.
profiles:
- node # Les profils à appliquer au service. Cela permet de personnaliser le comportement du service en fonction des besoins spécifiques.
depends_on: # Les services dont ce service dépend.
sidekick_service:
condition: service_completed_successfully # Le sidekick_service doit se terminer avec succès avant que ce service ne démarre.
jenkins_controller:
condition: service_started # Le service jenkins_controller doit démarrer avant que ce service ne démarre.
ports:
- "3000:3000" # Expose le port 3000 du service à l'hôte.
healthcheck: # La commande de vérification de santé pour le service.
test: [ "CMD-SHELL", "[ -f /home/jenkins/.ssh/authorized_keys ] || exit 1" ] # Vérifie si le fichier authorized_keys existe dans le chemin /home/jenkins/.ssh.
interval: 5s # Le temps entre les vérifications de santé.
timeout: 10s # Le temps à attendre avant de considérer que la vérification a échoué.
retries: 5 # Le nombre d'échecs consécutifs nécessaires pour considérer un service comme malsain.
volumes:
- agent-ssh-dir:/home/jenkins/.ssh:ro # Monte le volume agent-ssh-dir au chemin /home/jenkins/.ssh à l'intérieur du conteneur en lecture seule.
# Le service multi_jenkins_controller est un contrôleur Jenkins pour gérer plusieurs jobs et configurations Jenkins.
multi_jenkins_controller:
build: 06_multibranch_pipeline/dockerfiles/. # Le Dockerfile pour construire l'image du service multi Jenkins controller.
restart: on-failure # Le service sera redémarré s'il quitte en raison d'une erreur.
ports:
- "8080:8080" # Expose le port 8080 du service à l'hôte.
volumes:
- jenkins_home:/var/jenkins_home # Monte le volume jenkins_home au chemin /var/jenkins_home à l'intérieur du conteneur.
- agent-ssh-dir:/ssh-dir # Monte le volume agent-ssh-dir au chemin /ssh-dir à l'intérieur du conteneur.
depends_on: # Les services dont ce service dépend.
sidekick_service:
condition: service_completed_successfully # Le sidekick_service doit se terminer avec succès avant que ce service ne démarre.
healthcheck: # La commande de vérification de santé pour le service.
test: [ "CMD-SHELL", "[ -f /ssh-dir/conductor_ok ] || exit 1" ] # Vérifie si le fichier conductor_ok existe dans le chemin /ssh-dir.
interval: 5s # Le temps entre les vérifications de santé.
timeout: 10s # Le temps à attendre avant de considérer que la vérification a échoué.
retries: 5 # Le nombre d'échecs consécutifs nécessaires pour considérer un service comme malsain.
# Le service multi est un agent Jenkins pour gérer plusieurs jobs et configurations Jenkins.
multi:
build: 06_multibranch_pipeline/dockerfiles/agent/. # Le Dockerfile pour construire l'image du service multi Jenkins agent.
environment:
- GITPOD_WORKSPACE_URL=${GITPOD_WORKSPACE_URL} # Les variables d'environnement pour le service.
container_name: desktop-jenkins_agent-1 # Le nom du conteneur.
profiles:
- multi # Les profils à appliquer au service. Cela permet de personnaliser le comportement du service en fonction des besoins spécifiques.
depends_on: # Les services dont ce service dépend.
sidekick_service:
condition: service_completed_successfully # Le sidekick_service doit se terminer avec succès avant que ce service ne démarre.
multi_jenkins_controller:
condition: service_started # Le service multi_jenkins_controller doit démarrer avant que ce service ne démarre.
ports:
- "3000:3000" # Expose le port 3000 du service à l'hôte.
- "5000:5000" # Expose le port 5000 du service à l'hôte.
healthcheck: # La commande de vérification de santé pour le service.
test: [ "CMD-SHELL", "[ -f /home/jenkins/.ssh/authorized_keys ] || exit 1" ] # Vérifie si le fichier authorized_keys existe dans le chemin /home/jenkins/.ssh.
interval: 5s # Le temps entre les vérifications de santé.
timeout: 10s # Le temps à attendre avant de considérer que la vérification a échoué.
retries: 5 # Le nombre d'échecs consécutifs nécessaires pour considérer un service comme malsain.
volumes:
- agent-ssh-dir:/home/jenkins/.ssh:ro # Monte le volume agent-ssh-dir au chemin /home/jenkins/.ssh à l'intérieur du conteneur en lecture seule.
# Les volumes utilisés par les services.
volumes:
jenkins_home: # Un volume pour stocker les données de Jenkins.
agent-ssh-dir: # Un volume pour stocker les clés SSH.
name: agent-ssh-dir # Le nom du volume.
Indigeste? 😨
Ne relevons que les points importants:
# Le service maven est un agent Jenkins avec Maven installé.
maven:
build: dockerfiles/maven/. # Le Dockerfile pour construire l'image du service Maven.
container_name: desktop-jenkins_agent-1 # Le nom du conteneur.
profiles:
- maven # Les profils à appliquer au service. Cela permet de personnaliser le comportement du service en fonction des besoins spécifiques.
depends_on: # Les services dont ce service dépend.
sidekick_service:
condition: service_completed_successfully # Le sidekick_service doit se terminer avec succès avant que ce service ne démarre.
jenkins_controller:
condition: service_started # Le service jenkins_controller doit démarrer avant que ce service ne démarre.
healthcheck: # La commande de vérification de santé pour le service.
test: [ "CMD-SHELL", "[ -f /home/jenkins/.ssh/authorized_keys ] || exit 1" ] # Vérifie si le fichier authorized_keys existe dans le chemin /home/jenkins/.ssh.
interval: 5s # Le temps entre les vérifications de santé.
timeout: 10s # Le temps à attendre avant de considérer que la vérification a échoué.
retries: 5 # Le nombre d'échecs consécutifs nécessaires pour considérer un service comme malsain.
volumes:
- agent-ssh-dir:/home/jenkins/.ssh:ro # Monte le volume agent-ssh-dir au chemin /home/jenkins/.ssh à l'intérieur du conteneur en lecture seule.
Maven
FROM jenkins/ssh-agent:6.17.0-jdk17 as ssh-agent
# ca-certificates because curl will need it later on for the maven installation
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl && apt-get clean && \
rm -rf /var/lib/apt/lists/*
# Now time to install maven
ARG MAVEN_VERSION=3.9.2
# Set SHELL flags for RUN commands to allow -e and pipefail
# Rationale:https://github.com/hadolint/hadolint/wiki/DL4006
SHELL ["/bin/bash", "-eo", "pipefail", "-c"]
# Add a checksum for the maven binary
RUN curl -sS -L -O --output-dir /tmp/ --create-dirs https://archive.apache.org/dist/maven/maven-3/${MAVEN_VERSION}/binaries/apache-maven-${MAVEN_VERSION}-bin.tar.gz \
&& printf "%s" "$(sha512sum /tmp/apache-maven-${MAVEN_VERSION}-bin.tar.gz)" | sha512sum -c - \
&& curl -sS -L -O --output-dir /tmp/ --create-dirs https://downloads.apache.org/maven/maven-3/${MAVEN_VERSION}/binaries/apache-maven-${MAVEN_VERSION}-bin.tar.gz.sha512 \
&& printf "%s /tmp/apache-maven-${MAVEN_VERSION}-bin.tar.gz" "$(cat /tmp/apache-maven-${MAVEN_VERSION}-bin.tar.gz.sha512)" | sha512sum --check --status - \
&& tar xzf "/tmp/apache-maven-${MAVEN_VERSION}-bin.tar.gz" -C /opt/ \
&& rm "/tmp/apache-maven-${MAVEN_VERSION}-bin.tar.gz" \
&& ln -s /opt/apache-maven-${MAVEN_VERSION} /opt/maven \
&& ln -s /opt/maven/bin/mvn /usr/bin/mvn \
&& mkdir -p /etc/profile.d \
&& echo "export JAVA_HOME=$JAVA_HOME \n \
export M2_HOME=/opt/maven \n \
export PATH=${M2_HOME}/bin:${PATH}" > /etc/profile.d/maven.sh
ENV M2_HOME="/opt/maven"
ENV PATH="${M2_HOME}/bin/:${PATH}"
RUN echo "PATH=${PATH}" >> /etc/environment && chown -R jenkins:jenkins "${JENKINS_AGENT_HOME}"
Python
# Le service python est un agent Jenkins avec Python installé.
python:
build: dockerfiles/python/. # Le Dockerfile pour construire l'image du service Python.
container_name: desktop-jenkins_agent-1 # Le nom du conteneur.
profiles:
- python # Les profils à appliquer au service. Cela permet de personnaliser le comportement du service en fonction des besoins spécifiques.
depends_on: # Les services dont ce service dépend.
sidekick_service:
condition: service_completed_successfully # Le sidekick_service doit se terminer avec succès avant que ce service ne démarre.
jenkins_controller:
condition: service_started # Le service jenkins_controller doit démarrer avant que ce service ne démarre.
healthcheck: # La commande de vérification de santé pour le service.
test: [ "CMD-SHELL", "[ -f /home/jenkins/.ssh/authorized_keys ] || exit 1" ] # Vérifie si le fichier authorized_keys existe dans le chemin /home/jenkins/.ssh.
interval: 5s # Le temps entre les vérifications de santé.
timeout: 10s # Le temps à attendre avant de considérer que la vérification a échoué.
retries: 5 # Le nombre d'échecs consécutifs nécessaires pour considérer un service comme malsain.
volumes:
- agent-ssh-dir:/home/jenkins/.ssh:ro # Monte le volume agent-ssh-dir au chemin /home/jenkins/.ssh à l'intérieur du conteneur en lecture seule.
# Use the base image
FROM jenkins/ssh-agent:5.12.0-jdk17 as ssh-agent
# Install necessary Dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
binutils ca-certificates curl git python3 python3-pip python3-setuptools python3-wheel python3-dev wget
# Create an alias for python3 as python
RUN ln -s /usr/bin/python3 /usr/bin/python
# Install required python packages
RUN pip install docker-py feedparser nosexcover prometheus_client pycobertura pylint pytest pytest-cov requests setuptools sphinx pyinstaller
# Add the Jenkins agent user to the environment
RUN echo "PATH=${PATH}" >> /etc/environment
# Set ownership for Jenkins agent home directory
RUN chown -R jenkins:jenkins "${JENKINS_AGENT_HOME}"
À vous maintenant!
git clone https://github.com/jenkins-docs/quickstart-tutorials.git
À vous de modifier les sources de façon à passer par le cache ILI
Ensuite, ne restera qu’à lancer la "forge":
docker compose up --build -d --force-recreate maven
Ou encore si vous voulez tout construire localement:
docker compose -f build-docker-compose.yaml up --build -d --force-recreate maven
Et pourquoi pas s’attaquer au docker compose qui a généré ce cours ?
Et pourquoi pas s’attaquer au docker compose qui a généré ce cours ?
# Ceci est un fichier Docker Compose pour un projet qui comprend plusieurs services.
# 'x-slides-base' est une ancre YAML qui définit une configuration commune pour plusieurs services.
x-slides-base: &slides-base
# La configuration de construction pour l'image Docker.
build:
# Le contexte de construction est le répertoire courant.
context: ./
args:
# Active la fonction de mise en cache en ligne de Docker BuildKit.
BUILDKIT_INLINE_CACHE: 1
# Variables d'environnement pour le conteneur Docker.
environment:
- PRESENTATION_URL=${PRESENTATION_URL}
- REPOSITORY_URL=${REPOSITORY_URL}
# L'ID utilisateur qui exécute les commandes à l'intérieur du conteneur.
user: ${CURRENT_UID}
# Un montage tmpfs pour des opérations d'E/S plus rapides.
tmpfs:
- ${BUILD_DIR}
# Volumes pour le conteneur Docker.
volumes:
- ./content:/app/content
- ./assets:/app/assets
- ${DIST_DIR}:/app/dist
- ./gulp/gulpfile.js:/app/gulpfile.js
- ./gulp/tasks:/app/tasks
- ./npm-packages:/app/npm-packages
# Les services qui composent l'application.
services:
# Le service 'serve'.
serve:
# Utilise la configuration commune définie par l'ancre 'x-slides-base'.
<<: *slides-base
# Expose le port 8000 du conteneur à l'hôte.
ports:
- "8000:8000"
# Le service 'build'.
build:
<<: *slides-base
# Ce service dépend du service 'qrcode'.
depends_on:
qrcode:
# Le service 'qrcode' doit se terminer avec succès avant que ce service ne démarre.
condition: service_completed_successfully
# Le point d'entrée pour le conteneur Docker.
entrypoint: >
sh -xc 'gulp build && cp -r "${BUILD_DIR}"/* /app/dist/'
# Le service 'qrcode'.
qrcode:
<<: *slides-base
# Le point d'entrée pour le conteneur Docker.
entrypoint: /app/node_modules/.bin/qrcode
# La commande à exécuter dans le conteneur Docker.
command: >
-t png -o /app/content/media/qrcode.png ${PRESENTATION_URL}
# Le service 'pdf'.
pdf:
# L'image Docker pour ce service.
image: ghcr.io/astefanutti/decktape:3.7.0
# Ce service dépend du service 'build'.
depends_on:
build:
# Le service 'build' doit se terminer avec succès avant que ce service ne démarre.
condition: service_completed_successfully
# L'ID utilisateur qui exécute les commandes à l'intérieur du conteneur.
user: ${CURRENT_UID}
# Volumes pour le conteneur Docker.
volumes:
- ${DIST_DIR}:/slides
# La commande à exécuter dans le conteneur Docker.
command: >
/slides/index.html /slides/slides.pdf --size='1024x768' --pause 0
Et pourquoi pas s’attaquer au docker compose qui a généré ce cours ?
Indigeste? 😨
Ne relevons que les points importants:
Les ancres…
# Ceci est un fichier Docker Compose pour un projet qui comprend plusieurs services.
# 'x-slides-base' est une ancre YAML qui définit une configuration commune pour plusieurs services.
x-slides-base: &slides-base
# La configuration de construction pour l'image Docker.
build:
# Le contexte de construction est le répertoire courant.
context: ./
args:
# Active la fonction de mise en cache en ligne de Docker BuildKit.
BUILDKIT_INLINE_CACHE: 1
# Variables d'environnement pour le conteneur Docker.
environment:
- PRESENTATION_URL=${PRESENTATION_URL}
- REPOSITORY_URL=${REPOSITORY_URL}
# L'ID utilisateur qui exécute les commandes à l'intérieur du conteneur.
user: ${CURRENT_UID}
# Un montage tmpfs pour des opérations d'E/S plus rapides.
tmpfs:
- ${BUILD_DIR}
# Volumes pour le conteneur Docker.
volumes:
- ./content:/app/content
- ./assets:/app/assets
- ${DIST_DIR}:/app/dist
- ./gulp/gulpfile.js:/app/gulpfile.js
- ./gulp/tasks:/app/tasks
- ./npm-packages:/app/npm-packages
docker compose
⚓Docker Compose permet de définir et de gérer plusieurs services Docker dans un seul fichier YAML.
Les ancres (&
) et les alias (*
) sont des fonctionnalités YAML qui peuvent être utilisées dans Docker Compose pour réutiliser des configurations.
Une ancre est une référence à un objet ou à une valeur dans un fichier YAML.
Elle est définie en utilisant l’opérateur &
suivi d’un nom unique.
x-slides-base: &slides-base
build:
context: ./
Dans cet exemple, x-slides-base
est une ancre qui représente un objet avec une clé build
.
docker compose
⚓Les ancres peuvent être référencées ailleurs dans le fichier YAML en utilisant l’opérateur *
.
services:
serve:
<<: *slides-base
Les ancres permettent de réutiliser des configurations, ce qui rend le fichier Docker Compose plus lisible et plus facile à maintenir.
Si vous devez modifier une configuration qui est utilisée à plusieurs endroits, vous pouvez simplement modifier l’ancre.
En conclusion, les ancres et les alias sont des outils puissants pour gérer des configurations complexes dans Docker Compose. Ils permettent de réduire la duplication et d’améliorer la lisibilité de votre fichier Docker Compose.
Le fameux qrcode…
# Le service 'qrcode'.
qrcode:
<<: *slides-base
# Le point d'entrée pour le conteneur Docker.
entrypoint: /app/node_modules/.bin/qrcode
# La commande à exécuter dans le conteneur Docker.
command: >
-t png -o /app/content/media/qrcode.png ${PRESENTATION_URL}
Le service qui construit les slides à partir d'`asciidoc` avec reveal.js
.
# Le service 'build'.
build:
<<: *slides-base
# Ce service dépend du service 'qrcode'.
depends_on:
qrcode:
# Le service 'qrcode' doit se terminer avec succès avant que ce service ne démarre.
condition: service_completed_successfully
# Le point d'entrée pour le conteneur Docker.
entrypoint: >
sh -xc 'gulp build && cp -r "${BUILD_DIR}"/* /app/dist/'
Le service qui construit les slides à partir d'`asciidoc` avec reveal.js
.
# Le service 'serve'.
serve:
# Utilise la configuration commune définie par l'ancre 'x-slides-base'.
<<: *slides-base
# Expose le port 8000 du conteneur à l'hôte.
ports:
- "8000:8000"
Le service qui construit le pdf à partir des slides.
# Le service 'pdf'.
pdf:
# L'image Docker pour ce service.
image: ghcr.io/astefanutti/decktape:3.7.0
# Ce service dépend du service 'build'.
depends_on:
build:
# Le service 'build' doit se terminer avec succès avant que ce service ne démarre.
condition: service_completed_successfully
# L'ID utilisateur qui exécute les commandes à l'intérieur du conteneur.
user: ${CURRENT_UID}
# Volumes pour le conteneur Docker.
volumes:
- ${DIST_DIR}:/slides
# La commande à exécuter dans le conteneur Docker.
command: >
/slides/index.html /slides/slides.pdf --size='1024x768' --pause 0
Pour rappel, l’ancre contenait :
# 'x-slides-base' est une ancre YAML qui définit une configuration commune pour plusieurs services.
x-slides-base: &slides-base
# La configuration de construction pour l'image Docker.
build:
# Le contexte de construction est le répertoire courant.
context: ./
args:
# Active la fonction de mise en cache en ligne de Docker BuildKit.
BUILDKIT_INLINE_CACHE: 1
# Variables d'environnement pour le conteneur Docker.
environment:
- PRESENTATION_URL=${PRESENTATION_URL}
- REPOSITORY_URL=${REPOSITORY_URL}
# L'ID utilisateur qui exécute les commandes à l'intérieur du conteneur.
user: ${CURRENT_UID}
# Un montage tmpfs pour des opérations d'E/S plus rapides.
tmpfs:
- ${BUILD_DIR}
# Volumes pour le conteneur Docker.
volumes:
- ./content:/app/content
- ./assets:/app/assets
- ${DIST_DIR}:/app/dist
- ./gulp/gulpfile.js:/app/gulpfile.js
- ./gulp/tasks:/app/tasks
- ./npm-packages:/app/npm-packages
Docker BuildKit est un outil de construction de Docker qui apporte de nombreuses améliorations par rapport à l’ancien système de construction.
L’une de ces améliorations est la mise en cache en ligne.
La mise en cache en ligne est une fonctionnalité de Docker BuildKit qui permet de réutiliser les couches de cache existantes lors de la construction d’une image Docker.
Elle est activée en définissant l’argument BUILDKIT_INLINE_CACHE
à 1
.
args:
BUILDKIT_INLINE_CACHE: 1
# Variables d'environnement pour le conteneur Docker.
environment:
- PRESENTATION_URL=${PRESENTATION_URL}
- REPOSITORY_URL=${REPOSITORY_URL}
Deux variables d’environnement sont définies pour le conteneur Docker : PRESENTATION_URL
et REPOSITORY_URL
.
Ces variables sont définies à l’aide de la syntaxe ${VARIABLE_NAME}
, qui est une manière standard d’accéder aux variables d’environnement dans les fichiers de configuration YAML.
# Variables d'environnement pour le conteneur Docker.
environment:
- PRESENTATION_URL=${PRESENTATION_URL}
- REPOSITORY_URL=${REPOSITORY_URL}
Lorsque Docker Compose rencontre cette syntaxe, il cherche la valeur de la variable d’environnement dans plusieurs endroits, en suivant un ordre spécifique :
Il vérifie d’abord si la variable est définie dans le shell courant.
Si c’est le cas, il utilise cette valeur.
Si la variable n’est pas définie dans le shell, Docker Compose cherche ensuite dans un fichier .env
situé dans le même répertoire que le fichier docker-compose.yml
.
Si la variable est définie dans ce fichier, Docker Compose utilise cette valeur.
# Variables d'environnement pour le conteneur Docker.
environment:
- PRESENTATION_URL=${PRESENTATION_URL}
- REPOSITORY_URL=${REPOSITORY_URL}
Si la variable n’est définie ni dans le shell ni dans le fichier .env
, Docker Compose utilise la valeur par défaut spécifiée dans le fichier docker-compose.yml
(s’il y en a une).
Dans notre cas, aucune valeur par défaut n’est spécifiée, donc si la variable n’est définie ni dans le shell ni dans le fichier .env
, Docker Compose générera une erreur.
# L'ID utilisateur qui exécute les commandes à l'intérieur du conteneur.
user: ${CURRENT_UID}
Dans Docker, chaque instruction dans le Dockerfile est exécutée par un utilisateur particulier.
Par défaut, cet utilisateur est root, mais pour des raisons de sécurité, il est souvent recommandé d’exécuter les processus en tant qu’utilisateur non root.
Cette ligne définit l’ID de l’utilisateur qui exécutera les commandes à l’intérieur du conteneur Docker à la valeur de la variable d’environnement CURRENT_UID
.
La syntaxe ${CURRENT_UID}
est utilisée pour accéder à la valeur d’une variable d’environnement.
Dans ce cas, Docker Compose recherchera une variable d’environnement nommée CURRENT_UID
et utilisera sa valeur.
Si CURRENT_UID
n’est pas défini dans l’environnement où Docker Compose est exécuté, Docker Compose renverra une erreur.
# Ce Dockerfile met en place un environnement Node.js avec des outils et dépendances supplémentaires.
# Il est basé sur l'image Docker officielle de Node.js (version 22, variante Alpine).
FROM node:24-alpine
# Installer la dernière version des dépendances requises
# hadolint ignore=DL3018
RUN apk add --no-cache \
curl \ # Outil pour transférer des données avec des URLs
git \ # Système de contrôle de version distribué
tini \ # Un init minuscule mais valide pour les conteneurs
unzip # Outil pour décompresser les fichiers zip
# Installer les dépendances NPM globalement (dernières versions)
# hadolint ignore=DL3016,DL3059
RUN npm install --global npm npm-check-updates # Mettre à jour npm à la dernière version et installer npm-check-updates
# Copier les dépendances NPM de l'application dans l'image Docker
COPY ./npm-packages /app/npm-packages
# Créer des liens symboliques pour package.json et package-lock.json à la racine de /app
# Cela permet d'exécuter les opérations npm sans erreur ENOENT
RUN ln -s /app/npm-packages/package.json /app/package.json \
&& ln -s /app/npm-packages/package-lock.json /app/package-lock.json
# Définir le répertoire de travail dans l'image Docker à /app
WORKDIR /app
# Télécharger et installer une version spécifique de FontAwesome
ARG FONTAWESOME_VERSION=6.4.0
RUN curl --silent --show-error --location --output /tmp/fontawesome.zip \
"https://use.fontawesome.com/releases/v${FONTAWESOME_VERSION}/fontawesome-free-${FONTAWESOME_VERSION}-web.zip" \
&& unzip -q /tmp/fontawesome.zip -d /tmp \
&& mv /tmp/"fontawesome-free-${FONTAWESOME_VERSION}-web" /app/fontawesome \
&& rm -rf /tmp/font*
# Installer les dépendances NPM en utilisant le package-lock.json
# Si l'installation échoue, revenir à une installation npm régulière
RUN { npm install-clean && npx update-browserslist-db@latest; } || npm install
# Lier la commande gulp pour qu'elle soit disponible dans le PATH
# hadolint ignore=DL3059
RUN npm link gulp
# Copier les tâches gulp et la configuration dans l'image Docker
COPY ./gulp/tasks /app/tasks
COPY ./gulp/gulpfile.js /app/gulpfile.js
# Définir un volume pour le répertoire /app
VOLUME ["/app"]
# Exposer le port 8000 pour HTTP
EXPOSE 8000
# Utiliser tini comme point d'entrée, et exécuter gulp par défaut
ENTRYPOINT ["/sbin/tini","-g","gulp"]
CMD ["default"]
RUN npm install --global npm npm-check-updates # Mettre à jour npm à la dernière version et installer npm-check-updates
# Copier les dépendances NPM de l'application dans l'image Docker
COPY ./npm-packages /app/npm-packages
# Créer des liens symboliques pour package.json et package-lock.json à la racine de /app
# Cela permet d'exécuter les opérations npm sans erreur ENOENT
RUN ln -s /app/npm-packages/package.json /app/package.json \
&& ln -s /app/npm-packages/package-lock.json /app/package-lock.json
Ce Dockerfile met en place un environnement Node.js
avec des outils et dépendances supplémentaires.
Il installe les dernières versions de npm
et npm-check-updates
globalement, copie les dépendances npm
de l’application dans l’image Docker, et crée des liens symboliques pour package.json
et package-lock.json
à la racine de /app
.
ARG FONTAWESOME_VERSION=6.4.0
RUN curl --silent --show-error --location --output /tmp/fontawesome.zip \
"https://use.fontawesome.com/releases/v${FONTAWESOME_VERSION}/fontawesome-free-${FONTAWESOME_VERSION}-web.zip" \
&& unzip -q /tmp/fontawesome.zip -d /tmp \
&& mv /tmp/"fontawesome-free-${FONTAWESOME_VERSION}-web" /app/fontawesome \
&& rm -rf /tmp/font*
Il télécharge également et installe une version spécifique de FontAwesome
# Installer les dépendances NPM en utilisant le package-lock.json
# Si l'installation échoue, revenir à une installation npm régulière
RUN { npm install-clean && npx update-browserslist-db@latest; } || npm install
# Lier la commande gulp pour qu'elle soit disponible dans le PATH
# hadolint ignore=DL3059
RUN npm link gulp
Il installe les dépendances npm
en utilisant le package-lock.json
(en revenant à une installation npm
régulière si nécessaire), et lie la commande gulp
pour qu’elle soit disponible dans le PATH
.
# Copier les tâches gulp et la configuration dans l'image Docker
COPY ./gulp/tasks /app/tasks
COPY ./gulp/gulpfile.js /app/gulpfile.js
# Définir un volume pour le répertoire /app
VOLUME ["/app"]
# Exposer le port 8000 pour HTTP
EXPOSE 8000
Les tâches gulp
et la configuration sont copiées dans l’image Docker, un volume est défini pour le répertoire /app
, et le port 8000
est exposé pour HTTP
.
# Utiliser tini comme point d'entrée, et exécuter gulp par défaut
ENTRYPOINT ["/sbin/tini","-g","gulp"]
CMD ["default"]
Le point d’entrée est défini sur tini
, et gulp
est exécuté par défaut.
Tini est un init
minuscule mais valide pour les conteneurs. Il est conçu pour être le système init le plus simple possible.
Tini fait deux choses :
Il génère votre processus en tant que son enfant (directement, pas en tant que petit-enfant, comme le ferait un shell).
Il attend ensuite les signaux et les transmet au processus enfant.
Docker exécute un seul processus dans un conteneur par défaut. Si ce processus génère des processus enfants et ne les récolte pas correctement, ils deviennent des processus zombies.
Tini assure que ces processus zombies sont correctement récoltés, améliorant ainsi le comportement du conteneur et réduisant la probabilité de cas limites.
Dans notre Dockerfile, nous utilisons Tini comme point d’entrée :
ENTRYPOINT ["/sbin/tini","-g","gulp"]
Cela signifie que Tini est le premier processus qui est lancé dans notre conteneur.
Il lancera ensuite gulp
en tant que processus enfant.
Tout signal envoyé au conteneur sera transmis par Tini à gulp
.
Si gulp
génère des processus enfants et ne les récolte pas, Tini le fera.
# Ce Makefile est utilisé pour gérer le projet Docker Compose.
# Définir les valeurs par défaut pour les variables DIST_DIR et REPOSITORY_URL.
DIST_DIR ?= $(CURDIR)/dist
REPOSITORY_URL ?= file://$(CURDIR)
export REPOSITORY_URL DIST_DIR
# Activer Docker BuildKit pour une construction plus rapide et la mise en cache des images.
DOCKER_BUILDKIT ?= 1
COMPOSE_DOCKER_CLI_BUILD ?= 1
export DOCKER_BUILDKIT COMPOSE_DOCKER_CLI_BUILD
# Définition des commandes shell réutilisables pour Docker Compose.
# compose_cmd est une fonction qui exécute la commande 'docker compose' avec le fichier docker-compose.yml du répertoire courant.
# Elle prend un argument $(1) qui représente les options supplémentaires à passer à la commande 'docker compose'.
# $(CURDIR) est une variable d'environnement dans le Makefile qui représente le répertoire courant dans lequel
# le Makefile est exécuté. C'est une fonctionnalité intégrée de GNU Make. Elle est souvent utilisée pour référencer
# des fichiers ou des répertoires relatifs au répertoire courant.
compose_cmd = docker compose --file=$(CURDIR)/docker-compose.yml $(1)
# compose_up est une fonction qui utilise compose_cmd pour exécuter 'docker compose up'.
# Elle prend un argument $(1) qui représente les options supplémentaires à passer à la commande 'docker compose up'.
# L'option '--build' est toujours incluse, ce qui signifie que Docker construira les images avant de démarrer les conteneurs.
compose_up = $(call compose_cmd, up --build $(1))
# compose_run est une fonction qui utilise compose_cmd pour exécuter 'docker compose run'.
# Elle prend un argument $(1) qui représente les options supplémentaires à passer à la commande 'docker compose run'.
# L'option '--user=0' est toujours incluse, ce qui signifie que les commandes seront exécutées en tant que root à l'intérieur du conteneur.
compose_run = $(call compose_cmd, run --user=0 $(1))
# La cible par défaut. Elle nettoie le projet, le construit et le vérifie.
all: clean build verify
# Construire le projet à l'intérieur d'un conteneur Docker.
# Cette règle Makefile utilise la fonction compose_up définie précédemment pour exécuter 'docker compose up' avec l'option '--exit-code-from=build'.
# L'option '--exit-code-from=build' signifie que la commande 'docker compose up' renverra le code de sortie du service 'build'.
# Si le service 'build' se termine avec un code de sortie non nul (ce qui signifie qu'une erreur s'est produite), alors 'docker compose up' se terminera également avec un code de sortie non nul.
# Cela permet à Make de savoir si la construction du projet a réussi ou non.
build:
@$(call compose_up,--exit-code-from=build build)
# Vérifier le projet. Actuellement désactivé.
verify:
@echo "Vérification désactivée"
# Démarrer les services 'serve' et 'qrcode'.
serve:
@$(call compose_up, --force-recreate serve qrcode)
# Démarrer un shell à l'intérieur du conteneur du service 'serve'.
shell:
@$(call compose_run,--entrypoint=sh --rm serve)
# Mettre à jour le fichier de verrouillage pour les dépendances npm.
dependencies-lock-update:
@$(call compose_run,--entrypoint=npm --rm serve install --package-lock)
# Mettre à jour les dépendances npm et le fichier de verrouillage.
dependencies-update:
@$(call compose_run,--entrypoint=ncu --workdir=/app/npm-packages --rm serve -u)
@make -C $(CURDIR) dependencies-lock-update
# Construire une version PDF du projet.
pdf:
@$(call compose_up, --exit-code-from=pdf pdf)
# Nettoyer le projet en arrêtant et en supprimant les conteneurs Docker, les réseaux et les volumes.
clean:
@$(call compose_cmd, down -v --remove-orphans)
@rm -rf $(DIST_DIR)
# Démarrer le service 'qrcode'.
qrcode:
@$(call compose_up, qrcode)
# Déclarer des cibles factices.
.PHONY: all build verify serve qrcode pdf dependencies-update dependencies-lock-update
# Activer Docker BuildKit pour une construction plus rapide et la mise en cache des images.
DOCKER_BUILDKIT ?= 1
COMPOSE_DOCKER_CLI_BUILD ?= 1
export DOCKER_BUILDKIT COMPOSE_DOCKER_CLI_BUILD
Cette configuration de docker compose concerne Docker BuildKit, une fonctionnalité de Docker qui améliore les performances de construction des images Docker.
La ligne DOCKER_BUILDKIT ?= 1
vérifie si la variable d’environnement DOCKER_BUILDKIT
est déjà définie.
Si ce n’est pas le cas, elle lui attribue la valeur 1
, ce qui active Docker BuildKit.
De même, COMPOSE_DOCKER_CLI_BUILD ?= 1
vérifie si la variable d’environnement COMPOSE_DOCKER_CLI_BUILD
est déjà définie.
Si ce n’est pas le cas, elle lui attribue la valeur 1
.
Cette variable d’environnement est utilisée pour activer l’utilisation de Docker CLI lors de l’utilisation de Docker Compose.
C’est nécessaire car Docker Compose ne supporte pas BuildKit par défaut, donc cette variable d’environnement est une solution de contournement pour l’activer. En résumé, ces deux lignes de code activent Docker BuildKit pour améliorer les performances de construction des images Docker lors de l’utilisation de Docker Compose.
# Définition des commandes shell réutilisables pour Docker Compose.
# compose_cmd est une fonction qui exécute la commande 'docker compose' avec le fichier docker-compose.yml du répertoire courant.
# Elle prend un argument $(1) qui représente les options supplémentaires à passer à la commande 'docker compose'.
# $(CURDIR) est une variable d'environnement dans le Makefile qui représente le répertoire courant dans lequel
# le Makefile est exécuté. C'est une fonctionnalité intégrée de GNU Make. Elle est souvent utilisée pour référencer
# des fichiers ou des répertoires relatifs au répertoire courant.
compose_cmd = docker compose --file=$(CURDIR)/docker-compose.yml $(1)
# compose_up est une fonction qui utilise compose_cmd pour exécuter 'docker compose up'.
# Elle prend un argument $(1) qui représente les options supplémentaires à passer à la commande 'docker compose up'.
# L'option '--build' est toujours incluse, ce qui signifie que Docker construira les images avant de démarrer les conteneurs.
compose_up = $(call compose_cmd, up --build $(1))
# compose_run est une fonction qui utilise compose_cmd pour exécuter 'docker compose run'.
# Elle prend un argument $(1) qui représente les options supplémentaires à passer à la commande 'docker compose run'.
# L'option '--user=0' est toujours incluse, ce qui signifie que les commandes seront exécutées en tant que root à l'intérieur du conteneur.
compose_run = $(call compose_cmd, run --user=0 $(1))
# Construire le projet à l'intérieur d'un conteneur Docker.
# Cette règle Makefile utilise la fonction compose_up définie précédemment pour exécuter 'docker compose up' avec l'option '--exit-code-from=build'.
# L'option '--exit-code-from=build' signifie que la commande 'docker compose up' renverra le code de sortie du service 'build'.
# Si le service 'build' se termine avec un code de sortie non nul (ce qui signifie qu'une erreur s'est produite), alors 'docker compose up' se terminera également avec un code de sortie non nul.
# Cela permet à Make de savoir si la construction du projet a réussi ou non.
build:
@$(call compose_up,--exit-code-from=build build)
Cette règle Makefile
est utilisée pour construire le projet à l’intérieur d’un conteneur Docker.
Elle utilise la fonction compose_up
pour exécuter la commande docker compose up
avec l’option --exit-code-from=build
.
Cette option permet à la commande docker compose up
de renvoyer le code de sortie du service build
, ce qui permet à Make de savoir si la construction du projet a réussi ou non.
docker history nginx
IMAGE CREATED CREATED BY SIZE COMMENT
f35646e83998 4 weeks ago /bin/sh -c #(nop) CMD ["nginx" "-g" "daemon… 0B
<missing> 4 weeks ago /bin/sh -c #(nop) STOPSIGNAL SIGTERM 0B
<missing> 4 weeks ago /bin/sh -c #(nop) EXPOSE 80 0B
<missing> 4 weeks ago /bin/sh -c #(nop) ENTRYPOINT ["/docker-entr… 0B
<missing> 4 weeks ago /bin/sh -c #(nop) COPY file:0fd5fca330dcd6a7… 1.04kB
<missing> 4 weeks ago /bin/sh -c #(nop) COPY file:1d0a4127e78a26c1… 1.96kB
<missing> 4 weeks ago /bin/sh -c #(nop) COPY file:e7e183879c35719c… 1.2kB
<missing> 4 weeks ago /bin/sh -c set -x && addgroup --system -… 63.6MB
<missing> 4 weeks ago /bin/sh -c #(nop) ENV PKG_RELEASE=1~buster 0B
<missing> 4 weeks ago /bin/sh -c #(nop) ENV NJS_VERSION=0.4.4 0B
<missing> 4 weeks ago /bin/sh -c #(nop) ENV NGINX_VERSION=1.19.3 0B
<missing> 4 weeks ago /bin/sh -c #(nop) LABEL maintainer=NGINX Do… 0B
<missing> 4 weeks ago /bin/sh -c #(nop) CMD ["bash"] 0B
<missing> 4 weeks ago /bin/sh -c #(nop) ADD file:0dc53e7886c35bc21… 69.2MB
docker history nginx
IMAGE CREATED CREATED BY SIZE COMMENT
f35646e83998 4 weeks ago /bin/sh -c #(nop) CMD ["nginx" "-g" "daemon… 0B
<missing> 4 weeks ago /bin/sh -c #(nop) STOPSIGNAL SIGTERM 0B
<missing> 4 weeks ago /bin/sh -c #(nop) EXPOSE 80 0B
<missing> 4 weeks ago /bin/sh -c #(nop) ENTRYPOINT ["/docker-entr… 0B
<missing> 4 weeks ago /bin/sh -c #(nop) COPY file:0fd5fca330dcd6a7… 1.04kB
<missing> 4 weeks ago /bin/sh -c #(nop) COPY file:1d0a4127e78a26c1… 1.96kB
<missing> 4 weeks ago /bin/sh -c #(nop) COPY file:e7e183879c35719c… 1.2kB
<missing> 4 weeks ago /bin/sh -c set -x && addgroup --system -… 63.6MB
<missing> 4 weeks ago /bin/sh -c #(nop) ENV PKG_RELEASE=1~buster 0B
<missing> 4 weeks ago /bin/sh -c #(nop) ENV NJS_VERSION=0.4.4 0B
<missing> 4 weeks ago /bin/sh -c #(nop) ENV NGINX_VERSION=1.19.3 0B
<missing> 4 weeks ago /bin/sh -c #(nop) LABEL maintainer=NGINX Do… 0B
<missing> 4 weeks ago /bin/sh -c #(nop) CMD ["bash"] 0B
<missing> 4 weeks ago /bin/sh -c #(nop) ADD file:0dc53e7886c35bc21… 69.2MB
Il est préférable de limiter le nombre de couches pour limiter la bande passante.
RUN apk add \
patch \
tar \
mercurial \
git \
ruby \
ruby-devel \
rubygem-bundler \
make \
gcc-c++ \
zlib-devel \
libxml2-devel \
docker \
nodejs \
npm \
sssd \
&& mkdir -p /var/lib/docker-builder \
&& mkdir -p /etc/docker-builder
&& apk remove make gcc maven ... \
&& rm -f previously-downloaded.tar.gz \
&& apk clean all \
&& rm -rf /tmp/*
Il est utile, pour des raisons d’espace, de nettoyer le filesystem de ses futurs containers, en donnant les bonnes instructions dans le Dockerfile.
Les mots clés EXPOSE
et LABEL
peuvent fournir de précieuses informations aux utilisateurs de vos images !
(les ports exposés par défaut, les auteurs de l’image, la version d’un middleware embarqué, …)
Il est possible de lister toutes les modifications qui ont été apportées au filesystem d’un container.
docker diff 29f1c4
C /run
A /run/nginx.pid
C /var
C /var/lib
C /var/lib/nginx
C /var/lib/nginx/tmp
A /var/lib/nginx/tmp/client_body
A /var/lib/nginx/tmp/fastcgi
A /var/lib/nginx/tmp/proxy
A /var/lib/nginx/tmp/scgi
A /var/lib/nginx/tmp/uwsgi
C /var/log
C /var/log/nginx
A /var/log/nginx/access.log
A /var/log/nginx/error.log
Une bonne piste pour savoir quels volumes déclarer !
Savoir que mon PID 1 est toujours en cours d’exécution n’est peut-être pas la meilleure piste pour savoir si mon conteneur est en bonne santé !
FROM ghost:3
RUN apt update && apt install curl -y \
&& rm -rf /var/lib/apt/lists/*
HEALTHCHECK --interval=1m --timeout=30s --retries=3 CMD curl --fail http://localhost:2368 || exit 1
Les logs
des containers
du démon Docker
docker container exec
docker inspect
docker cp
docker history
docker stats
rappel : docker container logs
permet de lister les logs des conteneurs.
docker container logs 47d6
Fri Nov 20 00:39:52 UTC 2023
Fri Nov 20 00:39:53 UTC 2023
Par défaut, les logs des conteneurs sont stockés dans des fichiers json. Mais comment faire pour les envoyer vers un concentrateur ?
docker container run -d --log-driver=gelf --log-opt gelf-address=udp://localhost:12201 -p 88:80 nginx
on spécifie un driver
on configure le driver
Étapes
Lancer une stack ELK grâce aux fichiers fournis dans le repo https://gitlab.univ-artois.fr/bruno.verachten/devops-docker-tp13
Alimenter en logs avec la commande docker container run --log-driver=gelf --log-opt gelf-address=udp://localhost:12201 alpine bash -c 'seq 1 100'
Créer un index "timestamp" sur Kibana puis aller à l’écran "discover"
Lancer un conteneur nginx et concentrez ses logs dans ELK
Dependabot est un outil qui vous aide à maintenir vos dépendances à jour.
Il ouvre automatiquement des merge requests pour les mises à jour de dépendances dans vos projets GitLab.
Dependabot peut analyser vos Dockerfiles
et vos fichiers docker-compose
pour trouver les dépendances qui peuvent être mises à jour.
Il crée ensuite des merge requests pour chaque mise à jour de dépendance.
Garder vos dépendances à jour est crucial pour la sécurité et la stabilité de vos applications.
Dependabot automatise ce processus, vous faisant gagner du temps et réduisant le risque d’oublier une mise à jour importante.
Dependabot n’est pas magique, il faut que votre CI soit capable de construire votre application avec les nouvelles dépendances.
Ce qui n’est pas testé ne fonctionne pas.
Malheureusement, il n’y a pas encore de support officiel pour GitLab dans Dependabot (et vice versa).
Notre forge Gitlab est définie dans un fichier docker-compose.yml, et Dependabot aussi.
Dependabot doit avoir accès à notre forge GitLab pour pouvoir ouvrir des merge requests.
Suivons donc la documentation officielle.
Notez bien le token, vous ne le reverrez plus jamais. |
L’étape suivante, c’est de positionner les valeurs de certaines variables d’environnement.
Ça peut être dans un .env
, ou dans les variables d’environnement de votre machine.
export SETTINGS__GITLAB_URL=http://localhost
export SETTINGS__GITLAB_ACCESS_TOKEN=glpat-LXUfrxeFXuRJXNTNNifD
Démarrez l’application avec docker compose
:
curl -s https://gitlab.com/dependabot-gitlab/dependabot/-/raw/v3.8.0-alpha.1/docker-compose.yml | docker compose -f - up -d
😢
Vous vous souvenez du chapitre sur les réseaux Docker ?
Dans Docker, chaque conteneur a son propre espace de nom réseau, ce qui signifie que localhost à l’intérieur d’un conteneur fait référence au conteneur lui-même, et non à la machine hôte ou à d’autres conteneurs.
Lorsque vous essayez de faire un ping
sur gitlab-instance-gitlab-1
depuis le conteneur web, il ne connaît pas le nom d’hôte gitlab-instance-gitlab-1
car il n’est pas dans le même espace de nom réseau.
Pour permettre aux conteneurs de communiquer entre eux, ils doivent être dans le même réseau Docker.
Lorsque vous utilisez Docker Compose, il crée automatiquement un réseau par défaut pour votre application et tout service défini dans le docker-compose.yml
peut atteindre les autres en utilisant le nom du service comme nom d’hôte.
Dans notre cas, le conteneur web
et le conteneur gitlab-instance-gitlab-1
ne sont pas dans le même réseau Docker.
Vous pouvez vérifier cela en inspectant les réseaux de chaque conteneur à l’aide de la commande docker inspect
.
S’ils ne sont pas dans le même réseau, nous pouvons créer un réseau et ajouter les deux services à celui-ci dans notre fichier docker-compose.yml
.
Voici un exemple :
version: '3' services: web: image: web networks: - mynetwork gitlab-instance-gitlab-1: image: gitlab networks: - mynetwork networks: mynetwork:
Après avoir mis à jour votre fichier docker-compose.yml
, vous devez recréer vos conteneurs pour que les modifications prennent effet.
Vous pouvez le faire avec la commande docker compose up -d --force-recreate
.
Après cela, vous devriez pouvoir faire un ping
sur gitlab-instance-gitlab-1
depuis le conteneur web.
web:
image: *base_image
networks:
[...]
networks:
mynetwork:
Et…
gitlab:
# The Docker image to use for the GitLab server
image: 'gitlab/gitlab-ce:16.6.0-ce.0'
networks:
- mynetwork
[...]
networks:
mynetwork:
Sauf que…
root@95def7cb933d:/home/dependabot/app# nmap -sn 172.30.0.0/16
Starting Nmap 7.80 ( https://nmap.org ) at 2023-11-21 20:45 UTC
Nmap scan report for 172.30.0.1
Host is up (0.0000090s latency).
MAC Address: 02:42:50:A6:7C:5A (Unknown)
Nmap scan report for symbiosis-gitlab-1.symbiosis_mynetwork (172.30.0.2)
Host is up (0.000012s latency).
MAC Address: 02:42:AC:1E:00:02 (Unknown)
Nmap scan report for symbiosis-gitlab-runner-1-1.symbiosis_mynetwork (172.30.0.3)
Host is up (0.000015s latency).
MAC Address: 02:42:AC:1E:00:03 (Unknown)
Nmap scan report for symbiosis-gitlab-runner-2-1.symbiosis_mynetwork (172.30.0.4)
Host is up (0.000032s latency).
MAC Address: 02:42:AC:1E:00:04 (Unknown)
Nmap scan report for symbiosis-redis-1.symbiosis_mynetwork (172.30.0.5)
Host is up (0.0000070s latency).
MAC Address: 02:42:AC:1E:00:05 (Unknown)
Nmap scan report for symbiosis-mongodb-1.symbiosis_mynetwork (172.30.0.6)
Host is up (0.000016s latency).
MAC Address: 02:42:AC:1E:00:06 (Unknown)
Nmap scan report for symbiosis-docker-1.symbiosis_mynetwork (172.30.0.7)
Host is up (0.000021s latency).
MAC Address: 02:42:AC:1E:00:07 (Unknown)
Nmap scan report for symbiosis-worker-1.symbiosis_mynetwork (172.30.0.9)
Host is up (0.000024s latency).
MAC Address: 02:42:AC:1E:00:09 (Unknown)
Nmap scan report for 95def7cb933d (172.30.0.10)
Host is up.
Victoire?
docker compose -f docker-compose-dependabot.yml exec -it -u root web b
ash
root@95def7cb933d:/home/dependabot/app# ping symbiosis-gitlab-1.symbiosis_mynetwork
PING symbiosis-gitlab-1.symbiosis_mynetwork (172.30.0.2) 56(84) bytes of data.
64 bytes from symbiosis-gitlab-1.symbiosis_mynetwork (172.30.0.2): icmp_seq=1 ttl=64 time=0.165 ms
64 bytes from symbiosis-gitlab-1.symbiosis_mynetwork (172.30.0.2): icmp_seq=2 ttl=64 time=0.053 ms
64 bytes from symbiosis-gitlab-1.symbiosis_mynetwork (172.30.0.2): icmp_seq=3 ttl=64 time=0.039 ms
64 bytes from symbiosis-gitlab-1.symbiosis_mynetwork (172.30.0.2): icmp_seq=4 ttl=64 time=0.040 ms
64 bytes from symbiosis-gitlab-1.symbiosis_mynetwork (172.30.0.2): icmp_seq=5 ttl=64 time=0.056 ms
^C
--- symbiosis-gitlab-1.symbiosis_mynetwork ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4165ms
rtt min/avg/max/mdev = 0.039/0.070/0.165/0.047 ms
On y est presque!
rappel : `docker container exec `permet de lancer une commande dans un container
docker exec <containerID> echo "hello"
docker exec –it <containerID> bash
rappel : `docker inspect `permet de lister toutes les caractéristiques d’une image ou d’un container.
On peut filtrer le retour de la commande avec jq
ou l’option --format
.
Cette commande permet d’échanger des fichiers entre un conteneur et la machine hôte.
docker cp --help
Usage: docker cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH|-
docker cp [OPTIONS] SRC_PATH|- CONTAINER:DEST_PATH
Copy files/folders between a container and the local filesystem
Options:
-a, --archive Archive mode (copy all uid/gid information)
-L, --follow-link Always follow symbol link in SRC_PATH
Cette commande permet d’afficher la concaténation de tous les Dockerfiles qui ont abouti à cette image.
docker history nginx
IMAGE CREATED CREATED BY SIZE f35646e83998 4 weeks ago /bin/sh -c #(nop) CMD ["nginx" "-g" "daemon… 0B
<missing> 4 weeks ago /bin/sh -c #(nop) STOPSIGNAL SIGTERM 0B
<missing> 4 weeks ago /bin/sh -c #(nop) EXPOSE 80 0B
<missing> 4 weeks ago /bin/sh -c #(nop) ENTRYPOINT ["/docker-entr… 0B
<missing> 4 weeks ago /bin/sh -c #(nop) COPY file:0fd5fca330dcd6a7… 1.04kB
<missing> 4 weeks ago /bin/sh -c #(nop) COPY file:1d0a4127e78a26c1… 1.96kB
<missing> 4 weeks ago /bin/sh -c #(nop) COPY file:e7e183879c35719c… 1.2kB
<missing> 4 weeks ago /bin/sh -c set -x && addgroup --system -… 63.6MB
<missing> 4 weeks ago /bin/sh -c #(nop) ENV PKG_RELEASE=1~buster 0B
<missing> 4 weeks ago /bin/sh -c #(nop) ENV NJS_VERSION=0.4.4 0B
<missing> 4 weeks ago /bin/sh -c #(nop) ENV NGINX_VERSION=1.19.3 0B
<missing> 4 weeks ago /bin/sh -c #(nop) LABEL maintainer=NGINX Do… 0B
<missing> 4 weeks ago /bin/sh -c #(nop) CMD ["bash"] 0B
<missing> 4 weeks ago /bin/sh -c #(nop) ADD file:0dc53e7886c35bc21… 69.2MB
Cette commande permet d’avoir les stats en temps réel d’un conteneur.
docker stats 42f128
CONTAINER CPU % MEM USAGE/LIMIT MEM % NET I/O
42f128 0.00% 1.454 MB/4.145 GB 0.04% 648 B/648 B
Notre valeureux collègue Jean-Michel s’initiait à l’art mystérieux de Docker, et généreusement partageait ses créations sous forme d’images Docker pour l’entreprise.
Michel a soudainement décroché le jackpot du Loto, laissant derrière lui son bureau du jour au lendemain, alors qu’il était sur le point de nous offrir une image Nginx.
Désormais, nous n’avons que le binaire de son chef-d’œuvre, accessible à cette adresse :
Rassurez-vous, on nous a dit que docker load
est la première étape de la formule magique pour percer les secrets de son œuvre.
À vos claviers ! 🧙♂️✨
docker load
est la première étape de la formule magique
Okay…
🧙♂️✨
docker load -i bad-nginx.dkr
Loaded image: bad-nginx:1
Nous voilà bien avancés…
On essaye de lancer un conteneur basé dessus ?
docker container run -d --name bad-1 bad-nginx:1
f4caa4a641729c0ab3d7b5974fa735c128047f75292c7f16495b72c7c6808502
C’est lancé ?
docker container ls --format '{{.Names}}' | grep "bad"
Oups, il n’est pas là…
Essayons encore.
docker container ls -a --format '{{.Names}}' | grep "bad"
bad-1
C’est donc lancé, mais il a crashé.
Allons donc chercher les logs…
docker container logs bad-1
Error : nginx is not executable
On a trouvé le problème !
Spoiler alert : on a trouvé UN problème, pas LE problème.
Je ne sais pas ce qu’a fait Jean-Michel, mais il a cassé le binaire de Nginx.
Est-ce un problème de permission, de dépendance, de version, de compilation ?
En tous cas, c’est cassé.
Regardons un peu ce que nous dit docker inspect de tout ça…
docker image inspect bad-nginx:1
[
{
"Id": "sha256:a469e4d1cb0c61324298fc6fde09f78f211420a1848b690dec35d9ac324a6cab",
"RepoTags": [
"bad-nginx:1"
],
[...]
"ContainerConfig": {
[...]
"ExposedPorts": {
"80/tcp": {}
},
Port 80 exposé, c’est bon signe.
[...]
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
On a un PATH, c’est bon signe.
"Cmd": [
"/bin/sh",
"-c",
"#(nop) ",
"CMD [\"/bin/sh\" \"-c\" \"nginx\"]"
],
On a un CMD, c’est bon signe.
[...]
"Entrypoint": [
"/bin/sh",
"-c",
"/tmp/entrypoint.sh"
],
On a un Entrypoint, c’est bon signe.
[...]
"Labels": {
[...]
"org.label-schema.name": "CentOS Base Image",
[...]
}
},
"DockerVersion": "18.09.0",
[...]
"Architecture": "amd64",
"Os": "linux",
[...]
}
]
[...]
"Labels": {
[...]
"org.label-schema.name": "CentOS Base Image",
[...]
}
},
"DockerVersion": "18.09.0",
[...]
"Architecture": "amd64",
"Os": "linux",
[...]
}
]
C’est bien une image Linux amd64
(donc pour ton PC, mais pas pour ton Mac M1, toi là-bas), mais argh, c’est basé sur CentOS, pas sur Alpine ou Debian !
Ça a été construit avec une vieille version de Docker.
Qu’est-ce qu’on fait maintenant ?
Tournons-nous vers l’histoire !
docker history bad-nginx:1
IMAGE CREATED CREATED BY SIZE COMMENT
a469e4d1cb0c 3 years ago /bin/sh -c #(nop) CMD ["/bin/sh" "-c" "ngin… 0B
<missing> 3 years ago /bin/sh -c #(nop) ENTRYPOINT ["/bin/sh" "-c… 0B
<missing> 3 years ago /bin/sh -c #(nop) EXPOSE 80 0B
<missing> 3 years ago /bin/sh -c #(nop) COPY file:b0fc89bcaf962602… 98B
<missing> 3 years ago /bin/sh -c yum install -y nginx && yum clean… 56.7MB
<missing> 3 years ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 3 years ago /bin/sh -c #(nop) LABEL org.label-schema.sc… 0B
<missing> 3 years ago /bin/sh -c #(nop) ADD file:538afc0c5c964ce0d… 215MB
Ça se lit de bas en haut…
Le premier ADD
est l’image de base, CentOS.
La deuxième ligne, c’est une métadata, on s’en fiche un peu.
La troisième ligne, c’est le CMD
de l’image de base, qui est bash
.
La quatrième ligne, c’est l’installation de Nginx par l’infâme yum
de Centos (déjà repéré dans le docker inspect
).
docker history bad-nginx:1
IMAGE CREATED CREATED BY SIZE COMMENT
a469e4d1cb0c 3 years ago /bin/sh -c #(nop) CMD ["/bin/sh" "-c" "ngin… 0B
<missing> 3 years ago /bin/sh -c #(nop) ENTRYPOINT ["/bin/sh" "-c… 0B
<missing> 3 years ago /bin/sh -c #(nop) EXPOSE 80 0B
<missing> 3 years ago /bin/sh -c #(nop) COPY file:b0fc89bcaf962602… 98B
<missing> 3 years ago /bin/sh -c yum install -y nginx && yum clean… 56.7MB
<missing> 3 years ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 3 years ago /bin/sh -c #(nop) LABEL org.label-schema.sc… 0B
<missing> 3 years ago /bin/sh -c #(nop) ADD file:538afc0c5c964ce0d… 215MB
La cinquième ligne, c’est sans doute la copie du fichier index.html
.
La sixième ligne, c’est l' EXPOSE
de l’image finale, qui est 80
(déjà repéré dans le docker inspect
).
La septième ligne, c’est le ENTRYPOINT
de l’image finale, qui est /bin/sh -c /tmp/entrypoint.sh
(déjà repéré dans le docker inspect
).
Résumons-nous… On a:
une image de base CentOS (à remplacer par Alpine ou Debian)
une installation de nginx
.
un port 80
exposé
un ENTRYPOINT
qui lance un script nginx
un fichier HTML, qu’on n’a pas encore récupéré Ça progresse… On peut donc créer un premier 💀 Dockerfile
# Utiliser Alpine ou Debian comme image de base
FROM alpine
# Mettre à jour le système et installer nginx
RUN apk update && apk add nginx
# Exposer le port 80
EXPOSE 80
# Ajouter un fichier HTML (remplacer /chemin/vers/votre/fichier.html par le chemin réel vers votre fichier HTML)
ADD /chemin/vers/votre/fichier.html /usr/share/nginx/html
# Définir le point d'entrée pour lancer nginx
ENTRYPOINT ["nginx", "-g", "daemon off;"]
Comment récupérer ce fichu fichier HTML?
Essayons de lancer un conteneur basé sur notre image, et de récupérer le fichier HTML.
Le binaire nginx
est non fonctionnel, essayons de lancer un conteneur avec un shell.
docker container run -it --name bad-2 --entrypoint=sh bad-nginx:1 sh
/usr/bin/sh: /usr/bin/sh: cannot execute binary file
Oui, je sais, il y a une faute dans la légende du gif…
Jean-Micheeeeeeeeeeeeeeeeel, j’ai deux mots à te dire! 😡 |
Le conteneur qui tourne, on oublie, donc… Jean-Michel a laissé un terrain miné derrière lui. 💣
Mais on a d’autres armes en réserve… Le docker container create
par exemple !
docker container create --name temp_container bad-nginx:1
9149745197df413f7966181f6ca517b68713edd32ff7c35cea6e958d549a7553
Utilisons maintenant docker cp pour récupérer le fichier HTML.
Quel est le répertoire où nginx s’attend à trouver ses fichiers HTML à servir?
/usr/share/nginx/html
mkdir /tmp/html && cd /tmp/html
docker cp temp_container:/usr/share/nginx/html .
head html/index.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>Test Page for the Nginx HTTP Server on Red Hat Enterprise Linux</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<style type="text/css">
/*<![CDATA[*/
body {
background-color: #fff;
Jean-Michel n’avait même pas fini de remplir son fichier HTML… C’est encore le fichier de base de nginx sous CentOs. 😞
Jean-Michel, t’assures pas une 🥜, là…
# Ce Dockerfile met en place un serveur web simple en utilisant Nginx sur Alpine Linux.
# Utilise Alpine Linux comme image de base. Alpine Linux est une distribution Linux légère et orientée sécurité.
FROM alpine
# Met à jour la liste des paquets du système et installe Nginx.
# Nginx est un serveur web populaire qui peut également être utilisé comme proxy inverse, équilibreur de charge et cache HTTP.
RUN apk update && apk add nginx && rm -rf /var/cache/apk/*
# Expose le port 80 au monde extérieur. C'est le port standard pour le trafic HTTP.
EXPOSE 80
# Ajoute un fichier HTML à la racine du document Nginx.
# Remplacez /chemin/vers/votre/fichier.html par le chemin réel vers votre fichier HTML.
ADD /chemin/vers/votre/fichier.html /usr/share/nginx/html
# Définit le point d'entrée pour le conteneur. Cette commande sera exécutée lorsque le conteneur démarre.
# La commande "nginx" démarre le serveur Nginx.
# Le flag "-g" nous permet de définir des directives globales. Dans ce cas, nous disons à Nginx de fonctionner au premier plan (daemon off;).
ENTRYPOINT ["nginx", "-g", "daemon off;"]
Finalement, le 💀 de notre Dockerfile
était déjà bon…
Ou presque… Rien ne vous gêne dans ce Dockerfile?
👫 don’t let 👫…
Savoir lancer un container avec toutes les options
-v, -p, -w, -e, --rm, etc.
Écrire un Dockerfile optimisé pour "dockeriser" une application
pas trop gourmand, facile à modifier, paramétrable.
Étudier une image ou un conteneur pour tout débug éventuel.
Savoir s’outiller pour avoir des images qui servent d’environnement d’exécution à votre CI.
Monter un écosystème applicatif complexe via docker-compose.
Les TPs sont vos meilleurs amis.
La documentation officielle de Docker