Découverte d'Ansible Event Driven
Annoncé à l’ansibleFest 2022, ansible-rulebook
apporte à Ansible la
possibilité de déclencher des actions à partir d’un événement. En effet, jusqu’à
maintenant déclencher des playbooks Ansible suite à la survenue d’un événement
nous demandait d’écrire et de faire tourner régulièrement des jobs charger de
les collecter et de déclencher les traitements adéquats.
Pour rappel, une des tâches qui nous incombent est de chercher à automatiser un
maximum de tache sans valeurs ajoutées. Par exemple, on devrait ne pas
intervenir sur des incidents simples
ou la correction est connue, ou traiter
de simples demandes en provenance de nos clients. Je suis sûr que comme
moi, vous aimeriez pouvoir vous concentrer sur d’autres tâches à plus fortes
valeurs ajoutées que de traiter des tickets en série. Par exemple, pour ce qui
est du traitement des incidents, à défaut de pouvoir les résoudre
automatiquement, nous devrions au moins pouvoir automatiser la collecte des
informations nécessaires à l’identification de leurs causes premières.
Voyons ensemble comment fonctionne ce nouvel outil Ansible : ansible-rulebook
.
Ansible-rulebook
Commençons par l’installer :
Installation d’ansible-rulebook
Pour tester cet outil, comme d’habitude, je le fais en utilisant une VM popé sur mon poste de travail avec Vagrant :
vagrant init generic/debian11vagrant upvagrant ssh
Maintenant en parcourant la documentation, nous voyons qu’il existe deux manières de l’installer : une avec Ansible et une manuelle, je vais prendre la seconde solution, car j’aime bien savoir ce qu’il se passe :
sudo apt install python3-pip openjdk-17-jdkpip install ansible --userexport PATH=$PATH:$HOME/.local/binexport JAVA_HOME=/usr/lib/jvm/java-17-openjdkexport PIP_NO_BINARY=jpypip install wheel ansible-rulebook ansible ansible-runneransible-galaxy collection install community.general ansible.eda
Voilà tout est prêt. Passons à l’écriture de nos premiers rulebooks
:
Ecriture des rulebooks
La documentation ↗ est déjà disponible.
Un exemple de rulebook
:
---- name: Hello Events hosts: localhost sources: - ansible.eda.range: limit: 5 rules: - name: Say Hello condition: event.i == 1 action: run_playbook: name: ansible.eda.hello...
Ansible donc yaml
. J’en connais qui vont râler. Mais pour ceux qui codent des
playbooks Ansible, la syntaxe sera simple à prendre en main.
Nous retrouvons trois sections : hosts
, sources
et rules
. sources
permet
de définir d’où provient l’événement et rules
pour les traitements à lancer.
Pour qu’une action soit lancée, elle doit répondre à une condition de type
booléen. Ces conditions sont les mêmes que celles que nous utilisons dans nos
conditions when
.
Dans l’exemple ci-dessus la source utilise un range python
qui compte de 0 à
5. La règle définit une action qui se déclenche sur l’événement i==1
, ici le
lancement d’un playbook : ansible.eda.hello
Pour lancer l’exécution du rulebook, il faut définir un inventaire :
all: hosts: localhost: ansible_connection: local
Ici localhost
avec la connexion de type local
. On a tout ce qu’il faut pour
lancer le rulebook 👍:
ansible-rulebook --rulebook rule1.yaml -i inventory.yml --verboseINFO:ansible_rulebook.app:Starting sourcesINFO:ansible_rulebook.app:Starting rulesINFO:ansible_rulebook.engine:run_rulesetINFO:ansible_rulebook.rule_generator:{'all': [{'m': {'i': 1}}], 'run': <function make_fn.<locals>.fn at 0x7f6b194b8790>}INFO:ansible_rulebook.engine:ruleset define: ('Hello Events', {'r_0': {'all': [{'m': {'i': 1}}], 'run': <function make_fn.<locals>.fn at 0x7f6b194b8790>}})INFO:ansible_rulebook.engine:load sourceINFO:ansible_rulebook.engine:load source filtersINFO:ansible_rulebook.engine:Calling main in ansible.eda.rangeINFO:ansible_rulebook.engine:Waiting for event from Hello EventsINFO:ansible_rulebook.rule_generator:calling Say HelloINFO:ansible_rulebook.engine:call_action run_playbookINFO:ansible_rulebook.engine:substitute_variables [{'name': 'ansible.eda.hello'}] [{'event': {'i': 1}, 'fact': {'i': 1}}]INFO:ansible_rulebook.engine:action args: {'name': 'ansible.eda.hello'}INFO:ansible_rulebook.builtin:running Ansible playbook: ansible.eda.helloINFO:ansible_rulebook.builtin:ruleset: Hello Events, rule: Say HelloINFO:ansible_rulebook.builtin:Calling Ansible runner
PLAY [hello] *******************
TASK [debug] *******************ok: [localhost] => { "msg": "hello"}
PLAY RECAP *********************localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0INFO:ansible_rulebook.engine:Canceling all ruleset tasksINFO:ansible_rulebook.app:Cancelling event source tasksINFO:ansible_rulebook.app:Main complete
Cela a produit le résultat attendu. Passons à un autre exemple avec l’utilisation d’un webhook.
---- name: Listen for events on a webhook hosts: all
## Define our source for events
sources: - ansible.eda.webhook: host: 0.0.0.0 port: 5000
## Define the conditions we are looking for
rules: - name: Say Hello condition: event.payload.message == "Ansible is super cool!"
## Define the action we should take should the condition be met
action: run_playbook: name: ansible.eda.hello
La source est cette fois un webhook
qui écoute sur le port 5000. Pour que
l’exemple fonctionne, il faut installer au préalable le package python aiohttp
.
pip install aiohttp
On peut lancer ansible-rulebook
:
ansible-rulebook --rulebook rule2.yaml -i inventory.yml --verboseINFO:ansible_rulebook.app:Starting sourcesINFO:ansible_rulebook.app:Starting rulesINFO:ansible_rulebook.engine:run_rulesetINFO:ansible_rulebook.rule_generator:{'all': [{'m': {'payload.message': 'Test'}}], 'run': <function make_fn.<locals>.fn at 0x7f5f6a28d820>}INFO:ansible_rulebook.engine:ruleset define: ('Listen for events on a webhook', {'r_0': {'all': [{'m': {'payload.message': 'Test'}}], 'run': <function make_fn.<locals>.fn at 0x7f5f6a28d820>}})INFO:ansible_rulebook.engine:load sourceINFO:ansible_rulebook.engine:load source filtersINFO:ansible_rulebook.engine:Calling main in ansible.eda.webhookINFO:ansible_rulebook.engine:Waiting for event from Listen for events on a webhook
Contrairement à tout à l’heure, ansible-rulebook
attend un événement. Lançons
cet évènement depuis un autre terminal :
vagrant sshcurl -H 'Content-Type: application/json' -d '{"message": "Test"}' 127.0.0.1:5000/endpoint
Que s’est-il passé dans l’autre terminal. On peut y lire ceci 👍
INFO:aiohttp.access:127.0.0.1 [18/Oct/2022:06:43:27 +0000] "POST /endpoint HTTP/1.1" 200 158 "-" "curl/7.74.0"INFO:ansible_rulebook.rule_generator:calling Say HelloINFO:ansible_rulebook.engine:call_action run_playbookINFO:ansible_rulebook.engine:substitute_variables [{'name': 'ansible.eda.hello'}] [{'event': {'payload': {'message': 'Test'}, 'meta': {'endpoint': 'endpoint', 'headers': {'Host': '127.0.0.1:5000', 'User-Agent': 'curl/7.74.0', 'Accept': '*/*', 'Content-Type': 'application/json', 'Content-Length': '19'}}}, 'fact': {'payload': {'message': 'Test'}, 'meta': {'endpoint': 'endpoint', 'headers': {'Host': '127.0.0.1:5000', 'User-Agent': 'curl/7.74.0', 'Accept': '*/*', 'Content-Type': 'application/json', 'Content-Length': '19'}}}}]INFO:ansible_rulebook.engine:action args: {'name': 'ansible.eda.hello'}INFO:ansible_rulebook.builtin:running Ansible playbook: ansible.eda.helloINFO:ansible_rulebook.builtin:ruleset: Listen for events on a webhook, rule: Say HelloINFO:ansible_rulebook.builtin:Calling Ansible runnerincorrect line lengths
PLAY [hello] *******************
TASK [debug] *******************ok: [localhost] => { "msg": "hello"}
PLAY RECAP *********************localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Le traitement s’est lancé et ansible-rulebook
attend à nouveau des événements à
traiter. Lançons d’autres tests en changeant le contenu du message.
On peut aussi trouver un exemple sur le dépôt du projet
ansible-rulebook ↗ dans le
répertoire demo
.
vagrant sshcurl -H 'Content-Type: application/json' -d '{"message": "Test2"}' 127.0.0.1:5000/endpoint
Dans l’autre terminal juste une ligne apparaît avec aucune trace d’un traitement lancé :
INFO:aiohttp.access:127.0.0.1 [18/Oct/2022:06:45:43 +0000] "POST /endpoint HTTP/1.1" 200 158 "-" "curl/7.74.0"
Passage de variables
Comment récupérez des variables et les transmettre aux playbooks? Les facts ?
Une des réponses, je l’ai trouvé en utilisant l’action debug
. Remplaçons notre
action dans l’exemple précédent (d’ailleurs, on ne peut en mettre qu’une seule).
...
action: debug:
Si on relance modifiant notre appel curl avec ajout d’un champ :
curl -H 'Content-Type: application/json' -d '{"message": "Test", "Host": "test2"}' 127.0.0.1:5000/endpoint
On voit
INFO:ansible_rulebook.engine:action args: {}======================================================================================facts:[]===========================================kwargs:{'facts': {}, 'hosts': ['all'], 'inventory': {'all': {'hosts': {'localhost': {'ansible_connection': 'local'}}}}, 'project_data_file': None, 'ruleset': 'Listen for events on a webhook', 'source_rule_name': 'Say Hello', 'source_ruleset_name': 'Listen for events on a webhook', 'variables': {'event': {'meta': {'endpoint': 'endpoint', 'headers': {'Accept': '*/*', 'Content-Length': '36', 'Content-Type': 'application/json', 'Host': '127.0.0.1:5000', 'User-Agent': 'curl/7.74.0'}}, 'payload': {'Host': 'test2', 'message': 'Test'}}, 'fact': {'meta': {'endpoint': 'endpoint', 'headers': {'Accept': '*/*', 'Content-Length': '36', 'Content-Type': 'application/json', 'Host': '127.0.0.1:5000', 'User-Agent': 'curl/7.74.0'}}, 'payload': {'Host': 'test2', 'message': 'Test'}}}}===========================================
Donc essayons d’afficher un des champs de variables dans l’action debug :
action: debug: test: "{{ fact.payload.Host }}"
On voit dans la sortie apparaitre notre variable :
'source_ruleset_name': 'Listen for events on a webhook', 'test': 'test2', 'variables': {'event': {'meta': {'endpoint': 'endpoint',
Donc maintenant pour l’ajouter invoque un playbook avec un argument :
action: run_playbook: name: test.yaml
Dans ce playbook, je demande à afficher simplement hostvars :
---- name: test hosts: all gather_facts: true tasks: - name: debug ansible.builtin.debug: var: fact
Dans les hostvars, je relance et bingo, fact est transmis pas besoin de se prendre la tête. Je modifie et je teste directement fact.
TASK [debug] *******************ok: [localhost] => { "fact": { "meta": { "endpoint": "endpoint", "headers": { "Accept": "*/*", "Content-Length": "36", "Content-Type": "application/json", "Host": "127.0.0.1:5000", "User-Agent": "curl/7.74.0" } }, "payload": { "Host": "test2", "message": "Test" } }}PLAY RECAP *********************
Différents types types de sources
Si on étudie la documentation, on voit que pour le moment ansible-rulebook
propose des plugins par défaut. Qui dit plugin dit possibilité d’en coder soit
même.
Parmi ceux fournis :
alertmanager
: traiter des événements via un webhook en provenance d’alertmanagerazure_service_bus
- traiter des événements d’un service Azurefile
- charge les facts à partir de fichiers YAML et recharge lorsqu’un fichier change ?kafka
- traiter des événements d’un stream kafkarange
- un range pythontick
- un index croissant i illimitéurl_check
- interroge un ensemble d’URL et envoie des événements avec leurs statutswatchdog
- surveille le système de fichiers et envoie des événements lorsqu’un statut d’un fichier changenone
- vu dans l’exemple ci-dessus
D’autres types d’actions
run_module
- lancement d’un simple module Ansiblerun_playbook
- lancement d’un playbook via ansible-runnerset_fact
- définir des factsretract_fact
- efface un factspost_event
- envoie d’autres événements pour enchainer des traitementsdebug
- affiche l’événement et ses facts avec ses argumentsprint_event
- affiche l’événementnoop
- ne fais rienshutdown
- arrête ansible-rulebook
Ils sont tous documentés ici ↗
Plus loin avec ansible-rulebook
J’imagine déjà plein de cas d’usage pour cet outil, comme :
- lancer et enchainer des playbooks sur des machines qui viennent d’être créé sur notre infrastructure. J’imagine simplifier pas mal certains playbooks enchaînant des tas de roles et tasks en les découpant.
- traiter des demandes clientes depuis notre outil de ticketing ou des appels via curl (mais quid de la partie droit ?)
- et bien traiter des incidents bien identifiés.
- traiter le patch management avec reprise auto des erreurs connues.
- …
Mauvaise nouvelle, pour le moment l’extension vscode ansible ne prend pas en charge ce type de fichier.
Mais je vais approfondir longuement ce nouvel outil.