Overblog Tous les blogs Top blogs Technologie & Science Tous les blogs Technologie & Science
Editer l'article Suivre ce blog Administration + Créer mon blog
MENU

DOCKER

Publié le par damcuvelier

Docker, c'est la solution qui grimpe en ce moment. Vous en avez certainement entendu parler ces derniers temps.

Docker est un produit développé par la société du même nom. Initialement développé par un ingénieur français, Solomon Hykes, le produit a été dévoilé en mars 2013. Depuis cette date, Docker est devenu LE soft à la mode ! Nous allons voir à quoi il sert et comment vous pouvez vous en servir au quotidien.

 

Un peu de blabla (on n'y coupe pas et ça peut être intéressant):

Docker permet de créer des environnements (appelées containers) de manière à isoler des applications. Jusque là, je suis resté très pragmatique…Docker repose sur le kernel Linux et sur une fonctionnalité : les containers, que vous connaissez peut-être déjà sous le doux nom de LXC. L'idée est de lancer du code (ou d'exécuter une tâche, si vous préférez) dans un environnement isolé. Je m'arrêterai ici pour ce qui est des détails des containers Linux, pour plutôt me concentrer sur les fonctionnalités mises en avant par Docker.Enfin, un troisième composant est requis, cgroups qui va avoir pour objectif de gérer les ressources (utilisation de la RAM, CPU entre autres).

Docker utilise simplement des fonctionnalités natives de Linux comme son noyau, ses LXC et cgroups.

Docker et les containers Linux ne se comportent pas de la même manière qu'une VM. Une machine virtuelle isole tout un système (son OS), et dispose de ses propres ressources.

Dans le cas de  Docker, le kernel va partager les ressources du système hôte et interagir avec le(s) container(s). Techniquement, Docker n'est pas une VM, pas le moins du monde, mais en terme d'utilisation, Docker peut-être apparenté à une VM.

Lancer un environnement, et isoler les composants de ce container avec les composants de mon hôte, voilà ce que Docker sait faire ! Il le fait d'ailleurs très bien, et reste une alternative bien plus performante que les VMs (à utilisation équivalente).

 

Un peu de pratique:

Autour de notre baleine (Moby Dock pour les intimes). Docker repose sur une API RESTFul dont je vous parlerai dans un futur article. À cela, l'entreprise a associé un « docker-cli » permettant de facilement exécuter des commandes depuis notre terminal.

Depuis un terminal, si vous exécutez la commande docker, vous obtiendrez une liste de commandes exécutables.

Jusque là, je ne vous ai jamais parlé d'images. Je vous ai uniquement parlé de containers. Et pourtant, les images Docker sont d'une importance cruciale. En voici un peu plus sur ces images, si essentielles dans le fonctionnement de Docker.

Une image est un container statique. On pourrait comparer une image à une capture d'un container à un moment donné, d'une sorte de snapshot d'un de vos containers. Lorsqu'on souhaite travailler avec un container, on déclare forcément un container à partir d'une image.

De plus, les images Docker fonctionnent grâce à de l'héritage d'autres images. Votre image de Tomcat hérite elle-même de l'image de Java. Cette même image de Java qui a peut-être été construite à partir d'une Debian. Les héritages peuvent ainsi aller très loin ! Le container créé à partir d'une image contient le delta entre l'image de base à partir de laquelle le container a été instancié et l'état actuel. Grâce à ce système, la duplication de donnée est faible.

Comment Récupérer (pull) une image Docker ?

Il existe une plateforme maintenue par Docker sur laquelle tout le monde peut pusher et puller des images. C'est un peu comme le GitHub des images Docker ! Dès maintenant, nous allons tenter de récupérer une image.  Il existe des images officielles pour tout et n'importe quoi ! Cette bibliothèque géante partagée, c'est le Docker Hub Registry !

Vous pouvez chercher une image d'une distribution ou d'un produit déjà installé. Commençons par trouver une image Docker que nous pourrions utiliser. Vous disposez de deux moyens pour en chercher une image : via l'interface web, ou via la commande "docker search".

