Test d’un role Ansible

Après avoir réorganisé l’ensemble de mes rôles Ansible, j’avais en tête de trouver un moyen de tester leur exécution dans un environnement vierge. L’idée n’est pas nouvelle. Le but : vérifier l’exécution du rôle plus facilement, c’est-à-dire, sans savoir à installer une nouvelle VM, ou à louer un serveur à cette occasion, même si cette dernière solution est, par expérience : pratique, assez rapide à mettre en œuvre et d’un faible coût en utilisant un serveur facturé à l’heure.

Ayant parcouru quelques dépôts de rôle Ansible présent sur Ansible Galaxy et ayant sauvegardé quelques articles évoquant le sujet, j’avais donc une idée des outils disponibles et des exemples fonctionnels. Il ne restait plus qu’à se retrousser les manches, et à tenter l’implémentation sur l’un de mes rôles les plus simples consistant à installer fail2ban.

Installation

Première étape avant configuration, l’installation des outils qui permettront de tester le rôle en exécutant une seule commande, à savoir : Molecule et virtualenv. Avec python3 installé, je commence donc par installer virtualenv via: pip install virtualenv. J’initialise ensuite un environnement virtuel python avec virtualenv dans le dossier contenant mon rôle ansible, soit les commandes :

$ pip3 install virtualenv
$ virtualenv -p python3 .venv
$ source .venv/bin/activate

Étape suivante, l’installation de molecule via pip.

$ pip3 install --upgrade setuptools
$ pip3 install 'molecule[ansible,docker,lint]'

Configuration basique

Maintenant que les outils sont installés, je génère la configuration de base avec la commande: molecule init scenario. Cette commande a pour effet de créer quatre fichiers.

  • INSTALL.rst : contient des instructions pour l’installation d’autres dépendances, ou des étapes de configuration à réaliser. Dans mon cas, installation de molecule[docker], que j’ai donc directement intégré dans la commande citée précédemment.
  • molecule.yml : fichier principal décrivant les outils utilisés pour le test.
  • converge.yml : le playbook ansible chargé de déployer le rôle testé.
  • verify.yml : un fichier ansible permettant de décrire les vérifications à effectuer après déploiement du rôle.

Comme j’utilise docker pour la gestion de l’instance de test, je suis les préconisations de la documentation pour vérifier que docker fonctionne correctement.

$ docker run hello-world

Une fois l’environnement prêt, je peux passer à l’exécution du test.

$ molecule test

En théorie, ce premier test devrait au minimum réussir jusqu’à l’étape de vérification, en fonction des modifications effectuées dans verify.yml. En cas d’erreur, je trouve très utile d’exécuter manuellement les différentes étapes de molecule test. A savoir:

  1. molecule create : Création de l’instance.
  2. molecule converge: Déploiement du rôle.
  3. molecule login : Connexion à l’instance pour aller explorer son état, très pratique en cas d’erreur.
  4. molecule verify : Exécuter les vérifications.
  5. molecule destroy : Nettoyage.

Il convient de noter que, si le rôle ansible utilise service pour vérifier l’état d’un service, la configuration par défaut présente dans le fichier converge.yml n’est pas suffisante. J’ai été bloqué un bon moment avant de trouver des précisions à ce sujet au détour d’une issue sur Github. C’est pourquoi je vais maintenant décrire la configuration que j’ai mise en place.

Cas concret

La configuration que je décrite à la suite permet donc de tester un rôle relativement simple, que j’utilise pour installer fail2ban à partir de l’un des paquets deb du projet. Paquet distribué dans la partie release du projet GitHub. Commençons par le fichier molecule.yml.

---
dependency:
  name: galaxy
driver:
  name: docker
platforms:
  - name: instance
    image: geerlingguy/docker-debian10-ansible:latest
    command: ${MOLECULE_DOCKER_COMMAND:-""}
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:ro
    privileged: true
    pre_build_image: true
provisioner:
  name: ansible
  playbooks:
    prepare: prepare.yml
verifier:
  name: ansible

Cette configuration présente quelques adaptations par rapport à la configuration par défaut. En particulier, du côté de platforms, où les instructions command et volumes sont nécessaires pour que l’instruction service dans ansible soit fonctionnelle. Notons aussi l’ajout de prepare dans provisioner. Cet ajout a pour but d’effectuer quelques actions supplémentaires sur l’instance juste après son installation et avant de commencer le déploiement du rôle. Le contenu du fichier prepare.yml est le suivant :

