accueilRessourcesLogiciels
 

Introduction à Docker

Cet article est organisé comme un tutoriel sur Docker, un outil permettant de gérer facilement des conteneurs Linux


NOTE : cet article a été rédigé en avril 2014, Docker était en version 0.9. Il présente le fonctionnement de base de Docker, et est donc toujours valide. Cependant, n’hésitez pas à regarder la documentation officielle ou d’autres tutoriels plus récents, car Docker a énormément évolué !

Introduction

Une application est essentiellement composée de code, mais également de fichiers de configurations, de droits spécifiques, de répertoires particuliers, de comptes, de paramétrage, etc. La livraison d’une application d’un environnement à un autre (par exemple d’un environnement de développement à celui d’intégration, ou de la recette vers la production) nécessite donc généralement de livrer l’application, sous forme d’archive à extraire par exemple, et de procéder à son installation, en prenant soin de bien respecter tous les prérequis et procédures.

Cette tâche est souvent délicate et manuelle. Parfois, elle peut être très risquée quand une application déjà installée nécessite la version X de telle librairie, et que l’application que l’on souhaite déployer sur le même serveur requiert la version Y de cette même librairie. Une solution serait de livrer directement une machine virtuelle déjà configurée. L’inconvénient est qu’une VM prend beaucoup de ressources de la machine hôte, et est volumineuse car elle doit contenir un OS complet.

Docker propose une alternative entre les deux : l’unité de livraison est entre le zip (de l’application) et la VM. On déploie un conteneur. Un conteneur contient l’ensemble des fichiers nécessaires au bon fonctionnement de l’application, c’est-à-dire notre application elle-même, mais également les autres programmes (Python, MySQL, ...), les librairies (libcurl, ...), la configuration (/etc/lighttpd/lighttpd.conf, ...), etc. Par contre, il ne contient pas un OS entier, il utilise les ressources du noyau hôte.

L’idée n’est pas nouvelle, Docker s’appuie sur LXC qui est la technologie Linux de cloisonnement (tout comme les Jails de FreeBSD). Docker propose un ensemble d’outils pour rendre simple et efficace l’utilisation de LXC.

Cet article va montrer à travers deux exemples l’utilisation de Docker. Le premier exemple a pour objectif de créer un conteneur avec Tomcat, et d’accéder à ce serveur depuis la machine hôte. Le second exemple montrera un exemple plus complexe de mise en cluster de trois serveurs web.

Installation

Docker ne fonctionne que sur Linux, et nécessite un noyau récent (>= 3.8) avec LXC activé. Se référer à cette page pour l’installation de Docker.

Premier exemple : installation de Tomcat

Prise en main

Docker fonctionne à partir d’images, qui correspondent à une distribution Linux. Il est tout-à-fait possible d’avoir un conteneur faisant tourner CentOS sur un hôte Ubuntu. Ensuite, à partir de cette image, nous pouvons lancer un conteneur, et travailler dedans (installer des logiciels, ...). Ensuite, nous pouvons effectuer un commit sur ce conteneur, et s’en servir pour une base ultérieure.

La première étape est donc de récupérer une image avec docker pull. Docker propose au téléchargement plusieurs images. Attention toutefois à leur provenance (privilégier les images officielles) pour la sécurité.

steph@ubuntu $ docker pull ubuntu
Pulling repository ubuntu
eb601b8965b8 : Download complete
9cc9ea5ea540 : Download complete
9f676bd305a4 : Download complete
9cd978db300e : Download complete
5ac751e8d623 : Download complete
511136ea3c5a : Download complete
f323cf34fd77 : Download complete
7a4f87241845 : Download complete
1c7f181e78b9 : Download complete
6170bb7b0ad1 : Download complete
321f7f4200f4 : Download complete

Nous avons récupéré les images pour Ubuntu. Nous pouvons voir ici que Docker fonctionne par snapshots, c’est ce qui permet d’avoir des conteneurs aussi légers. Pour visualiser les images récupérées, taper la commande docker images.

