Containériser avec Docker

Docker est une application développé en langage Go initialement par le fondateur de dotCloud: Solomon Hykes.

Aujourd'hui le dépôt git contient plus de 8 450 commits, grâce aux 441 contributeurs, avec le tout premier commit créé le 19 Janvier 2013.

Docker face aux machines virtuelles

De nos jour, les fournisseurs d'hébergement ou de serveurs proposent en majorité des VPS ou KVM, sinon des serveurs dédiés.
Ces VPS et KVM sont, au final, un assemblage virtuelle de ressources (tant de CPU, tant de RAM et de disques).
Il y a donc une couche supplémentaire entre le système d'exploitation de la machine et votre serveur où vous y aurez installé un système d'exploitation comme Ubuntu par exemple.

Cet ensemble va donc consommer un supplément de ressources que Docker ne fera pas car un container tourne dans le système d'exploitation principale.
Ensuite l'utilisation de cgroup permettra qu'un container ne vienne pas perturber le fonctionnement d'un autre container.

La seule solution que je connaisse qui se rapproche de Docker est OpenVZ.

Installer Docker

Docker peux-être installé sur toutes les plate formes grâce a l'apparition récente de Boot2Docker.

Mais il est plus intéressant d'utiliser Docker sous Linux, car nous n'aurez besoin de rien d'autre que le Linux que nous utilisez tout les jours (alors que sous Mac et Windows, VirtualBox ou VMware sont obligatoire.

Pour installer Docker, rendez-vous sur la page Get Docker et sélectionnez la documentation qui correspond à votre cas.

Préparer votre application pour être containériser

L'idée d'un container est que une fois créé, il puisse fonctionné n'importe où où il se trouve.

Disons que vous déployez votre container lié à une base de donnée, et que pour X raisons vous devez changer l'adresse, le port, le nom d'utilisateur etc... vous pouvez modifier votre fichier de configuration (database.yml pour Rails), puis re-créer les images ... mais ca prendra beaucoup de temps, et c'est assez fastidieux.

L'autre façon est d'utiliser des variables d'environnements.
Voici un extrait du fichier database.yml de brewformulas.org:

Comme vous le voyez, je peux publier le fichier qui d'ordinaire ne se fait pas, sans risquer quoi que ce soit.
Quand je démarre mon container je n'ai plus qu'a passer les variables d'environnement pour que le container fonctionne.
Je peux aussi re-déployer mon container avec d'autres variables.
J'aurai put même aller encore plus loin en préparant une configuration par type de base données, et pouvoir passer le type de base de données grâce à une variable d'environnement.

Containériser votre application

Pour vous expliquer comment j'utilise Docker, je vais prendre comme exemple ma petite application brewformulas.org dont vous pourrez trouver le code source sur Github.

Docker peux être utilisé de deux façons différentes:

  • Manuellement, ce qui consiste en la création d'une image, puis vous la modifiez en installant des paquets (avec apt-get pour Ubuntu par exemple), changez des fichiers de configuration etc... puis, comme avec Git, vous commiter vos changements.
  • Automatiquement, ce qui consiste en la création d'un fichier Dockerfile qui décrit chaque changement a effectuer pour que l'image soit prête.

Nous allons utiliser la dernière option.

Résultat attendu

Avant de se lancer dans la création d'images Docker, je vais vous expliquer ce que nous allons réaliser:

  • 1 image principale
  • 1 image pour l'application web
  • 1 image pour les tâches de font (Sidekiq)

L'image principale contiendra le code source de notre application, et tout les gems installé.

Les deux images suivante seront créé à partir de l'image principale, puis pour l'image web, tagué web, il faudra ouvrir le port 80 et y démarrer l'application, alors que pour l'image des tâches de font, tagué worker, il faudra seulement lancer sidekiq.

Voici une représentation (généré par Quay.io):

Dockerfile

Un Dockerfile est un fichier qui décrit l'image source a utiliser, puis chaque modifications que nous voulons appliquer à cette image.
Ce fichier est a créer dans le dossier de votre application, puis il suffit d'utiliser l'action build de docker pour fabriquer l'image.

