J'écris cet article pour combattre une idée que je vois revenir souvent dans la blogosphère (française), qui dit que dans l'univers du logiciel libre c'est le bordel, c'est la guerre, trop de choix, des distributions qui se combattent au lieu de s'unir, et des développeurs qui se moquent de leurs utilisateurs.
J'ai un problème avec ça car le libre c'est mon métier. Je suis ingénieur systèmes Linux et si j'en suis arrivé là, c'est parce que je baigne dans un univers où on partage les connaissances, où on peut lire le code, où on peut poser des questions, et où globalement les logiciels marchent bien. Il y a 20 ans encore l'informatique c'était très majoritairement du Microsoft, un monde payant dans lequel il fallait des diplômes, des livres, des certifications, c'était quand même assez fermé et peu accessible. Aujourd'hui les produits libres en vogue tels que Docker ou Ansible publient leur documentation, des howto, des quickstart, un repo github, et si on ajoute stackoverfow/stackexchange on peut s'auto-former en quelques semaines. Bien sûr l'expérience en production est importante et ce n'est qu'après quelques mois voire années qu'on peut se vendre comme "expert" sur cette technologie, mais au moins c'est possible. Allez essayer de vous auto-former avec un Windows Server et un Exchange, outre les spécifications matérielles très élevées, hors programme MSDNA c'est juste impossible à cause du coût des licences.
Le libre c'est aussi la culture Devops dont je fais partie et où Microsoft a bien du mal à se positionner. On automatise, on industrialise, on code, on pousse sur le repo git, les tests unitaires et l'intégration sont déclenchés tout seuls, et on s'amuse. Merci au libre de nous avoir montré qu'on peut faire autre chose qu'installer des .exe à la main ou avec des logiciels très cher tels que SCCM.
Donc au final quand je tombe sur des articles présentant le monde du logiciel libre comme game of thrones, je me dis que ce n'est que la vision desktop, les 1% de parts de marché pour lesquels j'ai abandonné tout espoir. On oublie la culture qu'il y a derrière et moi j'aime travailler avec le libre, pas pour les raisons extrémistes de RMS et la FSF, mais pour l'efficacité, le partage, les compétences, l'accessibilité.
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.