Blog

Acelerando playbooks de Ansible

plabook_ansible.png

La automatización de tareas es una parte esencial de la administración de sistemas y la gestión de infraestructuras, ¿qué duda cabe? Sus ventajas están más que demostradas, tanto para la calidad de vida de las personas como para la optimización de procesos empresariales, sobre todo en empresas que desarrollan la cultura devops en sus equipos.

Pero no siempre es así. Automatizar sistemas tiene un coste en esfuerzo, a veces elevado, que debe ser recompensado en ahorro y optimización del tiempo global empleado en la implantación y mantenimiento de forma recurrente de dichos sistemas. ¿Cuántas veces nos hemos encontrado con automatizaciones anidadas de tareas, que provisionan y configuran sistemas que han ralentizado tanto su ejecución, que se llega a cuestionar su utilidad, valorando incluso volver a la implantación manual por los equipos de sysadmin? Seguro que más veces de las deseadas, perjudicando y reduciendo las ventajas de los objetivos de la automatización.

Hoy te contamos cómo optimizar y acelerar la ejecución de tus automatizaciones de Ansible.

Analizar el motivo de dicha lentitud es la clave. Hemos agrupado los seis motivos más comunes que nos permitirán reducir drasticamente los tiempos de ejecución de nuestros playbooks.

1. Poco uso de cpu: aumento de hilos de ejecución de Ansible

Si al ejecutar un playbook de Ansible lento, detectamos que el uso de cpu es muy bajo, puede que no estés utilizando suficientes hilos de ejecución de Ansible en diferentes hosts de forma concurrente. Para ello, revisa en la configuración de ansible.cfg el parámetro “forks”.


[defaults]
forks = 16

Puedes incrementar de forma controlada los hilos que vas utilizando hasta dar con una configuración óptima del uso de CPU en tu caso de uso. Es recomendable utilizar un hilo por cada core de CPU disponible. No obstante, de forma empírica, funciona bien sobrecargar/preparar dos hilos por core de CPU disponible, para minimizar los tiempos de espera de creación de un nuevo proceso cuando termina el anterior, ya que son llamadas al sistema históricamente más lentas.

2. Saturación de CPU: aumentar número de cores

Si por el contrario, al lanzar el playbook lento, detectas saturación en las CPUs disponibles, puedes optar por aumentar el número de cores de CPU, siempre que sea posible, y reducir el número de hilos de ejecución concurrente de Ansible sobre los diferentes hosts, disminuyendo el parámetro fork del punto anterior. Esto obviamente será un un parche TEMPORAL, mientras analizas el origen de este consumo excesivo de CPU e implementas la solución definitiva.

3. Divide y vencerás: simplificación y división de tareas lentas

Debes localizar qué tareas llevan mayor tiempo y consumo de CPU en tu ejecución. Después divídelas en varias tareas más pequeñas y livianas con las que obtengas el mismo resultado final. Esto te permitirá reducir el tiempo/CPU de forma unitaria para poder aplicarle otras optimizaciones. Si aún así son muy pesadas o no puedes dividirlas, puedes limitar su ejecución concurrente utilizando el parámetro “throttle” en la propia tarea. Esto nos permitirá limitar el número de workers concurrentes por hosts y aliviarán el uso de CPU concurrente de la tarea identificada. Recuerda que el parámetro throttle debe siempre ser inferior al parámetro fork para que tenga efecto.


tasks:
- command: /comando-uso-intensivo-cpu
  throttle: 1

4. Ejecuciones lentas de tareas en múltiples hosts: strategy free

En este caso, en la ejecución del playbook lento no detectamos consumos de CPUs altos, y el incremento del parámetro fork no ha sido efectivo. Da la sensación que no llega a aprovechar del todo los recursos. Lo que ocurre es que Ansible por defecto utiliza una estrategia de ejecución lineal. Podemos verlo definido así en el fichero de ansible.cfg o directamente no tener nada definido, ya que es la configuración por defecto de Ansible.


[defaults]
strategy = linear