steph@ubuntu $ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
ubuntu              13.10               9f676bd305a4        7 weeks ago         178 MB
ubuntu              saucy               9f676bd305a4        7 weeks ago         178 MB
ubuntu              raring              eb601b8965b8        7 weeks ago         166.5 MB
ubuntu              13.04               eb601b8965b8        7 weeks ago         166.5 MB
ubuntu              12.10               5ac751e8d623        7 weeks ago         161 MB
ubuntu              quantal             5ac751e8d623        7 weeks ago         161 MB
ubuntu              10.04               9cc9ea5ea540        7 weeks ago         180.8 MB
ubuntu              lucid               9cc9ea5ea540        7 weeks ago         180.8 MB
ubuntu              12.04               9cd978db300e        7 weeks ago         204.4 MB
ubuntu              latest              9cd978db300e        7 weeks ago         204.4 MB
ubuntu              precise             9cd978db300e        7 weeks ago         204.4 MB

Nous pouvons donc maintenant lancer un conteneur à partir d’une de ces images avec la commande docker run. Afin de montrer la rapidité, je préfixe la commande avec time :

steph@ubuntu $ time docker run -d ubuntu:saucy sh -c 'while true ; do echo hello world ; sleep 1 ; done'
756ca2e21bee1f11178cfe2d60bcfaf96f36424c92373410197a3953c2937707

real 0m0.088s user 0m0.009s sys 0m0.008s

La syntaxe de la commande indique de lancer en mode détaché (non-interactif) Ubuntu version Saucy et d’exécuter la commande shell indiquée.

En moins d’un dixième de seconde, j’ai lancé une instance d’Ubuntu avec un shell qui tourne en boucle. Il est possible de vérifier que le conteneur fonctionne avec la commande docker ps :

steph@ubuntu $ docker ps
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS               NAMES
756ca2e21bee        ubuntu:13.10        sh -c while true ; do   2 minutes ago       Up 2 minutes                            desperate_bell

Nous voyons que nous avons un identifiant attribué, et la commande qui tourne. Mais comment être sûr que la commande tourne vraiment ? Nous pouvons utiliser la commande docker logs pour voir la sortie standard d’un conteneur :

steph@ubuntu $ docker logs 756ca2e21bee | tail
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world

Cette commande, comme beaucoup d’autres, utilise l’identifiant du conteneur. Comme pour Git, il est possible de ne mettre que le début tant qu’il n’y a pas d’ambiguïté. Nous pouvons également utiliser le nom du conteneur (dernière colonne de docker ps).

Une autre façon est d’attacher un terminal au conteneur avec la commande docker attach :

steph@ubuntu $ docker attach —sig-proxy=false 75
hello world
hello world
hello world
hello world
^C

Ici, nous indiquons avec —sig-proxy=false que nous ne voulons pas que les signaux soient propagées au processus tournant dans le conteneur. Cela permet de faire CTRL-C pour quitter le conteneur, sans quitter le processus qui y tourne.

Pour arrêter un conteneur, nous pouvons utiliser la commande docker stop, qui fait essentiellement un kill puis un kill -9 le cas échéant. Une autre façon est de quitter le processus qui tourne dans le conteneur. En effet, quand Docker lancer le conteneur, il exécute la commande que nous lui donnons. Dès que ce processus se termine, Docker quitte le conteneur.

steph@ubuntu $ docker stop desperate_bell
desperate_bell

Si nous regardons la liste des conteneurs actifs, elle est vide :

steph@ubuntu $ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

Par contre, le conteneur est toujours présent, mais arrêté. Nous pouvons le voir avec la commande docker ps -a :

steph@ubuntu $ docker ps -a
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS               NAMES
756ca2e21bee        ubuntu:13.10        sh -c while true ; do   13 minutes ago      Exit -1                                 desperate_bell