---
- name: Prepare
  hosts: all
  become: true
  tasks:
    - name: Install ssh
      apt:
        name: [ 'ssh' ]
        state: present
        update_cache: yes
        cache_valid_time: 3600
    - name: restart ssh
      service:
        name: ssh
        state: restarted

Après quelques tentatives de tests et d’erreur au démarrage du service fail2ban, dernière étape de mon rôle, je constate que ssh ne semble pas installé par défaut dans l’image docker choisie. Problème, fail2ban sous debian active par défaut une jail pour ssh et se plaint donc de ne pas trouver les fichiers de logs pour ssh. Ces quelques lignes ont donc pour but d’installer ssh et de démarrer le service associé. On pourrait discuter du fait que ces vérifications devraient être portées par le rôle, mais cela ne me semble pas pertinent, étant donné que mon utilisation du rôle se fait toujours dans le cas d’une installation sur une machine à laquelle j’accède via ssh. Le service est donc toujours présent dans ma situation.

Vient ensuite le fichier converge.yml. Pas de modification de ce côté là.

---
- name: Converge
  hosts: all
  become: true
  tasks:
    - name: "Include role-install-fail2ban"
      include_role:
        name: "role-install-fail2ban"

Enfin verify.yml dernier fichier de configuration. Pour un premier test, j’ai fait au plus simple et je me contente de vérifier que fail2ban fait bien partie des paquets installés sur le système.

---
- name: Verify
hosts: all
tasks:
- name: Gather package facts
package_facts:
manager: auto

- name: Verify Packages
assert:
that: "'{{ item }}' in ansible_facts.packages"
with_items:
- fail2ban

Voilà pour la configuration du test de mon rôle d’installation de fail2ban.

Conclusion

Après une première soirée passée sur le sujet du test d’un rôle ansible, je suis plutôt satisfait du résultat. Bien que le rôle soit l’un de mes rôles les plus simples, cela ne devrait pas être trop compliqué de généraliser cette méthode à l’ensemble de mes rôles, mais cela nécessitera sans aucun doute de nombreuses heures de développement. Comme souvent, c’est un processus itératif qui sera effectué progressivement sur les prochains mois. La simplicité de réaliser un test sur un système vierge est vraiment appréciable et permettra de vérifier qu’un rôle modifié fonctionne toujours, et cela, sans avoir à passer par le déploiement d’un serveur vierge. Prochaines étapes : étudier l’intégration avec le système d’actions GitHub et tester le playbook permettant le déploiement automatique d’Unicoda.

Sources

Synapse: Procédure de mise à jour

J’effectue quelques essais autour de l’hébergement d’un serveur synapse pour le système de messagerie matrix. Petit pense-bête pour retrouver facilement la procédure de mise à jour du serveur synapse, sans avoir à la (re)chercher à chaque fois dans la documentation.

$ sudo systemctl stop matrix-synapse.service
$ sudo su synapse
$ cd ~/synapse
$ source env/bin/activate
$ pip install --upgrade matrix-synapse
$ pip install --upgrade pip
$ exit
$ sudo systemctl start matrix-synapse.service

exim4

J’ai effectué courant décembre 2020, une migration de mes services auto-hébergés vers une nouvelle machine. Ayant réorganisé en 2019 l’ensemble de mes roles Ansible, et passant d’une base Debian 9 à Debian 10, de nombreuses corrections ont une nouvelle fois été nécessaires avant que l’ensemble soit à nouveau déployé correctement de manière automatique. La majorité des adaptations provenaient du passage à php7.4, avec un test de la version 8 (encore trop récente pour plusieurs services). Le reste concernait l’envoi de mail depuis le serveur avec exim4.

En commençant l’aventure de l’auto-hébergement, la question de l’envoi des mails ne s’était pas posée, car, dans mes souvenirs en tout cas, WordPress arrivait à me notifier par mail sans avoir de modification à effectuer au niveau du système Debian fournit avec le serveur Kimsufi que j’utilisais à l’époque. En migrant par la suite sur un VPS et en auto-hébergeant certains services à mon domicile, l’envoi de mail avait cessé de fonctionner et j’avais choisi d’ignorer la question jusqu’à la mie 2019, où j’avais réussi à produire une configuration fonctionnelle, en utilisant les serveurs de mail de l’hébergeur. Une nouvelle migration d’Unicoda courant 2020 avait à nouveau rendu l’envoi de mail inopérant. Idem avec la récente migration de ma machine locale.

