Sono sempre alla ricerca di cose intelligenti da fare con Ansible. Con così tanti strumenti e servizi che sfruttano le API (Application Programming Interface) basate su HTTP, è chiaro che l'interazione con i servizi basati su API di Ansible a livello di codice è una capacità preziosa. Potrebbe sembrare una funzionalità avanzata, ma questo articolo illustra un caso d'uso che dimostra come anche ambienti semplici possono trarre vantaggio dalla potenza e dalla semplicità del modulo URI di Ansible.
Interazione con semplici endpoint
Innanzitutto, ti guiderò attraverso un playbook che sfrutta le capacità HTTP di Ansible per prendere decisioni intelligenti durante l'aggiornamento di un server web. Il playbook qui sotto:
- Esegue uno script di manutenzione.
- Verifica che un endpoint API di controllo dello stato restituisca un servizio 503 HTTP temporaneamente non disponibile messaggio.
- Esegue uno script per aggiornare l'applicazione.
- Esegue uno script successivo alla manutenzione per dire al server web di ricominciare a rispondere normalmente.
- Ricontrolla l'API di controllo dello stato per assicurarsi che risponda con 200 OK .
Ecco il playbook:
---
- hosts: all
tasks:
- name: Run maintenance start script
command:
cmd: /usr/local/sbin/start_maintenance.sh
- name: Confirm that 503 Unavailable response is returned
uri:
url: "http://{{ ansible_host }}/api/v1/healthcheck"
status_code: 503
- name: Update application
command:
cmd: /usr/local/sbin/update_app.sh
- name: Run maintenance end script
command:
cmd: /usr/local/sbin/end_maintenance.sh
- name: Confirm that 200 OK response is returned
uri:
url: "http://{{ ansible_host }}/api/v1/healthcheck"
status_code: 200
Sto utilizzando il modulo URI di Ansible per contattare /api/v1/healthcheck
sul server. La prima chiamata URI prevede un HTTP 503 codice di stato da restituire poiché il server dovrebbe essere in modalità di manutenzione e non soddisfare le richieste. Dopo l'aggiornamento, la chiamata URI prevede un HTTP 200 codice di stato, che indica che il server web è di nuovo integro.
Questo semplice approccio migliora la sicurezza del mio playbook. Se il server non entra in modalità di manutenzione, Ansible non eseguirà alcuna patch:
fsh$ ansible-playbook -i inventory.ini playbook-healthcheck.yml
PLAY [all] ***********************************************************************************
TASK [Gathering Facts] ***********************************************************************
ok: [nyc1-apiserver-1.example.com]
TASK [Run maintenance start script] **********************************************************
changed: [nyc1-apiserver-1.example.com]
TASK [Confirm that 503 Unavailable response is returned] *************************************
fatal: [nyc1-apiserver-1.example.com]: FAILED! => changed=false
connection: close
content: ''
content_length: '0'
content_type: application/octet-stream
cookies: {}
cookies_string: ''
date: Fri, 11 Sep 2020 18:35:08 GMT
elapsed: 0
msg: 'Status code was 200 and not [503]: OK (0 bytes)'
redirected: false
server: nginx
status: 200
url: http://nyc1-apiserver-1.example.com/api/v1/healthcheck
PLAY RECAP ***********************************************************************************
nyc1-apiserver-1.example.com : ok=2 changed=1 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
Se il server non si ripristina correttamente dopo l'applicazione della patch, Ansible non riesce con un errore:
fsh$ ansible-playbook -i inventory.ini playbook-healthcheck.yml
PLAY [all] ***********************************************************************************
TASK [Gathering Facts] ***********************************************************************
ok: [nyc1-apiserver-1.example.com]
TASK [Run maintenance start script] **********************************************************
changed: [nyc1-apiserver-1.example.com]
TASK [Confirm that 503 Unavailable response is returned] *************************************
ok: [nyc1-apiserver-1.example.com]
TASK [Update application] ********************************************************************
changed: [nyc1-apiserver-1.example.com]
TASK [Run maintenance end script] ************************************************************
changed: [nyc1-apiserver-1.example.com]
TASK [Confirm that 200 OK response is returned] **********************************************
fatal: [nyc1-apiserver-1.example.com]: FAILED! => changed=false
connection: close
content: |-
<html>
<head><title>503 Service Temporarily Unavailable</title></head>
<body>
<center><h1>503 Service Temporarily Unavailable</h1></center>
<hr><center>nginx</center>
</body>
</html>
content_length: '190'
content_type: text/html; charset=utf-8
date: Fri, 11 Sep 2020 18:55:01 GMT
elapsed: 0
msg: 'Status code was 503 and not [200]: HTTP Error 503: Service Temporarily Unavailable'
redirected: false
server: nginx
status: 503
url: http://nyc1-apiserver-1.example.com/api/v1/healthcheck
PLAY RECAP ***********************************************************************************
nyc1-apiserver-1.example.com : ok=5 changed=3 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
Si tratta di semplici controlli che possono essere integrati in quasi tutti i playbook per aggiungere migliori garanzie di sicurezza prima di eseguire lavori dirompenti o per garantire che il lavoro dirompente abbia avuto successo prima di definirlo un successo.
L'analisi ha restituito JSON
L'esempio precedente funziona alla grande per semplici controlli di integrità basati sullo stato HTTP. Tuttavia, in genere vorrai recuperare alcuni dati da un endpoint Web e quindi fare qualcosa con i dati restituiti. Ad esempio:cosa succede se voglio controllare la versione dell'applicazione tramite un endpoint esposto ed eseguire gli aggiornamenti solo se non è aggiornato?
La mia applicazione demo ha proprio un tale endpoint. Quando richiesto, restituisce la versione corrente dell'applicazione:
fsh$ http nyc1-apiserver-1.example.com/api/v1/appVersion
HTTP/1.1 200 OK
Accept-Ranges: bytes
Connection: keep-alive
Content-Length: 24
Content-Type: application/json
Date: Fri, 11 Sep 2020 18:36:15 GMT
ETag: "5f5bc33b-18"
Last-Modified: Fri, 11 Sep 2020 18:34:35 GMT
Server: nginx
{
"appVersion": "1.0.1"
}
Nota :Curioso di quel comando HTTP che ho eseguito? Dai un'occhiata all'articolo del mio collega sudoer Jonathan Roemer su HTTPie.
Posso usare il JSON restituito da questo endpoint per prendere decisioni nel mio playbook Ansible. La versione precedente di questo playbook eseguiva sempre lo script di aggiornamento dell'applicazione. Tuttavia, posso migliorarlo aggiornando l'applicazione solo quando non soddisfa i requisiti della versione desiderata:
---
- hosts: all
vars:
desired_app_version: "1.0.1"
tasks:
- name: Check API version
uri:
url: "http://{{ ansible_host }}/api/v1/appVersion"
register: api_version_result
- name: Perform maintenance tasks
block:
- name: Run maintenance start script
command:
cmd: /usr/local/sbin/start_maintenance.sh
- name: Confirm that 503 Unavailable response is returned
uri:
url: "http://{{ ansible_host }}/api/v1/healthcheck"
status_code: 503
- name: Update application
command:
cmd: /usr/local/sbin/update_app.sh
- name: Run maintenance end script
command:
cmd: /usr/local/sbin/end_maintenance.sh
- name: Confirm that 200 OK response is returned
uri:
url: "http://{{ ansible_host }}/api/v1/healthcheck"
status_code: 200
- name: Check API version after updates
uri:
url: "http://{{ ansible_host }}/api/v1/appVersion"
register: updated_api_version_result
failed_when: updated_api_version_result['json']['appVersion'] != desired_app_version
when: api_version_result['json']['appVersion'] != desired_app_version
Questo playbook introduce alcuni utili concetti di Ansible. Innanzitutto, puoi vedere che il modulo URI raggiunge il /api/v1/appVersion
Endpoint API e registra l'output di questa chiamata URI in una variabile. Le attività di aggiornamento sono state spostate in un blocco, che consente il raggruppamento logico delle attività. L'aggiunta del quando La clausola fa sì che questo blocco venga eseguito solo se la versione corrente dell'app è diversa dalla versione dell'app desiderata, come restituito da /api/v1/appVersion
punto finale. Infine, ho aggiunto un ulteriore controllo al processo di aggiornamento. Una volta eseguiti gli aggiornamenti, un'altra chiamata a /api/v1/appVersion
endpoint garantisce che l'aggiornamento sia riuscito e che la versione corrente dell'app corrisponda alla versione desiderata. Questo utilizza la sintassi fail_when, che ti consente di definire criteri di errore specifici per le attività.
Espressa in un linguaggio semplice, questa logica di blocco Ansible dice:"Esegui gli script di installazione e manutenzione dell'applicazione solo se la versione corrente dell'app non soddisfa la versione desiderata dell'app. Al termine dell'aggiornamento, assicurati che l'app sia stata effettivamente aggiornata."
Utilizzando solo poche righe di codice Ansible, ho scritto un modo potente ma semplice per utilizzare JSON restituito da un endpoint API per prendere decisioni intelligenti nei miei playbook.
Interazione con un endpoint autenticato
Finora, ho trattato l'interazione con gli endpoint API che non richiedono l'autenticazione. Tuttavia, probabilmente sei più abituato a interagire con le API che richiedono un qualche tipo di autenticazione, come un token API. Il modulo URI supporta questo impostando le intestazioni e il corpo di una richiesta HTTP.
[ Potrebbero interessarti anche:9 guide Ansible per facilitare l'automazione ]
Posso fare un ulteriore passo avanti nel mio playbook di manutenzione disabilitando e riattivando gli avvisi su ciascun host nel mio sistema di monitoraggio. Ciò richiede l'invio di un POST richiesta a un endpoint API sul server di monitoraggio. La richiesta deve contenere il mio token API e l'host all'interno del corpo con codifica JSON. Ansible lo rende semplice. Ecco il playbook finale:
---
- hosts: all
vars:
desired_app_version: "1.0.1"
api_token: "8897e9a6-b10c-42c8-83a2-c83e9c8b6703"
tasks:
- name: Check API version
uri:
url: "http://{{ ansible_host }}/api/v1/appVersion"
register: api_version_result
- name: Perform maintenance tasks
block:
- name: Disable host in monitoring
uri:
url: "http://nyc1-monitoring-1.example.com/api/v1/startMaintenance"
method: POST
headers:
X-API-KEY: "{{ api_token }}"
body_format: json
body:
host: "{{ ansible_host }}"
- name: Run maintenance start script
command:
cmd: /usr/local/sbin/start_maintenance.sh
- name: Confirm that 503 Unavailable response is returned
uri:
url: "http://{{ ansible_host }}/api/v1/healthcheck"
status_code: 503
- name: Update application
command:
cmd: /usr/local/sbin/update_app.sh
- name: Run maintenance end script
command:
cmd: /usr/local/sbin/end_maintenance.sh
- name: Confirm that 200 OK response is returned
uri:
url: "http://{{ ansible_host }}/api/v1/healthcheck"
status_code: 200
- name: Check API version after updates
uri:
url: "http://{{ ansible_host }}/api/v1/appVersion"
register: updated_api_version_result
failed_when: updated_api_version_result['json']['appVersion'] != desired_app_version
- name: Re-enable host in monitoring
uri:
url: "http://nyc1-monitoring-1.example.com/api/v1/stopMaintenance"
method: POST
headers:
X-API-KEY: "{{ api_token }}"
body_format: json
body:
host: "{{ ansible_host }}"
when: api_version_result['json']['appVersion'] != desired_app_version
Ora sto usando il modulo URI per inviare POST HTTP richieste (invece dell'impostazione predefinita GET richieste) al /api/v1/startMaintenance
e /api/v1/stopMaintenance
endpoint su nyc1-monitoring-1.example.com . Queste richieste contengono il mio token API per il server di monitoraggio nell'intestazione e il nome host del server è incluso nel corpo. Se una delle due richieste non riesce con un 200 diverso codice di stato, l'intero playbook di Ansible ha esito negativo.
Nota :In pratica, ti consigliamo di utilizzare qualcosa come Ansible Vault per archiviare un token API, invece di inserirlo direttamente nel playbook.
Questa serie finale di attività mi consente di automatizzare completamente il mio flusso di lavoro di aggiornamento:eseguire controlli della versione, interagire con il monitoraggio esterno per disabilitare gli avvisi per un sistema e garantire che il server restituisca i codici di stato HTTP corretti prima e dopo l'applicazione delle patch. Ora ho un flusso di lavoro end-to-end che automatizza molti dei passaggi comuni che seguo quando eseguo gli aggiornamenti su un sistema.
[ Hai bisogno di più su Ansible? Partecipa a un corso di panoramica tecnica gratuito di Red Hat. Ansible Essentials:Semplicità nell'automazione Panoramica tecnica. ]
Conclusione
Questo articolo è iniziato con un semplice playbook che eseguiva controlli Web di base su endpoint API non autenticati. Ti ho guidato attraverso l'analisi delle risposte JSON e persino l'interazione con gli endpoint API autenticati impostando intestazioni personalizzate e contenuto del corpo sulle richieste HTTP. Ansible semplifica l'interazione con i servizi Web e sono certo che troverai usi per questo tipo di approccio, anche in ambienti semplici.
Se stai cercando di saperne di più e di metterti alla prova, ecco alcune idee da implementare da solo:
- Utilizza il modulo URI per interagire con il tuo servizio Web basato su API preferito.
- Vedi se riesci a capire come eseguire l'autenticazione con i certificati client.
- Scopri come inviare un modulo a un sito Web utilizzando Ansible.