Quel est l’intérêt ? Cela permet de redémarrer ce conteneur avec docker start, ou de créer une nouvelle image, réutilisable, à partir de ce conteneur. Nous allons le voir dans la partie suivante.

Création manuelle d’une image

Une image est un snapshot d’un conteneur. Une façon de créer une image personnalisée est de démarrer un conteneur, d’y installer et de configurer ce que nous souhaitons :

steph@ubuntu $ docker run -i -t ubuntu:saucy bash
root@9f36d4fab32f :/# ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.8  0.0  18104  1876 ?        Ss   14:26   0:00 bash
root        10  0.0  0.0  15568  1148 ?        R+   14:27   0:00 ps aux
root@9f36d4fab32f :/#

Ici, nous avons demandé à Docker de lancer un conteneur (run), en mode iteractif (-i) et en y attachant un pseudo-terminal tty (-t). L’image utilisé est le tag saucy d’Ubuntu, et nous demandons à Docker de lancer la commande bash.

Nous nous retrouvons alors avec un shell root. Le nom de la machine (9f36d4fab32f) est l’identifiant du conteneur. Nous pouvons voir que seul le processus bash tourne.

Si nous quittons le shell, Docker arrête. Ici, nous conservons le shell et nous allons installer Java dans le conteneur. L’installation se faite de manière tout-à-fait classique (la sortie complète des commandes n’est pas affichée) :

root@9f36d4fab32f :/# apt-get install software-properties-common
root@9f36d4fab32f :/# add-apt-repository ppa:webupd8team/java
root@9f36d4fab32f :/# apt-get update
root@9f36d4fab32f :/# apt-get install oracle-java8-installer
root@9f36d4fab32f :/# echo "JAVA_HOME=/usr/lib/jvm/java-8-oracle" >> /etc/environment

Nous pouvons vérifier que Java 8 est bien installé dans le conteneur :

root@9f36d4fab32f :/# java -version
java version "1.8.0"
Java(TM) SE Runtime Environment (build 1.8.0-b132)
Java HotSpot(TM) 64-Bit Server VM (build 25.0-b70, mixed mode)

Nous pouvons maintenant quitter le conteneur en quittant le shell que nous avons lancé auparavant :

root@9f36d4fab32f :/# exit

En listant les conteneurs existants, nous retrouvons celui dans lequel nous avons installé Java :

steph@ubuntu $ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
9f36d4fab32f        ubuntu:13.10        bash                About an hour ago   Exit 0                                  angry_ritchie

Nous pouvons alors utiliser la commande docker commit pour en faire une image :

steph@ubuntu $ docker commit 9f36d4fab32f demo/java8
7bf2b6313e1336cb812bd636e85337255fc9a1b25320ad0caa3df27729fb3174

Nous avons maintenant une image, nommée demo/java8, dans laquelle Java est installé. Nous pouvons réutiliser cette image pour lancer d’autres conteneurs, ou construire d’autres images sur cette base :

steph@ubuntu $ docker images | head -4
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
demo/java8          latest              7bf2b6313e13        51 seconds ago      716.1 MB
ubuntu              13.10               9f676bd305a4        7 weeks ago         178 MB
ubuntu              saucy               9f676bd305a4        7 weeks ago         178 MB

steph@ubuntu $ time docker run -t demo/java8 java -version java version "1.8.0" Java(TM) SE Runtime Environment (build 1.8.0-b132) Java HotSpot(TM) 64-Bit Server VM (build 25.0-b70, mixed mode)

real 0m0.321s user 0m0.008s sys 0m0.015s

Nous voyons que nous avons bien Java 8, et il faut moins d’une seconde pour lancer ce conteneur et exécuter la commande.

Nous allons maintenant construire une autre image avec Tomcat, puis nous allons lancer Tomcat et accéder à la page web depuis notre machine hôte. Pour cela, nous allons lancer un conteneur sur la base de l’image demo/java8 et ouvrir un shell, puis installer Tomcat.

