Salut lecteur,

Oui toi, pas la peine de regarder à gauche ou à droite, il y a absolument personne, je parle bien à toi cher lecteur. Tu as cru que je t'avais oublié, pas vrai ? Du tout, mais comme tu le sais, j'ai eu pas mal de distractions ces derniers temps, j'ai vraiment du mal à trouver du temps pour t'écrire.

En plus, faut vraiment être motivé pour lire un article de plus sur Docker. Oui Docker, au travail j'ai carrément augmenté mon skill à ce sujet, et j'ai grave envie de t'en parler mon lecteur. D'ailleurs si t'as envie de connaitre mon opinion sur ce « truc », Aeris l'exprime parfaitement sur son blog, dans un article purement philosophique comme je les aime.

Au début je m'étais dit exactement comme tout le monde : AWESOME FAUT QUE JE DOCKERISE TOUS MES SERVEURS!!!! et puis je suis redescendu sur terre. Pour donner une proportion, j'en suis à 30% de mes services en container Docker, et ça n'augmentera plus. Pour illustrer pourquoi, je vais juste dire que mes serveurs imap et smtp utilisent les utilisateurs système de l'hôte, et ils doivent rester en correllation avec les utilisateurs SSH. C'est d'ailleurs la définition d'un « système », en fait. Pour le reste, soit.

Et puis vient la question de mettre quoi en container, de cloisonner quoi exactement. Dans un précédent article, je mettais tout en container, vraiment tout, et c'est vraiment pas pratique.

En fait, il m'a fallu un peu de temps avant de maitriser complètement la ligne de commande Docker, je parle de comprendre tous les concepts qu'elle induit, pour au final ne retenir qu'une seule philosophie. C'est le problème typique qu'on rencontre quand un système propose trop de possibilités. Avais-tu remarqué dans l'article précédent que je mettais les données statiques à disposition à travers un container statique ? Que se passerait-t-il si ce container disparaissait ?

Comme la plupart des gens qui font une utilisation intensive du service Docker, j'ai eu à un moment, tôt ou tard, besoin de supprimer /var/lib/docker pour résoudre un gros problème : Le service Docker ne démarrait plus. Inutile de te dire que le container disparait comme par magie, et que j'ai eu très peur pour mes données. Mais, je suis un hébergeur sérieux... j'ai des backups journaliers ^^

Cette mésaventure m'a poussé à repenser mes possiblités avec le service, cette fois-ci en considérant les containers comme non-définitifs. S'ils ne sont plus définitifs, alors ils ne doivent plus démarrer automatiquement au démarrage de l'hôte (virer l'option --restart always), et la "démonification" (option -d) doit être gérée par un gestionnaire de services, prenons Systemd.

Avec systemd, on pourra gérer à la fois le container, mais aussi les inputs passés au processus cloisonné.

L'avantage de passer par un orchestrateur de service est d'enlever au service Docker les rôles qui ne sont pas son point fort, comme le démarrage des containers dans un ordre précis, le redémarrage du container si le processus se termine en erreur (option --restart always), le status du processus cloisonné (le container est signalé comme "Up" alors qu'aucun processus n'est actif), et la gestion des logs du processus directement par Journald.

Pour récupérer la sortie standard et l'erreur, bref le log du processus, il faut que le container ne soit pas spawné avec l'option -d (détacher la tâche) pour ne pas qu'il parte en arrière-plan, c'est Systemd qui gère lui-même la partie « démon ». Du point de vue de Systemd, la commande ne forke pas, et toutes les sorties sont recueillies automatiquement par Journald.

Le second avantage est d'obtenir une meilleure intéraction avec le processus cloisonné. Tous les signaux comme SIGINT, SIGKILL et SIGHUP, envoyés par Systemd au container seront transférés par le service Docker directement au processus principal à l'intérieur du container. L'entrée standard reste ouverte si le container est attaché (à un tty ou à Systemd), mais on peut aussi demander de la laisser ouverte quand le container est détaché avec l'option -i.

