Catégories
Blog Open-source Systeme vitualisation

Varnish, NGinx et PHP-FPM sous Docker

Suite au précédent article d’introduction sur Docker (que je vous conseille de lire avant de dévorer cet article), je me suis penché sur le problème suivant: comment créer et maintenir une « stack web » de compétition (Varnish + Nginx + PHP-FPM) basée sur cette technologie de virtualisation par conteneurs.

Quel est l’objectif ?

Nous allons donc, dans ce billet, détailler l’installation et la mise à jour d’une infrastructure, basée sur Docker, qui pourra servir à l’hébergement d’un site personnel, d’un blog ou bien du site d’une PME:

  • la base de donnée sera portée par l’implémentation libre de MySQL, c’est à dire MariaDB (nous aborderons cette partie dans un deuxième billet)
  • pour le moteur PHP, j’utilise PHP-FPM
  • pour le serveur Web, je ne jure que par Nginx (léger, bien documenté)
  • afin pour tenir la charge (notamment si vous utilisé un moteur de blog de type WordPress, j’utilise le système de cache Varnish)LEMP (Docker)

Dans le reste de de l’article, on appellera machine hôte, la machine qui hébergera Docker (lire ce billet pour la procédure d’installation de Docker).

Cette dernière peut être un PC portable ou bien un serveur dédié. C’est un des avantages de la virtualisation par conteneurs. Une fois validé sur une machine , on est sûr que le conteneur fonctionnera de manière identique sur une autre installation de Docker. Ainsi pour la rédaction de ce billet, j’ai utiisé mon PC portable sous Ubuntu 14.04 et un (superbe) VPS mis à disposition par les amis de Web4All sous Debian 7 (merci Aurélien !).

Pour respecter la philosophie de la virtualisation par conteneur, chaque brique sera mise dans un conteneur dédié. On aura ainsi 4 conteneurs sur une machine hôte.

Chaque conteneur communiquera avec les autres selon le schéma ci-dessus.

Les données (data base DB, page statique du site et cache) seront stockées sur la machine hôte.

Création des conteneurs

Il y a deux méthodes pour choisie les images qui seront à la base de nos conteneurs.

La première, la plus noble mais la plus longue à mettre en œuvre, est d’écrire soit même les DockersFiles permettant l’installation et l’exécution des logiciels. On garde ainsi le contrôle de notre infrastructure et la possibilité de configurer finement les applications (notamment au niveau des options de compilation). Dans ce cas, et pour respecter les « best practices » de Docker, il faudra, pour chaque conteneur, repartir d’une image de base de type Debian sur laquelle on viendra installer les briques logicielles de notre LEMP.

La seconde, que j’ai choisi dans ce billet (pas parce-que je suis un gros fainéant mais par manque de temps), est de partir des images disponibles sur la registry officielle de Docker. On gagne ainsi en rapidité de mise en place.  Les composants de notre web stack étant très populaires, on trouve des images supportées et maintenues par la communauté Docker (notamment NGinx). Cependant, on constatera une trop grande diversité dans les systèmes de bases (Ubuntu, CentOS…).

Avant de commencer, nous allons créer un répertoire sur la machine hôte qui hébergera les fichiers utiles à notre infrastructure:

mkdir -p $HOME/data/webstack/conf $HOME/data/webstack/www

Le conteneur NGinx

Pierre angulaire de notre « web stack« , le serveur NGinx est très présent sur la « registry » officielle de Docker (plus de 900 images disponibles au moment de l’écriture de ce billet). J’ai choisi d’utiliser l’image officielle proposé par Docker (elle est conçue à partir de cete DockerFile).

On commence par télécharger les images NGinx officielle:

docker pull nginx

Puis on vérifie que les images sont visibles sur notre machine hôte (NGinx version 1.7.5 au moment de l’écriture de cet article):

$ docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
nginx 1.7.5 d2d79aebd368 7 days ago 100.2 MB
nginx latest d2d79aebd368 7 days ago 100.2 MB
nginx 1 d2d79aebd368 7 days ago 100.2 MB
nginx 1.7 d2d79aebd368 7 days ago 100.2 MB

On lance le conteneur:

$ docker run --name webstack_nginx_1 -v $HOME/data/webstack/www:/usr/share/nginx/html:ro -p 8080:80 -d nginx
f7baa81cebdc8799947327e6470a74ae73fe73bb0eb644ecfb2951847c40154b

Notre conteneur a le doux nom de webstack_nginx_1, il redirige le port TCP/8080 de notre hôte vers le port TCP/80 du conteneur (port par défaut de NGinx) et il assigne, en lecture seule, le volume /usr/share/nginx/html au répertoire hôte $HOME/data/www (à adapter à votre configuration). Pour résumer, toutes les pages HTML disponible dans le répertoire /home/nicolargo/data/www, seront accessible via le port HTTP/8080 de votre machine locale.

On ajoute une page HTML statique de test et une autre pour le test PHP que l’on utilisera dans le chapitre suivant:

echo "My first page" > $HOME/data/webstack/www/index.html
echo "<?php phpinfo(); ?>" > $HOME/data/webstack/www/phpinfo.php