steph@ubuntu $ docker run -i -t demo/java8 bash
root@03748ad26d04 :/# mkdir tomcat && cd tomcat
root@03748ad26d04 :/tomcat# wget http://apache.crihan.fr/dist/tomcat...
root@03748ad26d04 :/tomcat# apt-get install unzip
root@03748ad26d04 :/tomcat# unzip apache-tomcat-8.0.5.zip
root@03748ad26d04 :/tomcat# chmod +x apache-tomcat-8.0.5/bin/*.sh
root@03748ad26d04 :/tomcat# exit

Nous allons créer l’image correspondante :

steph@ubuntu $ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
03748ad26d04        demo/java8:latest   bash                12 minutes ago      Exit 130                                desperate_bardeen

steph@ubuntu $ docker commit 03748ad26d04 demo/tomcat8 c539f0f7375f37a30fbf8b7c5f77f5a7f31b3062f0ddbaa6c57f128a0eadcc66

steph@ubuntu $ docker images | head -4 REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE demo/tomcat8 latest c539f0f7375f 26 seconds ago 740 MB demo/java8 latest 7bf2b6313e13 30 minutes ago 716.1 MB ubuntu 13.10 9f676bd305a4 7 weeks ago 178 MB

Et nous vérifions que Tomcat est bien installé :

steph@ubuntu $ docker run -t demo/tomcat8 tomcat/apache-tomcat-8.0.5/bin/version.sh
Using CATALINA_BASE :   /tomcat/apache-tomcat-8.0.5
Using CATALINA_HOME :   /tomcat/apache-tomcat-8.0.5
Using CATALINA_TMPDIR : /tomcat/apache-tomcat-8.0.5/temp
Using JRE_HOME :        /usr
Using CLASSPATH :       /tomcat/apache-tomcat-8.0.5/bin/bootstrap.jar :/tomcat/apache-tomcat-8.0.5/bin/tomcat-juli.jar
Server version : Apache Tomcat/8.0.5
Server built :   Mar 24 2014 05:29:50
Server number :  8.0.5.0
OS Name :        Linux
OS Version :     3.11.0-18-generic
Architecture :   amd64
JVM Version :    1.8.0-b132
JVM Vendor :     Oracle Corporation

Lancement du Tomcat présent dans le conteneur

Maintenant, nous allons pouvoir lancer ce conteneur en mode détaché, en démarrant Tomcat. Tomcat écoute sur le port 8080, nous allons indiquer à Docker de faire correspondre le port local 8181 au port du conteneur 8080 en utilisant le flag -p :

steph@ubuntu $ docker run -d -p 8181:8080 demo/tomcat8 tomcat/apache-tomcat-8.0.5/bin/catalina.sh run
7aa9e1e5a806fd769684606335c2eb844bd963fb6836f64a5fe1183468cdcd1f

steph@ubuntu $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 7aa9e1e5a806 demo/tomcat8:latest tomcat/apache-tomcat 8 seconds ago Up 7 seconds 0.0.0.0:8181->8080/tcp cranky_bardeen

Nous pouvons voir la correspondance des ports dans le résultat de la commande docker ps. Nous pouvons donc maintenant accéder à Tomcat, qui tourne dans le conteneur, via le port 8181 :

steph@ubuntu $ curl -i -s http://localhost:8181 | head -5
HTTP/1.1 200 OK
Server : Apache-Coyote/1.1
Content-Type : text/html ;charset=UTF-8
Transfer-Encoding : chunked
Date : Sat, 29 Mar 2014 17:09:38 GMT

Nous avons donc maintenant une image Tomcat, réutilisable, à partir de laquelle nous pouvons créer d’autres images qui contiendront une application complète, avec par exemple un MySQL. Le déploiement de ce conteneur et son utilisation sont extrêmement faciles.

Deuxième exemple : cluster de trois serveurs web, et standardisation de la construction d’images