Afin de continuer de recevoir les notifications liées à l’exécution des sauvegardes journalières de l’ensemble des services, et de bénéficier à nouveau des notifications WordPress, comme celles des nouveaux commentaires, je me suis donc replonger dans les profondeurs de la configuration des services mails sous GNU/Linux, afin de corriger le rôle Ansible responsable de la configuration automatique des services mails.

Voici donc à la suite, le résultat de mes notes de 2019, adaptées et corrigées après les recherches de décembre l’an dernier. J’espère ne pas avoir oublié d’étapes, mais les essais successifs de paramètres différents conduisent parfois à des incohérences, lorsque les notes ne sont pas mises à jour avec les dernières modifications effectuées à l’issue d’une session éreintante de débogage :). Par ailleurs, si mon rôle Ansible a bien été mis à jour, il me reste néanmoins à le tester une nouvelle fois, sur un système vierge, dans l’idéal, afin de garantir son bon fonctionnement. Cela étant dit, voici la configuration qui permet à mes serveurs d’envoyer des mails.

Configuration

Commençons par installer les composants nécessaires:

sudo aptitude install exim4 openssl ca-certificates mailutils

Je modifie ensuite /etc/email-addresses pour lier utilisateurs locaux et adresses mails. Ici, le but est de renvoyer tout vers l’adresse example@unicoda.com (adresse fictive pour l’exemple):

root : example@unicoda.com
monUtilisateur : example@unicoda.com
* : example@unicoda.com

Manipulation similaire, cette fois dans /etc/aliases , puis j’exécute la commande newaliases :

root: monUtilisateur
monUtilisateur : example@unicoda.com

Je modifie ensuite le fichier /etc/exim4/passwd.client, pour y ajouter une ligne contenant les informations de connexion au serveur mail choisi pour l’envoi, de la forme target.mail.server.example:login:password , et lui applique un masque 640 pour les autorisations d’accès.

Avant d’aller plus loin dans la configuration, je m’assure également que mon fichier /etc/hosts contient une ligne faisant pointer le hostname de la machine vers elle-même, soit la ligne 127.0.0.1 hostname. Par ailleurs, je modifie également /etc/mailname pour y ajouter le hostname, cette modification m’ayant été particulièrement utile pour le cas d’un message à destination d’un utilisateur de la machine, soit par exemple root@hostname.

Étape suivante, déploiement du fichier /etc/exim4/exim4.conf.localmacros avec le contenu suivant, pour l’utilisation de TLS et du port 465:

MAIN_TLS_ENABLE = 1
REMOTE_SMTP_SMARTHOST_HOSTS_REQUIRE_TLS = *
TLS_ON_CONNECT_PORTS = 465
REQUIRE_PROTOCOL = smtps
IGNORE_SMTP_LINE_LENGTH_LIMIT = true

Avant de continuer plus avant, il faut créer un certificat TLS pour exim4, on peut se baser pour cela sur l’étape 4 de ce tutoriel, qui donne la commande à exécuter. De mon côté, j’utilise les modules openssl_privatekey, openssl_csr et openssl_certificate dans ansible pour réaliser ces opérations.

J’apporte quelques modifications au fichier /etc/exim4/update-exim4.conf.conf, correspondant aux étapes 9 et 10 du tutoriel cité ci-dessus, à savoir, l’ajout du bloc suivant, juste avant la ligne .ifdef REMOTE_SMTP_HEADERS_REWRITE (étape 9):

.ifdef REQUIRE_PROTOCOL
  protocol = REQUIRE_PROTOCOL
.endif

Ainsi que le bloc suivant, juste après la ligne (étape 10):

.ifdef TLS_ON_CONNECT_PORTS
  tls_on_connect_ports = TLS_ON_CONNECT_PORTS
.endif

Enfin, édition du fichier /etc/exim4/update-exim4.conf.conf pour spécifier le comportement d’exim4, qui peut également être généré dynamiquement via sudo dpkg-reconfigure exim4-config. À noter, que le mode satellite ne prend pas en considération l’envoi local, et ne tient donc pas compte des alias configurés (comme précisé dans ce message). Pour être complet, je lui ai donc préféré le mode smarthost. J’ai pour ma part changé le dc_readhost par rapport au tutoriel dont je me suis inspiré, qui proposait localhost comme valeur. Je crois me souvenir que localhost ne fonctionnait pas dans mon cas, mais une nouvelle série de tests avec cette configuration ne serait pas inutile pour être fixé.