Puis on valide que le serveur NGinx est bien opérationnel:

$ curl http://localhost:8080
My first page

Ou directement depuis votre navigateur Web préféré:

Sélection_245L’image NGinx utilisée redirige les fichiers de log (access.log et error.log vert la sortie standard). Il est donc possible de visualiser les accès à son serveur Web en « attachant » le terminal de notre hôte à la sortie standard du conteneur:

$ docker attach --sig-proxy=false webstack_nginx_1
2014/10/08 13:47:59 [error] 9#0: *1 open() "/usr/share/nginx/html/inconnu.html" failed (2: No such file or directory), client: 172.17.0.103, server: localhost, request: "GET /inconnu.html HTTP/1.1", host: "localhost"
172.17.0.103 - - [08/Oct/2014:13:47:59 +0000] "GET /inconnu.html HTTP/1.1" 404 168 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:32.0) Gecko/20100101 Firefox/32.0" "172.17.42.1"

Note: bien penser à mettre l’option –sig-proxy=true sous peine de stopper le conteneur lors de l’appuie sur ^C. Je vous conseille  de créer un alias dans votre shell.

Le conteneur PHP-FPM

Pour le moteur PHP, il n’existe pas d’image officielle. Mon choix s’est donc porté vers l’utilisation du repository de Jprjr basée sur une installation de PHP-FPM sous ArchLinux avec pas mal d’extensions par défaut (voir la liste dans le DockerFile).

On télécharge la dernière version des images avec:

docker pull jprjr/php-fpm

On obtient bien:

$ docker images | grep jprjr
jprjr/php-fpm          latest                 d40698b35f83        6 weeks ago         347.8 MB

On lance le conteneur:

docker run --name webstack_php_01 -p 9000:9000 -d jprjr/php-fpm

Le conteneur est configuré par défaut pour écouter sur le port TCP/9000.

Très bien, on doit donc avoir le serveur NGinx et le moteur PHP-FPM qui sont lancés dans deux conteneurs différents. On va vérifier cela avec la commande:

$ docker ps
CONTAINER ID        IMAGE                  COMMAND                CREATED             STATUS              PORTS                           NAMES
e9dd04e7ceec        jprjr/php-fpm:latest   "php-fpm -F"           13 seconds ago      Up 12 seconds       0.0.0.0:9000->9000/tcp          webstack_php_01        
f7baa81cebdc        nginx:1                "nginx -g 'daemon of   46 minutes ago      Up 46 minutes       443/tcp, 0.0.0.0:8080->80/tcp   webstack_nginx_01

Faire communiquer les deux conteneurs (NGinx & PHP-FPM)

Tout cela est très beau mais les deux conteneurs ne se connaissent pas. Il faut, pour cela, configurer le serveur NGinx pour utiliser le moteur PHP et donc que le conteneur NGinx connaisse l’adresse IP du conteneur PHP-FPM.

Heureusement, Docker propose l’option –link permettant de répondre à ce besoin. Cette option, à utiliser au moment du lancement du conteneur, va créer dynamiquement des entrées dans le fichier host et dans des variables d’environnement du conteneur, permettant ainsi à ce dernier de connaître comment joindre ses congénères.

Par exemple:

$ docker run -it -v --link webstack_php_01:webstack_php nginx /bin/bash

root@2ce3cdb8a767:/# cat /etc/hosts | grep webstack
172.17.0.5	webstack-php

root@d9ff8a80f12a:/# env | grep WEBSTACK
WEBSTACK_PHP_PORT=tcp://172.17.0.5:9000
WEBSTACK_PHP_PORT_9000_TCP=tcp://172.17.0.5:9000
WEBSTACK_PHP_PORT_9000_TCP_ADDR=172.17.0.5
WEBSTACK_PHP_PORT_9000_TCP_PORT=9000
WEBSTACK_PHP_NAME=/webstack_nginx/webstack_php
WEBSTACK_PHP_PORT_9000_TCP_PROTO=tcp

root@2ce3cdb8a767:/# exit
exit

$ docker rm `docker ps -lq`

Maintenant que l’on sait comment faire communiquer les deux conteneurs, il suffit de configurer NGinx pour rediriger les fichier .PHP vers le moteur PHP-FPM du second conteneur.

Pour cela, plusieurs solutions sont là encore possibles. La plus simple est de surcharger la configuration NGinx par défaut(nginx.conf). Sur notre hôte, nous allons créer le fichier de configuration en question:

$ docker webstack_nginx_01/etc/nginx/nginx.conf $HOME/data/webstack/conf/nginx.conf

$ vi $HOME/data/webstack/conf/nginx.conf

daemon off;

user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       80;
        server_name  localhost;

        location / {
            root   /usr/share/nginx/html;
            index  index.html index.htm;
        }

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        #error_page   500 502 503 504  /50x.html;
        #location = /50x.html {
        #    root   /usr/share/nginx/html;
        #}

        # Pass PHP scripts to PHP-FPM
        location ~* \.php$ {
            fastcgi_index   index.php;
            fastcgi_pass    webstack_php:9000;
            #fastcgi_pass   unix:/var/run/php-fpm/php-fpm.sock;
            include         fastcgi_params;
            fastcgi_param   SCRIPT_FILENAME    /srv/http$fastcgi_script_name;
            fastcgi_param   SCRIPT_NAME        $fastcgi_script_name;
        }    

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        location ~ /\.ht {
            deny  all;
        }
    }

}

