Naviguer sur votre infrastructure avec Docker

Naviguer sur votre infrastructure avec Docker

J’ai écrit, au cours de cette année, de nombreux articles à propos de Chef et comment automatiser son infrastructure et ses applications. J’ai également contribué à quelques librairies autour de cette technologie et son intégration au sein des différents processus et projet de JoliCode.

De temps en temps, il est bon de prendre un peu de recul sur notre travail : peut-on faire mieux et plus simple ?

Ces derniers temps, j’ai lu de nombreux articles à propos de Docker, et l’ai essayé pour différents projets. Cependant, avant d’aller plus loin, et pour être sûr de l’utilité de cet outil, je me suis posé une question assez simple :

De quoi a-t-on besoin pour automatiser ses applications et leurs configurations ?

Installation

La première chose, dans un nouveau projet, est de définir puis d’installer toutes les applications et les dépendances nécessaires. Pour automatiser cette partie, les scripts doivent donc garantir que, peu importe le serveur, ce sont les mêmes dépendances et les mêmes versions qui seront installées.

Configuration

Une fois l’installation achevée, il faut configurer toutes ces briques. Cette configuration peut se diviser en trois parties :

  • Configuration statique (ou par défaut) : cette configuration sera exactement la même sur tous les serveurs. Cette partie est en général directement incluse dans le processus d’installation ;
  • Configuration dynamique : celle-ci sera différente selon notre environnement (production, recette, développement, etc …). Un gestionnaire de configuration est nécessaire pour automatiser cette partie ;
  • Configuration dynamique : selon l’infrastructure où comment chaque service / application communique avec les autres : l’orchestration est votre amie.

Orchestration

Les projets évoluent, et peuvent nécessiter de plus en plus de serveurs. Or, cette scalabilité impacte principalement la production, et nous n’avons pas forcement envie de reproduire l’architecture à 100 serveurs pour développer en local.

Le rôle de la phase d’orchestration consiste à organiser correctement une infrastructure, peu importe le nombre de serveurs qui la composent. Cette phase sert aussi à lancer les scripts d’installation et de configuration sur tous les serveurs, en leur apportant le savoir nécessaire pour communiquer entre eux. C’est possible car l’outil d’orchestration a la connaissance complète de l’infrastructure.

Déploiement

Il est commun d’automatiser le déploiement d’un nouveau projet en utilisant Capistrano, Fabric ou bien d’autres outils. Mais en quoi consiste concrètement un déploiement ?

  • L’installation d’une application (en allant récupérer les sources ou un paquet généré) ;
  • La configuration de cette application selon le serveur et l’environnement ;
  • S’assurer que la configuration permet de communiquer avec les autres services.

Je pense que vous voyez où je veux en venir : toutes ces étapes sont déjà traitées par les trois points précédents.

Pourquoi parle-t-on de déploiement, alors ? C’est simple : l’installation d’un projet (par exemple, une application Symfony) ne se produit pas au même rythme que celle de ses dépendances (par exemple, Apache ou Nginx). Par ailleurs, il existe généralement un mécanisme de rollback permettant d’annuler ce déploiement, alors que c’est plus rare pour les dépendances (même si cela peut parfois arriver).

Le système que l’on va mettre en place doit donc simplement respecter les trois premiers points, avec certaines contraintes :

  • Il doit être rapide ;
  • Il doit être capable de rollback (et de manière rapide).

État actuel

Depuis quelques années, de nombreux outils d’automatisation ont émergé. Les plus populaires partagent quelques caractéristiques :

  • Ils sont généralement construits autour du même principe : l’idempotence, c’est-à-dire que l’on définit l’état souhaité de la machine et pas comment y parvenir ;
  • Ils font un peut tout : orchestration, installation, configuration, … (et pas encore le café) ;
  • Aucune pré-requis de système d’exploitation, cela est censé fonctionner sur tous les OS ;
  • Un tout nouveau langage à apprendre pour scripter l’automatisation.

