I. Information▲
I-A. Historique des modifications▲
- 2017-05-15 : Suppression de docker swarm via des conteneurs - Màj du tuto vers Docker 17.05.0-ce
- 2016-12-05 : Ajout d'explications sur les options de lancement d'un conteneur - Ajout partie plugin pour docker volume (netshare)
- 2016-11-26 : Ajout de katacoda (Merci @xavier)
- 2016-11-25 : Ajout partie plugin pour docker-machine (scaleway)
- 2016-11-22 : Passe sur l'orthographe (Merci flashcode)
- 2016-11-16 : Publication de la version 2 du Tutoriel
- 2016-09-08 : Rédaction d'un chapitre sur docker network - Rédaction d'un chapitre sur docker volume
- 2016-09-06 : Màj du tuto vers Docker 1.12.X - Màj de docker swarm
- 2016-09-02 : Ajout des bonnes pratiques pour la création d'un dockerfile - Refonte du chapitre Limiter les ressources d'un conteneur
- 2016-09-01 : Refonte du chapitre Création d'une image - Ajout d'informations dans la présentation de Docker
- 2016-06-30 : Ajout de l'installation de docker4mac (magicalex)
- 2016-06-24 : Ajout de l'installation de docker4windows
- 2016-05-30 : Amélioration de l'installation sous Mac OS (magicalex)
- 2016-05-25 : Màj du tuto vers Docker 1.11.X - Rédaction d'un chapitre sur docker-machine - Rédaction d'un chapitre sur Swarm
- 2016-03-12 : Mise à jour de l'environnement de test
- 2016-02-19 : Ajout de l'installation sous Mac OS (Merci Magicalex)
- 2016-01-30 : Ajout du paquet (aufs-tools) pour éviter les erreurs dans les logs système du type « Couldn't run auplink before unmount: exec: »auplink« : executable file not found in $PATH »
- 2015-11-05 : Ajout de l'utilisation de btrfs
- 2015-11-01 : Ajout de quelques liens
- 2015-10-29 : Ajout de l'autocomplétion
- 2015-10-17 : Version initiale
I-B. À faire▲
- Ajout d'une partie sur glusterfs (en version Docker)
- Refactor avec les nouveautés
I-C. Contribution▲
Toute contribution est la bienvenue. N'hésitez pas à contribuer aux tutoriels, ajout d'informations, correction de fautes (et il y en a), améliorations, etc. Ça se passe ici
I-D. Questions▲
Toute question sur la discussion ou sur github
II. Introduction▲
Tout le monde a déjà entendu parler de Docker, mais peu ont décidé de passer le cap. Docker est le truc qui monte en ce moment, et il le mérite (avis purement personnel).
J'ai décidé de rédiger ce tutoriel afin de montrer ce qu'on peut en faire.
Pour ce tutoriel, je me base principalement sur mon expérience, sur mon apprentissage, sur les problématiques que j'ai pu rencontrer (que je rencontre encore pour certaines). J'ai essayé de structurer au mieux ce tutoriel, et j'espère que celui-ci vous conviendra.
Pour simplifier, Docker est un outil permettant la création d'un environnement (appelé conteneurs, containers en anglais) afin d'isoler des applications pour ne pas en gêner d'autres. Docker utilise des fonctionnalités natives au noyau Linux, comme les cgroups ou les namespaces, mais offre les outils pour le faire de manière simplifiée.
ATTENTION : Je serais incapable d'évaluer le niveau requis pour ce tutoriel, mais en fonction de votre utilisation, il vous faudra peut-être un peu plus que quelques notions de base en administration GNU/Linux.
II-A. Qu'est-ce que Docker (pour notre ami Wikipédia) ?▲
Docker est un logiciel libre qui automatise le déploiement d'applications dans des conteneurs logiciels. Selon la firme de recherche sur l'industrie 45 Research, « Docker est un outil qui peut empaqueter une application et ses dépendances dans un conteneur virtuel, qui pourra être exécuté sur n'importe quel serveur Linux ». Ceci permet d'étendre la flexibilité et la portabilité d'exécution d'une application, que ce soit sur la machine locale, un cloud privé ou public, une machine nue, etc.
Docker étend le format de conteneur Linux standard, LXC, avec une API de haut niveau fournissant une solution de virtualisation qui exécute les processus de façon isolée. Docker utilise LXC, cgroups, et le noyau Linux lui-même. Contrairement aux machines virtuelles traditionnelles, un conteneur Docker n'inclut pas de système d'exploitation, s'appuyant sur les fonctionnalités du système d'exploitation fournies par l'infrastructure sous-jacente.
La technologie de conteneur de Docker peut être utilisée pour étendre des systèmes distribués de façon à ce qu'ils s'exécutent de manière autonome depuis une seule machine physique ou une seule instance par nœud. Cela permet aux nœuds d'être déployés au fur et à mesure que les ressources sont disponibles, offrant un déploiement transparent et similaire aux PaaS pour des systèmes comme Apache Cassandra, Riak ou d'autres systèmes distribués.
II-B. Conteneurs VS machines virtuelles▲
Il faut savoir que Docker n'a rien inventé, la technologie de conteneurisation existe depuis un moment, notamment avec les jails (prisons) sous BSD, les zones sous Solaris, même Linux a eu son lot, avec openvz, vserver ou plus récemment LXC.
Docker permet de simplifier l'utilisation des outils présents dans le noyau Linux, à savoir les namespaces et les cgroups.
Mais en fait, les conteneurs c'est comme les machines virtuelles ?
Oui, mais NON, la finalité est quasiment la même : isoler nos applications. Mais le fonctionnement est totalement différent.
Pour une machine virtuelle, vous créez comme son nom l'indique, une machine virtuelle, c'est-à-dire, vous lui indiquez la ram à utiliser, le nombre de cpu, et vous créez un disque dur virtuel pour installer un OS. Votre machine dispose de ses propres ressources, et n'a aucunement conscience d'être virtualisée.
Pour les conteneurs c'est différent, on n'installe pas d'OS à proprement parler, mais un rootfs (le / d'un unix/Linux) qui est appelé image, qui contient les bibliothèques et les binaires nécessaires. Le noyau quant à lui, est partagé avec le système hôte. Nous pouvons évidemment limiter les ressources des conteneurs.
Machines virtuelles et Conteneurs ont leurs avantages et bien évidemment leurs inconvénients. Par exemple lancer ou créer un conteneur est vraiment plus rapide que lancer une VM. Mais une VM offre une meilleure isolation. Et ils ne sont pas forcément incompatibles, bien souvent, Docker est simplement utilisé dans une VM pour uniformiser une application entre les différents environnements (prod, préprod, intégration, etc.). Il arrive même de trouver dans une VM un seul conteneur.
Le plus gros défaut des conteneurs, c'est le fait que ce n'est pas cross-platform. On lance des conteneurs Linux sous Linux, des conteneurs BSD sous BSD ou des conteneurs Windows sous Windows.
Voici une image illustrant la différence entre VM et Docker :
II-C. Docker, pour quoi faire ?▲
Docker n'a pas pour vocation de remplacer la virtualisation, voici plusieurs cas d'utilisation possibles.
- Le déploiement : puisque Docker a pour vocation de conteneuriser des applications, il devient simple de créer un conteneur pour notre application, et la dispatcher où bon nous semble. Un conteneur qui fonctionne sur une machine avec une distribution X, fonctionnera sur une autre machine avec une distribution Y.
- Le développement : cela permet de facilement avoir le même environnement de développement qu'en production, si ça marche quelque part, ça marchera partout. Cela permet également de pouvoir sur la même machine, tester avec plusieurs versions d'un même logiciel. Par exemple pour une application PHP, on pourrait facilement tester sur plusieurs versions de PHP, puis plusieurs versions de nginx et d'autres serveurs web.
- Installer des applications : étant donné que Docker propose une multitude d'outils, vous allez voir à quel point il est facile et rapide d'installer une application, bien souvent une seule ligne de commande suffit pour avoir par exemple notre nextcloud fonctionnel.
III. Installation▲
Docker n'est pour l'instant compatible qu'avec GNU/Linux (et BSD en compatibilité Linux). Windows travaille par contre sur le projet, et une version custom de Docker verra le jour pour la prochaine Release Candidate de Windows Server 2016. Cela ne veut pas dire qu'il n'y a aucun moyen de l'utiliser sur Windows ou Mac.
III-A. Installation sous Linux▲
Il existe des paquets tout prêts pour la plupart des distributions. Je vous renvoie vers ces paquets avec les procédures d'installation : https://docs.docker.com/installation/
Nous allons partir sur une debian Jessie (parce que !!!) : on commence par installer les prérequis puis on en profite pour faire une mise à jour :
2.
3.
$
apt-get update
$
apt-get upgrade
$
apt-get install apt-transport-https ca-certificates xz-utils iptables aufs-tools git
Puis on ajoute le dépôt, ainsi que la clé GPG de celui-ci :
2.
$
echo "deb [arch=amd64] https://download.docker.com/linux/debian
$(lsb_release -cs)
edge"
>
/etc/apt/sources.list.d/docker.list
$
curl -fsSL https://download.docker.com/linux/debian/gpg |
sudo apt-key add -
Nous utilisons ici le dépôt edge, qui est le dépôt testing de Docker avec une nouvelle version par mois, il est possible d'utiliser le dépôt stable, qui lui est mis à jour une fois tous les trois mois.
Puis on installe :
2.
$
apt-get update
$
apt-get install docker-ce
Il ne nous reste plus qu'à lancer Docker :
2.
$
systemctl start docker
$
systemctl enable docker
Pour que Docker fonctionne dans les meilleures conditions, il faut ajouter ceci sous Debian dans le /,etc./default/grub :
GRUB_CMDLINE_LINUX
=
"cgroup_enable=memory swapaccount=1"
Ceci permet de limiter la RAM utilisable par un conteneur.
Puis on régénère notre grub :
2.
3.
4.
5.
$
grub-mkconfig -o /boot/grub/grub.cfg
Création du fichier de configuration GRUB&
#8230;
Image Linux trouvée : /boot/vmlinuz-3
.16
.0
-4
-amd64
Image mémoire initiale trouvée : /boot/initrd.img-3
.16
.0
-4
-amd64
fait
On reboot, et c'est good.
On va tester avec une image de test :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
$
docker run hello-world
Unable to find image 'hello-world:latest'
locally
latest: Pulling from library/hello-world
03f4658f8b78: Pull complete
a3ed95caeb02: Pull complete
Digest: sha256:8be990ef2aeb16dbcb9271ddfe2610fa6658d13f6dfb8bc72074cc1ca36966a7
Status: Downloaded newer image for
hello-world:latest
Hello from Docker.
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1
. The Docker client contacted the Docker daemon.
2
. The Docker daemon pulled the "hello-world"
image from the Docker Hub.
3
. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4
. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$
docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker Hub account:
https://hub.docker.com
For
more examples and ideas, visit:
https://docs.docker.com/userguide/
Si quelque chose du genre s'affiche, c'est bon, Docker est fonctionnel.
III-B. Installation sous Windows▲
Sous Windows il existe trois manières de l'installer :
- À la main => Une VM docker, avec le client docker sur Windows
- Docker-toolbox => Exécutable qui installe tout, virtualbox, une VM, et les clients
- Docker4Windows => Comme docker-toolbox, mais en mieux, utilise hyper-v au lieu de virtualbox (encore en version bêta, seulement compatible à partir de Windows 10 Pro).
Nous ne verrons ici qu'avec docker-toolbox, ainsi que docker4windows. L'installation manuelle étant la même chose que sous Linux.
III-B-1. Docker4Windows▲
Docker4Windows est encore en version bêta, mais reste totalement utilisable au quotidien. Pour le moment il n'est compatible qu'avec Windows 10 (version pro, Enterprise et Education), donc si vous êtes sur une autre version de Windows, il faudra passer par docker-toolbox ou faire une installation manuelle.
Avant de commencer l'installation de docker4windows, nous devons activer hyper-v. Pour ce faire, clic droit sur le menu d'application -> panneau de configuration -> Programmes et fonctionnalités -> Activer ou désactiver des fonctionnalités Windows -> Cocher Hyper-V -> OK.
On redémarre le PC, et normalement c'est bon, nous avons Hyper-V.
On peut passer à l'installation de docker4windows, et là c'est vraiment simple, télécharger l'exécutable ici, ensuite c'est du next-next.
Vous aurez normalement dans la zone des notifications, une petite baleine, cela veut dire que Docker est bien installé.
III-B-2. Docker-toolbox▲
L'installation de docker-toolbox est rapide, il suffit de télécharger docker-toolbox, ensuite c'est du next-next. Ceci vous installera toute la panoplie du super-docker, c'est-à-dire, virtualbox avec une VM boot2docker, le client docker, docker-machine et docker-compose pour Windows. Puis également, si vous le souhaitez, kitematic, qui est un GUI pour installer des applications via docker.
III-C. Installation sous Mac▲
Il y a deux solutions pour installer docker sur Mac OS X :
- Docker4Mac ;
- Docker-toolbox.
III-C-1. Docker4Mac▲
Docker4Mac est encore en version bêta, mais reste totalement utilisable au quotidien.
L'installation de docker4Mac est vraiment simple, téléchargez l'exécutable ici et ensuite c'est du next-next.
III-C-2. Docker-toolbox▲
Il faut télécharger l'installateur « docker toolbox » ici : https://www.docker.com/products/docker-toolbox
Ensuite vous exécutez le pkg et installez docker comme indiqué ici : https://docs.docker.com/engine/installation/mac/
Ensuite il faut créer une VM docker, ça va créer une vm dans virtualbox qui aura pour nom docker (vous pourrez vérifier dans virtualbox)
$
docker-machine create --driver virtualbox docker
Pour connecter notre shell à chaque fois avec la vm docker
$
echo 'eval "$(docker-machine env docker)"'
>>
~/.bash_profile
N. B. Il se peut que le partage et la synchronisation de volumes soient lents, voire très lents (suite à un ralentissement du système de fichier NFS natif à OS X). Dans ce cas, la solution « Dinghy » peut vous aider à résoudre ces problèmes. https://github.com/codekitchen/dinghy
Nous avons ici utilisé docker-machine pour créer notre machine, nous verrons dans un autre chapitre comment utiliser cet outil.
Et pour finir on teste si ça fonctionne :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
$
docker run hello-world
Unable to find image 'hello-world:latest'
locally
latest: Pulling from library/hello-world
03f4658f8b78: Pull complete
a3ed95caeb02: Pull complete
Digest: sha256:8be990ef2aeb16dbcb9271ddfe2610fa6658d13f6dfb8bc72074cc1ca36966a7
Status: Downloaded newer image for
hello-world:latest
Hello from Docker.
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1
. The Docker client contacted the Docker daemon.
2
. The Docker daemon pulled the "hello-world"
image from the Docker Hub.
3
. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4
. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$
docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker Hub account:
https://hub.docker.com
For
more examples and ideas, visit:
https://docs.docker.com/userguide/
III-D. Mon environnement de test▲
Je suis sous archlinux, et utilise docker directement sur ma machine.
Version de docker :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
$
docker version
Client:
Version: 17
.05
.0
-ce
API version: 1
.29
Go version: go1.8
.1
Git commit: 89658bed64
Built: Fri May 5
22
:40
:58
2017
OS/Arch: linux/amd64
Server:
Version: 17
.05
.0
-ce
API version: 1
.29
(
minimum version 1
.12
)
Go version: go1.8
.1
Git commit: 89658bed64
Built: Fri May 5
22
:40
:58
2017
OS/Arch: linux/amd64
Experimental: false
IV. Le docker Hub▲
IV-A. Qu'est-ce que le docker Hub ?▲
Le docker Hub est un store ou les utilisateurs de docker peuvent partager leurs images. Les images de base ont été créées par l'équipe de docker. Il est accessible ici : https://hub.docker.com/explore/
Ceci fait partie des forces de docker, beaucoup d'images sont disponibles (peut-être même trop), allant d'une simple debian, à une debian préconfigurée pour nextcloud par exemple. C'est justement cette méthode que j'appelle la méthode feignasse ^^. Je veux nextcloud, je télécharge l'image et je crée un conteneur, vu que j'ai une bonne connexion, en moins d'une minute, j'ai un nextcloud fonctionnel, elle est pas belle la vie ?!
Aucun compte n'est nécessaire pour télécharger une image, mais bien évidemment pour pouvoir envoyer vos images, il faut un compte.
IV-B. Chercher une image▲
Il existe deux méthodes pour chercher sur le Hub, la première par le site web. Gardons mon exemple, je veux un nextcloud. Sur le site je cherche donc nextcloud, et j'obtiens plusieurs résultats :
Nous avons donc plusieurs résultats, avec plusieurs informations :
- Le nom de l'image => Généralement sous la forme USER/IMAGE_NAME, sauf dans le cas d'une image officielle, où c'est seulement IMAGE_NAME
- Le nombre de stars => Le système de notation
- Le nombre de pulls => Le nombre de téléchargements
On en choisit l'officielle. Nous allons avoir plusieurs informations :
L'onglet Repo Info est divisé en trois parties. La première est une description brève de l'image.
Sur la droite, nous avons la commande qui permet de la télécharger :
docker pull nextcloud
Puis dans le corps, plusieurs informations sur l'image, les versions des applications par exemple, puis souvent, les commandes/variables pour lancer un conteneur avec cette image, par exemple ceci :
docker run -d nextcloud
Ceci lance le conteneur, mais nous verrons ceci tout à l'heure.
Dans l'onglet Tags, ce sont les numéros de tags disponibles, souvent apparentés au numéro de version de l'application.
Nous avons parfois deux autres onglets, Dockerfile et Build Details. Ces onglets apparaissent quand les images sont « autobuildées » par le dockerhub, et donc il s'agit de la « recette » de l'image, c'est le fichier qui a permis de la construire. Nous verrons ceci plus loin dans le tutoriel. Autrement nous avons Build Details, qui permet de voir quand et comment se sont passées les constructions de l'image.
Autre possibilité pour trouver une image, en ligne de commande avec docker :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
$
docker search nextcloud
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
wonderfall/nextcloud Nextcloud - a safe home for
all your data.... 187
[OK]
nextcloud A safe home for
all your data 88
[OK]
greyltc/nextcloud Nextcloud: a safe home for
all your data. ... 31
[OK]
rootlogin/nextcloud Nextcloud docker image running on top of N... 9
[OK]
indiehosters/nextcloud Docker image for
Nextcloud application. 9
[OK]
sameersbn/nextcloud Dockerized Nextcloud 3
[OK]
freenas/nextcloud Access &
share your files, calendars, cont... 3
[OK]
aheimsbakk/nextcloud Nextcloud - a safe home for
all your data.... 2
[OK]
skybosh/nextcloud A simple Nextcloud image. 2
[OK]
cyphar/nextcloud NextCloud is a fork of OwnCloud. This is a... 1
[OK]
whatever4711/nextcloud Image for
Nextcloud with php-fpm 0
[OK]
bennibu/nextcloud nextcloud on php 7
0
[OK]
jefferyb/nextcloud Docker Image packaging for
Nextcloud - A s... 0
[OK]
mkuron/nextcloud Nextcloud with PHP IMAP extension 0
[OK]
dklein/nextcloud Added cron to the official nextcloud Image 0
[OK]
gorlug/nextcloud https://nextcloud.com/ 0
[OK]
icewind1991/nextcloud-dev Docker image for
NextCloud for
development 0
[OK]
aknaebel/nextcloud This docker image provide a nextcloud serv... 0
[OK]
whatwedo/nextcloud Nextcloud powered by Apache 0
[OK]
vger/nextcloud Nextcloud image based on Debian Jessie. 0
[OK]
martmaiste/nextcloud Nextcloud Docker using php-fpm and Nginx r... 0
[OK]
georgehrke/nextcloud Dockerized Nextcloud 0
[OK]
asannou/nextcloud A safe home for
all your data 0
[OK]
ianusit/nextcloud Nextcloud 0
[OK]
servercontainers/nextcloud nextcloud container on debian jessie with ... 0
[OK]
Nous avons par contre ici beaucoup moins d'informations, personnellement je n'utilise cette méthode que pour rechercher des images de base (debian, centos, fedora, etc.).
V. Gérer les images▲
Dans cette partie, nous allons voir comment gérer nos images, c'est-à-dire les télécharger, les lister, et bien sûr les supprimer.
Nous utiliserons ici docker image [subcommand] :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
$
docker image --help
Usage: docker image COMMAND
Manage images
Options:
--help Print usage
Commands:
build Build an image from a Dockerfile
history Show the history of an image
import Import the contents from a tarball to create a filesystem image
inspect Display detailed information on one or more images
load Load an image from a tar archive or STDIN
ls List images
prune Remove unused images
pull Pull an image or a repository from a registry
push Push an image or a repository to a registry
rm Remove one or more images
save Save one or more images to a tar archive (
streamed to STDOUT by default)
tag Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE
Run 'docker image COMMAND --help'
for
more information on a command.
V-A. Télécharger des images▲
Pour télécharger une image, on utilise cette commande :
$
docker image pull [nom image]:[tag]
Ce qui donne pour télécharger notre owncloud :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
$
docker image pull nextcloud
Using default tag: latest
latest: Pulling from library/nextcloud
10a267c67f42: Pull complete
370377701f89: Pull complete
455c73a122bc: Pull complete
fb71bac61c47: Pull complete
288a1d91ad4e: Pull complete
86e0256ba4b0: Pull complete
f14fbe7a9dfb: Pull complete
0f36dd91c0ab: Pull complete
c2e4a1f87acc: Pull complete
ca5541ee478f: Pull complete
afb657ecb370: Pull complete
8769771ac5f4: Pull complete
b08c0f680a7a: Pull complete
7248dd69b572: Pull complete
694f7bad4667: Pull complete
7a4238c1b120: Pull complete
727952036373
: Pull complete
b3fd60530d47: Pull complete
1b9a58bad45d: Pull complete
dd111b8b6245: Pull complete
c9ba8440391e: Pull complete
Digest: sha256:dcdd3f4feeacedfb936b802a5c05885db3abcbb909315aed162c2d8938f4ab29
Status: Downloaded newer image for
nextcloud:latest
Si on ne met pas de tag, il télécharge automatiquement la latest. Comme nous avons vu dans la partie sur le dockerhub, nextcloud possède plusieurs tags. En spécifiant un tag, par exemple 10.0 ça donnerait :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
$
docker image pull nextcloud:10
.0
10
.0
: Pulling from library/nextcloud
10a267c67f42: Already exists
370377701f89: Already exists
455c73a122bc: Already exists
fb71bac61c47: Already exists
288a1d91ad4e: Already exists
86e0256ba4b0: Already exists
f14fbe7a9dfb: Already exists
0f36dd91c0ab: Already exists
97d77fc1fd2c: Pull complete
d8c0a0fd7605: Pull complete
79601c211937: Pull complete
48bba15d4000: Pull complete
96a57b907249: Pull complete
fee2a4169fe8: Pull complete
aee16e9f06fe: Pull complete
660947d2c7c3: Pull complete
53dca82293cb: Pull complete
45482c69893c: Pull complete
70e2079f73d1: Pull complete
4a2509f39f50: Pull complete
54531e3804ca: Pull complete
Digest: sha256:b08e617ea87d39a9498ecd6ba2635017b8f1c661b057dae092df57c0c0eab968
Status: Downloaded newer image for
nextcloud:10
.0
Nous pouvons voir qu'il avait déjà des éléments, en fait une image est souvent basée sur une autre image, qui peut être basée sur une autre et ainsi de suite. Ce sont des layers (couches). Vous comprendrez mieux ceci lorsque nous apprendrons à créer des images. Chaque couche possède un id unique, c'est ce qui permet de savoir s'il est déjà présent ou non. Sur certaines images, comme les officielles, plusieurs tags peuvent être associés à une même image pour une même version, par exemple on peut voir sur le hub, que latest correspond également à 11.0.3-apache, 11.0-apache, 11-apache, apache, 11.0.3, 11.0, et 11.
Donc si maintenant je télécharge la version 11.0.3, puisqu'il a déjà toutes les couches, il ne devrait pas les retélécharger :
2.
3.
4.
$
docker image pull nextcloud:11
.0
.3
11
.0
.3
: Pulling from library/nextcloud
Digest: sha256:dcdd3f4feeacedfb936b802a5c05885db3abcbb909315aed162c2d8938f4ab29
Status: Downloaded newer image for
nextcloud:11
.0
.3
Donc effectivement, tout était déjà présent, donc il n'a rien téléchargé.
Pourquoi dans ce cas, mettre plusieurs tags ?
En fait c'est tout bête, prenons par exemple, si je veux rester dans la branche 11.X.X de nextcloud, il me suffit d'utiliser le tag 11, qui correspondra toujours à la dernière version 11.X.X, sans se soucier du nouveau numéro de version.
V-B. Lister les images▲
Pour lister les images téléchargées, donc disponibles en local, nous utiliserons cette commande :
2.
$
docker image ls
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
- REPOSITORY : Le nom de l'image
- TAG : Version de l'image
- IMAGE ID : Identifiant unique de l'image
- CREATED : Date de création de l'image
- VIRTUAL SIZE : Taille de l'image + toutes ses images dépendantes
Ce qui donne avec ce que l'on a téléchargé :
2.
3.
4.
5.
$
docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
nextcloud 11
.0
.3
05b0dd17351b 2
days ago 627MB
nextcloud latest 05b0dd17351b 2
days ago 627MB
nextcloud 10
.0
65f984a7a402 2
days ago 596MB
Nous voyons nos trois images. Comme nous pouvons le voir, nextcloud:11.0.3 et nextcloud:latest ont le même ID, mais rassurez-vous, ce sont juste des alias, elles ne prennent pas toutes les deux 627 MB d'espace disque.
Petite astuce pour ne pas afficher les doublons :
2.
3.
4.
$
docker image ls |
uniq -f 3
REPOSITORY TAG IMAGE ID CREATED SIZE
owncloud 8
.2
.5
52f7d60d34bd 13
days ago 699
.2
MB
owncloud 9
.0
.2
4e0dc7be3d39 3
weeks ago 698
.5
MB
Vous pouvez également afficher seulement l'image (ou les images) voulue :
2.
3.
4.
5.
$
docker images owncloud
REPOSITORY TAG IMAGE ID CREATED SIZE
owncloud 8
.2
.5
52f7d60d34bd 13
days ago 699
.2
MB
owncloud 9
.0
.2
4e0dc7be3d39 3
weeks ago 698
.5
MB
owncloud latest 4e0dc7be3d39 3
weeks ago 698
.5
MB
Ou si vous ne vous rappelez plus du nom complet, on peut jouer un peu avec les regex :
2.
3.
$
docker images */*cloud
REPOSITORY TAG IMAGE ID CREATED SIZE
wonderfall/owncloud latest 233e6e0c61de 3
days ago 201
MB
V-C. Supprimer les images▲
Pour supprimer une image, c'est plutôt simple :
$
docker image rm [nom Image ou ID image]:[tag]
Voici un exemple :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
$
docker image rm nextcloud:10
.0
Untagged: nextcloud:10
.0
Untagged: nextcloud@sha256:b08e617ea87d39a9498ecd6ba2635017b8f1c661b057dae092df57c0c0eab968
Deleted: sha256:65f984a7a402e076e8c859a6198d295a1ce587e7583c4665770d6499b5bf7f2d
Deleted: sha256:e90e466e6f2dc093e5975c8550eb1981a9ebb635cc47b0bd81aeac1b5aca448f
Deleted: sha256:a2fcda4803fdb8e2bef7064c3294b73c212778179b74b28600b9153933aa226a
Deleted: sha256:7a0e4dbef3ff7ff0edaa72de731f0919f287949a589156c46822aa30905079ce
Deleted: sha256:f23609f40dfb2fa2b9b2c998df6d0321aa6977dd23c20c6459b3cb067f78e020
Deleted: sha256:071048d8d0169cfaccf1f413da432818c20bc044664ade97e6ab8a639b611856
Deleted: sha256:40aab88ad21c9abf2932cc8a6ac14f42e159bde78b090989c49bd6479fbc2058
Deleted: sha256:dba4449ae481bb25cb8c58fe01724cd9d084fa3b60d5473c264dabe972fa8bbf
Deleted: sha256:389511eec4eafd25dacbcfa8164ea21411ca1d133ad20819875f85d4014cff1e
Deleted: sha256:2ad68ca5b887a6492d212f9b41a659aeb7f35de3c2352afe062254c5c9ea87af
Deleted: sha256:616fa657caf50c79f1dba57ab3030dafbcc377d8744ccdcee62b5f9afeed7cb2
Deleted: sha256:7cb5088120bb5f77d16b5451cf52ad157e506de215b031575689d41a008e8974
Deleted: sha256:29c7a1e9b9e530cbad0ea17b3b23c47b8186939363c04396efb948aeba99cfff
Deleted: sha256:2155b268258a477900f1eee8624d238c637db24b235f547e876b2696622d8137
On vérifie :
2.
3.
4.
$
docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
nextcloud 11
.0
.3
05b0dd17351b 2
days ago 627MB
nextcloud latest 05b0dd17351b 2
days ago 627MB
Par contre si on supprime une image que l'on possède avec plusieurs tags, il ne supprime pas l'image, mais l'alias :
2.
$
docker image rm nextcloud:11
.0
.3
Untagged: nextcloud:11
.0
.3
On peut vérifier :
2.
3.
$
docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
nextcloud latest 05b0dd17351b 2
days ago 627MB
Nous pouvons bien sûr supprimer plusieurs images :
$
docker image rm [image] [image] [image]
Petit bonus pour supprimer toutes les images (oui cela peut être utile) :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
$
docker image prune -a
WARNING!
This will remove all images without at least one container associated to them.
Are you sure you want to continue
? [y/N] y
Deleted Images:
untagged: nextcloud:latest
untagged: nextcloud@sha256:dcdd3f4feeacedfb936b802a5c05885db3abcbb909315aed162c2d8938f4ab29
deleted: sha256:05b0dd17351b75f9b818c0f8075e2d2b9f2ef25873422e2f52c20b12cf5c10bf
deleted: sha256:83e371d71cfbc5ac783026006959b8a38f582947ac7fc9d54f871de3df4be9f6
deleted: sha256:04be7a548f626aa9ab5e17a492194b51ff61df8c0545eb89f8fc35f1b073e70f
deleted: sha256:da435d4b91262cfe06ae635135399124e63afa4bd87ec4e1a161feba38a05036
deleted: sha256:82f6d723072ee7aad7cb4e95e6bc37d8760e055122487bfb32d92182551bc5f7
deleted: sha256:58603b6a763e5604d0fa3a858a891c7f17a23c5a10dafe16160e14b70f05f269
deleted: sha256:14b5b1d7e7f962b42dcbcb0413897bdb7d38584ad3e21f74ec8137306eee14db
deleted: sha256:176076b65ddaddff95ef2c19d65478746b61dd26973933fbe18c8d058ef06eb3
deleted: sha256:23befc96c53800701fa02c5f7e989c6122256143edcc2c84a8275b01e57f9dba
deleted: sha256:73585d624d5a4a0885e4c6de8463a6642dfb0fbef1b5847a300e0097bdbac064
deleted: sha256:9716cef6be433295bfb3cd6ab7a9bd0500555bfdbaedf953c4637e18c8a59ba7
deleted: sha256:9b03fea1b03a4c74a105dcef8d0df1e89b73124a7f4190a6703d615bff41dd6b
deleted: sha256:30963d11485e5a28117e91075497b16386a4d0b30b3ff263b57d19fb3388f3df
deleted: sha256:631b87d9cd40ecee449ddb56fb13c233e2ca779b0f6bd5ff3d857bdc6be82464
deleted: sha256:083157adf43b6c275b1612c03a0274f1ba1cde60f0a2d8139ae88f06d3846617
deleted: sha256:51ce5c8f7ebd5571e95a06f14049b0c7cd7de4bdf9ba6ceaec476b3586c99775
deleted: sha256:708b30f6a3f3cea053d1195467bd9ce08cca95581b4e7f55bc94d8516501aa7f
deleted: sha256:63d4aa293876898c55c282643fd737bb738f3afe8b1aa287c6fe12ad17411e5a
deleted: sha256:a5260b90ccbb5b6c6916ebadaf68fad4ccd98ac067a59440461592df8d3b719b
deleted: sha256:ad9f11b3ac807160c9963732f05e74e3fe2c173b4d88ff7324b315f2452e5c3b
deleted: sha256:53a165900cc96169bbfe9ab3d41511f180b6f6136c45ee9697cd03958d670445
deleted: sha256:8d4d1ab5ff74fc361fb74212fff3b6dc1e6c16d1e1f0e8b44f9a9112b00b564f
Total reclaimed space: 626
.5MB
V-D. Conclusion▲
Même si ce chapitre n'apporte pas grand-chose, il est tout de même utile (voire indispensable) de savoir gérer ces images, ne serait-ce que pour un souci d'espace disque. Comme vous avez pu le voir, il est vraiment simple de gérer ces images, et je vous rassure, docker est simple en règle générale.
VI. Gérer les conteneurs▲
Dans cette partie, nous verrons comment créer un conteneur (enfin on y vient !!! ^^), mais aussi comment le supprimer, comment les gérer, les relancer et plein d'autres choses indispensables.
Tout d'abord, un (tout petit) peu de théorie. Comme je l'ai dit tout au début, un conteneur ne peut se lancer que s'il a une commande à exécuter. Les images applicatives ont généralement une commande par défaut. Une fois cette commande terminée, le conteneur s'arrête. En fait ce principe est le même qu'une machine lambda, qui exécute un système d'init (systemd par exemple), et si celui-ci se termine, la machine s'arrête. En fait c'est là, la grosse différence avec une VM, même s'il est possible de le faire, un conteneur docker exécute seulement sa commande, pas besoin d'init pour gérer les points de montage, le réseau, le matériel, etc. seulement une commande.
VI-A. Lancer, arrêter et lister des conteneurs▲
La première commande que nous utiliserons, sera docker container run qui s'utilise comme ceci :
$
docker container run [OPTIONS] IMAGE [COMMANDE]
Nous allons commencer par un petit conteneur, basé sur debian (pourquoi pas), et nous lui dirons d'afficher « bonjour mondedie !!! » :
2.
3.
4.
5.
6.
7.
8.
$
docker container run debian echo "bonjour mondedie"
Unable to find image 'debian:latest'
locally
latest: Pulling from library/debian
10a267c67f42: Pull complete
Digest: sha256:476959f29a17423a24a17716e058352ff6fbf13d8389e4a561c8ccc758245937
Status: Downloaded newer image for
debian:latest
bonjour mondedie
Euh ?! il s'est passé quoi là ? Nous avons créé et exécuté notre conteneur, mais puisqu'il n'a pas trouvé l'image debian en local, il l'a téléchargée de lui même (sans avoir à utiliser docker image pull), pratique hein ?! Ensuite il a exécuté la commande qu'on lui a passée, à savoir écrire « bonjour mondedie ». Et c'est tout, puisque l'echo est terminé, il a éteint le conteneur.
Nous allons maintenant vérifier mes dires, nous allons vérifier si ce conteneur est démarré ou pas, pour ce faire nous utiliserons docker container ls :
2.
$
docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Nous n'avons aucun conteneur en cours.
Mais il doit bien être quelque part ce conteneur !! non ?!
Oui et nous pouvons bien évidemment le voir, il suffit d'ajouter l'option -a, qui permet de voir tous les conteneurs :
2.
3.
$
docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ce198d73aafc debian "echo 'bonjour mon..."
4
minutes ago Exited (
0
) 4
minutes ago pedantic_snyder
Le voici, petite explication de ce tableau.
- CONTAINER ID : ID du conteneur, généré de manière à ce qu'il soit unique.
- IMAGE : l'image utilisée pour ce conteneur.
- COMMAND : la commande exécutée.
- CREATED : temps depuis création du conteneur.
- STATUS : le statut actuel du conteneur, ici exited avec un code retour 0 (sans erreur) depuis 8 minutes.
- PORTS : liste des ports écoutés (nous verrons ceci plus tard).
- NAMES : nom du conteneur, ici c'est un nom aléatoire, car nous n'en avons pas défini à notre conteneur.
Relançons notre conteneur plusieurs fois, avec une boucle et un time :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
$
time sh -c 'i=1; while [ $i -le 20 ]; do docker container run debian echo "bonjour mondedie $i"; i=$(($i+1)); done'
bonjour mondedie 1
!!!
bonjour mondedie 2
!!!
bonjour mondedie 3
!!!
bonjour mondedie 4
!!!
bonjour mondedie 5
!!!
bonjour mondedie 6
!!!
bonjour mondedie 7
!!!
bonjour mondedie 8
!!!
bonjour mondedie 9
!!!
bonjour mondedie 10
!!!
bonjour mondedie 11
!!!
bonjour mondedie 12
!!!
bonjour mondedie 13
!!!
bonjour mondedie 14
!!!
bonjour mondedie 15
!!!
bonjour mondedie 16
!!!
bonjour mondedie 17
!!!
bonjour mondedie 18
!!!
bonjour mondedie 19
!!!
bonjour mondedie 20
!!!
real 0m 3
.53s
user 0m 0
.00s
sys 0m 0
.00s
Déjà on voit que c'est plus rapide, puisque l'image est en local, plus besoin de la télécharger, moins de 4 secondes pour 20 lancements.
Vérifions son état :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
$
docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8464c7bb5c96 debian "echo 'bonjour mon..."
About a minute ago Exited (
0
) About a minute ago jolly_swartz
e8d80d7dc23a debian "echo 'bonjour mon..."
About a minute ago Exited (
0
) About a minute ago reverent_galileo
56199baee7f9 debian "echo 'bonjour mon..."
About a minute ago Exited (
0
) About a minute ago optimistic_lichterman
64ae77f60124 debian "echo 'bonjour mon..."
About a minute ago Exited (
0
) About a minute ago condescending_mayer
379c6fcfb53f debian "echo 'bonjour mon..."
About a minute ago Exited (
0
) About a minute ago confident_roentgen
94cdbdf938cb debian "echo 'bonjour mon..."
About a minute ago Exited (
0
) About a minute ago sad_easley
938300e4e31f debian "echo 'bonjour mon..."
About a minute ago Exited (
0
) About a minute ago agitated_mcclintock
933b9542fd06 debian "echo 'bonjour mon..."
About a minute ago Exited (
0
) About a minute ago goofy_colden
8e674629468f debian "echo 'bonjour mon..."
About a minute ago Exited (
0
) About a minute ago eloquent_meninsky
2ecbda8dd3b4 debian "echo 'bonjour mon..."
About a minute ago Exited (
0
) About a minute ago pedantic_lamport
3d1b01dfe606 debian "echo 'bonjour mon..."
About a minute ago Exited (
0
) About a minute ago vigorous_sinoussi
d69d98bf2aa7 debian "echo 'bonjour mon..."
About a minute ago Exited (
0
) About a minute ago heuristic_raman
da2eb71d7eb5 debian "echo 'bonjour mon..."
About a minute ago Exited (
0
) About a minute ago happy_elion
27688ff57cc0 debian "echo 'bonjour mon..."
About a minute ago Exited (
0
) About a minute ago wonderful_tesla
64c31a7323b8 debian "echo 'bonjour mon..."
About a minute ago Exited (
0
) About a minute ago laughing_euler
d1f9f173ef1d debian "echo 'bonjour mon..."
About a minute ago Exited (
0
) About a minute ago naughty_jepsen
0387ccf1092a debian "echo 'bonjour mon..."
About a minute ago Exited (
0
) About a minute ago mystifying_khorana
eaf59f436d28 debian "echo 'bonjour mon..."
About a minute ago Exited (
0
) About a minute ago hardcore_murdock
aac609b95f89 debian "echo 'bonjour mon..."
About a minute ago Exited (
0
) About a minute ago infallible_nobel
6a6be6f146eb debian "echo 'bonjour mon..."
About a minute ago Exited (
0
) About a minute ago tender_minsky
ce198d73aafc debian "echo 'bonjour mon..."
6
minutes ago Exited (
0
) 6
minutes ago pedantic_snyder
Oula, c'est quoi tout ça ?!
En fait nous n'avons pas relancé notre conteneur, mais nous en avons créé d'autres. Cela vous montre la rapidité de création d'un conteneur.
Mais comment le relancer ?
Nous utiliserons docker container start :
2.
$
docker container start 8464c7bb5c96
8464c7bb5c96
Euh oui, mais là, ça n'a pas marché ?
En fait si, mais par défaut, il relance en arrière-plan, donc on ne voit rien s'afficher, mais on peut vérifier :
2.
$
docker container ls -a |
grep 8464c7bb5c96
8464c7bb5c96 debian "echo 'bonjour monded"
6
minutes ago Exited (
0
) 2
seconds ago jolly_swartz
Donc là, on voit qu'il a été créé il y a 6 minutes, mais qu'il s'est terminé il y 2 secondes, donc il vient de tourner.
Nous pouvons par contre le relancer en avant-plan, avec l'option -a :
2.
$
docker container start -a 8464c7bb5c96
bonjour mondedie 20
Là on voit la commande.
Nous allons maintenant voir comment arrêter un conteneur, rien de bien méchant, pour ce faire, je vais créer un conteneur qui fait un ping de google.fr en arrière-plan, comme ceci :
2.
$
docker container run -d debian ping google.fr
03b1d375ac58955f439867cfe84d5635064e357d814a7e1977ee536f42fe7616
Nous pouvons vérifier que le conteneur tourne :
2.
3.
$
docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
03b1d375ac58 debian "ping google.fr"
16
seconds ago Up 16
seconds adoring_thompson
Comme on peut le voir, il est démarré depuis 16 secondes.
Nous allons d'abord le redémarrer puis directement afficher son statut, pour cela nous utiliserons docker container restart :
2.
3.
4.
$
docker container restart 03b1d375ac58 &&
docker container ls
03b1d375ac58
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
03b1d375ac58 debian "ping google.fr"
About a minute ago Up Less than a second adoring_thompson
On voit bien qu'il a redémarré.
Maintenant on peut l'arrêter, parce qu'un conteneur qui fait une boucle qui ne sert à rien, bah ça sert à rien, pour cela nous utiliserons docker container stop :
2.
3.
$
docker container stop 03b1d375ac58 &&
docker container ls
03b1d375ac58
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Voilà il est bien éteint. Il arrive parfois qu'un conteneur rencontre des difficultés à s'arrêter, vous pouvez utiliser docker container kill qui permet d'ajouter le signal d'arrêt.
Je ne l'ai pas encore précisé, mais toutes les actions effectuées sur les conteneurs peuvent l'être avec l'ID (complet ou les premiers caractères uniques), ou avec le nom du conteneur, autogénéré ou non.
VI-B. Voir les logs des conteneurs▲
Tout sysadmin/devs doit penser, et même rêver des logs, c'est indispensable. Avec docker c'est assez spécial, les logs d'un conteneur sont en fait ce qui est en output (stdin et stderr) du shell.
C'est plutôt simple, même très simple, nous utiliserons docker container logs :
$
docker container logs [conteneur]
Exemple :
2.
3.
4.
5.
6.
7.
8.
9.
10.
$
docker container logs 03b1d375ac58
PING google.fr (
216
.58
.213
.131
): 56
data bytes
64
bytes from 216
.58
.213
.131
: icmp_seq
=
0
ttl
=
54
time
=
23
.563
ms
64
bytes from 216
.58
.213
.131
: icmp_seq
=
1
ttl
=
54
time
=
23
.317
ms
64
bytes from 216
.58
.213
.131
: icmp_seq
=
2
ttl
=
54
time
=
22
.317
ms
64
bytes from 216
.58
.213
.131
: icmp_seq
=
3
ttl
=
54
time
=
22
.888
ms
64
bytes from 216
.58
.213
.131
: icmp_seq
=
4
ttl
=
54
time
=
22
.600
ms
64
bytes from 216
.58
.213
.131
: icmp_seq
=
5
ttl
=
54
time
=
22
.595
ms
64
bytes from 216
.58
.213
.131
: icmp_seq
=
6
ttl
=
54
time
=
23
.009
ms
64
bytes from 216
.58
.213
.131
: icmp_seq
=
7
ttl
=
54
time
=
22
.187
ms
Il est possible de faire comme tail :
2.
$
docker container logs --tail
=
20
03b1d375ac58 # Affiche les 20 dernières lignes
$
docker container logs -f 03b1d375ac58 # Affiche les logs au fur et à mesure
Et en fait c'est tout, c'est très simple.
VI-C. Supprimer les conteneurs▲
Maintenant que nous avons vu comment créer, lister, démarrer, redémarrer et arrêter un conteneur, il ne nous reste plus qu'à… les supprimer. Pour cela, nous allons utiliser la commande :
$
docker container rm [conteneur]
Ce qui donnerait pour notre conteneur :
2.
$
docker container rm 0387ccf1092a
0387ccf1092a
Vous pouvez également supprimer un conteneur qui tourne, avec l'option -f.
Pas grand-chose d'autre à dire sur la suppression, à part comment supprimer tous les conteneurs arrêtés :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
$
docker container prune
WARNING!
This will remove all stopped containers.
Are you sure you want to continue
? [y/N] y
Deleted Containers:
8464c7bb5c966a3f970dc913698635265aa01f97cceedb454e256c25933d7015
e8d80d7dc23ae15f58571acc98d99859580b29ac4e5c131854008f38f2ee9456
56199baee7f9f53be84bbea7d29f2a19bc4cacefa2c8c025ba463fb5c84d0c6e
64ae77f60124af6b9f07d3f1be6849508b8f0401bd79494adf488a3c0bc88853
379c6fcfb53fa41a4ef3dc824c7f81338a1a0ff0d94f9267b23a38f900db8b48
94cdbdf938cb94098dc724792a2b973731f19b8d4d1de64e9a0ac2ab6eec8114
938300e4e31ffb59d902329d4dad22160df6b0dc9b29a60780dcc9d809ee8f0d
933b9542fd06126f7dd8453fca83ea11efec73d113a5ec36a739dc281816685f
8e674629468fb61abedbc39819536825dbd1e87dbf3bde15c326a94c26285d41
2ecbda8dd3b4721313e52eef3f443c7921730b35013a8d196a5a1df0bef8146d
3d1b01dfe6064e6f0aa6bd9adecce144e1c001b546eaa66dbf598c73df4f4a10
d69d98bf2aa7e0450e857b8459f7f77f4adf32a500048415e2477dce9cedc363
da2eb71d7eb5a755dbefffcdab759077d2a7c2ee5db3de7f7570444845f91c6c
27688ff57cc0a9428e51a85635a3ce705fdd7ccd78358c5218be6e1090266b07
64c31a7323b824782ff427909659824495988c8d780d7d46c2f46ffa7e200f6f
d1f9f173ef1d0a0798ef9bbc9924612541206a9dba1fd856fb293c4946734213
eaf59f436d289f1b390dc16d66491a65552bedaae1574309591d8fd44fadf206
aac609b95f89264d11e77a970911a86cc86b629ac5d7722b7b1932c78dfde634
6a6be6f146eb4d7b1687d84eb1697227dd174b532646097f83fd0feac75ef308
ce198d73aafcde404e3134ee338cd327dd1b6566e6c0587cdeff8495560dff0a
Total reclaimed space: 0B
Ou même tous les conteneurs, via la commande docker container rm -f $(docker container ls -aq)
Et voilà pour la gestion basique des conteneurs.
Passons aux choses sérieuses.
VI-D. Cas concrets▲
Jusqu'ici, nous n'avons rien fait de bien excitant, créer ou supprimer un conteneur c'est marrant cinq minutes, mais si celui-ci ne sert à rien… bah ça ne sert à rien. Nous allons donc maintenant voir des utilisations concrètes de conteneurs docker.
Avant de commencer, voici la liste des arguments que nous utiliserons dans cette partie :
- -t : fournit un terminal au docker ;
- -i : permet d'écrire dans le conteneur (couplé à -t) ;
- -d : exécute le conteneur en arrière-plan ;
- -v : permet de monter un répertoire local sur le conteneur ;
- -p : permet de binder un port sur le conteneur vers un port sur le host ;
- -e : permet l'ajout d'une variable d'environnement ;
- --name : donne un nom au conteneur ;
- --rm : détruit le conteneur une fois terminé ;
- -w : choisit le répertoire courant (dans le conteneur) ;
- --link : permet de faire un lien entre deux conteneurs.
Bien évidemment, beaucoup d'autres options existent, je vous renvoie à la documentation de docker run.
VI-D-1. Premier cas : le développeur▲
Admettons que j'ai développé une application nodejs, et je dois tester mon application sous différentes versions de node pour le rendre le plus « portable » possible. Installer plusieurs versions de nodejs peut être plutôt compliqué (sauf avec nvm) ou long si on utilise une VM par version, mais pas avec docker.
On commence par écrire notre code, un simple hello world :
2.
// vim app.js
console.log
(
"Hello World"
);
Puis on pull la version 6 et 7 de node :
2.
$
docker image pull xataz/node:6
$
docker image pull xataz/node:7
Puis on peut faire nos tests, pour commencer avec node 6 :
2.
$
docker container run -t --rm -v $
(
pwd):/usr/src/app -w /usr/src/app xataz/node:6
node app.js
Hello World
Puis node 7 :
2.
$
docker container run -t --rm -v $
(
pwd):/usr/src/app -w /usr/src/app xataz/node:7
node app.js
Hello World
C'est cool, notre code fonctionne avec les deux versions.
Qu'avons-nous fait ici ?
Nous avons lancé un conteneur via une image disposant de node (xataz/node:x), sur lequel nous avons mis plusieurs paramètres, un -t pour pouvoir voir le retour de la commande, ici nous n'avons pas besoin du -i puisque nous n'avons pas besoin d'interactivité avec le terminal. Nous avons monté le répertoire courant $(pwd) avec le paramètre -v dans /usr/src/app, nous avons donc choisi ce répertoire en répertoire de travail (workdir) avec l'option -w. Pour finir nous avons exécuté la commande node app.js.
Ici c'est une application plutôt simple, utilisons une application plus complète, comme un petit site, qui affichera Hello Mondedie avec la version vX.X.X. Donc voici le code :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
// vim app.js
var http =
require
(
'http'
);
var server =
http.createServer
(
function (
request,
response) {
response.writeHead
(
200
,
{
"Content-Type"
:
"text/plain"
}
);
response.end
(
"Hello Mondedie avec la version "
+
process.
version +
"
\n
"
);
}
);
server.listen
(
8000
);
console.log
(
"Server running at 0.0.0.0:8000"
);
Et nous lançons nos conteneurs, mais cette fois-ci en arrière-plan :
2.
3.
4.
$
docker container run -d -v $
(
pwd):/usr/src/app -w /usr/src/app -p 8001
:8000
--name node5 xataz/node:6
node app.js
7669bef4b5c06b08a6513ed1ce8b8b036ad5285236a9e21a969897e5a9a8c537
$
docker container run -d -v $
(
pwd):/usr/src/app -w /usr/src/app -p 8002
:8000
--name node6 xataz/node:7
node app.js
0e02e0844dd1b70a7e53e9e185831a05f93d9ed4f4a31f17d066b3eea38be90b
Ici nous n'avons que les id des conteneurs qui s'affichent, et nous rend la main directement, mais cela ne veut pas dire qu'ils ne tournent pas. Vérifions :
2.
3.
4.
$
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0e02e0844dd1 xataz/node:7
"node app.js"
About a minute ago Up About a minute 0
.0
.0
.0
:8002
->
8000
/tcp node7
7669bef4b5c0 xataz/node:6
"node app.js"
About a minute ago Up About a minute 0
.0
.0
.0
:8001
->
8000
/tcp node6
Nous pouvons tester dans notre navigateur, en tapant http://XX.XX.XX.XX:8001 et http://XX.XX.XX.XX:8002 (XX.XX.XX.XX étant l'ip de l'hôte docker), et nous voyons donc clairement que les deux pages affichent un numéro de version différent. J'ai donc ajouté trois paramètres ici, -d à la place de -t, pour lancer le conteneur en arrière-plan, -p pour rediriger un port de l'hôte vers le port du conteneur, c'est pour cela que nous avons utilisé les ports 8001 et 8002 pour accéder aux applications au lieu du port 8000. Ainsi que l'option --name qui donne un nom plus simple à notre conteneur, ce qui permet de mieux les gérer. J'ai également supprimé le --rm, qui logiquement n'est pas compatible avec un conteneur lancé en arrière-plan.
Maintenant je peux les supprimer avec leurs noms :
2.
3.
$
docker container rm -f node6 node7
node6
node7
Et voilà, on peut voir à quel point c'est pratique d'utiliser docker dans ce cas présent.
VI-D-2. Deuxième cas : installer une application▲
Nous allons maintenant voir comment installer/déployer une application. Sur le docker hub, on trouve toutes sortes d'images, comme des images pour ghost, ou pour wordpress, mais également des images plus spécifiques comme oracle. Ces images sont souvent des images AllinOne (Tout en un), c'est-à-dire qu'une fois le conteneur créé, c'est fonctionnel.
Nous allons ici créer un conteneur lutim. Nous prendrons ma propre image (ici).
Nous lançons donc notre application :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
$
docker container run -d --name lutim -p 8181
:8181
-e UID
=
1000
-e GID
=
1000
-e SECRET
=
mysecretcookie -e WEBROOT
=
/images -v /docker/config/lutim:/usr/lutim/data -v /docker/data/lutim:/usr/lutim/files xataz/lutim
Unable to find image 'xataz/lutim:latest'
locally
latest: Pulling from xataz/lutim
c1c2612f6b1c: Already exists
0e00ee3bbf34: Pull complete
58fda08c5f8a: Pull complete
1bb27614a217: Pull complete
0dff0105dd58: Pull complete
Digest: sha256:a71eb9f0cfa205083029f0170aa5184a5fc9e844af292b44832dbd0b9e8fdeba
Status: Downloaded newer image for
xataz/lutim:latest
766be7bdb450d42b45a56d4d1c11467825e03229548dc9110c1e46e0d3fbf033
On vérifie que ça tourne :
2.
3.
$
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
766be7bdb450 xataz/lutim "/usr/local/bin/start"
7
minutes ago Up 7
minutes 0
.0
.0
.0
:8181
->
8181
/tcp lutim
Nous avons ici ajouté des -e, ceci permet d'ajouter des variables d'environnement au conteneur. Ces variables seront utilisées soit directement par l'application, soit par le script d'init de l'image (que nous verrons dans la partie Créer une imageCréer une image). Dans notre cas nous avons ajouté quatre variables, mais il en existe d'autres (cf. README) :
- UID et GID sont des variables que vous trouverez dans toutes mes images, qui permet de choisir avec quels droits sera lancée l'application.
- WEBROOT est une variable qui permettra la modification du webroot du fichier de configuration de l'application, donc ici nous y accéderons via http://XX.XX.XX.XX:8181/images.
- SECRET est une variable qui permettra la modification du secret du fichier de configuration de l'application. Ces variables sont spécifiques à l'image
Nous pouvons vérifier les variables d'environnement via docker container inspect lutim, mais cette commande retourne toute la configuration de notre conteneur, nous allons donc le formater :
2.
$
docker container inspect -f '{{.Config.env}}'
lutim
[UID
=
1000
GID
=
1000
SECRET
=
mysecretcookie WEBROOT
=
/images PATH
=
/usr/local
/sbin:/usr/local
/bin:/usr/sbin:/usr/bin:/sbin:/bin CONTACT
=
contact@domain.tld MAX_FILE_SIZE
=
10000000000
DEFAULT_DELAY
=
1
MAX_DELAY
=
0
]
Nous avons ici également des variables que nous n'avons pas indiquées lors du lancement du conteneur, mais c'est normal, lors de la création d'une image, nous pouvons mettre des valeurs par défaut (nous verrons également ceci dans la partie Créer une image).
Puisque tout semble OK, on teste donc avec http://XX.XX.XX.XX:8181/images. Bon je ne vais pas rentrer dans les détails de fonctionnement de lutim, mais on voit que ça fonctionne.
Comme vous pouvez le voir, en quelques secondes nous avons installé un lutim, alors qu'il est normalement plus compliqué et plus long de le faire manuellement.
Cette partie ne vous apprendra rien de technique, je suppose, mais c'est simplement pour vous montrer ce que docker peut vous apporter si vous n'avez pas forcément la technique pour le faire à la main, ou tout simplement pour tester une application.
VI-D-3. Troisième cas : le déploiement▲
Dans ce troisième cas, nous allons partir sur quelque chose d'un peu plus complexe, et spécifique. Nous allons ici voir comment faire un déploiement en blue/green (version simplifiée), c'est-à-dire sans interruption de service (ou très peu, moins d'une seconde).
Dans ce scénario, nous aurons trois conteneurs, un conteneur nginx qui servira de reverse proxy, et deux conteneurs nodejs avec des versions différentes.
Nous allons donc reprendre notre code de tout à l'heure et lancer avec node5 et node6 :
2.
3.
4.
$
docker container run -d -v $
(
pwd):/usr/src/app -w /usr/src/app -p 8001
:8000
--name node-blue xataz/node:6
node app.js
e2a392d5b0ee7c65683dc277eb47c67dd93804ef36458968b2e5d34afc154957
$
docker container run -d -v $
(
pwd):/usr/src/app -w /usr/src/app -p 8002
:8000
--name node-green xataz/node:7
node app.js
18ff8c5b4c4d9c37cd2ee14eadd75e4addc10e04324cd513c77ae55b4912b042
node-blue est actuellement notre production, et node-green notre machine de test. Nous appellerons ceci des branches. Notre but est donc de mettre à jour notre node de la version 6 à la version 7, en s'assurant que tout fonctionne correctement.
Pour cela nous utiliserons nginx en reverse proxy. Nous commençons par créer notre fichier de configuration nginx :
2.
3.
4.
5.
6.
7.
8.
9.
# mkdir -p /docker/config/nginx
# vim /docker/config/nginx/bluegreen.conf
server {
listen 8080;
location / {
proxy_pass http://toto:8000;
}
}
On part sur un fichier de configuration plutôt simple. Pour vous expliquer rapidement, tout ce qui arrivera sur le port 8080 sera retransmis au conteneur node-blue qui répondra à nginx qui nous le retransmettra. Nous utilisons ici directement le port de l'application, puisque nous « attaquons » directement le conteneur. Nous verrons juste en dessous à quoi correspond le toto.
Puis on lance notre nginx :
$
docker container run -d -v /docker/config/nginx:/sites-enabled -p 80
:8080
--name reverse --link node-blue:toto --link node-green:tata xataz/nginx:mainline
Nous voyons ici un nouveau paramètre, le --link, celui-ci permet de créer un alias, au sein du conteneur lancé, afin de communiquer avec un autre conteneur, via cet alias. toto est le nom de l'alias qui pointe vers le conteneur node-blue, c'est donc identique avec tata et node-green. J'ai volontairement appelé les alias comme ceci, pour différencier le nom du conteneur et l'alias.
Si nous testons notre appli, avec l'URL http://XX.XX.XX.XX, nous devrions avoir affiché :
Hello Mondedie avec la version v6.10
.3
Maintenant que j'ai bien testé mon application sur node.js 7 (via l'URL http://XX.XX.XX.XX:8002), je peux facilement faire un basculement de branche, il me suffit de modifier le fichier de configuration de nginx, et de relancer le conteneur :
2.
3.
4.
5.
6.
7.
8.
# vim /docker/config/nginx/bluegreen.conf
server {
listen 8080;
location / {
proxy_pass http://tata:8000;
}
}
On relance nginx :
2.
$
docker restart reverse
reverse
Et on reteste la même URL ( http://XX.XX.XX.XX), nous avons maintenant la version 6 de node :
Hello Mondedie avec la version v7.10
.0
Maintenant, node-green est devenu notre production, et node-blue notre dev dans laquelle nous testerons la version 8 de node (par exemple). Et quand celle-ci sera prête, nous referons un basculement de branche sur notre nginx.
Bien sûr, ceci n'est qu'une ébauche du basculement blue/green, mais le principe est là. Nous pourrions améliorer ceci en utilisant un réseau docker, que nous verrons dans un prochain chapitre, ou avec l'utilisation d'un serveur DNS interne à notre réseau de conteneur.
VI-E. Conclusion▲
Cette partie fut plus concrète que les précédentes, nous savons maintenant comment créer un conteneur, et le gérer. À partir de ce moment, vous êtes totalement capable d'installer une application via docker.
VII. Créer une image▲
Utiliser des images docker c'est bien, mais les fabriquer soi-même c'est mieux. Nous avons plusieurs façons de faire une image. Nous pouvons le faire à partir d'un conteneur existant (via docker save), facile à mettre en place, mais compliqué à maintenir. From scratch, complexe, et difficile à maintenir. Puis via un dockerfile, un fichier qui comporte les instructions de la création de l'image (la recette), en se basant sur une image existante, c'est la meilleure méthode, et c'est facile à maintenir.
VII-A. Création d'un Dockerfile▲
VII-A-1. Créons une image apache▲
Le Dockerfile (toujours avec une majuscule) est un fichier qui contient toutes les instructions pour créer une image, comme des métadonnées (Mainteneur, label, etc.), ou même les commandes à exécuter pour installer un logiciel.
Voici la liste des instructions d'un Dockerfile :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
FROM
# Pour choisir l'image sur laquelle on se base, toujours en premier
RUN
# Permet d'exécuter une commande
CMD
# Commande exécutée au démarrage du conteneur par défaut
EXPOSE
# Ouvre un port
ENV
# Permet d'éditer des variables d'environnement
ARG
# Un peu comme ENV, mais seulement le temps de la construction de l'image
COPY
# Permet de copier un fichier ou répertoire de l'hôte vers l'image
ADD
# Permet de copier un fichier de l'hôte ou depuis une URL vers l'image, permet également de décompresser une archive tar
LABEL
# Des métadonnées utiles pour certains logiciels de gestion de conteneurs, comme rancher ou swarm, ou tout simplement pour mettre des informations sur l'image.
ENTRYPOINT
# Commande exécutée au démarrage du conteneur, non modifiable, utilisée pour package une commande
VOLUME
# Crée une partition spécifique
WORKDIR
# Permet de choisir le répertoire de travail
USER
# Choisit l'utilisateur qui lance la commande du ENTRYPOINT ou du CMD
ONBUILD
# Crée un step qui sera exécuté seulement si notre image est choisie comme base
HEALTHCHECK
# Permet d'ajouter une commande pour vérifier le fonctionnement de votre conteneur
STOPSIGNAL
# permet de choisir le [signal](http://man7.org/linux/man-pages/man7/signal.7.html) qui sera envoyé au conteneur lorsque vous ferez un docker container stop
Pour plus d'informations, je vous conseille de consulter la documentation officielle
Et la commande pour construire l'image :
docker image build -t [imagename]:[tag] [dockerfile folder]
Pour pouvoir construire une image, il faut connaître un minimum le logiciel que l'on souhaite conteneuriser, par exemple ici je vais conteneuriser apache, et je sais qu'il lui faut certaines variables d'environnement pour fonctionner. On commence par créer le répertoire de notre projet :
2.
3.
$
mkdir /home/xataz/superapache
$
cd /home/xataz/superapache
$
Puis on crée notre Dockerfile (le D toujours en majuscule) :
$
vim Dockerfile
On commence par mettre le FROM :
FROM
ubuntu
Puis on ajoute les variables d'environnement (qui sont normalement gérées par le système d'init) :
2.
3.
4.
5.
6.
ENV
APACHE_RUN_USER www-data
ENV
APACHE_RUN_GROUP www-data
ENV
APACHE_LOG_DIR /var/web/log/apache2
ENV
APACHE_PID_FILE /var/run/apache2.pid
ENV
APACHE_RUN_DIR /var/run/apache2
ENV
APACHE_LOCK_DIR /var/lock/apache2
On installe apache :
RUN
export DEBIAN_FRONTEND
=
noninteractive &&
apt-get update &&
apt-get -y -q upgrade &&
apt-get -y -q install apache2
Dans un Dockerfile, il ne faut aucune interactivité, donc on utilise : DEBIAN_FRONTEND=noninteractive et surtout l'option -y de apt-get
On expose les ports (facultatif) :
EXPOSE
80
443
Et pour finir, on ajoute la commande par défaut :
CMD
["apache2ctl"
,"-D"
,"FOREGROUND"
]
le Dockerfile au complet :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
FROM
ubuntu
ENV
APACHE_RUN_USER www-data
ENV
APACHE_RUN_GROUP www-data
ENV
APACHE_LOG_DIR /var/web/log/apache2
ENV
APACHE_PID_FILE /var/run/apache2.pid
ENV
APACHE_RUN_DIR /var/run/apache2
ENV
APACHE_LOCK_DIR /var/lock/apache2
RUN
export DEBIAN_FRONTEND
=
noninteractive &&
apt-get update &&
apt-get -y -q upgrade &&
apt-get -y -q install apache2
EXPOSE
80
443
CMD
["apache2ctl"
,"-D"
,"FOREGROUND"
]
Puis on construit notre image :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
$
docker image build -t xataz/superapache .
Sending build context to Docker daemon 3
.072
kB
Step 0
: FROM ubuntu
latest: Pulling from library/ubuntu
d3a1f33e8a5a: Pull complete
c22013c84729: Pull complete
d74508fb6632: Pull complete
91e54dfb1179: Pull complete
[...]
Step 10
: CMD apache2ctl -D FOREGROUND
--->
Running in
d435f9e2db87
--->
1f9e7590d11b
Removing intermediate container d435f9e2db87
Successfully built 1f9e7590d11b
On peut la tester :
2.
$
docker container run -ti -p 80
:80
xataz/superapache
AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 172.17.0.2. Set the '
ServerName' directive globally to suppress this message
Si on se connecte maintenant au site, vous devriez avoir la page Apache2 Ubuntu Default Page.
VII-A-2. Exemple d'une image lutim▲
Nous allons ici créer une image lutim, basée sur le tuto de solinvictus. Pour ceci, nous nous baserons sur Debian Jessie.
On commence par créer un dossier pour notre image :
$
mkdir lutim
Voici le Dockerfile :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
FROM
debian:jessie
ENV
GID=991
ENV
UID=991
ENV
CONTACT=contact@domain.tld
ENV
WEBROOT=/
ENV
SECRET=e7c0b28877f7479fe6711720475dcbbd
ENV
MAX_FILE_SIZE=10000000000
LABEL
description="lutim based on debian"
\
maintainer="xataz <https://github.com/xataz>"
RUN
apt-get update &&
apt-get install -y --no-install-recommends --no-install-suggests perl ca-certificates shared-mime-info perlmagick make gcc ca-certificates libssl-dev git
RUN
cpan install Carton
RUN
cd / &&
git clone https://git.framasoft.org/luc/lutim.git
RUN
cd /lutim &&
carton install
VOLUME
/lutim/files /data
EXPOSE
8181
COPY
lutim.conf /lutim/lutim.conf
COPY
startup /usr/bin/startup
RUN
chmod +x /usr/bin/startup
CMD
["startup"
]
C'est plutôt simpliste, j'ai suivi exactement le tutoriel. J'y ai ajouté des variables d'environnement qui seront utilisées par le script startup afin de générer le fichier lutim.conf. Je crée deux volumes, /lutim/files qui contiendra les images hébergées, et /data qui contient la base de données de lutim. Le port exposé est le 8181.
On écrit donc le lutim.conf :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
{
hypnotoad =>
{
listen =>
['http://0.0.0.0:8181'
],
},
contact =>
'<contact>'
,
secrets =>
['<secret>'
],
length =>
8
,
crypto_key_length =>
8
,
provis_step =>
5
,
provisioning =>
100
,
anti_flood_delay =>
5
,
max_file_size =>
<
max_file_size>
,
default_delay =>
1
,
max_delay =>
0
,
always_encrypt =>
1
,
token_length =>
24
,
stats_day_num =>
365
,
keep_ip_during =>
365
,
policy_when_full =>
'warn'
,
#broadcast_message => 'Maintenance',
prefix =>
'<webroot>'
,
db_path =>
'/data/lutim.db'
,
delete_no_longer_viewed_files =>
90
};
Ce fichier est presque un copier-coller de celui du tutoriel. Toutes les valeurs entre <> seront remplacées avec les variables d'environnement par le script startup.
Voici donc le startup :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
#!/bin/bash
grep lutim /etc/group >
/dev/null 2
>&
1; [[ $?
-eq 1
]] &&
addgroup --gid ${GID}
lutim
grep lutim /etc/passwd >
/dev/null 2
>&
1; [[ $?
-eq 1
]] &&
adduser --system --shell /bin/sh --no-create-home --ingroup lutim --uid ${UID}
lutim
chown -R lutim:lutim /data /lutim
sed -i -e 's|<secret>|'
${SECRET}
'|'
\
-e 's|<contact>|'
${CONTACT}
'|'
\
-e 's|<max_file_size>|'
${MAX_FILE_SIZE}
'|'
\
-e 's|<webroot>|'
${WEBROOT}
'|'
/lutim/lutim.conf
su - lutim -c "cd /lutim; /usr/local/bin/carton exec hypnotoad -f /lutim/script/lutim"
Le script est plutôt simpliste, il crée un utilisateur et un groupe lutim, puis lui donne les droits au répertoire /lutim et /data, ensuite il modifie le fichier de conf avec les bonnes valeurs et exécute lutim avec l'utilisateur lutim.
On peut tenter de construire l'image :
$
docker image build -t xataz/lutim .
ça va prendre un petit moment, c'est plutôt long à installer les dépendances.
Testons notre image :
2.
3.
4.
5.
$
docker container run -d -P xataz/lutim
bb40fd7df491b224a73146981fff831f9bc5d61efde8c040cd48fa2418450a54
$
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
bb40fd7df491 xataz/lutim "startup"
2
seconds ago Up 1
seconds 0
.0
.0
.0
:32770
->
8181
/tcp suspicious_hamilton
J'ai ici utilisé l'option -P, qui permet d'attribuer un port disponible à tous les ports EXPOSE, ici 32770->8181.
On teste dans le navigateur ( http://XX.XX.XX.XX:32770), et normalement, ça fonctionne.
VII-A-3. Créons une image de base▲
Nous allons ici créer une image de base, c'est-à-dire une image qui servira pour créer une autre image, comme debian, ubuntu ou alpine.
Ici nous créerons une image alpine.
On crée le répertoire de notre projet :
2.
$
mkdir alpine
$
cd alpine
On commence par créer un rootfs, pour ceci nous utiliserons l'outil officiel de alpine, c'est-à-dire apk (version actuelle 2.6.8-r2, à vérifier au moment de la lecture ici), la méthode est différente pour chaque distribution (pour debian c'est debootstrap, pour archlinux ou gentoo on télécharge directement le rootfs, etc.), il faut par contre faire ceci en root :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
$
wget http://dl-cdn.alpinelinux.org/alpine/latest-stable/main/x86_64/apk-tools-static-2
.6
.7
-r0.apk
$
tar xzvf apk-tools-static-2
.6
.7
-r0.apk
.SIGN.RSA.alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub
.PKGINFO
sbin/
tar: Ignoring unknown extended header keyword 'APK-TOOLS.checksum.SHA1'
sbin/apk.static.SIGN.RSA.alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub
tar: Ignoring unknown extended header keyword 'APK-TOOLS.checksum.SHA1'
$
./sbin/apk.static -X http://dl-cdn.alpinelinux.org/alpine/3
.4
/main -U --allow-untrusted --root rootfs --initdb add alpine-base
fetch http://dl-cdn.alpinelinux.org/alpine/latest-stable/main/x86_64/APKINDEX.tar.gz
(
1
/16
) Installing musl (
1
.1
.14
-r11)
(
2
/16
) Installing busybox (
1
.24
.2
-r11)
Executing busybox-1
.24
.2
-r11.post-install
(
3
/16
) Installing alpine-baselayout (
3
.0
.3
-r0)
Executing alpine-baselayout-3
.0
.3
-r0.pre-install
Executing alpine-baselayout-3
.0
.3
-r0.post-install
(
4
/16
) Installing openrc (
0
.21
-r2)
Executing openrc-0
.21
-r2.post-install
(
5
/16
) Installing alpine-conf (
3
.4
.1
-r2)
(
6
/16
) Installing zlib (
1
.2
.8
-r2)
(
7
/16
) Installing libcrypto1.0
(
1
.0
.2h-r1)
(
8
/16
) Installing libssl1.0
(
1
.0
.2h-r1)
(
9
/16
) Installing apk-tools (
2
.6
.7
-r0)
(
10
/16
) Installing busybox-suid (
1
.24
.2
-r11)
(
11
/16
) Installing busybox-initscripts (
3
.0
-r3)
Executing busybox-initscripts-3
.0
-r3.post-install
(
12
/16
) Installing scanelf (
1
.1
.6
-r0)
(
13
/16
) Installing musl-utils (
1
.1
.14
-r11)
(
14
/16
) Installing libc-utils (
0
.7
-r0)
(
15
/16
) Installing alpine-keys (
1
.1
-r0)
(
16
/16
) Installing alpine-base (
3
.4
.3
-r0)
Executing busybox-1
.24
.2
-r11.trigger
OK: 7
MiB in
16
packages
Notre rootfs est maintenant créé :
2.
3.
4.
$
ls
apk-tools-static-2
.6
.7
-r0.apk rootfs sbin
$
ls rootfs/
bin dev etc. home lib linuxrc media mnt proc root run sbin srv sys tmp usr var
Nous n'avons plus besoin de apk, on le supprime donc :
$
rm -rf apk-tools-static-2
.6
.7
-r0.apk sbin
Afin de gagner de l'espace, nous créons une archive de rootfs, et nous supprimons le dossier :
2.
$
tar czf rootfs.tar.gz -C rootfs .
$
rm -rf rootfs
Pour finir on crée notre Dockerfile :
2.
3.
FROM
scratch
ADD
rootfs.tar.gz /
scratch étant une image spécifique qui est vide, spécialement pour créer une image de base, ou à partir de rien.
On construit l'image et on teste :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
$
docker image build -t superalpine .
Sending build context to Docker daemon 3
.342
MB
Step 1
: FROM scratch
--->
Step 2
: ADD rootfs.tar.gz /
--->
fa8cc3e04a21
Removing intermediate container 385884a4f0c5
Successfully built fa8cc3e04a21
$
docker container run -ti superalpine /bin/sh
/ # ls
bin dev etc. home lib linuxrc media mnt proc root run sbin srv sys tmp usr var
Nous pouvons bien sûr l'améliorer, comme ajouter des dépôts, des paquets, une commande par défaut, etc. :
2.
3.
4.
5.
6.
7.
8.
9.
FROM
scratch
ADD
rootfs.tar.gz /
RUN
echo "http://dl-cdn.alpinelinux.org/alpine/v3.4/main"
>
/,etc./apk/repositories
RUN
apk add -U wget git
CMD
"/bin/sh"
Puis on reconstruit et teste :
2.
3.
4.
5.
$
docker image build -t superalpine .
[...]
$
docker container run -ti superalpine
/ # ls
bin dev etc. home lib linuxrc media mnt proc root run sbin srv sys tmp usr var
Et voilà vous avez votre image qui pourra vous servir de base à toutes vos autres images.
VII-B. Les bonnes pratiques▲
Cette partie vous donnera des conseils pour optimiser vos images. Je ne prétends pas avoir la science infuse, et ces astuces/conseils sont plutôt personnels, mais je pense que ce sont de bonnes pratiques.
VII-B-1. Limiter les layers▲
Qu'est-ce qu'un layer
Les images docker sont créées avec des couches de filesystems, chaque instruction d'un dockerfile est une couche (chaque étape d'un build). Ces couches sont des layers.
Reprenons notre image de base :
2.
3.
4.
5.
6.
7.
8.
9.
FROM
scratch
ADD
rootfs.tar.gz /
RUN
echo "http://dl-cdn.alpinelinux.org/alpine/v3.4/main"
>
/,etc./apk/repositories
RUN
apk add -U wget git
CMD
"/bin/sh"
Nous avons ici cinq étapes.
- Étape 1 : je pars sur la base d'une image vide => Layer 1 ;
- Étape 2 : le layer 1 passe en lecture, je crée un layer 2 et copie le contenu de rootfs.tar.gz dedans. => layer 2 ;
- Étape 3 : le layer 2 passe en lecture, je crée un layer 3 et crée le fichier /,etc./apk/repositories. => layer 3 ;
- Étape 4 : le layer 3 passe en lecture, j'installe wget et git. => layer 4 ;
- Étape 5 : le layer 4 passe en lecture, j'ajoute une commande par défaut. => layer 5.
Chaque layer comporte les modifications apportées par rapport au layer précédent. Si j'ajoute une étape entre la 4 et 5 pour supprimer wget et git. Lors du build je repars du cache de l'étape 4, par contre l'étape 5 sera rejouée, puisque j'ai ajouté une étape entre les deux, et son layer précédent n'est maintenant plus disponible. Chaque layer est donc le différentiel du layer précédent.
Lorsque l'on crée un conteneur, on crée une nouvelle couche sur l'image, qui est en écriture, les couches précédentes ne sont qu'en lecture. C'est ce qui permet d'utiliser la même image pour plusieurs conteneurs, sans perte d'espace.
Mais multiplier les layers diminue les performances en lecture, en effet, admettons que dans un des premiers layers, vous installez wget, et que vous avez 50 layers après. Dans votre conteneur, quand vous demanderez d'exécuter wget, il passera par chaque layer pour chercher cette commande. Dans notre exemple, si je demande à mon conteneur d'exécuter wget, il va d'abord le chercher dans le layer 6 (le fs du conteneur), il n'est pas ici, donc on passe au layer 5, mais il n'est pas ici, donc il cherche dans le layer 4 et là, il le trouve. Dans une image qui comporte une vingtaine de layers, les performances ne sont pas trop impactées, mais avec une centaine de layers à remonter, cela se sent.
Pour corriger cela, on met plusieurs commandes dans la même étape :
2.
3.
4.
5.
6.
7.
FROM
scratch
ADD
rootfs.tar.gz /
RUN
echo "http://dl-cdn.alpinelinux.org/alpine/v3.4/main"
>
/,etc./apk/repositories &&
apk add -U wget git
CMD
"/bin/sh"
VII-B-2. Limiter la taille d'une image▲
Plusieurs pistes pour diminuer la taille d'une image :
- utiliser une image de base minimaliste comme alpine par exemple (5 Mo pour alpine contre 120 Mo pour Debian) ;
- supprimer le cache des gestionnaires de paquets ou autres applications ;
- désinstaller les applications qui ne sont plus utiles.
Pour la première étape pas de souci, il faut juste changer l'image de base, ou pas, c'est au choix.
Pour les deux autres étapes, c'est encore une histoire de layers.
Reprenons notre exemple, nous allons supprimer git et wget, de deux manières différentes :
2.
3.
4.
5.
6.
7.
8.
FROM
scratch
ADD
rootfs.tar.gz /
RUN
echo "http://dl-cdn.alpinelinux.org/alpine/v3.4/main"
>
/,etc./apk/repositories &&
apk add -U wget git
RUN
apk del wget git &&
rm -rf /var/cache/apk/*
CMD
"/bin/sh"
Je la construis en la nommant superalpine1 :
$
docker image build -t superalpine1 .
Puis je crée un superalpine2 :
2.
3.
4.
5.
6.
7.
FROM
scratch
ADD
rootfs.tar.gz /
RUN
echo "http://dl-cdn.alpinelinux.org/alpine/v3.4/main"
>
/,etc./apk/repositories &&
apk add -U wget git &&
apk del wget git &&
rm -rf /var/cache/apk/*
CMD
"/bin/sh"
Que je construis en superalpine2 :
$
docker image build -t superalpine2 .
Regardons la différence maintenant :
2.
3.
4.
$
docker images "superalpine*"
:(
REPOSITORY TAG IMAGE ID CREATED SIZE
superalpine2 latest 7808b8c75444 5
minutes ago 4
.813
MB
superalpine1 latest 886e22de4865 5
minutes ago 23
.54
MB
Pourquoi superalpine1 est-il plus lourd que superalpine2 ? Comme précédemment dit, c'est une histoire de layers. Sur superalpine1, nous avons supprimé les paquets depuis un autre layer, ce qui fait que wget et git (ainsi que les dépendances) sont toujours présents dans le layer précédent, et donc utilisent de l'espace. Ce nouveau layer indique simplement que tel ou tel fichier a été supprimé.
VII-B-3. La lisibilité▲
Nous avons vu précédemment qu'il fallait limiter les layers, le problème c'est que cela peut vite devenir illisible. Ce que je conseille (ce n'est pas une obligation), c'est de faire une commande par ligne, pour notre superalpine, cela donnerait ceci :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
FROM
scratch
ADD
rootfs.tar.gz /
RUN
echo "http://dl-cdn.alpinelinux.org/alpine/v3.4/main"
>
/,etc./apk/repositories \
&& apk add -U wget \
git \
&& apk del wget \
git \
&& rm -rf /var/cache/apk/*
CMD
"/bin/sh"
Cela permet de voir en un seul coup d'œil, les différentes commandes. Ne surtout pas oublier le caractère d'échappement \ pour chaque nouvelle ligne.
Un autre exemple, en reprenant notre lutim, et en appliquant les précédentes règles :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
FROM
debian:jessie
ENV
GID=991
\
UID=991
\
CONTACT=contact@domain.tld \
WEBROOT=/ \
SECRET=e7c0b28877f7479fe6711720475dcbbd \
MAX_FILE_SIZE=10000000000
LABEL
description="lutim based on debian"
\
maintainer="xataz <https://github.com/xataz>"
RUN
apt-get update \
&& apt-get install -y --no-install-recommends --no-install-suggests perl \
ca-certificates \
shared-mime-info \
perlmagick \
make \
gcc \
ca-certificates \
libssl-dev \
git \
&& cpan install Carton \
&& cd / \
&& git clone https://git.framasoft.org/luc/lutim.git \
&& cd /lutim \
&& carton install \
&& apt-get purge -y make \
gcc \
ca-certificates \
libssl-dev \
git \
&& apt-get autoremove --purge -y \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /root/.cpan* /lutim/local/cache/* /lufi/utilities
VOLUME
/lutim/files /data
EXPOSE
8181
COPY
lutim.conf /lutim/lutim.conf
COPY
startup /usr/bin/startup
RUN
chmod +x /usr/bin/startup
CMD
["startup"
]
Si on tente de construire cette image :
$
docker image build -t lutim2 .
Ce qui donne pour comparer :
2.
3.
$
docker images
lutim2 latest ae75fa0e1f8a 2
minutes ago 252
.6
MB
xataz/lutim latest 3d72d9158f68 17
hours ago 577
.1
MB
Nous avons diminué de moitié la taille de l'image.
VII-B-4. Éviter les processus root▲
Ici c'est plus de l'administration qu'une spécificité docker.
Nous avons bien sûr la possibilité d'utiliser l'instruction USER directement dans un dockerfile, mais ceci comporte un défaut, c'est qu'on peut avoir besoin de root pour exécuter un script, pour par exemple créer un utilisateur, modifier des permissions, etc.
Pour pallier ceci, nous utiliserons su-exec et exec, tout se jouera dans le script de démarrage.
- su-exec est un outil qui fait la même chose que su, mais à la différence que su-exec ne crée qu'un seul processus. su-exec s'utilise de manière très simple : su-exec <user>:<group> <command>.
- exec est une fonction, qui permet d'exécuter une commande afin qu'elle remplace le PID actuel. exec s'utilise comme ceci : exec <command>
Pour l'exemple, reprenons notre script startup de lutim :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
#!/bin/bash
grep lutim /etc/group >
/dev/null 2
>&
1; [[ $?
-eq 1
]] &&
addgroup --gid ${GID}
lutim
grep lutim /etc/passwd >
/dev/null 2
>&
1; [[ $?
-eq 1
]] &&
adduser --system --shell /bin/sh --no-create-home --ingroup lutim --uid ${UID}
lutim
chown -R lutim:lutim /data /lutim
sed -i -e 's|<secret>|'
${SECRET}
'|'
\
-e 's|<contact>|'
${CONTACT}
'|'
\
-e 's|<max_file_size>|'
${MAX_FILE_SIZE}
'|'
\
-e 's|<webroot>|'
${WEBROOT}
'|'
/lutim/lutim.conf
su - lutim -c "cd /lutim; /usr/local/bin/carton exec hypnotoad -f /lutim/script/lutim"
Avec ceci, nous aurons deux processus root :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
$
docker container run -d --name lutim lutim
$
docker container top lutim
UID PID PPID C STIME TTY TIME CMD
root 22898
22887
0
11
:50
? 00
:00
:00
/bin/bash /usr/bin/startup
root 22942
22898
0
11
:50
? 00
:00
:00
su - lutim -c cd /lutim; /usr/local
/bin/carton exec hypnotoad -f /lutim/script/lutim
991
22947
22942
0
11
:50
? 00
:00
:00
-su -c cd /lutim; /usr/local
/bin/carton exec hypnotoad -f /lutim/script/lutim
991
22950
22947
9
11
:50
? 00
:00
:00
/lutim/script/lutim
991
22951
22950
0
11
:50
? 00
:00
:00
/lutim/script/lutim
991
22952
22950
0
11
:50
? 00
:00
:00
/lutim/script/lutim
991
22953
22950
0
11
:50
? 00
:00
:00
/lutim/script/lutim
991
22954
22950
0
11
:50
? 00
:00
:00
/lutim/script/lutim
$
docker container rm lutim
Sous debian, malheureusement il n'y a pas de paquet précompilé, il va donc falloir le faire à la main, ci-dessous le dockerfile avec l'ajout de l'installation de su-exec :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
FROM
debian:jessie
ENV
GID=991
\
UID=991
\
CONTACT=contact@domain.tld \
WEBROOT=/ \
SECRET=e7c0b28877f7479fe6711720475dcbbd \
MAX_FILE_SIZE=10000000000
LABEL
description="lutim based on debian"
\
maintainer="xataz <https://github.com/xataz>"
RUN
apt-get update \
&& apt-get install -y --no-install-recommends --no-install-suggests perl \
ca-certificates \
shared-mime-info \
perlmagick \
make \
gcc \
ca-certificates \
libssl-dev \
git \
libv6-dev \
&& cpan install Carton \
&& cd / \
&& git clone https://git.framasoft.org/luc/lutim.git \
&& git clone https://github.com/ncopa/su-exec \
&& cd /su-exec \
&& make \
&& cp su-exec /usr/local/bin/su-exec \
&& cd /lutim \
&& rm -rf /su-exec \
&& carton install \
&& apt-get purge -y make \
gcc \
ca-certificates \
libssl-dev \
git \
libc6-dev \
&& apt-get autoremove --purge -y \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /root/.cpan* /lutim/local/cache/* /lufi/utilities
VOLUME
/lutim/files /data
EXPOSE
8181
COPY
lutim.conf /lutim/lutim.conf
COPY
startup /usr/bin/startup
RUN
chmod +x /usr/bin/startup
CMD
["startup"
]
Nous commençons par remplacer notre su par su-exec dans le fichier startup :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
#!/bin/bash
grep lutim /etc/group >
/dev/null 2
>&
1; [[ $?
-eq 1
]] &&
addgroup --gid ${GID}
lutim
grep lutim /etc/passwd >
/dev/null 2
>&
1; [[ $?
-eq 1
]] &&
adduser --system --shell /bin/sh --no-create-home --ingroup lutim --uid ${UID}
lutim
chown -R lutim:lutim /data /lutim
sed -i -e 's|<secret>|'
${SECRET}
'|'
\
-e 's|<contact>|'
${CONTACT}
'|'
\
-e 's|<max_file_size>|'
${MAX_FILE_SIZE}
'|'
\
-e 's|<webroot>|'
${WEBROOT}
'|'
/lutim/lutim.conf
cd /lutim
su-exec lutim /usr/local
/bin/carton exec hypnotoad -f /lutim/script/lutim
Puis on reconstruit :
$
docker image build -t lutim .
On teste notre première modification :
2.
3.
4.
5.
6.
7.
8.
9.
10.
$
docker container run -d --name lutim lutim
$
docker container top lutim
UID PID PPID C STIME TTY TIME CMD
root 2142
2129
0
13
:03
? 00
:00
:00
/bin/bash /usr/bin/startup
991
2183
2142
1
13
:03
? 00
:00
:00
/lutim/script/lutim
991
2185
2183
0
13
:03
? 00
:00
:00
/lutim/script/lutim
991
2186
2183
0
13
:03
? 00
:00
:00
/lutim/script/lutim
991
2187
2183
0
13
:03
? 00
:00
:00
/lutim/script/lutim
991
2188
2183
0
13
:03
? 00
:00
:00
/lutim/script/lutim
$
docker container rm lutim
On voit qu'il nous reste un seul processus root.
Et si on ajoute, exec :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
#!/bin/bash
grep lutim /etc/group >
/dev/null 2
>&
1; [[ $?
-eq 1
]] &&
addgroup --gid ${GID}
lutim
grep lutim /etc/passwd >
/dev/null 2
>&
1; [[ $?
-eq 1
]] &&
adduser --system --shell /bin/sh --no-create-home --ingroup lutim --uid ${UID}
lutim
chown -R lutim:lutim /data /lutim
sed -i -e 's|<secret>|'
${SECRET}
'|'
\
-e 's|<contact>|'
${CONTACT}
'|'
\
-e 's|<max_file_size>|'
${MAX_FILE_SIZE}
'|'
\
-e 's|<webroot>|'
${WEBROOT}
'|'
/lutim/lutim.conf
cd /lutim
exec su-exec lutim /usr/local
/bin/carton exec hypnotoad -f /lutim/script/lutim
On reconstruit :
$
docker image build -t lutim .
Puis on teste :
2.
3.
4.
5.
6.
7.
8.
9.
$
docker container run -d --name lutim lutim
$
docker container top lutim
UID PID PPID C STIME TTY TIME CMD
991
2312
2299
11
13
:04
? 00
:00
:00
/lutim/script/lutim
991
2353
2312
0
13
:04
? 00
:00
:00
/lutim/script/lutim
991
2354
2312
0
13
:04
? 00
:00
:00
/lutim/script/lutim
991
2355
2312
0
13
:04
? 00
:00
:00
/lutim/script/lutim
991
2356
2312
0
13
:04
? 00
:00
:00
/lutim/script/lutim
$
docker container rm lutim
Et voilà, plus de processus root, un bon pas niveau sécurité.
VII-C. Conclusion▲
Nous avons vu ici comment créer une image applicative, mais aussi comment faire une image de base. L'optimisation de l'image est importante, que ce soit pour la sécurité, ou pour la taille de celle-ci. Normalement à partir de ce moment, vous devriez pouvoir créer vos propres images, et de manière propre.
VIII. Déployer/partager une image▲
Il existe trois façons de partager/déployer une image, la plus simple est sans doute de donner un dockerfile. Mais nous pouvons également l'envoyer sur le hub, ou tout simplement en envoyant une archive de l'image.
VIII-A. Via un dockerfile▲
Je doute avoir vraiment besoin de l'expliquer, vous avez créé votre dockerfile, ainsi que les scripts qui vont avec, il vous suffit de l'envoyer à la personne que vous souhaitez, ou de le partager sur github par exemple.
VIII-B. Via le docker hub▲
Je ne ferai pas une présentation complète du Hub, mais juste une explication sur comment envoyer votre image.
Pour commencer, il faut se créer un compte sur le Hub, rendez-vous ici.
Une fois inscrit, on se connecte depuis notre docker :
2.
3.
4.
5.
6.
$
docker login
Username: xataz
Password:
Email: xataz@mondedie.fr
WARNING: login credentials saved in
/home/xataz/.docker/config.json
Login Succeeded
Maintenant que l'on est connecté, on peut pousser notre image, pour ce faire, il faut que votre image soit nommée sous cette forme : username/imagename:tag :
2.
$
docker image push xataz/lutim:latest
The push refers to a repository [docker.io/xataz/lutim] (
len: 1
)
Et voilà, notre image est dans les nuages. Vous pouvez vous rendre sur votre Hub, et vous trouverez un nouveau repository. Vous pouvez donc ajouter une description et un mini tuto.
Bien évidemment, il existe d'autres options pour les envoyer sur le Hub, par exemple la possibilité de lier un repository à un github, ceci permet un build automatique (pratique, mais lent). Il est également possible de créer un hub privé (payant).
VIII-C. Via une image tar▲
On peut également faire un tar d'une image, et ensuite la partager. Nous allons créer une image tar de notre lutim, pour ceci c'est plutôt simple :
2.
3.
$
docker image save -o lutim.tar xataz/lutim
$
ls
Dockerfile lutim.conf lutim.tar startup
Et voilà on peut la partager.
L'avantage de cette méthode, c'est qu'il n'y a plus besoin de taper sur le hub pour installer l'image, car toutes les dépendances sont également dans le tar :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
$
docker image rm -f $
(
docker images -q)
$
docker image ls -a
REPOSITORY TAG IMAGE ID CREATED SIZE
$
docker image import lutim.tar xataz/lutim
sha256:a8dfc72dbef32cab0cd57a726f65c96c38f5e09736f46962bba7dbb3a86876d8
$
docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
xataz/lutim latest a8dfc72dbef3 6
seconds ago 598
.2
MB
$
docker image history xataz/lutim
IMAGE CREATED CREATED BY SIZE COMMENT
a8dfc72dbef3 12
seconds ago 598
.2
MB Imported from -
VIII-D. Conclusion▲
Nous avons vu ici trois méthodes pour partager notre travail, bien évidemment celle à privilégier est celle du Dockerfile, qui permet de fournir la source de notre image.
La partager sur le dockerhub est bien, et si possible, il vaut mieux faire un autobuild (pas toujours facile).
IX. Limiter les ressources d'un conteneur▲
IX-A. La mémoire▲
Pour limiter la mémoire, nous utiliserons trois options :
- -m --memory : on choisit la limite de mémoire (ex : -m 200M) ;
- --memory-swap : on choisit la limite de mémoire + swap (ex : -m 200M --memory-swap 1G, ceci limite la mémoire à 200 M et le swap à 800 M) ;
- --oom-kill-disable : OOM pour Out Of Memory, permet d'éviter qu'une instance plante si elle dépasse l'allocation mémoire accordée, peut être pratique pour une application gourmande.
D'autres options existent,
2.
3.
4.
5.
6.
7.
8.
9.
$
docker container run --help |
grep -Ei 'mem|oom'
--cpuset-mems string MEMs in
which to allow execution (
0
-3
, 0
,1
)
--kernel-memory bytes Kernel memory limit
-m, --memory bytes Memory limit
--memory-reservation bytes Memory soft limit
--memory-swap bytes Swap limit equal to memory plus swap: '-1'
to enable unlimited swap
--memory-swappiness int Tune container memory swappiness (
0
to 100
) (
default -1
)
--oom-kill-disable Disable OOM Killer
--oom-score-adj int Tune host's OOM preferences (-1000 to 1000)
Pour tester la mémoire nous utiliserons l'image debian :
2.
3.
4.
5.
6.
$
docker container run -ti --rm -m 500M debian
root@1044c936c209:/# free -h
total used free shared buffers cached
Mem: 23G 7
.8G 15G 97M 457M 1
.6G
-/+ buffers/cache: 5
.8G 17G
Swap: 0B 0B 0B
On ne voit aucune différence avec la machine hôte, en fait la limitation se fait au niveau du processus du conteneur.
Nous utiliserons stress pour nos tests, nous allons stresser le conteneur avec 500 M, qui devraient représenter environ 50 % de la mémoire de ma machine :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
root@1044c936c209:/# apt-get update && apt-get install stress
[...]
root@1044c936c209:/# stress --vm 1 --vm-bytes 500M &
[1
] 54
root@1044c936c209:/# stress: info: [54] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd
root@1044c936c209:/# ps aux
USER PID %CPU
%MEM
VSZ RSS TTY STAT START TIME COMMAND
root 1
0
.0
0
.1
20244
1644
? Ss 15
:08
0
:00
/bin/bash
root 54
0
.0
0
.0
7172
0
? S 15
:12
0
:00
stress --vm 1
--vm-bytes 500M
root 55
70
.5
49
.7
519176
506868
? R 15
:12
0
:04
stress --vm 1
--vm-bytes 500M
root 56
0
.0
0
.1
17500
1992
? R+ 15
:13
0
:00
ps aux
root@1044c936c209:/# kill 54 55
root@1044c936c209:/#
On voit que c'est correct.
Maintenant testons avec 900 M de ram :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
root@1044c936c209:/# stress --vm 1 --vm-bytes 900M &
[1
] 68
root@1044c936c209:/# stress: info: [68] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd
root@1044c936c209:/# ps aux
USER PID %CPU
%MEM
VSZ RSS TTY STAT START TIME COMMAND
root 1
0
.0
0
.1
20244
1696
? Ss 15
:08
0
:00
/bin/bash
root 68
0
.0
0
.0
7172
0
? S 15
:15
0
:00
stress --vm 1
--vm-bytes 900M
root 69
77
.6
47
.2
928776
482072
? D 15
:15
0
:02
stress --vm 1
--vm-bytes 900M
root 70
0
.0
0
.1
17500
2016
? R+ 15
:15
0
:00
ps aux
root@1044c936c209:/# kill 68 69
Comme on peut le voir, stress n'utilise que 47.2 % de la mémoire disponible sur l'hôte, alors qu'on lui a dit d'en utiliser environ 90 %. On voit donc que la limitation fonctionne.
Mais je sens des septiques parmi vous, nous allons tester cette même commande dans un conteneur non limité :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
$
docker container run -ti --rm debian
root@e3ba516add96:/# apt-get update && apt-get install stress
[...]
root@e3ba516add96:/# stress --vm 1 --vm-bytes 900M &
[1
] 51
root@e3ba516add96:/# stress: info: [51] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd
root@e3ba516add96:/# ps aux
USER PID %CPU
%MEM
VSZ RSS TTY STAT START TIME COMMAND
root 1
0
.0
0
.1
20244
1584
? Ss 15
:17
0
:00
/bin/bash
root 51
0
.0
0
.0
7172
0
? S 15
:18
0
:00
stress --vm 1
--vm-bytes 900M
root 52
41
.1
77
.0
928776
785696
? D 15
:18
0
:03
stress --vm 1
--vm-bytes 900M
root 54
0
.0
0
.1
17500
1940
? R+ 15
:18
0
:00
ps aux
IX-B. Le CPU▲
Pour limiter le CPU nous utiliserons trois options :
- -c -cpu-shares : permet le partage des ressources CPU, c'est une proportion, si on met tous les conteneurs à 1024, ils se partageront équitablement les ressources, si un conteneur à 1024 et deux autres à 512, cela donnera 50 % pour le conteneur 1 et 25 % pour les deux autres ;
- --cpu-quota : permet la limitation de l'utilisation CPU (50000 pour 50 %, 0 pour pas de limite) ;
- --cpuset-cpus :permet de choisir les CPU/core utilisés (0,1 utilise les cpus 0 et 1, 0-2 utilise les cpus 0, 1, et 2)
Il existe d'autres options, que vous pouvez trouver avec docker container run --help | grep -E 'cpu' :
2.
3.
4.
5.
6.
7.
8.
--cpu-period int Limit CPU CFS (
Completely Fair Scheduler) period
--cpu-quota int Limit CPU CFS (
Completely Fair Scheduler) quota
--cpu-rt-period int Limit CPU real-time period in
microseconds
--cpu-rt-runtime int Limit CPU real-time runtime in
microseconds
-c, --cpu-shares int CPU shares (
relative weight)
--cpus decimal Number of CPUs
--cpuset-cpus string CPUs in
which to allow execution (
0
-3
, 0
,1
)
--cpuset-mems string MEMs in
which to allow execution (
0
-3
, 0
,1
)
Nous allons également utiliser debian avec stress :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
$
docker container run -ti --rm --cpuset-cpus 0
debian
root@0cfcada740e4:/# apt-get update && apt-get install stress
root@0cfcada740e4:/# stress -c 2 &
stress: info: [75
] dispatching hogs: 1
cpu, 0
io, 0
vm, 0
hdd
root@0cfcada740e4:/# top
top - 23
:10
:14
up 2
days, 15
:45
, 0
users, load average: 1
.09
, 0
.86
, 0
.44
Tasks: 4
total, 2
running, 2
sleeping, 0
stopped, 0
zombie
%Cpu
(
s): 50
.0
us, 0
.0
sy, 0
.0
ni, 50
.0
id, 0
.0
wa, 0
.0
hi, 0
.0
si, 0
.0
st
KiB Mem: 1019768
total, 214828
used, 804940
free, 1564
buffers
KiB Swap: 1186064
total, 789032
used, 397032
free. 30952
cached Mem
PID USER PR NI VIRT RES SHR S %CPU
%MEM
TIME+ COMMAND
76
root 20
0
7164
88
0
R 49
.8
0
.0
0
:17
.90
stress
77
root 20
0
7164
88
0
R 50
.0
0
.0
0
:17
.90
stress
1
root 20
0
20236
3272
2792
S 0
.0
0
.2
0
:00
.02
bash
75
root 20
0
7164
868
788
S 0
.0
0
.0
0
:00
.00
stress
78
root 20
0
21968
2416
2024
R 0
.0
0
.1
0
:00
.00
top
Comme on peut le voir, stress n'utilise qu'un cpu. Nous allons tester avec les deux cœurs :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
$
docker container run -ti --rm --cpuset-cpus 0
,1
debian
root@d19a45b0cb82:/# apt-get update && apt-get install stress
root@d19a45b0cb82:/# stress -c 2 &
[1
] 60
stress: info: [60
] dispatching hogs: 2
cpu, 0
io, 0
vm, 0
hdd
root@d19a45b0cb82:/# top
top - 23
:14
:47
up 2
days, 15
:50
, 0
users, load average: 0
.70
, 0
.64
, 0
.45
Tasks: 5
total, 3
running, 2
sleeping, 0
stopped, 0
zombie
%Cpu
(
s):100
.0
us, 0
.0
sy, 0
.0
ni, 0
.0
id, 0
.0
wa, 0
.0
hi, 0
.0
si, 0
.0
st
KiB Mem: 1019768
total, 214828
used, 804940
free, 1564
buffers
KiB Swap: 1186064
total, 789032
used, 397032
free. 30952
cached Mem
PID USER PR NI VIRT RES SHR S %CPU
%MEM
TIME+ COMMAND
62
root 20
0
7164
88
0
R 100
.0
0
.0
0
:20
.66
stress
61
root 20
0
7164
88
0
R 99
.9
0
.0
0
:20
.66
stress
1
root 20
0
20236
3168
2688
S 0
.0
0
.2
0
:00
.03
bash
60
root 20
0
7164
864
784
S 0
.0
0
.0
0
:00
.00
stress
63
root 20
0
21968
2460
2060
R 0
.0
0
.1
0
:00
.00
top
Maintenant on va tester l'option --cpu-quota :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
$
docker container run -ti --cpu-quota 50000
--rm debian
root@1327f5aa6e13:/# apt-get update && apt-get install stress
root@1327f5aa6e13:/# stress -c 2 &
[1
] 53
root@1327f5aa6e13:/# stress: info: [53] dispatching hogs: 2 cpu, 0 io, 0 vm, 0 hdd
root@1327f5aa6e13:/# top
top - 15
:28
:43
up 2
days, 5
:46
, 0
users, load average: 0
.49
, 0
.23
, 0
.19
Tasks: 5
total, 3
running, 2
sleeping, 0
stopped, 0
zombie
%Cpu
(
s): 50
.5
us, 0
.0
sy, 0
.0
ni, 49
.5
id, 0
.0
wa, 0
.0
hi, 0
.0
si, 0
.0
st
KiB Mem: 1019768
total, 240840
used, 778928
free, 3208
buffers
KiB Swap: 1186064
total, 788908
used, 397156
free. 51508
cached Mem
PID USER PR NI VIRT RES SHR S %CPU
%MEM
TIME+ COMMAND
54
root 20
0
7172
88
0
R 25
.0
0
.0
0
:05
.70
stress
55
root 20
0
7172
88
0
R 25
.0
0
.0
0
:05
.71
stress
1
root 20
0
20244
3212
2728
S 0
.0
0
.3
0
:00
.02
bash
53
root 20
0
7172
936
856
S 0
.0
0
.1
0
:00
.00
stress
56
root 20
0
21988
2412
1964
R 0
.0
0
.2
0
:00
.00
top
On voit donc que c'est bien limité à 50 %.
IX-C. L'écriture disque▲
Nous pouvons également limiter la vitesse d'écriture du disque, ceci grâce à une option :
- --blkio-weight : ceci est une priorisation des I/O. (un chiffre en 10 et 1000) ;
- --blkio-weight-device : pareil que la précédente, mais pour un device particulier (--blkio-weight-device /dev/sda:100) ;
- --device-read-bps : limite la vitesse de lecture sur un device par seconde. (--device-read-bps /dev/sda:1mb) ;
- --device-read-iops ;: limite les IO en lecture sur un device par seconde. (--device-read-iops /dev/sda:1000) ;
- --device-write-bps : limite la vitesse d'écriture sur un device par seconde. (--device-write-bps /dev/sda:1mb) ;
- --device-write-iops : limite les IO en écriture sur un device par seconde. (--device-write-iops /dev/sda:1000).
Sans vous montrez toutes les options, voici par exemple comment limiter l'écriture disque :
2.
3.
4.
5.
6.
7.
8.
9.
$
docker container run -ti --device-write-bps /dev/sda:1mb --rm debian bash
root@43677eafb3f6:/# time dd if=/dev/zero of=/tmp/test bs=1M count=10 oflag=direct
10
+0
records in
10
+0
records out
10485760
bytes (
10
MB) copied, 13
.4905
s, 777
kB/s
real 0m13.491s
user 0m0.000s
sys 0m0.000s
Et sans limiter l'écriture :
2.
3.
4.
5.
6.
7.
8.
9.
$
docker container run -ti --rm debian bash
root@cc2079d07eba:/# time dd if=/dev/zero of=/tmp/test bs=1M count=10 oflag=direct
10
+0
records in
10
+0
records out
10485760
bytes (
10
MB) copied, 0
.0250823
s, 418
MB/s
real 0m0.026s
user 0m0.000s
sys 0m0.003s
X. Docker Volume▲
Depuis la version 1.9.0, docker a introduit une nouvelle sous-commande à docker, docker volume. Celle-ci permet de créer des volumes, facilement réutilisables pour plusieurs conteneurs, et depuis différentes sources avec une multitude de plugins (glusterfs, flocker, AFS etc.). N'hésitez pas à consulter la documentation.
Nous allons voir ici quelques possibilités que nous offre cette commande.
La syntaxe reste dans l'esprit de docker :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
$
docker volume --help
Usage: docker volume COMMAND
Manage Docker volumes
Options:
--help Print usage
Commands:
create Create a volume
inspect Display detailed information on one or more volumes
ls List volumes
rm Remove one or more volumes
Run 'docker volume COMMAND --help'
for
more information on a command.
X-A. Création d'un volume simple▲
Jusque-là, nous utilisions l'option -v avec un docker run, genre docker run -d -v /path/on/host:/path/on/container image.
Nous allons commencer par créer un volume, voyons ce que docker volume create prend comme arguments :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
$
docker volume create --help
Usage: docker volume create [OPTIONS]
Create a volume
Options:
-d, --driver string Specify volume driver name (
default "local"
)
--help Print usage
--label value Set metadata for
a volume (
default [])
--name string Specify volume name
-o, --opt value Set driver specific options (
default map[])
Donc on va créer notre premier volume :
2.
$
docker volume create --name test
test
Que nous utiliserons comme ceci :
2.
3.
4.
5.
$
docker run -ti -v test:/test alpine:3
.4
sh
/ # ls /test
/ # touch /test/bidule
/ # exit
$
Si je crée un autre conteneur, on retrouve notre fichier bidule :
2.
3.
$
docker run -ti -v test:/test alpine:3
.4
sh
/ # ls /test
bidule
On retrouve bien notre fichier, les données ont donc correctement persisté.
C'est bien beau, mais les fichiers, ils sont où sur l'hôte ?!
Les fichiers se retrouvent dans /var/lib/docker/volumes/<volumename>/_data.
2.
$
sudo ls /var/lib/docker/volumes/test/_data
bidule
Et si je veux choisir où les mettre, comme un -v /path:/path ?
C'est possible. Par exemple, je veux que mon volume test pointe vers /data/test, je peux le monter via cette commande :
$
docker volume create --name test -o type
=
none -o device
=
/data/test -o o
=
bind
Je recrée un conteneur où je vais créer un fichier :
2.
3.
$
docker run -ti -v test:/test alpine:3
.4
touch /test/fichier
$
sudo ls /data/test
fichier
Et voilà, mon fichier est correctement dans /data/test.
X-B. Un peu plus loin▲
Avec docker volume, il nous est possible de créer un volume via un device (par exemple /dev/sdb1).
Nous avons plein de possibilités.
Création d'un tmpfs, qui permettra de faire passer des données entre deux conteneurs, attention cependant, une fois ce volume non utilisé par un conteneur, les données sont effacées, puisqu'en ram :
2.
$
docker volume create -o type
=
tmpfs -o device
=
tmpfs -o o
=
size
=
100M,uid
=
1000
--name tmpfile
tmpfile
Ou alors monter une partition complète (attention, la partition ne doit pas être montée par l'hôte) :
2.
$
docker volume create -o type
=
ext4 -o device
=
/dev/sdb1 --name extpart
extpart
X-C. Encore et toujours plus loin avec les plugins▲
Comme précédemment cité, il nous est possible d'ajouter des plugins à docker. Nous utiliserons ici le plugin netshare. Le plugin netshare permet l'utilisation de NFS, AWS EFS, Samba/CIFS et ceph.
Pour les besoins du test, j'ai créé deux machines de test, une sans docker avec un serveur nfs, et l'autre avec docker.
Ma machine servant de serveur de partage aura comme IP 10.2.81.71, et l'autre 10.2.155.205.
Je ne partirai pas sur l'explication de l'installation d'un serveur nfs, vous trouverez ceci ici.
X-C-1. Prérequis▲
Il nous faudra nfs sur la machine docker :
$
apt-get install nfs-common
Comme indiqué dans le README du plugin, nous testons si notre partage fonctionne :
2.
3.
4.
$
mount -t nfs 10
.2
.81
.71
:/shared /mnt
$
root@scw-docker:~# ls /mnt
fichier_partager
$
umount /mnt
c'est bon tout fonctionne.
X-C-2. Installation du plugin▲
Nous avons de la chance, il existe un dpkg pour debian et ubuntu, on l'installe donc comme ceci :
2.
$
wget https://github.com/ContainX/docker-volume-netshare/releases/download/v0.20
/docker-volume-netshare_0.20_amd64.deb
$
dpkg -i docker-volume-netshare_0.20_amd64.deb
Et c'est tout ^^.
X-C-3. Utilisation▲
Il faut d'abord lancer le daemon :
$
service docker-volume-netshare start
Puis on crée notre volume :
$
docker volume create -d nfs --name 10
.2
.81
.71
/shared
Puis on le monte :
2.
3.
$
docker run -i -t -v 10
.2
.81
.71
/shared:/mount xataz/alpine:3
.4
sh
$
ls /mount
fichier_partager
Et voilà, nous voyons notre fichier partagé. Je n'ai absolument rien inventé ici, tout ce que j'ai écrit est indiqué dans le readme du plugin.
X-D. Conclusion▲
Nous avons vu ici comment utiliser docker volume, et comment gérer plusieurs volumes, dans plusieurs conteneurs. Nous avons même vu comment installer un plugin (ici netshare), pour pouvoir étendre les possibilités de la gestion de volume.
XI. docker network▲
Tout comme docker volume, docker network est apparu avec la version 1.9.0 de docker. Les networks ont plusieurs utilités, créer un réseau overlay entre plusieurs machines par exemple, ou alors remplacer les links en permettant à tous les conteneurs d'un même réseau de communiquer par leurs noms.
Voici la syntaxe :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
$
docker network
Usage: docker network COMMAND
Manage Docker networks
Options:
--help Print usage
Commands:
connect Connect a container to a network
create Create a network
disconnect Disconnect a container from a network
inspect Display detailed information on one or more networks
ls List networks
rm Remove one or more networks
Run 'docker network COMMAND --help'
for
more information on a command.
XI-A. Les types de réseaux▲
Nous avons de base, quatre types de networks :
- bridge : crée un réseau interne pour vos conteneurs ;
- host : ce type de réseau permet au conteneur d'avoir la même interface que l'hôte ;
- none : comme le nom l'indique, aucun réseau pour les conteneurs ;
- overlay : réseau interne entre plusieurs hôtes.
Bien évidemment il existe des plugins pour étendre ces possibilités.
Par défaut, nous avons déjà un réseau bridge, un réseau host et un réseau none. Nous ne pouvons pas créer de réseau host ou none supplémentaire. Ce chapitre expliquera donc l'utilisation des réseaux bridge. Pour le réseau overlay, ce type étant utilisé pour la communication interhôte, nous verrons ceci dans la partie swarm de ce tutoriel.
XI-B. Création d'un network▲
Voici les arguments que prend docker network create :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
$
docker network create --help
Usage: docker network create [OPTIONS] NETWORK
Create a network
Options:
--aux-address value Auxiliary IPv4 or IPv6 addresses used by Network driver (
default map[])
-d, --driver string Driver to manage the Network (
default "bridge"
)
--gateway value IPv4 or IPv6 Gateway for
the master subnet (
default [])
--help Print usage
--internal Restrict external access to the network
--ip-range value Allocate container ip from a sub-range (
default [])
--ipam-driver string IP Address Management Driver (
default "default"
)
--ipam-opt value Set IPAM driver specific options (
default map[])
--ipv6 Enable IPv6 networking
--label value Set metadata on a network (
default [])
-o, --opt value Set driver specific options (
default map[])
--subnet value Subnet in
CIDR format that represents a network segment (
default [])
Plus d'informations dans la documentation.
Nous allons directement attaquer en créant un réseau, que nous appellerons test :
2.
$
docker network create test
1b8d8e2fae05224702568d71a7d8e128601250018795d06dba884d860e124e65
Nous pouvons vérifier s'il est bien créé :
2.
3.
4.
5.
6.
$
docker network ls
NETWORK ID NAME DRIVER SCOPE
461b26287559 bridge bridge local
9a2cd2ffcb62 host host local
d535bd59f11a none null local
1b8d8e2fae05 test bridge local
Nous pouvons obtenir quelques informations sur ce réseau :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
$
docker network inspect test
[
{
"Name"
: "test"
,
"Id"
: "1b8d8e2fae05224702568d71a7d8e128601250018795d06dba884d860e124e65"
,
"Scope"
: "local"
,
"Driver"
: "bridge"
,
"EnableIPv6"
: false,
"IPAM"
: {
"Driver"
: "default"
,
"Options"
: {},
"Config"
: [
{
"Subnet"
: "172.18.0.0/16"
,
"Gateway"
: "172.18.0.1/16"
}
]
},
"Internal"
: false,
"Containers"
: {},
"Options"
: {},
"Labels"
: {}
}
]
Comme nous pouvons le voir, le réseau a comme sous-réseau 172.18.0.0/16, et comme gateway 172.18.0.1. Les conteneurs auront donc des IP attribuées entre 172.18.0.2 et 172.18.255.254 (172.18.255.255 étant le broadcast).
Nous pouvons évidemment choisir ce sous-réseau, les IP à attribuer aux conteneurs, etc. Pour ceci nous avons plusieurs options, comme --subnet, --gateway, ou --ip-range par exemple, ce qui donnerait :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
$
docker network create --subnet 10
.0
.50
.0
/24
--gateway 10
.0
.50
.254
--ip-range 10
.0
.50
.0
/28
test2
6d1a863a8dbadbabb2fb2d71c87d856309447e446837ba7a06dcde66c70614de
$
docker network inspect test2
[
{
"Name"
: "test2"
,
"Id"
: "6d1a863a8dbadbabb2fb2d71c87d856309447e446837ba7a06dcde66c70614de"
,
"Scope"
: "local"
,
"Driver"
: "bridge"
,
"EnableIPv6"
: false,
"IPAM"
: {
"Driver"
: "default"
,
"Options"
: {},
"Config"
: [
{
"Subnet"
: "10.0.50.0/24"
,
"IPRange"
: "10.0.50.0/28"
,
"Gateway"
: "10.0.50.254"
}
]
},
"Internal"
: false,
"Containers"
: {},
"Options"
: {},
"Labels"
: {}
}
]
Là j'ai créé un réseau 10.0.50.0/24 (donc de 10.0.50.0 à 10.0.50.255), j'ai mis la passerelle en 10.0.50.254, et un IPRange en 10.0.50.0/28 (donc une attribution de 10.0.50.1 à 10.0.50.14).
Pour chaque réseau créé, docker nous crée une interface :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
$
ifconfig
br-1b8d8e2fae05 Link encap:Ethernet HWaddr 02
:42
:E1:AC:C5:1A
inet addr:172
.18
.0
.1
Bcast:0
.0
.0
.0
Mask:255
.255
.0
.0
UP BROADCAST MULTICAST MTU:1500
Metric:1
RX packets:0
errors:0
dropped:0
overruns:0
frame:0
TX packets:0
errors:0
dropped:0
overruns:0
carrier:0
collisions:0
txqueuelen:0
RX bytes:0
(
0
.0
B) TX bytes:0
(
0
.0
B)
br-6d1a863a8dba Link encap:Ethernet HWaddr 02
:42
:B8:7E:FE:A8
inet addr:10
.0
.50
.254
Bcast:0
.0
.0
.0
Mask:255
.255
.255
.0
UP BROADCAST MULTICAST MTU:1500
Metric:1
RX packets:0
errors:0
dropped:0
overruns:0
frame:0
TX packets:0
errors:0
dropped:0
overruns:0
carrier:0
collisions:0
txqueuelen:0
RX bytes:0
(
0
.0
B) TX bytes:0
(
0
.0
B)
Personnellement je ne suis pas fan de ces notations pour le nom des interfaces. Nous pouvons également choisir ce nom, avec l'argument -o :
2.
3.
4.
5.
6.
7.
8.
9.
10.
$
docker network create --subnet 192
.168
.200
.0
/24
-o "com.docker.network.bridge.name=br-test"
test3
d9955df665853315647ed3987f06768199c309255b5e5f17ffe1a24b7bd5f388
docker@default:~$
ifconfig
br-test Link encap:Ethernet HWaddr 02
:42
:1E:7A:E4:0E
inet addr:192
.168
.200
.1
Bcast:0
.0
.0
.0
Mask:255
.255
.255
.0
UP BROADCAST MULTICAST MTU:1500
Metric:1
RX packets:0
errors:0
dropped:0
overruns:0
frame:0
TX packets:0
errors:0
dropped:0
overruns:0
carrier:0
collisions:0
txqueuelen:0
RX bytes:0
(
0
.0
B) TX bytes:0
(
0
.0
B)
C'est bien beau de créer des networks, mais il faut bien les utiliser avec les conteneurs.
XI-C. Utilisation des networks▲
Pour attacher un réseau à un conteneur, il suffit d'utiliser l'argument --network avec docker run :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
$
docker run -ti --network test3 --name ctest alpine:3
.4
sh
/ # ifconfig
eth0 Link encap:Ethernet HWaddr 02
:42
:C0:A8:C8:02
inet addr:192
.168
.200
.2
Bcast:0
.0
.0
.0
Mask:255
.255
.255
.0
inet6 addr: fe80::42
:c0ff:fea8:c8022674/64
Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500
Metric:1
RX packets:11
errors:0
dropped:0
overruns:0
frame:0
TX packets:5
errors:0
dropped:0
overruns:0
carrier:0
collisions:0
txqueuelen:0
RX bytes:926
(
926
.0
B) TX bytes:418
(
418
.0
B)
lo Link encap:Local
Loopback
inet addr:127
.0
.0
.1
Mask:255
.0
.0
.0
inet6 addr: ::12674
/128
Scope:Host
UP LOOPBACK RUNNING MTU:65536
Metric:1
RX packets:0
errors:0
dropped:0
overruns:0
frame:0
TX packets:0
errors:0
dropped:0
overruns:0
carrier:0
collisions:0
txqueuelen:1
RX bytes:0
(
0
.0
B) TX bytes:0
(
0
.0
B)
Nous sommes bien sur le réseau test3 que nous avons précédemment créé.
Nous pouvons également ajouter un réseau supplémentaire avec docker network connect : Dans un autre terminal, taper :
$
docker network connect test ctest
Puis dans le conteneur :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
/ # ifconfig
eth0 Link encap:Ethernet HWaddr 02
:42
:C0:A8:C8:02
inet addr:192
.168
.200
.2
Bcast:0
.0
.0
.0
Mask:255
.255
.255
.0
inet6 addr: fe80::42
:c0ff:fea8:c8022557/64
Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500
Metric:1
RX packets:8
errors:0
dropped:0
overruns:0
frame:0
TX packets:8
errors:0
dropped:0
overruns:0
carrier:0
collisions:0
txqueuelen:0
RX bytes:648
(
648
.0
B) TX bytes:648
(
648
.0
B)
eth1 Link encap:Ethernet HWaddr 02
:42
:AC:13
:00
:02
inet addr:172
.18
.0
.2
Bcast:0
.0
.0
.0
Mask:255
.255
.0
.0
inet6 addr: fe80::42
:acff:fe13:22557
/64
Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500
Metric:1
RX packets:16
errors:0
dropped:0
overruns:0
frame:0
TX packets:8
errors:0
dropped:0
overruns:0
carrier:0
collisions:0
txqueuelen:0
RX bytes:1296
(
1
.2
KiB) TX bytes:648
(
648
.0
B)
lo Link encap:Local
Loopback
inet addr:127
.0
.0
.1
Mask:255
.0
.0
.0
inet6 addr: ::12557
/128
Scope:Host
UP LOOPBACK RUNNING MTU:65536
Metric:1
RX packets:0
errors:0
dropped:0
overruns:0
frame:0
TX packets:0
errors:0
dropped:0
overruns:0
carrier:0
collisions:0
txqueuelen:1
RX bytes:0
(
0
.0
B) TX bytes:0
(
0
.0
B)
L'un des avantages des networks, c'est qu'il n'est plus nécessaire de créer des liens entre les conteneurs, et donc deux conteneurs peuvent communiquer ensemble sans souci. Pour faire un test, créons un conteneur dest puis un conteneur qui le ping qui s'appellera ping :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
$
docker run -d --network test3 --name dest alpine:3
.4
ping 8
.8
.8
.8
# ping 8.8.8.8 est là juste pour faire tourner le conteneur en arrière-plan
$
docker run -ti --network test3 --name ping alpine:3
.4
ping dest
PING dest (
192
.168
.200
.2
): 56
data bytes
64
bytes from 192
.168
.200
.2
: seq
=
0
ttl
=
64
time
=
0
.073
ms
64
bytes from 192
.168
.200
.2
: seq
=
1
ttl
=
64
time
=
0
.118
ms
64
bytes from 192
.168
.200
.2
: seq
=
2
ttl
=
64
time
=
0
.076
ms
64
bytes from 192
.168
.200
.2
: seq
=
3
ttl
=
64
time
=
0
.071
ms
64
bytes from 192
.168
.200
.2
: seq
=
4
ttl
=
64
time
=
0
.068
ms
64
bytes from 192
.168
.200
.2
: seq
=
5
ttl
=
64
time
=
0
.172
ms
^C
--- dest ping statistics ---
6
packets transmitted, 6
packets received, 0
%
packet loss
round-trip min/avg/max =
0
.068
/0
.096
/0
.172
ms
Et voilà, ça marche sans --link.
Maintenant que nos tests sont terminés, nous pouvons supprimer nos réseaux :
2.
3.
4.
$
docker network rm test test2 test3
test
test2
Error response from daemon: network test3 has active endpoints
OUPS !!!
Le réseau test3 est toujours utilisé par des conteneurs, nous pouvons voir les conteneurs actifs sur ce réseau :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
$
docker network inspect test3
[
{
"Name"
: "test3"
,
"Id"
: "971ffa0d0660547e309da67111d3826abff69f053dfba44b22ad05358bd78202"
,
"Scope"
: "local"
,
"Driver"
: "bridge"
,
"EnableIPv6"
: false,
"IPAM"
: {
"Driver"
: "default"
,
"Options"
: {},
"Config"
: [
{
"Subnet"
: "192.168.200.0/24"
}
]
},
"Internal"
: false,
"Containers"
: {
"edadf47462885d2433cc3bb8df17f262c3b9f8d57842951d148d31e746d2a155"
: {
"Name"
: "dest"
,
"EndpointID"
: "eede64502ee056b20f57f39e3cc6631bac801a424faed475d2d90d1f71cde7a1"
,
"MacAddress"
: "02:42:c0:a8:c8:02"
,
"IPv4Address"
: "192.168.200.2/24"
,
"IPv6Address"
: ""
}
},
"Options"
: {
"com.docker.network.bridge.name"
: "br-test"
},
"Labels"
: {}
}
]
Il s'agit du conteneur dest :
2.
3.
4.
$
docker stop dest &&
docker rm -f dest
dest
$
docker network rm test3
test3
XI-D. Conclusion▲
Nous avons vu ici, comment créer des réseaux pour nos conteneurs, ceci est plutôt pratique pour une isolation encore plus poussée des conteneurs, et surtout supprimer les limitations de --link, à savoir l'intercommunication entre conteneurs. Je vous invite une fois de plus à consulter la documentation, qui est comme toujours, très bien faite.
XII. Docker compose▲
Docker compose est un outil anciennement tiers puis racheté par docker, qui permet de composer une stack ou une infrastructure complète de conteneurs. Celui-ci permet de simplifier la création, l'interconnexion et la multiplication de conteneurs. En gros nous créons un fichier yml qui nous permettra de gérer un ensemble de conteneurs en quelques commandes.
Maintenant que vous connaissez bien docker (si j'ai bien fait mon boulot), je serai plus succinct sur les explications, surtout que docker-compose reprend les mêmes options que docker.
XII-A. Installation▲
XII-A-1. Sous Windows▲
Si vous avez installé Docker via docker-toolbox ou docker4windows, docker-compose est déjà installé et fonctionnel.
Sinon nous pouvons l'installer à la main, il suffit de télécharger le binaire sur github Exemple pour la version 1.8.1 (dernière version présentement) docker-compose
Si vous avez installé Docker en suivant la méthode manuelle de ce tutoriel, vous devriez avoir un répertoire c:\docker\bin, copiez-y le binaire et renommez-le en docker-compose.exe.
Et c'est tout, normalement tout est OK, on peut tester :
2.
3.
4.
5.
$
docker-compose version
docker-compose version 1
.12
.0
, build unknown
docker-py version: 2
.2
.1
CPython version: 3
.6
.1
OpenSSL version: OpenSSL 1
.1
.0e 16
Feb 2017
XII-A-2. Sous GNU/Linux▲
Nous avons trois possibilités d'installer docker-compose sous GNU/Linux, avec pip, à la main, ou avec un conteneur. Nous ne verrons ici que l'installation via pip, et l'installation via un conteneur. Je vous conseille tout de même l'installation par conteneur, plus simple et plus rapide à mettre à jour.
XII-A-2-a. Installation via pip▲
Pour ceux qui ne connaissent pas pip, pip est un gestionnaire de paquets pour python, qui permet l'installation d'applications, ou de bibliothèques. C'est un peu comme apt-get.
On installe d'abord pip, sous debian cela donne :
2.
3.
4.
5.
6.
$
apt-get install python-pip
Lecture des listes de paquets... Fait
Construction de l'arbre des dépendances
Lecture des informations d'
état... Fait
python-pip est déjà la plus récente version disponible.
0
mis à jour, 0
nouvellement installés, 0
à enlever et 0
non mis à jour.
(de mon côté j'avais déjà installé pip)
Il suffit maintenant de lancer cette commande :
$
pip install docker-compose
On peut tester :
2.
3.
4.
5.
$
docker-compose version
docker-compose version 1
.8
.0
, build d988a55
docker-py version: 1
.9
.0
CPython version: 2
.7
.11
OpenSSL version: OpenSSL 1
.0
.2d 9
Jul 2015
XII-A-2-b. Installation par un conteneur▲
C'est vraiment très simple, nous allons créer un alias qui lancera un conteneur docker-compose. Nous utiliserons ma propre image : xataz/compose
On édite le fichier .profile de notre utilisateur :
$
vim ~/.profile
Et on ajoute ceci dedans :
2.
3.
4.
5.
alias docker-compose
=
'docker run -v "$(pwd)":"$(pwd)" \
-v /var/run/docker.sock:/var/run/docker.sock \
-e UID=$(id -u) -e GID=$(id -g) \
-w "$(pwd)" \
-ti --rm xataz/compose:1.8'
On recharge le profile :
$
. ~/.profile
Et on peut faire un essai :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
$
docker-compose version
Unable to find image 'xataz/compose:1.8'
locally
1
.8
: Pulling from xataz/compose
c0cb142e4345: Already exists
a0bbf809363b: Pull complete
6d4c02e2941c: Pull complete
Digest: sha256:8a56828af12467a6f7ac55c54d6dd877d51e50026ff91d9fc88d0d0cedbadb3f
Status: Downloaded newer image for
xataz/compose:1
.8
docker-compose version 1
.8
.1
, build 878cff1
docker-py version: 1
.10
.3
CPython version: 2
.7
.12
OpenSSL version: OpenSSL 1
.0
.2i 22
Sep 2016
XII-B. Utilisation de docker-compose▲
Comme je le disais plus haut, docker-compose permet de simplifier la gestion de plusieurs conteneurs. Cette partie sera purement théorique, nous verrons dans la partie suivante, un cas concret.
Voyons d'abord les commandes de docker-compose :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
$
docker-compose --help
Define and run multi-container applications with Docker.
Usage:
docker-compose [-f <
arg>
...] [options] [COMMAND] [ARGS...]
docker-compose -h|
--help
Options:
-f, --file FILE Specify an alternate compose file (
default: docker-compose.yml)
-p, --project-name NAME Specify an alternate project name (
default: directory name)
--verbose Show more output
-v, --version Print version and exit
-H, --host HOST Daemon socket to connect to
--tls Use TLS; implied by --tlsverify
--tlscacert CA_PATH Trust certs signed only by this CA
--tlscert CLIENT_CERT_PATH Path to TLS certificate file
--tlskey TLS_KEY_PATH Path to TLS key file
--tlsverify Use TLS and verify the remote
--skip-hostname-check Don't check the daemon'
s hostname against the name specified
in
the client certificate (
for
example if
your docker host
is an IP address)
Commands:
build Build or rebuild services
bundle Generate a Docker bundle from the Compose file
config Validate and view the compose file
create Create services
down Stop and remove containers, networks, images, and volumes
events Receive real time events from containers
exec Execute a command in
a running container
help Get help on a command
kill Kill containers
logs View output from containers
pause Pause services
port Print the public port for
a port binding
ps List containers
pull Pulls service images
push Push service images
restart Restart services
rm Remove stopped containers
run Run a one-off command
scale Set number of containers for
a service
start Start services
stop Stop services
unpause Unpause services
up Create and start containers
version Show the Docker-Compose version information
Nous n'utiliserons pas toutes ces commandes, je ne vais pas vous faire une description de chaque commande, je pense que malgré l'anglais, elles sont claires.
Pour que docker-compose fonctionne, il nous faut un docker-compose.yml. Ce fichier, au format yaml, comportera les informations pour créer notre infrastructure, comme les conteneurs, les networks et les volumes. Le format yaml est vraiment très susceptible au niveau de l'indentation, j'utilise personnellement une règle, à chaque sous-configuration, je fais deux espaces.
Nous avons deux versions de docker-compose.yml, la version 1 et la version 2. Nous utiliserons ici la version 2, qui remplacera complètement la version 1 bientôt.
Bon on attaque !!!
Nous commençons notre fichier par préciser que nous utiliserons la version 2 :
version
:
'2'
Puis nous aurons trois parties :
2.
3.
volumes
:
networks
:
services
:
Le nom de ces trois parties est plutôt clair, volumes pour définir nos volumes (équivalent à docker volume), networks pour définir nos réseaux (équivalent à docker network) et services pour définir nos conteneurs (équivalent à docker run).
Par exemple, pour créer un conteneur nginx, avec un volume spécifique et un réseau spécifique, nous devrions faire :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
$
docker volume create --name vol_nginx
$
docker volume ls
DRIVER VOLUME NAME
local
vol_nginx
$
docker network create net_nginx
$
docker network ls
NETWORK ID NAME DRIVER SCOPE
4d9b424c1abb bridge bridge local
9a2cd2ffcb62 host host local
0a21f7e5462b net_nginx bridge local
d535bd59f11a none null local
$
docker run -d -p 80
:80
\
-v vol_nginx:/var/www \
--network net_nginx \
-e VARIABLE
=
truc \
--name nginx nginx
996626cbde9b5245a7c4bf22e9bc033379eda094fe2e9c758096730d07866d36
Et voilà la comparaison avec docker-compose, on commence par le fichier :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
version
:
'2'
volumes
:
vol_nginx
:
networks
:
net_nginx
:
services
:
nginx
:
image
:
nginx
container_name
:
nginx
volumes
:
-
vol_nginx
:
/var/www
environment
:
-
VARIABLE=truc
ports
:
-
"80:80"
networks
:
-
net_nginx
Puis on lance le tout :
2.
3.
4.
$
docker-compose up -d
Creating network "projects_net_nginx"
with the default driver
Creating volume "projects_vol_nginx"
with default driver
Creating nginx
Le nom des éléments est nommé en fonction du répertoire courant, ici mon répertoire était projects.
Et voilà, nous avons lancé notre conteneur, avec son réseau et son volume. Pas forcément utile pour un seul conteneur, mais pour plusieurs, cela prend tout son sens. Volumes et networks sont bien évidemment optionnels, si vous ne souhaitez pas créer un réseau particulier, ou si vous utilisez les volumes directement avec le chemin complet.
Je vous invite à regarder cette page pour plus d'information sur le fichier de configuration.
Une fois notre fichier fait, nous pouvons gérer nos conteneurs avec docker-compose. Toutes ces actions se font dans le répertoire du yaml.
Pour lancer les conteneurs : docker-compose up
En arrière-plan : docker-compose up -d
Pour arrêter les conteneurs : docker-compose stop ou docker-compose stop [containername]
On les relance : docker-compose start ou docker-compose start [containername]
Et on les redémarre : docker-compose restart ou docker-compose restart [containername]
Pour voir les conteneurs créés : docker-compose ps
Nous pouvons aussi voir les logs : docker-compose logs
Et nous pouvons aussi supprimer les conteneurs : docker-compose rm
XII-C. Créer une stack web▲
Nous allons créer une stack web, c'est-à-dire un ensemble de conteneurs pour faire un serveur web, nous utiliserons nginx, PHP-fpm et mariadb. Pour tester notre mixture, nous utiliserons adminer.php. Je passerai sur les détails de la configuration des applications, car ceci n'est pas le but du tutoriel.
Voici les images que nous utiliserons :
- nginx : nginx
- php : PHP:fpm
- mariadb : mariadb
Vous pouvez les pull manuellement, cela permettra de gagner du temps.
Si vous avez suivi mes conseils, nous avons normalement un répertoire Docker à la racine de notre machine docker. Nous créons un répertoire web, qui accueillera nos fichiers de configuration nginx ainsi que nos sites :
2.
3.
$
mkdir -p /Docker/web/{conf.d,sites,www}
$
ls /Docker/web
conf.d sites www
On copie ou télécharge adminer.php dans le répertoire /Docker/web/www :
$
wget https://www.adminer.org/static/download/4
.2
.5
/adminer-4
.2
.5
.php -O /Docker/web/www/adminer.php
On crée un fichier adminer.conf dans /Docker/web/sites et on y colle ceci :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
server {
listen 80;
root /var/www/html;
location / {
index adminer.php;
}
location ~ \.PHP$
{
try_files $uri
=
404;
fastcgi_pass php:9000;
fastcgi_index adminer.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
Pour PHP, il va falloir créer notre propre image, à partir de l'image officielle, afin d'y ajouter le module mysql :
2.
3.
4.
# vim php.dockerfile
FROM
php:fpm
RUN
docker-php-ext-install mysqli
CMD
["php-fpm"
]
En fait nous utilisons un script disponible dans l'image pour créer notre image.
On build :
$
docker build -t php-mysql:fpm -f php.dockerfile .
Et on crée notre stack avec un fichier docker-compose.yml :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
version
:
'2'
networks
:
default
:
driver
:
bridge
services
:
web
:
container_name
:
web
image
:
nginx
ports
:
-
"8080:80"
volumes
:
-
/Docker/web/www
:
/var/www/html
-
/Docker/web/sites
:
/etc/nginx/conf.d
PHP
:
container_name
:
PHP
image
:
php-
mysql
:
fpm
volumes
:
-
/Docker/web/www
:
/var/www/html
mariadb
:
container_name
:
mariadb
image
:
mariadb
environment
:
MYSQL_ROOT_PASSWORD
:
root
Puis on lance notre stack en arrière-plan :
2.
3.
4.
5.
$
docker-compose up -d
Creating web
Creating PHP
Creating mariadb
$
Et voilà notre stack est prête, on peut essayer de s'y connecter avec : http://IPDocker:8080, et normalement cela devrait fonctionner, si la page de connexion de adminer s'affiche, c'est que l'interaction entre le conteneur web et le conteneur PHP est correcte. Pour se connecter, il suffit de remplir comme ceci (le mot de passe est root comme indiqué dans le docker-compose) :
Et si la connexion fonctionne, c'est que PHP et mariadb fonctionne correctement.
Nous allons modifier notre docker-compose, afin d'y ajouter un alias pour mariadb, car c'est fastidieux de taper mariadb tout le temps :
2.
3.
4.
5.
6.
7.
8.
9.
10.
[
...]
mariadb
:
container_name
:
mariadb
image
:
mariadb
environment
:
MYSQL_ROOT_PASSWORD
:
root
networks
:
default
:
aliases
:
-
db
Puis on relance notre stack :
2.
3.
4.
$
docker-compose up -d
web is up-to-date
Recreating mariadb
PHP is up-to-date
Comme on peut le voir, docker-compose est intelligent, et ne touche qu'aux conteneurs modifiés, ici mariadb. Puis on peut retester adminer, en mettant cette fois-ci dans serveur seulement db, et normalement, cela fonctionne correctement.
XII-D. Conclusion▲
Ceci n'est qu'une ébauche, nous avons ici créé une stack vraiment simple. Mais il est possible de faire des infras complètes via docker-compose. Je vous invite de nouveau à regarder cette page pour plus d'informations sur docker-compose.
XIII. Docker-machine▲
XIII-A. Qu'est-ce que docker-machine ?▲
Docker-machine est un outil qui permet de provisionner des machines (physiques ou virtuelles) afin d'y installer le nécessaire pour faire fonctionner docker. Machine permet de provisionner sur virtualbox, mais également beaucoup de services cloud, comme digitalOcean, Azure, Google ou plus génériques sur du openstack. Il installera donc docker, mais génère également un certificat ssl, un jeu de clés pour une connexion ssh, et ceux sur de nombreuses distributions GNU/Linux (debian, centos, archlinux …).
Je n'ai personnellement commencé à utiliser docker-machine que très récemment, et je trouve cela vraiment indispensable.
XIII-B. Installation▲
Lors de la rédaction de ce chapitre, nous sommes à la version 0.11.0 de docker-machine, les liens indiqués sont donc contextualisés avec cette version. Il se peut donc que lors de votre lecture, une autre version soit sortie, pour le vérifier, vous pouvez regarder sur les releases du github de docker-machine.
XIII-B-1. Sous Windows▲
Si vous avez installé boot2docker, docker-machine est déjà préinstallé, sinon l'installation est plutôt simple, il suffit de télécharger l'exécutable : docker-machine 32bitsdocker-machine 64 bits
Il faut ensuite le placer dans un endroit stratégique, personnellement c:\docker\bin, je vous conseille également de le renommer en docker-machine.exe, car ce n'est pas très pratique de toujours taper docker-machine-Windows-x86_64.exe.
Si vous avez suivi mon tutoriel pour l'installation sous Windows, normalement vous ne devriez pas avoir à modifier les variables d'environnement, sinon n'oubliez pas de rajouter l'emplacement de votre binaire dans la variable d'environnement PATH afin qu'il soit utilisable sans devoir être dans le répertoire c:\docker\bin.
Et normalement cela fonctionne, on teste :
2.
$
docker-machine.exe version
docker-machine version 0
.7
.0
, build 61388e9
XIII-B-2. Sous GNU/Linux et OS X▲
L'installation est encore plus simple sous un système Unix, il suffit de télécharger le binaire (sauf si vous avez utilisé la toolbox pour OS X) :
2.
3.
4.
5.
$
wget https://github.com/docker/machine/releases/download/v0.11
.0
/docker-machine-$
(
uname -s)-$
(
uname -m) -O /usr/local
/bin/docker-machine
$
chmod +x /usr/local
/bin/docker-machine
# $(uname -s) : Permets d'obtenir le type d'OS (Linux ou darwin)
# $(uname -m) : Permets d'obtenir l'architecture de l'OS (i386 ou x86_64)
# Exemple sous OS X : docker-machine-$(uname -s)-$(uname -m) devient docker-machine-darwin-x86_64
Normalement c'est bon, pour tester :
2.
$
docker-machine version
docker-machine version 0
.11
.0
, build 61388e9
XIII-C. Utilisation▲
Nous utiliserons docker-machine avec le driver virtualbox, le principe reste cependant le même avec les autres drivers (cf. liste).
Pour voir les commandes de docker-machine :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
$
docker-machine
Usage: docker-machine.exe [OPTIONS] COMMAND [arg...]
Create and manage machines running Docker.
Version: 0
.7
.0
, build a650a40
Author:
Docker Machine Contributors - <
https://github.com/docker/machine>
Options:
--debug, -D Enable debug mode
-s, --storage-path "C:\Users\xataz\.docker\machine"
Configures storage path [$MACHINE_STORAGE_PATH
]
--tls-ca-cert CA to verify remotes against [$MACHINE_TLS_CA_CERT
]
--tls-ca-key Private key to generate certificates [$MACHINE_TLS_CA_KEY
]
--tls-client-cert Client cert to use for
TLS [$MACHINE_TLS_CLIENT_CERT
]
--tls-client-key Private key used in
client TLS auth [$MACHINE_TLS_CLIENT_KEY
]
--github-api-token Token to use for
requests to the Github API [$MACHINE_GITHUB_API_TOKEN
]
--native-ssh Use the native (
Go-based) SSH implementation. [$MACHINE_NATIVE_SSH
]
--bugsnag-api-token BugSnag API token for
crash reporting [$MACHINE_BUGSNAG_API_TOKEN
]
--help, -h show help
--version, -v print the version
Commands:
active Print which machine is active
config Print the connection config for
machine
create Create a machine
env Display the commands to set up the environment for
the Docker client
inspect Inspect information about a machine
ip Get the IP address of a machine
kill Kill a machine
ls List machines
provision Re-provision existing machines
regenerate-certs Regenerate TLS Certificates for
a machine
restart Restart a machine
rm Remove a machine
ssh Log into or run a command on a machine with SSH.
scp Copy files between machines
start Start a machine
status Get the status of a machine
stop Stop a machine
upgrade Upgrade a machine to the latest version of Docker
url Get the URL of a machine
version Show the Docker Machine version or a machine docker version
help Shows a list of commands or help for
one command
Run 'docker-machine.exe COMMAND --help'
for
more information on a command.
Comme on peut le voir, les commandes sont toujours similaires à compose ou docker. Si vous êtes sous Windows, installer avec docker-toolbox, vous devriez déjà avoir une machine, pour vérifier, faites :
2.
3.
4.
$
docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
mydocker - generic Running tcp://192
.168
.1
.201
:2376
v1.10
.3
swarm - virtualbox Running tcp://192
.168
.99
.100
:2376
v1.11
.0
Sinon, nous y reviendrons.
XIII-C-1. Créer une machine sous virtualbox▲
Comme dit précédemment, nous utiliserons virtualbox, nous allons donc créer notre machine avec docker-machine create. Pour voir les arguments possibles pour create il suffit de faire :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
$
docker-machine create
Usage: docker-machine create [OPTIONS] [arg...]
Create a machine
Description:
Run 'C:\Program Files\Docker Toolbox\docker-machine.exe create --driver name'
to include the create flags for
that driver in
the help text.
Options:
--driver, -d "none"
Driver to create machine with. [$MACHINE_DRIVER
]
--engine-install-url "https://get.docker.com"
Custom URL to use for
engine installation [$MACHINE_DOCKER_INSTALL_URL
]
--engine-opt [--engine-opt option --engine-opt option] Specify arbitrary flags to include with the created engine in
the form flag
=
value
--engine-insecure-registry [--engine-insecure-registry option --engine-insecure-registry option] Specify insecure registries to allow with the created engine
--engine-registry-mirror [--engine-registry-mirror option --engine-registry-mirror option] Specify registry mirrors to use [$ENGINE_REGISTRY_MIRROR
]
--engine-label [--engine-label option --engine-label option] Specify labels for
the created engine
--engine-storage-driver Specify a storage driver to use with the engine
--engine-env [--engine-env option --engine-env option] Specify environment variables to set in
the engine
--swarm Configure Machine with Swarm
--swarm-image "swarm:latest"
Specify Docker image to use for
Swarm [$MACHINE_SWARM_IMAGE
]
--swarm-master Configure Machine to be a Swarm master
--swarm-discovery Discovery service to use with Swarm
--swarm-strategy "spread"
Define a default scheduling strategy for
Swarm
--swarm-opt [--swarm-opt option --swarm-opt option] Define arbitrary flags for
swarm
--swarm-host "tcp://0.0.0.0:3376"
ip/socket to listen on for
Swarm master
--swarm-addr addr to advertise for
Swarm (
default: detect and use the machine IP)
--swarm-experimental Enable Swarm experimental features
--tls-san [--tls-san option --tls-san option] Support extra SANs for
TLS certs
Et plus particulièrement pour le driver virtualbox :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
$
docker-machine create -d virtualbox --help
Usage: docker-machine create [OPTIONS] [arg...]
Create a machine
Description:
Run 'C:\Program Files\Docker Toolbox\docker-machine.exe create --driver name'
to include the create flags for
that driver in
the help text.
Options:
--driver, -d "none"
Driver to create machine with. [$MACHINE_DRIVER
]
--engine-env [--engine-env option --engine-env option] Specify environment variables to set in
the engine
--engine-insecure-registry [--engine-insecure-registry option --engine-insecure-registry option] Specify insecure registries to allow with the created engine
--engine-install-url "https://get.docker.com"
Custom URL to use for
engine installation [$MACHINE_DOCKER_INSTALL_URL
]
--engine-label [--engine-label option --engine-label option] Specify labels for
the created engine
--engine-opt [--engine-opt option --engine-opt option] Specify arbitrary flags to include with the created engine in
the form flag
=
value
--engine-registry-mirror [--engine-registry-mirror option --engine-registry-mirror option] Specify registry mirrors to use [$ENGINE_REGISTRY_MIRROR
]
--engine-storage-driver Specify a storage driver to use with the engine
--swarm Configure Machine with Swarm
--swarm-addr addr to advertise for
Swarm (
default: detect and use the machine IP)
--swarm-discovery Discovery service to use with Swarm
--swarm-experimental Enable Swarm experimental features
--swarm-host "tcp://0.0.0.0:3376"
ip/socket to listen on for
Swarm master
--swarm-image "swarm:latest"
Specify Docker image to use for
Swarm [$MACHINE_SWARM_IMAGE
]
--swarm-master Configure Machine to be a Swarm master
--swarm-opt [--swarm-opt option --swarm-opt option] Define arbitrary flags for
swarm
--swarm-strategy "spread"
Define a default scheduling strategy for
Swarm
--tls-san [--tls-san option --tls-san option] Support extra SANs for
TLS certs
--virtualbox-boot2docker-url The URL of the boot2docker image. Defaults to the latest available version [$VIRTUALBOX_BOOT2DOCKER_URL
]
--virtualbox-cpu-count "1"
number of CPUs for
the machine (
-1
to use the number of CPUs available) [$VIRTUALBOX_CPU_COUNT
]
--virtualbox-disk-size "20000"
Size of disk for
host in
MB [$VIRTUALBOX_DISK_SIZE
]
--virtualbox-host-dns-resolver Use the host DNS resolver [$VIRTUALBOX_HOST_DNS_RESOLVER
]
--virtualbox-hostonly-cidr "192.168.99.1/24"
Specify the Host Only CIDR [$VIRTUALBOX_HOSTONLY_CIDR
]
--virtualbox-hostonly-nicpromisc "deny"
Specify the Host Only Network Adapter Promiscuous Mode [$VIRTUALBOX_HOSTONLY_NIC_PROMISC
]
--virtualbox-hostonly-nictype "82540EM"
Specify the Host Only Network Adapter Type [$VIRTUALBOX_HOSTONLY_NIC_TYPE
]
--virtualbox-import-boot2docker-vm The name of a Boot2Docker VM to import [$VIRTUALBOX_BOOT2DOCKER_IMPORT_VM
]
--virtualbox-memory "1024"
Size of memory for
host in
MB [$VIRTUALBOX_MEMORY_SIZE
]
--virtualbox-nat-nictype "82540EM"
Specify the Network Adapter Type [$VIRTUALBOX_NAT_NICTYPE
]
--virtualbox-no-dns-proxy Disable proxying all DNS requests to the host [$VIRTUALBOX_NO_DNS_PROXY
]
--virtualbox-no-share Disable the mount of your home directory [$VIRTUALBOX_NO_SHARE
]
--virtualbox-no-vtx-check Disable checking for
the availability of hardware virtualization before the vm is started [$VIRTUALBOX_NO_VTX_CHECK
]
Bon maintenant que nous avons les arguments, nous pouvons créer notre machine :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
$
docker-machine create -d virtualbox --virtualbox-cpu-count "2"
--virtualbox-memory "2048"
--virtualbox-disk-size "25000"
tutoriel
Running pre-create checks...
Creating machine...
(
tutoriel) Copying C:\Users\xataz\.docker\machine\cache\boot2docker.iso to C:\Users\xataz\.docker\machine\machines\tutoriel\boot2docker.iso...
(
tutoriel) Creating VirtualBox VM...
(
tutoriel) Creating SSH key...
(
tutoriel) Starting the VM...
(
tutoriel) Check network to re-create if
needed...
(
tutoriel) Waiting for
an IP...
Waiting for
machine to be running, this may take a few minutes...
Detecting operating system of created instance...
Waiting for
SSH to be available...
Detecting the provisioner...
Provisioning with boot2docker...
Copying certs to the local
machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...
Checking connection to Docker...
Docker is up and running!
To see how to connect your Docker Client to the Docker Engine running on this virtual machine, run: C:\Program Files\Docker Toolbox\docker-machine.exe env tutoriel
Nous avons donc créé notre machine, comme nous pouvons le vérifier :
2.
3.
4.
$
docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
mydocker - generic Running tcp://192
.168
.1
.201
:2376
v1.10
.3
tutoriel * virtualbox Running tcp://192
.168
.99
.100
:2376
v1.11
.0
XIII-C-2. Utilisons notre machine▲
Créer une machine c'est bien, mais l'utiliser c'est mieux.
Si nous tentons de lister les images par exemple, nous avons une erreur (ici sous Windows) :
2.
$
docker images
An error occurred trying to connect: Get http:////./pipe/docker_engine/v1.23
/images/json: open //./pipe/docker_engine: Le fichier spécifié est introuvable.
Pour ce faire il faut indiquer à Docker sur quelle machine se connecter, comme ceci :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
$
docker --tlsverify -H tcp://192
.168
.99
.100
:2376
--tlscacert
=
/c/Users/xataz/.docker/machine/machines/tutoriel/ca.pem --tlscert
=
/c/Users/xataz/.docker/machine/machines/tutoriel/cert.pem --tlskey
=
/c/Users/xataz/.docker/machine/machines/tutoriel/key.pem info
Containers: 0
Running: 0
Paused: 0
Stopped: 0
Images: 0
Server Version: 1
.11
.0
Storage Driver: aufs
Root Dir: /mnt/sda1/var/lib/docker/aufs
Backing Filesystem: extfs
Dirs: 0
Dirperm1 Supported: true
Logging Driver: json-file
Cgroup Driver: cgroupfs
Plugins:
Volume: local
Network: host bridge null
Kernel Version: 4
.1
.19
-boot2docker
Operating System: Boot2Docker 1
.11
.0
(
TCL 7
.0
); HEAD : 32ee7e9 - Wed Apr 13
20
:06
:49
UTC 2016
OSType: Linux
Architecture: x86_64
CPUs: 2
Total Memory: 1
.955
GiB
Name: tutoriel
ID: ZYFO:VNOS:UWNZ:LKHI:WC7A:D2XC:RFKD:GAMN:VF42:GI5Y:D27G:HSIK
Docker Root Dir: /mnt/sda1/var/lib/docker
Debug mode (
client): false
Debug mode (
server): true
File Descriptors: 12
Goroutines: 30
System Time: 2016
-04
-20T18:03
:14
.189404964Z
EventsListeners: 0
Registry: https://index.docker.io/v1/
Labels:
provider
=
virtualbox
Admettez-le, c'est plutôt fastidieux à faire, nous avons une autre méthode, qui consiste à « sourcer » les informations de la machine, de mettre ceci en variable d'environnement avec l'option env :
2.
3.
4.
5.
6.
7.
$
docker-machine env tutoriel
export DOCKER_TLS_VERIFY
=
"1"
export DOCKER_HOST
=
"tcp://192.168.99.100:2376"
export DOCKER_CERT_PATH
=
"C:\Users\xataz\.docker\machine\machines\tutoriel"
export DOCKER_MACHINE_NAME
=
"tutoriel"
# Run this command to configure your shell:
# eval $("C:\Program Files\Docker Toolbox\docker-machine.exe" env tutoriel)
Nous avons ici les différentes informations pour pouvoir s'y connecter, et même la commande à taper :
$
eval $
(
"C:\Program Files\Docker Toolbox\docker-machine.exe"
env tutoriel)
Sous l'invite de commande Windows (j'utilise mingw64), la commande sera : @
FOR
/f "tokens=
" %i IN
('docker-machine env tutoriel') DO
@
%i, mais elle est également indiquée via la commande.
Et c'est tout, nous pouvons tester :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
$
docker info
Containers: 0
Running: 0
Paused: 0
Stopped: 0
Images: 0
Server Version: 1
.11
.0
Storage Driver: aufs
Root Dir: /mnt/sda1/var/lib/docker/aufs
Backing Filesystem: extfs
Dirs: 0
Dirperm1 Supported: true
Logging Driver: json-file
Cgroup Driver: cgroupfs
Plugins:
Volume: local
Network: bridge null host
Kernel Version: 4
.1
.19
-boot2docker
Operating System: Boot2Docker 1
.11
.0
(
TCL 7
.0
); HEAD : 32ee7e9 - Wed Apr 13
20
:06
:49
UTC 2016
OSType: Linux
Architecture: x86_64
CPUs: 2
Total Memory: 1
.955
GiB
Name: tutoriel
ID: ZYFO:VNOS:UWNZ:LKHI:WC7A:D2XC:RFKD:GAMN:VF42:GI5Y:D27G:HSIK
Docker Root Dir: /mnt/sda1/var/lib/docker
Debug mode (
client): false
Debug mode (
server): true
File Descriptors: 12
Goroutines: 30
System Time: 2016
-04
-20T18:08
:33
.287063691Z
EventsListeners: 0
Registry: https://index.docker.io/v1/
Labels:
provider
=
virtualbox
Simple non ?! Nous pouvons maintenant utiliser Docker comme si on était en local.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
$
docker run -d -P xataz/nginx:1
.9
Unable to find image 'xataz/nginx:1.9'
locally
1
.9
: Pulling from xataz/nginx
420890c9e918: Pulling fs layer
49453f6fdf36: Pulling fs layer
14a932cbdb93: Pulling fs layer
179d8f2a0f72: Pulling fs layer
de957a98ee12: Pulling fs layer
4237b3506f00: Pulling fs layer
87aa5a2470bc: Pulling fs layer
e0d4bf63eb3c: Pulling fs layer
179d8f2a0f72: Waiting
de957a98ee12: Waiting
4237b3506f00: Waiting
87aa5a2470bc: Waiting
e0d4bf63eb3c: Waiting
49453f6fdf36: Download complete
420890c9e918: Verifying Checksum
420890c9e918: Pull complete
49453f6fdf36: Pull complete
14a932cbdb93: Verifying Checksum
14a932cbdb93: Download complete
14a932cbdb93: Pull complete
4237b3506f00: Verifying Checksum
4237b3506f00: Download complete
179d8f2a0f72: Verifying Checksum
179d8f2a0f72: Download complete
179d8f2a0f72: Pull complete
87aa5a2470bc: Verifying Checksum
87aa5a2470bc: Download complete
de957a98ee12: Verifying Checksum
de957a98ee12: Download complete
e0d4bf63eb3c: Verifying Checksum
e0d4bf63eb3c: Download complete
de957a98ee12: Pull complete
4237b3506f00: Pull complete
87aa5a2470bc: Pull complete
e0d4bf63eb3c: Pull complete
Digest: sha256:a04aebdf836a37c4b5de9ce32a39ba5fc2535e25c58730e1a1f6bf77ef11fe69
Status: Downloaded newer image for
xataz/nginx:1
.9
818cebd0bed38966c05730b1b0a02f3a3f48adf0aea5bf52d25da7578bdfee15
$
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
818cebd0bed3 xataz/nginx:1
.9
"tini -- /usr/bin/sta"
15
seconds ago Up 14
seconds 0
.0
.0
.0
:32769
->
8080
/tcp, 0
.0
.0
.0
:32768
->
8443
/tcp hungry_hawking
Attention, les volumes restent locaux à la machine créée, et non à la machine cliente.
XIII-C-3. Gérer nos machines▲
Maintenant que nous avons créé notre machine, il va falloir la gérer, et franchement c'est simpliste.
Commençons par l'arrêter :
2.
3.
4.
5.
6.
7.
8.
$
docker-machine stop tutoriel
Stopping "tutoriel"
...
Machine "tutoriel"
was stopped.
$
docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
mydocker - generic Running tcp://192
.168
.1
.201
:2376
v1.10
.3
tutoriel - virtualbox Stopped Unknown
Puis nous pouvons la démarrer :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
$
docker-machine start tutoriel
Starting "tutoriel"
...
(
tutoriel) Check network to re-create if
needed...
(
tutoriel) Waiting for
an IP...
Machine "tutoriel"
was started.
Waiting for
SSH to be available...
Detecting the provisioner...
Started machines may have new IP addresses. You may need to re-run the `docker-machine env`
command.
$
docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
mydocker - generic Running tcp://192
.168
.1
.201
:2376
v1.10
.3
tutoriel * virtualbox Running tcp://192
.168
.99
.100
:2376
v1.11
.0
Il aurait été plus simple de la redémarrer :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
$
docker-machine restart tutoriel
Restarting "tutoriel"
...
(
tutoriel) Check network to re-create if
needed...
(
tutoriel) Waiting for
an IP...
Waiting for
SSH to be available...
Detecting the provisioner...
Restarted machines may have new IP addresses. You may need to re-run the `docker-machine env`
command.
$
docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
mydocker - generic Running tcp://192
.168
.1
.201
:2376
v1.10
.3
tutoriel * virtualbox Running tcp://192
.168
.99
.100
:2376
v1.11
.0
Nous pouvons même mettre à jour docker :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
$
docker-machine upgrade mydocker
Waiting for
SSH to be available...
Detecting the provisioner...
Upgrading docker...
Restarting docker...
xataz@DESKTOP-2JR2J0C MINGW64 /
$
docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
mydocker - generic Running tcp://192
.168
.1
.201
:2376
v1.11
.0
tutoriel * virtualbox Running tcp://192
.168
.99
.100
:2376
v1.11
.0
Il se peut que le besoin de se connecter en ssh sur la machine se fasse, dans ce cas nous pouvons :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
$
docker-machine ssh tutoriel
## .
## ## ## ==
## ## ## ## ## ===
/"""""""""""""""""\___/ ===
~~~ {~~ ~~~~ ~~~ ~~~~ ~~~ ~ / ===- ~~~
\______ o __/
\ \ __/
\____\_______/
_ _ ____ _ _
| |__ ___ ___ | |_|___ \ __| | ___ ___| | _____ _ __
| '_ \ / _ \ / _ \| __| __) / _` |/ _ \ / __| |/ / _ \ '__|
| |_) | (_) | (_) | |_ / __/ (_| | (_) | (__| < __/ |
|_.__/ \___/ \___/ \__|_____\__,_|\___/ \___|_|\_\___|_|
Boot2Docker version 1.11.0, build HEAD : 32ee7e9 - Wed Apr 13 20:06:49 UTC 2016
Docker version 1.11.0, build 4dc5990
docker@tutoriel:~
$
Et enfin nous pouvons la supprimer :
2.
3.
4.
5.
6.
7.
8.
9.
10.
$
docker-machine stop tutoriel &&
docker-machine rm tutoriel
Stopping "tutoriel"
...
Machine "tutoriel"
was stopped.
About to remove tutoriel
Are you sure? (
y/n): y
Successfully removed tutoriel
$
docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
mydocker - generic Running tcp://192
.168
.1
.201
:2376
v1.11
.0
XIII-D. Les Plugins▲
Comme précédemment cités, nous pouvons installer des plugins, nous installerons ici celui de scaleway.
XIII-D-1. Installation▲
Normalement, lorsqu'on vous fournit un plugin pour docker, tout est indiqué dans le readme.md (ce qui est ici le cas).
Ici nous avons plusieurs solutions, soit par homebrew (OS X uniquement), soit en téléchargeant les binaires (Linux/Unix/OS X), soit en compilant les sources.
XIII-D-1-a. Avec les binaires▲
Scaleway ne fournissant plus de binaire pour Windows, ceci n'est compatible qu'avec Linux/Unix/OS X.
2.
3.
4.
$
wget https://github.com/scaleway/docker-machine-driver-scaleway/releases/download/v1.3
/docker-machine-driver-scaleway_1.3_$
(
uname -s)_$
(
uname -m).zip
$
unzip docker-machine-driver-scaleway_1.3_$
(
uname -s)_$
(
uname -m).zip
$
cp docker-machine-driver-scaleway_1.3_$
(
uname -s)_$
(
uname -m)/docker-machine-driver-scaleway /usr/local
/bin/docker-machine-driver-scaleway
$
chmod +x /usr/local
/bin/docker-machine-driver-scaleway
XIII-D-1-b. Avec homebrew▲
Homebrew étant disponible que sous OS X, ceci ne fonctionne que sur celui-ci.
2.
$
brew tap scaleway/scaleway
$
brew install scaleway/scaleway/docker-machine-driver-scaleway
XIII-D-1-c. Via les sources ▲
Si vous êtes sous Windows, pas le choix, il faudra compiler, mais avant de commencer, il faudra installer golang, ainsi que git (je passe ici les détails d'installation).
On configure golang (ici sous powershell) :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
PS C:\Users\xataz\go>
$env
:GOPATH=
"c:\Users\xataz\go"
PS C:\Users\xataz\go>
$env
:GOBIN=
"c:\Users\xataz\go\bin"
PS C:\Users\xataz\go>
$env
:PATH=
$env
:PATH+
";"
+
$env
:GOBIN
PS C:\Users\xataz\go>
go env
set GOARCH=
amd64
set GOBIN=
c:\Users\xataz\go\bin
set GOEXE=
.exe
set GOHOSTARCH=
amd64
set GOHOSTOS=
Windows
set GOOS=
Windows
set GOPATH=
c:\Users\xataz\go
set GORACE=
set GOROOT=
F:\Programs\Go
set GOTOOLDIR=
F:\Programs\Go\pkg\tool\windows_amd64
set CC=
gcc
set GOGCCFLAGS=
-m64
-mthreads
-f
message-le
ngth=
0
set CXX=
g++
set CGO_ENABLED=
1
PS C:\Users\xataz\go>
Vous pouvez ajouter les variables d'environnement via l'interface, ici elles ne seront pas persistantes, dès que vous lancerez un nouveau powershell, il faudra les recharger.
Puis on peut compiler notre driver (compter quelques minutes) :
2.
PS C:\Users\xataz\go>
go get -u
github.com/
scaleway/
docker-
machine-
driver-
scaleway
PS C:\Users\xataz\go>
Et normalement c'est bon, on teste :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
PS C:\Users\xataz\go>
docker-
machine create -d
scaleway --help
Usage: docker-
machine create [OPTIONS] [arg..
.]
Create a machine
Description:
Run 'C:\Program Files\Docker\Docker\Resources\bin\docker-machine.exe create --driver name'
to include the create flags for
that driver in
the help text.
Options:
--driver
, -d
"none"
Driver to create machine with. [$MACHINE_DRIVER
]
--engine
-
env [--engine
-
env option --engine
-
env option] Specify environment variables to set in
the engine
--engine
-
insecure-
registry [--engine
-
insecure-
registry option --engine
-
insecure-
registry option] Specify insecure registries to allow with the created engine
--engine
-
install-
url "https://get.docker.com"
Custom URL to use for
engine installation [$MACHINE_DOCKER_INSTALL_URL
]
--engine
-
label [--engine
-
label option --engine
-
label option] Specify labels for
the created engine
--engine
-
opt [--engine
-
opt option --engine
-
opt option] Specify arbitrary flags to include with the created engine in
the form flag=
value
--engine
-
registry-
mirror [--engine
-
registry-
mirror option --engine
-
registry-
mirror option] Specify registry mirrors to use [$ENGINE_REGISTRY_MIRROR
]
--engine
-
storage-
driver Specify a storage driver to use with the engine
--scaleway
-
commercial-
type "VC1S"
Specifies the commercial type [$SCALEWAY_COMMERCIAL_TYPE
]
--scaleway
-
debug Enables Scaleway client debugging [$SCALEWAY_DEBUG
]
--scaleway
-
image "ubuntu-xenial"
Specifies the image [$SCALEWAY_IMAGE
]
--scaleway
-
ip Specifies the IP address [$SCALEWAY_IP
]
--scaleway
-
ipv6 Enable ipv6 [$SCALEWAY_IPV6
]
--scaleway
-
name Assign a name [$SCALEWAY_NAME
]
--scaleway
-or
ganization Scaleway organization [$SCALEWAY_ORGANIZATION
]
--scaleway
-
port "22"
Specifies SSH port [$SCALEWAY_PORT
]
--scaleway
-
region "par1"
Specifies the location (
par1,ams1) [$SCALEWAY_REGION
]
--scaleway
-
token Scaleway token [$SCALEWAY_TOKEN
]
--scaleway
-
user "root"
Specifies SSH user name [$SCALEWAY_USER
]
--scaleway
-
volumes Attach additional volume (
e.g., 50G) [$SCALEWAY_VOLUMES
]
--swarm
Configure Machine to join a Swarm cluster
--swarm
-
addr addr to advertise for
Swarm (
default
: detect and use the machine IP)
--swarm
-
discovery Discovery service to use with Swarm
--swarm
-
experimental Enable Swarm experimental features
--swarm
-
host "tcp://0.0.0.0:3376"
ip/
socket to listen on for
Swarm master
--swarm
-
image "swarm:latest"
Specify Docker image to use for
Swarm [$MACHINE_SWARM_IMAGE
]
--swarm
-
join-
opt [--swarm
-
join-
opt option --swarm
-
join-
opt option] Define arbitrary flags for
Swarm join
--swarm
-
master Configure Machine to be a Swarm master
--swarm
-
opt [--swarm
-
opt option --swarm
-
opt option] Define arbitrary flags for
Swarm master
--swarm
-
strategy "spread"
Define a default
scheduling strategy for
Swarm
--tls
-
san [--tls
-
san option --tls
-
san option] Support extra SANs for
TLS certs
XIII-D-2. Utilisation▲
L'utilisation n'est pas si différente que sous virtualbox, mais nous aurons des options obligatoires :
2.
--scaleway-organization Scaleway organization [$SCALEWAY_ORGANIZATION
]
--scaleway-token Scaleway token [$SCALEWAY_TOKEN
]
Puis d'autres possibilités, toutes celles commençant par --scaleway en fait.
On commence par récupérer l'organization et le token, tout se passe sur le site de scaleway :
L'organization est l'access key entouré. Pour le token, il faudra le générer en cliquant sur Create new token
Puis on crée notre première machine :
2.
3.
4.
5.
6.
7.
PS C:\Users\xataz\go>
docker-
machine create -d
scaleway --scaleway
-
token "montoken"
--scaleway
-or
ganization "monaccesskey"
test
Running pre-
create checks..
.
Creating machine..
.
(
test) Creating SSH key..
.
(
test) Creating server..
.
(
test) Starting server..
.
Waiting for
machine to be running, this may take a few minutes..
.
Pendant ce temps, vous pouvez vérifier sur votre dashboard scaleway, vous verrez que votre machine est en cours de création.
Et on voit que ça se termine :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
Detecting operating system of created instance..
.
Waiting for
SSH to be available..
.
Detecting the provisioner..
.
Provisioning with ubuntu
(
systemd)..
.
Installing Docker..
.
Copying certs to the local machine directory..
.
Copying certs to the remote machine..
.
Setting Docker configuration on the remote daemon..
.
Checking connection to Docker..
.
Docker is up and running!
To see how to connect your Docker Client to the Docker Engine running on this virtual machine, run: C:\Program Files\Docker\Docker\Resources\bin\docker-
machine.exe env test
On teste :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
PS C:\Users\xataz\go>
docker-
machine env test | Invoke-
Expression
PS C:\Users\xataz\go>
docker version
Client:
Version: 1
.12
.3
API version: 1
.24
Go version: go1.6
.3
Git commit: 6b644ec
Built: Wed Oct 26
23
:26
:11
2016
OS/
Arch: windows/
amd64
Server:
Version: 1
.12
.3
API version: 1
.24
Go version: go1.6
.3
Git commit: 6b644ec
Built: Wed Oct 26
22
:01
:48
2016
OS/
Arch: linux/
amd64
PS C:\Users\xataz\go>
docker info
Containers: 0
Running: 0
Paused: 0
Stopped: 0
Images: 0
Server Version: 1
.12
.3
Storage Driver: aufs
Root Dir: /
var/
lib/
docker/
aufs
Backing Filesystem: extfs
Dirs: 0
Dirperm1 Supported: true
Logging Driver: json-f
ile
Cgroup Driver: cgroupfs
Plugins:
Volume: local
Network: null bridge host overlay
Swarm: inactive
Runtimes: runc
Default
Runtime: runc
Security Options: seccomp
Kernel Version: 4
.8
.3
-
docker-
1
Operating System: Ubuntu 16
.04
LTS
OSType: Linux
Architecture: x86_64
CPUs: 2
Total Memory: 1
.954
GiB
Name: test
ID: SPZ4:P4AY:2S6V:LYGF:4QCK:SODF:JA7S:M6MH:4K2Y:OKAO:X2XO:DVXC
Docker Root Dir: /
var/
lib/
docker
Debug Mode (
client): false
Debug Mode (
server): false
Registry: https://
index.docker.io/
v1/
Labels:
provider=
scaleway
(
VC1S)
Insecure Registries:
127
.0
.0
.0
/
8
Nous pouvons créer des environnements de variables encore une fois, ce qui nous simplifiera la tâche et de ne pas copier toujours le token et l'access key
2.
PS C:\Users\xataz\go>
$env
:SCALEWAY_ORGANIZATION=
"ton access key"
PS C:\Users\xataz\go>
$env
:SCALEWAY_TOKEN=
"ton token"
Petite astuce hors sujet : vous pouvez ajouter une variable d'environnement permanente en powershell, comme ceci :
"[Environment]::SetEnvironmentVariable("
SCALEWAY_ORGANIZATION", "
ton access key", "
User")
Il vous faudra par contre, relancer votre console powershell.
On recrée une machine sans ajouter ces options :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
PS C:\Users\xataz\go>
docker-
machine create -d
scaleway test2
Running pre-
create checks..
.
Creating machine..
.
(
test2) Creating SSH key..
.
(
test2) Creating server..
.
(
test2) Starting server..
.
Waiting for
machine to be running, this may take a few minutes..
.
Detecting operating system of created instance..
.
Waiting for
SSH to be available..
.
Detecting the provisioner..
.
Provisioning with ubuntu
(
systemd)..
.
Installing Docker..
.
[..
.]
Et voilà c'est plus simple maintenant, vous pouvez ajouter toutes les options dans des variables d'environnement.
On va aller un peu plus loin, comme nous avons vu, par défaut, il crée une instance VC1S, avec ubuntu 16.04. Nous allons créer une instance V1M, sous centOS, et aux Pays-Bas.
PS C:\Users\xataz\go>
docker-
machine create -d
scaleway --scaleway
-
name docker-
centos --scaleway
-
commercial-
type "VC1M"
--scaleway
-
image "centos"
--scaleway
-
region "ams1"
docker-
centos
Sur le github du plugin, il explique même comment créer une instance ARM sous docker :
$
docker-machine create -d scaleway --scaleway-commercial-type
=
C1 --scaleway-image
=
docker --engine-install-url
=
"http://bit.ly/1sf3j8V"
arm-machine
Maintenant que nos tests sont faits, on n'oublie pas de supprimer les machines :
2.
3.
4.
5.
PS C:\Users\xataz\go>
docker-
machine rm -y
test test2 docker-
centos
About to remove test, test2, docker-
centos
Successfully removed test
Successfully removed test2
Successfully removed docker-
centos
Je vous conseille de vérifier si les machines/volumes et IP sont bien supprimés, il m'est arrivé que les IP étaient toujours présentes.
XIII-E. Conclusion▲
Nous avons vu ici comment gérer de multiples machines avec docker-machine sur un environnement VirtualBox. Mais comme nous venons de le voir, nous pouvons créer/gérer des machines sur divers hébergeurs, soit nativement comme digitalOcean ou Azure, ou via un plugin comme pour scaleway. Ceci est très rapide pour créer une nouvelle machine, et nous évite des étapes fastidieuses.
XIV. Clustering avec Swarm▲
Dans ce chapitre, nous apprendrons à créer un cluster docker. Nous utiliserons virtualbox comme provider. Cette partie n'est qu'une ébauche sur l'utilisation de swarm. Ceci ne sera pas forcément utile pour tout le monde, mais il peut être utile de comprendre le principe.
XIV-A. Qu'est-ce que Swarm ?▲
Swarm est l'outil natif de gestion de cluster docker. Il permet de gérer l'ordonnancement des tâches ainsi que l'allocation de ressources par conteneur. Pour simplifier, nous laissons swarm choisir la machine qui exécutera notre conteneur, nous voyons donc l'ensemble des hôtes Docker (appelés node) en un seul et unique hôte.
Nous créerons d'abord un cluster simple, mais tout à la main, ce qui permettra d'en comprendre le fonctionnement. Ensuite nous créerons un autre cluster avec docker-machine (plus rapide), mais plus complexe, avec une gestion réseau internode.
XIV-B. Docker swarm (docker >= 1.12.X)▲
La commande docker swarm est une nouveauté de Docker 1.12, qui permet la simplification de l'utilisation de swarm. Ceci nécessite par contre d'apprendre de nouveaux concepts et de nouvelles commandes.
Voici les nouvelles commandes :
2.
3.
$
docker swarm # Permet la gestion du cluster
$
docker service # Permet la gestion des conteneurs
$
docker node # Permet la gestion des Nodes
XIV-B-1. Créons notre cluster▲
Pour cet exemple, nous créerons trois machines, une maître, et deux nœuds (toujours avec notre boucle de fainéant) :
$
for
machine in
master node1 node2; do
docker-machine create -d virtualbox --virtualbox-memory "512"
${machine}
; done
Maintenant que nos machines sont créées, nous allons configurer swarm, on commence par le master :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
$
docker-machine ssh master
docker@master:~$
docker swarm init --advertise-addr 192
.168
.99
.105
# on utilise l'IP de la machine hôte
Swarm initialized: current node (
btb5ek9guet2ymqijqtagbv2i) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join \
--token SWMTKN-1
-2ihwqcfv3jh26q95e46gi5xp5owm3e9ggjg5aezbncio9qn21q-5gjfybsp34q2rf3y8yjajt2gl \
192
.168
.99
.105
:2377
To add a manager to this swarm, run 'docker swarm join-token manager'
and follow the instructions.
Ceci nous retourne directement la commande à exécuter sur les nodes, c'est pas magnifique ?!
2.
3.
4.
5.
$
docker-machine ssh node1
docker@node1:~$
docker swarm join \
>
--token SWMTKN-1
-2ihwqcfv3jh26q95e46gi5xp5owm3e9ggjg5aezbncio9qn21q-5gjfybsp34q2rf3y8yjajt2gl \
>
192
.168
.99
.105
:2377
This node joined a swarm as a worker.
Et on fait de même sur le node2 :
2.
3.
4.
5.
$
docker-machine ssh node2
docker@node2:~$
docker swarm join \
>
--token SWMTKN-1
-2ihwqcfv3jh26q95e46gi5xp5owm3e9ggjg5aezbncio9qn21q-5gjfybsp34q2rf3y8yjajt2gl \
>
192
.168
.99
.105
:2377
This node joined a swarm as a worker.
Et voilà notre cluster est créé, pour vérifier nous nous connecterons au master :
2.
3.
4.
5.
6.
$
docker-machine ssh master
docker@master:~$
docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
694gceuhyzshrb4l0v9kpmu71 node2 Ready Active
6k228dx8eteh3q4bgjumd0bks node1 Ready Active
btb5ek9guet2ymqijqtagbv2i * master Ready Active Leader
Nous pouvons promouvoir les nodes en manager rapidement :
2.
3.
4.
5.
6.
7.
docker@master:~$
docker node promote node2
Node node2 promoted to a manager in
the swarm.
docker@master:~$
docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
694gceuhyzshrb4l0v9kpmu71 node2 Ready Active Reachable
6k228dx8eteh3q4bgjumd0bks node1 Ready Active
btb5ek9guet2ymqijqtagbv2i * master Ready Active Leader
Le node2 est maintenant un manager, mais en slave. Nous pouvons donc maintenant contrôler nos nodes/services depuis le node2.
XIV-B-2. Les services▲
Le concept de service est nouveau, un service est un conteneur scalable, je peux par exemple créer un service nginx avec cinq conteneurs, qui sera ensuite disponible via une VIP (virtual IP), qui permet de faire un HAproxy.
Toujours connecté au serveur master, nous allons créer notre premier service :
2.
docker@master:~$
docker service create --replicas 1
--name web -p 80
:8080
xataz/nginx:mainline
cwrfie1hxn8gb2rle8jy6sapx
vérifions si notre service tourne :
2.
3.
4.
5.
6.
docker@master:~$
docker service ls
ID NAME REPLICAS IMAGE COMMAND
cwrfie1hxn8g web 1
/1
xataz/nginx:mainline
docker@master:~$
docker service ps web
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR
7b2osbdqc3sg84mixgzxip1fk web.1
xataz/nginx:mainline node1 Running Running 12
seconds ago
Nous voyons ici, avec docker service ls que nous avons un service qui s'appelle web. Puis dans ce service web, avec docker service ps web, nous voyons notre conteneur qui tourne sur le node1.
Nous pouvons le multiplier :
2.
docker@master:~$
docker service scale web
=
5
web scaled to 5
Puis vérifions :
2.
3.
4.
5.
6.
7.
8.
9.
10.
docker@master:~$
docker service ls
ID NAME REPLICAS IMAGE COMMAND
cwrfie1hxn8g web 5
/5
xataz/nginx:mainline
docker@master:~$
docker service ps web
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR
7b2osbdqc3sg84mixgzxip1fk web.1
xataz/nginx:mainline node1 Running Running 5
minutes ago
cwpw2g0mw5gqnds0mvdem5gyx web.2
xataz/nginx:mainline master Running Running 26
seconds ago
2y4mhbc6liaohkls5io76vwbu web.3
xataz/nginx:mainline node2 Running Running 26
seconds ago
f01kh9kn8pprj528mnl3xnzj1 web.4
xataz/nginx:mainline node2 Running Running 26
seconds ago
avn8ukb1jt9zd7ct462h07e0l web.5
xataz/nginx:mainline node1 Running Running 26
seconds ago
Nous voyons maintenant, à l'aide de docker service ls, que le service web possède cinq réplicats, et qu'ils tournent tous. Puis avec docker service ps web, nous voyons les cinq conteneurs tourner.
Ceci était simplement pour l'exemple, nous allons aller un peu plus loin, nous commençons par supprimer le service :
2.
3.
4.
docker@master:~$
docker service rm web
web
docker@master:~$
docker service ls
ID NAME REPLICAS IMAGE COMMAND
On va relancer trois conteneurs nginx, mais en redirigeant le port 80 vers le 8080 :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
$
docker service create --name web --replicas 5
-p 80
:8080
xataz/nginx:mainline
9emtiwgv4jtvpzj16oxg6h7og
$
docker service ls
ID NAME REPLICAS IMAGE COMMAND
9emtiwgv4jtv web 5
/5
xataz/nginx:mainline
$
docker service ps web
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR
4ptfdheqs3yrocevde8wbsk7n web.1
xataz/nginx:mainline node1 Running Running 11
seconds ago
9b8e8s5cmku9vk60s5tk2f7o0 web.2
xataz/nginx:mainline master Running Running 10
seconds ago
8fty69tnwcg8szoo01ffaf0nc web.3
xataz/nginx:mainline node2 Running Running 10
seconds ago
6l922h3ih828cyec65sru89ss web.4
xataz/nginx:mainline node2 Running Running 10
seconds ago
blk42isuztm35ou3z56npzf67 web.5
xataz/nginx:mainline master Running Running 10
seconds ago
Comment est-ce possible de bind plusieurs fois le port 80 ? En fait, docker service est beaucoup plus intelligent, il a en fait créé une VIP (Virtual IP) par node pour le service web, et c'est sur cette IP qu'il bind le port 80. Celle-ci redirige à tour de rôle vers un conteneur ou un autre.
Pour tester rapidement :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
i
=
0; while
[ $i
-lt 10
]; do
curl http://192
.168
.99
.105
2
>
/dev/null |
grep h1; sleep 1; i
=
$
((
$i
+1
)); done
<
h1>
Nginx 1
.11
.3
on 1e5f3f6f3375<
/h1>
<
h1>
Nginx 1
.11
.3
on 78792c3765a5<
/h1>
<
h1>
Nginx 1
.11
.3
on e35b7b05243d<
/h1>
<
h1>
Nginx 1
.11
.3
on 49df7c284fb4<
/h1>
<
h1>
Nginx 1
.11
.3
on f638593093a3<
/h1>
<
h1>
Nginx 1
.11
.3
on 1e5f3f6f3375<
/h1>
<
h1>
Nginx 1
.11
.3
on 78792c3765a5<
/h1>
<
h1>
Nginx 1
.11
.3
on e35b7b05243d<
/h1>
<
h1>
Nginx 1
.11
.3
on 49df7c284fb4<
/h1>
<
h1>
Nginx 1
.11
.3
on f638593093a3<
/h1>
Comme on peut le voir, il tourne entre les conteneurs. Et cela fonctionne également avec l'IP d'un node :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
i
=
0; while
[ $i
-lt 10
]; do
curl http://192
.168
.99
.106
2
>
/dev/null |
grep h1; sleep 1; i
=
$
((
$i
+1
)); done
<
h1>
Nginx 1
.11
.3
on 1e5f3f6f3375<
/h1>
<
h1>
Nginx 1
.11
.3
on 78792c3765a5<
/h1>
<
h1>
Nginx 1
.11
.3
on e35b7b05243d<
/h1>
<
h1>
Nginx 1
.11
.3
on 49df7c284fb4<
/h1>
<
h1>
Nginx 1
.11
.3
on f638593093a3<
/h1>
<
h1>
Nginx 1
.11
.3
on 1e5f3f6f3375<
/h1>
<
h1>
Nginx 1
.11
.3
on 78792c3765a5<
/h1>
<
h1>
Nginx 1
.11
.3
on e35b7b05243d<
/h1>
<
h1>
Nginx 1
.11
.3
on 49df7c284fb4<
/h1>
<
h1>
Nginx 1
.11
.3
on f638593093a3<
/h1>
Comme vous pouvez le voir, c'est vraiment simple de faire un cluster avec cette nouvelle fonction.
XIV-C. Conclusion▲
Nous avons vu ici comment créer un cluster swarm, mais il existe d'autres outils pour faire ceci, comme rancher, ou kubernetes.
Nous pourrions également aller beaucoup plus loin, en utilisant par exemple des outils de clustering de fs, comme ceph ou glusterfs, mais ceci serait hors sujet.
XV. Registry▲
Nous allons ici créer notre propre registry, c'est-à-dire un hub (sans webUI) autohébergé. Ce sera un petit chapitre.
XV-A. Installation de registry▲
L'installation est simple, il suffit de lancer un conteneur :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
$
docker run -d -p 5000
:5000
-v /data/registry:/var/lib/registry --name registry registry:2
Unable to find image 'registry:2'
locally
2
: Pulling from library/registry
e110a4a17941: Already exists
2ee5ed28ffa7: Pulling fs layer
d1562c23a8aa: Pulling fs layer
06ba8e23299f: Pulling fs layer
802d2a9c64e8: Pulling fs layer
802d2a9c64e8: Waiting
06ba8e23299f: Download complete
2ee5ed28ffa7: Download complete
2ee5ed28ffa7: Pull complete
d1562c23a8aa: Verifying Checksum
d1562c23a8aa: Download complete
802d2a9c64e8: Download complete
d1562c23a8aa: Pull complete
06ba8e23299f: Pull complete
802d2a9c64e8: Pull complete
Digest: sha256:1b68f0d54837c356e353efb04472bc0c9a60ae1c8178c9ce076b01d2930bcc5d
Status: Downloaded newer image for
registry:2
e24f1ad2a82396361d74b59152baff0e4fa3f67cd743450da238a76e9142f02a
Et voilà c'est installé, il écoute le port 5000 sur localhost (ou IP conteneur:5000).
XV-B. Utilisation▲
L'utilisation est plutôt simple, tout va se jouer avec le nom de repository, où habituellement on met notre nom d'utilisateur. Nous allons donc renommer notre image lutim en localhost:5000 :
$
docker tag xataz/lutim localhost:5000
/lutim
On peut maintenant la push sur notre registry :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
$
docker push localhost:5000
/lutim
The push refers to a repository [localhost:5000
/lutim]
d581c9307ce6: Preparing
232aeb6486cf: Preparing
07bd11c49c2e: Preparing
7d887264d1fb: Preparing
92abf950c77c: Preparing
2f71b45e4e25: Preparing
2f71b45e4e25: Waiting
232aeb6486cf: Pushed
07bd11c49c2e: Pushed
d581c9307ce6: Pushed
92abf950c77c: Pushed
7d887264d1fb: Pushed
2f71b45e4e25: Pushed
latest: digest: sha256:313f64018302aa8c3fdef7baa308c3436b067ace706067c0a0e7737bd563acd6 size: 1573
Pour tester notre registry, nous allons supprimer l'image en local (l'original et la retagguée), et la retélécharger :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
$
docker rmi localhost:5000
/lutim xataz/lutim &&
docker pull localhost:5000
/lutim
Untagged: localhost:5000
/lutim:latest
Untagged: localhost:5000
/lutim@sha256:313f64018302aa8c3fdef7baa308c3436b067ace706067c0a0e7737bd563acd6
Untagged: xataz/lutim:latest
Untagged: xataz/lutim@sha256:313f64018302aa8c3fdef7baa308c3436b067ace706067c0a0e7737bd563acd6
Deleted: sha256:b22de6f27e376ce9d875cd647d0d7aca894e5ea0f2071eb83763d14092410188
Deleted: sha256:870865d9861d1ebb21012d5d3fa04c453fdd6a254623024e6522c2e76bc5db8e
Deleted: sha256:fed8ca9580ad53059c3dd107b45f991b87538260cdc4ab4fd44abae5631f6701
Deleted: sha256:b6622b6004255e1523dc17f3f7a5960b95ba51503838b4c7e19eff447c483d7e
Deleted: sha256:1d9f78dac3a778b636d02e64db2569de7e54464a50037be90facea3838390808
Deleted: sha256:be9cfba7137b53173b10101ce96fa17a3bedfb13cf0001a69096dee3a70b37be
Using default tag: latest
latest: Pulling from lutim
357ea8c3d80b: Already exists
c9a3b87a9863: Pulling fs layer
c828912554c6: Pulling fs layer
a0ec173a645d: Pulling fs layer
7848baf27247: Pulling fs layer
5b2cd2a8ffca: Pulling fs layer
7848baf27247: Waiting
5b2cd2a8ffca: Waiting
a0ec173a645d: Verifying Checksum
a0ec173a645d: Download complete
7848baf27247: Verifying Checksum
7848baf27247: Download complete
5b2cd2a8ffca: Verifying Checksum
5b2cd2a8ffca: Download complete
c9a3b87a9863: Verifying Checksum
c9a3b87a9863: Download complete
c828912554c6: Verifying Checksum
c828912554c6: Download complete
c9a3b87a9863: Pull complete
c828912554c6: Pull complete
a0ec173a645d: Pull complete
7848baf27247: Pull complete
5b2cd2a8ffca: Pull complete
Digest: sha256:313f64018302aa8c3fdef7baa308c3436b067ace706067c0a0e7737bd563acd6
Status: Downloaded newer image for
localhost:5000
/lutim:latest
XV-C. Conclusion▲
Ceci n'est qu'une ébauche, afin de vous montrer simplement que ceci est possible, si vous souhaitez plus d'informations, vous pouvez consulter la documentation.
XVI. Bonus▲
Dans cette partie que je nomme bonus, je mettrai quelques astuces, ou des trucs ^^. En fait il y aura un peu de tout et de rien en rapport avec docker, mais qui ne rentre dans aucune des autres parties.
XVI-A. L'autocomplétion▲
Ceci vous permettra d'avoir la liste des commandes qui s'affichent lorsque vous appuyez sur tab. exemple :
2.
3.
4.
$
docker
attach cp diff export images inspect login network ps rename rmi search stop unpause wait
build create events help import kill logout pause pull restart run start tag version
commit daemon exec history info load logs port push rm save stats top volume
On installera donc l'autocomplétion pour docker et docker-compose.
En premier, et pour pas se faire avoir comme moi et passer trois heures à chercher le problème, il faut installer bash-completion :
$
apt-get install bash-completion
Ensuite on télécharge les fichiers d'autocomplétion (en root) :
2.
$
curl -L https://raw.githubusercontent.com/docker/docker-ce/master/components/cli/contrib/completion/bash/docker >
/etc/bash_completion.d/docker
$
wget -O /etc/bash_completion.d/docker-compose https://raw.githubusercontent.com/docker/compose/1
.14
.0
/contrib/completion/bash/docker-compose
Attention à votre version de docker-compose, version 1.14 à l'heure où j'écris ces lignes.
Et on ajoute dans le .profile de notre utilisateur :
$
echo ". /etc/bash_completion"
>>
~/.profile
Pour finir on source notre profile :
$
source ~/.profile
Enjoy :
2.
3.
4.
5.
$
docker r
rename restart rm rmi run
$
docker-compose
build kill migrate-to-labels ps restart run start up
help logs port pull rm scale stop version
Et voilà le travail !!
XVI-B. Docker avec btrfs▲
Comme je vous en ai parlé, Docker fonctionne par défaut sur aufs. Mais il est possible de le faire fonctionner avec btrfs, device-mapper, zfs ou même overlay. Le principe est le même pour tous, mais nous verrons ici comment le configurer pour btrfs.
Mais pourquoi utiliser btrfs à la place de aufs ?! Pour plusieurs raisons, la première est que les images basées sur redhat (centOS, fedora etc.) n'aiment pas trop aufs, et il y a des fois des paquets qui refusent de s'installer, par exemple httpd. Donc il fallait choisir un autre FS, device-mapper, je n'aime pas trop le principe, en gros chaque layer est un disque virtuel (en gros), donc difficile de faire des modifs directement dedans. ZFS est plutôt gourmand, et pas fan de zfs sur Linux (peut-être à tort). Overlay je ne connais pas, mais apparemment c'est ce qu'il y a de plus rapide. Donc je choisis btrfs.
Pour cette partie, il vous faudra une partition vide, de mon côté, j'ai configuré le partitionnement de mon serveur avec lvm, et j'ai préparé une partition de 20 Go qui s'appelle /dev/vg1/docker.
Si vous avez déjà des conteneurs et des images, vous perdrez tout.
Toutes ces actions sont à exécuter en root.
On commence par installer btrfs :
$
apt-get install btrfs-tools
On arrête docker :
$
systemctl stop docker.service
On formate notre partition :
$
mkfs.btrfs /dev/vg1/docker
Puis on monte la partition (on le rajoute évidemment dans le fstab) :
$
mount /dev/vg1/docker /var/lib/docker
Maintenant, on édite le fichier default de docker :
2.
3.
$
vim /,etc./default/docker
DOCKER_OPTS
=
-s btrfs
Et pour finir on relance docker :
$
systemctl start docker
Si on vérifie dans le répertoire de docker, normalement vous avez ceci :
2.
$
ls /var/lib/docker/
btrfs containers graph linkgraph.db repositories-btrfs tmp trust volumes
XVII. Conclusion▲
Normalement, si j'ai bien fait mon travail, vous devriez être totalement capable d'utiliser docker. Il est simple de créer un environnement, de le multiplier, de le partager, de le modifier, sans perte de performance.
En commençant l'écriture de ce tutoriel, je ne pensais pas qu'il serait aussi difficile et long d'expliquer l'utilisation de docker. Mais malgré cette taille énorme (sans sous-entendu), nous n'avons fait qu'effleurer ses possibilités, qui sont grandement suffisantes pour une utilisation personnelle.
Je finirai en beauté, avec cette image trouvée sur un article de wanadev sur docker, qui représente bien la mise en prod avec docker :
XVIII. Ressources▲
- Documentation Docker (en) : https://docs.docker.com/
- Docker commands reference (en) : https://docs.docker.com/engine/reference/commandline/
- Chaîne officielle docker (en) : https://www.youtube.com/user/dockerrun
- Chaîne YouTube « Quoi de neuf docker » (fr) : https://www.youtube.com/channel/UCOAhkxpryr_BKybt9wIw-NQ
- ImageLayers pour voir la composition d'une image : https://imagelayers.io
- Plateforme d'apprentissage (en) : https://www.katacoda.com
Quelques github avec des images sympa :
- Les dockerfiles de Wonderfall : Plein de bonnes choses
- Les repos d'Hardware : avec notamment mailserver et nsd
- Les dockerfiles de jfrazelle : Une nana qui fait des images hallucinantes
- Mes dockerfiles : Le meilleur (autocongrat)
XIX. Note de la rédaction de Developpez.com ▲
Nous tenons à remercie Winjerome pour la mise au gabarit Developpez.com et Claude Leloup pour la relecture orthographique.