Nous avons vu comment créer des images avec les logiciels souhaités, comment lancer des conteneurs à partir de ces images, et comment y accéder depuis le serveur hôte. Nous allons maintenant aller un peu plus loin avec l’utilisation des Dockerfile permettant la construction automatique d’une image, et l’option —link pour faire communiquer les conteneurs entre-eux.

Nous allons créer une image contenant un serveur Web (ultra-simple). L’idée est de lancer 3 conteneurs avec cette même image, et d’avoir un quatrième conteneur faisant office de load balancer.

Création de l’image du serveur web

Nous allons commencer par le serveur Web en Python. Créer en local un répertoire webserver et y créer le fichier Server.py avec le contenu suivant :

from BaseHTTPServer import HTTPServer
from BaseHTTPServer import BaseHTTPRequestHandler
import socket

PORT = 8080

class Handler(BaseHTTPRequestHandler) : def do_GET(s) : s.send_response(200) s.send_header("Content-type", "text/plain") s.end_headers() s.wfile.write("Host is : %s" % socket.gethostname())

if __name__ == '__main__' : httpd = HTTPServer(("", PORT), Handler) print "serving at port", PORT try : httpd.serve_forever() except KeyboardInterrupt : pass httpd.server_close()

Ce mini-serveur web peut être lancé avec la commande python Server.py, et il répond aux requêtes avec le nom de la machine sur laquelle il tourne.

Afin d’automatiser la création de notre image de serveur web, nous allons utiliser un Dockerfile. C’est un fichier texte qui indique à Docker comment construire notre image. Voici tout de suite celui utilisé pour le serveur web :

FROM ubuntu:saucy
MAINTAINER Stéphane Deraco

# Install python RUN apt-get update && apt-get -y install python

# Copy Python WebServer file ADD Server.py /

# Run Python Server EXPOSE 8080 CMD ["python", "Server.py"]

Il est constitué d’une liste de commandes qui seront exécutées par Docker :

  • FROM indique à partir de quelle image construire celle-ci
  • MAINTENER précise qui a construit et maintient l’image
  • les commandes RUN sont exécutées telles quelles dans le conteneur
  • les commandes ADD permettent de copier un fichier local dans le conteneur
  • les commandes EXPOSE indiquent à Docker que nous souhaitons interagir plus tard avec un port donné de ce conteneur (nous utiliserons ce principe à la fin)
  • enfin, la commande CMD indique quelle commande lancer par défaut au démarrage du conteneur

Pour lancer la création de l’image, se placer dans le répertoire où se trouve ce fichier nommé Dockerfile, et lancer la construction avec la commande docker build (sortie tronquée) :

steph@ubuntu $ docker build -t demo/webserver .
Uploading context 3.584 kB
Uploading context
Step 0 : FROM ubuntu:saucy
 ---> 9f676bd305a4
Step 1 : MAINTAINER Stéphane Deraco
 ---> Running in a58cc28c3b7f
 ---> 20c652072321
Step 2 : RUN apt-get update && apt-get -y install python
 ---> Running in f5e218a1c67d
...
Setting up python (2.7.5-5ubuntu1) ...
 ---> e819588d7b3f
Step 3 : ADD Server.py /
 ---> 9c8c8a18043d
Step 4 : EXPOSE 8080
 ---> Running in 4ffd8d1e75e9
 ---> 9db3b0957a85
Step 5 : CMD ["python", "Server.py"]
 ---> Running in 77bd79e266bc
 ---> 64df247beec9
Successfully built 64df247beec9
Removing intermediate container a58cc28c3b7f
Removing intermediate container f5e218a1c67d
Removing intermediate container e14c4d966871
Removing intermediate container 4ffd8d1e75e9
Removing intermediate container 77bd79e266bc