Ils sont concrètement très complets et permettent de bien gérer des infrastructures complexes, surtout si on les compare aux anciennes méthodes. Cependant, de par leur principe, ces outils posent plusieurs problèmes :

  • ils sont rapides, mais pas assez rapides pour du déploiement. Le temps initial pour déployer sur un nouveau serveur est long, de l’ordre d’une dizaine de minutes pour une application moyenne. Ce temps est tout à fait normal et sera toujours le même, peu importe l’outil employé, car l’installation est fortement dépendante du téléchargement de toutes les dépendances.

    Un point est plus gênant : les exécutions successives de ces scripts sont également lentes, de l’ordre de la minute pour un même projet. La raison de cette lenteur est le principe d’idempotence, dans la mesure où, à chaque fois que le script est lancé, il doit vérifier pour chaque dépendance qu’elle est bien synchronisée avec l’état souhaité (dans une certaine version, configurée de telle manière, etc.) . Cette phase peut être très rapide s’il s’agit simplement de faire un différentiel de fichiers, mais elle peut prendre beaucoup de temps s’il faut vérifier la version d’une librairie sur un service externe. Votre patience et votre machine à café sont alors mises à rude épreuve lors du débogage d’un script Chef ou Puppet sur une infrastructure volumineuse.

    De plus, ces scripts sont lancés sur tous nos serveurs, et les opérations sont donc répétées n fois. Une autre approche pourrait consister à répliquer sur l’ensemble des serveurs de même type l’état final obtenu par l’exécution des scripts sur un unique serveur. Ce comportement est tout-à-fait possible à mettre en place (avec un paquet système, une VM pré-générée), mais il faut tout de même ajouter une couche supplémentaire demandant un certain investissement.

    Avec cette lenteur, il est difficile de synchroniser des déploiements sur différents serveurs.

  • Ces solutions, avec leurs propres APIs ou frameworks, ajoutent une couche de complexité et d’investissement supplémentaire à notre infrastructure. Leur coût n’est pas négligeable pour la création initiale du projet, même si avec le temps cet investissement sera amorti. Malgré tout, il peut être difficile de demander au décideur deux semaines ou plus pour outiller correctement le déploiement d’une application.

  • Dernier point, les scripts créés sont très rarement indépendants du système d’exploitation, et il y aura toujours du code ou de la configuration spécifique fonctionnant uniquement sur un système donné. Les librairies employées pour écrire les scripts de déploiement offrent certes des fonctionnalités permettant de s’astreindre des différences entre OS, mais cela nécessite que les auteurs de ces scripts fassent bien leur travail et utilisent correctement les APIs mises à leur disposition – ce qui ajoute encore un peu plus d’investissement.

Docker, une autre façon de penser.

Docker est apparu fin mars 2013 dans une version publique. Pour le résumer, il possède deux rôles :

  • Sa fonction principale est d’encapsuler une application et ses dépendances dans un container, avec LXC;
  • Il est aussi capable d’enregistrer un état d’un container à travers des images. Ces états peuvent ensuite être commit et push dans un registre Docker puis être pull pour récréer une infinité de containers avec ces images.

C’est rapide ?

Docker est très rapide. Quand on pull un nouvel état, aucune commande n’est jouée, car cet état a déjà était généré. Le pull est donc un différentiel fichier sur tout le système grâce au système de fichiers AUFS. Le facteur principal de la durée de mise à jour sera donc souvent la taille du différentiel par rapport à la bande passante disponible entre le serveur et le registre.

C’est complexe ?

Docker utilise un Dockerfile, qui explique comment aller de l’état A à l’état B, et comment le container fonctionne. Voyez par vous-même un exemple pour un container faisant tourner l’application Jenkins :

FROM ubuntu
MAINTAINER Allan Espinosa "allan.espinosa@outlook.com"

RUN echo deb http://archive.ubuntu.com/ubuntu precise universe >> /etc/apt/sources.list
RUN apt-get update
RUN apt-get install -q -y openjdk-7-jre-headless
ADD http://mirrors.jenkins-ci.org/war/latest/jenkins.war /root/jenkins.war

EXPOSE 8080
CMD ["java", "-jar", "/root/jenkins.war"]

L’état de départ est une simple image d’Ubuntu, vierge de toute application, qui est déjà disponible sur le registre public de Docker. Puis, certaines commandes sont jouées pour installer les dépendances (par exemple openjdk-7-jre-headless) et télécharger les fichiers nécessaires (jenkins.war). Après cela, il ne reste qu’à spécifier ce qu’expose notre service : quelle commande, et sur quel port.