dc_eximconfig_configtype='smarthost'
dc_other_hostnames=''
dc_local_interfaces='127.0.0.1 ; ::1'
dc_readhost='unicoda.com'
dc_relay_domains=''
dc_minimaldns='false'
dc_relay_nets=''
dc_smarthost='target.mail.server.example::465'
CFILEMODE='644'
dc_use_split_config='false'
dc_hide_mailname='true'
dc_mailname_in_oh='true'
dc_localdelivery='mail_spool'

Nous y sommes presque, il suffit à présent de déployer la configuration exim4 via sudo update-exim4.conf, puis de redémarrer le service avec la commande sudo service exim4 restart. On peut ensuite essayer d’envoyer un mail depuis la machine avec echo "test" | mail -s "Test" example@unicoda.com. Si tout va bien, le mail arrive correctement à l’adresse fournie, sinon, il vous faudra, comme moi, parcourir les logs (sudo tail /var/log/exim4/mainlog) pour comprendre la nature du problème de configuration et parcourir les nombreux sujets et documentation autour d’exim4.

Pour finir, avant une liste d’article qui m’avait été utile lors de la configuration, voici encore quelques commandes utiles pour déboguer une configuration exim4 non fonctionnelle.

Commandes

Consulter la liste des messages gelés.

exim4 -bp

Supprimer tous les messages gelés.

exiqgrep -z -i | xargs exim -Mrm

Sources

Unicoda a brûlé !

Amis lecteurs bonsoir !

Étrange journée que cette journée du 10 mars 2021, car, s’il y a bien un risque qu’on envisage pas immédiatement pour son serveur, lorsqu’on fait la liste des périls qui le guettent, c’est celui de l’incendie. Eh oui, en ce mercredi 10 mars 2021, Unicoda, où plutôt son serveur de résidence, est parti en fumée avec le reste des serveurs et des sites hébergés dans le datacenter SBG2 d’OVH à Strasbourg.

Pour Unicoda, plus de peur que de mal, puisque si vous lisez ces lignes, c’est que nous sommes (déjà) de retour. C’est bien sûr un événement dont tout le monde se passerait. J’ai pu voir quelques annonces de données irrémédiablement perdues, par exemple, du côté des serveurs du jeu Rust. Un événement qui démontre encore une fois, l’importance des sauvegardes, et que, même en environnement contrôlé, personne n’est à l’abri.

Voici donc un petit retour pour décrire la façon dont nous avons réagi à cet incident.

Tout commence ce matin vers 10h, lorsque mon frère Mathieu m’appelle au téléphone, en me demandant où est hébergé Unicoda dans les infrastructures OVH. Il continue en m’annonçant qu’il vient de lire qu’un incendie s’est déclaré dans le datacenter SBG2, que tout SBG est à l’arrêt et que Unicoda ne répond plus. Je lui confirme qu’Unicoda est bien hébergé à Strasbourg dans SBG2 et vérifie rapidement la connexion au serveur (en échec bien sûr). Vérification dans mes mails, pas de rapport journalier de sauvegarde ce matin, le feu s’étant déclaré peu avant le processus de sauvegarde automatique.

Je prends ensuite un peu de temps pour réfléchir. Je suis à 80% sûr qu’Unicoda était hébergé dans SBG2, rien dans la console d’administration d’OVH ne permet de valider l’information, rien sur les factures. Le rapport de sauvegarde du 9 mars indique qu’il s’est terminé avec succès. Enfin, SBG est à l’arrêt pour plusieurs jours, voir semaine, il faut donc préparer le redéploiement d’Unicoda sur un nouveau serveur. Profitant d’une pause dans ma matinée de travail, je commande donc un nouveau VPS OVH dans le datacenter de Gravelines.

Arrive le temps de la pause méridienne. Le VPS n’étant toujours pas disponible, je commande un VPS chez Hetzner en Allemagne, VPS facturé à l’heure avec un plafond mensuel, extrêmement pratique pour effectuer des tests de courtes durées. Livraison en moins d’une minute. J’ouvre le dépôt contenant mon script de déploiement automatisé ansible, change les DNS pour pointer vers la nouvelle machine et démarre l’exécution. Quelques erreurs du fait d’une réorganisation récente de l’ensemble de mes rôles ansible (et dont les tests devaient avoir lieu dans les prochains mois) interrompront deux fois la réinstallation. Au troisième essai, après quelques dizaines de minutes, Unicoda est à nouveau accessible.