Le service Docker ne sert plus d'intermédiaire comme un émulateur de VM qui doit relayer les signaux de fin de tâche à tous ses invités. Je serais tenté de croire qu'il y a un gain de temps non-négligeable dans le temps de réponse des processus, mais je n'ai pas fait de benchmark...

Et puis, les choses intéressantes commencent à arriver, on parle de container non-définitif, on parle de cloisonner seulement le processus, et surtout on parle de terminer le processus, tu as deviné la suite mon lecteur. =)

L'idée c'est de pouvoir perdre les containers, mais pas les données personnelles.

J'ai retourné toutes les pistes à fond, je suis même allé jusqu'au "container-boite-noire" (un trip vraiment obscure), mais aucune d'entre-elles ne répondait correctement à ma problèmatique. Si on perd les containers, il faut maitriser le moment de la perte (il ne faut pas que ce soit à un moment aléatoire). Pire encore, il faut recréer rapidement juste après les nouveaux containers qui doivent remplacer les anciens, et ils doivent être instantannément opérationnels sans avoir besoin de faire un quelconque setup manuel.

Il y a un événement que Systemd maitrise et qui pourrait servir de déclencheur à la destruction de l'ancien container. « Le processus se termine, alors je détruis le container. » Il y a une multitude de scénarios possibles, comme par exemple : « Au démarrage du nouveau processus, je détruis l'ancien container, puis je lance le nouveau » Tu remarqueras cher lecteur que lors du premier démarrage, il y aura des erreurs car aucun ancien container n'existe pas au préalable.

Heureusement, le service Docker gère bien ce point (ouf!), il suffit de passer dans la ligne de commande l'option --rm et lorsque le processus se terminera, son container sera détruit automatiquement. Systemd n'aura qu'à spawner les containers et tuer les processus à l'intérieur. C'est ce qu'on attend d'un gestionnaire de services, en fait.

Et donc, si l'on se dit que les données perso n'ont pas besoin d'être isolées, alors on peut les laisser dans un répertoire quelconque sur le disque dur (avec l'option -v).

Volume de données quelconque, mais pas trop, quand même...

Le service Docker va gérer les modes et ownership des fichiers. Si le processus cloisonné les modifie, alors ils changeront sur le disque dur. Il est question seulement des fichiers à l'intérieur du volume exposé, il ne peut pas altérer les fichiers à l'extérieur. Tu remarqueras cher lecteur, que le processus peut changer lui-même les permissions du répertoire racine (le lien entre le volume sur disque et le volume dans le container). Si tu veux que les autres utilisateurs ne puissent pas regarder dedans, tu peux setter manuellement en root les bonnes permissions sur le répertoire parent.

Dans cet optique de permissions, il vaut mieux regrouper les volumes exposés du container dans un seul répertoire par container, pour pouvoir globalement interdire, ou pas, l'accès aux volumes du container aux utilisateurs système.

Okay on gère les droits linux de base, voyons si on peut pas augmenter le level.

SELinux propose une couche d'abstraction supplémentaire à la gestion des droits linux (jugée imparfaite par la NSA), directement au niveau du noyau Linux... mais je radotte, tu le sais déjà mon vieux lecteur. Le service Docker propose une option intéressante pour les systèmes possèdant SELinux. Oui, toute l'arborescence de répertoires exposée sur disque par le container sera cloisonnée par SELinux. Aucun processus, s'il n'a pas le droit de modifier les fichiers ayant le contexte 'svirt_sandbox_file_t', ne pourra JAMAIS altérer cette arborescence. Je sais pas pour toi, mais moi, perso, ça me fait kiffer. On pourrait aller beaucoup plus loin avec le mode Multi Level Security, malheureusement ce n'est pas le mode courant sur mon serveur.

En ligne de commande ça ressemble à ça :
-v /rep/sur/hote:/rep/dans/container:Z

Cette option exécute automatiquement un change context (commande 'chcon') sur les répertoires du disque. Attention pour que ce soit persistent après un restorecon il faut ajouter la règle avec semanage :
semanage fcontext -a -s system_u -t svirt_sandbox_file_t "/contener/mariadb-casper-im(/.*)?"
Dans tous les cas, il faut user et abuser de la commande restorecon pour bien vérifier ses règles, et je t'invite à lire cet article qui m'a sauvé la vie, d'ailleurs.

