Article pense-bête. J'écris un petit rôle pour configurer firewalld, en gros je veux spécifier quelles interfaces et quels services il faut ajouter dans les zones. Mon idée c'est de définir tout ça dans un dictionnaire dans le playbook, et ensuite de récupérer les élements et sous-élements dans le rôle.
Voici ce que ça donne:
# install.yml
- hosts: nas
roles:
- nfs-server
- firewalld
vars:
firewalld_zones:
- zone: internal
interfaces:
- eth0
services:
- mountd
- nfs
- rpc-bind
- name: Define zones for interfaces
firewalld:
zone: "{{ item.0.zone }}"
permanent: true
immediate: true
interface: "{{ item.1 }}"
state: enabled
with_subelements:
- "{{ firewalld_zones }}"
- interfaces
- name: Allow services for zones
firewalld:
zone: "{{ item.0.zone }}"
permanent: true
immediate: true
service: "{{ item.1 }}"
state: enabled
with_subelements:
- "{{ firewalld_zones }}"
- services
Mouais. C'est quand même loin d'être clair ou évident. Mais c'est le seul moyen que j'ai trouvé pour le moment. Si quelqu'un connaît une méthode plus propre/lisible je suis preneur.
Il y a des gens qui préfèrent les distributions stable telles que debian et il y en a d'autres qui, au contraire, veulent du rolling release. Sur serveur j'ai toujours privilégié la stabilité, donc debian mais je n'ai pas toujours le choix.
Mon NAS tourne sous FreeNAS, et ayant besoin d'étendre ses possibilités j'ai ajouté quelques jails qui offrent donc du FreeBSD quasi upstream. Or FreeBSD a une base stable mais la plupart des ports/packages non. J'ai fait la configuration avec Ansible pour pouvoir tout réinstaller facilement sans me poser de question (voir Docker, Ansible, NixOS : le savoir (re)faire). Et justement le cas s'est présenté, suite au passage au chiffrement des disques j'ai du backuper/restaurer toutes les données et recréer mes jails. Le problème est qu'Ansible n'a pas pu jouer les playbook à cause de cet aspect rolling release qui fait que le comportement ou les fichiers de conf des logiciels à installer a changé. Je pense notamment à unbound, je parse le /usr/local/etc/unbound.conf à la recherche d'une ligne "listen 0.0.0.0" que je créé si elle n'existe pas. Or depuis peu elle est présente dans une autre contexte, cela induit donc mon playbook en erreur. Ce n'est pas trop grave, il m'a fallu moins de 1h pour tout corriger et tout installer.
J'étudie la possibilité de remplacer ces jails FreeBSD par des machines virtuelles debian, car FreeNAS supporte la virtualisation avec bhyve. Une grande stabilité est nécessaire pour orchestrer et automatiser les choses proprement.
Dans l'article Configuration et déploiement avec Ansible je livrais déjà quelques conseils issus de mon expérience. J'ai eu la chance de continuer à travailler sur cette technologie en entreprise et je livre donc les compléments suivants:
- L'idempotence est importante, c'est à dire la capacité à rejouer le même rôle une seconde fois. Si votre playbook doit créer un utilisateur (Unix ou mysql), pensez à ce qui peut se passer au bout de 6 mois. Imaginez qu'entre temps un collègue a changé le mot de passe root de Mysql et ne l'a pas mis à jour dans Ansible. Si vous rejouez le rôle, il sera écrasé et votre prod pourra potentiellement tomber. Pour cela restez à l'affut des options des modules, par exemple pour mysql_user on a update_password: on_create (on ne change le mot de passe que lors de la création du user, on y touche pas s'il existe déjà). C'est pareil avec le module user, si vous mettez le groupe "sudo", l'utilisateur sera mis dans ce groupe et retiré des autres! A moins d'utiliser l'option append: yes. Idempotence et anticipation!
- La documentation et le guide des bonnes pratiques de Ansible recommandent de faire des playbook webservers.yml, dbservers.yml, etc. Mais en pratique il est parfois plus approprié d'avoir 1 playbook par serveur (srv-web-01.yml, srv-web-02.yml, etc). Car dans la réalité de la production on a pas toujours la même chose sur les serveurs.
- Évitez d'éparpiller vos variables partout. Mettez-les dans le .yml du serveur si possible, puis dans le group_vars/groupe et c'est tout. Évitez les host_vars ou encore les roles/truc/vars car cela peut rapidement devenir un enfer de retrouver où sont définies les variables. Parfois il vaut mieux faire moins optimisé mais plus lisible.
- Ne faites pas trop de groupes, car l'inventaire de base (constitué d'un fichier) peut rapidement devenir impossible à maintenir. On peut être tenté de faire des groupes par location géographique, par type d'environnement, par type d'applicatif, mais il sera alors chargé et illisible ce qui entrainera des erreurs dans les playbook (oubli de certains serveurs).
- Soyez verbeux, utilisez le module debug pour afficher des informations. Pour les opérations sensibles utilisez le module pause permettant de valider l'exécution d'une opération, exemple: "Installation de Gluster 3.5.2-4 sur Debian Jessie, infra Paris. Enter pour valider ou CTRL+C pour annuler" (un cluster constitué de différentes versions de Gluster peut crasher, d'où l'intérêt de bien vérifier les versions).
- Documentez clairement, faites des README dans chaque rôle expliquant leur utilité.
- Faites des templates pour que vos collègues/successeurs aient des exemples de playbook utilisant vos rôles.
A l'année prochaine pour l'Ep3...
Ansible vault permet de stocker de manière chiffrée certaines informations, par exemple des variables utilisables dans vos playbooks.
Exemple d'information sensible lisible dans un playbook (sans vault):
---
- hosts: localhost
remote_user: root
tasks:
- name: ensure 'utux' user exists
user:
name: utux
append: yes
groups: sudo
shell: /bin/bash
password: "{{ utux_passwd }}"
generate_ssh_key: yes
state: present
vars:
utux_passwd: "{{ 'secret' | password_hash('sha256') }}"
C'est lisible mais le mot de passe ('secret') se promène en clair, ce qui n'est pas top surtout si vous stockez votre projet sur un dépôt. La solution est d'utiliser ansible vault pour stocker dans un fichier sécurisé le mot de passe.
Création d'un vault:
$ mkdir -p group_vars/all
$ ansible-vault --ask-vault-pass create group_vars/all/vault
On y met une variable contenant notre mot de passe:
vault_utux_passwd: "{{ 'secret' | password_hash('sha256') }}"
Comme on peut le voir, notre fichier est bien chiffré:
$ cat group_vars/all/vault
$ANSIBLE_VAULT;1.1;AES256
66623865616561613766343831613161343936636563373530643933323037333363363139666235
6135616635313332626637636466666236346365373037620a356436363133343161636239313133
36343539626564316431323135626533366462306631323761633330623231386434613734653934
3263383130386164620a663561363238303035336631326437643337646430653139643939363039
62663533626261373863323137316562643038613737333139303536623162633931
Et si on veut l'éditer:
$ ansible-vault --ask-vault-pass edit group_vars/all/vault
On adapte notre playbook:
---
- hosts: localhost
remote_user: root
tasks:
- name: ensure 'utux' user exists
user:
name: utux
append: yes
groups: sudo
shell: /bin/bash
password: "{{ utux_passwd }}"
generate_ssh_key: yes
state: present
vars:
utux_passwd: "{{ vault_utux_passwd }}"
On exécute notre playbook:
ansible-playbook myuser.yml --ask-vault-pass
Ça marche, mais c'est un peu lourd car il faut taper le mot de passe vault à chaque exécution du playbook. Heureusement on peut le définir dans un fichier. On prendra soin de faire un gitignore pour ce fichier, voire même le placer dans un autre dossier:
$ echo "motdepassevault" > vault_passwd
$ echo "vault_passwd" >> .gitignore
On doit ensuite spécifier à Ansible où trouver ce fichier. Vous pouvez le définir au niveau du /etc/ansible/ansible.cfg ou dans le ansible.cfg local du projet (ce que je préfère faire, ainsi il est commité dans le dépôt et tout le monde a la bonne version):
# ansible.cfg
[defaults]
vault_password_file = vault_passwd
Vous devriez maintenant pouvoir exécuter votre playbook sans le --ask-vault-pass.
L'informatique, comme tous les métiers, demande un savoir-faire, celui-ci vient avec l'expérience et la pratique. Mais s'il y a un autre point qui est important et souvent négligé, c'est de savoir refaire. Je vais expliquer.
Imaginez-vous administrateur système dans une entreprise, on vous charge d'installer un serveur web pour afficher une simple page html. Vous allez alors installer Debian + Apache puis placer le fichier html à la racine, rien de difficile jusque là.
Imaginez ensuite qu'au bout de 2 mois ce fichier html soit remplacé par un fichier php un peu plus avancé, vous allez alors installer libapache2-mod-php5 là encore c'est facile. Par la suite le fichier évolue encore et vous oblige à installer des modules, par exemple php5-gd et php5-curl.
Puis le serveur vit sa vie et au bout de 2 ans on vous demande d'en mettre en place un deuxième avec exactement le même rôle. Et là ça se complique car 2 ans c'est long et vous avez probablement oublié tout ce que vous et vos collègues avez fait pour configurer ce serveur au fur et à mesure. S'il est facile d'identifier que apache2 et php5 sont présents, en revanche les modules sont beaucoup moins évidents, surtout si vous avez utilisé des gestionnaires tiers tels que pecl, cpan, pip.
L'une des grandes problématiques est donc de trouver un moyen pour garder une trace et refaire rapidement toutes les étapes qui ont accompagné la vie du serveur. Alors bien sûr le grand classique est d'utiliser un wiki, mais qui vous garanti que ce qu'y s'y trouve représente bien ce qui est en production ? Personne n'est à l'abri d'une modification "urgente" en prod non rapportée sur le wiki.
Une solution que l'on retrouve souvent et qui est appliquée dans Ansible, Docker et NixOS, c'est d'éliminer toutes les manipulations sur la prod. On y touche plus, à la place on travaille sur un fichier de recette, que l'on va ensuite deployer et jouer. Sur Ansible ce sont les playbook, sur docker les Dockerfile, et sur NixOS le fichier configuration.nix.
Cela parait beaucoup plus propre et ça l'est, c'est une rigueur pas forcément naturelle qui paye sur le long terme. Néanmoins ce n'est pas toujours évident, une manipulation rapide à faire sur un serveur peut se transformer en plusieurs minutes voire heures de devops dans Ansible. Let's Encrypt est un exemple, son automatisation n'est pas simple car vous allez devoir gérer deux cas différents selon la présence ou non d'un certificat. En effet certbot peut soit utiliser le mode standalone et donc couper Nginx (car nécessité du port 80) ou alors utiliser le mode webroot et justement passer par Nginx + vhost de votre site, or ce dernier a justement besoin du certificat pour démarrer. Et quid du cas de Docker où vous devez créer un nouveau container et le relier à l'existant juste pour renouveler vos certificats.
En conclusion le boulot de sysadmin, développeur ou devops n'est pas seulement de faire ou réparer les choses mais également de prévoir l'avenir, s'imposer une rigueur même si elle parait contre-productive sur le moment. J'aurai probablement l'occasion de parler de plus en détail de Docker et de NixOS dans de prochains articles, restez branchés.