Docker et Make sur nos projets : on voulait améliorer le confort de nos développeurs, maintenant ils sont accro
Chez Troopers, nous utilisons Docker et Make pour simplifier la gestion technique de tous nos projets depuis 2019. C'est une conférence de Romain Gautier qui nous a ouvert les yeux à l'époque.
Je vais vous expliquer pourquoi je n’imagine plus faire sans Docker et Makefile pour nos projets, tout en évitant d’entrer dans une présentation exhaustive et trop technique.
Pourquoi utiliser Docker ?
En deux mots : pour reproduire un environnement projet rapidement, sans dévier de la configuration cible commune à tous. Sans cela, on risque de perdre du temps à chaque fois qu’un nouveau développeur arrive sur le projet.
Docker est une solution de virtualisation applicative : Docker ne crée pas de machines virtuelles, mais des conteneurs. La configuration logicielle (système d’exploitation, logiciels installés etc) des conteneurs est définie au travers de fichiers texte appelés Dockerfile.
Nos projets ont des configurations diverses : Apache ou Nginx ? MySQL ou PostgreSQL ? ElasticSearch ? Sans parler de leurs versions, ni de celles de PHP, Node…
Nous définissons un Dockerfile pour chacun de ces éléments et Docker nous permet de définir quelles applications exécuter dans quel conteneur. Ces applications sont isolées de la machine hôte : machine de développeur, serveur d’intégration continue, serveur de (pré)production… Fini les conflits de versions lorsque l'on change de projet, fini les dépendances manquantes sur le serveur.
Chaque projet contient sa propre configuration Docker, qui verrouille les versions utilisées mais aussi les tâches d'installation et de configuration. Besoin d'un module PHP peu courant ? Cela sera inscrit dans la configuration et installé automatiquement dans tous les environnements du projet.
Et même si l’environnement de production n'est pas dockerisé, ce n'est pas un problème : vous pouvez reprendre la procédure décrite dans le Dockerfile et être certains de construire un environnement compatible.
Docker-compose : "un service, un conteneur"
Dans chaque projet, la configuration Docker est découpée en plusieurs fichiers. Cela permet de configurer séparément chaque service du projet (serveur web, base de données, application PHP ou JS...).
Lorsque votre projet évolue et qu'il requiert un nouveau service, vous n'aurez qu'à ajouter la configuration Docker de ce service, construire et lancer le conteneur et configurer votre projet pour qu'il communique avec...
Aïe ! Faire communiquer des conteneurs Docker entre eux, ce n'est pas très facile lorsque l'on se limite à l'outil Docker de base. C'est là le rôle de docker-compose : permettre à nos conteneurs de discuter entre eux avec une configuration minimale.
Déjà deux outils supplémentaires…
J'ai remarqué qu'à ce stade, les développeurs ne sont pas forcément convaincus : Docker et docker-compose ajoutent de la complexité, ce sont de nouveaux outils à apprivoiser. Et surtout, on se retrouve avec des commandes à rallonge.
Alors que je pouvais tranquillement faire un composer install
lorsque le projet était installé en local sur ma machine, je me retrouve à devoir taper docker-compose exec php composer install
. Et franchement, c'est moins bien !
Make à la rescousse
Pour pallier ce problème, on utilise un outil très répandu : Make. On peut imaginer Make comme un cuisinier à qui l’on donne un livre de recettes (le makefile). On peut alors lui demander d’exécuter une recette (une “cible” du makefile).
On va lister les commandes à rallonge dans un fichier makefile, et définir des cibles plus courtes et plus simples à mémoriser, comme ceci :
1vendor:
2 docker-compose exec php composer install
Le but de Make est de "fabriquer" des fichiers ou répertoires. Dans mon exemple, lorsque je vais lancer la commande make vendor
, la commande composer install
va être exécutée dans le conteneur php
. Elle va créer le répertoire vendor
et peupler son contenu. vendor
est la cible de l'opération, alors c’est le nom que je donne à la commande.
Et si je relance make vendor
, il ne se passe rien : le dossier existe. Make ne lance pas la commande, je ne perds pas mon temps à attendre une commande inutile.
On peut aller plus loin et lier entre elles les cibles. composer install
se base sur un fichier de verrou de version : composer.lock
. Et ce fichier peut être créé ou mis à jour via composer update
. On peut traduire ceci dans le Makefile de la façon suivante :
1composer.lock:
2 docker-compose exec php composer update
3
4vendor: composer.lock
5 docker-compose exec php composer install
Make nous donne alors un avantage très précieux : lorsque j'utilise make vendor
, Make va vérifier si composer.lock
existe :
- S'il n'existe pas, il va le créer grâce aux commandes définies ;
- Mieux, si le dossier
vendor
est plus récent que le fichiercomposer.lock
, Make va considérer que le dossier est à jour et donc ne rien faire - Et si le fichier
composer.lock
est plus récent, Make va exécuter les commandes définies et mettre à jour mon projet.
En résumé, cela permet de chaîner les opérations requises en listant leur dépendances, et de laisser Make faire le tri : il n'exécute que les dépendances requises.
Make permet d'autres choses très pratiques avec les variables, ne serait-ce que pour conserver un fichier lisible. Il y a également une astuce très pratique pour avoir une commande help
qui liste le contenu du Makefile. C’est technique, c’est pour les développeurs : je n’en parlerai pas plus ici. Pour les curieux, vous pouvez vous référer au travail de François Zaninotto.
Make pour toutes les opérations du projet
On va remplir le Makefile avec les commandes d'installation et mise à jour des dépendances. Mais aussi celles permettant de construire et démarrer nos conteneurs Docker. Une fois le système en place, autant l'utiliser au maximum : les tests y seront également automatisés.
Et au final, même le système d'intégration continue va utiliser le Makefile pour construire et tester le projet. Fini les excuses du genre "mais ce test passait sur ma machine !" car les développeurs et l’intégration continue utilisent le même environnement (Docker) et les mêmes commandes (make).
La mort du README
Nos projets n'ont plus de README (ou presque). En général, ces fichiers contenaient des indications plus ou moins à jour pour installer le projet, ou lancer certaines opérations. Maintenant, tout cela est défini dans le Makefile, qui double son rôle de configuration par un rôle de documentation.
C'est un confort très appréciable pour un développeur comme moi : je sais que pour intervenir sur un projet, je dois le récupérer avec la commande git clone
, puis le construire avec la commande make install
. Le temps que le projet se construise (tout seul !), je peux parcourir le code et la configuration du projet pour me renseigner.
Ça marche, c'est rapide et facilement répétable sur la machine d’un collègue.
Ou bien via un système de cloud pour construire un nouvel environnement de test.
Ou encore en utilisant le système de review apps de Gitlab
C'est un pas en avant sur l'industrialisation de nos projets, et une bonne pratique que je vous invite à explorer.