La semaine dernière, j'ai publié un billet sur la mise en place d'un cluster WildFly avec Docker. Une des premières réactions est venue de Cédric Exbrayat pour me donner quelques conseils pour réduire la taille de mes images. J'ai donc testé les pistes de cet article avec mes deux images : sewatech/modcluster et sewatech/wildfly.

Les pistes qui sont proposées sont :
  • le chaînage des commandes, pour réduire le nombre de couches,
  • l'import + export, qui réduit encore plus les couches.
Enfin, j'ai testé l'utilisation de plusieurs images de base, dont BusyBox, comme me l'a suggéré Damien Duportal.


Taille des images

J'ai construit mes deux images avec des Dockerfiles. Chaque Dockerfile est une séquence de commandes ADD, COPY, RUN,... Le résultat est une image Docker constituée d'une image de base et de couches (layers) successives, chaque commande générant une nouvelle couche. La taille d'une image étant la somme de ses couches, chaque couche supplémentaire fait grossir le résultat final. En revanche, si deux images utilisent la même image de base, celle-ci n'est présente qu'une seule fois.

Les versions initiales de mes images occupent respectivement 401 Mo (sewatech/modcluster) et 794 Mo (sewatech/wildfly), pour une taille totale de 1105 Mo ; l'image debian:jessie partagée par les deux images fait 90 Mo.

Chaînage des commandes

Commençons par le chaînage des commandes. Plutôt que de faire une succession de commandes RUN, on regroupe toutes les commandes shell dans un seul RUN pour réduire le nombre de couches intermédiaires.

J'ai commencé à tester pour sewatech/modcluster. Avec le script initial, l'image faisait 401 Mo, en chaînant les commandes, on est passé à 395 Mo. C'est mieux, mais ce n'est pas vraiment impressionnant.

J'ai fait la même opération pour sewatech/wildfly, qui a fait passer la taille de 794 Mo à 794 Mo. Bof. L'effort n'en vaut vraiment pas la peine. Si en plus, ça nuit à la lisibilité du Dockerfile, c'est même contre-productif. En regardant le détail de l'image, avec docker history, montre que le téléchargement de WildFly par la commande ADD occupe une place de 120 Mo dans sa propre couche. En remplaçant ADD par RUN + curl, et en le chaînant aux autres commandes on gagne une place importante, faisant passer l'image à 680 Mo. Ah, là ça commence à être intéressant.

En optimisant le chainage, on arrive à une taille totale de 971 Mo.

Choisir l'image de base

En démarrant, j'avais choisi de partir d'une image Debian parce que c'est un système que je connais un peu et que l'image est relativement petite (90 Mo). C'est nettement moins que Ubuntu (225 Mo), Fedora (374 Mo) ou CentOS (244 Mo), mais plus que BusyBox (2,5 Mo) ou BusyBox:ubuntu (5,5 Mo).

Par curiosité, j'ai créé la même image en partant d'Ubuntu:14.04. Le résultat est effectivement plus gros (766 Mo au lieu de 680 Mo), mais la différence est moins importante que la différence entre les images de base.

Pour ce qui est de Busybox, la tâche me semble un peu plus ardue.

Aplatir l'image

Lorsqu'on a construit une image à partir d'un Dockerfile, elle est constituée de couches qui conservent dans les métadonnées la façon dont elles ont été produite. On peut aussi créer une image en exportant un conteneur dans un fichier tar puis en réimportant ce fichier. De cette manière, l'image n'est constituée que d'une seule couche. On arrive à un résultat similaire à la construction directe d'une image de base, suggérée par Haïkel Guémar.

Si on applique ça à l'image sewatech/wildfly initiale, qui faisait 794 Mo, on obtient l'image aplatie de 674 Mo.

docker export $(docker run -d sewatech/wildfly) | docker import - sewatech/wildfly:flat

Pour sewatech/modcluster, on passe de 401 Mo à 393 Mo.

Le gain de cette technique est relativement faible par rapport à un bon chaînage et fait perdre le partage des images de base. Avec les images plates, on arrive à un total de 1067 Mo, ce qui est plus que pour le chaînage. Et j'y vois un autre défaut : cette technique ne fonctionne pas avec les builds automatiques de Docker Hub, basés sur un Dockerfile,

Spécialisation des images

Concernant sewatech/modcluster, il est difficile de faire ce ménage. En effet, pendant la construction de l'image, on a besoin de package de développement pour compiler les modules. Or à l'exécution, tous ces packages sont inutiles. La meilleure façon de réduire la taille de l'image est de construire une image préliminaire pour la compilation et de transférer les fichiers binaires vers une autre image. En procédant ainsi, on obtient une image finale de 162 Mo. WIN !

Conclusion

Ma préférence va au chaînage des commandes, avec quelques trucs à respecter :
  • ne pas utiliser ADD pour télécharger un fichier temporaire,
  • faire le ménage en fin de chaîne : retirer des répertoires et packages temporaires.
Pour le choix de l'image de base, je reste sur Debian qui est à mon goût le bon compromis entre taille et facilité d'utilisation, ou Ubuntu pour des packages qui ne sont pas encore présents sur Debian. Et en utilisant une petite gamme d'images de base, on optimise les possibilités de partage.

Remarque : les tailles indiquées dans ce billet étaient valables le jour des tests, le 18 août, et on changé depuis à cause d'une mise à jour des packages Debian.