Utilisation de Docker

Changelog :
- 2016-12-05 : Ajout d'explication 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 : Maj du tuto vers docker 1.12.X - Maj 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'information 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 : Maj 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'auto complétion
- 2015-10-17 : Version initiale

Todolist :
- Suppression de docker swarm via des conteneurs (1.12 only)
- Ajout d'une partie sur glusterfs (en version docker)
- Ajout partie plugin pour docker volume (netshare)

Sommaire

  1. Sommaire
  2. Introduction
    • Qu'est-ce que Docker (pour notre ami wikipedia) ?
    • Conteneurs VS machines virtuelles
    • Docker, pour quoi faire ?!
  3. Installation
    • Installation sous Linux
    • Installation sous Windows
      • Docker4Windows
      • Docker-toolbox
      • Installation manuelle
    • Installation sous Mac
      • Docker4Mac
      • Docker-toolbox
    • Mon environnement de test
  4. Les commandes de base
  5. Le DockerHub
    • Qu'est-ce que le docker hub ?
    • Chercher une image
  6. Gérer les images
    • Télécharger des images
    • Lister les images
    • Supprimer les images
    • Conclusion
  7. Gérer les conteneurs
    • Lancer, arrêter et lister des conteneurs
    • Voir les logs des conteneurs
    • Supprimer les conteneurs
    • Cas concrets
      • Premier cas : Le développeur
      • Deuxième cas : Installer une application
      • Troisième cas : Le déploiement
    • Conclusion
  8. Créer une image
    • Création d'un Dockerfile
      • Créons une image apache
      • Exemple d'une image lutim
      • Créons une image de base
    • Les bonnes pratiques
      • Limiter les layers
      • Limiter la taille d'une image
      • La lisibilité
      • Éviter les processus root
    • Conclusion
  9. Déployer/partager une image
    • Via un dockerfile
    • Via le docker hub
    • Via une image tar
    • Conclusion
  10. Limiter les ressources d'un conteneur
    • La mémoire
    • Le CPU
    • L'écriture disque
    • Conclusion
  11. Docker volume
    • Création d'un volume simple
    • Un peu plus loin
    • Encore et toujours plus loin avec les plugins
      • Prérequis
      • Installation du plugin
      • Utilisation
    • Conclusion
  12. Docker network
    • Les types de réseaux
    • Création d'un network
    • Utilisation des networks
    • Conclusion
  13. Docker compose
    • Installation
      • Sous Windows
      • Sous GNU/Linux
      • Sous MacOS
    • Utilisation de docker-compose
    • Créer une stack web
    • Conclusion
  14. Docker machine
    • Qu'est-ce que docker-machine ?
    • Installation
      • Sous Windows
      • Sous GNU/Linux et OS X
    • Utilisation
      • Créer une machine sous virtualbox
    • Les Plugins
      • Installation
      • Utilisation
    • Conclusion
  15. Clustering avec Swarm
    • Qu'est-ce que Swarm ?
    • Création de notre cluster
      • Création des machines
      • Configuration de master
      • Configuration des nodes
      • Utilisation de swarm
    • Plus loin avec overlay network
    • Docker swarm (docker >= 1.12.X)
      • Créons notre cluster
      • Les services
    • Conclusion
  16. Registry
    • Installation de registry
    • Utilisation
    • Conclusion
  17. Bonus
    • L'auto Completion
    • Docker avec btrfs
  18. Conclusion
  19. Ressources

Contribution

Toute contribution est la bienvenue.
N'hésitez pas à contribuer aux Tutoriels, ajout d'information, correction de fautes (et il y en a), amélioration etc ...
Ça se passe ici

Questions

Toute question sur la discussion ou sur github

un mois plus tard

Introduction

Tout le monde à 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ées 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 serai 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.

Qu'est-ce que Docker (pour notre ami wikipedia) ?

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.

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, a 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 librairies et les binaires nécessaires. Le noyau quand à 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 un conteneur par VM.
Le plus gros défaut des conteneurs, c'est le fait que ce n'est pas cross-platfrom. On lance des conteneurs Linux sous Linux, des conteneurs BSD sous BSD ou des conteneurs Windows sous Windows (cela arrive avec windows 2016).

Voici une image illustrant la différence entre VM et Docker :

Docker, pour quoi faire ?!

Docker n'a pas pour vocation à 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 vous 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 version de php, puis plusieurs version 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 owncloud fonctionnel.

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 à aucun moyen de l'utiliser sur Windows ou Mac.

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 :

$ 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 :

$ echo "deb https://apt.dockerproject.org/repo debian-jessie main" > /etc/apt/sources.list.d/docker.list
$ apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D

Puis on installe :

$ apt-get update && apt-get install docker-engine

Il existe plusieurs versions de docker dans ce dépôt, vous pouvez choisir celle-ci :

$ apt-cache policy docker-engine
$ apt-get install docker-engine=1.10.1

Vous pouvez également ajouter les dépôts testing (release candidate) ou experimental (build git) :

$ echo "deb https://apt.dockerproject.org/repo debian-jessie testing" > /etc/apt/sources.list.d/docker.list

Il ne nous reste plus qu'à lancer docker :

$ 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 regénère notre grub :

grub-mkconfig -o /boot/grub/grub.cfg
Création du fichier de configuration GRUB…
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 :

$ 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/

Installation sous Windows

Sous windows il existe 3 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 beta, seulement compatible à partir de windows 10 Pro)

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-tools 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.

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, les clients 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.

Installation manuelle

Puisqu'on est là pour apprendre, je vais vous montrer une méthode manuelle.
Je vais donc vous expliquer comment faire pour l'installer sur windows, et vous allez voir, c'est pas compliqué.
Nous allons donc faire la même installation que docker-toolbox.

Premièrement, il faut une machine virtuelle, utilisez la partie précédente pour vous en créer une.
Personnellement j'utilise hyper-v, mais vous pouvez utiliser virtualbox ou vmware.

Une fois votre machine virtuelle configurée, nous avons quelques spécificités à configurer :
Nous allons commencer par configurer correctement le daemon de notre machine docker serveur :

vim /etc/default/docker

puis on écrit ceci :

# /etc/default/docker
DOCKER_OPTS="-H tcp://0.0.0.0:4242 -H unix:///var/run/docker.sock"

En gros on dit à docker d'écouter également le port 4242 pour une utilisation à distance, en plus du socket pour une utilisation en local (via ssh par exemple).
On relance bien sûr le daemon :

systemctl restart docker.service

Maintenant on passe à l'installation du client docker :
On télécharge d'abord l'exécutable sur ce [lien]https://get.docker.com/builds/Windows/x86_64/docker-latest.zip)
On crée un répertoire dans le C: qui s'appellera docker puis un autre dossier dedans qui s'appellera bin, puis on copie le .exe dans c:\docker\bin.