Este modo de funcionamiento consiste en una ejecución paralela en “n” hosts de una misma tarea, pero no comenzará la siguiente tarea hasta que todos los hosts hayan completado dicha tarea. Esto es una protección que nos proporciona Ansible, ya que desconoce las posibles implicaciones que pueda tener que unos hosts ejecuten la siguiente tarea sin que otros hosts hayan terminado. Si en tu caso de uso esto no supone un problema, puedes utilizar la estrategia “free”. Lo puedes hacer cambiando la configuración en tu fichero ansible.cfg .


[defaults]
strategy = free

O en tu playbook, si no te interesa cambiarlo a nivel global:


- hosts: webservers
  strategy: free
  tasks:
  # …

5. Evitar uso de facts de forma innecesaria

En muchas configuraciones no necesitamos el uso de facts en Ansible, por lo que desactivarlo nos ahorra tiempo de ejecución en cada host a desplegar. Para ello, podemos desactivarlo directamente en el playbook con el parámetro gather_facts:


- hosts: webservers
  gather_facts: no
  tasks:
  # ...

Cabe destacar que esta optimización sólo se notará de forma efectiva en su conjunto si lanzas el playbook a muchos hosts a la vez, ya que solo se ejecuta una vez por cada host al inicio del playbook.

6. Ejecuciones lentas de tareas en un solo host: ejecución asíncrona

Por último, veremos el caso más complejo. Una vez detectados y solucionados los puntos vistos anteriormente, si seguimos observando tiempos de ejecución excesivos, tenemos que seguir analizando el comportamiento del playbook lento.

Al verlo en detalle nos encontramos con una ejecución concurrente en diferentes hosts bastante óptima, pero algunas tareas que tardan mucho en ejecutarse en un solo host. Además, algunas de estas tareas son iteraciones que podrían ser ejecutadas de forma paralela sin perjudicar al resultado final, pero no es así debido a que el proceso de cada iteración es síncrono. Es decir, hasta que no acaba la ejecución de un “ítem” no comienza el siguiente, y esto perjudica el tiempo de ejecución final. Para mejorarlo tenemos que ejecutar estas iteraciones de forma asíncrona. ¿Es posible? Vamos a ver un sencillo ejemplo:


- hosts: webservers
  strategy: free
  gather_facts: no
  tasks:
    - name: ejecución de iteración en paralelo. Ejecución asíncrona.
      command: "/bin/sleep {{ item }}"  # operación normalmente lenta sobre la iteración de elementos. Lo simulamos con el comando sleep y los segundos de espera en cada iteración.
      loop:
        - 10
        - 7
        - 12
        - 9
      register: procesos_asincronos  # registramos la salida de cada ejecución
      async: 600  # ejecución máxima en segundos de cada iteración, para evitar bloqueos por falta de respuesta.
      poll: 0  # no hacemos polling, continúa con la siguiente iteración

    - name: esperamos la finalización de las anteriores ejecuciones
      async_status:
        jid: "{{ item.ansible_job_id }}"
      register: estado_procesos
      until: estado_procesos.finished
      delay: 1      # comprobamos el estado cada segundo
      retries: 600  # reintentamos durante 600 segundos
      loop: "{{ procesos_asincronos.results }}"

Es importante que las tareas iterativas que vas a ejecutar de forma asíncrona no tengan relación entre sí, para que el resultado en distinto orden de cada una de ellas no afecte al resultado del conjunto. Si es así, obtendrás una mejora de tiempos de ejecución considerable en esta tarea lenta iterativa, ejecutada de forma paralela.

 

Esperamos que con estos consejos puedas acelerar las ejecuciones de tus playbook de Ansible, manteniendo en todo momento las ventajas de automatizar la configuración de tus sistemas y el despliegue de la infraestructura.

 

Newsletter de STR Sistemas

Suscríbete a nuestra newsletter para recibir contenido interesante del mundo DevOps y artículos escritos por nuestros técnicos

¡Usamos cookies propias y de terceros para mejorar tu experiencia en esta web! Si sigues navegando, consientes y aceptas estas cookies en tu ordenador, móvil o tablet.

Más información sobre las cookies y cómo cambiar su configuración en tu navegador aquí.

x