![Logo Ansible](images/logo-ansible.svg) # Playbooks Ansible Jouer et rejouer facilement un jeu de commandes prédéfini sur un ensemble de machines. ![Schéma archi globale](images/ansible-archi-playbooks.png) ## Format du Playbook * Il se présente sous la forme d'un simple fichier texte au format `yaml`. * Il peut donc être facilement versionné dans un outil de gestion de version. ### yaml * Format ouvert de représentation de données. * Acronyme récursif de _YAML Ain't Markup Language_. * Permet de représenter des données complexes tout en conservant une excellente lisibilité. * Utilisation en forte progression ces dernières années. Ansible, GitLab, Docker Compose, Kubernetes manifests, etc... * Site officiel : https://yaml.org/ ### YAML basics https://fr.wikipedia.org/wiki/YAML * _attention_ ! l'indentation se fait avec un ou plusieurs espaces, jamais avec des tabulations ! * Les commentaires sont signalés par le signe dièse `#` et se prolongent sur toute la ligne. * Les éléments de listes sont dénotés par le tiret `-`, suivi d'une espace, à raison d'un élément par ligne. * Les tableaux sont de la forme `clé: valeur`, à raison d'un couple par ligne. ### YAML basics https://fr.wikipedia.org/wiki/YAML * Les chaînes de caractères peuvent être entourées de guillemets doubles `"`, ou simples `'`, sachant qu'un guillemet s'échappe avec un antislash `\`, alors qu'une apostrophe s'échappe avec une autre apostrophe. * Les chaînes de caractères peuvent de plus être représentées par un bloc indenté avec des modificateurs facultatifs pour conserver `|` ou éliminer `>` les retours à la ligne. * Plusieurs documents rassemblés dans un seul fichier sont séparés par trois traits d'union `---`. ### YAML basics https://fr.wikipedia.org/wiki/YAML ``` --- receipt: Oz-Ware Purchase Invoice date: 2012-08-06 customer: given: Dorothy family: Gale items: - part_no: A4786 descrip: Water Bucket (Filled) price: 1.47 quantity: 4 - part_no: E1628 descrip: High Heeled "Ruby" Slippers size: 8 price: 100.27 quantity: 1 bill-to: &id001 street: | 123 Tornado Alley Suite 16 city: East Centerville state: KS ship-to: *id001 specialDelivery: > Follow the Yellow Brick Road to the Emerald City. Pay no attention to the man behind the curtain. ... ``` ### yaml - Définition d’une collection (-) ```yaml # Une liste de fruits fruits: - pomme - orange - framboise - mangue ``` Forme abrégée : ```yaml # Une liste de fruits fruits: ['pomme', 'orange', 'framboise', 'mangue'] ``` ### Définition d’un dictionnaire (key: value) ```yaml # Un utilisateur martin: name: Martin Dupond job: developer skill: python ``` Forme abrégée : ```yaml # Un utilisateur martin: { name: Martin Dupond, job: developer, skill: python } ``` ## Tasks * Les commandes d'un Playbook sont découpées en instructions unitaires appelées _tâches_ (tasks). * Chaque tâche exécute un module Ansible avec des paramètres spécifiques. * Format d'une tâche : ```none - name: Description de la tâche : : : : ``` ### Liste de tâches * Un playbook peut décrire une liste de plusieurs tâches. * Les tâches seront exécutées dans l'ordre d'apparition et de façon séquentielle sur chacune des machines cibles. ```none tasks: <----------------------------- liste de tâche - name: Ma tâche 1 <--------------- tâche 1 : : : : - name: Ma tâche 2 <--------------- tâche 2 : : : ``` ### Exemple réel ```yaml - hosts: web # exécution d'un 'Play' sur le groupe 'web' tasks: - name: Installation of Apache Package # tâche 1 yum: name: httpd state: present update_cache: yes - name: Ensure Apache is running (and enabled at boot) # tâche 2 service: name=httpd state=started enabled=yes ``` * Ici on mixe les deux types de syntaxes (normale et abrégée). * __Les bonnes pratiques préconisent l'utilisation de la syntaxe normale.__ ### Exemple réel avec syntaxe normale ```yaml - hosts: web tasks: - name: Installation of Apache Package yum: name: httpd state: present update_cache: yes - name: Ensure Apache is running (and enabled at boot) service: name: httpd state: started enabled: yes ``` ### Notion de Play ```yaml --- - hosts: webservers # Play 1 sur le groupe webservers tasks: - name: My task ... - hosts: databases # Play 2 sur le groupe databases tasks: - name: My task... ... ``` * Chaque Play contient sa propre liste de tâches et cible un ensemble spécifique de machines. ## Lancer un playbook `$ ansible-playbook -i ` ```none $ ansible-playbook playbook.yaml -i ./hosts PLAY [web] ******************************************************************* TASK [setup] ***************************************************************** ok: [web1.formation.sii.fr] ok: [web2.formation.sii.fr] TASK [Installation du package Apache] **************************************** changed: [web2.formation.sii.fr] changed: [web1.formation.sii.fr] TASK [Ensure Apache is running (and enable it at boot)] ********************** changed: [web1.formation.sii.fr] changed: [web2.formation.sii.fr] PLAY RECAP ******************************************************************* web1.formation.sii.fr : ok=3 changed=2 unreachable=0 failed=0 web2.formation.sii.fr : ok=3 changed=2 unreachable=0 failed=0 ``` * Notre Playbook comportait 2 tâches seulement, nous en voyons 3 ! * Ansible a automatiquement ajouté à l'exécution une tâche nommée _setup_ dont l'objectif est de récupérer les _facts_ des machines cibles. ## Récapitulatif du Playbook ```none PLAY RECAP ******************************************************************* web1.formation.sii.fr : ok=3 changed=2 unreachable=0 failed=0 web2.formation.sii.fr : ok=3 changed=2 unreachable=0 failed=0 ``` * Sur les 2 machines cibles : - `ok` : 3 tâches ont été exécutées avec succès. - `changed` : 2 tâches ont modifié l'état du système. - `unreachable` : Toutes les machines étaient joignables. - `failed` : Aucune tâche n'a échoué. ## Relancer un playbook ```none $ ansible-playbook playbook.yaml -i ./hosts --become PLAY [web] ******************************************************************* TASK [setup] ***************************************************************** ok: [web1.formation.sii.fr] ok: [web2.formation.sii.fr] TASK [Installation du package Apache] **************************************** ok: [web1.formation.sii.fr] ok: [web2.formation.sii.fr] TASK [Ensure Apache is running (and enable it at boot)] ********************** ok: [web1.formation.sii.fr] ok: [web2.formation.sii.fr] PLAY RECAP ******************************************************************* web1.formation.sii.fr : ok=3 changed=0 unreachable=0 failed=0 web2.formation.sii.fr : ok=3 changed=0 unreachable=0 failed=0 ``` ## Première exécution ```none PLAY RECAP ******************************************************************* web1.formation.sii.fr : ok=3 changed=2 unreachable=0 failed=0 web2.formation.sii.fr : ok=3 changed=2 unreachable=0 failed=0 ``` ## Seconde exécution ```none PLAY RECAP ******************************************************************* web1.formation.sii.fr : ok=3 changed=0 unreachable=0 failed=0 web2.formation.sii.fr : ok=3 changed=0 unreachable=0 failed=0 ``` ## Ordre d'exécution * Il est possible de contrôler l'ordre dans lequel les machines cibles sont adressées. * L'ordre par défaut est l'ordre d'apparition dans l'inventaire. ```yaml - hosts: all order: sorted # l'ordre est défini ici gather_facts: False tasks: - debug: var: inventory_hostname ``` Ordre | Description - | - `inventory` | Ordre d'apparition dans l'inventaire. C'est le choix par défaut. `reverse_inventory` | Ordre inverse d'apparition dans l'inventaire. `sorted` | Ordre alphabétique des noms de machines. `reverse_sorted` | Ordre alphabétique inverse des noms de machines. `shuffle` | Ordre aléatoire. ## Démarrer l'exécution à un endroit précis `$ ansible-playbook playbook.yaml --start-at-task="my task"` Cette commande démarre l'exécution du playbook à partir de la tâche nommée `my task`. https://docs.ansible.com/ansible/latest/user_guide/playbooks_startnstep.html ## Ignorer le code de retour d'une commande * Les modules _command_ et _shell_ sont sensibles au code de retour des commandes. * Pour ignorer les erreurs sur une commande qui renvoie un code > 0 on peut utiliser `ignore_errors`. * Ou, utiliser `failed_when`. * De même, `changed_when` peut être utilisé. ```yaml tasks: - name: run this command and ignore the result shell: /usr/bin/somecommand ignore_errors: True ``` ## Déclaration de variables ```yaml - hosts: web vars: - app_directory: /var/www/html - app_user: apache - app_group: apache tasks: - name: Modify permission on {{ app_directory }} file: dest: '{{ app_directory }}' mode: 0755 owner: '{{ app_user }}' group: '{{ app_group }}' recurse: yes ``` Les variables sont déclarées dans _vars_ et résolues avec `{{ }}`. ```none $ ansible-playbook playbook.yaml -i ./hosts PLAY [web] **************************************************************** TASK [setup] ************************************************************** ok: [web1.formation.sii.fr] ok: [web2.formation.sii.fr] TASK [Modify permission of directory /var/www/html] *********************** changed: [web1.formation.sii.fr] changed: [web2.formation.sii.fr] PLAY RECAP **************************************************************** web1.formation.sii.fr : ok=2 changed=1 unreachable=0 failed=0 web2.formation.sii.fr : ok=2 changed=1 unreachable=0 failed=0 ``` ## Les _handlers_ et les _notify_ ```yaml - hosts: web vars: - apache_listen_port: 8080 tasks: - name: Modify Apache configuration lineinfile: dest: /etc/httpd/conf/httpd.conf regexp: '^Listen ' line: 'Listen {{ apache_listen_port }}' notify: Reload Apache # Signale que la configuration # d'Apache doit être rechargée handlers: - name: Reload Apache # Recharge la configuration service: name: httpd state: reloaded ``` ```none $ ansible-playbook playbook.yaml -i ./hosts PLAY [web] ******************************************************************* TASK [setup] ***************************************************************** ok: [web1.formation.sii.fr] ok: [web2.formation.sii.fr] TASK [Modify Apache configuration] ******************************************* changed: [web1.formation.sii.fr] changed: [web2.formation.sii.fr] RUNNING HANDLER [Reload Apache] ********************************************* changed: [web1.formation.sii.fr] changed: [web2.formation.sii.fr] PLAY RECAP ******************************************************************* web1.formation.sii.fr : ok=3 changed=2 unreachable=0 failed=0 web2.formation.sii.fr : ok=3 changed=2 unreachable=0 failed=0 ``` ## Idempotence * L'idempotence signifie qu'une opération a le même effet qu'on l'applique une ou plusieurs fois. * Les commandes d'un Playbook doivent être écrites de manière à produire le même résultat quel que soit le nombre de fois où elles sont exécutées sur une même cible. ## Travaux pratiques ![Travaux pratiques](images/tp.gif) [TP Ansible : playbooks](travaux-pratiques/tp-ansible-playbooks.html) ## Variables sur la ligne de commande Il est possible d'initialiser des variables directement sur la ligne de commande avec l'option `--extra-vars` (ou `-e`). * Ces variables peuvent être définies sous la forme : - chaîne de caractères `$ ansible-playbook playbook.yaml --extra-vars "my_var_1=foo my_var_2=bar"` - json `$ ansible-playbook playbook.yaml --extra-vars '{"my_var_1":"foo","my_var_2":"bar"}'` `$ ansible-playbook playbook.yaml --extra-vars '{"my_var":"foo","my_list":["foo","bar"]}'` * Utilisez le fomat _json_ si vous voulez passer autre chose que des _strings_ : - booleans - integers - floats - lists - ... ## Découper un Playbook * Un Playbook peut se présenter sous la forme d'un fichier unique. * Toutefois il est possible de le découper en plusieurs fichiers séparés afin de mieux organiser et favoriser la ré-utilisation de certaines parties. * Il existe plusieurs manières de découper un Playbook : les _includes_, les _imports_, et les _roles_. https://docs.ansible.com/ansible/latest/user_guide/playbooks_reuse.html ### Includes et Imports * Disponibles à partir de Ansible v2.4. * Permettent le découpage des tâches d'un gros Playbook en fichiers plus petits. * Ces fichiers peuvent ensuite être appelés : - depuis un ou plusieurs Playbooks - plusieurs fois dans un même Playbook. ### Import dynamique vs statique * Les commandes `import` permettent un chargement statique. (import_playbook, import_tasks, etc.) * Les commandes `include` permettent un chargement dynamique. (include_tasks, include_role, etc.) ### Statique vs Dynamique * _Statique_ Ansible traite les imports statique au moment de l'analyse du Playbook (avant l'exécution). * _Dynamique_ Ansible traite les imports dynamiques au fur et à mesure durant l'exécution du Playbook. * Les imports statiques et dynamiques peuvent être mixés, toutefois cela n'est pas recommandé car cela rend le debug des Playbooks plus complexe. ### Import de Playbooks * Il est possible d'importer un ou plusieurs Playbooks à l'intérieur d'un Playbook maître, avec `import_playbook`. ### Roles * Plus puissants que les _includes_ et les _imports_. * Permettent d'empaqueter un ensemble de tâches ainsi que les variables, handlers et autres autres éléments associés. * Les _roles_ peuvent être facilement ré-utilisés et partagés. ## Travaux pratiques ![Travaux pratiques](images/tp.gif) [TP Ansible : imports](travaux-pratiques/tp-ansible-imports.html)