This repository has been archived on 2024-03-03. You can view files and clone it, but cannot push or open issues or pull requests.
adlin/tutorial/ansible/ansible.md

564 lines
18 KiB
Markdown
Raw Normal View History

2018-02-26 08:05:38 +00:00
\newpage
Automatiser la configuration de son SI
=======================================
2018-03-07 03:14:36 +00:00
Comme tout bon ~~paresseux~~ sys. admin qui se respecte, sans plus attendre,
2023-05-05 07:57:22 +00:00
nous allons vouloir automatiser toutes les actions rébarbatives que nous avons
faites jusque là. Comme de très nombreuses personnes sont passées par là avant
nous, il existe un grand nombre de solutions pour gérer les configurations d'un
parc de machines. Parmi les plus connues, citons :
[Puppet](https://puppet.com/), [Chef](https://www.chef.io/),
[SaltStack](https://saltstack.com/) ou encore
2018-03-07 03:14:36 +00:00
[Ansible](https://www.ansible.com/).
2018-02-26 08:05:38 +00:00
Introduction à Ansible
----------------------
2018-03-07 03:14:36 +00:00
Ansible est une solution de gestion de configuration. Basé sur
[Python](https://www.python.org/) et
2023-05-05 07:57:22 +00:00
[YAML](https://www.yaml.org/spec/1.2/spec.html), sa principale particularité est
2022-05-12 01:14:29 +00:00
de ne pas nécessiter de daemon sur les machines qu'il va gérer : tout se fait
2018-03-07 03:14:36 +00:00
exclusivement via SSH, à partir de la machine d'un administrateur, possédant
2021-03-05 14:03:10 +00:00
Ansible (ou bien d'un système de gestion de configuration tel qu'[Ansible
2023-05-05 07:57:22 +00:00
Tower](https://www.ansible.com/products/tower),
[AWX](https://github.com/ansible/awx) ou [Semaphore](https://github.com/ansible-semaphore/semaphore/)).
2018-03-07 03:14:36 +00:00
Son installation est très simple, car les dépendances sont minimes et l'outil
2019-03-12 12:02:39 +00:00
n'a pas besoin de base de données pour fonctionner : tout va se faire à partir
2018-03-07 03:14:36 +00:00
d'une arborescence de fichiers, qui sera gérée par Git.
2018-02-26 08:05:38 +00:00
2023-05-05 07:57:22 +00:00
Commençons par installer Ansible, sur une machine distincte de la machine
virtuelle démarrée : la machine hôte sera parfaitement adaptée à cette
tâche. Mais vous pouvez aussi choisir de l'installer dans une seconde machine
virtuelle.
2018-03-07 03:14:36 +00:00
2023-05-05 07:57:22 +00:00
[Consultez la procédure d'installation pour votre distribution ici](https://docs.ansible.com/ansible/latest/intro_installation.html).
2018-03-07 03:14:36 +00:00
Résultat attendu
2018-02-26 08:05:38 +00:00
----------------
2023-05-05 07:57:22 +00:00
À la fin de ce TP, vous devez être en mesure de déployer une nouvelle
machine, identique à celle que vous avez configurée, à partir d'une ISO et
2022-03-08 20:17:08 +00:00
d'un nouveau disque.
2018-03-07 03:14:36 +00:00
2023-05-05 07:57:22 +00:00
Vous devrez pour cela réaliser des *playbooks* :
- `basis.yml`, accompagné de toutes ses dépendances : celui-ci doit faire les
configurations **minimales** du système et des utilisateurs (le seul
*playbook* qui se connecte à l'utilisateur `root` directement, retirez le
maximum de chose de ce *playbook* qui pourraient aller dans le suivant).
2019-03-15 11:59:55 +00:00
2023-05-05 07:57:22 +00:00
- `securing.yml`, accompagné de toutes ses dépendances : ce *playbook* doit
s'occuper de toute l'installation de la machine pour qu'elle soit aussi
sécurisée que possible.
- `vitrine.yml` et ses dépendances : celui-ci
doit permettre de déployer une page vitrine typique d'une entreprise. Cette
page doit être accessible depuis votre domaine
<https://login-x.adlin2024.example.tld/>.
- `name-server.yml` et ses dépendances : celui-ci doit mettre en place le
serveur DNS faisant autorité sur le domaine qui vous est concédé.
2022-03-08 20:17:08 +00:00
::::: {.warning}
2022-03-09 09:35:37 +00:00
Il est attendu que la vitrine soit générée avec
[Hugo](https://gohugo.io/), sur la [machine exécutant
Ansible](https://docs.ansible.com/ansible/latest/user_guide/playbooks_delegation.html#delegating-tasks)
(et non pas sur la machine où elle sera hébergée).
Pour réaliser cela, vous êtes autorisé à faire usage du module
[`ansible.builtin.command`](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/command_module.html#ansible-collections-ansible-builtin-command-module).
2022-03-08 20:17:08 +00:00
:::::
2018-03-07 03:14:36 +00:00
2022-05-12 01:14:29 +00:00
Ma première commande
--------------------
2018-03-07 03:14:36 +00:00
### Inventaire
Comme il sera bientôt question de maintenir plusieurs machines, la première
chose à faire consiste à les inventorier.
Commençons par créer un répertoire, qui contiendra l'ensemble de nos
configurations à destination d'Ansible. Dans ce répertoire, créons un fichier
2019-03-12 12:02:39 +00:00
d'inventaires `hosts`, contenant l'IP de notre machine :
2018-03-07 03:14:36 +00:00
<div lang="en-US">
```yaml
all:
hosts:
192.168.0.106
```
</div>
2022-03-08 20:17:08 +00:00
::::: {.more}
2018-03-07 03:14:36 +00:00
Vous pouvez lancer une autre machine virtuelle à ce stade et ajouter son IP sur
une nouvelle ligne du fichier `hosts`.
2022-03-08 20:17:08 +00:00
:::::
2018-03-07 03:14:36 +00:00
Plus tard, c'est dans ce fichier que vous pourrez créer des groupes de machines
2022-03-08 20:17:08 +00:00
(pour, par exemple, regrouper les serveurs web, les bases de données, etc.) ou
2018-03-07 03:14:36 +00:00
donner des caractéristiques spécifiques à vos machines.
2023-05-05 07:57:22 +00:00
[Plus d'infos sur le fichier d'inventaire par ici.](https://docs.ansible.com/ansible/latest/inventory_guide/index.html)
2018-03-07 03:14:36 +00:00
### `ping`
2023-05-05 07:57:22 +00:00
Lançons maintenant la commande suivante :
2018-03-07 03:14:36 +00:00
<div lang="en-US">
```
2023-05-05 07:57:22 +00:00
42sh$ ansible --inventory-file hosts all --user root --module-name ping
192.168.0.106 | SUCCESS => {
"changed": false,
"ping": "pong"
}
2018-03-07 03:14:36 +00:00
```
</div>
2023-05-05 07:57:22 +00:00
Nous demandons avec cette commande, qu'`ansible` utilise le fichier d'inventaire `hosts` ; `all` est un filtre qui nous permet d'indiquer les machines auxquelles on souhaite se restreindre (`all`, on va lancer le module sur toutes les machines de l'inventaire). L'option `--user` précise le nom de l'utilisateur que l'on veut utiliser pour la connexion SSH. Enfin nous avons les options du modules, et cela commence par le nom du module lui-même.
2018-03-07 03:14:36 +00:00
Vous devriez avoir un retour similaire à celui-ci, indiquant simplement que la
2023-05-05 07:57:22 +00:00
connexion a bien été effectuée et que le nécessaire pour utiliser Ansible est
bien installé sur la machine distance.\
2022-03-08 20:17:08 +00:00
::::: {.question}
2018-03-07 03:14:36 +00:00
2021-03-05 14:03:10 +00:00
Si votre clef SSH n'a pas été récupérée depuis le CRI, vous pouvez rajouter
l'option `--ask-pass`, afin de pouvoir indiquer le mot de passe de connexion au
2022-03-08 20:17:08 +00:00
compte, sinon vous obtiendrez une erreur plutôt incompréhensible.
2021-03-05 14:03:10 +00:00
2022-03-08 20:17:08 +00:00
:::::
2018-03-07 03:14:36 +00:00
2023-05-05 07:57:22 +00:00
En ajoutant une seconde machine dans notre inventaire, nous aurions quelque chose comme cela :
<div lang="en-US">
```
42sh$ ansible --inventory-file hosts all --user root --module-name ping
192.168.0.106 | SUCCESS => {
"changed": false,
"ping": "pong"
}
192.168.0.142 | SUCCESS => {
"changed": false,
"ping": "pong"
}
```
</div>
2018-03-07 03:14:36 +00:00
### Confort
2023-05-05 07:57:22 +00:00
Une des premières choses que nous devrions faire, est de nous créer un
utilisateur puis d'installer notre clef SSH sur les machines, pour éviter
d'avoir à taper le mot de passe à chaque test.
Pour créer notre utilisateur, nous pouvons utiliser le [module `user`](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/user_module.html) :
<div lang="en-US">
```
42sh$ ansible --inventory-file hosts all --user root --module-name user --args "name=login_x uid=1042"
192.168.0.106 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"append": false,
"changed": true,
"comment": "",
"group": 1001,
"home": "/home/login_x",
"move_home": false,
"name": "login_x",
"shell": "/bin/sh",
"state": "present",
"uid": 1042
}
```
</div>
On voit que le retour de notre commande est bien différent (ne serait-ce que la
couleur du texte !). En particulier, on peut constater sur la première ligne de
sortie standard `CHANGED` au lieu de `SUCCESS` précédemment.
En fait notre utilisateur vient d'être créé, Ansible rapporte qu'il a apporté
une modification au système cible. Si l'on renvoie exactement la même commande :
<div lang="en-US">
```
42sh$ ansible --inventory-file hosts all --user root --module-name user --args "name=login_x uid=1042"
192.168.0.106 | SUCCESS => {
[...]
```
</div>
Aucun changement, l'utilisateur est déjà créé et possède ces attributs, Ansible
rapporte qu'il n'y a pas eu d'altération.\
2018-03-07 03:14:36 +00:00
2023-05-05 07:57:22 +00:00
Maintenant que notre utilisateur est créé, nous pouvons lui ajouter notre clef SSH. Pour cela, on utilisera le [module `authorized_key`](https://docs.ansible.com/ansible/latest/collections/ansible/posix/authorized_key_module.html) :
<div lang="en-US">
```
ansible --inventory-file hosts all --module-name authorized_key --args "user=login_x key='ssh-ed25519 AAAAC3NzaC1lZD...FD'"
```
</div>
Notez que j'ai maintenant arrêté d'utilisé l'option `--user root`, puisque je peux me connecter avec l'utilisateur que j'ai créé. Sans préciser, le nom d'utilisateur de votre compte est utilisé pour établir les connexions.
::::: {.exercice}
Pour continuer, il va nous falloir installer `sudo` (en `root`), ajouter notre utilisateur au groupe `sudo` et éventuellement configurer le fichier `sudoers` pour accepter que l'on puisse lancer des commandes sans utiliser de mot de passe.
Vous pourrez faire tout cela avec les modules :
- [`apt`](https://docs.ansible.com/ansible/2.10/collections/ansible/builtin/apt_module.html)
- [`user`](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/user_module.html)
- [`lineinfile`](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/lineinfile_module.html)
:::::
## Utiliser `sudo`
Après avoir installé et configuré `sudo`, voyons comment l'utiliser avec Ansible.
Ansible peut utiliser différents programme pour élever ses privilèges (pas seulement `sudo`) ; il va détecter de lui-même quel programme utiliser.
Il faudra cependant lui indiquer que l'on souhaite qu'il élève ses privilège
juste après la connexion en tant que simple utilisateur. On ajoute pour cela
les options `--become` et `--ask-become-pass` (utilisez `--sudo` et
2019-03-12 12:02:39 +00:00
`--ask-sudo-pass` pour les vieilles versions) :
2018-03-07 03:14:36 +00:00
<div lang="en-US">
```bash
2023-05-05 07:57:22 +00:00
ansible --inventory-file hosts all -m ping -u login_x --become --ask-become-pass
2018-03-07 03:14:36 +00:00
```
</div>
2023-05-05 07:57:22 +00:00
L'option `--ask-become-pass` vous demandera systématiquement le mot de passe *sudoer*, avant d'établir la connexion.
Maintenant que l'on sait se connecter proprement, essayons d'en apprendre un peu plus sur les modules.
2018-03-07 03:14:36 +00:00
### Les modules
2019-03-12 12:02:39 +00:00
Les modules Ansible ont généralement deux missions distinctes :
2018-03-07 03:14:36 +00:00
2019-03-12 12:02:39 +00:00
- récupérer des informations en allant les extraire sur chaque machine ;
2018-03-07 03:14:36 +00:00
- pousser des nouvelles informations pour atteindre un état précis.
Lorsque les informations récupérées montrent que l'état est déjà atteint,
aucune modification n'est faite sur la machine, on parle d'idempotence.
À noter cependant que lorsque l'on retire un état de notre recette, il est
conservé tel quel sur la machine. Par exemple, si un utilisateur `toto` est
2019-03-12 12:02:39 +00:00
créé suite à l'application d'une recette décrivant l'utilisateur `toto` ; si
2018-03-07 03:14:36 +00:00
l'on supprime la description, l'utilisateur ne sera pas supprimé des machines
sur lequel la recette aura été appliquée. Pour qu'il soit supprimé, il faut
modifier la description pour signaler que cet utilisateur ne doit pas
exister. À l'application de cette nouvelle recette, si un tel utilisateur est
trouvé, il sera donc supprimé.
2023-05-05 07:57:22 +00:00
### Collecte d'informations
2018-03-07 03:14:36 +00:00
Parmi les modules de base, le module `setup` permet de récupérer un grand
2019-03-12 12:02:39 +00:00
nombre de caractéristiques de la machine distance, voyez plutôt :
2018-03-07 03:14:36 +00:00
<div lang="en-US">
```bash
ansible -i hosts all -m setup
2018-03-07 03:14:36 +00:00
```
</div>
2022-05-12 01:14:29 +00:00
Les informations récupérées (quel que soit le module), sont ensuite accessibles
2018-03-07 03:14:36 +00:00
dans des variables afin de permettre de compléter des modèles ou pour faire de
l'exécution conditionnelle d'état (par exemple on pourra utiliser la variable
2023-05-05 07:57:22 +00:00
`ansible_distribution` pour distinguer les gestionnaires de paquets à utiliser
selon la distribution de la machine).
2018-03-07 03:14:36 +00:00
Ma première recette
-------------------
Un livre de recettes (*playbook* dans le vocabulaire d'Ansible), regroupe les
descriptions des états que l'on souhaite obtenir sur un groupe de machines données.
2019-03-12 12:02:39 +00:00
Par exemple, voici à quoi pourrait ressembler un tel recueil :
2018-03-07 03:14:36 +00:00
<div lang="en-US">
```yaml
---
- hosts: webservers
remote_user: root
tasks:
- name: ensure nginx is at the latest version
apt: name=nginx state=latest
- name: write the apache config file
template: src=/srv/httpd.j2 dest=/etc/httpd.conf
- hosts: databases
remote_user: root
tasks:
- name: ensure postgresql is at the latest version
yum: name=postgresql state=latest
2020-03-27 12:13:10 +00:00
- name: ensure that mysql is started
service: name=mysqld state=started
2018-03-07 03:14:36 +00:00
```
</div>
On filtre d'abord sur les hôtes concernés par la configuration, on donne
ensuite des paramètres qui seront utilisées pour chaque tâche et enfin on
décrit les tâches.
Le guide de référence pour connaître toutes les syntaxes possibles est
disponible dans [la documentation
2023-05-05 07:57:22 +00:00
d'Ansible](https://docs.ansible.com/ansible/latest/playbooks.html).
2018-03-07 03:14:36 +00:00
### Exécution d'un *Playbook*
Placer le contenu dans un fichier YAML, par exemple `playbook.yml`, non loin du
2022-05-12 01:14:29 +00:00
fichier `hosts` créé à la section précédente, puis lancez :
2018-03-07 03:14:36 +00:00
<div lang="en-US">
```bash
ansible-playbook -i hosts playbook.yml
2018-03-07 03:14:36 +00:00
```
</div>
### Coup de pouce
Voici à quoi ressemblerait votre premier playbook créant l'utilisateur
2019-03-12 12:02:39 +00:00
`adeline` :
2018-03-07 03:14:36 +00:00
<div lang="en-US">
```yaml
---
- hosts: all
remote_user: root
tasks:
- name: ensure adeline as an account
2023-05-05 07:57:22 +00:00
ansible.builtin.user:
2018-03-07 03:14:36 +00:00
name: adeline
state: present
shell: /bin/fish
groups: sudo
append: yes
```
</div>
2023-05-05 07:57:22 +00:00
::::: {.exercice}
2018-03-07 03:14:36 +00:00
2022-03-08 20:17:08 +00:00
À vous maintenant, à l'aide [des collections de modules à votre
disposition](https://docs.ansible.com/ansible/latest/collections/index.html)
2018-03-07 03:14:36 +00:00
de copier vos fichiers de configuration à leur place et avec les bons droits
(`authorized_keys`, `.bashrc`, ...). Installez également les paquets que vous
2022-05-12 01:14:29 +00:00
aviez installés à la main (client DHCP, `htop`, ...).
2023-05-05 07:57:22 +00:00
:::::
Les notifications
-----------------
Au sein d'un *playbook*, il est possible de définir des tâches particulières
(nommées *handlers* dans le vocabulaire Ansible), qui ne seront exécutées que
2022-05-12 01:14:29 +00:00
si un changement a eu lieu. Par exemple, nous pourrions déclarer une tâche de
redémarrage du serveur web, seulement lorsque sa configuration est mise à
jour.
2019-03-12 12:02:39 +00:00
Pour se faire, il faut ajouter un élément `notify` à sa tâche :
<div lang="en-US">
```yaml
- name: template configuration file
template: src=template.j2 dest=/etc/httpd.conf
notify:
- restart cherokee
```
</div>
2019-03-12 12:02:39 +00:00
Puis, au même niveau que les *tasks*, on déclare nos *handlers* :
<div lang="en-US">
```yaml
handlers:
- name: restart cherokee
service: name=cherokee state=restarted
```
</div>
Il est attendu que le nom de la notification corresponde à l'attribut `name`
d'un handler.
Voir [la
2022-03-08 20:17:08 +00:00
documentation](https://docs.ansible.com/ansible/latest/user_guide/playbooks_handlers.html)
pour davantage de détails et d'exemples.
2023-05-05 07:57:22 +00:00
::::: {.exercice}
La configuration de votre serveur SSH laisse à désirer. Corriger les problèmes
2023-05-05 07:57:22 +00:00
énoncés par ces guides et articles :
2021-03-05 14:03:10 +00:00
- <https://linuxhandbook.com/ssh-hardening-tips/> ;
2022-03-08 20:17:08 +00:00
- <https://www.ssi.gouv.fr/guide/recommandations-pour-un-usage-securise-dopenssh/> ;
- <https://stribika.github.io/2015/01/04/secure-secure-shell.html>.
2023-05-05 07:57:22 +00:00
Pour modifier un fichier de configuration, on utilise généralement le module [`ansible.builtin.lineinfile`](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/lineinfile_module.html).
Puis, mettez en place un *handler* pour relancer votre serveur SSH en cas de
modification de sa configuration.
2023-05-05 07:57:22 +00:00
:::::
Les variables
-------------
2019-03-12 12:02:39 +00:00
Les variables peuvent provenir de très nombreux emplacements : définies
spécifiquement pour l'hôte, chargées depuis des fichiers lié au groupe auquel
appartient la machine, liées à la recette ou à la tâche en cours d'exécution,
récupérée par un module (comme le module `setup`).
### Définition
#### Dans l'inventaire
<div lang="en-US">
```yaml
all:
hosts:
192.168.0.106
vars:
ntp_server: ntp.epitech.eu
```
</div>
Cet inventaire définit une variable `ntp_server` qui pourra ensuite être
utilisée par une recette installant un serveur NTP. Le cadre d'utilisation de
ce genre de variable est adapté lorsqu'un hôte ou un groupe d'hôte seulement
partagent des caractéristiques, comme par exemple leur localisation ou
datacenter de rattachement, etc.
#### Dans un *playbook*
De la même manière que dans l'inventaire, il est possible de définir des
2019-03-12 12:02:39 +00:00
variables dans une recette :
<div lang="en-US">
```yaml
- hosts: webservers
vars:
http_port: 80
```
</div>
2019-03-12 12:02:39 +00:00
Ces variables peuvent être placées dans un fichier, pour plus de lisibilité :
<div lang="en-US">
```yaml
- hosts: webservers
vars_files:
2023-05-05 07:57:22 +00:00
- vars/webservers_common.yml
```
</div>
2019-03-12 12:02:39 +00:00
Le format correspond au sous arbre que l'on pourrait trouver sous `vars` :
<div lang="en-US">
```yaml
http_port: 80
https_port: 443
```
</div>
### Utilisation
#### Modèles
Ces variables peuvent être utilisées pour remplir un emplacement de texte dans
2023-05-05 07:57:22 +00:00
un modèle. Ansible utilise [Jinja2](https://jinja.pocoo.org/) comme moteur de
template, un format très courant dans le milieu du développement Python.
<div lang="en-US">
```
I'm running {{ ansible_system }} {{ ansible_os_family }} on {{ ansible_system_vendor }},
with {{ ansible_memory_mb.swap.free }} MB Swap available.
```
</div>
Ce format est relativement générique. Ainsi, pour accéder aux éléments d'une
2019-03-12 12:02:39 +00:00
table de hash/dictionnaire, on utilise le caractère `.` ; les crochets `[x]`
sont utilisés principalement pour accéder aux éléments des tableaux :
<div lang="en-US">
```
My first nameserver is: {{ ansible_dns.nameservers[0] }}.
```
</div>
2019-03-12 12:02:39 +00:00
ou d'un dictionnaire lorsque la clef doit être récupérée d'une variable :
<div lang="en-US">
```
My disks are: {% for disk in ansible_device_links.uuids %}
- {{ disk }}: {{ ansible_device_links.uuids[disk] }}, {{ ansible_device_links.ids[disk]|join(', ') }}
{% endfor %}
```
</div>
Notez dans ce dernier exemple l'utilisation d'un filtre `join` permettant
d'aplatir la liste retournée.
#### Dans les recettes
Au sein même de votre description YAML, vous pouvez utiliser la puissance de
2019-03-12 12:02:39 +00:00
Jinja2 :
<div lang="en-US">
```yaml
- hosts: webservers
vars_files:
2023-05-05 07:57:22 +00:00
- vars/webservers_common.yml
- vars/webservers_{{ ansible_os_family }}.yml
```
</div>
Dans cet exemple, les variables des fichiers `/vars/webservers_common.yml`,
puis celles de `/vars/webservers_Debian.yml` seront chargées dans le contexte.
#### Conditions
Une autre utilisation possible des variables est d'appliquer ou de passer des
2019-03-12 12:02:39 +00:00
tâches. Par exemple :
<div lang="en-US">
```yaml
tasks:
- name: "pkg setup (Debian)"
apt: name=chrony state=present
when: ansible_os_family == "Debian"
- name: "pkg setup (CentOS)"
yum: name=chrony state=present
when: ansible_os_family == "CentOS"
```
</div>
Vous trouverez bien d'autres cas d'utilisation dans [la
2023-05-05 07:57:22 +00:00
documentation](https://docs.ansible.com/ansible/latest/playbooks_conditionals.html).