Le déploiement par conteneurs avec Docker 30


Mettre son projet en production, c’est la galère. Tellement que mille méthodes ont vu le jour pour automatiser tout ça. Chef, salt, fabric, des script bash, virtualenv, git hooks, etc.

Après, il y a ceux qui utilisent des VM, qui elles, ont leur propres outils d’automatisation type Vagrant.

Et comme ça ne suffit pas, des services ce sont mis en place pour faciliter la mise en prod dans le cloud comme Heroku, Gondor, dotCloud…

Malgré ça, Max fait encore beaucoup de trucs à la main parce que “ça marche jamais comme prévu”. Pas très scalable.

Dernièrement, grâce à notre cher Cortex, j’ai découvert un projet écrit en Go nommé Docker, qui propose encore une autre approche du problème.

J’ai été intrigué après avoir visionné une conf sur le sujet car le principe est très cool, mais aussi parce que j’ai bossé à une époque avec un des mecs de chez Docker. Et ce gars est un monstre. Mais vraiment. Une brute. Le genre qui n’a pas besoin de souris ni de gestionnaire de fenêtre, mais qui peut vous régler votre problème en tapant d’une main tout en vous expliquant ce qu’il fait dans votre domaine d’expertise alors que ce n’est pas le sien. Je l’ai vu faire. C’est énervant.

Pour le moment, je dois dire que Docker est vraiment sympa. Petit tour du propriétaire.

C’est comme si chaque process avait sa mini VM

Pour faire simple, docker, c’est de la virtualisation légère.

Ça marche comme cela : on prend une image d’un Linux de base qu’on fait tourner dans Docker, on lui installe de quoi faire tourner un process – par exemple Redis – et on obtient une nouvelle image qui contient juste la différence avec l’image précédente. On fait ça avec plein d’autres process, et quand on a besoin de l’un d’entre eux, on lance l’image qui contient l’install de celui-ci.

Ce sont des images très légères, on peut en lancer 100 sur une même machine si on le souhaite. Mais elles sont parfaitement isolées : elles ont leur propre système de fichier, leurs propres arbres de processus, utilisateurs, permissions, ports… Donc si par exemple je fais un nginx compilé avec des extensions exotiques et une config zarb, je peux en faire une image puis l’utiliser sur n’importe quel serveur qui contient Docker.

Le but, c’est de créer plein de petits conteneurs légers et isolés de votre machine. Et au lieu de configurer chaque serveur, on envoie juste les conteneurs dont on a besoin, et on est certain qu’ils marchent partout pareil, peu importe la config à l’arrivée. On virtualise des services, qu’on combine, et non une machine complète.

Quand je vous dis que c’est léger, c’est VRAIMENT léger. A peine plus gourmand que le process sans la VM. En fait, un conteneur Docker démarre presque instantanément, il n’y a pas de “boot” comme pour une vraie VM. Mais on en a les avantages car votre Redis dockerisé marchera pareil sur une Fedora et une Ubuntu.

Docker ne marche que sur Linux pour le moment, et encore, sur un Linux pas trop vieux car il utilise une technologie récente, dont vous avez probablement peu entendu parler : LXC.

Détail amusant : Docker est pas mal utilisé dans la communauté des devs sous Mac, qui du coup ont une VM Ubuntu dans laquelle ils font tourner Docker pour travailler. N’est-ce pas merveilleux ?

La démonstration obligatoire

Je ne vais pas vous laisser comme ça et vous demander de vous la mettre derrière l’oreille, donc exemple d’utilisation sous Bubuntoune.

Je suis en 14.04, mais je pense que ça marche pareil depuis la 12.04.

Installation :

$ sudo apt-get install docker.io

Cela va mettre la commande docker.io à votre disposition, mais sous d’autres systèmes, elle s’appelle juste docker. Perso je fais un

alias docker="sudo docker.io"

puisque de toute façon il faut toujours lancer cette commande avec les droits admin.

Ensuite, on va télécharger une image de base sur laquelle baser toutes nos images :

$ docker pull ubuntu

Docker.io, ce n’est pas juste un software, c’est aussi un service qui héberge les images. Vous pouvez bien entendu créer vos propres images de base, et héberger votre dépôt personnel d’images, mais on ne va pas se faire chier à faire ça ici. Prévoyez quelques gigas de place, Docker ne prend pas beaucoup de ressources CPU/RAM, mais par contre ça bouffe en espace disque.

Donc là, je demande la récupération des images “ubuntu”, qui vont nous servir d’images de base. Elles sont toutes faites et prêtes à l’emploi, que demande le peuple ?

