Ansible TLDR
23 septembre 2020Besoin d’une introduction Ă Ansible et la flemme de lire la documentation ? Bienvenue.
Principes
Ansible est un outil de gestion de configuration. Ă€ partir d’une machine, dite “contrĂ´leur”, on exĂ©cute des tâches sur des machines distantes via SSH. Les machines distantes n’ont donc besoin que d’avoir Python installĂ© et d’ĂŞtre accessible en SSH.
Intérêts
Utiliser Ansible pour dĂ©ployer et configurer un service permet d’avoir une installation/configuration de facto documentĂ©e, automatisĂ©e et reproductible. Ce qui peut s’avĂ©rer utile dans une multitude de cas, par exemple : passage de l’environnement de test, Ă la preprod et Ă la prod, dĂ©ployer plusieurs instances d’un mĂŞme service, avoir un plan de reprise d’activitĂ©, etc.
Installation
La meilleure manière, selon moi, est d’installer Ansible dans un environnement virtuel avec pip :
$ python3 -m venv ~/.local/share/pyvenv/ansible-test
$ source ~/.local/share/pyvenv/ansible-test/bin/activate
$ pip install ansible
Pour les autres mĂ©thodes d’installation c’est par-lĂ : https://docs.ansible.com/ansible/latest/installation_guide/index.html
Inventaire
Pour que Ansible sache Ă qui parler il a besoin d’un inventaire, le plus simple Ă©tant d’utiliser le format INI :
$ cat inventory/hosts
serveur-bdd-01 ansible_host=192.0.2.1 ansible_port=2222 ansible_user=root
serveur-app-01 ansible_host=192.0.2.2 ansible_user=admin ansible_become=true
serveur-app-02 ansible_host=192.0.2.3 ansible_user=admin ansible_become=true
serveur-front-01 ansible_host=192.0.2.3 ansible_user=admin ansible_become=true
[groupe_bdd]
serveur-bdd-01
[groupe_app]
serveur-app-01
serveur-app-02
[groupe_front]
serveur-front-01
Les premières lignes correspondent à la liste des hôtes avec leurs informations de connexion. Détails :
serveur-bdd-01: un nom arbitraireansible_host=: l’hĂ´te SSH distant sous forme d’un FQDN, une adresse IP ou une entrĂ©eHostde ssh_configansible_port=: le port de connexion distante SSHansible_user=: l’utilisateur distant utilisĂ© pour la connexion SSHansible_become=true: un paramètre qui permet de dire Ă Ansible d’acquĂ©rir les permissions root (via sudo par dĂ©faut, donc il faut que l’utilisateur soit sudoer, avec l’option NOPASSWD idĂ©alement)
Les lignes suivantes sont les groupes et leurs membres.
On peut tester que tous les membres de l’inventaire sont joignable en utilisant le module ping sur le groupe all, avec la commande suivante :
$ ansible --inventory inventory/hosts --module-name ping all
serveur-app-02 | SUCCESS => {
"changed": false,
"ping": "pong"
}
serveur-app-01 | SUCCESS => {
"changed": false,
"ping": "pong"
}
serveur-bdd-01 | SUCCESS => {
"changed": false,
"ping": "pong"
}
serveur-front-01 | SUCCESS => {
"changed": false,
"ping": "pong"
}
Plus de dĂ©tails sur l’inventaire : https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html
Plus spécifiquement sur les options de connexion : https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html#connecting-to-hosts-behavioral-inventory-parameters
Playbook
Pour faire des choses sympa avec Ansible, on utilise la commande ansible-playbook qui permet de lancer des playbooks. Un playbook c’est un fichier YAML avec une suite de tâches Ă exĂ©cuter.
$ cat playbooks/ansible-tldr-debug.yml
---
- name: ANSIBLE TLDR
hosts: all
tasks:
- name: print hello world
debug:
msg: hello world
$ ansible-playbook --inventory inventory/hosts playbooks/ansible-tldr-debug.yml
PLAY [ANSIBLE TLDR] **************************************************************************************
TASK [Gathering Facts] ***********************************************************************************
ok: [serveur-app-01]
ok: [serveur-front-01]
ok: [serveur-bdd-01]
ok: [serveur-app-02]
TASK [print hello world] *********************************************************************************
ok: [serveur-bdd-01] => {}
MSG:
hello world
ok: [serveur-app-01] => {}
MSG:
hello world
ok: [serveur-app-02] => {}
MSG:
hello world
ok: [serveur-front-01] => {}
MSG:
hello world
PLAY RECAP ***********************************************************************************************
serveur-app-01 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
serveur-app-02 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
serveur-bdd-01 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
serveur-front-01 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Pour faire simple, on a exĂ©cutĂ© le module debug, qui ne fait qu’afficher un message ou une variable, depuis tous les hosts membres du groupe all. Le groupe all, comme son nom l’indique contient tous les hĂ´tes de l’inventaire.
Un autre exemple, un peu plus utile :
$ cat playbooks/ansible-tldr-apt.yml
---
- name: ANSIBLE TLDR
hosts: groupe_app
tasks:
- name: dist upgrade
apt:
cache_valid_time: 3600
upgrade: dist
- name: remove noob text editor
apt:
name: nano
state: absent
- name: ensure best text editor in the world is installed
apt:
name: vim
$ ansible-playbook --inventory inventory/hosts playbooks/ansible-tldr-apt.yml
[...]
Cette fois ci, pour les membres du groupe groupe_app, on a mis à jour le système, désinstallé nano et installé vim. Tout ceci avec le module apt.
Si vous exĂ©cutez le playbook plusieurs fois de suite, vous verrez que les fois suivantes l’Ă©tat des tâches n’est plus changed, c’est ce qu’on appelle l’idempotence.
Pour plus de détails sur les playbooks : https://docs.ansible.com/ansible/latest/user_guide/playbooks_intro.html
Variables
Quand on exĂ©cute des playbooks sur plusieurs noeuds on potentiellement besoin d’avoir des paramètres diffĂ©rents pour chacun, ou simplement on ne veut pas mettre en dur des valeurs dans les tâches, on utilise donc des variables.
$ cat playbooks/ansible-tldr-debug.yml
---
- name: ANSIBLE TLDR
hosts: all
variables:
my_name: Nicolas
tasks:
- name: print hello world
debug:
msg: hello {{ my_name }}
LĂ on a dĂ©fini une variable my_name directement dans le playbook et on l’a utilisĂ©e dans la tâche debug grâce aux doubles accolades {{ ... }}. Ansible utilise Jinja pour les variables et plein d’autres choses, vous retrouverez donc sa syntaxe Ă plein d’endroits.
De manière gĂ©nĂ©rale, pour l’assignation des variables on utilise plutĂ´t les dossiers de l’inventaire. Voici comment ça se prĂ©sente :
$ tree inventory/
inventory/
├── group_vars
│  ├── all.yml <-- les variables assignées dans ce fichier s'appliqueront à tous les noeuds
│  ├── groupe_app.yml <-- s'applique pour le groupe "groupe_app"
│  ├── groupe_bdd.yml
│  └── groupe_front.yml
├── host_vars
│  ├── serveur-app-01.yml <-- les variables de ce fichier ne s'appliqueront que pour cet hôte
│  ├── serveur-app-02.yml
│  ├── serveur-bdd-01.yml
│  └── serveur-front-01.yml
└── hosts
Plus de détails sur les variables : https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html
RĂ´le
Quand on fait des choses plutĂ´t complexes, plutĂ´t que d’avoir un playbook Ă rallonge, on va dĂ©couper les tâches en rĂ´les. Pour ma part j’ai tendance Ă faire un rĂ´le par service.
Exemple avec le service sshd :
$ tree roles/sshd/
roles/sshd/
├── defaults
│  └── main.yml <-- ici on défini les valeurs par défaut des variables du rôle
├── handlers
│  └── main.yml <-- ici des actions qui peuvent être déclenchées lorsqu'une tâche applique un changement
└── tasks
└── main.yml <-- ici les tâches à executer
D’autres dossiers peuvent exister dans un rĂ´le, mais on a lĂ les principaux. Voyons le dĂ©tail des fichiers :
$ cat roles/sshd/defaults/main.yml
---
sshd_config:
# ensure public key authentication is enabled
- regexp: '^#?PubkeyAuthentication\s+.*$'
replace: 'PubkeyAuthentication yes'
# ensure password authentication is disabled
- regexp: '^#?PasswordAuthentication\s+.*$'
replace: 'PasswordAuthentication no'
# disable challenge-response authentication
- regexp: '^#?ChallengeResponseAuthentication\s+.*$'
replace: 'ChallengeResponseAuthentication no'
sshd_authorized_keys: []
$ cat roles/sshd/handlers/main.yml
---
- name: restart sshd
systemd:
name: sshd.service
state: restarted
$ cat roles/sshd/tasks/main.yml
---
- name: configure sshd
notify: restart sshd
loop: "{{ sshd_config }}"
replace:
path: /etc/ssh/sshd_config
regexp: "{{ item.regexp }}"
replace: "{{ item.replace }}"
- name: configure authorized_keys
loop: "{{ sshd_authorized_keys }}"
authorized_key:
user: "{{ item.user }}"
key: "{{ item.key }}"
state: "{{ item.state | d('present') }}"
Si vous regardez le contenu du dernier fichier, vous pouvez constater qu’on utilise le module replace, mais entre le nom de la tâche et l’appel du module il y a deux options :
notify: qui permet de dĂ©clencher le handler nommĂ©restart sshdlorsque cette tâche aura l’Ă©tat “changed”, ce qui aura pour effet de redĂ©marrer le service SSH Ă la fin de l’exĂ©cution du playbook (uniquement si il y aura eu des changements)loop: qui permet d’exĂ©cuter le module pour chacun des Ă©lĂ©ments de la variable (de type “liste”) qui lui est passĂ©e, pour chaque itĂ©ration de la boucle l’Ă©lĂ©ment sera disponible via la variableitem
Plus de dĂ©tails sur les rĂ´les : https://docs.ansible.com/ansible/latest/user_guide/playbooks_reuse_roles.html Plus de dĂ©tails sur les boucles (“loop”) : https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html Plus de dĂ©tails sur les handlers : https://docs.ansible.com/ansible/latest/user_guide/playbooks_handlers.html
Ansible permet de faire encore plein d’autres choses comme des exĂ©cutions conditionnelles, utiliser des templates, des tags, chiffrer des variables, utiliser des inventaires dynamiques, utiliser ses propres modules, etc. Mais lĂ , pas le choix il faudra passer par la documentation pour explorer tout ça. J’espère que ça vous aura donnĂ© un bon aperçu des possibilitĂ©s et Ă©galement l’envie d’aller plus loin.
Idées reçues
Et pour terminer quelques rĂ©ponses Ă des idĂ©es reçues au sujet d’Ansible ou semblable.
Ça prend plus de temps à écrire un rôle, le tester et le déployer, que de déployer le service à la main
Oui mais c’est du temps gagnĂ©, partiellement en documentation, et ensuite surtout si il faut le rĂ©utiliser pour dĂ©ployer de nouvelles instances supplĂ©mentaires ou dans diffĂ©rents environnements. C’est aussi du temps gagnĂ© quand il faut s’assurer de la conformitĂ© de la configuration d’une machine ou d’un service, au lieu de se connecter sur la machine et scruter chacun des fichiers, re-exĂ©cution du playbook et on en parle plus on sait que la machine est dans l’Ă©tat attendu.
Ça ajoute juste une couche de complexité
Ça ajoute potentiellement une complexitĂ© par rapport Ă une installation manuelle, mais ça n’est qu’une syntaxe de scripting un peu particulière Ă apprendre et comprendre. Ça reste plus simple et plus efficace que de faire du scripting manuel, et encore plus simple que d’apprendre un nouveau langage de programmation, donc la marche Ă franchir n’est franchement pas haute non plus.
Comme c’est automatisĂ© on perd la maĂ®trise, plus personne ne sait comment faire les choses manuellement
Alors lĂ , grosse erreur, au contraire. Il n’y a rien de magique, ce n’est ni plus ni moins que du scripting simplifiĂ© et optimisĂ©. Toutes les tâches sont Ă Ă©crire au lieu d’ĂŞtre exĂ©cutĂ©e manuellement. Et faire les choses ainsi oblige Ă les poser, Ă rĂ©flĂ©chir Ă ce qu’elles doivent faire, comment le faire, et ça implique souvent de devoir bien comprendre ce qu’on va faire. Ce qui n’est pas nĂ©cessairement le cas lorsqu’on bidouille un serveur jusqu’Ă ce qu’il tombe en marche.
Ça a mĂŞme l’avantage d’avoir toutes les informations sur une installation et configuration Ă un seul endroit, le dĂ©pĂ´t Git des rĂ´les et playbooks.