Note:

  • « fastcgi_pass webstack_php:9000; » qui utilise donc le hostname présent dans le fichier host
  • « fastcgi_param   SCRIPT_FILENAME    /srv/http$fastcgi_script_name; » qui va permettre à PHP-FPM d’aller chercher les pages PHP dans le même volume que le serveur Nginx mais assigné au répertoire /srv/http du conteneur (c’est la configuration par défaut de notre conteneur PHP-FPM)

Revenons à notre cas. Nous souhaitons que le conteneur NGinx (nommé webstack_nginx_01) connaisse le conteneur PHP (webstack_php_01).

On commence donc par supprimer les deux conteneurs existant:

docker stop webstack_php_01 && docker rm webstack_php_01
docker stop webstack_nginx_01 && docker rm webstack_nginx_01

puis de les recréer avec l’option –link (pour le conteneur NGinx qui va initier la connexion TCP vers le moteur PHP-FPM), la nouvelle configuration de NGinx et les volumes pointant sur notre page HTML et PHP:

docker run --name webstack_php_01 -v $HOME/data/webstack/www:/srv/http:ro -p 9000:9000 -d jprjr/php-fpm
docker run --name webstack_nginx_01 -v $HOME/data/webstack/www:/usr/share/nginx/html:ro -v $HOME/data/webstack/conf/nginx.conf:/nginx.conf:ro -p 8080:80 --link webstack_php_01:webstack_php -d nginx nginx -c /nginx.conf

On teste:

$ curl http://localhost:8080/index.html
My first page

$ curl http://localhost:8080/phpinfo.php
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"><head>
<style type="text/css">
...

Sélection_246

Bingo !

A ce stade, nous avons donc deux conteneurs communiquant entre eux pour nous offrir un serveur Web compatible avec le langage PHP.

Le conteneur Varnish en frontal

Si vous suivez ce blog, vous savez tout le bien que je pense de l’utilisation du cache Varnish pour absorber la montée en charge des sites Web (sinon, vous pouvez effectuer une session de rattrapage en lisant ces articles). Nous allons donc créer un nouveau conteneur qui exposera le port TCP/80 et qui mettra en cache les pages construites par le serveur NGinx (qui est en écoute sur le port TCP/8080 si vous avez bien suivi…).

Nous allons utiliser l’image Varnish mise à disposition et maintenue par le contributeur jacksoncage.

On commence par la récupérer:

docker pull jacksoncage/varnish

Puis on vérifie qu’elle est bien disponible sur notre hôte:

$ docker images | grep jacksoncage
jacksoncage/varnish    latest                 46f0ea7021c1        8 months ago        472.2 MB

Comme il est indiqué dans la documentation de l’image, il faut écrire le fichier de configuration par défaut de Varnish (le fichier default.vcl):

$ vi $HOME/data/webstack/conf/default.vcl

backend default {
    .host = "webstack_nginx";
    .port = "80";
}

On demande donc à Varnish d’aller directement communiquer avec le conteneur NGinx via l’adresse IP webstack-nginx (rappelez-vous que le hostname est créé dynamiquement par Docker au démarrage du conteneur avec l’option –link) et sur le port TCP 80 (attention, ce n’est pas le port TCP/8080 exposé par Docker sur notre hôte mais celui vraiment utilisé par NGinx dans le conteneur).

A noter que cette configuration est à compléter, notamment si vous voulez héberger un blog sous WordPress (des exemples de fichiers de configuration sont disponibles ici). Attentinon, ces exemples sont pour Varnish 4.0, donc à adapter si la version du conteneur jacksoncage/varnish n’est pas en ligne.

Puis lancer le conteneur avec les options suivantes:

docker run --name webstack_varnish_01 -v $HOME/data/webstack/conf/default.vcl:/etc/varnish/default.vcl:ro -p 80:80 --link webstack_nginx_01:webstack_nginx -d jacksoncage/varnish

On redirige le port 80 du conteneur vers le port 80 de votre hôte (il ne doit bien sûr pas y avoir de serveur Web déjà en écoute sur ce port) puis on fait le lien entre le conteneur Varnish et le conteneur NGinx.

On vérifie que nos trois conteneurs sont lancés:

$ docker ps
CONTAINER ID        IMAGE                        COMMAND                CREATED             STATUS              PORTS                           NAMES
a1b853f1a38e        jacksoncage/varnish:latest   "/start"               4 seconds ago       Up 3 seconds        0.0.0.0:80->80/tcp              webstack_varnish_01                                                                        
c2bd3138864a        nginx:1                      "nginx -c /nginx.con   54 minutes ago      Up 54 minutes       443/tcp, 0.0.0.0:8080->80/tcp   webstack_nginx_01,webstack_varnish_01/webstack-nginx                                          
7cc072bb0df2        jprjr/php-fpm:latest         "php-fpm -F"           54 minutes ago      Up 54 minutes       0.0.0.0:9000->9000/tcp          ...