Je vais utiliser la commande "docker search" (car je suis un barbu) pour trouver des distributions Debian, qui possèdent suffisamment d'étoiles (confiance des utilisateurs de la plateforme).

Comment créer une image ?

Désormais nous voulons apprendre à travailler de manière autonome et construire nos images à notre guise. Il existe plusieurs méthodes :

  1. le Dockerfile : déjà vu dans le premier article, il vous permet entre autres de partir d'une image initiale, de lancer des actions et de construire une nouvelle image.
  2. de lancer un container, effectuer des actions soit-même et commiter (avec la commande "docker commit") les modifications dans une nouvelle image.

Aujourd'hui je ne vais pas détailler la deuxième méthode, qui pour moi est moins utile dans un premier temps. A titre personnel, je me sers de cette méthode pour historiser mes personnalisations. Cela me permet de garder des états étape par étape de ma customisation de containers, afin de pouvoir repartir d'une étape bien précise, par exemple (d'où l'utilisation du terme snapshot utilisé plus haut).

Les différences entre un container et son image peuvent être vues grâce à la commande "docker diff".

Créer une image avec un Dockerfile

Ce fichier comporte un mélange d'instructions et de métadonnées. Grâce à ce ficher, vous donnez la recette pour que Docker puisse construire votre image. Le site officiel décrit bien évidemment l'intégralité des instructions qui peuvent être réalisées.

