Siempre estoy buscando cosas inteligentes que hacer con Ansible. Con tantas herramientas y servicios que aprovechan las interfaces de programación de aplicaciones (API) basadas en HTTP, está claro que interactuar con los servicios controlados por API de Ansible mediante programación es una capacidad valiosa. Esto puede sonar como una función avanzada, pero este artículo lo lleva a través de un caso de uso que demuestra cómo incluso los entornos simples pueden beneficiarse del poder y la simplicidad del módulo URI de Ansible.
Interactuar con puntos finales simples
Primero, lo guiaré a través de un libro de jugadas que aprovecha las capacidades HTTP de Ansible para tomar decisiones inteligentes durante una actualización del servidor web. El libro de jugadas a continuación:
- Ejecuta un script de mantenimiento.
- Comprobaciones para garantizar que un extremo de la API de comprobación de estado devuelva un HTTP Servicio 503 no disponible temporalmente mensaje.
- Ejecuta un script para actualizar la aplicación.
- Ejecuta una secuencia de comandos posterior al mantenimiento para indicarle al servidor web que comience a responder normalmente de nuevo.
- Vuelve a verificar la API de verificación de estado para asegurarse de que responda con 200 OK .
Aquí está el libro de jugadas:
---
- 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
Estoy usando el módulo URI de Ansible para comunicarme con /api/v1/healthcheck
en el servidor La primera llamada URI espera un HTTP 503 código de estado que se devolverá ya que el servidor debe estar en modo de mantenimiento y no atender solicitudes. Después de la actualización, la llamada URI espera un HTTP 200 código de estado, que indica que el servidor web vuelve a estar en buen estado.
Este enfoque simple mejora la seguridad de mi libro de jugadas. Si el servidor no puede ingresar al modo de mantenimiento, Ansible no realizará ningún parche:
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
Si el servidor no vuelve a funcionar correctamente después de parchear, entonces Ansible falla con un error:
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
Estos son controles simples que se pueden incorporar en casi cualquier libro de jugadas para agregar mejores garantías de seguridad antes de realizar un trabajo disruptivo o para garantizar que el trabajo disruptivo fue exitoso antes de llamarlo un éxito.
El análisis devolvió JSON
El ejemplo anterior funciona muy bien para comprobaciones de estado simples basadas en el estado de HTTP. Sin embargo, normalmente querrá recuperar algunos datos de un punto final web y luego hacer algo con los datos devueltos. Por ejemplo:¿Qué pasa si quiero verificar la versión de la aplicación a través de un punto final expuesto y solo realizar actualizaciones si no está actualizada?
Mi aplicación de demostración tiene tal punto final. Cuando se consulta, devuelve la versión actual de la aplicación:
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 :¿Tienes curiosidad por el comando HTTP que ejecuté? Consulte el artículo de mi compañero sudoer Jonathan Roemer sobre HTTPie.
Puedo usar el JSON devuelto desde este punto final para tomar decisiones en mi libro de jugadas de Ansible. La versión anterior de este libro de jugadas siempre ejecutaba el script de actualización de la aplicación. Sin embargo, puedo mejorar esto solo actualizando la aplicación cuando no cumple con los requisitos de la versión deseada:
---
- 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
Este libro de jugadas presenta algunos conceptos útiles de Ansible. Primero, puede ver que el módulo URI llega a /api/v1/appVersion
punto final de la API y registra la salida de esta llamada URI a una variable. Las tareas de actualización se han movido a un bloque, lo que permite la agrupación lógica de tareas. La adición del cuando hace que este bloque solo se ejecute si la versión actual de la aplicación es diferente de la versión deseada de la aplicación, según lo devuelto por /api/v1/appVersion
punto final Finalmente, agregué una verificación adicional al proceso de actualización. Una vez que se hayan ejecutado las actualizaciones, otra llamada a /api/v1/appVersion
endpoint garantiza que la actualización se haya realizado correctamente y que la versión actual de la aplicación coincida con la versión deseada. Esto usa la sintaxis fail_when, que le permite definir criterios de falla específicos para las tareas.
Expresado en un lenguaje sencillo, esta lógica de bloque de Ansible dice:“Solo ejecute los scripts de instalación y mantenimiento de la aplicación si la versión actual de la aplicación no cumple con la versión deseada de la aplicación. Una vez finalizada la actualización, asegúrese de que la aplicación realmente se haya actualizado.”
Usando solo unas pocas líneas de código Ansible, he escrito una forma poderosa pero simple de usar JSON devuelto desde un punto final de API para tomar decisiones inteligentes en mis libros de jugadas.
Interactuar con un punto final autenticado
Hasta ahora, he cubierto la interacción con puntos finales de API que no requieren autenticación. Sin embargo, probablemente esté más acostumbrado a interactuar con API que requieren algún tipo de autenticación, como un token de API. El módulo URI admite esto configurando los encabezados y el cuerpo de una solicitud HTTP.
[ También puede disfrutar:9 guías de Ansible para ayudarlo a facilitar la automatización ]
Puedo llevar mi libro de jugadas de mantenimiento un paso más allá al deshabilitar y volver a habilitar las alertas en cada host en mi sistema de monitoreo. Esto requiere enviar un POST solicitud a un extremo de la API en el servidor de supervisión. La solicitud debe contener mi token API y el host dentro del cuerpo codificado en JSON. Ansible hace esto simple. Aquí está el libro de jugadas final:
---
- 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
Ahora estoy usando el módulo URI para enviar HTTP POST solicitudes (en lugar del predeterminado GET solicitudes) al /api/v1/startMaintenance
y /api/v1/stopMaintenance
puntos finales en nyc1-monitoring-1.example.com . Estas solicitudes contienen mi token API para el servidor de monitoreo en el encabezado y el nombre de host del servidor está incluido en el cuerpo. Si cualquiera de las solicitudes falla con un valor distinto de 200 código de estado, todo el libro de jugadas de Ansible falla.
Nota :En la práctica, querrá usar algo como Ansible Vault para almacenar un token de API, en lugar de colocarlo directamente en el libro de jugadas.
Este conjunto final de tareas me permite automatizar por completo mi flujo de trabajo de actualización:realizar verificaciones de versiones, interactuar con el monitoreo externo para deshabilitar las alertas de un sistema y garantizar que el servidor devuelva los códigos de estado HTTP correctos antes y después de la aplicación de parches. Ahora tengo un flujo de trabajo integral que automatiza muchos de los pasos comunes que sigo cuando realizo actualizaciones en un sistema.
[ ¿Necesita más información sobre Ansible? Realice un curso gratuito de descripción técnica de Red Hat. Ansible Essentials:descripción técnica de la simplicidad en la automatización. ]
Conclusión
Este artículo comenzó con un libro de jugadas simple que realizó comprobaciones web básicas contra puntos finales de API no autenticados. Lo guié a través del análisis de respuestas JSON e incluso interactuando con puntos finales de API autenticados al configurar encabezados personalizados y contenido del cuerpo en solicitudes HTTP. Ansible facilita la interacción con los servicios web y estoy seguro de que encontrará usos para este tipo de enfoque, incluso en entornos simples.
Si está buscando aprender más y desafiarse a sí mismo, aquí hay algunas ideas para implementar por su cuenta:
- Utilice el módulo URI para interactuar con su servicio web basado en API favorito.
- Vea si puede descubrir cómo realizar la autenticación con certificados de cliente.
- Obtenga información sobre cómo enviar un formulario a un sitio web mediante Ansible.