Nous pouvons voir qu’à chaque commande, Docker l’exécute dans un conteneur différent, basé sur le précédent. Cela permet de réutiliser des étapes et d’économiser du temps. Par exemple, si je relance la même commande de build (qui récupère depuis internet divers paquets et les installe), cela dure moins de deux dixièmes de secondes, car Docker a détecté qu’il pouvait réutiliser des conteneurs précédents :

steph@ubuntu $ time docker build -t demo/webserver .
Uploading context 3.584 kB
Uploading context
Step 0 : FROM ubuntu:saucy
 ---> 9f676bd305a4
Step 1 : MAINTAINER Stéphane Deraco
 ---> Using cache
 ---> 20c652072321
Step 2 : RUN apt-get update && apt-get -y install python
 ---> Using cache
 ---> e819588d7b3f
Step 3 : ADD Server.py /
 ---> Using cache
 ---> 9c8c8a18043d
Step 4 : EXPOSE 8080
 ---> Running in a8bc5545f171
 ---> f332c7e9edf0
Step 5 : CMD ["python", "Server.py"]
 ---> Running in 66e13645f55c
 ---> 819f50d5b0ab
Successfully built 819f50d5b0ab
Removing intermediate container a8bc5545f171
Removing intermediate container 66e13645f55c

real 0m0.194s user 0m0.018s sys 0m0.006s

Nous pouvons voir notre image avec docker images :

steph@ubuntu $ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED              VIRTUAL SIZE
demo/webserver      latest              819f50d5b0ab        About a minute ago   204.9 MB

Création de l’image du load balancer

Nous allons maintenant attaquer la création de notre image de load balancer. J’utilise ici Lighttpd comme front-end. Nous allons procéder comme pour le serveur web : créer un répertoire loadbalancer, dans lequel on créera notre fichier Dockerfile.

Pour configurer Lighttpd en mode load balancer, il faut lui indiquer de charger le module mod_proxy. Nous lui indiqueront de faire du round-robin afin de pouvoir observer, dans la réponse du serveur web en Python, le changement du hostname à chaque requête. Par contre, la configuration attend une liste semblable à celle-ci ;

(
	( "host" => "adresse.ip.1", "port" => port.1),
	( "host" => "adresse.ip.2", "port" => port.2),
	...
)

Il nous faut donc un moyen de récupérer l’adresse IP associée aux conteneurs des serveurs web. Nous pouvons récupérer l’adresse IP (entre autres informations) d’un conteneur qui tourne avec la commande docker inspect :

steph@ubuntu $ docker inspect -f '.NetworkSettings.IPAddress' 456c241a0c3f
172.17.0.2

Le flag -f permet d’indique un template Go pour requêter les résultats.

Faire communiquer les conteneurs entre-eux

Le problème est que depuis le conteneur du load balancer, nous ne pouvons pas lancer la commande docker inspect qui est au niveau de l’hôte. Il nous faut donc un autre mécanisme. C’est ici que nous allons utiliser la notion EXPOSE du Dockerfile.

Dans le Dockerfile du server web, nous avons mis la ligne EXPOSE 8080. Le fait d’avoir mis cette ligne va permettre d’utiliser le flag —link de la commande docker run. Quand nous allons lancer un conteneur avec ce flag, nous aurons accès à des variables d’environnement indiquant avec quel serveur (IP, port) nous sommes lié.

Voyons un exemple plus concret. Lançons une instance de notre image demo/webserver (qui a EXPOSE 8080 dans son image) :

steph@ubuntu $ docker run -d —name server1 demo/webserver
252905edf7d108cb41fa5100ec8323ccc2e882471a012e9fdfde28747d49e7b0

steph@ubuntu $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 252905edf7d1 demo/webserver:latest python Server.py 2 seconds ago Up 1 seconds 8080/tcp server1

Nous retrouvons le port 8080 dans la colonne PORTS. Lançons maintenant un autre conteneur, et demandons-lui d’afficher les variables d’environnement :