On peut désormais tester, on ouvre une console (touche windows+R puis cmd), il faut se positionner dans le C:\docker\bin, puis on teste la connexion (penser à changer l'IP par celle de votre serveur docker) :

C:\docker>docker.exe -H tcp://192.168.1.16:4242 version
Client:
 Version:      1.12.0
 API version:  1.24
 Go version:   go1.6.3
 Git commit:   8eab29e
 Built:        Thu Jul 28 21:04:48 2016
 OS/Arch:      windows/amd64
 Experimental: true

Server:
 Version:      1.12.0
 API version:  1.24
 Go version:   go1.6.3
 Git commit:   8eab29e
 Built:        Thu Jul 28 21:15:28 2016
 OS/Arch:      linux/amd64

Le -H permet de dire sur quelle machine on se connecte.

Pour le moment c'est un peu contraignant de devoir se mettre dans le C:\docker et devoir faire un -H tcp://192.168.1.16:4242 à chaque fois, mais on va régler ceci. Pour cela il va falloir jouer avec les variables d'environnement.
Pour ce faire :
Clique droit sur "Ordinateur" (ou "Ce PC") => Propriétés => Paramètres système avancés => Variables d'environnement

Et on ajoute une variable utilisateur comme ceci :

On modifie également la variable système Path et on ajoute à la fin :
;c:\docker\bin
il faut faire attention, il ne faut rien supprimer, mais ajouter à la fin, sans oublier le ; pour séparer la nouvelle variable.

Maintenant on peut refaire un essai sans se positionner dans le bon dossier :

D:\Users\xataz>docker version
Client:
 Version:      1.12.0
 API version:  1.24
 Go version:   go1.6.3
 Git commit:   8eab29e
 Built:        Thu Jul 28 21:04:48 2016
 OS/Arch:      windows/amd64
 Experimental: true

Server:
 Version:      1.12.0
 API version:  1.24
 Go version:   go1.6.3
 Git commit:   8eab29e
 Built:        Thu Jul 28 21:15:28 2016
 OS/Arch:      linux/amd64

\o/ ça marche !!!

Nous allons rajouter une petite chose pour que l'environnement soit parfait, nous allons créer un partage entre notre VM et notre windows.

Partie sous windows :

Pour ce faire, créez un dossier Docker dans un endroit stratégique, personnellement j'ai choisi D:\Docker.
Puis clic droit sur ce dossier -> propriétés -> onglet "Partage" -> cliquez sur "Partager..." -> Puis cliquez sur "Partager".

Il vous faudra par contre absolument un mot de passe à votre compte windows.

Partie sous la VM :

On crée également un répertoire stratégique, personnellement j'ai créé un répertoire Docker à la racine :

$ mkdir /Docker

Il faut simplement éditer le fstab en ajoutant ceci :

//192.168.1.11/Docker /Docker cifs defaults,uid=xataz,gid=docker,file_mode=0777,dir_mode=0777,username=userwindows,password=mdpwindows 0 0

N'oubliez pas de remplacer l'uid par votre user linux, et username,password par ceux de windows. 192.168.1.11 étant l'ip de mon windows.

Et on monte le partage :

$ root@boot2docker:~# mount -a

Malgré qu'il soit possible de tout faire via le docker installé sur windows, je conseille de l'utiliser via une console ssh, pour la simple et bonne raison que l'affichage est adapté au terminal Linux.

Installation sous Mac

Il y a deux solutions pour installer docker sur Mac OS X :
Docker4Mac
Docker-toolbox

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.

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

Nous avons ici utiliser docker-machine pour créer notre machine, nous verrons dans un autre chapitre comment l'utiliser.

Et pour finir on teste si ça fonctionne :

$ 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/

Mon environnement de test

Je suis sous windows 10, avec docker4windows, donc ma VM sous hyper-v. Je l'utilise le plus souvent sous powershell, mais il m'arrive de l'utiliser sous bash.

Version de docker :

PS C:\> docker version
Client:
 Version:      1.12.0
 API version:  1.24
 Go version:   go1.6.3
 Git commit:   8eab29e
 Built:        Thu Jul 28 21:04:48 2016
 OS/Arch:      windows/amd64
 Experimental: true

Server:
 Version:      1.12.0
 API version:  1.24
 Go version:   go1.6.3
 Git commit:   8eab29e
 Built:        Thu Jul 28 21:15:28 2016
 OS/Arch:      linux/amd64

Les commandes de base

Je vais ici vous expliquer les commandes de base de docker, c'est-à-dire celles que nous utiliserons très régulièrement.
Affiche les commandes disponibles :

$ docker --help
Usage: docker [OPTIONS] COMMAND [arg...]
       docker [ --help | -v | --version ]

A self-sufficient runtime for containers.

Options:

  --config=%USERPROFILE%\.docker              Location of client config files
  -D, --debug                                 Enable debug mode
  -H, --host=[]                               Daemon socket(s) to connect to
  -h, --help                                  Print usage
  -l, --log-level=info                        Set the logging level
  --tls                                       Use TLS; implied by --tlsverify
  --tlscacert=%USERPROFILE%\.docker\ca.pem    Trust certs signed only by this CA
  --tlscert=%USERPROFILE%\.docker\cert.pem    Path to TLS certificate file
  --tlskey=%USERPROFILE%\.docker\key.pem      Path to TLS key file
  --tlsverify                                 Use TLS and verify the remote
  -v, --version                               Print version information and quit

Commands:
    attach    Attach to a running container
    build     Build an image from a Dockerfile
    commit    Create a new image from a container's changes
    cp        Copy files/folders between a container and the local filesystem
    create    Create a new container
    deploy    Create and update a stack from a Distributed Application Bundle (DAB)
    diff      Inspect changes on a container's filesystem
    events    Get real time events from the server
    exec      Run a command in a running container
    export    Export a container's filesystem as a tar archive
    history   Show the history of an image
    images    List images
    import    Import the contents from a tarball to create a filesystem image
    info      Display system-wide information
    inspect   Return low-level information on a container, image or task
    kill      Kill one or more running container
    load      Load an image from a tar archive or STDIN
    login     Log in to a Docker registry.
    logout    Log out from a Docker registry.
    logs      Fetch the logs of a container
    network   Manage Docker networks
    node      Manage Docker Swarm nodes
    pause     Pause all processes within one or more containers
    plugin    Manage Docker plugins
    port      List port mappings or a specific mapping for the container
    ps        List containers
    pull      Pull an image or a repository from a registry
    push      Push an image or a repository to a registry
    rename    Rename a container
    restart   Restart a container
    rm        Remove one or more containers
    rmi       Remove one or more images
    run       Run a command in a new container
    save      Save one or more images to a tar archive (streamed to STDOUT by default)
    search    Search the Docker Hub for images
    service   Manage Docker services
    stack     Manage Docker stacks
    start     Start one or more stopped containers
    stats     Display a live stream of container(s) resource usage statistics
    stop      Stop one or more running containers
    swarm     Manage Docker Swarm
    tag       Tag an image into a repository
    top       Display the running processes of a container
    unpause   Unpause all processes within one or more containers
    update    Update configuration of one or more containers
    version   Show the Docker version information
    volume    Manage Docker volumes
    wait      Block until a container stops, then print its exit code

Run 'docker COMMAND --help' for more information on a command.

Affiche les paramètres d'une commande :

$ docker [command] --help

Exemple :

$ docker search --help

Usage:  docker search [OPTIONS] TERM

Search the Docker Hub for images

  --automated=false    Only show automated builds
  --help=false         Print usage
  --no-trunc=false     Don't truncate output
  -s, --stars=0        Only displays with at least x stars

Voici les commandes principales que nous utiliserons :

# Cherche une image sur le dockerhub (que j'explique dans la partie suivante) :
docker search

# Télécharge une image depuis le dockerhub :
docker pull

# Envoie une image sur le dockerhub :
docker push

# Liste les images disponibles :
docker images

# Supprime une image :
docker rmi

# Crée un conteneur :
docker run

# Éteint un conteneur :
docker stop

# Liste les conteneurs démarrés :
docker ps

# Affiche les processus en cours d'un conteneur :
docker top

# Supprime un conteneur :
docker rm

# Crée un conteneur avec un dockerfile :
docker build

# Sauvegarde un conteneur ou une image au format tar.gz :
docker save

# Gère les réseaux :
docker network

# Gère les volumes :
docker volume

Voila pour les commandes de base. Ceci peut sembler être du charabia pour le moment, mais nous verrons toutes ces commandes dans les chapitres suivants.

Il est très important de savoir consulter les aides de docker (enfin de tout applications en fait), bien souvent on en trouve nos réponse.

Le docker Hub

Qu'est-ce que le docker hub ?

Le docker Hub est un store ou les utilisateurs de docker peuvent partager leur 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 owncloud par exemple.
C'est justement cet méthode que j'appelle la méthode feignasse . Je veux owncloud, je télécharge l'image et je crée un conteneur, vu que j'ai une bonne connexion, en 1 minute max, j'ai un owncloud fonctionnel, elle est pas belle la vie ?!

Aucun compte n'est nécessaire pour télécharger une image, mais bien évidemment pour pouvoir envoyé vos images, il faut un compte.

Chercher une image

Il existe 2 méthodes de chercher sur le Hub, la première par le site web.
Gardons mon exemple, je veux un owncloud.
Sur le site je cherche donc owncloud, et j’obtiens plusieurs résultat :

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, ou 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 owncloud

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 -p 80:80 owncloud:8.1

Ceci lance le conteneur et rend accessible l'application sur le port 80, on verra 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é" 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 :

$ docker search owncloud
NAME                       DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
owncloud                   ownCloud is a self-hosted file sync and sh...   310       [OK]
l3iggs/owncloud            The very latest ownCloud server release, a...   104                  [OK]
jchaney/owncloud           ownCloud 7 on Nginx                             48                   [OK]
pschmitt/owncloud          Reasonably configurable Docker image for t...   15                   [OK]
dperson/owncloud                                                           9                    [OK]
radial/owncloud            Spoke container for Owncloud, an open sour...   4                    [OK]
webdeskltd/owncloud        ownCloud based on opensuse Run: docker run...   3                    [OK]
desertbit/owncloud         Deploy the latest ownCloud on Debian 8 wit...   2                    [OK]
mikewhy/owncloud           Pain free recent version of ownCloud            1                    [OK]
actualys/owncloud          Owncloud                                        1                    [OK]
dinkel/owncloud            ownCloud running on Nginx with data persis...   1                    [OK]
splattael/owncloud         ownCloud/MySQL on Alpine (image size: 98 MB)    1                    [OK]
martingabelmann/owncloud   Dockerized OwnCloud based on l3iggs/archli...   1                    [OK]
aheimsbakk/owncloud-apcu   ownCloud Docker with APCu enabled               1                    [OK]
d4v1d31/owncloud           Simple OwnCloud container based on alpine ...   1                    [OK]
webhippie/owncloud         Docker images for owncloud                      0                    [OK]
derjudge/owncloud          Arch Linux based ownCloud server                0                    [OK]
cloyne/owncloud            OwnCloud Docker image.                          0                    [OK]
gnarea/owncloud            Lightweight Docker image for ownCloud v8        0                    [OK]
jangmarker/owncloud        based on the official owncloud build, incl...   0                    [OK]
corexx/owncloud            ownCloud running in docker container.           0                    [OK]
inox42/owncloud            owncloud                                        0                    [OK]
gymnae/owncloud            Alpine:edge based owncloud 9 image              0                    [OK]
kampka/owncloud            An owncloud image build on top of arch linux    0                    [OK]
jgkamat/owncloud           A small owncloud repostiory                     0                    [OK]

Nous avons par contre ici beaucoup moins d'information, personnellement j'utilise cette méthode que pour rechercher des images de base (debian, centos, fedora, etc...).

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.

Télécharger des images

Pour télécharger une image on utilise cette commande :

$ docker pull [nom image]:[tag]

Ce qui donne pour télécharger notre owncloud :

$ docker pull owncloud
Using default tag: latest
latest: Pulling from library/owncloud
8b87079b7a06: Pull complete
a3ed95caeb02: Pull complete
af1704cb90e1: Pull complete
6acdef7ebe13: Pull complete
4fc566a7c22a: Pull complete
b06a1bacee51: Pull complete
c5fc21fb6c09: Pull complete
df319b61c869: Pull complete
f958e5267409: Pull complete
b06010f04fa8: Pull complete
17c1b5f8acff: Pull complete
2ed292d591c7: Pull complete
5ea0585ac6f2: Pull complete
509094981275: Pull complete
0230a11d5475: Pull complete
45adc0bd0ad5: Pull complete
f2c849b99a41: Pull complete
93fd4527756c: Pull complete
2604861d7d02: Pull complete
2bc27baafb34: Pull complete
Digest: sha256:778d04334dbee5b2515778ad5d2feac6182a01c9464598cd9e2de3973a53d396
Status: Downloaded newer image for owncloud: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, owncloud possède plusieurs tags.
En spécifiant un tag, par exemple 8.2.5 ça donnerait :

$ docker pull owncloud:8.2.5
8.2.5: Pulling from library/owncloud
8b87079b7a06: Already exists
a3ed95caeb02: Pull complete
af1704cb90e1: Already exists
6acdef7ebe13: Already exists
4fc566a7c22a: Already exists
b06a1bacee51: Already exists
c5fc21fb6c09: Already exists
df319b61c869: Already exists
f958e5267409: Already exists
b06010f04fa8: Already exists
17c1b5f8acff: Already exists
2ed292d591c7: Already exists
5ea0585ac6f2: Already exists
509094981275: Already exists
0230a11d5475: Already exists
45adc0bd0ad5: Already exists
f2c849b99a41: Already exists
93fd4527756c: Already exists
6606f8b61dfb: Pull complete
2804563ef896: Pull complete
Digest: sha256:40b03f13ff7a4341e5a07521d21853a689f673673b0a56dd7cee1065b8cea4f9
Status: Downloaded newer image for owncloud:8.2.5

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 à 9.0.2-apache, 9.0.2, 9.0-apache, 9.0, 9-apache, 9, et apache.

Donc si maintenant je télécharge la version 9.0.2, puisqu'il à déjà toutes les couches, il ne devrait pas les retélécharger :

$ docker pull owncloud:9.0.2
9.0.2: Pulling from library/owncloud
8b87079b7a06: Already exists
a3ed95caeb02: Already exists
af1704cb90e1: Already exists
6acdef7ebe13: Already exists
4fc566a7c22a: Already exists
b06a1bacee51: Already exists
c5fc21fb6c09: Already exists
df319b61c869: Already exists
f958e5267409: Already exists
b06010f04fa8: Already exists
17c1b5f8acff: Already exists
2ed292d591c7: Already exists
5ea0585ac6f2: Already exists
509094981275: Already exists
0230a11d5475: Already exists
45adc0bd0ad5: Already exists
f2c849b99a41: Already exists
93fd4527756c: Already exists
2604861d7d02: Already exists
2bc27baafb34: Already exists
Digest: sha256:a5ceee7887a1e1aa83ffe0e6e4c3ca1a4c182b398c68a61928715331391bae8c
Status: Downloaded newer image for owncloud:9.0.2

Donc effectivement, tous é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 8.X.X de owncloud, il me suffit d'utiliser le tag 8, qui correspondra toujours à la dernière version 8.X.X, sans se soucier du nouveau numéro de version.

Lister les images

Pour lister les images téléchargées, donc disponibles en local, nous utiliserons cette commande :

$ docker images
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é :

$ docker images
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

Nous voyons nos 3 images. Comme nous pouvons le voir, owncloud:9.0.2 et owncloud:latest ont le même ID, mais rassurez vous, ce sont juste des alias, elles ne prennent pas toutes les deux 698,5MB d'espace disque.

Petite astuce pour ne pas afficher les doublons :

$ docker images | 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) voulues :

$ 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 ne vous rappeler plus du nom complet, on peut jouer un peu avec les regex :