Et on teste notre serveur de cache Varnish:

$ curl -I http://localhost/index.html

HTTP/1.1 200 OK
Server: nginx/1.7.5
Content-Type: text/html
Last-Modified: Sat, 04 Oct 2014 16:30:45 GMT
ETag: "543020b5-15"
Content-Length: 21
Accept-Ranges: bytes
Date: Sun, 05 Oct 2014 15:26:47 GMT
X-Varnish: 320901887 320901884
Age: 46
Via: 1.1 varnish
Connection: keep-aliv

Le « Via: 1.1 varnish » confirme que notre infra commence à avoir de la gueule :).

En aparté, on peut d’ailleurs juger de la puissance de Varnish en lançant un simple bench tout d’abord directement sur le serveur NGinx (donc en baille-passant Varnish):

$ ab -t 30 -c 5 http://localhost:8080/

Complete requests:      7107
Requests per second:    232.24 [#/sec] (mean)
Time per request:       21.529 [ms] (mean)
Time per request:       4.306 [ms] (mean, across all concurrent requests)

Puis en faisant le même test en passant par Varnish:

$ ab -t 30 -c 5 http://localhost/

Complete requests:      50000
Requests per second:    3694.60 [#/sec] (mean)
Time per request:       1.353 [ms] (mean)
Time per request:       0.271 [ms] (mean, across all concurrent requests)

Les chiffres parlent d’eux même…

Gestion des conteneurs

Un simple pull sur les images préalablement téléchargé s’occupera de leurs mises à jour:

docker pull nginx
docker pull jprjr/php-fpm
docker pull jacksoncage/varnish

Comme l’on reconstruit notre infrastructure à partir de ces images, il suffira ensuite d’arrêter et de relancer les conteneurs:

docker stop webstack_varnish_01 && docker rm webstack_varnish_01
docker stop webstack_nginx_01 && docker rm webstack_nginx_01
docker stop webstack_php_01 && docker rm webstack_php_01

docker run --name webstack_php_01 -v $HOME/data/webstack/www:/srv/http:ro -p 9000:9000 -d jprjr/php-fpm
docker run --name webstack_nginx_01 -v $HOME/data/webstack/www:/usr/share/nginx/html:ro -v $HOME/data/webstack/conf/nginx.conf:/nginx.conf:ro -p 8080:80 --link webstack_php_01:webstack_php -d nginx nginx -c /nginx.conf
docker run --name webstack_varnish_01 -v $HOME/data/webstack/conf/default.vcl:/etc/varnish/default.vcl:ro -p 80:80 --link webstack_nginx_01:webstack_nginx -d jacksoncage/varnish

Note: Il est également possible de créer des images maison (avec commit + tag) et de les relancer. Cette phase peut être utile dans le cadre d’un déploiement d’une infrastructure à une autre.

Orchestration de l’infrastructure

On vient de voir que le lancement d’une infrastructure basée sur Docker peut rapidement devenir compliqué. Les conteneurs doivent être lancés dans un certain ordre, avec des options spécifiques. Il est toujours possible de scripter les commandes du chapitre précédent ou plus simplement d’utiliser un outil d’orchestration.

Vagrant vous vient en tête ? Pourtant, ce n’est pas la solution que nous allons utiliser dans ce billet.

Nous allons nous tourner vers docker-compose (anciennement Fig). C’est un logiciel en ligne de commande permettant de d’écrire son infrastructure Docker à partir d’un fichier de configuration texte au format YAML.

L’installation de Fig se fait via la commande:

sudo pip install docker-compose

Note: pour d’autre méthode d’installation, consultez la documentation sur le site officiel.

Le fichier de configuration Fig correspondant à notre infrastructure est le suivant (à éditer dans le fichier $HOME/data/webstack/docker-compose.yml):

# Webstack PHP
php:
    image: jprjr/php-fpm
    volumes:
    - www:/srv/http
    ports:
    - 9000:9000
# Webstack NGINX
nginx:
    image: nginx
    links:
    - php:webstack_php
    volumes:
    - www:/usr/share/nginx/html
    - conf/nginx.conf:/nginx.conf
    ports:
    - 8080:80
    command: nginx -c /nginx.conf
# Webstack VARNISH
varnish:
    image: jacksoncage/varnish
    links:
    - nginx:webstack_nginx
    volumes:
    - conf/default.vcl:/etc/varnish/default.vcl
    ports:
    - 80:80

On lance ensuite notre infrastructure en une seule et unique commande:

$ docker-compose up -d
Creating webstack_php_1...
Creating webstack_nginx_1...
Creating webstack_varnish_1...

Tout comme avec la ligne de commande Docker, on peut voir les conteneurs en cours d’exécution:

$ docker-compose ps
       Name                Command          State           Ports         
-------------------------------------------------------------------------
webstack_php_1                              Up      9000->9000/tcp        
webstack_nginx_1     nginx -c /nginx.conf   Up      443/tcp, 8080->80/tcp 
webstack_varnish_1   /start                 Up      80->80/tcp

Les arrêter (sans les supprimer):

$ docker-compose stop
Stopping webstack_varnish_1...
Stopping webstack_nginx_1...
Stopping webstack_php_1...

Les relancer:

$ docker-compose start
Starting webstack_php_1...
Starting webstack_nginx_1...
Starting webstack_varnish_1...

Les supprimer:

docker-compose stop && docker-compose rm

Je vous laisse découvrir les autres commandes de docker-compose sur le site officiel de la documentation.

Conclusion

Nous venons de créer les bases d’une infrastructure Web performante, facilement maintenable et évolutive. Il est ainsi facile d’y ajouter d’autres services comme une base de donnée (type MariaDB), un serveur sFTP (pour la mise à jour des pages Web) ou bien encore un outil d’analyse des logs (Varnish et NGinx).

Et de votre coté, avez-vous mis en place une infrastructure Docker pour votre serveur Web ? Si oui comment ?

Partagez votre expérience avec nous !

Catégories
Open-source Planet-libre

Support de présentation de Docker

A l’occasion de la commission open-source de la Telecom Valley, j’ai eu le plaisir de faire un talk sur le sujet à la mode du moment: Docker ou la virtualisation par conteneurs (j’en avais déjà parlé sur ce blog dans un précédent billet).

Au vu des questions et débats qui ont suivi la présentation, je ne doute pas de l’explosion des cas d’usage sur le sujet dans les prochains mois !

Et vous chers lecteurs, ou en êtes vous dans vos projets autours de l’écosystème Docker ? En phase de test ? De production ?

Partagez avec nous vos XP sur le sujet !

Catégories
Developpement Open-source Planet-libre Reseau Systeme

Virtualisation légère avec Docker

Dans le petit monde des DevOps, la solution de virtualisation Docker a le vent en poupe. Nous allons dans ce billet essayer de décrypter ce qui ce cache derrière ce nouvel outil et proposer un tutoriel sur l’installation et les premiers pas pour une utilisation souple et efficace.Sélection_178

C’est quoi donc ?

Docker est un logiciel open-source (sous licence Apache 2.0) et un service en ligne optionnel (Docker.io) permettant de gérer des conteneurs (« dockers »).

Contrairement aux machines virtuelles classiques qui comprennent un système hôte (« guest OS ») et les librairies/applications, les conteneurs ne contiennent, au maximum, que les applications/librairies.

 Sélection_179

Quand je dis au maximum c’est qu’une copie d’un conteneur qui ne change que la partie application ne contiendra que les différences par rapport au conteneur initial.

Sélection_181

Le système hôte est ainsi géré directement par le Docker Engine (en vert dans le premier schéma). On a ainsi une mutualisation qui permet de lancer les conteneurs de manière très rapide: le temps de lancement d’un conteneur est presque le même que le temps de lancement des applications qu’il contient. En effet, le système d’exploitation est déjà lancé. Donc pas de phase de démarrage ni d’initialisation de cette couche. Il faut quand même avoir à l’esprit que même si l’on est pas dans une machine virtuelle, on dispose tout de même d’un environnement isolé (processus, système de fichier, ports réseau). L’utilisateur n’a qu’à se préoccuper que de ce qu’il veut virtualiser (les applications/services) et ne s’occupe pas du reste.

L’autre gros avantage de cette technologie est la portabilité. En effet, il est tout à fait possible de concevoir un conteneur sur son PC portable puis ensuite de le déployer sur son infrastructure de production physique ou virtuelle. La taille des conteneurs étant relativement réduite, on peut donc imaginer un workflow de déploiement basée sur un repo central (type Git) et des Docker Engine installés sur les machines qui font elle même tourner des conteneurs. Il n’y a pas vraiment de limite au nombre de conteneurs qu’une machine puisse faire tourner. La limite vient de l’occupation mémoire/CPU/réseau de vos applications.

Si vous avez encore des questions sur la différence entre Docker et VM, je vous conseille la lecture de cette question sur StackOverflow: « How is Docker.io different from a normal virtual-machine ?« .

Installation de Docker

Note (merci à @jb_barth pour le commentaire): jusqu’à la version 0.9, Docker se basait sur la technologie LXC (« Linux Containers ») du noyau Linux et ne fonctionnait donc que sur des distributions GNU/Linux avec un noyau >= 2.6.24. La dépendance stricte à LXC a sauté depuis la v0.9.0, le driver par défaut est maintenant libcontainer, une lib pur go produite par le projet pour accéder de façon propre aux APIs dispos dans le kernel. C’est ce qui a permis de supporter des noyaux aussi vieux d’ailleurs, au début c’était >= 3.8

Voici les étapes à suivre pour installer Docker sur une distribution Ubuntu 14.04 LTS. A noter, pour les miséreux développeurs sous Windows ou Mac OS, il est est toujours possible de faire tourner une VM Ubuntu (par exemple avec VirtualBox) et de suivre cette installation.

Docker est directement disponible dans les repositories d’Ubuntu, donc un simple:

sudo apt-get install docker.io
sudo ln -sf /usr/bin/docker.io /usr/local/bin/docker

suffit pour effectuer l’installation complète de Docker sur notre machine comprenant:

  • le Docker Engine (ou service Docker)
  • le Docker Client (permettant de discuter avec le Docker Engine)

Cependant si vous souhaiter travailler avec une version plus ressente il faut utiliser les commandes suivantes:

sudo apt-key adv --keyserver hkp://pgp.mit.edu:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D
sudo sh -c "echo deb https://apt.dockerproject.org/repo ubuntu-trusty main \ > /etc/apt/sources.list.d/docker.list"
sudo apt-get update
sudo apt-get purge lxc-docker*
sudo apt-get install docker-engine

Comme les conteneurs vont utiliser les serveurs DNS de la machine hôte, il est nécessaire de configurer les adresses des serveurs DNS dans le fichier /etc/default/docker.io (par exemple avec les serveurs OpenDNS):

$ vi /etc/default/docker.io

# Use DOCKER_OPTS to modify the daemon startup options.
DOCKER_OPTS="-dns 208.67.220.220 -dns 208.67.220.222"

Pour que la configuration soit prise en compte, il faut relancer le service Docker avec la commande:

sudo service docker restart

Un petit ps permet de voir que le service est bien lancé:

0.0   0.1  563M 9.65M   733 root         0 S  0:00.46     0     0 /usr/bin/docker.io -d -dns 208.67.220.220 -dns 208.67.220.222

Récupération des images système

Nous allons commencer par récupérer des images qui vont servir de de bases à notre conteneur. Une liste assez conséquente d’images sont disponibles sur le site officiel du projet. Vous pouvez ainsi récupérer celle qui est le plus proche de l’environnement que vous recherché. Un moteur de recherche est disponible à l’adresse https://index.docker.io/ pour trouver des images conçues par les développeurs du projet et par des contributeurs.

On retrouve ainsi des distributions GNU/Linux minimales comme Ubuntu, CentOS, BusyBox pour ne siter que les images officiellement supportées par le projet Docker.io (il existe également des repos non officiels avec Fedora, RH, Arch…).

Petit mention spéciale pour les images BusyBox qui sont vraiment très légère (moins de 10 Mo) pour un système fonctionnel !

Même si il est possible de directement télécharger l’image au lancement du conteneur, je préfère avoir sur ma machine les images des différentes versions de l’OS que j’utilise le plus: Ubuntu.

Pour récupérer l’ensemble des images du repo Ubuntu qui contient les versio  minimale d’Ubuntu de la version 10.04 à 14.04), il faut saisir la commande suivante:

$ sudo docker pull ubuntu

74fe38d11401: Pulling dependent layers
316b678ddf48: Pulling dependent layers
3db9c44f4520: Pulling dependent layers
5e019ab7bf6d: Pulling dependent layers
99ec81b80c55: Pulling dependent layers
a7cf8ae4e998: Pulling dependent layers
511136ea3c5a: Download complete
e2aa6665d371: Downloading [==>                                                ] 2.027 MB/40.16 MB 6m14s

Note: le téléchargement initial des images peut prendre un peu de temps selon la vitesse de votre liaison Internet.

Il est également possible de faire de même avec les autres distributions citées ci-dessus:

$ sudo docker pull centos

$ sudo docker pull busybox

Une fois le téléchargement terminé, on peut demandé à Docker un status de son état actuel:

$ sudo docker info
Containers: 64
Images: 46
Storage Driver: aufs
 Root Dir: /var/lib/docker/aufs
 Dirs: 174
Execution Driver: native-0.1
Kernel Version: 3.13.0-24-generic
WARNING: No swap limit support

ainsi que la liste des images disponibles:

$ sudo docker images
REPOSITORY          TAG                   IMAGE ID            CREATED             VIRTUAL SIZE
busybox             buildroot-2013.08.1   352f47ad2ecf        17 hours ago        2.489 MB
busybox             ubuntu-14.04          6a95c08a9391        17 hours ago        5.609 MB
busybox             ubuntu-12.04          1720a1681f1c        17 hours ago        5.455 MB
busybox             buildroot-2014.02     f66342b343ae        17 hours ago        2.433 MB
busybox             latest                f66342b343ae        17 hours ago        2.433 MB
ubuntu              glances_develop       a483f92d9ab3        24 hours ago        556.8 MB
ubuntu              14.04_nicolargo       8574cc29575e        28 hours ago        440.8 MB
ubuntu              13.10                 5e019ab7bf6d        4 weeks ago         180 MB
ubuntu              saucy                 5e019ab7bf6d        4 weeks ago         180 MB
ubuntu              12.04                 74fe38d11401        4 weeks ago         209.6 MB
ubuntu              precise               74fe38d11401        4 weeks ago         209.6 MB
ubuntu              12.10                 a7cf8ae4e998        4 weeks ago         171.3 MB
ubuntu              quantal               a7cf8ae4e998        4 weeks ago         171.3 MB
ubuntu              14.04                 99ec81b80c55        4 weeks ago         266 MB
ubuntu              latest                99ec81b80c55        4 weeks ago         266 MB
ubuntu              trusty                99ec81b80c55        4 weeks ago         266 MB
ubuntu              13.04                 316b678ddf48        4 weeks ago         169.4 MB
ubuntu              raring                316b678ddf48        4 weeks ago         169.4 MB
ubuntu              lucid                 3db9c44f4520        5 weeks ago         183 MB
ubuntu              10.04                 3db9c44f4520        5 weeks ago         183 MB
centos              centos6               0b443ba03958        6 weeks ago         297.6 MB
centos              6.4                   539c0211cd76        14 months ago       300.6 MB
centos              latest                539c0211cd76        14 months ago       300.6 MB

Comme alternative du moteur de recherche https://index.docker.io/, il est bien sûr possible d’utiliser la ligne de commande. Par exemple pour trouver toutes les images Ubuntu disponibles sur le repo central:

sudo docker search ubuntu | less

A noter qu’il est tout à fait possible, si vous ne trouvez pas votre bonheur de concevoir « from scratch » votre propre image système en suivant cette documentation sur le site officiel (la procédure se base sur DebootStrap).

Création de son premier conteneur

Bon assez de préliminaires, nous allons maintenant pourvoir créer notre premier conteneur qui va se limiter à exécuter la commande ‘ls’ (si c’est pas du conteneur de compétition):

$ sudo docker run ubuntu:latest /bin/ls -alF

total 8280
drwxr-xr-x  46 root root    4096 May 28 06:50 ./
drwxr-xr-x  46 root root    4096 May 28 06:50 ../
-rw-r--r--   1 root root     102 May 28 06:50 .dockerenv
-rwx------   1 root root 8394118 May 27 15:37 .dockerinit*
drwxr-xr-x   2 root root    4096 Apr 16 20:36 bin/
drwxr-xr-x   2 root root    4096 Apr 10 22:12 boot/
drwxr-xr-x   4 root root    4096 May 28 06:50 dev/
drwxr-xr-x  64 root root    4096 May 28 06:50 etc/
drwxr-xr-x   2 root root    4096 Apr 10 22:12 home/
drwxr-xr-x  12 root root    4096 Apr 16 20:36 lib/
drwxr-xr-x   2 root root    4096 Apr 16 20:35 lib64/
drwxr-xr-x   2 root root    4096 Apr 16 20:35 media/
drwxr-xr-x   2 root root    4096 Apr 10 22:12 mnt/
drwxr-xr-x   2 root root    4096 Apr 16 20:35 opt/
dr-xr-xr-x 236 root root       0 May 28 06:50 proc/
drwx------   2 root root    4096 Apr 16 20:36 root/
drwxr-xr-x   7 root root    4096 Apr 16 20:36 run/
drwxr-xr-x   2 root root    4096 Apr 24 16:17 sbin/
drwxr-xr-x   2 root root    4096 Apr 16 20:35 srv/
dr-xr-xr-x  13 root root       0 May 28 06:50 sys/
drwxrwxrwt   2 root root    4096 Apr 24 16:17 tmp/
drwxr-xr-x  11 root root    4096 Apr 16 20:35 usr/
drwxr-xr-x  14 root root    4096 Apr 16 20:36 var/

Arrêtons-nous un peu sur la commande: sudo docker.io run ubuntu:latest /bin/ls -alF

On demande donc le lancement (run) d’un conteneur basée sur la dernière version d’Ubuntu (ubuntu:latest qui est un lien vers l’image minimale de la version 14.04) qui va exécuter la commande ls (/bin/ls -alF). Comme vous pouvez le voir dans le résultat de la commande, on est dans un environnement isolé avec son propre système de fichier.

Première constatation, la vitesse d’exécution de notre environnement virtuel est vraiment impressionnante. Sur une petit commande,on peut voir que l’overhead de lancement du conteneur est négligeable:

$ time sudo docker run ubuntu:latest ls -alF /
...
real	0m0.331s
user	0m0.014s
sys	0m0.012s

$ time ls -alF /
...
real	0m0.007s
user	0m0.003s
sys	0m0.004s

Puis un conteneur persistant

Passons maintenant à la création d’un conteneur persistant, c’est à dire un conteneur  qui va faire tourner une tache pendant un temps indéterminé (je prends par exemple un pin infini vers le site Google).

$ sudo docker run -d ubuntu:latest ping www.google.fr
7404bfa4beca4ba97459c96f8d93242c4fba6ecf2c5b11d18c09acd2fce9991e

Noter le -d dans la ligne de commande pour détacher le conteneur et ainsi lui permettre de tourner en tache de fond.

En retour on obtient le numéro d’identifiant unique du conteneur qui va nous permettre de le contrôler.

On commence donc par vérifier qu’il tourne bien:

$ sudo docker ps
CONTAINER ID        IMAGE               COMMAND              CREATED             STATUS              PORTS               NAMES
7404bfa4beca        ubuntu:14.04        ping www.google.fr   3 minutes ago       Up 3 minutes                            mad_lumiere         

On peut ainsi, l’arrêter:

sudo docker stop 7404bfa4beca

Le démarrer:

sudo docker start 7404bfa4beca

Le redémarrer (l’équivalent d’un stop/start):

sudo docker restart 7404bfa4beca

Lui envoyer des signaux:

sudo docker kill 7404bfa4beca

Ensute on supprime le conteneur avec la séquence stop/rm:

sudo docker stop 7404bfa4beca

sudo docker rm 7404bfa4beca

Un Shell dans un conteneur

Pour accéder à un shell dans le conteneur, il faut utiliser les options suivantes dans la ligne de commande:

  • -t: Allocate a pseudo-tty
  • -i: Keep STDIN open even if not attached
$ sudo docker run -i -t ubuntu:latest bash
root@bb89ed6cdd3c:/#

Structurer la création des conteneurs avec les Dockerfiles

Les Dockerfiles sont des fichiers textes décrivant les différentes étapes pour la création d’un conteneur. Idéalement, ce sont ces fichiers que vous aller gérer en configuration et que vous aller partager avec les différentes personnes utilisatrices de votre projet. Pour illustrer mes dires, je vais prendre l’exemple d’un dockerfile qui va permettre aux contributeurs du projet Glances de tester simplement la branche de développement du logiciel.

En gros, le dockfile doit:

  1. utiliser un OS de base (Ubuntu 14.04 LTS)
  2. installer les pré-requis système
  3. télécharger la dernière version de Glances sous Github

Le dockfile glances_develop_install correspondant est alors le suivant:

# Install Glances Development branch
#
# $ sudo docker build -t ubuntu:glances_develop - < glances_develop_install
#
# VERSION 1.0

# Use the ubuntu base image provided by dotCloud
FROM ubuntu
MAINTAINER Nicolargo, nicolas@nicolargo.com

# Make sure the package repository is up to date
RUN apt-get -y update

# Install prerequirement
RUN apt-get install -y python-dev python-pip git lm-sensors
RUN pip install psutil bottle batinfo https://bitbucket.org/gleb_zhulik/py3sensors/get/tip.tar.gz

# Patch for current Docker version
RUN ln -s /proc/mounts /etc/mtab

# Install Glances from the Pipy repository
RUN git clone -b develop https://github.com/nicolargo/glances.git

Voyons un peu en détail le contenu du fichier.  On commence donc par définir l’OS de base:

FROM ubuntu

On peut bien sûr utiliser l’image/tag que l’on souhaite.

Ensuite on passe aux commandes que l’on souhaite exécuter:

RUN apt-get -y update

RUN apt-get install -y python-dev python-pip git lm-sensors

RUN pip install psutil bottle batinfo https://bitbucket.org/gleb_zhulik/py3sensors/get/tip.tar.gz

RUN ln -s /proc/mounts /etc/mtab

RUN git clone -b develop https://github.com/nicolargo/glances.git

Rien de bien compliqué, il suffit de faire précéder la commande Unix par le mot clé RUN…

Il ne reste plus qu’à lancer la construction du conteneur et son exportation vers une nouvelle image (nommé ubuntu:glances_develop) à partir de la ligne de commande:

sudo docker build -t ubuntu:glances_develop - < glances_develop_install

ou alors directement depuis un repos (par exemple GitHub) ou vos Dockers files sont gérés en configuration:

sudo docker build -t ubuntu:glances_develop https://raw.githubusercontent.com/nicolargo/dockersfiles/master/glances_develop_install

On vérifie ensuite que l’image a bien été créée:

$ sudo docker images | grep glances
ubuntu glances_develop a483f92d9ab3 31 hours ago 556.8 MB

Puis on lance Glances depuis ce conteneur (en le metant à jour par un pull avant le lancement):

sudo docker run -i -t --entrypoint /bin/bash ubuntu:glances_develop -c "cd glances ; git pull origin develop ; python -m glances"

Et hop c’est magique:

Glances in a docker

Un des trucs fun avec Docker c’est que si l’on souhaite modifier un conteneur en ajoutant par exemple l’installation d’un nouveau logiciel, il suffit de modifier le Docker file puis de refaire la commande de build. L’installation ne se fera que par delta par rapport au premier conteneur.

Controler la CPU et la mémoire de ses conteneurs

Une fonction intéressante de Docker est sa capacité de contraindre un conteneur en terme de CPU et de MEM. Par exemple on peut limiter le conteneur à 512 Mo de RAM et donner un priorité moins forte en terme de CPU à ce conteneur par rapport aux autres. Il faut cependant que le hôte qu héberge Docker soit compatible avec ces options ce qui n’est pas mon cas:

$ sudo docker run -m 512m -i -t --entrypoint /bin/bash ubuntu:glances_develop -c "cd glances ; git pull origin develop ; python -m glances"

WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.

Pour activer ces options sous Ubuntu, il faut modifier la configuration du Kernel via GRUB. Pour cela, il est nécessaire de suivre cette procédure.

Conclusion

L’approche est vraiment différente des solutions de virtualisation classiques mais si vous cherchez un outil simple pour valider vos applications Linux dans un environnement controlé alors Docker est définitivement une solution à envisager. Sa souplesse et sa légéreté en font une solution idéale à embarquer sur son PC portable ou à mettre en production sur son infrastructure.

Je vous conseille de vous plonger dans la documentation officielle qui est vraiment complète et très bien faite.

Des expériences à nous faire partager sur l’utilisation de Docker ?

Quelques références: