Point de départ

Mon serveur à ma maison (qui sert notamment à héberger ces lignes) commence à se faire vieux et surtout à être relativement surchargé :

  • Messagerie avec Postfix/Dovecot/Spamassassin/OpenDKIM
  • Messagerie instantannée avec Prosody (même s'il n'y a plus grand-monde dessus, il faut bien le reconnaître…)
  • Listes de diffusion avec Mailman
  • Plein d'applis PHP/MariaDB pour ma tronche (PrivateBin, TTRSS, Roundcube, Z-Push, NextCloud, etc…)
  • Des applis PHP/MariaDB pour le Dojo (notamment un Wordpress et un forum phpBB)
  • Une appli PHP/MariaDB pour Pipoworld
  • Un serveur de conversation Teamspeak
  • Un serveur de conversation Mumble (murmur pour les intimes)
  • Un serveur de diffusion audio Icecast2 pour le dojobar
  • Un transmission et un Sonarr
  • Une instance Mastodon dockérisé

Ça fait donc beaucoup beaucoup de bordel au même endroit, dans la même petite Debian.

Dans l'absolu, cette approche ne me pose pas tellement de souci : tous les logiciels sont installés proprement (NginX, PHP-FPM, etc…) et maintenus correctement par le projet Debian ou au pire par un dépôt tiers. Je n'ai pas vraiment de mouton à 3 têtes (sauf peut-être Mastodon, c'est bien pour ça qu'il est conteneurisé).

Au quotidien, le principal problème, c'est surtout que j'ai peur de tout casser : je fais les mises à jour régulières de sécurité sans trop me poser de question mais au moment de passer de Jessie à Stretch, je me suis fait quelques belles frayeurs. phpBB notamment ne supportait pas vraiment bien le passage de PHP 5 à PHP 7.

Fort heureusement, étant très prudent de base, c'est pratiquement le seul effet de bord que j'ai eu.

Mon second problème avec ce setup, c'est que je ne peux que difficilement déléguer : je peux donner des accès distants en SSH, je peux toujours faire des trucs avec du sudoers, mais ça devient assez vite limité s'il faut déléguer des droits plus fins ou plus permissifs.

L'objectif est donc de changer le fusil d'épaule et de passer à quelque chose de plus modulaire. Pour déléguer, mais aussi pour séparer un peu les choses.

Comparaison des différentes solutions possibles

Je suis donc reparti sur le tableau blanc pour essayer d'imaginer les différentes alternatives et solutions qui pourraient s'offrir à moi.

Touche pas, ça marche

La première consistait à reprendre tout tel quel. Ça ne simplifie pas forcément la migration (sic!) et en plus, je me retrouve avec le même bordel à l'arrivée. Garbage-in, garbage-out, j'ai éliminé très rapidement cette solution.

VM partout, justice nulle part

En poussant un peu le curseur, je me suis dit que je pourrais aussi virtualiser tout : une machine virtuelle pour chaque grande famille d'applications, avec une adresse IPv6 publique. Bien entendu, il faudrait forcément rajouter des petites choses pour que tout se passe bien :

  • un load-balancer (type HAProxy) pour la partie HTTP/HTTPS en IPv4. Je pensais à une solution qui ferait du load-balancing sur la partie HTTPS en utilisant l'entête SNI et en ouvrant ensuite la connexion vers le bon serveur en TCP. C'est con, mais j'ai testé, ça fonctionne correctement. Ça permettrait aussi de gérer proprement la partie gestion des certificats sur chaque VM.
  • il faudra obligatoirement un orchestrateur pour gérer tout ça : SaltStack, Ansible ou approchant. Il va forcément y avoir beaucoup d'éléments de configuration qui vont être communs entre tous ces services.

Évidemment, le démarrage risque d'être le plus douloureux : il faut créer un socle de configuration commun à toutes les VMs (SSH, NTP, configuration générale de zsh, etc…) qui peut prendre pas mal de temps. Une fois le socle en place néanmoins, déployer une nouvelle VM devrait prendre un temps minimal.

A priori, ça peut être une bonne solution, mais il y a tout de même quelques réserves à émettre :

  • le fait de gérer des VMs est de facto plus lourd que le fait de gérer des services. Par contre, ça peut permettre de séparer correctement les services, les utilisateurs, les administrateurs, etc…
  • les ressources utilisées sont beaucoup plus importantes que sur une machine seule. En plus, il y a peu de mutualisation réelle de ressources possibles : 1 base MariaDB bien optimisée vaut-il mieux que 4 bases MariaDB pas du tout optimisées ?
  • la complexité globale de la configuration risque d'être d'autant plus importante

Pas une mauvaise solution donc, mais il faut quand même rajouter quelques composants pour arriver à s'en sortir. En terme de ressources, ça n'est pas forcément à la portée de tout le monde non plus (faut une machine costaude).

Conteneur LXD/LXC

LXC est une solution de conteneur permettant d'installer des « vrais » systèmes. Contrairement aux conteneurs Docker, l'objectif est ici d'avoir une gestion très proche des machines virtuelles, tout en ayant une empreinte mémoire bien plus légère qu'une machine virtuelle. On parle alors de VE (Virtual Environment) plutôt que de VM (Virtual Machine).

On peut donc y retrouver les mêmes principes de fonctionnement et avantages que pour la solution précédente, mais l'inconvénient de la ressource en moins. Cela oblige également à déployer les mêmes outils complémentaires dans la plupart des cas (orchestrateur, etc…).

LXD est l'évolution logique de LXC : plus simple, plus propre, plus facile, plus rigolo. Si on doit choisir entre les deux aujourd'hui, je pense qu'il vau mieux privilégier LXD. Le seul incovénient pour le moment, c'est que LXD ne tourne correctement que sous Ubunutu. L'intégrer correctement dans d'autres distributions est prévu mais pas encore fait pour le moment.

Un inconvénient majeur à retenir tout de même : si on peut faire à peu près n'importe quoi en terme d'image, c'est limité à Linux. Impossible d'installer un FreeBSD, un OpenBSD ou même (horreur !) un Windows. Par contre, on a accès à une large palette de Linux, d'Alpine à CentOS, en passant par Ubuntu, Archlinux et d'autres.

Il est même possible de faire du nesting pour faire du conteneur dans du conteneur (donc Docker dans LXC par exemple).

C'est pour le moment la solution qui me semble la plus adaptée à mon besoin. Mais ça m'empêche pas de regarder ailleurs pour vérifier s'il y a pas plus marrant à faire.

Conteneur Docker

Pour séparer correctement des applications, pourquoi ne pas utiliser Docker ? Après tout, c'est fait pour. Des dizaines d'image de conteneurs disponibles sur le hub directement faite par des gens kissiconèsse© et prêts à l'emploi.

Le souci, c'est que c'est compliqué de faire du Docker « seul ». Il faut obligatoirement l'outiller un peu aussi pour arriver à faire quelques chose. Pour déployer un ou deux conteneurs un peu à l'arrache, ça marche très bien, mais dès qu'il s'agit de gérer toute une batterie de conteneurs, il faut obligatoirement outiller l'ensemble avec un orchestrateur de conteneurs par exemple.

Il y a plusieurs possibilités pour ça :

  • docker-compose permet de « scripter » un ensemble de conteneurs dans un fichier YAML. Il suffit donc de décrire l'ensemble des interactions entre des conteneurs et hop, le tour est joué
  • Docker Swarm qui est un orchestrateur de conteneurs qui permet d'aller déployer les mêmes fichiers YAML mais à travers plusieurs machines (au moins 3 de ce que j'ai compris).

Du coup, pour rigoler, j'ai monté une petite maquette en Docker Swarm pour voir si ça pourrait correspondre au besoin :

  • 3 machines ça fait beaucoup, mais au moins la redondance de l'ensemble est assurée.
  • Docker prend en compte la redondance des conteneurs eux-mêmes (si l'on déploie un conteneur NginX, il sera téléchargé sur chaque nœud du cluster Swarm), mais pas la redondance des volumes de données. Il faut donc une solution complémentaire pour cela (GlusterFS, NAS, etc…)
  • c'est nettement plus simple de déployer un conteneur qu'une VM et le conteneur a déjà une fonction spécialisée (nginx, php-fpm, mariadb, etc…)
  • on déploie un peu la version de conteneur que l'on veut, sans trop se précoccuper de ce qu'il y a dedans. Envie d'un PHP5 ou lieu d'une PHP7 ? Rien de plus facile, il suffit de changer le tag du conteneur.
  • on a forcément la dernière version stable ou même la dernière version tout court de n'importe quel logiciel. Les logiciels que l'on utilise n'ayant pas toujours les mêmes cycles de développement, cela peut se révéler très très utile dans certains cas.

Maintenant, Docker (et Docker Swarm) apporte aussi son lot de problème, que je vais essayer de résumer ci-après.

Le fontionnement de Swarm lui-même oblige à avoir des ports « publiable ». Je m'explique : vous faites tourner votre conteneur NginX et vous voulez publier le port 80. Swarm permet de le faire et à ce moment, le port 80 est accessible (en IPv4 et en IPv6) sur l'ensemble des nœuds du cluster Swarm. C'est cool mais ça veut aussi dire que pour avoir un second conteneur NginX, il faut obligatoirement un autre port. Il faut également prévoir un load-balancer pour balancer les requêtes entre les différents nœuds du cluster du coup : on ne peut joindre qu'un seul port 80 sur l'ensemble du cluster, mais si on redirige le port 80 vers un seul nœud et que ce dernier tombe, c'en est fini…

Une autre solution pourrait consister à utiliser Keepalived en frontal pour avoir une adresse VRRP partagée entre les nœuds du Swarm.

La logique de Docker veut qu'on fasse tourner un seul processus dans un conteneur. Sauf qu'avec un seul processus MariaDB, je peux faire tourner 10 bases de données différentes pour 10 applications différentes. Que faut-il que je fasse dans ce cas précis ? Rien n'est moins évident…

Dans le même ordre d'idée, un conteneur de type Sonarr, NextCloud ou Gitlab fait tourner bien plus qu'un seul processus et pourtant, ça ne choque personne. La documentation de s6-overlay précise que normalement faudrait avoir qu'un seul process par conteneur, mais qu'en fait on s'en bat les couilles. Et les mecs font des conteneurs super populaires, la logique, on repassera donc…

Il y a aussi quelques points de la mise en œuvre de Docker, spécifiquement pour les applications PHP, qui me semblent relativement discutables : hors de question de faire du Apache (faut pas déconner non plus !), il faut donc nécessaire un conteneur NginX et un conteneur PHP-FPM lié pour faire tourner une appli. Cela implique qu'il faut un volume partagé entre les deux. Jusque là, rien de choquant…

Mais faut-il inclure les données non variables de l'application PHP (toute la partie « programme ») dans le conteneur également ? Dans un NextCloud par exemple, le dossier config/ et data/ sont les seuls à bouger en théorie. Pourquoi ne pas inclure tout le reste ?

La gestion de la configuration reste aussi un peu plus complexe : on peut faire quelques templates de configuration dans des volumes accessibles en lecture seule à plusieurs conteneurs, mais il y a souvent beaucoup de choses à inclure pour faire fonctionner certains composants. Typiquement, pour NginX : il faut un nginx.conf correctement configuré, il faut y adjoindre quelques autres fichiers de configuration (mime.types, fastcgi_params, etc…), le fichier de conf de l'appli elle-même, ainsi que le certificat et la clé privée.

Autant avec Salt, il suffit de déployer ces fichiers de conf correctement modélisés sur l'ensemble des nœuds ayant besoin de cette configuration. En  Docker Swarm, on peut certes faire la même chose, mais modifier la configuration de certains services à la volée est complexe.

Dans le même ordre d'idée, faire tourner certains types d'applications qui se basent sur de la communication inter-process ou sur le fait de lancer une commande d'une autre programme, peut rapidement tourner au cauchemar. Postfix lance la commande deliver de Dovecot pour livrer les mails : on fait comment quand tout est dans un conteneur ? Il faut forcément repenser les choses autrement.

Mon dilemme est donc à présent le suivant : il est certain que j'aurais du Docker d'une manière ou d'une autre (ne serait-ce que pour reprendre mon instance Mastodon). Tout mettre dans du Docker me permettrait éventuellement de me simplifier considérablement la vie dans certains cas, mais ça ne résout pas tout et surtout pas les soucis de délégation… Que faire donc ?

Du coup, on fait quoi ?

Et bien on maquette mes loulous, on maquette.

Monter une maquette LXD va être très simple. Il y a beaucoup de choses à tester, mais ça permet de se mettre rapidement le pied à l'étrier.

Monter une maquette Swarm n'est pas beaucoup plus compliqué et cela permet au moins de vérifier comment on pourrait faire fonctionner le tout.

La suite au prochain numéro !