Quels enseignements retenir de cet incident ?

Comme énoncé déjà ici: sauvegarder, sauvegarder et sauvegarder !! Je suis une nouvelle fois très satisfait par la solution de sauvegarde et de déploiement automatique que j’ai mise en place pour Unicoda et qui est le résultat de plusieurs années d’améliorations. Néanmoins, il serait bon d’ajouter un deuxième emplacement distant de stockage des sauvegardes. En effet, si par un hasard extraordinaire, un problème similaire était arrivé au datacenter stockant la sauvegarde chiffrée, c’eût été retour à la sauvegarde enregistrée sur mon poste local et pouvant parfois remonter à plusieurs semaines. Un emplacement distant pour les sauvegardes, c’est bien, deux, c’est mieux.

Deuxième point à noter, en cas de réorganisation complexe des scripts de déploiement automatique, il aurait été utile de conserver une copie du dépôt avant réorganisation, lorsque les scripts fonctionnaient sans erreurs et avait été testé. Ainsi, on s’évite quelques corrections en état de stress et pressé par le temps. Cela est bien entendu variable en fonction du service affecté, dans le cas d’Unicoda, je ne suis pas à la minute près, bien que j’apprécie d’avoir le moins de temps d’indisponibilité possible.

Enfin, j’ai apporté des modifications sur le Time To Live (TLL) du champ A des DNS d’Unicoda, entrée DNS responsable de la redirection vers le serveur. J’avais jusqu’à présent laissé ce champ à la valeur par défaut dans l’interface à savoir 86400 secondes soit 1 jour. Si cela ne pose guère de problèmes dans le cas d’une migration programmée, où l’ancien serveur continue de fonctionner quelques jours, c’est plus embêtant dans le cas d’un incident comme celui d’aujourd’hui, où on voudra que la modification DNS soit propagée le plus rapidement possible une fois le serveur de remplacement opérationnel. J’ai donc choisi une valeur de 7200 secondes (2 heures) de TTL, ce qui me semble être un bon compromis.

En conclusion, Unicoda s’en sort bien, plus de peur que de mal. On a beau avoir un processus de sauvegarde, des scripts de déploiement automatique, un léger doute subsiste toujours. Cet incident a permis de vérifier, en dehors d’un événement programmé, que les processus mis en place fonctionnent: une très bonne chose. Il me reste à vérifier dans les prochains jours que la sauvegarde s’effectue correctement sur ce nouveau serveur et, dans les prochaines semaines, à migrer une nouvelle fois Unicoda pour un retour chez OVH. A tous mes camarades informaticiens ayant perdu un serveur dans cet incendie: courage !

P.S. Pour suivre l’avancement de la remise en service, voici les tickets liés à l’incident:

Si nut ne détecte pas l’onduleur

En redémarrant mon Pi connecté à l’onduleur, après avoir éteint l’onduleur et le Pi, débranché câbles et alimentations, et déplacé les deux appareils, j’ai constaté à plusieurs reprises que l’onduleur n’était pas détecté après un redémarrage du système.

Je n’ai pour l’instant pas trouvé de meilleur solution que de redémarrer manuellement les services nut-monitor et nut-server, ce qui suffit à résoudre le problème.

$ sudo service nut-monitor status
$ sudo service nut-monitor restart
$ sudo service nut-server status
$ sudo service nut-server start

Par ailleurs, en écrivant ces lignes, je me rends compte que cela pourrait provenir de l’ordre de branchement des câbles, avec un branchement du câble de connexion à l’onduleur, qui arriverait après la tentative d’initialisation des services, car généralement, l’extinction de la machine liée à l’onduleur s’accompagne d’un déplacement de cette dernière et donc d’un débranchement de tous les câbles connectés.

En tous cas, si vous avez trouvé une solution à un problème similaire, n’hésitez pas à la partager, sinon, la solution manuelle est placée ci-dessus pour référence.

Note: Suite à une migration récente, nut-monitor n’est pas installé et tout semble fonctionner. Redémarrer nut-server devrait donc suffire, ou à défaut, nut-client dans le cas d’une machine cliente.