$ docker images */*cloud
REPOSITORY            TAG                 IMAGE ID            CREATED             SIZE
wonderfall/owncloud   latest              233e6e0c61de        3 days ago          201 MB

Supprimer les images

Pour supprimer une image, c'est plutôt simple :

$ docker rmi [nom Image ou ID image]:[tag]

Voici un exemple :

$ docker rmi owncloud:8.2.5
Untagged: owncloud:8.2.5
Deleted: sha256:52f7d60d34bdfdcd347fec38143955cf68e5481fa6f6bcfd5b4891af3c7d520f
Deleted: sha256:d60f7117b4747827d6678250990276c01794735d3f304d76d8908fefc0d9e886
Deleted: sha256:c687ceacff43dd4b11a4cb2210947bccc2c3907b8ffec81790a50a701b9a489c
Deleted: sha256:8fc47fd7e14e788d5a3704caa624c2645fb804986448db79c38fe7d73dcecba0
Deleted: sha256:bf2a37e9dc311b8168b2c46b36bb6ff0fec1d6238464c8205e7ebb60f368978b

On vérifie :

$ docker images
REPOSITORY            TAG                 IMAGE ID            CREATED             SIZE
wonderfall/owncloud   latest              233e6e0c61de        3 days ago          201 MB
debian                jessie              1742affe03b5        7 days ago          125.1 MB
owncloud              9.0.2               4e0dc7be3d39        3 weeks ago         699.2 MB
owncloud              latest              4e0dc7be3d39        3 weeks ago         698.5 MB

Par contre si on supprime une image que l'on possède avec plusieurs tags, il ne supprime pas l'image, mais l'alias :

$ docker rmi owncloud:9.0.2
Untagged: owncloud:9.0.2

On peut vérifier :

$ docker images
REPOSITORY            TAG                 IMAGE ID            CREATED             SIZE
wonderfall/owncloud   latest              233e6e0c61de        3 days ago          201 MB
debian                jessie              1742affe03b5        7 days ago          125.1 MB
owncloud              latest              4e0dc7be3d39        3 weeks ago         698.5 MB

Nous pouvons bien sur supprimer plusieurs images :

$ docker rmi debian:jessie owncloud:latest
Untagged: debian:jessie
Deleted: sha256:1742affe03b5c991cd0fbbeb47135dab46fa97b740f52a51a05fed0a3bdd84f4
Deleted: sha256:db80480bef0957c557c70969684a28889240dac9f55018d97da96cc2d8948c11
Deleted: sha256:4dcab49015d47e8f300ec33400a02cebc7b54cadd09c37e49eccbc655279da90
Untagged: owncloud:latest
Deleted: sha256:4e0dc7be3d395e65815989a73fb7299bce100e6e6d18c39b5b68f6b061b0cdeb
Deleted: sha256:32bb608e19cb7e816d5aae1c4e39540e77146983217add352cb0837b592d4891
Deleted: sha256:979fd15de62314c00ec5f0fbd5f7a097c238c650be3b1de858ab7f3fc71247f9
Deleted: sha256:30cabcb2c05095379d620ef9fad46d2f9d1a4d84f5d9921b64f5dd2899b2de33
Deleted: sha256:75f7ebe74c8083db31b9aecb55b9f43abe92f4dcd6cedbbdf2680c443c551ceb
Deleted: sha256:570f6a2ce8286bc711b4306e6e1a310f33eb6ed5be6d518091c6549edbdf9356
Deleted: sha256:a1b29b7955b8fd57f7e0a4b6668f33197ccfbc8a9e1affec544a5ad0f8e1481c
Deleted: sha256:f7e1eb882b197c314fd858e25e06fb97d20b4f070f07e24491c384a1ddba6469
Deleted: sha256:b98417d7b04b25956648f768b5d950206e855011856e00c517b621ef567f496e
Deleted: sha256:94b17218a5c1685eac9a0d2446be6af60644c2150a3eaa30c1fc05db072f0ce8
Deleted: sha256:2bca05a2bddbf1abf9b32629717c201b41ccf1442fbc07c482b4802c18541ec0
Deleted: sha256:afb273893ab6729929cde812b318600366fe2eceb53a228865d4030ebd74a370
Deleted: sha256:ce349204edc144e45d07a814f59be0bf37e8fd6253314f302029cba8872d09b8
Deleted: sha256:d24d38864be3eea4f8abcaa031b1ef975fc5064a86c5ffb58f810436306dcad0
Deleted: sha256:2ae71cf459e4ca52ecc57bfefa3cd6c601b23a798f91f0ef3b198a2adb06ee07
Deleted: sha256:ad46b2eda6f188462b63531aee6149da4f3e5fcbc95f0cca600a98d1cc71c5ab
Deleted: sha256:0a212ffa405f1cd406e4a97fd485e863840428797af53f6e57bacfad97676f6e
Deleted: sha256:6d581dc9d9afdd9fd31281ff0606e52d2be99261c64af7e46859a67f287e818f
Deleted: sha256:29620f6dd0717e78db3d779f9173a846ac3c82e46b4d87940214219689d0aa9f
Deleted: sha256:ca196a4a4517d0b495b2f3fdf6a077073319a70ffc060ea5a07d8efebbc66671
Deleted: sha256:eb44c8a6f1bfa99887d7f7773a79b1f9d69964306c51e40e4666d3b7e1daef27
Deleted: sha256:790688aa28d7bcdff22a2cef7cb5750b45ea0815000cd70de1c466bf6eb6e51c
Deleted: sha256:7e6855ffa6e0ff57c29e7ed77921589c731afc4136dc0a71b34d73f7299af904
Deleted: sha256:ff377ea60156d8038c52b3b5471a9dc349c6b33d705aa0b40ead24ee1a6bb54c
Deleted: sha256:0208ee8ca577ad6117bd111265a40e217a6bd4c9e0d63d49c4df4fd34bf8cffe
Deleted: sha256:a95ffdbe15d25ab648626f5f13f26b738c2873322f39c7bda3cd187ba1130518
Deleted: sha256:e98aa4b04b76fbb6b1587fee9b8222d464932e937e965631305eec1968b98ca8
Deleted: sha256:9dfcb17dacf0bad18ae6068580bff957bdbcc4e4893f4d08cf9cefddaba19b21
Deleted: sha256:c85db86ae5100ac5d917bac57570333f3f79ace731f806efd96c8cad7979b4b2
Deleted: sha256:daaad8877c765f98fbdd066e949553a1ebf167a8c4b24494b87752b7d5ed571a
Deleted: sha256:553ea5a836fe2efd03a6b6bd0fa694bdedeea8c0f12b9b738f0790ce8a646ad4
Deleted: sha256:ffe4962f48816ad81bef807251c79066642740dcc36d1517706af0890c39581b
Deleted: sha256:dbd3971f96c0f482a08fb3e7773cfe5ea5722f5610aed42101b9fc6988cce41b
Deleted: sha256:f7cf774ed4094c199f554927b7b01690920b834f118e702ff2643560549f27fc
Deleted: sha256:6eb35183d3b8bb6aee54874076fb1ad77b5259c93b330986b0cbcaa44cbbbc00

Petit bonus pour supprimer toutes les images (oui cela peut être utile) :

$ docker rmi $(docker images -q)
Untagged: wonderfall/owncloud:latest
Deleted: sha256:233e6e0c61de6d1e45265ad6f144b6d49cde26084023e4287de8c82daf9e766d
Deleted: sha256:8fb3c617bd3ee07a9b39df29d34fdcafb08b9035ccd33201400efc6d42b1c96e
Deleted: sha256:9029f5a42f65f482f57996a99c1540647866e75637a06aefe42deccee2b44d4d
Deleted: sha256:4d2f8700463f9dc97804f53dce2b7ed1a8c743cd04719b519a83465d5301a916
Deleted: sha256:4af4d88b79312af837521809b629961d6c4868d3f9a9f35784d4a0b47a5ce82b
Deleted: sha256:92d29fb19654511972a5b6f4518b4b7a07b4b865835f95f365501632f7ee6ade
Deleted: sha256:eaebaae1c2d814fa8ae8efe98de30c6a7de068a4de14dc357cf20e3564d78dcd
Deleted: sha256:958eeb7c64e26f5fcb542ea72674980086269d5e4dd7445d9e6c99ca5a495e1c
Deleted: sha256:79e52a87b518051d802164ed5be14f71c2c8c2ca7934719d13eb40c29e9a74f8
Deleted: sha256:11e2d1ac6a9a01bb2169ad8782082bc74195e9fd2683d02c4c575c7b4884c8bf
Deleted: sha256:ba9fb12ee087f22a04dc72ce939ab8a01ba9e0564bcedb9f00348ab40344db12

Le -q permet de n'afficher que les IDs des images.

Conclusion

Même si ce chapitre n'apporte pas grand chose, il est tout de même utile (voir 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éral.

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 pleins d'autres choses indispensables.

Tous 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é, 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 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.

Lancer, arrêter et lister des conteneurs

La première commande que nous utiliserons, sera docker run qui s'utilise comme ceci :

$ docker run [OPTIONS] IMAGE [COMMANDE]

Nous allons commencer par un petit conteneur, basé sur debian (pourquoi pas), et nous lui dirons d'afficher "bonjour mondedie !!!" :

$ docker run debian echo "bonjour mondedie !!!"
Unable to find image 'debian:latest' locally
latest: Pulling from library/debian
51f5c6a04d83: Pull complete
a3ed95caeb02: Pull complete
Digest: sha256:978927d00fdd51a21dab7148aa8bbc704a69b518fa6a12aa8f45be3f03495860
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é de lui même (sans avoir à utiliser docker pull), pratique hein ?!
Ensuite il a exécuté la commande qu'on lui a passé, à savoir écrire "bonjour mondedie !!!".
Et c'est tous, 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 ps :

$ docker ps
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 :

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                     PORTS               NAMES
c63e726069a9        debian              "echo 'bonjour monded"   8 minutes ago       Exited (0) 8 minutes ago                       gloomy_panini

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 :

$ time sh -c 'i=1; while [ $i -le 20 ]; do docker 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 :

docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS                          PORTS               NAMES
4fc67c5c3e8b        debian              "echo 'bonjour monded"   About a minute ago   Exited (0) About a minute ago                       pensive_joliot
f37333411a66        debian              "echo 'bonjour monded"   About a minute ago   Exited (0) About a minute ago                       clever_hoover
e6be9afe3621        debian              "echo 'bonjour monded"   About a minute ago   Exited (0) About a minute ago                       sad_wilson
36aaa3ad01d0        debian              "echo 'bonjour monded"   About a minute ago   Exited (0) About a minute ago                       boring_knuth
19b69a1a10e5        debian              "echo 'bonjour monded"   About a minute ago   Exited (0) About a minute ago                       high_keller
013f6a4cb5f3        debian              "echo 'bonjour monded"   About a minute ago   Exited (0) About a minute ago                       elegant_tesla
cba87a0647ed        debian              "echo 'bonjour monded"   About a minute ago   Exited (0) About a minute ago                       kickass_gates
41845ccc95ec        debian              "echo 'bonjour monded"   About a minute ago   Exited (0) About a minute ago                       agitated_wozniak
59aab95779d8        debian              "echo 'bonjour monded"   About a minute ago   Exited (0) About a minute ago                       tender_jang
7c5f9e005fd2        debian              "echo 'bonjour monded"   About a minute ago   Exited (0) About a minute ago                       jovial_varahamihira
1effc526ad29        debian              "echo 'bonjour monded"   About a minute ago   Exited (0) About a minute ago                       elegant_darwin
e5c31b313ee6        debian              "echo 'bonjour monded"   About a minute ago   Exited (0) About a minute ago                       small_stonebraker
8e2aa9e92f2e        debian              "echo 'bonjour monded"   About a minute ago   Exited (0) About a minute ago                       insane_northcutt
2ae3d5af5fca        debian              "echo 'bonjour monded"   About a minute ago   Exited (0) About a minute ago                       jovial_elion
c975789547a5        debian              "echo 'bonjour monded"   About a minute ago   Exited (0) About a minute ago                       cocky_einstein
9b6322a88c19        debian              "echo 'bonjour monded"   About a minute ago   Exited (0) About a minute ago                       romantic_bose
24d3ffeef954        debian              "echo 'bonjour monded"   About a minute ago   Exited (0) About a minute ago                       condescending_hopper
9333f3482646        debian              "echo 'bonjour monded"   About a minute ago   Exited (0) About a minute ago                       focused_austin
cf885f230c54        debian              "echo 'bonjour monded"   About a minute ago   Exited (0) About a minute ago                       romantic_goldwasser
3b4cb93c3526        debian              "echo 'bonjour monded"   About a minute ago   Exited (0) About a minute ago                       romantic_noyce

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 ?

C'est plutôt simple, avec docker start :

$ docker start 4fc67c5c3e8b
4fc67c5c3e8b

Euh oui mais la, ç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 :

$ docker ps -a | grep 4fc67c5c3e8b
4fc67c5c3e8b        debian              "echo 'bonjour monded"   6 minutes ago       Exited (0) 2 seconds ago                       pensive_joliot

Donc la on voit qu'il a été créé il y a 6 minutes, mais qu'il c'est terminé il y 2 secondes, donc il vient de tourner.

Nous pouvons par contre le relancer en avant plan, avec l'option -a :

$ docker start -a 4fc67c5c3e8b
bonjour mondedie 20 !!!

Là on voit la commande.

Nous allons maintenant voir comment arrêter un conteneur, rien de bien compliqué, pour ce faire je vais créer un conteneur qui exécute une boucle infini (pas bien) en arrière plan, comme ceci :

$ docker run -d debian sh -c 'while true;do echo "ceci est une boucle"; done'

Nous pouvons vérifier que le conteneur tourne :

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
26a90f804cd6        debian              "sh -c 'while true;do"   38 seconds ago      Up 37 seconds                           determined_dijkstra

Comme on peut le voir il est démarré depuis 37 seconcdes.

Nous allons d'abord le redémarrer puis directement afficher son statut, pour cela nous utiliserons docker restart :

$ docker restart 26a90f804cd6 && docker ps
26a90f804cd6
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                  PORTS               NAMES
26a90f804cd6        debian              "sh -c 'while true;do"   3 minutes ago       Up Less than a second                       determined_dijkstra

On voit bien qu'il a redémarré.

Maintenant on peut l'arrêter, parce qu'un conteneur qui fait une boucle qui sert à rien, bah ça sert à rien, pour cela nous utiliserons docker stop :

$ docker stop 26a90f804cd6 && docker ps
26a90f804cd6
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 kill qui fonctionne pareil.

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 unique), ou avec le nom du conteneur, auto-généré ou non.

Voir les logs des conteneurs

Tout informaticien doit penser, et même rêver des logs, c'est indispensable.
Avec docker c'est assez spécial, les logs d'un conteneur est en fait ce qui est en output du shell.

C'est plutôt simple, même très simple, nous utiliserons docker logs :

$ docker logs conteneur

Exemple :

$ docker logs 26a90f804cd6
ceci est une boucle
ceci est une boucle
ceci est une boucle
ceci est une boucle
ceci est une boucle
ceci est une boucle
ceci est une boucle
ceci est une boucle
ceci est une boucle
ceci est une boucle
ceci est une boucle
[...]
ceci est une boucle
ceci est une boucle
ceci est une boucle
ceci est une boucle
ceci est une boucle
ceci est une boucle
ceci est une boucle
ceci est une boucle
ceci est une boucle
ceci est une boucle
ceci est une boucle

Il est possible de faire comme tail :

$ docker logs --tail=20 26a90f804cd6 # Affiche les 20 dernières lignes
$ docker logs -f 26a90f804cd6 # Affiche les logs au fur et à mesure

Et en fait c'est tout, c'est très simple.

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 rm CONTENEUR

Ce qui donnerait pour notre conteneur :

$ docker rm 26a90f804cd6
26a90f804cd6

Vous pouvez également supprimer un conteneur qui tourne, avec l'option -f.

Pas grand choses d'autre à dire sur la suppression, à part que je peux vous faire profiter d'une petite astuce, qui permet la suppression de tous les conteneurs :

$ docker rm $(docker ps -aq)
4fc67c5c3e8b
f37333411a66
e6be9afe3621
36aaa3ad01d0
19b69a1a10e5
013f6a4cb5f3
cba87a0647ed
41845ccc95ec
59aab95779d8
7c5f9e005fd2
1effc526ad29
e5c31b313ee6
8e2aa9e92f2e
2ae3d5af5fca
c975789547a5
9b6322a88c19
24d3ffeef954
9333f3482646
cf885f230c54
3b4cb93c3526

Et voila pour la gestion basique des conteneurs.

Passons aux choses sérieuses.

Cas concrets

Jusqu'ici, nous n'avons rien fait de bien exitant, créer ou supprimer un conteneur c'est marrant 5 minutes, mais si celui ci ne sert à rien, bah sa 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.

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 :

// vim app.js
console.log("Hello World");

Puis on pull la version 5 et 6 de node :

$ docker pull xataz/node:5
$ docker pull xataz/node:6

Puis on peut faire nos tests, pour commencer avec node 5 :

$ docker run -t --rm -v $(pwd):/usr/src/app -w /usr/src/app xataz/node:5 node app.js
Hello World

Puis node 6 :

$ docker run -t --rm -v $(pwd):/usr/src/app -w /usr/src/app xataz/node:6 node app.js
Hello World

C'est cool, notre code fonctionne avec les deux versions.

Qu'avons nous fait ici ?

Nous avons lancer 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é 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 :

// 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 :

$ docker run -d -v $(pwd):/usr/src/app -w /usr/src/app -p 8001:8000 --name node5 xataz/node:5 node app.js
7669bef4b5c06b08a6513ed1ce8b8b036ad5285236a9e21a969897e5a9a8c537
$ docker run -d -v $(pwd):/usr/src/app -w /usr/src/app -p 8002:8000 --name node6 xataz/node:6 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 :

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS              PORTS                                                NAMES
0e02e0844dd1        xataz/node:6        "node app.js"            About a minute ago   Up About a minute   0.0.0.0:8002->8000/tcp                               node6
7669bef4b5c0        xataz/node:5        "node app.js"            About a minute ago   Up About a minute   0.0.0.0:8001->8000/tcp                               node5

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 au application 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 :

$ docker rm -f node5 node6
node5
node6

Et voilà, on peut voir à quel point c'est pratique d'utiliser docker dans ce cas présent.

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écifique comme oracle.
Ces images sont souvent des images AllinOne (Tout en un), c'est à dire que 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 :

$ docker 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 :

$ 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 permets d'ajouter des variables d'environnement au conteneur. Ces variables seront utilisé sois directement par l'application, sois par le script d'init de l'image (que nous verrons dans la partie Créer une image).
Dans notre cas nous avons ajouté 4 variables, mais il en existe d'autre (cf README) :
UID et GID sont des variables que vous trouverez dans toutes mes images, qui permets de choisir avec quel droit sera lancé l'application.
WEBROOT est une variable qui permettra la modification du webroot du fichier de configuration de l'application, donc ici nous y accederons 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.

Nous pouvons vérifier les variables d'environnement via docker inspect lutim, mais cette commande retourne toute la configuration de notre conteneur, nous allons donc le formatter :

$ docker 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é 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 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 apporté si vous n'avez pas forcément la technique pour le faire à la main, ou tout simplement pour tester une application.

Troisième cas : Le déploiement

Dans ce troisième cas, nous allons partir sur quelques chose d'un peu plus complexe, et spécifique. Nous allons ici voir comment faire un déploiement en blue/green (Version simplifié), c'est à dire sans interruption de service (ou très peu, moins d'une seconde).

Dans ce scénario, nous aurons 3 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 :

$ docker run -d -v $(pwd):/usr/src/app -w /usr/src/app -p 8001:8000 --name node-blue xataz/node:5 node app.js
e2a392d5b0ee7c65683dc277eb47c67dd93804ef36458968b2e5d34afc154957
$ docker run -d -v $(pwd):/usr/src/app -w /usr/src/app -p 8002:8000 --name node-green xataz/node:6 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 mettre à jour notre node de la version 5 à la version 6, 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 :

# 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 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 v5.11.0

Maintenant que j'ai bien testé mon application sur node.js 6 (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 :

# vim /docker/config/nginx/bluegreen.conf
server {
  listen 8080;

  location / {
    proxy_pass http://tata:8000;
  }
}

On relance nginx :

$ 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 v6.2.1

Maintenant, node-green est devenu notre production, et node-blue notre dev dans laquelle nous testerons la version 7 de node. Et quand celle ci sera prête, nous re-fairons 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.

Conclusion

Cette partie fût plus concrète que les précédentes, nous savons maintenant comment créer un conteneur, et le gérer. A partir de ce moment, vous êtes totalement capable d'installer une application via docker.

Créer une image

Nous avons plusieurs façons de faire une image. Nous pouvons le faire à partir d'un conteneur existant, 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.

Dans cette partie nous créerons des images avec un dockerfile, car c'est la méthode la plus rapide et la plus simple à maintenir. Il existe d'autres méthodes, comme docker save qui permet de sauvegarder l'état d'un conteneur en tar.gz, ou alors de créer l'image dans un chroot, et de l'importer par la suite, mais nous ne verrons pas ces méthodes ici, car difficilement maintenable, et surtout non adapté à l'OpenSource.

Création d'un Dockerfile

Créons une image apache

Le Dockerfile (toujours avec une majuscule) est un fichier qui contient toutes les informations pour créer une image, comme des méta-données (Mainteneur, label etc ...), ou même les commandes à exécuter pour installer un logiciel.

Voici la liste des instructions d'un Dockerfile :

FROM # Pour choisir l'image sur laquelle on se base, toujours en premier
MAINTAINER # Le nom du créateur/mainteneur sous forme "prénom nom [ou pseudo] <email>"
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éta-données utiles pour certain logiciel de gestion de conteneur, 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

Et la commande pour construire l'image :

docker 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 :

$ 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, MAINTAINER :

FROM ubuntu
MAINTAINER xataz <xataz@mondomaine.dev>

Puis on ajoute les variables d'environnements (qui sont normalement gérer par le système d'init) :

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

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 :

FROM ubuntu
MAINTAINER xataz <xataz@mondomaine.fr>

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 :

$ docker 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 :

$ docker 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 (soit via docker.local, soit via l'IP de la VM), vous devriez avoir la page Apache2 Ubuntu Default Page.

Exemple d'une image lutim

Nous allons ici créer une image lutim, basé 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 :

FROM debian:jessie
MAINTAINER xataz <https://github.com/xataz>

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"

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 2 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 :

{
    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.

Ainsi que le startup :

#!/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 simple, il crée une 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.

On peut tenter de construire l'image :

$ docker build -t xataz/lutim .

ça va prendre un petit moment, c'est plutôt long à installer les dépendances.

Testons notre image :

$ docker 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 test dans le navigateur, et normalement, ça fonctionne.

Créons une image de base

Nous allons ici créer une image de base, c'est a 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 :

$ mkdir alpine
$ cd alpine

On commence par créer un rootfs, pour ceci nous utiliserons l'outils officiel de alpine, c'est à dire apk (version actuelle 2.6.7-r0, à 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 ...), faire ceci en root :

$ 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éé :

$ 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 :

$ tar czf rootfs.tar.gz -C rootfs .
$ rm -rf rootfs

Pour finir on crée notre Dockerfile :

FROM scratch

ADD rootfs.tar.gz /

scratch étant une image spécifique qui est vide, spécialement pour créer une image de base.

On construit l'image et on teste :

$ docker 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 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 ... :

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 :

$ docker build -t superalpine .
[...]
$ docker 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.

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.

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 :

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 5 é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 conteneur 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és par rapport au layer précédent. Si j'ajoute un é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 layer, 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 cherche 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 la il le trouve.
Dans une image qui comporte une vingtaine de layers, les performances ne sont pas trop impacté, mais avec une centaine de layers à remonté, cela se sent.

Pour corriger cela, on mets plusieurs commandes dans la même étape :

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"

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 (5Mo pour alpine contre 120Mo 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 soucis, 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 :

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 build -t superalpine1 .

Puis je crée un superalpine2 :

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 build -t superalpine2 .

Regardons la différence maintenant :

$ 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 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 utilise de l'espace. Ce nouveau layer indique simplement que tel ou tel fichier à été supprimé.

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 :

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.

Une autre exemple, en reprenons notre lutim, et en appliquant les précédentes règles :

FROM debian:jessie
MAINTAINER xataz <https://github.com/xataz>

ENV GID=991 \
    UID=991 \
    CONTACT=contact@domain.tld \
    WEBROOT=/ \
    SECRET=e7c0b28877f7479fe6711720475dcbbd \
    MAX_FILE_SIZE=10000000000

LABEL description="lutim based on debian"

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 build -t lutim2 .

Ce qui donne pour comparer :

$ 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.

Éviter les processus root

Ici c'est plus de l'administration qu'une spécificité docker.

Nous avons bien sûr la possibilités 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 outils 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 actuelle. exec s'utilise comme ceci : exec <command>

Pour l'exemple, reprenons notre script startup de lutim :

#!/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 :

$ docker run -d --name lutim lutim
$ docker 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 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 :

FROM debian:jessie
MAINTAINER xataz <https://github.com/xataz>

ENV GID=991 \
    UID=991 \
    CONTACT=contact@domain.tld \
    WEBROOT=/ \
    SECRET=e7c0b28877f7479fe6711720475dcbbd \
    MAX_FILE_SIZE=10000000000

LABEL description="lutim based on debian"

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 :

#!/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 build -t lutim .

On teste notre première modification :

$ docker run -d --name lutim lutim
$ docker 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 rm lutim

On voit qu'il nous reste un seul processus root.

Et si on ajoutes exec :

#!/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

Puis qu'on teste :

$ docker run -d --name lutim lutim
$ docker 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 rm lutim

Et voilà, plus de processus root, un bon pas niveau sécurité.

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.

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 tous simplement en envoyant une archive de l'image.

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.

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 :

$ 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 :

$ docker 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'autre option pour les envoyer sur le Hub, comme 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).

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 :

$ docker 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 :

$ docker rmi -f $(docker images -q)
$ docker images -a
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
$ docker import lutim.tar xataz/lutim
sha256:a8dfc72dbef32cab0cd57a726f65c96c38f5e09736f46962bba7dbb3a86876d8
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
xataz/lutim         latest              a8dfc72dbef3        6 seconds ago       598.2 MB
$ docker history xataz/lutim
IMAGE               CREATED             CREATED BY          SIZE                COMMENT
a8dfc72dbef3        12 seconds ago                          598.2 MB            Imported from -

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, mais si possible, il vaut mieux faire un autobuild (pas toujours facile).

Limiter les ressources d'un conteneur

Cette partie sera brève, les options ne sont malheureusement pas nombreuses.

La mémoire

Pour limiter la mémoire, nous utiliserons 3 options :

-m : 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 à 200M et le swap à 800M)

--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.

Pour tester la mémoire nous utiliserons l'image debian :

$ docker run -ti --rm -m 500M debian
root@1044c936c209:/# free -h
             total       used       free     shared    buffers     cached
Mem:          995M       918M        77M       705M       6.3M       759M
-/+ buffers/cache:       152M       843M
Swap:         1.1G        76M       1.1G

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 test, nous allons stresser le conteneur avec 500M, qui devrais représenter environ 50% de la mémoire de ma machine :

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 900M de ram :

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 utilise que 47.2% de la mémoire disponible sur l'hôte, alors qu'on lui à 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é :

$ docker 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

Le CPU

Pour limiter le CPU nous utiliserons 3 options :
-c : 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 2 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)

Nous allons également utiliser debian avec stress :

$ docker 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 2 cœurs :

$ docker 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 :

$ docker 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%.

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)
--device-read-bps : Limite la vitesse de lecture sur un device. (--device-read-bps /dev/sda:1mb)
--device-write-bps : Limite la vitesse d'écriture sur un device. (--device-write-bps /dev/sda:1mb)

Malheureusement c'est une fonction que je n'ai pas réussi à faire fonctionner chez moi, sauf avec la machine virtuelle officielle de docker. Je pense que c'est un problème avec les cgroups. Mais j'y travaille .

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 :

$ 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.

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 prends comme arguments :

$ 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 :

$ docker volume create --name test
test

Que nous utiliserons comme ceci :

$ 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 :

$ docker run -ti -v test:/test alpine:3.4 sh
/ # ls /test
bidule

On retrouve bien notre fichier, les données sont 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.

$ 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 ou je vais créer un fichier :

$ docker run -ti -v test:/test alpine:3.4 touch /test/fichier
$ sudo ls /data/test
fichier

Et voila, mon fichier est correctement dans /data/test.

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és, puisqu'en ram :

$ 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) :

$ docker volume create -o type=ext4 -o device=/dev/sdb1 --name extpart
extpart

Encore et toujours plus loin avec les plugins

Comme précédemment cités, il nous est possible d'ajouter des plugins a docker. Nous utiliserons ici le plugins netshare. Le plugins netshare permets 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 partirais pas sur l'explication de l'installation d'un serveur nfs, vous trouverez ceci ici.

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 :

$ mount -t nfs 10.2.81.71:/shared /mnt
$ root@scw-docker:~# ls /mnt
fichier_partager
$ umount /mnt

c'est bon tout fonctionne.

Installation du plugin

Nous avons de la chance, il existe un dpkg pour debian et ubuntu, on l'installe donc comme ceci :

$ 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 ^^.

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 :

$ 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 écris est indiqué dans le readme du plugin.

Conclusion

Nous avons vu ici comment utiliser docker volume, et comment gérer plusieurs volume, 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.

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éseaux de communiquer par leurs noms.

Voici la syntaxe :

$ 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.

Les types de réseaux

Nous avons de base, 4 types de networks :

  • bridge : Crée un réseau interne pour vos conteneurs.
  • host : Ce type de réseau permet au conteneurs 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 inter-hôte, nous verrons ceci dans la partie swarm de ce tutoriel.

Création d'un network

Voici les arguments que prend docker network create :

$ 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'information dans la documentation.

Nous allons directement attaquer en créant un réseau, que nous appellerons test :

$ docker network create test
1b8d8e2fae05224702568d71a7d8e128601250018795d06dba884d860e124e65

Nous pouvons vérifier s'il est bien créé :

$ 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 :

$ 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 :

$ 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": {}
    }
]

La 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 :

$ 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 :

$ 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.

Utilisation des networks

Pour attacher un réseau à un conteneur, il suffit d'utiliser l'argument --network avec docker run :

$ 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:c802%32674/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: ::1%32674/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 :

/ # 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:c802%32557/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:2%32557/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: ::1%32557/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 soucis.
Pour faire un test, créons un conteneur dest puis un conteneur qui le ping qui s'appellera ping :

$ docker run -d --network test3 --name dest alpine:3.4 ping 8.8.8.8 # ping 8.8.8.8 est la 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 :

$ 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 :

$ 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 :

$ docker stop dest && docker rm -f dest
dest
$ docker network rm test3
test3

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.

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 de vous connaissez bien docker (Si j'ai bien fait mon boulot), je serais plus succinct sur les explications, surtout que docker-compose reprend les mêmes options que docker.

Installation

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, copier y le binaire et renommer le en docker-compose.exe.

Et c'est tout, normalement tout est OK, on peut tester :

PS P:\> 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

Sous GNU/Linux

Nous avons 3 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 a mettre à jour.

Installation via pip

Pour ceux qui ne connaissent pas pip, pip est un gestionnaire de paquet pour python, qui permet l'installation d'application, ou de librairie. C'est un peu comme apt-get.

On installe d'abord pip, sous debian cela donne :

$ 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 coté j'avais déjà installé pip)

Il suffit maintenant de lancer cette commande :

$ pip install docker-compose

On peut tester :

$ 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

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 :

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 :

$ 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

Sous MacOS

????

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 suivantes, un cas concret.

Voyons d'abord les commandes de docker-compose :

$ 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, comporteras 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 2 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 3 parties :

volumes:
networks:
services:

Le nom de ces 3 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 :

$ 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 voila la comparaison avec docker-compose, on commence par le fichier :

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 :

$ 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, sont 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 prends 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 arriere 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

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 :

$ 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 :

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 :

# 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 :

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 :

$ docker-compose up -d
Creating web
Creating php
Creating mariadb
$

Et voilà notre stack est prête, on peut essayer de si 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 correct.
Pour s
e connecter, il suffit de remplir comme ceci (le mot de passe est root comme indiqué dans le docker-compose) :
adminer

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 chiant de taper mariadb tout le temps :

[...]
  mariadb:
    container_name: mariadb
    image: mariadb
    environment:
      MYSQL_ROOT_PASSWORD: root
    networks:
      default:
        aliases:
          - db

Puis on relance notre stack :

$ 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.

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.

Docker-machine

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 service cloud, comme digitalOcean, Azure, Google ou plus générique sur du openstack. Il installera donc docker, mais génère également un certificat ssl, un jeu de clé pour une connexion ssh, et ceux sur de nombreuses distribution GNU/Linux (debian, centos, archlinux ...).

J'ai personnellement commencé à utiliser docker-machine que très récemment, et je trouve cela vraiment indispensable.

Installation

Lors de la rédaction de ce chapitre, nous sommes à la version 0.7.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.

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 32bits
docker-machine 64bits

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 c'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 :

$ docker-machine.exe version
docker-machine version 0.7.0, build 61388e9

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) :

$ wget https://github.com/docker/machine/releases/download/v0.7.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 :

$ docker-machine version
docker-machine version 0.7.0, build 61388e9

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 :

$ 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 :

$ 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.

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 :

$ 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 :

$ 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 :

$ 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 :

$ 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

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) :

$ docker images
An error occurred trying to connect: Get http://%2F%2F.%2Fpipe%2Fdocker_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 :

$ 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 chiant à 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 :

$ 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 information pour pouvoir si 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é via la commande.*

Et c'est tout, nous pouvons tester :

$ 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.

$ 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.

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 :

$ 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 :

$ 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 :

$ 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 :

$ 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 :

$ 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 :

$ 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

Les Plugins

Comme précédemment cités, nous pouvons installer des plugins, nous installerons ici celui de scaleway.

Installation

Normalement, lorsqu'on vous fourni un plugin pour docker, tout est indiqué dans le readme.md (ce qui est ici le cas).

Ici nous avons plusieurs solutions, sois par homebrew (OS X uniquement), soit en téléchargeant les binaires (Linux/Unix/OS X), soit en compilant les sources.

Avec les binaires :

Scaleway ne fournissant plus de binaire pour windows, ceci n'est compatible qu'avec Linux/Unix/OS X.

$ 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

Avec homebrew :

Homebrew étant disponible que sous OS X, ceci ne fonctionne que sur celui ci.

$ brew tap scaleway/scaleway
$ brew install scaleway/scaleway/docker-machine-driver-scaleway

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) :

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 -fmessage-length=0
set CXX=g++
set CGO_ENABLED=1
PS C:\Users\xataz\go>

Vous pouvez ajouter les variables d'environnements via l'interface, ici elles ne seront pas persistantes, dès vous lancerez un nouveau powershell, il faudra les recharger.


Puis on peux compiler notre driver (compter quelques minutes) :
```powershell
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 test :

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-organization                                                                              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

Utilisation

L'utilisation n'est pas si différentes que sous virtualbox, mais nous aurons des options obligatoires :

--scaleway-organization                                                                              Scaleway organization [$SCALEWAY_ORGANIZATION]
--scaleway-token                                                                                     Scaleway token [$SCALEWAY_TOKEN]

Puis des alternatives, toutes celle commençant par --scaleway en faites.

On commence par récupérer l'organization et le token, tout ce passe sur le site de scaleway :
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éer notre première machine :

PS C:\Users\xataz\go> docker-machine create -d scaleway --scaleway-token "montoken" --scaleway-organization "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 vois que ça ce termine :

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 test :

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-file
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 tache et ne pas copier toujours le token et l'access key

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 :

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 voila c'est plus simple maintenant, vous pouvez ajouter toutes les options dans des variables d'environnements.

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 au 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 fait, on oublie pas de supprimer les machines :

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 supprimer, il m'est arriver que les IPs étaient toujours présente

Conclusion

Nous avons vu ici comment gérer de multiples machines avec docker-machine sur un environnement VirtualBox. Mais comme nous vennons de le voir, nous pouvons créer/gérer des machines sur divers hébergeurs, sois nativement comme digitalOcean ou Azure, ou via un plugion comme pour scaleway.
Ceci est très rapide pour créer un nouvelle machine, et nous évite des étapes fastidieuses.

un an plus tard

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.

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é 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 inter-node.

Création de notre cluster

Création des machines

Nous allons créer 3 machines esclaves (nommées cls-nodeX), et une machine maître (nommée cls-master), pour ceci nous utiliserons docker-machine, comme vu dans le chapitre précédent.
Ayant 32Go de RAM sur mon PC, je crée les machines virtuelles avec 4Go de RAM chacune, je laisse le reste par défaut.

Étant une feignasse, je crée une boucle :

$ for machine in master node1 node2 node3; do docker-machine create -d virtualbox --virtualbox-memory "4096" cls-${machine}; done

Voilà, nos quatre machines sont créées :

$ docker-machine ls
NAME         ACTIVE   DRIVER       STATE     URL                         SWARM   DOCKER    ERRORS
cls-master   -        virtualbox   Running   tcp://192.168.99.100:2376           v1.11.0
cls-node1    -        virtualbox   Running   tcp://192.168.99.101:2376           v1.11.0
cls-node2    -        virtualbox   Running   tcp://192.168.99.102:2376           v1.11.0
cls-node3    -        virtualbox   Running   tcp://192.168.99.103:2376           v1.11.0

Configuration de master

Ce serveur sera celui qui contrôlera les autres.

On source donc master :

$ eval $(docker-machine env cls-master)

Pour faire fonctionner swarm, il nous faut un discovery, qui permet de stocker des informations sur les nodes, les conteneurs, leurs emplacements et d'autres informations. Celui qui peut être un service en ligne, ou un service auto-hébergé.
Nous utiliserons ici le discovery de docker, directement géré par swarm.

On génère donc notre token :

$ docker run --rm swarm create
Unable to find image 'swarm:latest' locally
latest: Pulling from library/swarm
8c01723048ed: Pulling fs layer
28ef38ffcca5: Pulling fs layer
f1f933319091: Pulling fs layer
a3ed95caeb02: Pulling fs layer
a3ed95caeb02: Waiting
f1f933319091: Verifying Checksum
f1f933319091: Download complete
28ef38ffcca5: Verifying Checksum
28ef38ffcca5: Download complete
8c01723048ed: Verifying Checksum
8c01723048ed: Download complete
a3ed95caeb02: Verifying Checksum
a3ed95caeb02: Download complete
8c01723048ed: Pull complete
28ef38ffcca5: Pull complete
f1f933319091: Pull complete
a3ed95caeb02: Pull complete
Digest: sha256:8b007c8fc861cfaa2f0b9160e6ed3a39109af6e28dfe03982a05158e218bcc52
Status: Downloaded newer image for swarm:latest
f0d718e5df0caf7a6fbf6496de482657

Ce qui nous intéresse est f0d718e5df0caf7a6fbf6496de482657, il nous sera utile pour tous les nodes.

Nous pouvons lancer notre manager :

$ docker run -d -p 3376:3376 -t -v /var/lib/boot2docker:/certs:ro --name swarm-manager swarm manage -H 0.0.0.0:3376 --tlsverify --tlscacert=/certs/ca.pem --tlscert=/certs/server.pem --tlskey=/certs/server-key.pem token://f0d718e5df0caf7a6fbf6496de482657
9cba87fd0387c76b9020e8794f42ae593c4b13f446e93f0d840552118ee7f44d

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                              NAMES
9cba87fd0387        swarm               "/swarm manage -H 0.0"   3 seconds ago       Up 2 seconds        2375/tcp, 0.0.0.0:3376->3376/tcp   swarm-manager

Et voila, notre serveur maître est configuré. Passons aux cls-nodeX.

Configuration des nodes

Nous pouvons attaquer la configuration de nos nodes (ceci sera à faire pour chaque node).

On source le node :

$ eval $(docker-machine env cls-node1)

Et on lance swarm en slave :

$ docker run -d swarm join --addr=$(docker-machine ip cls-node1):2376 token://f0d718e5df0caf7a6fbf6496de482657
Unable to find image 'swarm:latest' locally
latest: Pulling from library/swarm
8c01723048ed: Pulling fs layer
28ef38ffcca5: Pulling fs layer
f1f933319091: Pulling fs layer
a3ed95caeb02: Pulling fs layer
a3ed95caeb02: Waiting
f1f933319091: Verifying Checksum
f1f933319091: Download complete
28ef38ffcca5: Verifying Checksum
28ef38ffcca5: Download complete
a3ed95caeb02: Verifying Checksum
a3ed95caeb02: Download complete
8c01723048ed: Verifying Checksum
8c01723048ed: Download complete
8c01723048ed: Pull complete
28ef38ffcca5: Pull complete
f1f933319091: Pull complete
a3ed95caeb02: Pull complete
Digest: sha256:8b007c8fc861cfaa2f0b9160e6ed3a39109af6e28dfe03982a05158e218bcc52
Status: Downloaded newer image for swarm:latest
99da3ccbcc63be7ff928ee175a87b1769d3c1f9fc93016270aa9cb4a84a5cb66

Et nous vérifions qu'il tourne bien :

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
99da3ccbcc63        swarm               "/swarm join --addr=1"   21 seconds ago      Up 20 seconds       2375/tcp            tiny_kare

Et on recommence pour les deux autres nodes (je ne vous montre pas, c'est la même chose mais avec node2 et node3), vous pouvez également le lancer sur le cls-master, qui peut servir de manager et de node en même temps. (C'est d'ailleurs ce que j'ai fait)

Utilisation de swarm

Swarm sera quasiment transparent, c'est lui qui choisira l'hôte dont il a besoin, en fonction de la charge de ceux-ci et d'autres paramètres.

Nous allons donc sourcer la machine :

$ eval $(docker-machine env cls-master)

Cependant ce n'est pas suffisant, si vous vous souvenez bien, swarm écoute le port 3376, et non 2376 qui lui est le port d'écoute de docker, comme nous pouvons le voir :

$ docker-machine env master
export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://192.168.99.100:2376"
export DOCKER_CERT_PATH="C:\Users\xataz\.docker\machine\machines\master"
export DOCKER_MACHINE_NAME="master"
# Run this command to configure your shell:
# eval $("C:\Program Files\Docker Toolbox\docker-machine.exe" env master)

Il nous faut donc exporter à la main la variable DOCKER_HOST :

$ export DOCKER_HOST="tcp://192.168.99.100:3376"
$ echo $DOCKER_HOST
tcp://192.168.99.100:3376

Ou alors en une commande :

$ eval $(docker-machine env cls-master | sed 's/2376/3376/')

Maintenant que ceci est fait, nous allons vérifier que cela fonctionne bien :

$ docker info
Containers: 5
 Running: 5
 Paused: 0
 Stopped: 0
Images: 4
Server Version: swarm/1.2.0
Role: primary
Strategy: spread
Filters: health, port, dependency, affinity, constraint
Nodes: 4
 cls-master: 192.168.99.100:2376
  â”” Status: Healthy
  â”” Containers: 2
  â”” Reserved CPUs: 0 / 1
  â”” Reserved Memory: 0 B / 2.053 GiB
  â”” Labels: executiondriver=, kernelversion=4.1.19-boot2docker, operatingsystem=Boot2Docker 1.11.0 (TCL 7.0); HEAD : 32ee7e9 - Wed Apr 13 20:06:49 UTC 2016, provider=virtualbox, storagedriver=aufs
  â”” Error: (none)
  â”” UpdatedAt: 2016-04-21T18:27:36Z
  â”” ServerVersion: 1.11.0
 cls-node1: 192.168.99.101:2376
  â”” Status: Healthy
  â”” Containers: 1
  â”” Reserved CPUs: 0 / 1
  â”” Reserved Memory: 0 B / 2.053 GiB
  â”” Labels: executiondriver=, kernelversion=4.1.19-boot2docker, operatingsystem=Boot2Docker 1.11.0 (TCL 7.0); HEAD : 32ee7e9 - Wed Apr 13 20:06:49 UTC 2016, provider=virtualbox, storagedriver=aufs
  â”” Error: (none)
  â”” UpdatedAt: 2016-04-21T18:27:38Z
  â”” ServerVersion: 1.11.0
 cls-node2: 192.168.99.102:2376
  â”” Status: Healthy
  â”” Containers: 1
  â”” Reserved CPUs: 0 / 1
  â”” Reserved Memory: 0 B / 2.053 GiB
  â”” Labels: executiondriver=, kernelversion=4.1.19-boot2docker, operatingsystem=Boot2Docker 1.11.0 (TCL 7.0); HEAD : 32ee7e9 - Wed Apr 13 20:06:49 UTC 2016, provider=virtualbox, storagedriver=aufs
  â”” Error: (none)
  â”” UpdatedAt: 2016-04-21T18:27:38Z
  â”” ServerVersion: 1.11.0
 cls-node3: 192.168.99.103:2376
  â”” Status: Healthy
  â”” Containers: 1
  â”” Reserved CPUs: 0 / 1
  â”” Reserved Memory: 0 B / 2.053 GiB
  â”” Labels: executiondriver=, kernelversion=4.1.19-boot2docker, operatingsystem=Boot2Docker 1.11.0 (TCL 7.0); HEAD : 32ee7e9 - Wed Apr 13 20:06:49 UTC 2016, provider=virtualbox, storagedriver=aufs
  â”” Error: (none)
  â”” UpdatedAt: 2016-04-21T18:27:38Z
  â”” ServerVersion: 1.11.0
Plugins:
 Volume:
 Network:
Kernel Version: 4.1.19-boot2docker
Operating System: linux
Architecture: amd64
CPUs: 4
Total Memory: 8.214 GiB
Name: 9cba87fd0387
Docker Root Dir:
Debug mode (client): false
Debug mode (server): false
WARNING: No kernel memory limit support

Nous voyons bien nos 4 nodes.

Et si on lançait un conteneur :

$ docker run -d -P xataz/nginx:1.9
c9c898b30c0fbe5b16298acab3f00445c9a91bd0dedcd218b4242ad835ca6c70

Puis on regarde ou il tourne :

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                                            NAMES
c9c898b30c0f        xataz/nginx:1.9     "tini -- /usr/bin/sta"   2 seconds ago       Up 2 seconds        192.168.99.101:32769->8080/tcp, 192.168.99.101:32768->8443/tcp   cls-node1/agitated_carson

On voit qu'il tourne sur le node1.

Testons un deuxième et un troisième :

$ docker run -d -P xataz/nginx:1.9
1e01fa62a664b64437ba379af8e5fa07b8050e42cb1dab2cce9e0e2a941e161c
$ docker run -d -P xataz/nginx:1.9
b32051ffce167b7212256e295acd566ef47a5cd80ee645375b0bc5bcd0ff1321

On regarde où ils tournent :

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS              PORTS                                                            NAMES
b32051ffce16        xataz/nginx:1.9     "tini -- /usr/bin/sta"   3 seconds ago        Up 2 seconds        192.168.99.103:32769->8080/tcp, 192.168.99.103:32768->8443/tcp   cls-node3/hopeful_boyd
1e01fa62a664        xataz/nginx:1.9     "tini -- /usr/bin/sta"   10 seconds ago       Up 9 seconds        192.168.99.102:32769->8080/tcp, 192.168.99.102:32768->8443/tcp   cls-node2/romantic_boyd
c9c898b30c0f        xataz/nginx:1.9     "tini -- /usr/bin/sta"   About a minute ago   Up About a minute   192.168.99.101:32769->8080/tcp, 192.168.99.101:32768->8443/tcp   cls-node1/agitated_carson

Je les supprime :

$ docker rm -f b32 1e c9
b32
1e
c9

Et on va faire un petit test, créer 10 conteneurs nginx (avec une petite boucle de fainéant):

$ i=0; time while [ $i -lt 10 ]; do docker run -d --name nginx${i} -P xataz/nginx:1.9;i=$(($i+1)); done
28ddd40b6ada3e220b375dbdb293b869fae365b595cc1b7ea7d872661a84c71d
d72c940c9dc3ad13b4498d8b74910d597134359b6201b72c88943075a6542d5d
d4f8bfbbc1884a7a2e3e98a3f819617a7ca2a36a4f1b121a25aece490c977b21
6364046a04631b5762da26071a1dfe3c764176b0e2784d29e4d1f51593599e1b
20eb3fc1bb23713de0a324937e828e0a824213d554c1a4229588b4303656e354
c3ff1f045ae48279c01c658334d67303c8aa4d4c2c72706b070eaa1abeda5770
fbf5bbeb00748d320c6c0872081b7d69eb27a1774b38d53813c3f07ccef51505
bc4d1c51ec45ce512500f3009b30ea2a79aa4a558ee7170d8dafd4a8ec888135
2fb1a8a72dc7004787f52e204452474a26bc134c49549b1ffb59e31a0b09205b
53151725fdd3661bc351910491bcaf0ed51adf04afb0383a4b3bfdb9ae8b9a20

real    0m2.049s
user    0m0.015s
sys     0m0.060s

Je suis toujours impressionné par la rapidité de docker pour créer des conteneurs, 2s pour créer 10 conteneurs sur 4 machines différentes.

Bon on vérifie quand même :

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                                            NAMES
53151725fdd3        xataz/nginx:1.9     "tini -- /usr/bin/sta"   14 seconds ago      Up 14 seconds       192.168.99.103:32773->8080/tcp, 192.168.99.103:32772->8443/tcp   cls-node3/nginx9
2fb1a8a72dc7        xataz/nginx:1.9     "tini -- /usr/bin/sta"   14 seconds ago      Up 14 seconds       192.168.99.100:32771->8080/tcp, 192.168.99.100:32770->8443/tcp   cls-master/nginx8
bc4d1c51ec45        xataz/nginx:1.9     "tini -- /usr/bin/sta"   14 seconds ago      Up 14 seconds       192.168.99.102:32773->8080/tcp, 192.168.99.102:32772->8443/tcp   cls-node2/nginx7
fbf5bbeb0074        xataz/nginx:1.9     "tini -- /usr/bin/sta"   15 seconds ago      Up 15 seconds       192.168.99.102:32771->8080/tcp, 192.168.99.102:32770->8443/tcp   cls-node2/nginx6
c3ff1f045ae4        xataz/nginx:1.9     "tini -- /usr/bin/sta"   15 seconds ago      Up 15 seconds       192.168.99.100:32769->8080/tcp, 192.168.99.100:32768->8443/tcp   cls-master/nginx5
20eb3fc1bb23        xataz/nginx:1.9     "tini -- /usr/bin/sta"   22 seconds ago      Up 22 seconds       192.168.99.103:32771->8080/tcp, 192.168.99.103:32770->8443/tcp   cls-node3/nginx4
6364046a0463        xataz/nginx:1.9     "tini -- /usr/bin/sta"   22 seconds ago      Up 22 seconds       192.168.99.101:32771->8080/tcp, 192.168.99.101:32770->8443/tcp   cls-node1/nginx3
d4f8bfbbc188        xataz/nginx:1.9     "tini -- /usr/bin/sta"   22 seconds ago      Up 22 seconds       192.168.99.102:32769->8080/tcp, 192.168.99.102:32768->8443/tcp   cls-node2/nginx2
d72c940c9dc3        xataz/nginx:1.9     "tini -- /usr/bin/sta"   29 seconds ago      Up 29 seconds       192.168.99.103:32769->8080/tcp, 192.168.99.103:32768->8443/tcp   cls-node3/nginx1
28ddd40b6ada        xataz/nginx:1.9     "tini -- /usr/bin/sta"   36 seconds ago      Up 36 seconds       192.168.99.101:32769->8080/tcp, 192.168.99.101:32768->8443/tcp   cls-node1/nginx0

Il est vraiment rapide de multiplier les conteneurs sur un cluster, très pratique avec le script qui va bien de pouvoir gérer un HA proxy par exemple.

Plus loin avec overlay network

Overlay network est un protocole qui permet d'encapsuler un réseau virtuel dans du TCP/IP de manière plutôt simple.
Nous allons recréer un autre cluster, cette fois-ci, directement avec docker-machine, mais nous ajouterons un service de discovery consul (qui est plus qu'un simple discovery).

Commençons par supprimer notre précédent cluster :

$ for cls in cls-master cls-node1 cls-node2 cls-node3; do docker-machine stop $cls && docker-machine rm -f $cls; done
Stopping "cls-master"...
Machine "cls-master" was stopped.
About to remove cls-master
Successfully removed cls-master
Stopping "cls-node1"...
Machine "cls-node1" was stopped.
About to remove cls-node1
Successfully removed cls-node1
Stopping "cls-node2"...
Machine "cls-node2" was stopped.
About to remove cls-node2
Successfully removed cls-node2
Stopping "cls-node3"...
Machine "cls-node3" was stopped.
About to remove cls-node3
Successfully removed cls-node3

Nous créons notre machine qui servira de discovery hors du cluster :

$ docker-machine create -d virtualbox keystore

Puis nous lançons un conteneur consul :

$ docker run -d -p "8500:8500" -h "consul" progrium/consul -server -bootstrap

Voilà, notre service de discovery est prêt. Nous pouvons créer notre cluster.

Créons d'abord le master :

$ docker-machine create -d virtualbox \
--swarm --swarm-master \
--swarm-discovery="consul://$(docker-machine ip keystore):8500" \
--engine-opt="cluster-store=consul://$(docker-machine ip keystore):8500" \
--engine-opt="cluster-advertise=eth1:2376" \
cls-master
  • swarm-master : Permet la création d'un master (swarm manage).
  • swarm : Permet la création d'un node (swarm join).
  • engine-opt : Rajoute une option de lancement à docker-engine, ici nous rajoutons un store, et l'advertise, c'est ceci qui permettra de partager le réseau entre les différents nodes. (A la main, il faudrait modifier le fichier /etc/default/docker, et ajouter les options --cluster-store et --cluster-advertise).

Puis on crée les nodes :

$ docker-machine create -d virtualbox \
--swarm \
--swarm-discovery="consul://$(docker-machine ip keystore):8500" \
--engine-opt="cluster-store=consul://$(docker-machine ip keystore):8500" \
--engine-opt="cluster-advertise=eth1:2376" \
cls-node1

$ docker-machine create -d virtualbox \
--swarm \
--swarm-discovery="consul://$(docker-machine ip keystore):8500" \
--engine-opt="cluster-store=consul://$(docker-machine ip keystore):8500" \
--engine-opt="cluster-advertise=eth1:2376" \
cls-node2

$ docker-machine create -d virtualbox \
--swarm \
--swarm-discovery="consul://$(docker-machine ip keystore):8500" \
--engine-opt="cluster-store=consul://$(docker-machine ip keystore):8500" \
--engine-opt="cluster-advertise=eth1:2376" \
cls-node3

Et c'est tout, notre cluster est opérationnel. On vérifie :

$ eval $(docker-machine env --swarm cls-master)

$ docker info
Containers: 5
 Running: 5
 Paused: 0
 Stopped: 0
Images: 4
Server Version: swarm/1.2.0
Role: primary
Strategy: spread
Filters: health, port, dependency, affinity, constraint
Nodes: 4
 cls-master: 192.168.99.105:2376
  â”” Status: Healthy
  â”” Containers: 2
  â”” Reserved CPUs: 0 / 1
  â”” Reserved Memory: 0 B / 1.021 GiB
  â”” Labels: executiondriver=, kernelversion=4.1.19-boot2docker, operatingsystem=Boot2Docker 1.11.0 (TCL 7.0); HEAD : 32ee7e9 - Wed Apr 13 20:06:49 UTC 2016, provider=virtualbox, storagedriver=aufs
  â”” Error: (none)
  â”” UpdatedAt: 2016-04-21T18:58:43Z
  â”” ServerVersion: 1.11.0
 cls-node1: 192.168.99.106:2376
  â”” Status: Healthy
  â”” Containers: 1
  â”” Reserved CPUs: 0 / 1
  â”” Reserved Memory: 0 B / 1.021 GiB
  â”” Labels: executiondriver=, kernelversion=4.1.19-boot2docker, operatingsystem=Boot2Docker 1.11.0 (TCL 7.0); HEAD : 32ee7e9 - Wed Apr 13 20:06:49 UTC 2016, provider=virtualbox, storagedriver=aufs
  â”” Error: (none)
  â”” UpdatedAt: 2016-04-21T18:59:05Z
  â”” ServerVersion: 1.11.0
 cls-node2: 192.168.99.107:2376
  â”” Status: Healthy
  â”” Containers: 1
  â”” Reserved CPUs: 0 / 1
  â”” Reserved Memory: 0 B / 1.021 GiB
  â”” Labels: executiondriver=, kernelversion=4.1.19-boot2docker, operatingsystem=Boot2Docker 1.11.0 (TCL 7.0); HEAD : 32ee7e9 - Wed Apr 13 20:06:49 UTC 2016, provider=virtualbox, storagedriver=aufs
  â”” Error: (none)
  â”” UpdatedAt: 2016-04-21T18:59:22Z
  â”” ServerVersion: 1.11.0
 cls-node3: 192.168.99.108:2376
  â”” Status: Healthy
  â”” Containers: 1
  â”” Reserved CPUs: 0 / 1
  â”” Reserved Memory: 0 B / 1.021 GiB
  â”” Labels: executiondriver=, kernelversion=4.1.19-boot2docker, operatingsystem=Boot2Docker 1.11.0 (TCL 7.0); HEAD : 32ee7e9 - Wed Apr 13 20:06:49 UTC 2016, provider=virtualbox, storagedriver=aufs
  â”” Error: (none)
  â”” UpdatedAt: 2016-04-21T18:59:22Z
  â”” ServerVersion: 1.11.0
Plugins:
 Volume:
 Network:
Kernel Version: 4.1.19-boot2docker
Operating System: linux
Architecture: amd64
CPUs: 4
Total Memory: 4.085 GiB
Name: 919ac51aa662
Docker Root Dir:
Debug mode (client): false
Debug mode (server): false
WARNING: No kernel memory limit support

C'est beaucoup plus simple et plus rapide avec docker-machine, mais il est important de savoir comment monter son cluster sans docker-machine, cela permet une meilleure compréhension du fonctionnement.

Nous allons maintenant voir comment faire communiquer des machines les unes avec les autres, tout en étant sur des nodes différents.

Commençons par un docker-compose.yml plutôt simple :

version: '2'

networks:
  default:
    driver: bridge

services:
  nginx:
    image: nginx
    networks:
      default:
        aliases:
          - nginx

  php:
    image: php:fpm

Nous avons donc 2 conteneurs, qui doivent communiquer ensemble.
Problème, si je crée ma stack :

$ docker-compose.exe up -d
Creating docker_nginx_1
Creating docker_php_1

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
73865910e75e        php:fpm             "php-fpm"                3 seconds ago       Up 2 seconds        9000/tcp            cls-master/docker_php_1
cf519a2ca125        nginx               "nginx -g 'daemon off"   3 seconds ago       Up 2 seconds        80/tcp, 443/tcp     cls-master/docker_nginx_1

On voit clairement que mes deux conteneurs sont sur le même node.
Et ceux même si on scale le truc :

$ docker-compose.exe scale nginx=10 php=10
Creating and starting docker_nginx_2 ... done
Creating and starting docker_nginx_3 ... done
Creating and starting docker_nginx_4 ... done
Creating and starting docker_nginx_5 ... done
Creating and starting docker_nginx_6 ... done
Creating and starting docker_nginx_7 ... done
Creating and starting docker_nginx_8 ... done
Creating and starting docker_nginx_9 ... done
Creating and starting docker_nginx_10 ... done
Creating and starting docker_php_2 ... done
Creating and starting docker_php_3 ... done
Creating and starting docker_php_4 ... done
Creating and starting docker_php_5 ... done
Creating and starting docker_php_6 ... done
Creating and starting docker_php_7 ... done
Creating and starting docker_php_8 ... done
Creating and starting docker_php_9 ... done
Creating and starting docker_php_10 ... done

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
baab8dc58991        php:fpm             "php-fpm"                11 seconds ago      Up 9 seconds        9000/tcp            cls-master/docker_php_10
470dba210ca1        php:fpm             "php-fpm"                11 seconds ago      Up 9 seconds        9000/tcp            cls-master/docker_php_5
b1bfb880c99c        php:fpm             "php-fpm"                11 seconds ago      Up 9 seconds        9000/tcp            cls-master/docker_php_4
724aeb69465c        php:fpm             "php-fpm"                11 seconds ago      Up 9 seconds        9000/tcp            cls-master/docker_php_3
6c06038615da        php:fpm             "php-fpm"                11 seconds ago      Up 9 seconds        9000/tcp            cls-master/docker_php_9
b3abb75b003a        php:fpm             "php-fpm"                11 seconds ago      Up 10 seconds       9000/tcp            cls-master/docker_php_7
7e9929bc3805        php:fpm             "php-fpm"                11 seconds ago      Up 9 seconds        9000/tcp            cls-master/docker_php_6
398c266a6269        php:fpm             "php-fpm"                11 seconds ago      Up 9 seconds        9000/tcp            cls-master/docker_php_8
144de1b8dcb2        php:fpm             "php-fpm"                11 seconds ago      Up 9 seconds        9000/tcp            cls-master/docker_php_2
6427daf52cf8        nginx               "nginx -g 'daemon off"   12 seconds ago      Up 11 seconds       80/tcp, 443/tcp     cls-master/docker_nginx_4
12cb3b5d341c        nginx               "nginx -g 'daemon off"   12 seconds ago      Up 10 seconds       80/tcp, 443/tcp     cls-master/docker_nginx_9
4f22614426f1        nginx               "nginx -g 'daemon off"   12 seconds ago      Up 11 seconds       80/tcp, 443/tcp     cls-master/docker_nginx_8
b2bc5b7ecf92        nginx               "nginx -g 'daemon off"   12 seconds ago      Up 11 seconds       80/tcp, 443/tcp     cls-master/docker_nginx_10
5ee79c1dd755        nginx               "nginx -g 'daemon off"   12 seconds ago      Up 10 seconds       80/tcp, 443/tcp     cls-master/docker_nginx_5
0e2e38ce02ab        nginx               "nginx -g 'daemon off"   12 seconds ago      Up 11 seconds       80/tcp, 443/tcp     cls-master/docker_nginx_6
396a3fa4ca17        nginx               "nginx -g 'daemon off"   12 seconds ago      Up 11 seconds       80/tcp, 443/tcp     cls-master/docker_nginx_3
308972afce32        nginx               "nginx -g 'daemon off"   13 seconds ago      Up 11 seconds       80/tcp, 443/tcp     cls-master/docker_nginx_7
eed9e62a3a01        nginx               "nginx -g 'daemon off"   13 seconds ago      Up 11 seconds       80/tcp, 443/tcp     cls-master/docker_nginx_2
73865910e75e        php:fpm             "php-fpm"                2 minutes ago       Up 2 minutes        9000/tcp            cls-master/docker_php_1
cf519a2ca125        nginx               "nginx -g 'daemon off"   2 minutes ago       Up 2 minutes        80/tcp, 443/tcp     cls-master/docker_nginx_1

Donc effectivement, nos machines communiquent, mais c'est pas top, et dans ce cas notre cluster ne sert strictement à rien.

Pour corriger ceci, nous allons utiliser un réseau overlay, celui ci permettra de communiquer entre les nodes. Et c'est la que consul utilise "toute" sa puissance.

Pour ce faire, dans notre docker-compose.yml :

version: '2'

networks:
  default:
    driver: overlay

services:
  nginx:
    image: nginx
    networks:
      default:
        aliases:
          - nginx

  php:
    image: php:fpm

On stoppe et supprime notre stack :

$ docker-compose.exe stop && docker-compose rm -f

On supprime le network créé par compose :

$ docker network rm docker_default

et on recrée notre stack:

$ docker-compose up -d
Creating network "docker_default" with driver "overlay"
Creating docker_nginx_1
Creating docker_php_1

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
bb241b726dc7        php:fpm             "php-fpm"                10 seconds ago      Up 8 seconds        9000/tcp            cls-node2/docker_php_1
00c51f680b77        nginx               "nginx -g 'daemon off"   10 seconds ago      Up 8 seconds        80/tcp, 443/tcp     cls-node1/docker_nginx_1

Maintenant il tourne bien sur des nodes différents, testons la communication :

$ docker exec -ti docker_php_1 ping docker_nginx_1
PING docker_nginx_1 (10.0.0.2): 56 data bytes
64 bytes from 10.0.0.2: icmp_seq=0 ttl=64 time=0.413 ms
64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=1.100 ms
64 bytes from 10.0.0.2: icmp_seq=2 ttl=64 time=1.120 ms
64 bytes from 10.0.0.2: icmp_seq=3 ttl=64 time=1.151 ms
64 bytes from 10.0.0.2: icmp_seq=4 ttl=64 time=0.838 ms
^C--- docker_nginx_1 ping statistics ---
5 packets transmitted, 5 packets received, 0% packet loss
round-trip min/avg/max/stddev = 0.413/0.924/1.151/0.279 ms

$ docker exec -ti docker_php_1 ping nginx
PING nginx (10.0.0.2): 56 data bytes
64 bytes from 10.0.0.2: icmp_seq=0 ttl=64 time=0.378 ms
64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=1.155 ms
64 bytes from 10.0.0.2: icmp_seq=2 ttl=64 time=1.081 ms
^C--- nginx ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max/stddev = 0.378/0.871/1.155/0.350 ms

$ docker exec -ti docker_nginx_1 ping docker_php_1
PING docker_php_1 (10.0.0.3): 56 data bytes
64 bytes from 10.0.0.3: icmp_seq=0 ttl=64 time=0.422 ms
64 bytes from 10.0.0.3: icmp_seq=1 ttl=64 time=1.194 ms
64 bytes from 10.0.0.3: icmp_seq=2 ttl=64 time=1.273 ms
^C--- docker_php_1 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max/stddev = 0.422/0.963/1.273/0.384 ms

Hey !!! ça marche !!!

Et si on scale le bordel :

$ docker-compose scale nginx=5 php=5
Creating and starting docker_nginx_2 ... done
Creating and starting docker_nginx_3 ... done
Creating and starting docker_nginx_4 ... done
Creating and starting docker_nginx_5 ... done
Creating and starting docker_php_2 ... done
Creating and starting docker_php_3 ... done
Creating and starting docker_php_4 ... done
Creating and starting docker_php_5 ... done

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
49fdea7ba02e        php:fpm             "php-fpm"                6 seconds ago       Up 5 seconds        9000/tcp            cls-master/docker_php_2
29474ac28a64        php:fpm             "php-fpm"                6 seconds ago       Up 5 seconds        9000/tcp            cls-node3/docker_php_3
af48170254f8        php:fpm             "php-fpm"                6 seconds ago       Up 5 seconds        9000/tcp            cls-node1/docker_php_5
831df683abef        php:fpm             "php-fpm"                6 seconds ago       Up 5 seconds        9000/tcp            cls-node3/docker_php_4
1ad51dd92185        nginx               "nginx -g 'daemon off"   6 seconds ago       Up 6 seconds        80/tcp, 443/tcp     cls-master/docker_nginx_5
fa3b817c5199        nginx               "nginx -g 'daemon off"   6 seconds ago       Up 6 seconds        80/tcp, 443/tcp     cls-node1/docker_nginx_3
7164cf15aa83        nginx               "nginx -g 'daemon off"   6 seconds ago       Up 6 seconds        80/tcp, 443/tcp     cls-node2/docker_nginx_4
51f49569830c        nginx               "nginx -g 'daemon off"   6 seconds ago       Up 6 seconds        80/tcp, 443/tcp     cls-node3/docker_nginx_2
bb241b726dc7        php:fpm             "php-fpm"                4 minutes ago       Up 4 minutes        9000/tcp            cls-node2/docker_php_1
00c51f680b77        nginx               "nginx -g 'daemon off"   4 minutes ago       Up 4 minutes        80/tcp, 443/tcp     cls-node1/docker_nginx_1

Et voilà, notre problème est résolu.

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 :

$ docker swarm # Permet la gestion du cluster
$ docker service # Permet la gestion des conteneurs
$ docker node # Permet la gestion des Nodes

Créons notre cluster

Pour cette exemple, nous créerons 3 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 :

$ 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 ?!

$ 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 :

$ 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 voila notre cluster est créé, pour vérifier nous nous connecterons au master :

$ 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 :

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.

Les services

Le concept de service est nouveau, un service est un conteneur scalable, je peux par exemple créer un service nginx avec 5 conteneurs, qui serons 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 :

docker@master:~$ docker service create --replicas 1 --name web -p 80:8080 xataz/nginx:mainline
cwrfie1hxn8gb2rle8jy6sapx

vérifions si notre service tourne :

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 :

docker@master:~$ docker service scale web=5
web scaled to 5

Puis vérifions :

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 5 replicas, et qu'ils tournent tous.
Puis avec docker service ps web, nous voyons les 5 conteneurs tourner.

Ceci était simplement pour l'exemple, nous allons aller un peu plus loin, nous commençons par supprimer le service :

docker@master:~$ docker service rm web
web
docker@master:~$ docker service ls
ID  NAME  REPLICAS  IMAGE  COMMAND

On va relancer 3 conteneurs nginx, mais en redirigeant le port 80 vers le 8080 :

$ 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 :

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 :

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.

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 du hors-sujet.

Registry

Nous allons ici créer notre propre registry, c'est-à-dire un hub (sans webUI) auto-hébergé. Ce sera un petit chapitre.

Installation de registry

L'installation est simple, il suffit de lancer un conteneur :

$ 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 voila c'est installé, il écoute le port 5000 sur localhost (ou IP conteneur:5000).

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 :

$ 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 :

$ 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

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.

Bonus

Dans cette partie que je nomme bonus, je mettrai quelques astuces, ou des trucs ^^. En fait y'aura un peu de tout et de rien en rapport avec docker, mais qui ne rentrent dans aucune des autres parties.

L'auto Completion

Ceci vous permettra d'avoir la liste des commandes qui s'affiche lorsque vous appuyez sur tab.
exemple :

$ 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'auto complétion pour docker et docker-compose.

En premier, et pour pas se faire avoir comme moi et passer 3 heures à chercher le problème, il faut installer bash-completion :

$ apt-get install bash-completion

Ensuite on télécharge les fichiers d'auto complétion (en root) :

$ wget -O /etc/bash_completion.d/docker https://raw.githubusercontent.com/docker/docker/master/contrib/completion/bash/docker
$ wget -O /etc/bash_completion.d/docker-compose https://raw.githubusercontent.com/docker/compose/1.8.0/contrib/completion/bash/docker-compose

attention à votre version de docker-compose, version 1.8 à 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 :

$ 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 !!

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és sur redhat (centOS, fedora etc ...), n'aime pas trop aufs, et il y a des fois des paquets qui refuse de s'installer, comme par exemple httpd.
Donc il fallait choisir un autre FS, device-mapper, j'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 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 cotés, j'ai configuré le partitionnement de mon serveur avec lvm, et j'ai préparé une partition de 20Go 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 la 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 :

$ 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 :

$ ls /var/lib/docker/
btrfs  containers  graph  linkgraph.db  repositories-btrfs  tmp  trust  volumes

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 (pas de sous-entendu), nous n'avons fait qu'effleurer ces possibilités, qui sont grandement suffisante pour une utilisation personnelle.

Je finirai en beauté, avec cette image trouvé sur un article de wanadev sur docker, qui représente bien la mise en prod avec docker :

Ressources

Quelques github avec des images sympa :

un an plus tard
Magicalex a déverrouillé la discussion.
Répondre…