steph@ubuntu $ docker run —rm -t ubuntu:saucy env
HOME=/
PATH=/usr/local/sbin :/usr/local/bin :/usr/sbin :/usr/bin :/sbin :/bin
HOSTNAME=56c74bbbf634
TERM=xterm

Le flag —rm permet de supprimer automatiquement le conteneur une fois la commande terminée.

Recommençons avec cette fois-ci le flag —link :

steph@ubuntu $ docker run —rm -t —link server1:mon_serveur_web ubuntu:saucy env
HOME=/
PATH=/usr/local/sbin :/usr/local/bin :/usr/sbin :/usr/bin :/sbin :/bin
HOSTNAME=5fdda2ce149b
TERM=xterm
MON_SERVEUR_WEB_PORT=tcp ://172.17.0.2:8080
MON_SERVEUR_WEB_PORT_8080_TCP=tcp ://172.17.0.2:8080
MON_SERVEUR_WEB_PORT_8080_TCP_ADDR=172.17.0.2
MON_SERVEUR_WEB_PORT_8080_TCP_PORT=8080
MON_SERVEUR_WEB_PORT_8080_TCP_PROTO=tcp
MON_SERVEUR_WEB_NAME=/nostalgic_bohr/mon_serveur_web

Docker a injecté différentes variables d’environnement. Nous retrouvons notamment l’adresse IP et le port qui nous intéressent pour le load balancer.

Configuration de Lighttpd

Lighttpd permet d’utiliser la commande include_shell dans son fichier de configuration afin d’appeler un script. La sortie standard de ce script sera ajoutée au fichier de configuration de Lighttpd. Il faut donc écrire un script shell permettant d’extraire les informations souhaitées à partir des variables d’environnement. En partant du principe que l’alias dans le flag —link est nommé serverX, alors nous pouvons utiliser le script suivant :

# !/bin/bash

echo 'server.modules += ( "mod_proxy" )' echo echo '## Balance algorithm, possible values are : "hash", "round-robin" or "fair" (default)' echo 'proxy.balance = "round-robin"' echo echo '## Redirect all queries' echo 'proxy.server = ( "" => (' for server in $(env | egrep 'SERVER.*_NAME' | cut -d'_' -f1) do eval host=\$$server_PORT_8080_TCP_ADDR eval port=\$$server_PORT_8080_TCP_PORT echo '( "host" => "'$host'", "port" => '$port'),' done echo ')' echo ')' echo

NOTE : Selon le shell utilisé, il peut être nécessaire d’ajouter des double quotes autour de $server, par exemple : eval host=\$"$server"_PORT_8080_TCP_ADDR

Sauvegarder ce contenu dans le fichier init-lb.sh, et le rendre exécutable. Nous pouvons maintenant écrire notre fichier Dockerfile :

FROM ubuntu:saucy
MAINTAINER Stéphane Deraco

# Install python RUN apt-get update && apt-get -y install lighttpd

# Ajout du fichier pour faire le LB sur tous les serveurs déclarés ADD init-lb.sh /usr/share/lighttpd/init-lb.sh

# Modif de la conf de Lighttpd RUN echo 'include_shell "/usr/share/lighttpd/init-lb.sh"' >> /etc/lighttpd/lighttpd.conf

# Commande par défaut CMD ["/usr/sbin/lighttpd","-D","-f","/etc/lighttpd/lighttpd.conf"]

Nous demandons à Docker d’installer Lighttpd, nous ajoutons notre script shell (avec la directive ADD), et nous modifions la configuration de Lighttpd. Enfin, la commande par défaut lancera Lighttpd. Construisons l’image avec docker build :

steph@ubuntu $ docker build -t demo/loadbalancer .
...

steph@ubuntu $ docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE demo/loadbalancer latest 9c16819ba547 39 seconds ago 227.9 MB demo/webserver latest 819f50d5b0ab 32 minutes ago 204.9 MB ...

Test final du cluster

