Pour les plus attentifs d’entre vous, vous aurez remarqué que cela fait un moment que je n’ai pas publié d’article ici. Le problème n’étant pas un quelconque manque de motivation ou d’idées, mais plutôt d’ordre technique.
En effet, mon netbook étant ma principale plateforme de développement GNU/Linux et m’ayant lâché récemment (problème matériel, sans doute la carte mère), je me retrouve à devoir recréer un système de déploiement pour mon site (et à racheter un portable par la même occasion).
Le problème n’étant pas forcément la publication du site statique généré vers le serveur, mais plutôt la génération de ces pages.
Meet the Cabal Hell
C’est quoi ?
Si vous avez déjà fait un peu joujou avec Haskell, vous avez peut-être rencontré un problème de gestion de dépendances avec l’outil d’installation de bibliothèques externes Cabal (équivalent de pip pour les Pythonneux).
Étant donné que les problèmes de dépendances sont particulièrement difficiles à résoudre avec Cabal, on parle donc de « Cabal Hell », enfer de Cabal.
Cette configuration peut apparaitre de différentes manières toutes plus variées les unes que les autres. On peut prendre l’exemple suivant pour illustrer ce genre de problème.
Votre programme dépend d’une bibliothèque A et d’une bibliothèque B, sachant que :
- A dépend de C version 1
- B dépend de C version 2.
Quelle version de C installer pour que tout fonctionne ? C’est impossible, il faut donc voir avec les mainteneurs de ces bibliothèques pour que les deux utilisent la version la plus récente de C.
Au niveau d’Hakyll
Mon problème à moi™, c’est que Archlinux propose actuellement une version trop récente de GHC. Si ce n’était que le compilateur, cela ne poserait pas problème car les bibliothèques seraient aux bonnes versions, sauf que GHC fourni une bibliothèque « de base » (nommée base d’ailleurs) qui possède du coup une version trop récente !
Il est donc impossible de compiler Hakyll à cause de la dépendances à d’autres bibliothèques qui dépendent de base plus ancien. Plutôt que de perdre du temps à aller me plaindre sur IRC, je me suis dit que c’était l’occasion d’aborder le problème autrement, d’où Docker.
Des distributions peu adaptées
De la multitude de distributions GNU/Linux qui trainent un peu partout, il y en a deux que j’apprécie particulièrement et dont j’aurai du mal à me séparer : Archlinux et Debian.
Cependant, ces deux dernières ne sont pas adaptées au process de build du site, principalement à cause de cette histoire de versions incompatibles au niveau de Haskell.
Archlinux, trop à jour
D’un côté, Archlinux propose des versions trop récentes de GHC, ce qui fait que les bibliothèques derrière ne suivent pas et l’ensemble devient inutilisable.
Il existe cependant un dépôt spécial Haskell pour Archlinux nommé ArchHaskell que j’ai pris le temps d’expérimenter, mais celui-ci n’est pas à la hauteur de mes attentes, notamment au niveau des mises à jour qui provoquent un burn CPU de 5s par bibliothèque car il a l’air de générer la documentation à chaque fois. Multiplié par 100 ça commence à faire beaucoup de temps perdu pour pas grand chose.
Debian, pas assez à jour
De l’autre côté, mon expérience de Debian se limite à Debian stable (et je n’ai pas envie de me dépatouiller à faire marcher un SID). Les paquets sont tout naturellement beaucoup trop anciens pour espérer faire ce que ce soit sur cette distribution avec Haskell.
Docker à la rescousse
Le but est de trouver une distribution qui nous propose une distribution Haskell ni trop récente, ni trop ancienne. C’est ici qu’intervient Ubuntu, dont nous allons pouvoir créer un container Docker pour avoir quelque chose qui fonctionne.
Présentation de Docker
Pour ceux qui ne connaissent pas Docker, c’est comme un système de machines virtuelles (cf. VirtualBox), sauf que ça ne virtualise pas tout un ordinateur mais juste ce qu’il faut pour simuler une autre distribution Linux (pour faire simple).
L’avantage est que cette virtualisation est beaucoup plus légère que de simuler un ordinateur x86 complet ; le noyau Linux est partagé donc la consommation mémoire ainsi que le temps de démarrage sont fortement réduits tandis que la performance est quasiment celle d’un applicatif lancé sur l’hôte.
Prototypage
Une fois une image Docker de Ubuntu téléchargée sur la machine (qui est plus petite qu’une vraie distribution, comptez dans les 300 Mo), on peut lancer une instance de ce container et jouer avec pour essayer de faire tourner GHC dessus.
Il faut penser à enregistrer les changements apportés à la machine en effectuant un commit à chaque changement pour considérer la dernière instance comme le nouveau point de départ pour la création de futures instances (sinon vous repartirez d’une image brute d’Ubuntu à chaque fois).
Des résultats encourageants
Après avoir installé GHC, libghc-hakyll-dev et un pack de langues français pour gérer l’UTF-8, tout fonctionne et on peut maintenant compiler le site à l’intérieur de la VM en utilisant un point de montage pour les sources du site entre le conteneur et le système hôte.
Écriture d’un Dockerfile
Afin de pouvoir recréer ce conteneur sur d’autres systèmes, on peut se lancer dans l’écriture d’un Dockerfile qui − comme un Makefile − permet de répéter le processus de construction sur d’autres systèmes.
En y inscrivant l’image de base sur laquelle se base votre instance (Ubuntu 14.04 ici) ainsi que les différentes opérations à effectuer pour la rendre prête à opérer, on peut rendre reproductible la création de ce conteneur.
La syntaxe est plutôt sympa et concise. Voici le contenu du Dockerfile que j’ai réalisé pour vous donner une idée :
FROM ubuntu:14.04 MAINTAINER MicroJoe <microjoe@yolo> # Install all required stuff + create user RUN apt-get update && apt-get upgrade -y && \ apt-get install -y ghc libghc-hakyll-dev language-pack-fr make && \ useradd microjoe
Et voilà, plus qu’à appeler docker build pour construire une instance de ce conteneur (ce qui peut prendre un peu de temps) et qui sera directement opérationnel par la suite !
Conclusion
Docker possède un énorme potentiel et permet de créer des sortes de « sandboxes » sur son système de manière plutôt propre. Je pense que je n’hésiterai pas à m’y réessayer si besoin est dans un futur proche.
Cependant, il faut faire attention à ne pas tomber dans une folie qui consisterait à essayer de tout mettre sous la forme de conteneurs Docker. En effet, cet outil est pratique en période de développement mais il ne semble pas adapté à un contexte de production pour l’instant.
Par exemple, en cas de faille de sécurité sur un composant critique tel qu’OpenSSL, il faut alors mettre à jour un à un les conteneurs utilisés en production. De plus, il faut faire entièrement confiance au fournisseur de l’image de base sur laquelle s’axent les instances (ne pas prendre l’image qu’une personne a réalisée elle-même dans son coin pour s’en servir en production).
Peut-être qu’en automatisant la mise à jour des conteneurs contre les failles de sécurités et en privilégiant uniquement les images « officielles » fournies par les distributions elles-mêmes, on pourra alors faire un peu plus confiance à Docker pour le déploiement en production d’applications.