Voici le Dockerfile de brewformulas.org (pour fabriquer l'image principale):

Première instruction FROM. Ici j'indique que je désire utiliser l'image publique, hébergé sur l'index de Docker.io, litaio/ruby.
Cette image contient la dernière version d'Ubuntu (14.04) et de Ruby (2.1.2).
C'est de cette façon que vous devez choisir vos image. Regardez ce dont vous avez besoin, puis recherchez une image qui peux vous fournir une base, comme ici pour moi Ruby.

L'instruction suivante est MAINTAINER. Cette instruction n'a aucun effet sur l'image, c'est une instruction purement informative.

Ensuite grâce à l'instruction RUN, vous pourrez lancer différentes commandes, qui seront exécuté uniquement pendant la construction de l'image, mais pas lorsque vous ferez tourner l'image.
Ici, de la ligne 10 à 14, je m'assure que tout les paquets sont bien à jour, et que wget et git (qui sont utilisé par la suite) soient bien installé.

De la ligne 17 à 21, j'utilise wget pour installer newrelic.

De la ligne 24 à 29 je créer un répertoire /application à la racine du container et j'y clone le dépôt git de breformulas.org.

Finalement les dernières lignes installent Bundler, puis toutes les dépendances du mon application, comme ce serai fait sur le serveur de production.

Construire l'image principale

Maintenant que nous avons notre Dockerfile prêt, il suffit de lancer l'action build de docker, en précisant un nom pour notre image, et pour finir l'emplacement de notre fichier Dockerfile:

sudo docker build zedtux/brewformulas.org .

La commande va télécharger en premier l'image litaio/ruby, puis vous verrez chaque commande exécutée puis commitée.

Une fois la construction terminé, vous pourrez voire votre image avec la commande sudo docker images:

Félicitations! Vous venez de créer votre première image Docker! :-)

Construire les images tagué

Structure des dossiers

Pour pouvoir créer nos images tagués (web et worker), il nous faut 2 autre fichiers Dockerfile.

Nous ne pouvons mes avoirs dans le même répertoire que l'application pour des raisons évidentes de collisions dans les noms de fichiers.

Voici donc comment j'ai structuré mon projet pour réaliser les images tagués:

Le fichier Dockerfile utilisé pour l'image principale se trouve dans le dossier brewformulas.org/brewformulas.org, tant dis que pour les deux autres images tagué j'ai un soucis dossier brewformulas.org/docker/ puis un dossier par tag.

Le Dockerfile pour le tague web

Voici le Dockerfile pour créer l'image tagué web:

L'instruction FROM réfère donc à l'image principale créé juste avant, et le MAINTAINER reste le même.

Ensuite, puisque nous faisons l'image web, il nous faut compiler les assets de Rails (compresser les CSS et les JS) grâce à la commande bundle exec rake assets:precompile à la ligne 12.

Avant de lancer l'application, nous ouvrons le port 80 du container, créer la variable d'environnement pour indiquer l'environnent de production aux lignes 16 et 17.

Pour finir j'ai ajouté l'instruction ENTRYPOINT. Cette instruction sera celle lancé lorsque vous exécuterez l'action run de docker.
C'est a dire que cette instruction est l'application qui tourne dans le container. Sans elle, votre container se lancerai puis finirai tout de suite après.
Ici j'utilise cette instruction pour lancer newrelic, puis je m'assure que la base donnée est bien créée et que toutes les migrations sont bien exécutées avant de lancer l'application sur le port 80.

Cela veux dire que la première fois que vous déploierez vos containers, et démarrerez l'image tagué web, vous verrez dans les logs que la base de donnée sera créée puis que les migrations vont tourner.
Par contre, la prochaine instance (soit vous re-déployez, soit vous augmentez le nombre de container qui tourne simultanément), seule le serveur sera démarré.

Ici nous allons créer une nouvelle image, mais en tant qu'un tag de l'image principale.
C'est à dire que en fin de compte, vous aurez que un seul dépôt, mais avec 2 images qui vont 2 choses différentes.

Pour créer notre image tagué web il faut lancer docker, toujours depuis le dossier de l'application (brewformulas.org/brewformulas.org dans mon cas), de cette façon:

sudo docker build -t zedtux/brewformulas.org:web ../docker/web

L'argument -t permet de spécifier le nom du tag.

L'or de la création de l'image principale, nous avions passé un point (.) comme chemin de fichier pour le Dockerfile puisqu'il se trouvait dans le dossier de l'application.
Ici nous passons le chemin du Dockerfile web ../docker/web.
Notez qu'il est possible de passer une URL d'un dépôt git contenant le fichier Dockerfile par exemple.

Le Dockerfile pour le tag worker

Le principe est exactement le même que pour le tag web, donc je ne m'attarde pas trop dessus.

Voici le Dockerfile:

Ici je démarre newrelic et sidekiq.

Puis il faut lancer la création du tag:

sudo docker build -t zedtux/brewformulas.org:worker ../docker/worker

Résultat

Voici ce que vous devez obtenir:

Configuration de newelic

Peut-être vous êtes vous intérogé pour newrelic comment est-ce que je passe ma clé de lisence puisque null part je modifie le fichier de configuration de newrelic.

Après analyse du fichier init.d de newrelic, je me suis apercu que ce script regarde si la variable d'environnemnt NEW_RELIC_LICENSE_KEY existe.
Si c'est le cas, le script va modifier le fichier de configuration afin d'y mettre la clé passé, puis démarre l'agent de newrelic.

De cette façon je peux publier le Dockerfile sans risque que quelqu'un récupère ma clé :-)

Conclusion

Comme vous l'aurez vue, c'est assez facile une fois que l'on a compris le fonctionnement du fichier Dockerfile.

Je vous invite à lire la documentation du Dockerfile puis-qu’ici je vous ai montré qu'une partie des possibilités.