Ça va downloader pendant pas mal de temps, puis vous aurez plusieurs images à votre disposition, que l’on peut lister ainsi :

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
ubuntu              12.04               8dbd9e392a96        4 months ago        131.5 MB (virtual 131.5 MB)
ubuntu              12.10               b750fe79269d        5 months ago        24.65 kB (virtual 180.1 MB)
ubuntu              latest              8dbd9e392a96        4 months ago        131.5 MB (virtual 131.5 MB)
ubuntu              precise             8dbd9e392a96        4 months ago        131.5 MB (virtual 131.5 MB)
ubuntu              quantal             b750fe79269d        5 months ago        24.65 kB (virtual 180.1 MB)

Vous faites votre marché : quelle image de base voulez-vous utiliser ?

La 12.04 est une LTS qui est déjà bien field testée, donc je vais prendre celle là.

Ensuite, pour lancer une commande avec, il faut faire docker run id_image commande. Ceci va lancer l’image, lancer la commande, et dès que la commande se termine, arrêter l’image :

$ docker run 74fe38d11401 echo 'YEAHHHHHHHHHHHHHH'
WARNING: Local (127.0.0.1) DNS resolver found in resolv.conf and containers can't use it. Using default external servers : [8.8.8.8 8.8.4.4]
YEAHHHHHHHHHHHHHH

Mesurons le temps pris en tout pour faire cela :

$ time -f "%e seconds" sudo docker.io run 74fe38d11401 echo 'YEAHHHHHHHHHHHHHH'
WARNING: Local (127.0.0.1) DNS resolver found in resolv.conf and containers can't use it. Using default external servers : [8.8.8.8 8.8.4.4]
YEAHHHHHHHHHHHHHH
0.45 seconds

Comme vous le voyez, démarrer un conteneur, c’est vraiment rapide. Mais faire un

echo

, ça ne sert pas à grand chose :)

Faisons quelque chose de plus utile : lançons un terminal bash et demandons un accès à stdin/stdout (-i) ainsi qu’un tty (-t) :

$ docker run -i -t 74fe38d11401 bash
WARNING: Local (127.0.0.1) DNS resolver found in resolv.conf and containers can't use it. Using default external servers : [8.8.8.8 8.8.4.4]
root@8195e92a5e62:/#

Et hop, comme le process ne se termine pas tant qu’on est connecté au tty, on a le conteneur qui continue de tourner, mais on a un terminal dessus, nous permettant d’entrer n’importe quelle commande.

Si on installait 0bin ?

D’abord, on a besoin de pip dans le conteneur:

# apt-get install python-pip

Notez qu’on est toujours root dans son conteneur. Après tout, c’est virtuel et isolé, donc pas de raison de se faire chier.

Ensuite, on tape dans pypi :

# pip install zerobin

Pas besoin de virtualenv, on est déjà dans un environnement isolé.

Faisons un petit fichier de config. On va avoir besoin de vim :

# apt-get install vim
# cd /home
# vi settings.py

On met des settings perso pour zerobin, histoire de montrer le principe de faire un conteneur avec un setup sur mesure (on peut, bien entendu, faire 1000 fois plus compliqué, c’est un exemple bande de bananes) :

# on le fait ecouter sur l'exterieur
HOST = "0.0.0.0"
# on change le menu de la page d'accueil
MENU = (
    ('Home', '/'),
    ('Contact', 'mailto:mysupermail@awesomesite.com')
)

Et on lance notre petit zerobin :

# zerobin --settings-file=settings.py

Et voilà, on a un zerobin qui tourne dans notre conteneur isolé.

On va sauvegarder tout ça. Sur notre machine hôte (donc pas dans le conteneur), on va sauvegarder notre nouvelle image. D’abord, on va trouver l’ID du conteneur qui tourne :

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                     NAMES
8195e92a5e62        ubuntu:12.04        bash                37 minutes ago      Up 37 minutes                                 boring_mccarthy

Ensuite on commite ce conteneur pour obtenir une nouvele image. Pour faire simple, on va l’appeller “zerobin” :

$ docker commit 8195e92a5e62 zerobin

On peut alors tranquillement arrêter notre conteneur :

$ docker stop 8195e92a5e62

Maintenant on peut pusher notre image tout neuve sur un repo distant avec docker push pour pouvoir la puller plus tard. Il faut ouvrir un compte sur leur service ou créer son propre depot, je vous laisse trouver comment faire. Plus simplement, on peut juste dumper l’image avec docker save id_image > nom_image.tar, la copier sur une clé USB, et la recharger avec docker load < nom_image.tar.

