Ansible - Les meilleures pratiques - Partie 1
Publié le : 1 mars 2023 | Mis à jour le : 27 juin 2023Table des matières
Ça fait maintenant 7 ans que j’utilise Ansible quotidiennement pour provisionner et configurer des infrastructures. Comme j’ai vu plusieurs implémentations au fil de mes changements de poste, je vous propose deux billets sur les bonnes pratiques et les meilleurs design patterns Ansible que j’ai pu rencontrer.
Si vous êtes débutant avec Ansible, je vous propose de suivre ce billet qui est une sorte d’index de tout ce qui se trouve sur mon blog parlant d’Ansible. En effet, avant de lire ce billet, il faut maîtriser le vocabulaire ansible pour le comprendre.
Qualité de code
Le DSL Ansible : YAML
Ansible utilise le YAML comme DSL. Un fichier yaml débute toujours avec
---
sur la première ligne et ...
sur la dernière ligne.
Utilisez deux espaces pour l’indentation.
---
- name: update web servers
hosts: webservers
become: true
tasks:
- name: ensure apache is at the latest version
ansible.builtin.package:
name: httpd
state: latest
...
Utilisez des espaces cohérents : Pour bien séparer les choses et améliorer la
lisibilité, envisagez de laisser une ligne vierge entre les blocks
, les tasks
,
les handlers
.
Outils d’assistance d’écriture
Pour vous aider à écrire votre code Ansible, utilisez un éditeur permettant d’installer l’extension fournit par Redhat.
Pour valider votre code, utilisez les outils ansible-lint ou spotter qui vous indiquera comment en améliorer la qualité.
Mettre en place des règles de nommage
Utilisez une stratégie de nommage cohérente pour les variables, les playbooks, les taches, les rôles et modules.
Exemples de règles de nommage :
- Tous les fichiers doivent utiliser l’extension
.yml
et non.YML
, ni.yaml
, … - Toutes les variables devant être passé dans la ligne de commande débute par
cli_
. - Toutes les variables globales débutent par
g_
. - Toutes les variables utilisées la convention snake case.
- **Toutes les variables globales des rôles doivent posséder le nom du rôle
comme préfixe
install_nginx_g_nginx_version
. - **Seules les variables pouvant être modifié doivent être placé dans
default/main.yml
. Toutes les autres dansvars/main.yml
. - Toutes les variables se trouvant dans
vars/main.yml
doivent commencer par un_
. - On utilise
_
comme séparateur de noms de variables, de nom de playbooks, de roles … - **Les noms de variables Jinja sont encadrés par des espaces
{{ variable_1 }}
. - …
Exemple de nommage de variables :
myappIP: "127.0.0.1" # incorrect
myapp_Ip: "127.0.0.1" # incorrect
myapp_ip: "127.0.0.1" # correct
Mettre en place des règles de programmation
Sur le code
- Comme pour tout langage de programmation, lorsque votre code nécessite une
explication supplémentaire pour sa compréhension, n’hésitez pas à ajouter un
commentaire. Un commentaire débute par le caractère
#
.
---
- name: update web servers
hosts: webservers # n'installe le package http que sur les serveurs webs
become: true
...
- Éviter de construire des usines à gaz !; Comme tout langage de programmation, Ansible offre de nombreuses façons de découper son code. Une règle que je me fixe depuis toujours : programmer en utilisant les algorithmes les plus simples possibles -> Votre code sera plus facile à maintenir.
- Dont Repeat Yourself : Factoriser un maximum votre code en ayant
recours aux rôles (un rôle rempli un objectif) et collections Ansible. Les
rôles doivent être stockés dans un gestionnaire de code et posséder un
pipeline d’intégration permettant de tester si le code fonctionne toujours à
chaque montée de versions des dépendances :
ansible-core
, les collections, les librairies python, … - Pour les tests d’intégration la meilleure solution est d’utilisée molecule via les drivers mis à disposition : docker, podman, vagrant, ec2, gcp …
- Le recours aux modules
raw
,shell
etcommand
ne doit être fait qu’en dernier recours. En effet, ses modules cassent l’idempotence de votre code Ansible. - Découper vos fichiers
yaml
pour les rendre lisibles. Utilisez un fichier chapeaumain.yml
pour orchestrer d’autres taches de niveau inférieur en les regroupant par tâches. - Définissez toujours des valeurs par défaut pour vos variables
"{{ some_variable | default('default_value') }}"
- Indiquez toujours l’état des paramètres booléens même si cela n’est pas nécessaire en raison de la valeur par défaut. Rien ne dit qu’à la prochaine version la valeur par défaut change !
- name: Creation d'un user applicatif
ansible.builtin.user:
name: "{{ user_appli_name }}"
uid: "{{ user_appli_uid }}"
group: "{{ user_appli_group_name }}"
create_home: true
shell: "/sbin/nologin"
force: false
append: false
remove: false
system: false
move_home: false
non_unique: false
become: true
- Évitez un maximum le recours à
ignore_errors
. Pour cela, vous pouvez :- Pour toutes les taches qui ne doivent s’exécuter que si une des précédentes a
provoqué un changement, pensez à utiliser les
handlers
.
- Pour toutes les taches qui ne doivent s’exécuter que si une des précédentes a
provoqué un changement, pensez à utiliser les
tasks:
- name: Création d'une base toto
community.postgresql.postgresql_db:
name: acme
state: present
notify: Execute script
handlers:
- name: Execute script
community.postgresql.postgresql_script:
db: acme
path: /var/lib/pgsql/insert.sql
encoding: UTF-8
- Utilisez les conditions
when
:
tasks:
- name: Création d'une base toto
community.postgresql.postgresql_db:
name: acme
state: present
register: r_create_database
- name: Execute script
when: r_create_database | changed
community.postgresql.postgresql_script:
db: acme
path: /var/lib/pgsql/insert.sql
encoding: UTF-8
...
Sur les inventaires
- Utilisez des inventaires séparés pour chaque environnement pour les isoler les uns des autres et éviter ainsi les erreurs en ciblant les mauvais environnements.
- Pensez à utiliser le regroupement dynamique au moment de l’exécution à l’aide
du module
group_by
. - Utilisez des groupes d’inventaire : Hôtes de groupe basés sur des attributs communs qu’ils pourraient partager (géographie, objectif, rôles, environnement). Attention à la précédence des variables Ansible.
- Ne mettre dans l’inventaire que des variables globales propre à l’infrastructure. Pour les autres variables les mettre au niveau du playbook;
- Utilisez la commande
ansible-inventory --list
pour vérifier le résultat.
Sécurité
Gestion des secrets
On utilise tous git pour stocker nos données (playbooks, inventaires, roles, collections …) Ansible. Mais attention à ne pas divulguer vos données secrètes comme des mots de passe, des clés SSH, des tokens d’API, …
Ansible met à disposition ansible-vault
(un
coffre-fort) qui permet de chiffrer ces données. Au besoin ansible saura
déchiffrer les données.
Utilisation des roles et collections disponibles sur Ansible Galaxy
Faites preuve de diligence raisonnable lorsque vous utilisez des rôles disponibles depuis Ansible Galaxy : Validez leur contenu et choisissez des rôles auprès de contributeurs fiables : gueerlinguy, robert-de-bock
Stockez vos rôles dans vos référentiels de code et utilisez un pipeline pour en contrôler le contenu (pas de secrets).
Au moment d’exécuter un playbook sur la production
Si vous n’êtes pas l’auteur d’un playbook, vous pouvez :
- Utiliser l’option
--check
qui ne réalise aucune modification. - Demander à juste visualiser les tâches qui s’exécuteront. Il faut juste
ajouter l’option
--list-tasks
à la commandeansible-playbook
.
ansible-playbook configure.yml --list-tasks
playbook: configure.yml
play #1 (all:!bastion): all:!bastion TAGS: []
tasks:
vérification que les machines sont accessibles TAGS: []
play #2 TAGS: []
tasks:
application/installation : Creation du groupe applicatif general TAGS: []
application/installation : Creation du user applicatif general TAGS: []
application/installation : Creation du répertoire data TAGS: []````
...
- Vous pouvez aussi valider pour chaque tache quelles cibles sont concernées avec
l’option
--list-hosts
.
ansible-playbook configure.yml --list-tasks --list-hosts
playbook: configure.yml
play #1 (all:!bastion): all:!bastion TAGS: []
pattern: ['all:!bastion']
hosts (4):
machine1
machine2
tasks:
vérification que les machines sont accessibles TAGS: []
play #2 (!rnvp:outils): !rnvp:outils TAGS: []
pattern: ['!rnvp:outils']
hosts (3):
bastion
machine1
machine2
tasks:
application/installation : Creation du groupe applicatif general TAGS: []
application/installation : Creation du user applicatif general TAGS: []
application/installation : Creation du répertoire data TAGS: []
Plus loin
Dans ce billet de blog, j’ai essayé de regrouper toutes les bonnes pratiques que j’utilise pour construire mon code Ansible. Si vous avez d’autres n’hésitez pas à me les remonter. La suite se trouve ici avec au menu tout ce qui permet d’optimiser le temps d’exécution des playbooks Ansible.
Le prochain billet présentera quelques designs patterns que j’utilise.