Maximizando el rendimiento de Ansible con ControlPersist y Pipelining
Aumente significativamente el rendimiento de sus playbooks de Ansible habilitando la reutilización de conexiones SSH con ControlPersist y optimizando la ejecución de módulos mediante Pipelining. Esta guía proporciona información esencial y configuraciones prácticas para reducir los tiempos de ejecución, especialmente en entornos a gran escala. Aprenda a ajustar su `ansible.cfg` para una automatización de TI más rápida y eficiente.
Maximizando el rendimiento de Ansible con ControlPersist y Pipelining
Las ejecuciones lentas de Ansible suelen parecer misteriosas hasta que observas lo que sucede en la red. Un playbook con veinte tareas pequeñas contra cien hosts puede pasar una cantidad sorprendente de tiempo abriendo sesiones SSH, copiando archivos de módulos temporales, ejecutando Python, recopilando resultados y cerrando conexiones. El trabajo en el host remoto puede llevar milisegundos, pero la sobrecarga de conexión se repite una y otra vez.
Dos configuraciones suelen ayudar: la reutilización de conexiones SSH mediante ControlPersist y el pipelining de Ansible. No son interruptores mágicos y no solucionarán espejos de paquetes lentos, bases de datos sobrecargadas o tareas que realizan trabajo pesado. Lo que hacen es reducir la sobrecarga de comunicación evitable, que es exactamente donde muchos playbooks con tareas pequeñas pierden tiempo.
Primero, mida el problema actual
Antes de cambiar la configuración, ejecute el playbook una vez con el tiempo habilitado:
ANSIBLE_CALLBACKS_ENABLED=ansible.posix.profile_tasks ansible-playbook site.yml
Si ese callback no está instalado, use la salida de tiempo integrada más simple de su sistema de CI o envuelva el comando con time. El objetivo no es un benchmark perfecto. Quiere una línea base y una idea de si las tareas lentas son trabajo real o tareas pequeñas repetidas en muchos hosts.
Una prueba de humo útil es un ping ad-hoc en un inventario representativo:
time ansible all -m ping
Ejecútelo dos veces. Si la segunda ejecución es mucho más rápida después de configurar la reutilización de conexiones, habrá confirmado que el costo de configuración de SSH era parte del problema.
Qué cambia ControlPersist
ControlPersist es una característica de OpenSSH. Mantiene una conexión SSH maestra abierta durante un período de tiempo para que comandos SSH posteriores al mismo host, usuario y puerto puedan reutilizarla. Ansible comúnmente usa SSH para cada tarea, por lo que la multiplexación de conexiones elimina los handshakes repetidos.
Un ansible.cfg práctico a nivel de proyecto se ve así:
[defaults]
forks = 20
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=600s -o ControlPath=~/.ansible/cp/%%h-%%p-%%r
Cree el directorio de sockets con permisos privados:
mkdir -p ~/.ansible/cp
chmod 700 ~/.ansible ~/.ansible/cp
ControlMaster=auto le dice a SSH que use una conexión maestra existente cuando esté disponible y que cree una cuando no lo esté. ControlPersist=600s mantiene la maestra durante diez minutos después de que la última sesión termine. Ese valor no es sagrado. Para trabajos de CI cortos, unos minutos pueden ser suficientes. Para un operador que ejecuta playbooks repetidamente durante una ventana de mantenimiento, diez o quince minutos pueden ser cómodos.
El ControlPath importa más de lo que la gente espera. Las rutas de sockets Unix tienen límites de longitud en muchos sistemas. Un nombre de host de inventario largo dentro de una ruta de espacio de trabajo profunda puede romper la multiplexación con un error confuso. Mantener la ruta corta, como bajo ~/.ansible/cp, evita esa clase de fallo.
Las versiones recientes de Ansible ya habilitan la multiplexación SSH por defecto en muchas configuraciones normales, pero la configuración explícita en un ansible.cfg de proyecto hace que el comportamiento sea más fácil de auditar. Verifique la configuración activa con:
ansible-config dump --only-changed
Qué cambia el pipelining
Sin pipelining, Ansible a menudo copia un módulo a un directorio temporal en el host remoto, lo ejecuta y luego lo limpia. Con pipelining habilitado, Ansible puede pasar el código del módulo a través de la conexión SSH en lugar de escribir tantos archivos temporales. Eso ahorra viajes de ida y vuelta y trabajo en el sistema de archivos remoto.
Habilítelo en el mismo archivo:
[ssh_connection]
pipelining = True
O pruébelo para una ejecución:
ANSIBLE_PIPELINING=True ansible-playbook site.yml
La configuración es más notable cuando el playbook tiene muchos módulos pequeños: file, lineinfile, template, user, service y tareas de comando cortas. Importa menos cuando una tarea pasa la mayor parte del tiempo instalando paquetes, compilando software, transfiriendo artefactos grandes o esperando un servicio externo.
La trampa de sudo requiretty
El problema clásico del pipelining es requiretty en sudoers. Algunas configuraciones empresariales antiguas de Linux requerían un TTY para sudo. El pipelining no funciona bien con ese requisito porque Ansible intenta transmitir trabajo a través de SSH de forma no interactiva.
Revise sudoers cuidadosamente. No edite /etc/sudoers con un editor normal; use visudo:
sudo visudo
Si ve una línea global como esta:
Defaults requiretty
Puede que necesite eliminarla o anularla para el usuario de automatización de Ansible:
Defaults:ansible !requiretty
Solo haga ese cambio si coincide con la política de seguridad de su organización. En muchas distribuciones modernas, requiretty no está habilitado por defecto.
Una configuración combinada más segura
Para muchos equipos, este es un punto de partida razonable:
[defaults]
forks = 20
gathering = smart
fact_caching = jsonfile
fact_caching_connection = .ansible_facts
fact_caching_timeout = 86400
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=600s -o ControlPath=~/.ansible/cp/%%h-%%p-%%r
pipelining = True
forks controla el paralelismo. Está relacionado con el rendimiento, pero no es la misma optimización. Aumentar forks de 5 a 20 puede ayudar si su nodo de control y la red pueden manejarlo. Aumentarlo demasiado puede sobrecargar los hosts bastión, los repositorios de paquetes o los propios nodos gestionados.
El almacenamiento en caché de hechos ayuda a un tipo diferente de lentitud. Si sus playbooks recopilan hechos en cada ejecución y los hechos no necesitan estar actualizados cada minuto, el almacenamiento en caché puede eliminar el trabajo de configuración repetido. Úselo deliberadamente; los hechos obsoletos pueden ser confusos si depende de datos actuales de memoria, disco o interfaz.
Cómo probar sin engañarse a sí mismo
Pruebe en un subconjunto que se parezca a la producción. Cinco VMs de prueba inactivas en la misma subred no le dirán mucho sobre cien hosts mixtos detrás de un bastión.
Ejecute el mismo playbook antes y después del cambio. Limpie los sockets de control existentes entre ejecuciones de prueba si necesita una comparación en frío:
rm -f ~/.ansible/cp/*
time ansible-playbook site.yml --limit web
Luego ejecute una comparación en caliente:
time ansible-playbook site.yml --limit web
Busque menos segundos gastados en tareas pequeñas repetidas. También observe los modos de fallo. Si las tareas que usan become comienzan a fallar, investigue la configuración de sudo antes de culpar al pipelining en sí.
Cuándo estas configuraciones no ayudarán mucho
ControlPersist y pipelining no hacen que el trabajo remoto lento sea rápido. Si apt update espera en un espejo, la reutilización de conexión no le salvará. Si un reinicio de servicio espera treinta segundos porque la aplicación drena conexiones, el pipelining es irrelevante. Si su playbook copia un artefacto de 2 GB a cada host, concéntrese en la distribución de artefactos, el almacenamiento en caché o los repositorios de paquetes locales.
Tampoco reemplazan el diseño de playbooks idempotentes. Un playbook que ejecuta comandos de shell incondicionalmente seguirá perdiendo tiempo. Use módulos que puedan detectar el estado actual, agregue creates o removes a las tareas de comando cuando sea apropiado y evite recopilar hechos cuando el play no los use.
El enfoque práctico es simple: habilite ControlPersist con una ruta de control corta y privada; pruebe el pipelining con su política de sudo; ajuste forks gradualmente; y mida con el mismo inventario y carga de trabajo cada vez. En muchos entornos reales de Ansible, estos cambios convierten una ejecución lenta en una tolerable porque eliminan la sobrecarga repetida en lugar de ocultarla.
Hosts bastión y hosts de salto
Muchos inventarios no se conectan directamente a los nodos gestionados. Pasan a través de un bastión. ControlPersist sigue ayudando, pero debe pensar en ambas conexiones: nodo de control a bastión, y bastión a destino.
Una variable de inventario común se ve así:
[private]
app01 ansible_host=10.0.10.11
app02 ansible_host=10.0.10.12
[private:vars]
ansible_user=ansible
ansible_ssh_common_args='-o ProxyJump=bastion.example.com'
Si cada tarea construye repetidamente una conexión de salto, el bastión se convierte en parte de la sobrecarga. Mantenga la ruta de control corta y privada, y vigile los límites de conexión SSH del bastión. Un playbook que funciona bien contra diez hosts puede sobrecargar un bastión pequeño cuando forks se eleva a cincuenta.
Para clientes SSH más antiguos, puede ver ProxyCommand en lugar de ProxyJump:
ansible_ssh_common_args='-o ProxyCommand="ssh -W %h:%p bastion.example.com"'
Pruebe una conexión SSH simple antes de culpar a Ansible:
ssh -o ProxyJump=bastion.example.com [email protected] hostname
Si eso es lento, Ansible también lo será.
Pipelining y become en playbooks reales
El consejo antiguo de que el pipelining no funciona con la escalada de privilegios es demasiado amplio. El pipelining puede funcionar con become; el bloqueador común es sudo que requiere un TTY o una política que interfiere con la ejecución no interactiva. La única respuesta confiable es probar con la misma configuración de become que usan sus playbooks.
Un pequeño play de prueba es suficiente:
- hosts: all
become: true
gather_facts: false
tasks:
- name: Confirm privileged command works
ansible.builtin.command: id
changed_when: false
Ejecútelo con pipelining habilitado. Si falla, verifique sudoers, las solicitudes de autenticación y si el usuario de automatización puede ejecutar los comandos requeridos sin un mensaje de contraseña. Las solicitudes de contraseña en ejecuciones de automatización grandes son frágiles; también hacen que las mediciones de tiempo sean ruidosas.
Ajuste forks con respeto a las dependencias
Aumentar forks es tentador porque produce un cambio visible inmediato. También puede crear un nuevo cuello de botella. Si un play actualiza los cachés de paquetes en todos los hosts a la vez, un recuento alto de forks puede castigar el espejo de paquetes. Si cada host se reinicia y se reconecta a la misma base de datos a la vez, la base de datos ve la ráfaga.
Un enfoque medido es mejor:
[defaults]
forks = 10
Ejecute el play. Pruebe 20. Luego 30. Observe la CPU del nodo de control, el recuento de procesos SSH, la carga del bastión, la saturación de la red, el repositorio de paquetes y el servicio que se está cambiando. La configuración más rápida no siempre es la de mayor paralelismo. Para implementaciones de aplicaciones continuas, también puede querer serial en el playbook para proteger la disponibilidad:
- hosts: web
serial: 10
forks controla cuánto puede hacer Ansible a la vez. serial controla cuántos hosts debe procesar el play a la vez. Resuelven problemas diferentes.
Limpie los sockets de control obsoletos
Los sockets de control generalmente se limpian solos, pero las laptops se duermen, los trabajos de CI se matan y las rutas de red cambian. Si SSH comienza a reportar errores de multiplexación, elimine los sockets obsoletos:
rm -f ~/.ansible/cp/*
Eso es seguro cuando no hay ninguna ejecución de Ansible activa para esos sockets. En runners de automatización compartidos, evite colocar sockets de control en un directorio compartido escribible. Cada usuario de automatización debe tener su propia ruta privada.
El diseño del inventario puede borrar las ganancias de rendimiento
El ajuste de conexión ayuda, pero el diseño del inventario aún puede ralentizar todo. Los scripts de inventario dinámico que llaman a APIs en la nube lentamente, las vars de grupo que ejecutan búsquedas costosas y los playbooks que recopilan hechos para hosts que nunca tocan pueden agregar demora antes de que SSH siquiera comience.
Si una ejecución se detiene antes de la primera tarea, perfile la carga del inventario. Almacene en caché el inventario dinámico donde el plugin lo soporte. Evite búsquedas costosas de variables en tiempo de análisis. Mantenga los patrones de host ajustados:
ansible-playbook site.yml --limit web:&prod
Ese comando apunta a hosts tanto en web como en prod. Ejecutar un play amplio contra all y luego saltar la mayoría de las tareas con condiciones when pierde tiempo y hace que la salida sea más difícil de leer.
Prefiera menos tareas, más claras cuando el estado está relacionado
La legibilidad de Ansible importa, pero las tareas excesivamente pequeñas pueden hacer que la sobrecarga de conexión sea más visible. Si establece diez líneas relacionadas en un archivo de configuración con diez tareas lineinfile separadas, paga la sobrecarga de tareas diez veces y hace que el razonamiento de reversión sea más difícil. Una plantilla puede ser más clara y rápida:
- name: Render application config
ansible.builtin.template:
src: app.conf.j2
dest: /etc/app/app.conf
mode: '0644'
notify: restart app
Esto no es un argumento para scripts de shell gigantes dentro de Ansible. Es un recordatorio para modelar el estado deseado en el nivel correcto. Una plantilla para un archivo de configuración suele ser mejor que muchas microediciones.
Evite cambios globales prematuros
Ponga las configuraciones de rendimiento en el ansible.cfg del proyecto cuando sea posible. Cambiar /etc/ansible/ansible.cfg en un nodo de control compartido puede sorprender a otros equipos. Un archivo a nivel de proyecto hace que el comportamiento viaje con el repositorio y mantiene CI, laptops y runners de automatización más cerca de la misma configuración.
Confirme qué archivo de configuración está usando Ansible:
ansible --version
La salida incluye la ruta de configuración activa. Esto detecta un error común: editar un ansible.cfg mientras Ansible está leyendo otro.
Sepa cuándo dejar de ajustar Ansible
Si la sobrecarga de conexión ya no es una parte importante del tiempo de ejecución, deje de ajustar SSH y mire el trabajo. Las actualizaciones de caché de paquetes, las extracciones de contenedores, las migraciones de bases de datos, las comprobaciones de salud de servicios y las llamadas a APIs en la nube a menudo dominan los playbooks maduros. En ese punto, la mejor optimización puede ser un espejo de paquetes local, almacenamiento en caché de artefactos, lotes de implementación más pequeños o mover el aprovisionamiento único fuera de cada ejecución de implementación.