Tools/Ansible/Use Cases
Sommaire
- 1 Include des variables depuis un fichier Ansible Vault
- 2 Inclure des "tasks"
- 3 Utilisation de import_playbook & fichiers YAML dynamiques
- 4 Utilisation de sections pre_tasks et post_tasks
- 5 Transformation de données
- 5.1 Types de variables ansible
- 5.2 Utilisation des filtres
- 5.3 Principaux filtres
Include des variables depuis un fichier Ansible Vault
Utilisation de plusieurs "tasks"
Ex, le fichier de variables dépend de l’environnement (prod,dev, qual etc ...)
---
# ./configure_app_servers.yml
- name: configure app servers
hosts: app_servers
pre_tasks:
- name: include env vars
include_vars: "{{ env }}.yml"
tags: ["always"]
- name: include vault for env
include_vars: "{{ env }}.vault.yml"
tags: ["always"]
roles:
...
Vous pouvez régler env
en passant l'option --extra-vars
à ansible-playbook
:
$ ansible-playbook configure_app_servers.yml --extra-vars "env=prod"
Exemples de fichier de param :
---
# ./vars/prod.yml
domain: example.com
rails_env: production
https: true
Le fichier prod.vault.yml
peut ressembler à ceci (en texte brut) :
$ANSIBLE_VAULT;1.1;AES256
3462353065386462383165626...
...
Mais à ceci si on utilise la commande ansible-vault view ./vars/prod.vault.yml
:
---
# ./vars/prod.vault.yml
database_password: "94BqEabtebgzbQItkqPEVMyqjKbp57Gc"
api_key: "xVQKRARKgcwDktPCJjYRFiqmHGsvNFd9"
Utilisation d'une boucle
L'exemple précédent peut être plus clair et concis si on utilise une boucle avec des include_vars
plutôt que deux tâches séparées :
---
# ./configure_app_servers.yml
- name: configure app servers
hosts: app_servers
pre_tasks:
- name: include vars and vault for env
include_vars: "{{ item }}.yml"
tags: ["always"]
loop:
- "{{ env }}"
- "{{ env }}.vault"
roles:
...
Inclure des "tasks"
gestion de boucles
Cette technique est utile chaque fois que vous devez exécuter plusieurs tâches pour chaque élément d'une boucle.
exemple :
---
# ./greet_tasks.yml
- name: set greeting fact
set_fact:
greeting: "Greeting {{ index }}: Hello, {{ name }}!"
- name: print greeting fact
debug: var=greeting
Vous pouvez exécuter ces tâches dans une bloucle comme ceci :
# ./greet.yml
- name: greet people
hosts: "*"
tasks:
- include_tasks: greet_tasks.yml
loop:
- World
- Percy
loop_control:
loop_var: name
index_var: index
Ce qui donne à l'exécution du playbook :
TASK [set greeting fact]
ok: [123.123.123.123]
TASK [print greeting fact]
ok: [123.123.123.123] => {
"greeting": "Greeting 0: Hello, World!"
}
TASK [set greeting fact]
ok: [123.123.123.123]
TASK [print greeting fact]
ok: [123.123.123.123] => {
"greeting": "Greeting 1: Hello, Percy!"
}
Inclure des "tasks" à partir de fichier dynamiques
L'inclusion de tâches avec un nom de fichier dynamique est le plus souvent utilisée pour faire varier les tâches en fonction du système d'exploitation de l'hôte distant.
Prenons l'exemple suivant :
# ./roles/redis/tasks/main.yml
- name: install redis on Debian based distros
apt:
name: redis-server
state: present
update_cache: true
become: true
when: ansible_os_family == 'Debian'
- name: ensure epel-release repo is installed on RHEL based distros
yum:
name: epel-release
state: present
update_cache: true
become: true
when: ansible_os_family == 'RedHat'
- name: install redis on RHEL based distros
yum:
name: redis
state: present
update_cache: true
become: true
when: ansible_os_family == 'RedHat'
Il peut être avantageusement factorisé en 3 tâches distinctes de la façon suivante :
---
# ./roles/redis/tasks/setup-Debian.yml
- name: install redis on Debian based distros
apt:
name: redis-server
state: present
update_cache: true
become: true
when: ansible_os_family == 'Debian'
---
# ./roles/redis/tasks/setup-RedHat.yml
- name: ensure epel-release repo is installed on RHEL based distros
yum:
name: epel-release
state: present
update_cache: true
become: true
when: ansible_os_family == 'RedHat'
- name: install redis on RHEL based distros
yum:
name: redis
state: present
update_cache: true
become: true
when: ansible_os_family == 'RedHat'
# ./roles/redis/tasks/main.yml
- include_tasks: "setup-{{ ansible_os_family }}.yml"
Utilisation de import_playbook
& fichiers YAML dynamiques
---
# ./master.yml
- import_playbook: common_roles.yml
- import_playbook: staging_roles.yml
when: env == 'staging'
- import_playbook: "{{ server_type }}.yml"
On peut les playbooks specifiques aux servers pour qu'ils ne contiennent qu'une tâche chacun :
---
'''# ./web.yml'''
- name: configure web servers
hosts: "{{ env }}_web"
roles:
- nginx
- my_site
---
'''# ./database.yml'''
- name: configure database servers
hosts: "{{ env }}_database"
roles:
- postgres
A partir de là, il est possible de configurer à la fois les serveurs web et base de données en utilisant seulement le fichier playbook master.yml
et en utilisant env
et server_type
pour specifier quels serveurs on désire configurer :
$ ansible-playbook master.yml --extra-vars "env=staging server_type=web"
$ ansible-playbook master.yml --extra-vars "env=staging server_type=database"
Utilisation de sections pre_tasks et post_tasks
exemple d'utilisation :
Configurez un playbook pour ajouter un message du jour (MOTD, Message Of The Day) sur les serveurs HAProxy lorsqu'une opération de maintenance est en cours. De cette façon, lorsqu'un membre de l'équipe d'assistance se connecte, le système l'informe de la fenêtre de maintenance. Le playbook doit également informer votre les admins à chaque fois qu'Ansible recharge le service haproxy. Un message doit également apparaître dans syslog afin que l'événement doit tracer par le système de corrélation de logs.
- name: Ensure HAProxy is deployed
hosts: lb_servers
force_handlers: True
pre_tasks:
- name: Setting the maintenance message
copy:
dest: /etc/motd.d/maintenance
content: "Maintenance in progress\n"
roles:
# The "haproxy" role has a dependency on the "firewall" role.
# The "firewall" role requires a "firewall_rules" variable be defined.
- role: haproxy
post_tasks:
- name: Removing the maintenance message
file:
path: /etc/motd.d/maintenance
state: absent
handlers:
- name: Sending an email to admins
mail:
subject: "HAProxy reloaded on {{ inventory_hostname }}"
to: admin@supervision.lab.example.com
delegate_to: localhost
become: false
listen: reload haproxy
- name: Logging a message to syslog
syslogger:
msg: "HAProxy reloaded on {{ inventory_hostname }}"
delegate_to: localhost
become: false
listen: reload haproxy
Transformation de données
Types de variables ansible
Principaux types de variables | ||||
---|---|---|---|---|
Type | Description | |||
Chaîne de carractères
|
Une chaîne est une séquence de caractères qui est le type de données par défaut dans Ansible. Il n'est pas nécessaire de placer les chaînes entre guillemets ou guillemets doubles.
Ansible supprime les espaces de fin de chaîne des chaînes sans guillemets.
Le format YAML permet de définir des chaînes à plusieurs lignes. Utilisez l'opérateur de barre verticale (|) pour conserver les sauts de ligne, ou l'opérateur supérieur à (>) pour les supprimer. string_with_breaks: |
This string
has several
line breaks
string_without_breaks: >
This string will not
contain any line breaks.
Separated lines are joined
by a space character.
| |||
Nombres
|
Lorsque le contenu de la variable est conforme à un nombre, Ansible (ou YAML, pour être précis) analyse la chaîne et génère une valeur numérique, Integer ou Float.
+ ou - :
| |||
Booléens
|
Les valeurs booléennes contiennent les chaînes yes, no, y, n, on, off, true ou false. Les valeurs ne sont pas sensibles à la casse, mais la documentation Jinja2 recommande d'utiliser des minuscules à des fins de cohérence. | |||
Date
|
Si la chaîne est conforme à la norme ISO-8601, Ansible convertit la chaîne en une valeur
typée date. my_date_time: 2019-05-30T21:15:32.42+02:00
my_simple_date: 2019-05-30 | |||
Null
|
La valeur null spéciale déclare une variable comme étant « undefined ». La chaîne null, ou le caractère tilde (~), affecte la valeur null à la variable.
| |||
Listes ou matrices
|
Une liste, également connue sous le nom de matrice, est une collection triée de valeurs. Les listes sont les structures de base des collections de données et des boucles.
Écrivez des listes sous la forme d'une séquence de valeurs séparées par des virgules, insérées entre crochets, ou d'une série d'éléments, un par ligne, chacun préfixé par un tiret (-). Les exemples suivants sont équivalents : my_list: ['Douglas', 'Marvin', 'Arthur']
my_list:
- Douglas
- Marvin
- Arthur À l'instar des matrices de la plupart des langages de programmation, vous pouvez accéder à des éléments spécifiques d'une liste en utilisant un numéro d'index à partir de 0 : - name: Confirm that the second list element is "Marvin"
assert:
that:
- my_list[1] == 'Marvin' | |||
Dictionnaires
|
Les dictionnaires, également appelés mappages ou hachages dans d'autres contextes, sont des structures qui lient des clés de chaîne à des valeurs pour un accès direct. À l'instar des listes, les dictionnaires peuvent être écrits en une seule ligne ou sur plusieurs lignes à l'aide du signe deux-points (:) :
my_dict: { Douglas: Human, Marvin: Robot, Arthur: Human }
my_dict:
Douglas: Human
Marvin: Robot
Arthur: Human Accédez à un élément dans un dictionnaire par sa clé, fournie entre crochets immédiatement après le nom du dictionnaire : - name: Confirm that the second list element is "Marvin"
assert:
that:
- my_dict['Marvin'] == 'Robot'
|
Utilisation des filtres
Les filtres permettent de traiter la valeur d'une variable afin d'extraire des informations, de les transformer ou de les utiliser pour calculer une nouvelle valeur. Pour appliquer un filtre, faites suivre le nom de la variable par le caractère de barre verticale (|) et le nom du filtre à appliquer.
Certains filtres peuvent nécessiter des arguments ou des options facultatifs entre parenthèses.
Il est possible d'enchaîner plusieurs filtres dans une même expression.
À titre d'exemple, l'expression suivante filtre la valeur de la variable myname, ce qui garantit que la première lettre de la valeur est mise en majuscule à l'aide d'un filtre Jinja2 standard :
{{ myname | capitalize }}
Cette expression peut également être utilisée pour convertir une variable en un type différent.
L'exemple d'expression suivant garantit que le résultat est une chaîne, et non un entier ou un nombre flottant :
{{ mynumber | string }}
L'exemple suivant est plus complexe et affiche une tâche complète. Le module assert vérifie si les expressions sont « true » et échoue si elles ne le sont pas. L'expression Jinja2 au début du test prend la liste [ 1, 4, 2, 2 ]
et utilise le filtre unique
pour supprimer les éléments dupliqués, puis utilise le filtre sort pour les trier.
Cet exemple utilise un filtre pour manipuler des données codées en dur plutôt que la valeur d'une variable.
- name: Test to see if the assertion is true, fail if not
assert:
that:
- "{{ [ 1, 4, 2, 2 ] | unique | sort }} is eq( [ 1, 2, 4 ] )"
La sortie du filtre sort
est comparée avec la liste attendue, aux fins d'égalité, à l'aide du test
Jinja2 eq
. Étant donné que le résultat et la valeur attendue sont égaux, le module assert réussit.
|
Principaux filtres
Vérification de la définition d'une variable
Les deux premiers filtres d'Ansible s'appliquent si l'entrée est ou non définie. Ces filtres sont utiles dans les rôles pour s'assurer que les variables ont des valeurs raisonnables.
mandatory
Échoue et interrompt le playbook Ansible si la variable n'est pas définie avec une valeur.
{{ my_value | mandatory }}
default
Si la variable n'est pas définie avec une valeur, alors ce filtre lui affecte la valeur spécifiée entre parenthèses. Si le second paramètre entre parenthèses est True, le filtre affecte alors également la valeur par défaut à la variable, si la valeur initiale de cette dernière est une chaîne vide ou la valeur booléenne False.
{{ my_value | default(my_default, True) }}
Le filtre default
peut également prendre la valeur spéciale omit
, ce qui a pour effet de ne pas définir la valeur dans le cas où il n'y en avait aucune à l'origine. Si la variable possède déjà une valeur, omit
ne modifie pas celle-ci.
La tâche suivante qui vérifie l'existence de l'utilisateur jonfoo est un exemple d'utilisation du filtre default(omit)
.
Si la variable supplementary_groups['jonfoo']
est déjà définie, la tâche s'assure que l'utilisateur est membre de ces groupes. S'il n'est pas déjà défini, le paramètre groups du module user n'est pas défini dans cette tâche.
- name: Ensure user jonfoo exists.
user:
name: jonfoo
groups: "{{ supplementary_groups['jonfoo'] | default(omit) }}"
Exécution de calculs mathématiques
- name: Ensure user jonfoo exists.
user:
name: jonfoo
groups: "{{ supplementary_groups['jonfoo'] | default(omit) }}"
Opérations arithmétiques
Opérateur | Description |
---|---|
+
|
Ajouter deux nombres. |
-
|
Soustraire deux nombres. |
/
|
Effectuer une division en virgule flottante. |
//
|
Effectuer une division d'entiers. |
%
|
Récupérer le reste de la division d'un entier. |
*
|
Multiplier deux nombres. |
**
|
Augmenter le nombre de gauche à la puissance du nombre correct. |
Dans certains cas, il peut-être nécessaire de d'abord convertir la valeur en un entier avec le filtre int
,
ou en une valeur flottante avec le filtre float
.
Par exemple, l'expression Jinja2 suivante ajoute une heure à l'heure actuelle, qui est collectée en tant que fait et stockée en tant que chaîne, et non en tant que nombre entier :
{{ ( ansible_facts['date_time']['hour'] | int ) + 1 }}
Il existe également un certain nombre de filtres qui peuvent effectuer une opération mathématique sur un nombre : log, pow, root, abs et round sont des exemples.
{{ 1764 | root }}
Manipulation de listes
Il existe de nombreux filtres qutilisables pour analyser et manipuler des listes.
Si la liste se compose de nombres, utiliser max, min ou sum
pour trouver le plus grand nombre, le plus petit nombre et la somme de tous les éléments de la liste.
{{ [2, 4, 6, 8, 10, 12] | sum }}
Extraire des éléments d'une liste
- obtenir des informations sur le contenu de la liste, telles que le premier élément (first), le dernier (last), ou la longueur (length) de la liste :
- name: All three of these assertions are true
assert:
that:
- "{{ [ 2, 4, 6, 8, 10, 12 ] | length }} is eq( 6 )"
- "{{ [ 2, 4, 6, 8, 10, 12 ] | first }} is eq( 2 )"
- "{{ [ 2, 4, 6, 8, 10, 12 ] | last }} is eq( 12 )"
Le filtre random renvoie un élément aléatoire de la liste :
{{ ['Douglas', 'Marvin', 'Arthur'] | random }}
Modifier l'ordre des éléments d'une liste
Une liste peut être réordonnée de plusieurs façons. Le filtre sort trie la liste dans l'ordre naturel de ses éléments. Le filtre reverse renvoie une liste dans laquelle l'ordre est le contraire de l'ordre d'origine. Le filtre shuffle renvoie une liste avec les mêmes éléments, mais dans un ordre aléatoire.
- name: All three of these assertions are true
assert:
that:
- "{{ [ 2, 4, 6, 8, 10 ] | reverse | list }} is eq( [ 10, 8, 6, 4, 2] )"
- "{{ [ 4, 8, 10, 6, 2 ] | sort | list }} is eq( [ 2, 4, 6, 8, 10 ] )"
Fusionner des listes
Dans certains cas, il est utile de fusionner plusieurs listes en une seule pour simplifier l'itération. Le filtre flatten prend de manière récursive une liste interne dans la valeur de la liste d'entrée, et ajoute les valeurs internes à la liste externe.
- name: Flatten turns nested lists on the left to list on the right
assert:
that:
- "{{ [ 2, [4, [6, 8]], 10 ] | flatten }} is eq( [ 2, 4, 6, 8, 10] )"
Utilisez flatten
pour fusionner les valeurs des listes issues de l'itération d'une liste parente.
Listes et opérations ensemblistes
Assurer qu'une liste ne comporte aucun élément en double avec le filtre unique. Cela peut s'avérer utile si vous utilisez une liste de faits que vous avez collectés, tels que des noms d'utilisateur ou d'hôte qui peuvent contenir des entrées en double.
- name: 'unique' leaves unique elements
assert:
that:
- "{{ [ 1, 1, 2, 2, 2, 3, 4, 4 ] | unique | list }} is eq( [ 1, 2, 3, 4 ] )"
Si deux listes n'ont aucun élément en double, il est possible d'utiliser des opérations de théorie ensembliste sur celles-ci.
- Le filtre union renvoie un ensemble avec des éléments provenant des deux jeux de données d'entrée.
- Le filtre intersect renvoie un ensemble avec des éléments communs aux deux jeux de données d'entrée.
- Le filtre difference renvoie un ensemble avec des éléments du premier jeu qui ne sont pas présents dans le second jeu.
- name: 'difference' provides elements not in specified set
assert:
that:
- "{{ [ 2, 4, 6, 8, 10 ] | difference([2, 4, 6, 16]) }} is eq( [8, 10] )"
Utilisez le filtre symmetric_difference pour obtenir les éléments à prendre en compte lors de l'utilisation d'un jeu.
Manipulation des dictionnaires
Contrairement aux listes, les dictionnaires ne respectent aucun ordre. Il s'agit simplement d'une collection de paires clé-valeur. Mais vous pouvez utiliser des filtres pour construire des dictionnaires. Ensuite, vous pouvez les convertir en listes ou convertir des listes en dictionnaires.
Liaison de dictionnaires
Deux dictionnaires peuvent être reliés par le filtre combine. Les entrées du second dictionnaire ont une priorité plus élevée que les entrées du premier dictionnaire, comme le montre la tâche suivante :
Exemple
- name: 'combine' combines two dictionaries into one
vars:
expected:
A: 1
B: 4
C: 5
assert:
that:
- "{{ {'A':1,'B':2} | combine({'B':4,'C':5}) }} is eq( expected )"
Remodelage de dictionnaires
Un dictionnaire peut être remodelé en une liste d'éléments avec le filtre dict2items, et une liste d'éléments peut être remodelée en un dictionnaire avec le filtre items2dict :
Exemple
- name: converting between dictionaries and lists
vars:
characters_dict:
Douglas: Human
Marvin: Robot
Arthur: Human
characters_items:
- key: Douglas
value: Human
- key: Marvin
value: Robot
- key: Arthur
value: Human
assert:
that:
- "{{ characters_dict | dict2items }} is eq( characters_items )"
- "{{ characters_items | items2dict }} is eq( characters_dict )"
Hachage, codage et manipulation de chaînes
Un certain nombre de filtres sont disponibles pour manipuler le texte d'une valeur. Vous pouvez prendre plusieurs checksums, créer des hachages de mots de passe et convertir du texte à partir d'un encodage Base64 et vers celui-ci, tel qu'il est utilisé par un certain nombre d'applications.
Hachage de chaînes et de mots de passe
Le filtre hash renvoie la valeur de hachage de la chaîne d'entrée, à l'aide de l'algorithme de hachage fourni :
- name: the string's SHA-1 hash
vars:
expected: '8bae3f7d0a461488ced07b3e10ab80d018eb1d8c'
assert:
that:
- "'{{ 'Arthur' | hash('sha1') }}' is eq( expected )"
Utilisez le filtre password_hash pour générer des hachages de mot de passe :
{{ 'secret_password' | password_hash('sha512') }}
Encodage de chaînes
Les données binaires peuvent être traduites en base64 par le filtre b64encode, puis retraduites au format binaire par le filtre b64decode :
- name: Base64 encoding and decoding of values
assert:
that:
- "'{{ 'âÉïôú' | b64encode }}' is eq( 'w6LDicOvw7TDug==' )"
- "'{{ 'w6LDicOvw7TDug==' | b64decode }}' is eq( 'âÉïôú' )"
Avant d'envoyer des chaînes au shell sous-jacent et d'éviter les problèmes d'analyse ou d'injection de code, il est recommandé d'assainir la chaîne à l'aide du filtre quote :
- name: Put quotes around 'my_string'
shell: echo {{ my_string | quote }}
Mise en forme de texte
Utilisez les filtres lower, upper ou capitalize pour appliquer la casse d'une chaîne d'entrée :
- name: Change case of characters
assert:
that:
- "'{{ 'Marvin' | lower }}' is eq( 'marvin' )"
- "'{{ 'Marvin' | upper }}' is eq( 'MARVIN' )"
- "'{{ 'marvin' | capitalize }}' is eq( 'Marvin' )"
Remplacement de texte
Le filtre replace est utile lorsque vous devez remplacer toutes les occurrences d'une sous-chaîne à l'intérieur de la chaîne d'entrée :
- name: Replace 'ar' with asterisks
assert:
that:
- "'{{ 'marvin, arthur' | replace('ar','**') }}' is eq( 'm**vin, **thur' )"
Des recherches et des remplacements beaucoup plus complexes sont disponibles en utilisant des expressions rationnelles et les filtres regex_search et regex_replace.
- name: Test results of regex search and search-and-replace
assert:
that:
- "'{{ 'marvin, arthur' | regex_search('ar\S*r') }}' is eq( 'arthur' )"
- "'{{ 'arthur up' | regex_replace('ar(\S*)r','\\1mb') }}' is eq( 'thumbup' )"
Manipulation de données JSON
De nombreuses structures de données utilisées par Ansible sont au format JSON. Les notations JSON et YAML sont étroitement liées et les structures de données Ansible peuvent être traitées en tant que JSON. De même, de nombreuses API avec lesquelles les playbooks Ansible peuvent interagir consomment ou utilisent des informations au format JSON. Étant donné que ce format est largement utilisé, les filtres JSON sont particulièrement utiles.
Requêtes JSON
Utilisez le filtre json_query pour extraire des informations des structures de données Ansible.
- name: Get the 'name' elements from the list of dictionaries in 'hosts'
vars:
hosts:
- name: bastion
ip:
- 172.25.250.254
- 172.25.252.1
- name: classroom
ip:
- 172.25.252.254
assert:
that:
- "{{ hosts | json_query('[*].name') }} is eq( ['bastion','classroom'] )"
Analyse et encodage de structures de données
La transformation de structures de données vers du texte et à partir de celui-ci est utile pour le débogage et la communication. Les structures de données sont sérialisées au format JSON ou YAML avec les filtres to_json et to_yaml. Utilisez les filtres to_nice_json et to_nice_yaml pour obtenir une sortie mise en forme lisible par l'utilisateur.
- name: Convert between JSON and YAML format
vars:
hosts:
- name: bastion
ip:
- 172.25.250.254
- 172.25.252.1
hosts_json: '[{"name": "bastion", "ip": ["172.25.250.254", "172.25.252.1"]}]'
assert:
that:
- "'{{ hosts | to_json }}' is eq( hosts_json )"