Dans tous les cas, une fois sur le serveur, vous pouvez lancer votre image "zerobin", ici encore une fois avec la commande bash:

# docker run -t -i -p 7777:8000 zerobin bash

Cette fois, on rajoute une option de plus : -p 7777:8000 dit à docker de relier le port 7777 de ma machine au port 8000 (port de 0bin par défaut) de mon conteneur. Ainsi, si une app va sur le port 7777 de ma machine, elle va en fait parler au port 8000 du conteneur sans s'en rendre compte.

Du coup, je peux lancer mon petit zerobin depuis mon contenur :

# cd /home
# zerobin --settings-file=settings.py

Et voilà ! J'ai un zerobin qui marche, qui est préconfiguré, isolé et portable. Si je change de serveur, ou même d'OS, je peux juste reprendre le même conteneur et le relancer tel quel. Et je peux faire ça avec plein de process et les faire communiquer entre eux.

Capture d'écran de zerobin

J'accède à 7777 sur la machine, mais ça tape sur 8000 dans mon conteneur

Docker est un outil très riche, et vous vous doutez bien que je ne vous ai montré que le début.

Par exemple, vous voulez sans doute ne pas avoir à lancer bash, puis un cd, puis zerobin. Ça peut s'automatiser avec un dockerfile. Vous voulez aussi peut être lancer le conteneur en arrière-plan, ça se fait avec l'option -d. Ou peut être voulez-vous un dossier partagé entre tous les conteneurs ? C'est possible avec l'option volume.

Parmi les usages vraiment intéressants de dockers : packager les services très custos comme les nginx ou ffmpeg compilés avec des options cryptiques, deployer son app django avec toutes les libs Python et les settings qui vont bien, remplacer une app à chaud en redirigeant juste le load balancer sur le nouveau conteneur, ou encore fournir un env de test à un client. Perso, rien que pour tester des nouveaux logiciels sans pourrir ma machine, je trouve ça pratique : on a bien moins peur de faire un make install.

Je vous laisse prendre en main le nouveau joujou, j'ai des serveurs à planter.

