Tirer toute la puissance d'Ansible avec les rôles
Ansible est absolument génial. Il permet d’automatiser l’installation et la maintenance de machines et d’infrastructures complètes. J’ai déjà consacré un article à la prise en main d’Ansible, nous nous concentrerons ici sur les rôles.
Les rôles représentent une manière d’abstraire les directives includes. C’est en quelque sorte une couche d’abstraction. Grâce aux rôles, il n’est plus utile de préciser les divers includes dans le playbook, ni les paths des fichiers de variables etc. Le playbook n’a qu’à lister les différents rôles à appliquer et le tour est joué !
En outre, depuis les tasks du rôle, l’ensemble des chemins sont relatifs. Inutile donc de préciser l’intégralité du path lors d’un copy
, template
ou d’une tâche. Le nom du fichier suffit, Ansible s’occupe du reste.
On peut faire beaucoup avec les playbooks et les include
, cependant, lorsque nous commençons à gérer des infrastructures complexes, on a beaucoup de tâches et les rôles s’avèrent salvateurs dans l’organisation et l’abstraction qu’ils apportent.
Par ailleurs, Ansible met à disposition une plate-forme permettant de télécharger et de partager des rôles utilisateurs : Ansible galaxy. Un bon moyen de ne pas réinventer la roue.
Pour illustrer cet article, nous allons préparer différents rôles et les assembler dans un playbook afin d’installer un simple serveur LAMP (testé sur Ubuntu uniquement, mais l’idée est là). Pour faciliter le suivi, j’ai mis l’ensemble sur un repo GitHub. C’est parti !
En CLI, on peut utiliser ansible-galaxy afin de préparer l’arborescence d’un rôle vide. Nos playbooks seront à la racine de notre dossier tandis que les rôles seront dans roles. Ainsi, lorsque nous appellerons un rôle depuis un playbook, sans avoir besoin de préciser autre chose que son nom, Ansible saura où chercher.
# on initialise un role vide depuis le dossier roles
ansible-galaxy init common
cd common && ls -R
README.md handlers tasks vars
defaults meta tests
./defaults:
main.yml
./handlers:
main.yml
./meta:
main.yml
./tasks:
main.yml
./tests:
inventory test.yml
./vars:
main.yml
On voit ici qu’Ansible nous a créé plusieurs dossiers avec un fichier main.yml
dans chacun d’eux. Par défaut, Ansible ira chercher les informations dans les main.yml
de chaque répertoire. Cependant, vous pouvez créer d’autres fichiers et les référencer dans vos instructions. Par exemple, il est tout à fait possible de créer une autre tâche dans tasks
et de l’inclure depuis tasks/main.yml
. Passons rapidement en revue la fonction de chaque répertoire.
La commande ansible-galaxy
est normalement destinée à être utilisée avec Galaxy, nous en tirons cependant profit pour facilement obtenir un boilerplate de rôle vide. Par défaut, elle ne créée pas les répertoires files
et templates
dont nous allons tout de même parler.
- defaults
- Ce sont ici les variables par défaut qui seront à disposition du rôle.
- vars
- De la même manière que `defaults`, il s'agit ici de variables qui seront à disposition du rôle, cependant, celles-ci ont en général vocation à être modifiées par l'utilisateur et elles prennent le dessus sur celles de defaults si elles sont renseignées.
- tasks
- Sans grande surprise, c'est ici que vous référencerez vos tâches.
- files
- Tous les fichiers étant destinés à être traités par le module `copy` seront placés ici.
- templates
- Idem que `copy`, mais cela concerne les fichiers du module `template`.
- meta
- Il y a ici plusieurs usages, notamment dans le cas de rôles publiés sur Galaxy. Dans notre cas, on référencera ici les dépendances à d'autres rôles.
- tests
- Pas utile pour nous, c'est seulement pour Galaxy et la doc n'est pas très loquace à ce propos…
En plus des répertoires sus-mentionnés, il y a le README qu’il est de bon ton de renseigner afin d’expliquer comment utiliser le rôle, quelles sont les variables à définir etc.
Aucun des répertoires n’est impératif – quoique sans tasks
, notre rôle ne sert pas à grand chose. On effacera donc les répertoires dont on n’a pas l’utilité.
On est parti pour la création des différents rôles utiles à notre serveur LAMP. On commence avec le rôle common
(créé précédemment), qui sert de base à l’ensemble de nos serveurs. Cette tâche est assez fournie, ça permet d’avoir un exemple de diverses choses qu’on peut faire, vous pouvez néanmoins la lire en diagonale, le but n’est pas de configurer un serveur, c’est de découvrir les rôles Ansible !
# tasks/main.yml
---
# maj du système qui est fraichement installé
- name: Update & upgrade system
apt:
update_cache: yes
upgrade: dist
# souvent livré avec un mdp root envoyé par mail
# on en génère un aléatoire avant de le remplacer
# on va configurer ssh pour n'accepter que les clefs
- name: Generate password
command: openssl rand -base64 32
register: randomPass
- name: Change root passwd
shell: echo "root:{{ randomPass }}" | chpasswd
# divers choses pouvant servir sur tous les serveurs
- name: Install base soft (transport-https, fail2ban)
apt:
name: "{{ item }}"
with_items:
- apt-transport-https
- software-properties-common
- fail2ban
- exim4
- htop
- glances
- iotop
- unattended-upgrades
# on configure les maj auto de secu
# on pourrait ici très bien utiliser un fichier de template au lieu de remplacer à coup de regexp
- name: Configure unattended-upgrades
replace:
dest: /etc/apt/apt.conf.d/50unattended-upgrades
regexp: "{{ item.regexp }}"
replace: "{{ item.replace }}"
with_items:
- { regexp: '//Unattended-Upgrade::Mail "root";', replace: 'Unattended-Upgrade::Mail "{{ email }}";' }
- { regexp: '//Unattended-Upgrade::MailOnlyOnError "true";', replace: 'Unattended-Upgrade::MailOnlyOnError "true";' }
- { regexp: '//Unattended-Upgrade::Remove-Unused-Dependencies "false";', replace: 'Unattended-Upgrade::Remove-Unused-Dependencies "true";' }
# ici, on upload directement le fichier de config qui va bien
- name: Enable unattended-upgrades
copy:
src: 20auto-upgrades
dest: /etc/apt/apt.conf.d/20auto-upgrades
# on remplace tout bonnement la config ssh par défaut
- name: Upload ssh server config file
copy:
src: sshServerConfig
dest: /etc/ssh/sshd_config
mode: 0644
# on ajoute un script d'init iptables
- name: Install iptables.sh
copy:
src: iptables.sh
dest: /usr/local/sbin/iptables.sh
mode: 0744
# qui se lancera au boot
- name: Make iptable start on boot
copy:
src: rc.local
dest: /etc/rc.local
mode: 0755
# fail2ban c'est bien aussi, mangez-en
- name: Upload fail2ban config file
copy:
src: fail2ban.conf
dest: /etc/fail2ban/jail.d/defaults-debian.conf
mode: 0644
# les alias pour récupérer les emails
- name: Configure /etc/aliases
lineinfile:
dest: /etc/aliases
regexp: ^root:.*
line: "root: {{ email }}"
# on configure exim4
- name: Configure exim4
replace:
dest: /etc/exim4/update-exim4.conf.conf
regexp: "dc_eximconfig_configtype='local'"
replace: "dc_eximconfig_configtype='internet'"
On voit déjà que là, on fait pas mal de choses (j’ai bien allégé par rapport à ce que j’utilise réellement) qui seront communes à absolument tous nos serveurs. Un rôle qui sera donc utilisé absolument partout.
Vous notez certainement qu’il y a dans cette tâche des variables et des fichiers importés. Il faut ajouter tout cela, sinon Ansible balancera une erreur.
Concernant les variables, il s’agit simplement de l’email, pas de defaults
ici, car il faut que l’email soit renseigné, il ne peut pas être “par défaut”. Nous n’avons donc pas l’utilité de defaults
et nous mettons cette variable directement dans vars/main.yml
:
---
email: mon@email.fr
Point de vars:
, Ansible sait qu’il s’agit de variable car c’est dans le dossier vars
, what else?
Il y aurait pas mal de choses à dire sur cette conf, concernant ssh, on ne pourra se connecter qu’avec une clef ed25519. Pour en savoir plus sur la config ssh, je vous invite à lire mon article dédié, idem pour fail2ban. Quant à iptables, il ferme tout sauf le web, ssh et quelques services de base (DNS, DHCP, NTP…).
files
contient naturellement l’ensemble des fichiers que nous avons besoin d’uploader. Vous trouverez leur contenu sur le git de l’article.
# voici quand même la liste
ls files/
20auto-upgrades fail2ban.conf iptables.sh rc.local sshServerConfig
Nous n’allons pas détailler tous les rôles un à un car vous les retrouverez sur le git, nous allons créer un rôle nommé lampserver
, qui se chargera d’installer PHP, Apache2, MySQL et letsencrypt. Comme vous vous en doutez, chacune des différentes briques constitue un rôle en elle-même. Nous pourrions vouloir installer letsencrypt avec Nginx en reverse d’un Node.js. Il serait un peu con de devoir réécrire des tâches alors que nous avons déjà fait le boulot… Vous voyez la logique ?
On commence donc pas créer notre meta/main.yml
dans lequel on référence nos dépendances :
dependencies:
- { role: letsencrypt }
- { role: mariadb }
- { role: apache }
# notez qu'il est possible de passer des paramètres directement aux rôles
- { role: letsencrypt, withCerts: true }
# très intéressant également, il est possible de lister des rôles depuis git
# le tag est optionnel mais représente une option à connaître également
- { role: 'git+https://gitlab.com/user/rolename,v1.0.0'}
Eh oui, tout simplement. On précise ici que ce rôle dépend des rôles letsencrypt
, mariadb
et apache
qui devront donc être exécutés avant.
Ces rôles sont bien entendu présents dans roles
au même niveau que les autres (sauf s’ils sont référencés via une URL).
Notre tâche est ensuite tout ce qu’il y a de plus classique.
# tasks/main.yml
---
# à ce stade, apache est déjà installé car ce rôle précise ses dépendances
- name: Install php and common php ext
apt:
name: "{{ item }}"
with_items:
- libapache2-mod-php
- php
- php-curl
- php-gd
- php-json
- php-mbstring
- php-mysql
- php-xml
- name: Enable required apache modules
apache2_module:
name: "{{ item }}"
state: present
with_items:
- expires
- headers
- http2
- rewrite
- ssl
Enfin, il ne nous reste plus qu’à créer notre playbook et à donner les instructions pour l’exécution des rôles.
---
- hosts: webserver
roles:
- common
- lampserver
# il est possible de passer des variables aux rôles ici
- { role: foo, myvar: 'blabla' }
# ou d'en définir au niveau global
# elles seront prises en compte dans les rôles
# vars:
# blurp: test
# ici, on demande directement à l'exécution de renseigner une variable
vars_prompt:
- name: "mysqlRootPass"
prompt: "password for MySQL root"
Attention cependant à la priorité des variables. Dans le cas d’une variable référencée à la fois dans le playbook et dans vars
du rôle avec deux valeurs différentes, c’est vars
qui l’emporte. Néanmoins, la référence dans le playbook l’emporte sur defaults
. En outre, il peut être très pratique de référencer une variable dans le playbook. Par exemple, l’email sera certainement requis dans de nombreux rôles, autant ne le déclarer qu’une seule fois.
Nous avons à peu-prêt fait le tour des rôles. J’espère que vous y voyez un peu plus clair, et surtout, que vous êtes convaincu d’en user et d’en abuser !
Commentaires
Rejoignez la discussion !