Cibles hétérogènes
Objectif de cet atelier pratique
-
Gérer des Target Hosts hétérogènes
-
Prendre en compte les différences entre les systèmes
Démarrer le labo
Placez-vous dans le répertoire du dix-septième atelier pratique :
Voici les cinq machines virtuelles de cet atelier :
| Machine virtuelle | Adresse IP |
|---|---|
ansible |
192.168.56.10 |
rocky |
192.168.56.20 |
debian |
192.168.56.30 |
suse |
192.168.56.40 |
ubuntu |
192.168.56.50 |
Démarrez les VM :
Connectez-vous au Control Host :
L'environnement de cet atelier est préconfiguré et prêt à l'emploi :
-
Ansible est installé sur le Control Host.
-
Le fichier
/etc/hostsdu Control Host est correctement renseigné. -
L'authentification par clé SSH est établie sur les trois Target Hosts.
-
Le répertoire du projet existe et contient une configuration de base et un inventaire.
-
Direnv est installé et activé pour le projet.
-
Le validateur de syntaxe
yamllintest également disponible.
Rendez-vous dans le répertoire des playbooks :
$ cd ansible/projets/ema/playbooks/
direnv: loading ~/ansible/projets/ema/.envrc
direnv: export +ANSIBLE_CONFIG
Exécution conditionnelle
Les tâches (tasks) d'un playbook peuvent être liées à une condition,
c'est-à-dire qu'elles s'exécutent uniquement lorsqu'un ou plusieurs prérequis
sont remplis. Pour ce faire, on utilise le paramètre when en combinaison avec
des facts ou d'autres variables.
Un exemple pratique vous permettra tout de suite de mieux comprendre. Voici un playbook qui affiche uniquement les hôtes qui tournent sur un système basé sur Debian :
--- # hello-debian.yml
- hosts: all
tasks:
- name: Check if target host is a Debian-based system
debug:
msg: "This is a Debian-based system."
when: ansible_os_family == "Debian"
...
Voilà le résultat :
TASK [Check if target host is a Debian-based system] *************************
skipping: [rocky]
ok: [debian] => {
"msg": "This is a Debian-based system."
}
skipping: [suse]
ok: [ubuntu] => {
"msg": "This is a Debian-based system."
}
Dans l'exemple ci-dessous, on utilise l'opérateur not pour inverser la
condition :
--- # hello-non-debian.yml
- hosts: all
tasks:
- name: Check if target host is not a Debian-based system
debug:
msg: "This is not a Debian-based system."
when: ansible_os_family != "Debian"
...
L'opérateur in permet de vérifier l'occurrence dans une liste :
--- # hello-others.yml
- hosts: all
tasks:
- name: Check if target host is running Rocky or SUSE
debug:
msg: "This is either Rocky or SUSE."
when: ansible_distribution in ["Rocky", "openSUSE Leap"]
...
Il est parfois nécessaire de vérifier la version d'un système ou d'un composant
logiciel. Ansible offre un test version pour ce cas de figure. Dans l'exemple
ci-dessous, on utilise également l'opérateur and pour combiner deux
conditions :
--- # system-check.yml
- hosts: all
tasks:
- name: Check if we're running Ubuntu 22.04 or higher
debug:
msg: We're running Ubuntu 22.04 or higher
when: ansible_distribution == "Ubuntu"
and
ansible_distribution_version is version('22.04', '>=')
...
Gérer les cibles hétérogènes
Maintenant que nous savons utiliser les conditions, nous avons tous les outils nécessaires pour gérer une installation sur des cibles qui n'utilisent pas la même distribution.
Concrètement, nous allons reprendre notre installation Apache et l'étendre sur l'ensemble de nos Target Hosts. Le premier problème à résoudre, c'est que le paquet Apache n'a pas le même nom selon la distribution que vous utilisez.
Les gros sabots
Voici une première idée pas très subtile pour différencier vos Target Hosts :
--- # apache-01.yml
- hosts: all
tasks:
- name: Update package information on Debian/Ubuntu
apt:
update_cache: true
cache_valid_time: 3600
when: ansible_os_family == "Debian"
- name: Install Apache on Debian/Ubuntu
apt:
name: apache2
when: ansible_os_family == "Debian"
- name: Install Apache on Rocky Linux
dnf:
name: httpd
when: ansible_distribution == "Rocky"
- name: Install Apache on SUSE Linux
zypper:
name: apache2
when: ansible_distribution == "openSUSE Leap"
...
Cette méthode fonctionne dans la mesure où une poule est capable de voler et un cheval de nager. Exécutez le playbook, et vous verrez que les paquets s'installent certes comme prévu. Il n'empêche qu'on a sorti les gros sabots et que l'approche est peu élégante, puisqu'il faudrait opérer la distinction pour chacune des tâches :
-
Le nom du paquet Apache est différent.
-
Le service correspondant n'a pas le même nom.
-
La page web par défaut n'a pas le même emplacement.
-
Le fichier de configuration n'est pas rangé au même endroit non plus.
Conclusion : on laisse tomber et on cherche une autre solution.
Un peu plus subtil
Voici un playbook revu et amélioré avec une approche un peu plus subtile :
--- # apache-02.yml
- hosts: all
tasks:
- name: Parameters for Debian/Ubuntu
set_fact:
apache_package_name: apache2
when: ansible_os_family == "Debian"
- name: Parameters for Rocky Linux
set_fact:
apache_package_name: httpd
when: ansible_distribution == "Rocky"
- name: Parameters for SUSE Linux
set_fact:
apache_package_name: apache2
when: ansible_distribution == "openSUSE Leap"
- name: Update package information on Debian/Ubuntu
apt:
update_cache: true
cache_valid_time: 3600
when: ansible_os_family == "Debian"
- name: Install Apache
package:
name: "{{apache_package_name}}"
...
Dans un premier temps, on définit systématiquement une variable qui contient le
nom du paquet Apache. Ensuite, il ne reste plus qu'à installer le paquet grâce
au module générique package.
À première vue, on se complique un peu la tâche ici. Mais une fois que vous avez compris que les trois premières tâches nous permettent de définir toutes les propriétés spécifiques des cibles, vous commencez à comprendre qu'on est peut-être sur une bonne piste.
On va donc poursuivre cette idée et l'étendre sur les deux premières tâches à exécuter :
--- # apache-03.yml
- hosts: all
tasks:
- name: Parameters for Debian/Ubuntu
set_fact:
apache_package_name: apache2
apache_service_name: apache2
when: ansible_os_family == "Debian"
- name: Parameters for Rocky Linux
set_fact:
apache_package_name: httpd
apache_service_name: httpd
when: ansible_distribution == "Rocky"
- name: Parameters for SUSE Linux
set_fact:
apache_package_name: apache2
apache_service_name: apache2
when: ansible_distribution == "openSUSE Leap"
- name: Update package information on Debian/Ubuntu
apt:
update_cache: true
cache_valid_time: 3600
when: ansible_os_family == "Debian"
- name: Install Apache
package:
name: "{{apache_package_name}}"
- name: Start Apache & enable it on boot
service:
name: "{{apache_service_name}}"
state: started
enabled: true
...
Beaucoup plus subtil
Différents chemins mènent à Saint-Bauzille-de-Putois selon un adage un peu moins connu. Voici une approche encore plus subtile pour installer et lancer Apache sur des cibles hétérogènes :
--- # apache-04.yml
- hosts: all
vars:
apache:
Debian:
package_name: apache2
service_name: apache2
Ubuntu:
package_name: apache2
service_name: apache2
Rocky:
package_name: httpd
service_name: httpd
openSUSE Leap:
package_name: apache2
service_name: apache2
tasks:
- name: Update package information on Debian/Ubuntu
apt:
update_cache: true
cache_valid_time: 3600
when: ansible_os_family == "Debian"
- name: Install Apache
package:
name: "{{apache[ansible_distribution].package_name}}"
- name: Start Apache & enable it on boot
service:
name: "{{apache[ansible_distribution].service_name}}"
state: started
enabled: true
...
Ici on met en place une structure de données dans laquelle les valeurs sont disponibles en utilisant le nom de la distribution comme clé.
C'est déjà plus élégant comme approche, et vous noterez au passage qu'elle
permet de se passer des distinctions de cas de figure avec when (sauf pour le
rafraîchissement des paquets sous Debian/Ubuntu que le module générique
package ne gère pas).
Carrément plus subtil
Enfin, une dernière possibilité consiste en une approche modulaire qui se distingue par une certaine élégance, même si on se complique la vie un petit peu :
--- # apache-05.yml
- hosts: all
tasks:
- include_vars: >
apache01_{{ansible_distribution|lower|replace(" ", "-")}}.yml
- name: Update package information on Debian/Ubuntu
apt:
update_cache: true
cache_valid_time: 3600
when: ansible_os_family == "Debian"
- name: Install Apache
package:
name: "{{apache_package_name}}"
- name: Start Apache & enable it on boot
service:
name: "{{apache_service_name}}"
state: started
enabled: true
...
Ici, les paramètres spécifiques à la distribution sont enregistrés dans un
fichier externe apache01_<DISTRIBUTION>.yml. On se retrouve donc avec quatre
fichiers supplémentaires :
-
apache01_debian.yml -
apache01_rocky.yml -
apache01_opensuse-leap.yml -
apache01_ubuntu.yml
La directive include_vars va rechercher ces fichiers dans le répertoire du
playbook ou dans un sous-répertoire vars/ à côté du playbook. J'ai préféré
cette deuxième variante pour plus de lisibilité.
-
Debian :
-
OpenSUSE Leap :
-
Rocky :
-
Ubuntu
Ubuntu vs. Debian
Notez en passant qu'on aurait pu remplacer le fichier apache_ubuntu.yml
par un simple lien symbolique vers apache_debian.yml, étant donné que
sous le capot, Ubuntu est essentiellement un système Debian.
Le playbook complet
Nos compétences nouvellement acquises nous permettent à présent d'effectuer une installation complète d'Apache sur toutes nos cibles :
--- # apache-06.yml
- hosts: all
tasks:
- name: Load distribution-specific parameters
include_vars: >
apache02_{{ansible_distribution|lower|replace(" ", "-") }}.yml
- name: Update package information on Debian/Ubuntu
apt:
update_cache: true
cache_valid_time: 3600
when: ansible_os_family == "Debian"
- name: Install Apache
package:
name: "{{apache_package_name}}"
- name: Start Apache & enable it on boot
service:
name: "{{apache_service_name}}"
state: started
enabled: true
- name: Install custom web page
copy:
dest: "{{apache_document_root}}/index.html"
mode: 0644
content: |
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Test</title>
</head>
<body>
<h1>My Ansible-managed website</h1>
</body>
</html>
- name: Configure redirect
copy:
dest: "{{apache_config_directory}}/redirect.conf"
content: |
Redirect /start http://www.startpage.com
notify: Reload Apache
- name: Activate redirect on Debian/Ubuntu
command:
cmd: a2enconf redirect
creates: /etc/apache2/conf-enabled/redirect.conf
when: ansible_os_family == "Debian"
handlers:
- name: Reload Apache
service:
name: "{{apache_service_name}}"
state: reloaded
...
Et voici la liste complète des fichiers de variables externes.
-
Debian :
-
OpenSUSE Leap :
-
Rocky :
-
Ubuntu :
À vous de jouer !
Écrivez successivement deux playbooks pour mettre en place la synchronisation NTP avec Chrony sur vos quatre Target Hosts sous Debian, Rocky Linux, SUSE Linux et Ubuntu. Il vous faudra identifier le nom du paquet, le service correspondant et le chemin vers le fichier de configuration par défaut pour chacune des distributions.
Voici la configuration qu'il faudra installer sur chacune des quatre cibles :
# chrony.conf
server 0.fr.pool.ntp.org iburst
server 1.fr.pool.ntp.org iburst
server 2.fr.pool.ntp.org iburst
server 3.fr.pool.ntp.org iburst
driftfile /var/lib/chrony/drift
makestep 1.0 3
rtcsync
logdir /var/log/chrony
-
Le premier playbook
chrony-01.ymlutilisera les modules de gestion de paquets natifsapt,dnfetzypperet s'inspirera de la méthode « gros sabots » utilisée plus haut dans cet atelier. -
Le deuxième playbook
chrony-02.ymldéfinira trois variableschrony_package,chrony_serviceetchrony_confdiret utilisera le module de gestion de paquets génériquepackage.
Clin d'œil
Vous mettez en place un service et une configuration personnalisée. Vous
aurez donc forcément un handler avec un notify correspondant quelque
part dans vos deux playbooks.
La rédaction de cette documentation demande du temps et des quantités significatives de café espresso. Vous appréciez ce site ? Offrez un café au rédacteur en cliquant sur la tasse.