Commençons par créer un fichier nommé Dockerfile (sans extension ! Le nom de ce fichier n'est pas négociable) dans le dossier de notre choix, et indiquons lui quelles sont les prérogatives pour la création d'une nouvelle image. Pour notre exemple, nous souhaitons partir d'une Debian Wheezy (ou Debian 7), dans laquelle nous ajoutons Nginx.

Ligne par ligne, détaillons notre futur Dockerfile :

1 - J'indique la distribution de départ avec la ligne

FROM debian:wheezy

2 - Je nomme la personne qui à écrit ce Dockerfile, ici c'est moi. Grâce à la ligne

MAINTAINER Baptiste Donaux <bdonaux@wanadev.fr>

3 - Je recherche les paquets disponibles et j'installe Nginx.

RUN apt-get update \
    && apt-get install -y \
        nginx

4 - Je copie successivement les configurations et scripts de mon système hôte vers mon image

COPY nginx.conf /etc/nginx/nginx.conf
COPY service_start.sh /home/docker/script/service_start.sh

5 - J'applique les droits pour exécuter mon script

RUN chmod 744 /home/docker/script/service_start.sh

6 - Je définie un point d'entrée : le premier script qui va se lancer au démarrage du container

ENTRYPOINT /home/docker/script/service_start.sh

7 - Le dossier dans lequel je serai quand j'exécuterai un nouveau container sera WORKDIR

WORKDIR /home/docker

Nous avons toutes les lignes pour faire notre Dockerfile. Le voilà en version complete :

FROM debian:wheezy

MAINTAINER Baptiste Donaux <bdonaux@wanadev.fr>

RUN apt-get update \
    && apt-get install -y \
        nginx

COPY nginx.conf /etc/nginx/nginx.conf

COPY service_start.sh /home/docker/script/service_start.sh
RUN chmod 744 /home/docker/script/service_start.sh

ENTRYPOINT /home/docker/script/service_start.sh
WORKDIR /home/docker

Construire une image depuis un Dockerfile

Maintenant que nous avons un Dockerfile, nous voulons créer notre image. Et la commande magique est... « docker build » !

$ docker build .

Lorsque vous exécutez « docker build », vous devez spécifier le chemin du Dockerfile (d'où le point à la fin de la commande si vous lancez la commande depuis le même endroit).

Durant la construction vous allez voir défiler toutes les étapes s'exécuter les une après les autres. Le premier lancement est long, car aucune des étapes n'a été déjà appelée. Lors d'un second build, vous verrez que les étapes sont expédiées à grande vitesse ! (Essayez, pour voir !). Tout ça à condition que les étapes précédentes n'aient pas été modifiées dans votre Dockerfile, bien sûr !

Évidemment, Docker historise toutes les actions qu'il réalise (chaque instruction réalisée dans un Dockerfile est stockée dans un container intermédiaire). C'est également ce principe qui est utilisé pour réaliser des héritages entre images. Si les instructions sont inchangées et que leur ordre est identique à la précédente fois, elles ne sont PAS régénérées car Docker sait utiliser son cache et ré-utiliser les actions déjà réalisées.

Tagger une image

Toute image ou tout container possède un identifiant unique. Nous pourrions utiliser ces identifiants unique, mais Docker Inc a prévu une fonctionnalité pour tagger nos images ainsi que nos containers afin de les réutiliser plus facilement.

Voici comment nommer vos images :

  1. Donner un nom à la construction de l'image avec l'option --tag
  2. Utiliser la commande docker-tag
  3. Nommer votre image au moment de l'utilisation de docker-commit

Exemple plus concret : création d'une image grâce à la commande docker-build et de son option --tag

$ docker build --tag="myImage[:myTag]"

Et voilà, faites désormais un docker-images et vous votre image correctement taggée apparaîtra dans votre liste !

Gestion des containers

Définir le fonctionnement d'un container

Nous en avons déjà parlé au-dessus, un container incarne la notion d'environnement. Dans Docker, même les images sont des containers, mais la différence entre une image et un container est dûe aux comportements.

Les images sont statiques ; on n'écrit pas directement dans une image. On déclare un container À PARTIR d'une image. Ce container va vivre et contenir les différences réalisés par rapport à l'image de base. Vous suivez ?

Un container contient également d'autres éléments, il stocke des paramètres d'exécutions, comme par exemple le mappage de port, les dossiers à partager.

 

Le point technique !

Actuellement on applique des paramètres à un container comme le forward de port, le partage de dossier… Mais il n'est pas possible de changer à chaud ses paramètres. Docker Inc. travaille également sur la possibilité de changer à chaud ses variables. Actuellement, le seul moyen pour nous de changer les paramètres est de :
  1. Enregistrer son container comme image (docker commit)
  2. Lancer un container à partir de la nouvelle image avec l'intégralité des paramètres que vous souhaitez utiliser.

Faisons ensemble un exercice.

Déclarer un container

Nous avons normalement une image debian avec plusieurs tags sur notre machine grâce à l'image créée plus haut. Déclarons un container.

$ docker run debian:wheezy

Et voilà, vous avez exécuté un container depuis une image Docker ! Avec cela, ne pensez pas faire des choses dingues, mais c'est un début. Si vous avez l'impression que cette commande n'a rien fait, vous pouvez voir avec la commande "docker ps -l" que des choses se sont passées

Même si votre container est éteint (statut Exited), celui-ci n'est pas supprimé, il est stocké (c'est important pour la suite). Vous pouvez d'ailleurs afficher tous les containers (ceux en cours et ceux stoppés) en utilisant la commande "docker ps -a".

Désormais, nous voulons interagir avec notre container. Pour cela, nous allons devoir utiliser quelques options de la commande "docker run", que voici :

$ docker run --tty --interactive debian:7

Vous devriez être dans un magnifique container avec un bash prêt à répondre. Vous remarquerez le temps de lancement est assez faible. Mais qu'avons-nous fait ? Détaillons ce qu'elle veut dire :

  • L'option --tty permet d'attacher la console à notre console actuelle et de ne pas perdre le focus. C'est grâce à cette option que votre container ne va pas se terminer.
  • L'option --interactive vous permet de dialoguer avec votre container. Sans cette option, tout ce que vous taperez dans votre console ne sera pas transmis au bash du container.

Dans notre cas, la commande lancée a été /bin/bash mais il ne s'agit pas d'un hasard. Lors de la déclaration d'une image (dans son Dockerfile), vous pouvez spécifier certaines metadatas comme CMD qui vous permettent de donner une commande par défaut si aucune n'est spécifiée.

Si vous aviez voulu lancer une autre commande que /bin/bash, vous auriez pu faire comme ceci au moment de la déclaration de votre container :

$ docker run debian:7 echo Docker is fun

Et voilà, votre container a été créé et a lancé la commande que vous avez donné ("echo Docker is fun"), puis s'est terminé car aucune commande exécutée n'a pris le focus de la console.

Utiliser un container

Maintenant que vous savez lancer des commandes dans un container, allons un peu plus loin avec un exemple simple.

Un container est l'équivalent d'un Filesystem. Aussi, mon projet Symfony utilise une base de données. Puisque ma base de données va changer, nous ne voulons pas perdre nos données (enfin, j'imagine ;) !). Ce serait le cas si vous faisiez un "docker run" à chaque fois que vouliez lancer votre service de base de données. Pourquoi ? Car comme vu plus haut, le "docker run" lance un nouveau container. Un nouveau ! Il n'utilise pas celui qui avait déjà été créé auparavant ! Voyons comment faire pour ne pas avoir ce problème.

Détaillons le fonctionnement de "docker run"

Au début, c'était LA (!) commande que j'utilisais tout le temps. Maintenant plus vraiment. Que fait vraiment le "docker run" ? Cette commande exécute successivement deux autres commandes auxquelles vous avez accès : "docker create" et "docker start". Détaillons les ensemble.

docker-create

"docker create" est une commande indispensable ! Si vous faites un man, vous remarquerez que celle-ci contient les mêmes options que "docker run". Avec cette commande, vous allez créer un container à partir d'une image en lui donnant des paramètres d'exécution.

Parmi les données paramétrables, vous allez pouvoir donner un nom à votre container. Docker donnera toujours un nom à votre container (furious_hopper, vous vous souvenez ?), mais il vous sera plus simple de retrouver le container de votre projet avec un nom adéquat plutôt que "condescending_wozniak" ! XD

Voici la commande complète pour créer un container, celle que vous utiliserez désormais !

$ docker create --tty --interactive --name="wonderful_mobidock" debian:7

Le retour de cette commande devrait être une suite indigeste de caractères (indiquant l'ID donné à votre container).

docker-start

Notre container est désormais créé grâce à la commande "docker create". D'ailleurs en faisant un "docker ps -a" vous devriez le trouver.

Maintenant que vous avez créé votre container et que vous l'avez configuré, vous pouvez le lancer avec la commande "docker start".

$ docker start wonderful_mobidock

Par défaut, "docker start" ne vous attache pas la console, mais vous pouvez le spécifier avec l'option --attach.

$ docker start --attach wonderful_mobido

Voilà qui est simple non ?

Ré-utiliser un container

Notre container est lancé et nommé. Notre base de données vit dans notre container, et d'une fois sur l'autre nous ne voulons pas perdre nos données (ce qui serait dommageable avouons-le). La commande "docker stop" permet d'éteindre proprement un container (l'utilisation de "docker kill" doit être limitée).

Si plus tard, vous voulez relancer ce wonderful_mobydock  et récupérer vos données, exécutez "docker start" !

Les commandes et Docker

Docker, un service avant tout

Docker est un service, et comme tout service, avant de répondre à des commandes user-friendly il répond à une API. Cette API RESTFul est par conséquent disponible via IP ou via une socket. Par défaut, quand vous utilisez docker-cli, les commandes sont réalisées sur votre socket local, généralement dans /run/docker.sock.

Bien évidemment, vous pouvez configurer cette entrée.

$ docker
…
Options:
  --api-enable-cors=false                        Enable CORS headers in the remote API
  -b, --bridge=""                                Attach containers to a pre-existing network bridge
                                                   use 'none' to disable container networking
  --bip=""                                       Use this CIDR notation address for the network bridge's IP, not compatible with -b
  -D, --debug=false                              Enable debug mode
  -d, --daemon=false                             Enable daemon mode
  --dns=[]                                       Force Docker to use specific DNS servers
  --dns-search=[]                                Force Docker to use specific DNS search domains
  -e, --exec-driver="native"                     Force the Docker runtime to use a specific exec driver
  --fixed-cidr=""                                IPv4 subnet for fixed IPs (ex: 10.20.0.0/16)
                                                   this subnet must be nested in the bridge subnet (which is defined by -b or --bip)
  -G, --group="docker"                           Group to assign the unix socket specified by -H when running in daemon mode
                                                   use '' (the empty string) to disable setting of a group
  -g, --graph="/var/lib/docker"                  Path to use as the root of the Docker runtime
  -H, --host=[]                                  The socket(s) to bind to in daemon mode or connect to in client mode, specified using one or more tcp://host:port, unix:///path/to/socket, fd://* or fd://socketfd.
  --icc=true                                     Enable inter-container communication
  --insecure-registry=[]                         Enable insecure communication with specified registries (no certificate verification for HTTPS and enable HTTP fallback) (e.g., localhost:5000 or 10.20.0.0/16)
  --ip=0.0.0.0                                   Default IP address to use when binding container ports
  --ip-forward=true                              Enable net.ipv4.ip_forward
  --ip-masq=true                                 Enable IP masquerading for bridge's IP range
  --iptables=true                                Enable Docker's addition of iptables rules
  --mtu=0                                        Set the containers network MTU
                                                   if no value is provided: default to the default route MTU or 1500 if no default route is available
  -p, --pidfile="/var/run/docker.pid"            Path to use for daemon PID file
  --registry-mirror=[]                           Specify a preferred Docker registry mirror
  -s, --storage-driver=""                        Force the Docker runtime to use a specific storage driver
  --selinux-enabled=false                        Enable selinux support. SELinux does not presently support the BTRFS storage driver
  --storage-opt=[]                               Set storage driver options
  --tls=false                                    Use TLS; implied by tls-verify flags
  --tlscacert="/home/baptiste/.docker/ca.pem"    Trust only remotes providing a certificate signed by the CA given here
  --tlscert="/home/baptiste/.docker/cert.pem"    Path to TLS certificate file
  --tlskey="/home/baptiste/.docker/key.pem"      Path to TLS key file
  --tlsverify=false                              Use TLS and verify the remote (daemon: verify client, client: verify daemon)
  -v, --version=false                            Print version information and quit
…

Parmi ces options, vous remarquerez --host qui vous permettra de travailler avec un autre serveur via une IP par exemple. De cette manière, on peut très bien imaginer déployer un nouveau container sur un serveur de production sans pour autant se connecter via SSH. Cette partie peut paraitre innocente, mais a pour objectif de faciliter les évolutions du serveur (la partie core), du client (la partie cli), mais également de permettre le développement d'applications tierces ((il s'agit de l'API JSON).

Un peu plus loin dans l'utilisation de l'API

Il est une chose de se servir des commandes dans un terminal manuellement, mais peut-être aurez-vous le besoin de créer une application permettant de manager vos images et vos containers un peu plus facilement.

Pour ma part, j'ai testé l'API JSON avec un projet « maison » disponible sur GitHub. Dans cette petite application web, je contrôle la mise en pause et l’extinction des containers. C'est peu, mais c'est un extrait de ce que l'on peut faire.

Même si Docker a vocation à être utilisé par de gros systèmes ainsi que par des personnes initiées, il peut être bien dans certains cas d'avoir un peu de visuels. D'ailleurs, des solutions ont vu le jour pour monitorer ces containers. Celle-ci ne se repose pas en intégralité sur Docker, mais également sur le composant cgroup, l'un des trois composants nécessaires au fonctionnement de Docker.

Vous pouvez imaginer toutes sortes d'applications, et même manager un cluster en lançant des opérations sur plusieurs serveurs depuis la même console. Pour ce qui est de la configuration des serveurs, vous devez savoir que chaque instance devra être lancée comme démon et devra écouter une plage d'IP et un port. Je vous laisse chercher comment réaliser cette opération.

Conclusion

Docker est avant tout une API. Il existe plusieurs moyens de gérer ces containers. En plus des commandes que l'on utilise par défaut.  L'intégralité de la documentation est disponible ici.

 

 

J'espère avoir été assez clair.

enjoy

ps: un tuto docker sous windows très bien fait:

https://www.noobunbox.net/virtualisation/installer-docker-sous-windows

 

 

 

 

Commenter cet article