30 thoughts on “Le déploiement par conteneurs avec Docker

  • k3c

    Merci pour ce tuto, ça va me mettre le pied l’étrier.

    Typo je suppose
    s/ffpeg/ffmpeg

  • Emmanuel

    Très bonne introduction à Docker.

    Quand en commence à lier les containers en eux, lancer toutes les commandes Docker dans le bon ordre peut vite devenir lassant (surtout si on doit tout rebuilder pour intégrer une modif).

    Pour ne pas se compliquer la vie avec plusieurs containers, j’ai lancé gaudi il y’a quelques semaines : http://gaudi.io.

    Il est dispo via les repos apt et permet de lancer des container liés (avec peu de conf) en une seule commande.

  • blop

    Merci pour le l’article.
    Petites corrections :
    * Docker est vraiment sympas / sympa
    * un conteneur docker démarrer presque / démarre

    Sinon, j’ai un problème ,dès que j’essaie de lancer quoi que ce soit : j’ai en retour la date et “exec format error”…

  • joshuafr

    Ouh que ça a l’air bon ça, m’en vais le tester de suite! Merci pour l’article

  • salelodenouye

    Bonjour,
    J’espère que quelque chose m’échappe, mais est ce que Docker est bien fait pour tourner sur un environnement de production ??
    Et si oui, comment fait-on pour déployer les mises à jours ?

    Par exemple en ayant 3 docks basés sur une même image ubuntu :
    Peut on mettre à jour une seule fois l’image de base ?
    Ou faut-il mettre à jour chaque dock indépendamment ? (Du coup quel est l’intérêt de docker par rapport à un LXC seul ?)

    Bon voila, question con ou pertinente, mais question quand même.

    Bonne journée !

  • residante

    J’utilise tellement docker maintenant, que je ne sais pas comment je faisais avant. Ça facilite tellement la vie.

    On peut même aller plus loin et voir docker comme un système de packaging. On distribue non pas un deb, rpm de son application, mais un petit Dockerfile qui permet de déployer l’application sur n’importe quelle machine, sans se soucier du système, des versions, des dépendances, de l’impact sur ton système etc…

    J’utilise docker sur :
    – Mon laptop : pour le dev essentiellement, mais pas que..
    – Mon NAS : transmission + OpenVPN (+peu de config réseau), samba, nfs, plex, sickbeard…le tout bien configuré une seule fois (Dockerfile = reproductible à souhait)
    – Mon serveur : Plus jamais de prise de tête sur les dépendances. Et avec les volumes, on sépare bien les data/config (persistant) des librairies et du système (runtime)

    Au final je gagne énormément de temps par rapport à avant.

    J’ai toujours rêvé d’une sorte de virtualenv au niveau système, avec docker je suis aux angles :D (Sinon il y a aussi le projet nix pour le “virtualenv au niveau système” mais c’est un autre sujet)

  • mortim3r

    Merci pour ce tuto, top comme d’habitude.
    Ca m’ouvre de nouvelles perspectives pour certains de mes serveurs (le plus dur reste à l’expliquer au chef…).

  • Emmanuel

    @salelodenouye depuis Docker 0.8, tu peux créer des images filles à partir d’une de tes propres images, en mettant à jour la base image ses images filles seront mises à jour également.

    Dans le Dockerfile de ton image de base:
    FROM ubuntu

    Tu la buildes avec :
    docker build -t base/mon_image_ubuntu .

    Pour toutes tes images filles, le Dockerfile sera:
    FROM base/mon_image_ubuntu

    Il y’a ensuite la directive ONBUILD qui permet à l’image de base d’executer des actions sur ses images filles, lus d’info ici : http://www.tech-d.net/2014/02/06/docker-quicktip-3-onbuild/

  • blop

    @Sam : je n’ai pas détaillé car je ne pense pas que des commentaires soient fait pour résoudre des bug, mais si tu as une idée de solution why not et merci pour ton aide.

    Objectif : tester une utilisation basique de docker
    Qu’est-ce que j’ai essayé de faire :
    docker pull ubuntu
    docker run 74fe38d11401 echo 'helloworld'
    Résultats :
    2014/05/16 09:15:43 exec format error
    Au lieu de quelque chose du genre :
    helloworld
    Qu’est ce que j’ai testé pour essayer résoudre mon problème :
    * Lancer d’autres commandes que echo sur l’image
    * Installer d’autres images : docker pull debian
    * Supprimer toutes les images de docker avec : docker rmi $(docker images -q)
    * Réinstaller docker : sudo apt-get remove --purge docker.io
    Quelle est ma situation :
    Linux Mint Debian Edition : Linux 3.11-2-486 #1 Debian 3.11.8-1 (2013-11-13) i686 GNU/Linux
    Docker version 0.11.1, build fb99f99 installé depuis les dépôts

  • Sam Post author

    @salelodenouye: oui docker est fait pour tourner en prod. Néanmoins il n’y a pas de mécanisme de cascade pour les images. Par contre, il y a un mécanisme de cache pour les dockerfile, donc je pense que le workflow est : faire des dockerfiles en cascade, quand on fait un update, on change le docker file, on rebuild (ce qui doit être rapide à cause du cache), et ensuite on swap les conteneurs en prod un par un. Après à mon avis il y a des stratégies différentes au cas pas cas (et quid de la mémoire des process comme avec redis ?). Je ne suis pas encore expert avec docker, donc il me manque des infos sur ce cas. Par ailleurs, les gars de docker stipulent que c’est du beta à ne pas mettre en prod, bien que ce soit déjà fait un peu partout. A mon avis, le mieux est de mitiger le problème en limitant les conteneurs à une responsabilité.

    L’interêt par rapport à lxc seul, c’est la standardisation et l’outillage. Ce post l’explique très bien.

    @blop: j’ai cherché un peu, tout ce que j’ai pu trouver c’est un bug qu’on retrouve dans les versions de docker < 0.9. Donc si c’est ton cas, essaye d’up.

  • salelodenouye

    @Emmanuel

    Haa merci ! c’est l’info qu’il me manquait.
    Dans ce cas c’est tout bonnement génial :)
    c’est de la “containerisation” objet en somme (désolé pour le barbarisme).

    On peut vraiment faire toutes les tâches “chiantes” une fois et se concentrer sur la partie intéressante.

    J’en connais un qui va essayer d’utiliser LXC autrement :)

  • Anb

    Chouette article, il faut vraiment que je test ce truc.

    Pour l’intérêt par rapport à LXC, dans leur présentation ils font l’analogie avec les containers maritimes :

    Avant c’était la memerde, fallait attacher tout un fatras de caisses/sacs de différents formats, ça prenait un temps fou et ça se pétait la gueule régulièrement.

    Avec les containers, on les empiles les un sur les autres sans se soucier de ce qu’il y a dedans, c’est standard, ça s’attache facilement puisque c’est prévu pour et on gagne en rapidité et fiabilité.

  • Goldy

    Moi aussi ça déconne, avec des Unrecognized input header quand je cherche à lancer après avoir modifié et commit une image.

    En tout cas l’outil à l’air puissant et va me permettre de faire plein de choses quand il sera prêt pour la prod :3

  • Silvus

    Humm, faire un alias docker="sudo docker.io", c’est cool mais ça casse l’autocomplete.

    Du coup pour le conserver tout en gardant l’alias :
    alias docker="sudo docker.io"
    complete -F _docker docker

  • Sam Post author

    Hum, bon à savoir.

    Un autre truc: si on se rajoute au groupe “docker”, on a plus besoin de sudo.

  • Arnaud

    quelques tutos ont été mis à disposition ici, dans un contexte de déploiement de docker à l’échelle d’une entreprise, avec des réponses aux questions légitimes qu’on peut se poser autour de cette solution. Orchestration des conteneurs, cycle de vie, gestion de la disponibilité (aussi bien en local qu’à distance), haute dispo, multitenant.

  • Oliver

    Incroyable comme j’ai reconnu Jérôme sans que son nom soit prononcé! Je ne suis donc pas le seul à qui il a laissé cette impression.

  • Matt

    @blop : tu as sans doute trouvé la solution depuis longtemps, mais je pense que ton problème est lié au fait que ta distrib Mint est en 32 bits (” Linux 3.11-2-486 #1 Debian 3.11.8-1 (2013-11-13) i686 GNU/Linux”).
    Docker, et les images existantes, ne fonctionnement qu’en 64 bits, en tout cas pour l’instant (en interne, Docker a déjà des éléments pour gérer différentes architectures, mais je ne sais pas ce qu’ils vont en faire).

  • Sam Post author

    Docker est parfaitement près pour la prod. Docker vise l’isolation des composants pour le déploiement, pas le sandboxing pour la sécurité. Il faut utiliser le bon outil pour la bonne tâche, et pas se plaindre que son marteau abîme les pas de vis.

  • blop

    @Matt Merci,le problème vient de là mais je ne l’avais pas résolu. J’ai testé avec plusieurs distributions, sans succès.
    D’après ce que j’ai lu il faut le recompiler pour que ça fonctionne en 32bits (ma bécane tourne sur du Celeron je ne peux pas mettre d’OS amd64). Tant pis ce sera pour une prochaine machine.

  • Fab

    Merci pour la découverte, ça a l’air géant. Cependant j’ai une question bête, mais je la pose quand même. Comment gère-t-on la persistence des données d’une application? Pour un CMS connecté à une BDD par exemple, puis ayant un dossier d’upload : doit-on gérer une BDD et un dossier partagé hors du container? Comment fait on quand on veut mettre à jour son conteneur?

  • Matt

    @Fab : Oui, puisque ta BDD est quelque chose que tu veux vraiment conserver au cours des changements/upgrades de container, tu devrais stocker ses données sur un ‘volume’ Docker, cf l’option ‘-v’.
    Même chose pour le dossier d’upload.
    Plusieurs solutions:

    – créer un volume uniquement lié au container,
    – créer un volume sur l’hôte physique avec un montage ‘bind’ dans ton container
    – créer un container avec un volume (le container ne fait rien et peut même être stoppé) et utiliser l’option ‘volumes-from’ pour chaque container devant utiliser le volume

  • Fab

    @Matt : ok, effectivement c’est logique. J’ai jeté un oeil dans la doc et j’étais passé à côté d’une page dédiée à cette problématique. Du coup la solution de créer un container avec un volume semble bien appropriée. Ceci dit ça ajoute encore à truc à gérer alors lors du déploiement, mais j’ai vu qu’il y avait des outils pour orchestrer tout ça. Je vais tester tout ça, ça a l’air vraiment pas mal.

  • jc

    Merci pour le tuto!

    Je me suis basé dessus pour passer direct aux travaux pratiques, et créer une image configurable/réutilisable pour 0bin (la version php de zerobin en avait déjà une, pas la votre, c’était un excellent prétexte)

    Les sources sont là (Dokerfile + custom settings.py):
    https://github.com/jcsaaddupuy/docker-0bin

    L’image est là :
    https://registry.hub.docker.com/u/jcsaaddupuy/0bin/

    Et le how-to est là (full disclo : c’est de la pub, j’ai écris cet article):
    http://slash-dev-blog.me/how-to-dockerize-0bin.html

Leave a comment

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <pre> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Des questions Python sans rapport avec l'article ? Posez-les sur IndexError.