Toutes les commandes sont simplement des instructions Unix que tout le monde connaît. Seuls quelques mot-clés assez simples doivent être connus pour pouvoir créer un Dockerfile. Il existe une belle et courte documentation expliquant tous ces mots-clés. Il suffit ensuite d’utiliser l’outil en ligne de commande Docker pour push, commit et pull vos images.

Le shell de Docker

Il n’est pas obligatoire d’utiliser un Dockerfile. On peut, en effet, travailler directement une image en effectuant les commandes et tâches nécessaires, puis commit les changements dans une image. Le Dockerfile est tout de même recommander car il permet de conserver une trace des opérations effectuées sur votre image.

Pour un meilleur début sur Docker et un guide plus complet, il existe un très bon tutoriel interactif. Docker est aussi simple que cela, une fois votre image créée, vous pouvez la réutiliser n’importe où !

Mais c’est spécifique à un système d’exploitation, non ?

Oui et non : vos containers sont spécifiques à un OS, mais il n’est pas forcement le même que celui de votre système hôte. C’est d’ailleurs un plus, car on peut ainsi cibler le meilleur système pour chacun de nos services, et on ne se préoccupe pas de la généricité de nos scripts. Cependant, vu qu’ils partagent tous le même noyau, le seul besoin est que chaque système doit s’appuyer sur un noyau Unix.

Peut-on orchestrer ses containers avec Docker ?

Actuellement, il n’est pas possible d’orchestrer plusieurs conteneurs Docker. C’est une fonctionnalité que dotCloud, l’entreprise qui sponsorise le développement de Docker, veut ajouter dans le futur.

De mon point de vue, c’est un plus car j’aime la façon dont Docker reste simple et n’essaye pas de proposer des milliers de fonctionnalités. Son seul rôle consiste à installer et exposer un service, et il le fait bien.

Si vraiment c’est une feature que vous souhaitez, il existe des outils prometteur, comme Flynn ou Deis pour orchestrer ses containers.

Du coup, on oublie Chef, Puppet, … et on ré-écrit tous nos serveurs avec Docker ?

Certainement pas : Docker est jeune et même les principaux contributeurs ne recommandent pas son utilisation en production, même si plusieurs entreprises ont déjà franchi le pas. De plus, pour des infrastructures larges et complexes, Chef, Puppet, etc., sont pour le moment bien meilleurs car ils disposent d’un écosystème plus développé et proposent une meilleure stabilité.

Mais les choses bougent vite, gardez ce nom en tête, commencez à jouer avec, et jetez-y un coup d’oeil. Je recommande même aux personnes n’ayant jamais mis les pieds dans l’automatisation d’infrastructure de commencer directement par Docker : vous ne perdrez pas votre temps.

Peut on utiliser ces outils en même temps ?

Oui et non.

Pour exemple, utiliser Chef comme un orchestrateur de vos containers (ce que fait Deis) peut être une bonne idée, même si de mon point de vue je trouve que c’est un overkill, par son nombre de fonctionnalités, et qu’il vaut mieux attendre des outils plus simples se basant sur Docker.

Ceci dit, il ne faut surtout pas faire tourner un chef-client ou un puppet apply à l’intérieur de nos containers. Ces deux systèmes sont opposés sur leur principe, l’un enregistrant les changements d’état au fur et a mesure, alors que l’autre est idempotent. Ainsi, un même chef-client ne produira pas la même suite de commandes selon le serveur, mais il sera enregistré de la même manière sous un Docker, ce qui, à terme, vous apporterait un grand nombre de complications.

Quelques conseils et mots finaux.

Docker est fait pour être scalable et est très bon dans ce domaine, grâce à sa vitesse et sa facilité de reproduction. Une image doit toujours être construite dans cette vision. Si un service a besoin de plusieurs applications pour y arriver : utilisez supervisord, c’est actuellement l’approche la plus commune pour ce cas.

Si vous n’avez jamais mis les pieds dans le monde de Docker et que vous possédez quelques minutes, testez le, cela vaut le détour !

blog comments powered by Disqus