12 KiB
\newpage
Docker
Docker est un outil haut niveau permettant de faire fonctionner facilement les conteneurs.
Composition de Docker
Docker est un daemon lancé au démarrage de votre machine, avec lequel vous interagissez via un client qui se connecte au daemon au moyen d'une socket (le client peut donc être sur une machine distincte du daemon où sont exécutés les conteneurs).
Mon premier conteneur
Afin de tester la bonne marche de votre installation, exécutez la commande :
docker run hello-world
Cette commande va automatiquement exécuter une série de commandes pour vous, comme indiqué dans le message affiché en retour :
D'abord, le démon va rechercher s'il possède localement l'image hello-world. Si ce n'est pas le cas, il va aller la récupérer sur hub.docker.com. Ce site met à votre disposition un grand nombre d'images : des systèmes de base comme Ubuntu, Debian, Centos, etc. jusqu'à des conteneurs prêts à l'emploi : le serveur web nginx, la base de données MySQL, un serveur node.js, etc.
Vous pouvez directement utiliser le client pour rechercher une image sur le
hub, en utilisant la commande search
:
docker search mariadb
Vous pouvez mettre à jour vos images locales ou simplement pré-télécharger des
images depuis le hub en utilisant la commande pull
:
docker pull ubuntu
Pour consulter la liste des images dont vous disposez localement (soit
parce que vous les avez téléchargées, soit parce que vous les avez
créées vous-même), utilisez la commande images
:
docker images
Vous devez constater la présence de deux images « Ubuntu », ayant un TAG
différent. Souvent, il existe plusieurs versions d'une même image. Pour Ubuntu
par exemple, vous avez la possibilité de lancer la version vivid
, trusty
ou
precise
.
Chaque image est identifiable par son Image ID unique, les noms d'images ainsi que leurs tags sont, comme les tags Git, une manière humainement plus simple de faire référence aux identifiants.
Chaque nom d'image possède au moins un tag associé : latest, c'est le tag qui est automatiquement recherché lorsque vous ne le précisez pas en lançant l'image.
Exécuter un programme dans un conteneur
Maintenant que nous avons à notre disposition l'image d'un conteneur Ubuntu, lançons-la !
La commande run
de Docker prend comme derniers arguments le programme à
lancer dans le conteneur ainsi que ses éventuels arguments. Essayons d'afficher
un Hello World :
docker run ubuntu /bin/echo "Hello World"
Dans notre exemple, c'est bien le /bin/echo
présent dans le conteneur qui est
appelé (et non pas le programme /bin/echo
de la machine hôte qui est
transféré dans le conteneur).
Modifier un conteneur
À chaque fois que vous lancez un run
, un nouveau conteneur est créé à partir
de l'image que vous précisez (via un mécanisme de Copy-On-Write, c'est donc
très rapide et ne consomme pas beaucoup d'espace disque). Cela signifie que
lorsque vous exécutez une commande modifiant le contenu d'un conteneur, cela ne
modifie pas l'image de base, mais crée une nouvelle image. Que vous pouvez
ensuite utiliser comme image de base.
Commençons par entrer dans un nouveau conteneur pour modifier l'image :
docker run -it ubuntu /bin/bash
Vous voilà maintenant dans le conteneur ! Il est assez épuré, il n'y a rien de superflu : vous n'avez pas d'éditeur de texte : ni vim, ni emacs, même pas vi !
La première chose à faire est de mettre à jour la liste des paquets :
apt-get update
Il peut arriver que des paquets présents dans l'image officielle ne soient pas à jour. Afin de garder un environnement cohérent, il est recommandé de ne pas utiliser le gestionnaire de paquets pour mettre à jour les paquets, mais plutôt de contacter le mainteneur de l'image pour qu'il la mette à jour.
Installons maintenant un programme :
apt-get install nano
En attendant la fin de l'installation, jetez un œil à la commande dans un autre terminal :
docker ps
Cette commande liste les conteneurs actifs. Notez le Container ID ainsi que le NAMES du conteneur du conteneur actuellement en cours d'installation de nano.
Lorsque l'installation de nano
est terminée, quittez l'image en tapant
exit
.
Sauvegardez votre image modifiée avec la commande commit
pour pouvoir
commencer directement de votre image avec nano
:
docker commit CONTAINER my_nano
En remplaçant CONTAINER
par le nom ou l'identifiant de votre
container. my_nano
est le nom que vous voudrez utiliser à la place
d'ubuntu
:
docker run -it my_nano /bin/bash
Vous constatez cette fois que vous pouvez lancer nano
, alors que vous ne
pouvez toujours pas le faire dans un conteneur issue d'une image ubuntu
!
Dockerfile
Pour construire une image, vous n'êtes pas obligé de passer par une série de commits. Docker dispose d'un mécanisme permettant d'automatiser la construction de nouvelles images. Vous pouvez arriver au même résultat que ce que l'on a réussi à faire précédemment en utilisant le Docker file suivant :
FROM ubuntu:latest
RUN apt-get update
RUN apt-get install -y nano
La syntaxe d'un Dockerfile
est simple, le premier mot de chaque ligne est
l'intitulé d'une instruction (que l'on écrit généralement en majuscule), elle
est suivie de ses arguments.
Dans notre exemple, nous utilisons FROM
qui indique une image de départ à
utiliser ; RUN
est une commande qui sera exécutée dans le conteneur, dans le
but de le construire. Chaque ligne est exécutée indépendamment des autres ;
cela signifie que l'exemple suivant ne fonctionne pas :
COPY db.sql /db.sql
RUN service mysqld start
RUN mysql -u root -p toor virli < /db.sql
Cet exemple ne fonctionne pas car le serveur MySQL est lancé dans le premier RUN, n'est plus lancé au moment du deuxième RUN. Pour avoir le résultat escompté, il faut exécuter les commandes ensemble :
COPY db.sql /db.sql
RUN service mysqld start && mysql -u root -p toor virli < /db.sql
Après le RUN
, MySQL sera de nouveau arrêté, si on veut l'utiliser dans le
conteneur, il ne faudra pas oublier lancer le processus.
Pour lancer la construction de la nouvelle image, créer un nouveau dossier ne
contenant que votre fichier Dockerfile
, placez-vous dedans, puis utilisez la
commande build
:
docker build --tag=my_editor .
Une fois la construction de l'image terminée, vous pouvez la lancer et constater l'existence de notre éditeur favori :
docker run -it my_editor /bin/bash
Consultez https://docs.docker.com/reference/builder/ pour la liste complète des instructions reconnues.
Exposer des ports
Construisons maintenant un conteneur avec un serveur web :
FROM my_editor
RUN apt-get update
RUN apt-get install -y nginx
EXPOSE 80
L'instruction EXPOSE
sera traité plus tard par le client Docker
(équivalent à l'argument --expose
). Il s'agit de préciser les ports
sur lesquels votre image écoute.
En utilisant l'option -P
du run
, vous allez pouvoir assigner une
redirection de port aléatoire sur la machine hôte vers votre
conteneur :
docker build --tag=my_webserver .
docker run -it -P my_webserver /bin/bash
service nginx start
Dans un autre terminal, lancer un docker ps
et consulter la colonne
PORTS pour connaître le port choisit par Docker pour effectuer la
redirection.
Rendez-vous ensuite dans votre navigateur sur http://localhost:49153/.
À vous de jouer : utilisez l'instruction COPY
pour afficher votre
propre index.html
remplaçant celui installé de base par nginx.
Lancement de commande automatique
Vous pouvez placer dans un Dockerfile
une instruction CMD
qui sera
exécutée si aucune commande n'est passée lors du run
, par exemple :
CMD nginx -g "daemon off;"
docker build --tag=my_nginx .
docker run -d -P my_nginx
L'option -d
passée au run
lance le conteneur en tâche de
fond. Si vous constatez via un docker ps
que le conteneur s'arrête
directement, retirer cette option pour voir ce qui ne va pas, ou
utilisez la commande docker logs
.
Volumes
Il est possible de partager des répertoires entre plusieurs
conteneurs. Pour ce faire, il faut déclarer dans le Dockerfile
une
ou plusieurs instructions VOLUME
avec le chemin du répertoire à
considérer comme volume (il est également possible de le faire via
l'option --volume
du client). Ces deux lignes sont équivalentes :
VOLUME /var/log/nginx
docker run -v /var/log/nginx my_nginx
Pour monter les volumes dans un autre conteneur, on utilise l'argument
--volume-from
du client, en indiquant le nom du conteneur avec
lequel on souhaite partager les volumes :
docker run -it --volume-from romantic_archimedes ubuntu /bin/bash
Vous constaterez que le répertoire /var/log/nginx
est partagé entre
romantic_archimedes
et le dernier conteneur lancé.
\newline
Le concept principal de Docker est de concevoir des conteneurs applicatifs : on va préférer assigner un unique rôle à un conteneur (donc géralement on ne va lancer qu'une seule application par conteneur) et concevoir un service complet en créant un groupe de conteneur, partageant des données entre-eux par des volumes.
Une lecture intéressante sur ce sujet est sans doute cet article de Michael Crosby.
Data Volume Container
Dans de nombreuses situation, il est intéressant de séparer les données de l'application, et donc d'avoir un conteneur exécutant l'application et un second stockant les données.
Cela est particulièrement utile dans le cas d'une base de données : on veut pouvoir mettre à jour le conteneur exécutant le serveur, sans pour autant perdre les données.
L'idée derrière le concept de Data Volume Container
est de partager un volume
avec un conteneur dont le seul rôle est de stocker les données.
Il est parfaitement possible de partager un volume avec un conteneur qui n'est plus lancé. En effet, tant que vous n'avez pas demandé explicitement à un conteneur d'être supprimé, il est préservé dans un coin en attendant des jours meilleurs.
Voici comment on pourrait lancer un conteneur exécutant une base de données :
docker run -v /var/lib/mysql --name dbdata busybox
docker run --volume-from dbdata -e MYSQL_ROOT_PASSWORD=mysecretpassword -d mysql
Le premier conteneur, sans commande passée, va s'arrêter dès son lancement. Busybox est l'une des plus petites images possédant tous les outils de base (il est possible d'obtenir un shell en cas de besoin). Il expose un volume qui sera utiliser comme stockage persistant.
Le second conteneur va lancer le serveur MySQL et utiliser le répertoire partagé pour stocker les données.
Lorsqu'il y aura besoin de mettre à jour le conteneur MySQL, les données ne seront pas perdues (et s'il y avait besoin de migrer les données entre les deux versions des conteneurs, un conteneur intermédiaire pourrait parfaitement s'en charger).
Cela facile également les sauvegardes, qui peuvent s'exécuter dans un conteneur distinct, dédié à la tâche de sauvegarde.
Lier les conteneurs
En plus de vouloir partager des répertoires entre deux conteneurs, il est souvent nécessaire de partager des ports.
Pour automatiser le partage d'informations sur les IP et ports exposés, la
commande run
possède l'option --link
qui permet de définir dans les
variables d'environnement du conteneur que l'on va lancer.
Le détail des variables ajoutées dans cette situation est disponible à https://docs.docker.com/userguide/dockerlinks/#environment-variables.
On utiliser généralement cette liaison pour fournir au conteneur hébergeant un site web dynamique l'IP et le port où trouver la base de données :
docker run -e MYSQL_ROOT_PASSWORD=mysecretpassword -d --name db1 mysql
docker run --link db1 my_nginx
Ambasador
Afin d'abstraire le plus possible l'infrastructure sous-jacente et d'autoriser les migrations de conteneurs, on utilise le modèle ambassador.
On lancera systématiquement un conteneur entre deux conteneurs que l'on veut lier : l'ambassadeur. Celui-ci s'occupera de router correctement le trafic. En cas de changement de route (si l'un des conteneurs change de machine hôte par exemple), on a simplement à redémarrer l'ambassadeur plutôt que le conteneur principal.
La documentation officielle pour ce modèle est disponible à https://docs.docker.com/articles/ambassador_pattern_linking/.