Architecte Technique à la M A R S à l’IGN
Utilisateur de Docker depuis 2014
Ce cours est librement inspiré de plusieurs sources dont celles de Thibault Coupin.
Il existe plusieurs niveaux de virtualisation en informatique, avec un but commun : partager des ressources physiques pour simplifier et accélérer la transformation. Voici plusieurs niveau de virtualisation de ressources :
Les conteneurs sont maintenant disponible sous Windows!
La notion de Cloud tel qu’on l’entend aujourd’hui a été popularisée par Amazon quand ces derniers ont eu l’idée de mettre à disposition de tous leurs serveurs inutilisés pendant les périodes creuses de leur activité. Il était nécessaire de fournir des solutions de virtualisations rapide et sûre.
Petit à petit l’utilisation de conteneur pour simplifier les taches automatisées et améliorer le TimeToMarket s’est imposé. Aujourd’hui les plateforme de Paas, Saas et de Faas sont basés sur des conteneurs (pas forcément Docker car il existe des alternatives).
Inventé par Solomon Hyke , Docker est une technologie qui vise à populariser l’usage des conteneurs afin de faciliter la mise en place de méthodologies Devops.
Docker c’est avant tout deux notions :
Des Conteneurs constitués :
Des Image contenants :
Et un peu aussi des réseaux et des volumes
Les installations classiques de Docker sont composées de 2 éléments principaux (sans descendre plus bas niveau).
l’OCI est Un organisme qui à pour but de normaliser les éléments constitutifs de la virtualisation et des conteneurs. C’est important car il existe également des alternatives/concurrents à Docker.
Depuis mars 2017, la nomenclature des versions suit la forme des versions de Ubuntu à savoir AA.MM avec AA l’année et MM le mois (ex. : 17.04 pour la version d’avril 2017).
Un conteneur ne devrait isoler qu’un seul et unique processus à la fois, une fois ce processus terminé le conteneur s’arrête. Cette règle peut parfois être transgressé lorsqu’une amélioration notable peut être apportée par l’exécution de plusieurs processus dans un même conteneur (par ex: apache et php).
Mickaël Borne (IGN) à préparé une excellente documentation, basée sur la documentation officielle (https://docs.docker.com/engine/install/ubuntu/) pour installer Docker dans l’environnement IGN.
http://dev-env.sai-experimental.ign.fr/outils/docker/installation-docker-ce/
Pour une installation à l’ENSG, nous simplifieront quelques aspects.
Nous allons installer Docker dans la machine virtuelle Ubuntu à partir de la documentation officielle
Il est nécessaire d’installer curl
Pour permettre les accès à internet dans un environnement ou un proxy doit être utilisé, il faut configurer celui ci à plusieurs endroits. Tout d’abord, au niveau du Daemon Docker qui est lancé par System-D :
sudo mkdir -p /etc/systemd/system/docker.service.d/
sudo vi /etc/systemd/system/docker.service.d/proxy.conf
Dont le contenu sera (à adapter) :
Lors des modification de configuration, il faut s’assure que le daemon Docker tourne correctement:
Puis, au besoin il faut le relancer :
préciser qu’il faudra indiquer les réglages proxy dans les images également
Le fichier /etc/docker/daemon.json va permettre de définir les spécificités réseaux de l’installation Docker.
{
"bip": "192.168.199.1/24",
"fixed-cidr": "192.168.199.1/25",
"default-address-pools" : [
{"base" : "192.168.200.0/23","size" : 28}
]
//...
}
Au niveau du réseau, cette configuration permet de ne pas avoir de collision avec les réseaux IGN/ENSG, particulièrement lors de l’utilisation du VPN.
Si on souhaite interagir avec les environnements interne à l’IGN, il faut également préciser :
Pour éviter d’avoir à faire sudo docker, on peut ajouter un utilisateur au groupe docker :
Nous venons de lancer notre premier conteneur
note : Le projet PWD permet également de tester Docker sans l’installer au travers d’une interface web. Les sessions de travail durent 4h et la vitesse de l’interface dépend souvent de l’activité des autres utilisateurs…
Attention : alerte sur les problèmes de sécurité avec Docker et les images ROOT.
docker container run hello-world
# Alias : docker run hello-world
La commande par défaut de cette image affiche un message confirmant la bonne installation de Docker Engine. Si l’image n’est pas disponible en local, elle sera téléchargée sans avoir à faire un docker image pull hello-world
L’image est le “disque dur” figé sur lequel va se baser le conteneur.
Elle contient l’application, ses dépendances et des métadonnées.
C’est le moule pour créer les conteneurs.
REGISTRY
: URL du dépôt (par défaut hub.docker.io)IMAGE
: nom de l’image (peut contenir un chemin)TAG
: tag de l’image (par défaut latest)par exemple :
node:14.20-alpine
registry.gpf-tech.ign.fr/geoplateforme/gpf-rok4:latest
Dans un premier temps, nous allons juste les utiliser afin de construire des Conteneurs Docker
Nous verrons plus tard comment travailler avec les images :
docker image ls
docker image pull
docker image inspect
docker image rm
docker image build
OPTIONS
: diverses options sont possiblesREGISTRY/IMAGE:TAG
: l’image à utiliserCOMMANDE
: la commande à lancer dans le conteneur. Une commande par défaut peut être définie dans les métadonnées de l’image.Cette commande créé le conteneur (l’environnement d’exécution) et lance le processus dans le conteneur.
Exemple :
alpine
cat /etc/hostname
/etc/hostname
et s’arrête.--rm
permet de détruire le conteneur une fois la commande terminé (Sinon, le conteneur reste présent dans l’état Exited )Exemple :
Démarre un shell dans le conteneur.
Comme si on était dans une VM.
Vous pouvez explorez le système de fichier pour voir ce qui s’y trouve.
Cette commande n'est pas toujours possible (voir --entrypoint)
Mais pourquoi on ne voit pas les conteneurs d’avant ?
docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a5b74e24da65 alpine "cat /etc/hostname" 9 seconds ago Exited (0) 6 seconds ago happy_cori
On voit le premier conteneur resté dans l’état Exited
stop
et start
(kill
aussi)restart
pause
et unpause
Options | Description |
---|---|
--name |
donner un nom au conteneur |
-i |
interactif |
-t |
forcer l’allocation d’un TTY |
--rm |
supprimer le conteneur à la fin de son exécution |
-d |
démarrer le conteneur en arrière-plan |
--entrypoint |
redéfini le premier processus de l’image |
Il en existe beaucoup d’autres : gestion des ressources, du réseau, de l’environnement d’exécution, etc…
Le conteneur dispose généralement de son propre réseau virtuel.
Docker permet de définir :
La commande docker container run
dispose de l’option --net
4 valeurs possibles :
none
: pas de réseauhost
: les réseaux de l’hôtebridge
(par défaut) : un réseau isolé avec un mécanisme de bridgedocker network create
docker container run --rm --net host alpine ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
33481: eth0@if33482: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
link/ether e6:bd:09:a3:dc:84 brd ff:ff:ff:ff:ff:ff
inet 192.168.0.8/23 scope global eth0
valid_lft forever preferred_lft forever
Ce mode est peu utilisé, il peut parfois entrer en collisions avec des réglages réseaux d’entreprise (VPN,…). Il pose aussi des problèmes de sécurité ( suppression de l’isolation réseau).
-p
).bridge
+ résolution DNS des autres conteneursLe port 8080 de la machine hôte est redirigé vers le port 80 du conteneur apache (httpd basé sur un OS alpine).
Un conteneur est “jetable”
Les volumes apportent une solution à cela
On utilise l’option -v LOCAL_PATH:PATH_ON_CONTAINER:MODE
LOCAL_PATH
: le chemin absolu sur l’hôtePATH_ON_CONTAINER
: où brancher ce dossier dans le conteneur ?MODE
(optionnel) : mode d’accès, principalement rw (par défaut) et rodocker volume create
docker volume ls
docker volume rm
On peut préciser le driver à utiliser. Le driver dépend du backend, par défaut local.
Les métadonnées d’une image peuvent forcer la création d’un volume
Lister les volumes
$ docker volume ls
DRIVER VOLUME NAME
local 2bd7394a7adebb03f073bd82048048124578e0b506adea3064fda5d38ef7b678
local data-telegraf
local e0c1ad4b13ed61067082a3511feaae14dbdcacd19632594c129548e241575e0c
local minidlna
local mongodb
Dans certains cas, Docker créé des volumes “anonymes”, leur nom est une longue chaîne alphanumérique
⚠️ Ne concerne que les volumes anonymes
L’option --mount
permet des montages plus élaborés :
/etc/fstab
docker volume create
(ex. : pas de création de l’export NFS sur le serveur)Un volume hôte est un --mount
particulier.
Lors de l’installation , nous avons créé un conteneur pour vérifier que Docker était bien installé
docker ls
est un alias de docker container ls
La première commande n’affiche rien car le conteneur lancé est déjà arrêté. En effet, l’image hello-world
lance une commande qui affiche un message et c’est tout. Il faut ajouter l’option -a
pour afficher également les conteneurs arrêtés.
On peut trouver le nom du conteneur à supprimer dans le résultat de la commande docker container ls -a
. Si aucun nom n’est spécifié lors de la création du conteneur (option --name
), docker génère un nom aléatoire composé d’un adjectif et d’un nom de personnalité.
On pourrait aussi utiliser l’identifiant du conteneur à la place du nom.
docker image ls
docker image rm hello-world:latest
docker run hello-world
docker ls -a
docker rm <CONTAINER-NAME>
docker image rm hello-world
La commande docker container rm
ne supprime que le conteneur. Pour supprimer l’image, il faut utiliser la commande docker image rm <IMAGE-NAME>
(ou l’alias docker rmi <IMAGE-NAME>
). Une image ne peut pas être supprimée si il existe un conteneur basé dessus, même stoppé. Il faut donc supprimer le conteneur avant de pouvoir supprimer l’image.
https://hub.docker.com/_/alpine/
Dockerhub est le dépôt d’image par défaut de Docker. Ici, on ne précise pas le tag. C’est donc le tag latest
qui est téléchargé. On affiche ensuite les métadonnées de l’image téléchargée, on y trouvera des informations utiles sur l’image :
La commande docker tag permet de gérer les tag de vos images locales, cela est utile pour “renommer” une image afin de l’envoyer sur un repository distant. Cette commande permet de “nommer” l’image bce5g99azq58 afin de l’uploader dans le registry du géoportail (Il faut les droits associés)
Une fois dans le conteneur, on peut afficher le nom d’hôte du conteneur avec la commande cat /etc/hostname
. On peut également modifier la commande (unique) lancée dans un conteneur.
On peut voir le conteneur en état Exited
. Dans le premier terminal, on peut voir que le shell est arrêté.
Pour aller plus loin, cherchez des informations sur les options -d
, -w
, -h
, --rm
et --name
de la commande docker container run
et testez ces options.
/host
en mode volume hote et en lecture seule dans le conteneur/host
~/
et observez ce dossier dans votre conteneur AlpineLe fichier /etc/hostname
contient bien le nom d’hôte du conteneur. Le fichier /host/etc/hostname
contient le nom d’hôte de la machine hôte. Le dossier /host/home/ubuntu/
contient votre home (si votre user est bien ubuntu…)
/data
/data/
puis supprimez le conteneur/data
/data/
docker run --rm -i -t -v NAME:/data:rw alpine sh
docker run -i -t -v NAME:/data:rw alpine sh
docker volume ls
docker volume rm NAME
On ne peut supprimer un volume que si aucun conteneur ne l’utilise. Le message d’erreur indique l’identifiant du conteneur utilisant ce volume :
ip a
--net host
puis regardez les interfacesL’option --net host
branche le conteneur sur les interfaces réseaux de la machine hôte. Il n’y a donc pas d’isolation réseau., c’est une option à éviter dans la plupart des cas.
https://hub.docker.com/r/containous/whoami/
L’IP du conteneur n’est accessible que depuis la machine hébergeant le conteneur. On ne peut pas y accéder depuis l’extérieur de la machine hôte avec son adresse IP car c’est un réseau privé, interne à notre hôte.
On peut toujours accéder au port 80 de l’IP du conteneur. On peut maintenant également accéder au port 8080 de la machine grâce à l’option -p
y compris depuis l’exterieur de la machine (ici le LAN ENSG).
docker inspect
pour voir ce qui a été créénginx
nommé (--name
) nginx , attaché au réseau test-ningx et qui tourne en daemon.alpine/curl
sans préciser de réseau , avec un terminal interactif (/bin/sh
)
alpine/curl
attaché au réseau test-nginx avec un terminal interactif
curl -v "http://[IP|hostname][:PORT]/"
docker network create test-nginx
docker inspect test-nginx
docker run --network test-nginx --name nginx -d nginx
docker run --rm -ti alpine/curl /bin/sh
# / # curl nginx
# curl: (6) Could not resolve host: nginx
docker run --rm -ti --network test-nginx alpine/curl /bin/sh
# / # curl nginx
# <!DOCTYPE html>
# ...
Les commandes suivantes vont tenter de nettoyer tout ce qui n’est pas utilisé. A utiliser avec tact, mais peut être salvateur !
Il est possible de décorréler la CLI du Daemon Docker de votre Machine et ainsi de “piloter” un autre host avec votre CLI. Les Contextes Docker sont fait pour cela.
La commande docker exec
permet de lancer un nouveau processus dans un conteneur actif existant, cette commande est pratique pour lancer un Shell d’observation ou de debug par exemple.
La commande docker cp
permet d’échanger des fichiers/dossiers entre le user-space d’un conteneur et celui de votre host.
docker run --name alpineContainer -d alpine tail -f /dev/null
docker cp /etc/hostname alpineContainer:/local.hostname
docker exec alpineContainer cat /local.hostname
docker stop alpineContainer
docker cp alpineContainer:/etc/hostname ./alpine.hostname
# Successfully copied 2.05kB to /home/CEsnault/alpine.hostname
cat alpine.hostname
# 4583aca170a9
docker rm alpineContainer
Cette commande fonctionne également lors de l’utilisation d’un contexte distant.
tail -f /dev/null permet au processus principal de perdurer en arrière plan
Il existe d’autres commandes docker dont la documentation est disponible sur https://docs.docker.com/engine/reference/commandline/docker/
⚠️ Attention, certaines de ces commandes sont propres au mode d’exécution en cluster swarm
Le but de ce TP est de mettre en place les éléments nécessaires d’un serveur web de type LAMP.
Les fichiers nécessaires sont disponibles dans le dépôt GIT de ce cours, dans le dossier ressources
https://github.com/cedric-esnault-ign/cours_docker.git . Utilisez git pour récuperer ce dépot et travaillez dans le dossier ressouces
.
git clone https://github.com/cedric-esnault-ign/cours_docker.git
cd cours_docker/ressources
L’image à utiliser ici est httpd
. Les options --name -d -p -v --net
peuvent être utiles. La racine du serveur web dans l’image est /usr/local/apache2/htdocs/
apache-racine
pour le montage hostapache-racine
et exposant le port 80 du conteneur sur le port 8080 de la machine host.index-lamp.html
présent dans le dossier cartopoint pour remplacer la page d’accueildocker create network lamp
docker run --net lamp --name web -d -p 8080:80 -v "$(pwd)/apache-racine/:/usr/local/apache2/htdocs/" httpd:latest
cp cartopoint/index-lamp.html apache-racine/index.html
index.html
par index-lamp.php
(pensez à le renommer en index.php). qu’observez vous?httpd
est de base un simple serveur web sans fonctionnalité php. Il faudrait ajouter php dans cette image et configurer httpd pour interpréter les fichiers php. Sans cela, httpd cherche uniquement les fichiers index.html
si aucun fichier n’est précisé dans l’URL.
Même en essayant http://127.0.0.1:8080/index.php
, le résultat n’est pas satisfaisant, il n’y a qu’une page verte alors qu’elle devrait afficher l’heure.
/var/www/html
et plus /usr/local/apache2/htdocs/
!Note : On déroge ici un peu à la règle 1 processus par conteneur. On pourrait séparer apache et PHP, mais la liaison serait plus complexe.
docker logs <NAME>
docker rm -f web
docker run --net lamp --name web -d -p 8080:80 -v "$(pwd)/apache-racine/:/var/www/html/" php:7.4-apache
cp cartopoint/index-lamp.php apache-racine/index.php
On a pas besoin d’utiliser la commande docker cp
car le dossier apache-racine
est partagé entre l’host et le conteneur
Notre site web évolue ! Il va maintenant afficher une carte. Un clic permet de créer un point, sauvegardé en base de données. Un clic sur un point le supprime.
Utilisez l’image cedricici/php:7.4-apache-mysql
puis remplacez le fichier index.php
par le fichier index-bdd.php
, observez les erreurs
Il faut ensuite lancer un second conteneur pour notre base de données. Utilisez l’image mariadb:10
. Vous trouverez des informations sur la configuration de cette image sur hub.docker.com. (Précisez bien la version 10, il y a des soucis de compatibilité avec les plus récentes)
Configurez la base de données de façon à ce que le code php fonctionne (nom d’utilisateur, mot de passe et base de données). L’option -e
de docker run
permet de passer des variables d’environnement au conteneur. Les deux conteneurs doivent se situer sur le même réseau pour pouvoir se “parler” (DNS)
Les variables à utiliser :
Nom | Valeur | Explication |
---|---|---|
MARIADB_RANDOM_ROOT_PASSWORD |
yes |
Laisse le choix du mot de passe du super administrateur de la base à mariadb. On pourrait utiliser MARIADB_ROOT_PASSWORD pour choisir ce mot de passe, mais dans le contexte de ce TP, ça n’a pas d’importance. |
MARIADB_DATABASE |
mymap |
C’est le nom de la base de données à créer. Ce nom est défini dans le code PHP. |
MARIADB_USER |
user |
C’est le nom de l’utilisateur à créer. Il est défini dans le code PHP. |
MARIADB_PASSWORD |
s3cr3t |
C’est le mot de passe de l’utilisateur à créer. Il est défini dans le code PHP. |
Avec la commande docker exec ...
, lancez le client mariadb en ligne de commande pour explorer la base de données et observez le contenu de la table point au fur et à mesure des interactions avec la carte. (select * from point;
sur la table mymap )
Le fait de définir les paramètres de connexion à la base de données dans un code source est une mauvaise pratique. Il faudrait que le code PHP détermine ces informations à partir de variable d’environnement.
docker rm -f web
docker network create lamp
docker run --net lamp -d --name web -p 8080:80 -v $(pwd)/apache-racine/:/var/www/html/ cedricici/php:7.4-apache-mysql
docker run --net lamp -d --name database -e MARIADB_RANDOM_ROOT_PASSWORD=yes -e MARIADB_DATABASE=mymap -e MARIADB_USER=user -e MARIADB_PASSWORD=s3cr3t mariadb:10
# Pour lancer un client mariadb d'observation de la base
docker exec -it database mariadb -u user -D mymap --password="s3cr3t"
Détruisez les conteneurs, puis reconstruisez l’ensemble. Les points sont perdus :-(
Le but est de mettre en place NextCloud, un gestionnaire de fichier en ligne. L’image à utiliser est nextcloud, disponible sur https://hub.docker.com/_/nextcloud/. Cette page contient beaucoup de détails que vous pouvez trouver dans les métadonnées de l’image.
un serveur simple avec une base de données SQLite (un fichier)
docker exec
pour lister vos fichiers uploadé ( /var/www/html/data/
)docker network create nextcloud docker run -d –name nextcloud -v nextcloud-data:/var/www/html/data/ –net nextcloud -p 8080:80 nextcloud docker run -d –name nextcloud-database -v databasedata:/var/lib/mysql –net nextcloud -e MARIADB_RANDOM_ROOT_PASSWORD=yes -e MARIADB_DATABASE=nextcloud -e MARIADB_USER=nextcloud -e MARIADB_PASSWORD=s3cr3t mariadb
docker network create nextcloud
docker run -d --name nextcloud -v nextcloud-data:/var/www/html/data/ --net nextcloud -p 8080:80 nextcloud
docker run -d --name nextcloud-database -v databasedata:/var/lib/mysql --net nextcloud -e MARIADB_RANDOM_ROOT_PASSWORD=yes -e MARIADB_DATABASE=nextcloud -e MARIADB_USER=nextcloud -e MARIADB_PASSWORD=s3cr3t mariadb
On construit une image avec un Dockerfile
DOCKERFILE_PATH
est le chemin du dossier contenant le Dockerfile. Il est conseillé de dédier un nouveau dossier pour construire les images, toutes les références seront relative à ce dossier (et on ne pourra pas en sortir)
build
est ici un alias de buildx
que vous pouvez rencontrer, le moteur de construction par défaut étant buildkit
La documentation de référence du docker file est présente ici :
Le Dockerfile est constitué d’une suite d’instructions, chaque ligne résultant en une nouvelle couche dans l’image.
Un Dockerfile commence généralement par l’identification de l’image de base. (la seule exception est le passage d’un argument ARG
permettant de définir dynamiquement l’image de FROM
)
Modifier les métadonnées
Le consortium OpenContainers a défini une liste de clés :
Lancer une commande
Ajouter des fichiers locaux ou distants
L’emplacement <src>
est relatif au dossier de build.
Globalement identiques mais :
ADD
supporte les URLADD
décompresse les archivesCOPY
est plus “prévisible”COPY
est compatible avec le Multi-stageModifier l’environnement d’exécution
ENV #Variable d environnement
USER #Changement d utilisateur
WORKDIR #Changement du dossier de travail
Il est fortement conseillé de ne pas utiliser le user root pour des raisons de sécurité.
USER root interdit dans un environnement kubernetes sécurisé
Modifier l’exécution
CMD #Commande par défaut
EXPOSE #Déclarer un port réseau
VOLUME #Déclarer un volume
HEALTHCHECK #Définit une sonde de vie
D’autres instructions son disponible sur https://docs.docker.com/engine/reference/builder/
Il est possible de passer des paramètres lors du build de l’image.
Dans le reste du fichier, on fait référence à cette variable avec ${name:-default_value}
Pour définir la valeur lors de la construction :
Certaines variables sont automatiquement ajouté (Les réglages de proxy par exemple)
Le conteneur de production ne doit contenir que ce qui est nécessaire pour le run, il ne doit rien rester dans l’image en rapport avec le dev/build. Pour optimiser le poids de l’image de RUN, on sépare la création de l’image en plusieurs phases. Un conteneur de build génère un package à copier sur le conteneur de run
FROM debian:jessie as builder
RUN apt-get update && apt-get install -y build-essential BUILD_DEPENDENCIES
ADD https://github.com/...../master.zip /master
RUN make # Cette commande génère le fichier /master/monBinaire
FROM debian:jessie
RUN apt-get update && apt-get install RUN_DEPENDENCIES
COPY --from=builder /master/monBinaire /opt/bin/
CMD /opt/bin/monBinaire
Seul le dernier FROM sera contenu dans l’image finale
En mode cloud natif, on évitera l’utilisation de volume en favorisant les applications stateless.
Mickaël Borne a regroupé un ensemble de bonnes pratiques qui sont disponibles ici :
Nous allons ici simplement créer une nouvelle image pour avoir une moyen de livrer notre application.
php:7.4-apache
avec le mot clé FROM (mais pas cedricici/php:7.4-apache-mysql
qui est déjà un build d’image)php:7.4-apache
contient une commade pour cela : docker-php-ext-install mysqli pdo_mysql
index-bdd.php
en index.php
dans le dossier par défaut du serveur web.docker build
FROM php:7.4-apache
RUN docker-php-ext-install mysqli pdo_mysql
COPY index-bdd.php /var/www/html/index.php
docker build -t cartopoint:1.0 .
docker rm -f web
docker run -d --name web --net lamp -p 8080:80 cartopoint:1.0
Cela devrait fonctionner si vous n’avez pas détruit la stack LAMP! Sinon, il faudra recréer réseau et base de donnée.
Cette fois ci, nous allons créer pas à pas une image Docker pour une application node.js dont le code de l’application est disponible dans le dossier ressources\findmefast\
Dockerfile
1111
/app
à la racine du conteneurpackage.json
dans ce dossiernpm install --production
afin d’installer les dépendances/public
et le fichier server.js
) dans ce dossiernpm start
Et on pourra tester l’application avec la commande docker run
en mappant un port de votre machine sur le port 1111
du conteneur.
=> Pour pouvoir tester l’application entre vous il faudra faire une petite manipulation pour mapper ce port dans la VM Virtualbox
Nous allons maintenant créer une petite image multistage pour compiler puis exécuter un petit programme qui calcul n n nombre premier (le seul intérêt de ce programme est de solliciter le CPU). Nous profiterons de ce programme pour bien comprendre la notion de Kernel et de Userspace dans les conteneurs. Dans un premier temps , nous allons compiler le programme sur notre host.
ressources/prime
gcc
: sudo apt-get update && sudo apt-get install gcc
gcc prime.c -o prime
time
, le programme ./prime
prends en argument la quantité de nombre premier à trouver.Créez une nouvelle image que vous nommerez prime:debian
debian
dernière version disponible./prime 1
)Que pensez vous des résultats obtenus, est-ce normal d’après vous?
FROM debian
RUN apt-get update && apt-get install -y gcc
COPY prime.c prime.c
RUN gcc prime.c -o prime
CMD [ "./prime","1" ]
$ docker build -t prime:debian .
$ time docker run prime:debian ./prime 10000
Calcul des 10000 premiers nombres premiers
real 0m2,624s
user 0m0,022s
sys 0m0,018s
Le temps dans l’espace utilisateur de notre Hote est nul car toute l’opération se déroule dans l’espace utilisateur du conteneur.
Nous allons améliorer l’image en créant une image multistage
FROM
pour définir un premier stage⚠️ n’écrasez pas l’image prime:debian ⚠️
On peut même aller plus loin et utiliser une image plus petite et se basant sur l’image scratch qui ne contient rien. Il faut donc inclure les librairies dans le binaire final (option -static). Cela pose tout de même quelques limitation, l’image ne contenant même pas de shell, il n’est pas possible de passer des paramètres.
Vous êtes en avance, voici une proposition de Dockerfile à créer en toute autonomie : dockeriser une application LibreQR, une application web de génération de QR Code. Voici les ressources nécéssaires :
php:7.4-apache
libpng-dev
et unzip
docker-php-ext-install gd
www-data
.FROM php:7.4-apache
RUN apt-get update && \
apt-get install -y unzip libpng-dev
RUN docker-php-ext-install gd
ADD main.zip /main.zip
ADD config.inc.php /var/www/html/
WORKDIR /var/www/html
RUN unzip /main.zip && \
mv libreqr/* . && \
chown -R www-data:www-data /var/www/html/
ENV LIBREQR_THEME=libreqr \
LIBREQR_DEFAULT_LOCALE=fr \
LIBREQR_CUSTOM_TEXT_ENABLED=true \
LIBREQR_CUSTOM_TEXT="LibreQR on docker" \
LIBREQR_DEFAULT_REDUNDANCY=high \
LIBREQR_DEFAULT_MARGIN=20 \
LIBREQR_DEFAULT_SIZE=300 \
LIBREQR_DEFAULT_BGCOLOR=FFFFFF \
LIBREQR_DEFAULT_FGCOLOR=000000
Nous pouvons voir ensemble les étapes nécessaires à la Dockerisation de vos applications.
Imaginez la complexité pour déployer un CMS comprenant :
Docker compose permet de définir tous les éléments nécessaires pour faire tourner une application multi-conteneurs.
L’application est définie dans un fichier au format YAML avec 3 sections principales, plus quelques autres informations.
Compose à été complètement réécrit en 2020 et intégré à la CLI Docker
docker compose
et non plus docker-compose
Le modèle du docker-compose.yml a plusieurs versions possibles.
Compose file format | Docker Engine |
---|---|
specification | 19.03.0+ |
3.8 | 19.03.0+ |
3.7 | 18.06.0+ |
… | … |
2.0 | 1.10.0+ |
1.0 | 1.9.1.+ |
https://docs.docker.com/compose/compose-file/compose-versioning/
La dernière version specification est devenue un “standard” de manière à ne plus être propre à l’utilisation avec le daemon docker.
Permet de définir les éléments composant l’application :
Tous les détails sur la doc.
Quelle est la base du conteneur ?
Utiliser une image est plus sûr. Un pipeline de CI est chargé de construire les images avec une gestion des Tags permettant d’avoir des releases connues. La création de l’image “à la volée” dans le docker-compose est une option viable en mode de développement.
Montage des volumes Docker ou host
volumes:
# sans précision, Docker créé un volume nommé (sans nom...)
- /var/lib/mysql
# Un volume Docker nommé
- datavolume:/var/lib/mysql
# Un volume "host" en chemin absolu
- /opt/data:/var/lib/mysql
# Un volume "host" en chemin relatif au fichier docker-compose
- ./cache:/tmp/cache
# Un volume "host" en chemin relatif au home de l'utilisateur
- ~/configs:/etc/configs/:ro
Branchement des réseaux et exposition de ports sur la machine hôte
services:
some-service:
networks:
- some-network
- other-network
ports:
- "80:80" # Bien mettre les guillemets
Seuls les services qui seront exposés doivent être connecté au réseau “externe” , les autres services doivent être connectés au même réseau interne pour pouvoir communiquer entres eux. Il n’est nécéssaire de déclarer que les ports ouvert sur le host. Les conteneurs d’un même réseau ont un accès total aux autre conteneurs de ce réseau.
Docker-compose démarre les conteneurs dans le bon ordre à condition qu’il le connaisse…
Pour surcharger la commande par défaut, on utilise le paramètre command
Quelques autres options sont intéressantes :
Tous les détails sur la doc.
Il est également possible de définir des volumes temporaires qui seront stockés en RAM et seront détruits avec le conteneur
Tous les détails sur la doc.
Une nouvelle partie a fait son apparition pour simplifier la configuration des stack compose.
#TODO
Depuis la version2 de docker compose, la commande docker-compose
est intégré à la commande docker
et devient donc une sous-commande de la commande docker
.
On lance l’application avec la commande
L’option -d
permet de lancer les conteneurs en arrière-plan. L’option -f
permet de spécifier un fichier compose différent (par défaut docker-compose.yaml
)
Gestion des conteneurs
Nettoyage des conteneurs stoppés
Nettoyage des éléments
docker compose help
ou
Pour ce TP, nous alons tenter de reproduire le TP LAMP en utilisant un docker-compose.yaml
note : Il existe des solutions plus “sûr” pour passer des variables d’environnement sensibles (mot de passe)
services:
web:
image: cartopoint:1.0
ports:
- "8080:80"
networks:
- lamp
depends_on:
- database
database:
image: mariadb:10
environment:
- MARIADB_RANDOM_ROOT_PASSWORD=yes
- MARIADB_DATABASE=mymap
- MARIADB_USER=user
- MARIADB_PASSWORD=s3cr3t
volumes:
- databasedata:/var/lib/mysql
networks:
- lamp
volumes:
databasedata:
networks:
lamp:
Quand on parle de Docker en 2024, on peut difficillement ne pas évoquer Kubernetes (K8S).
K8S est une solution d’orchestration de conteneur mise au point par Google et devenue la référence en la matière. On peut résumer K8S à un super compose, même si il permet beaucoup plus de chose. K8S a permis d’amener les conteneurs Docker (créé avant tout pour les développeurs) en production , en apportant le contrôle et la sécurité nécéssaire.
Docker propose lui aussi son orchestrateur, SWARM, dont la CLI est intégré au client docker
. Celui -ci n’étant pas au niveau de kubernetes, nous n’en parlerons pas, même si il a eu l’avantage d’être plus simple que K8S il y a quelques années.
curl -sfL https://get.k3s.io | sh - export KUBECONFIG=“/etc/rancher/k3s/k3s.yaml”
kompose pour convertir une application docker-compose en manifests K8S
Une solution d’orchestration de conteneur va permettre de déployer et de maintenir en fonctionnement des conteneurs. Du point de vue de l’utilisateur développeur, k8s c’est surtout :
L’API de k8s est extensible, c’est à dire qu’il est possible de créer de nouveaux types d’objets kubernetes et ainsi d’ajouter de nouvelles fonctionalitées à vos cluster.
Il y a bien d’autres types d’objets Kubernetes !
Pour pouvoir tester k8s, il va nous falloir installer une mini-distribution kubernetes : k3s. Cette distribution permet de tester rapidement kubernetes sur un seul noeud en masquant sa complexité sous-jacente.
Après quelques instant, nous pouvons tester si le cluster (de 1 noeud…) est disponible :
Nous allons essayer de déployer notre application cartopoint dans notre k3s, pour cela il faut définir les Manifest des différents objets nécéssaires.
Pour faire cela, nous allons utiliser l’outil Kompose qui permet de traduire un dockerfile en Manifests.
Il faut ensuite lancer la conversion :
Une alerte est levée car nous n’avons pas précisé l’exposition de la base de donnée, nous y reviendrons.
Kompose à généré des fichiers Manifest :
Observons en détail le fichier database-persistentcolumeclaim.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
creationTimestamp: null
labels:
io.kompose.service: databasedata
name: databasedata
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Mi
status: {}
Puis déployons le :
Nous pouvons ensuite appliquer le reste des Manifests
sudo k3s kubectl apply -f database-deployment.yaml
sudo k3s kubectl apply -f lamp-networkpolicy.yaml
sudo k3s kubectl apply -f web-deployment.yaml
sudo k3s kubectl apply -f web-service.yaml
sudo k3s kubectl apply -f web-deployment.yaml
Si nous ré-appliquons les Manifests , k3s change de message :
Cela indique que le système est idempotent et qu’il prendra en compte toute modification mais uniquement celles ci.
On peut vérifier que les objets sont présents :
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
databasedata Bound pvc-d3705298-0b05-4c91-b9a1-7d9c5760685b 100Mi RWO local-path 11h
Puis, pour accéder à l’application, nous pouvons utiliser le proxy fourni par k3s
Cette commande transfert le port 8080 du service web dans le k3s, vers le port 9999 de votre machine.
Quelques soucis subsistent ?
Il semblerait que le pod web ne soit pas correctement démarré
En effet l’image cartopoint:1.0 n’est pas disponible! Modifions donc le Manifest web-deployment.yaml pour utiliser une image publique :
Appliquons la différence, et vérifions
Si tout se passe bien, nous devrions accéder maintenant au site https://127.0.0.1:9999
Il semblerait que non…
Le soucis ici vient du fait que notre application web ne trouve pas sa base de donnée. En effet, Kompose nous avait averti que celle ci n’était pas exposé dans le dockerfile.
Corrigeons ceci et créons un Manifest database-service.yaml pour exposer la base de donnée, exposées sur le port 3306.
apiVersion: v1
kind: Service
metadata:
name: database
labels:
io.kompose.service: database
spec:
ports:
- name: "3306"
port: 3306
targetPort: 3306
selector:
io.kompose.service: database
Appliquons le Manifest
docker tag cartopoint:1.0 ghcr.io/cedric-esnault-ign/cartopoint:1.0 docker login ghcr.io -u cedric-esnault-ign docker push ghcr.io/cedric-esnault-ign/cartopoint:1.0
sudo k3s kubectl exec –stdin –tty web-f9c8ff8db-kssc8 – /bin/bash
sudo k3s kubectl port-forward service/web 9999:8080
Kubernetes fourni un outils (dashboard) pour visualiser rapidement le contenu d’un cluster. voici comment l’installer grace à helm, un outil qui permet de packager des applications pour K8S.
Installation helm :
Puis suivre la documentation :
https://kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/