Pour terminer en beauté, le service Docker ne supprime pas les volumes exposés sur disque. Que ce soit dans le répertoire par défaut (/var/lib/docker/volumes) ou bien un répertoire assigné avec l'option -v, ce répertoire ne sera pas supprimé si le container est volontairement ou involontairement supprimé.

Voici une unité systemd pour le serveur de base de données de ma messagerie instantannée (Jabber) en guise d'exemple :

[Unit]
Description=Mariadb Server Casper IM
After=docker.service

[Service]
Restart=always
EnvironmentFile=/etc/%p.env
ExecStart=/usr/bin/docker run -i --rm --name %p -p 127.0.0.1:16000:3306 \
          -e MYSQL_USER \
          -e MYSQL_PASSWORD \
          -e MYSQL_DATABASE \
          -e MYSQL_ROOT_PASSWORD \
          -v /contener/%p/var/lib/mysql/data:/var/lib/mysql/data:Z \
          -v /contener/%p/etc/my.cnf.d:/etc/my.cnf.d:Z \
          -v /contener/%p/var/log/mariadb:/var/log/mariadb:Z \
          mariadb:10.1.20-8
ExecReload=/usr/bin/kill -HUP $MAINPID

[Install]
WantedBy=multi-user.target


Le fichier /etc/mariadb-casper-im.env contient les credentials du serveur Mariadb (user mysql, mot de passe, etc...) Il initialise les variables d'environnement MYSQL_USER, MYSQL_PASSWORD, etc... présentes dans l'unité systemd. Il a aussi le mode 400 car il contient des mots de passe. L'image docker est une image custom basée sur celle de Fedora Cloud (le dépôt Git.) Ma seule modification n'est pas vraiment une contribution, j'ai juste ajouté 2 volumes exposés.

VOLUME ["/var/lib/mysql/data", "/etc/my.cnf.d", "/var/log/mariadb"]

Voici quelques conseils de gestion de disque :
  1. Regrouper les volumes exposés dans un répertoire par container
  2. Regrouper les répertoires de container sur une partition montée
  3. Cette partition devrait être un volume LVM de type miroir
  4. Cette configuration ne dispense pas d'avoir des backups journalier (dump de base de données ou autre)
En parlant backup, n'hésitez pas à vous servir de 'docker exec' pour intéragir avec le programme cloisonné, par exemple :

docker exec -i mariadb-casper-blog  mysqldump --opt -u casperlefantom -p$MYSQL_USER_PASSWORD casperlefantom > db-casperlefantom-$(date +%Y%m%d).dump

Magie, le fichier "db-casperlefantom-20161220.dump" est généré à l'extérieur du container. J'ai volontairement utilisé des exemples avec Mariadb car c'est le programme qui m'a donné le plus de fil à retordre, les autres programmes comme apache pour servir des sites statiques étaient bien plus faciles, sans credentials, sans variables d'environnement, etc... Mais toujours avec backup ;)

[Unit]
Description=Apache HTTP Server Vhost onion1
After=docker.service

[Service]
Restart=always
ExecStart=/usr/bin/docker run -i --rm --name %p -p 127.0.0.1:8085:80 \
          -v /contener/%p/var/www/html:/var/www/html:Z \
          -v /contener/%p/etc/httpd/conf.d:/etc/httpd/conf.d:Z \
          -v /contener/%p/etc/pki/tls/private:/etc/pki/tls/private:Z \
          -v /contener/%p/etc/pki/tls/certs:/etc/pki/tls/certs:Z \
          -v /contener/%p/var/log/httpd:/var/log/httpd:Z \
          apache-single:2.4.23-2
ExecReload=/usr/bin/docker exec %p /usr/sbin/httpd -k graceful

[Install]
WantedBy=multi-user.target
Pour ajouter de l'orchestration entre les services, on peut jouer avec les options After et BindTo dans la partie [Service] des unités systemd. Indispensable lorsque l'on utilise du linkage entre 2 containers...