Nous avons maintenant tout ce qu’il nous faut : l’image du serveur web, l’image du load balancer, comment faire communiquer les conteneurs entre-eux (—link), et comment accéder à un conteneur depuis l’extérieur (-p ou —port). Démarrons notre cluster :

steph@ubuntu $ docker run -d —name server1 demo/webserver
5052ad93dbc3cb8b0fafc096bce5559a97566a79414dbcde7e49bce9a71fab69

steph@ubuntu $ docker run -d —name server2 demo/webserver 6bac53e716cde218e3f112c910eadd3261a99054f8f32249b0b7b6bc39093db5

steph@ubuntu $ docker run -d —name server3 demo/webserver c3ce5c7506aff9596882b1ecd4d746338aa03327058a76a8e9ce60b72740bda3

steph@ubuntu $ docker run -d -p 8080:80 —name loadbalancer —link server1:server1 —link server2:server2 —link server3:server3 demo/loadbalancer 34e00f8b333174ee1586ac827ba3e74e13bffd378f578556cd9c58d8534a443f

steph@ubuntu $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 34e00f8b3331 demo/loadbalancer:latest /usr/sbin/lighttpd - 21 seconds ago Up 20 seconds 0.0.0.0:8080->80/tcp loadbalancer c3ce5c7506af demo/webserver:latest python Server.py 2 minutes ago Up 2 minutes 8080/tcp loadbalancer/server3,server3 6bac53e716cd demo/webserver:latest python Server.py 2 minutes ago Up 2 minutes 8080/tcp loadbalancer/server2,server2 5052ad93dbc3 demo/webserver:latest python Server.py 2 minutes ago Up 2 minutes 8080/tcp loadbalancer/server1,server1

Et le test ultime, depuis l’hôte :

steph@ubuntu $ for n in $(seq 1 10) ; do curl http://localhost:8080 ; echo ; done
Host is : c3ce5c7506af
Host is : 5052ad93dbc3
Host is : 6bac53e716cd
Host is : c3ce5c7506af
Host is : 5052ad93dbc3
Host is : 6bac53e716cd
Host is : c3ce5c7506af
Host is : 5052ad93dbc3
Host is : 6bac53e716cd
Host is : c3ce5c7506af

Nous basculons bien sur un serveur différent à chaque requête !

Conclusion

Nous pouvons imaginer se monter facilement un cluster MySQL, ou de se créer des environnements de tests à la volée. Quand nous déployons un conteneur en production, cela peut être le même que celui sur lequel nous avons développé, testé, etc.

Docker permet beaucoup de plus de choses que ce qui est présenté dans cet article, comme accéder à des données en dehors du conteneur (fichiers de paramétrage, répertoires de logs, fichiers de données). Il permet également de contrôler plus finement l’utilisation CPU et mémoire d’un conteneur. Il est également possible de partager les images dans un registry privé.

Docker connait un très fort intérêt. RedHat va d’ailleurs l’intégrer à RHEL 7.

A l’usage, la création d’un conteneur est déconcertante de facilité et de rapidité. Lancer trois images de serveur web, et une image de load balancer prend quelques secondes, et extrêmement peu de ressources du serveur hôte.

Cependant, Docker n’est pas encore stable. De plus, il ne faut imaginer qu’il va pouvoir remplacer une VM dans tous les cas de figures. Son utilisation doit être raisonnée. Mais je vous invite fortement à tester ! D’autant plus qu’il existe un tutoriel interactif pour découvrir.

Ressources

 

Stéphane DERACO
Envoyer un courriel

 

Licence Creative Commons


ARESU
Direction des Systèmes d'Information du CNRS

358 rue P.-G. de Gennes
31676 LABEGE Cedex

Bâtiment 1,
1 Place Aristide Briand
92195 MEUDON Cedex



 

 

Direction des Systèmes d'Information

Pôle ARESU

Accueil Imprimer Plan du site Credits