Saltar al contenido principal

Ansible

Definición

Ansible es una herramienta de automatización de código abierto creada por Red Hat que gestiona la configuración, el despliegue de aplicaciones y la automatización de tareas en una flota de servidores usando archivos YAML simples y legibles por humanos llamados playbooks. Su elección arquitectónica definitoria es ser sin agentes: Ansible se conecta a nodos gestionados a través de SSH (Linux) o WinRM (Windows) y ejecuta tareas directamente, sin necesidad de software daemon o agente en las máquinas de destino. Esto lo hace significativamente más fácil de adoptar que las herramientas basadas en agentes — se pueden gestionar servidores existentes sin ningún software preinstalado más allá de Python y un servidor SSH.

Ansible opera con un modelo push: un operador ejecuta un playbook desde un nodo de control, Ansible se conecta al inventario objetivo de hosts y ejecuta tareas en orden. Las tareas llaman a módulos — unidades de trabajo idempotentes que saben cómo instalar paquetes, gestionar archivos, iniciar servicios, ejecutar comandos e interactuar con APIs de nube. Los módulos comunitarios y oficiales cubren prácticamente todos los gestores de paquetes de Linux, servicios, proveedores de nube, dispositivos de red y aplicaciones. Los roles agrupan tareas, archivos, plantillas y variables relacionados en unidades reutilizables y compartibles que pueden publicarse en Ansible Galaxy o mantenerse en repositorios Git internos.

En contextos de ML e ingeniería de datos, Ansible llena el vacío que deja Terraform. Terraform aprovisiona infraestructura (crea la instancia GPU, la VPC, el bucket S3); Ansible configura lo que se ejecuta en esa infraestructura (instala la versión correcta de CUDA, configura el entorno Python, configura las dependencias de entrenamiento distribuido y garantiza que las herramientas de monitoreo de GPU estén en ejecución). Las dos herramientas son complementarias en lugar de competidoras: un flujo de trabajo típico de MLOps usa Terraform para aprovisionar recursos en la nube y Ansible para arrancar esos recursos en un estado listo para el entrenamiento.

Cómo funciona

Inventario

El inventario define qué hosts gestiona Ansible. Un inventario estático es un archivo INI o YAML que lista nombres de host o direcciones IP agrupadas por rol (p. ej., [gpu_training_nodes], [model_serving]). Los inventarios dinámicos consultan las APIs de la nube (AWS EC2, GCP Compute, Azure VMs) en tiempo de ejecución para construir la lista de hosts a partir de la infraestructura en vivo — esencial para entornos de autoescalado. Las variables de host y grupo definen valores de configuración por host o por grupo que se referencian en los playbooks.

Playbooks y tareas

Un playbook es un archivo YAML que contiene uno o más plays. Cada play apunta a un grupo de hosts y una lista de tareas. Cada tarea llama a un módulo con argumentos y opcionalmente define condiciones (when), bucles (loop) y manejadores activados por cambios. Las tareas se ejecutan secuencialmente dentro de un play; los plays pueden ejecutarse en paralelo entre hosts. El resultado de cada tarea es uno de: ok (no se necesita cambio), changed (se realizó el cambio), failed o skipped. Ansible imprime un resumen de estos resultados después de cada ejecución de playbook.

Roles

Los roles proporcionan una estructura de directorios estandarizada para organizar la automatización relacionada: tasks/, handlers/, templates/, files/, vars/, defaults/ y meta/. Un rol puede aplicarse a múltiples plays en múltiples playbooks, y los roles pueden depender de otros roles. Ansible Galaxy alberga miles de roles de la comunidad (p. ej., geerlingguy.docker, nvidia.nvidia_driver) que pueden instalarse con ansible-galaxy install y usarse directamente en playbooks.

Variables y plantillas

Ansible usa el motor de plantillas Jinja2 en todos los playbooks y archivos de plantilla. Las variables pueden definirse en múltiples niveles (valores predeterminados de rol, vars de grupo, vars de host, vars de playbook, vars adicionales pasadas con -e) con un orden de precedencia claro. Las plantillas (archivos .j2) generan archivos de configuración dinámicamente — por ejemplo, generando un archivo de configuración de entrenamiento distribuido con la IP correcta del nodo maestro, el número de GPUs y el tamaño de lote para cada entorno.

Idempotencia y manejadores

Los módulos de Ansible están diseñados para ser idempotentes: ejecutar un playbook varias veces produce el mismo estado final sin causar efectos secundarios no deseados. Si un paquete ya está instalado en la versión correcta, la tarea reporta ok y no hace nada. Los manejadores son tareas especiales que se ejecutan al final de un play solo si son notificados por una tarea que resultó en changed — utilizados para reiniciar servicios (como un daemon de entrenamiento acelerado por CUDA) solo cuando su configuración realmente cambia.

Cuándo usar / Cuándo NO usar

Usar cuandoEvitar cuando
Se configura software en servidores existentes: instalar CUDA, Python, paquetes pip, servicios del sistemaSe aprovisiona nueva infraestructura en la nube desde cero (usar Terraform para eso)
Se arrancan nodos de entrenamiento GPU después de que Terraform los creaSe necesita seguimiento de estado detallado en cientos de recursos (Ansible no tiene archivo de estado)
Se configuran entornos ML coherentes en máquinas de desarrollo, staging y producciónSe necesitan grafos de dependencias complejos entre recursos de nube con ordenamiento automático
Se ejecutan comandos ad-hoc en una flota de servidores (p. ej., actualizar un archivo de configuración en todas partes)Las máquinas de destino no son accesibles vía SSH o WinRM desde el nodo de control
Se despliegan actualizaciones de aplicaciones o se lanzan cambios de configuración en muchos nodosSe aprovisiona recursos nativos de nube (VPCs, roles IAM, buckets S3) — usar Terraform
Equipos que necesitan herramientas IaC con poca barrera de aprendizaje YAMLSe necesita ejecución paralela muy rápida; la sobrecarga SSH de Ansible limita la escalabilidad con miles de nodos

Comparaciones

CriterioAnsibleTerraform
ParadigmaProcedimental con módulos idempotentes — las tareas se ejecutan en ordenDeclarativo — describir el estado deseado, Terraform calcula el diferencial
Gestión de estadoSin estado — sin seguimiento integrado de lo aplicado previamenteArchivo de estado explícito que mapea la configuración a los IDs de recursos reales
Caso de uso principalGestión de configuración y despliegue de software en hosts existentesAprovisionamiento de infraestructura en la nube (instancias, redes, almacenamiento)
Soporte de proveedores de nubeExisten módulos de nube pero son menos completos que los proveedores de TerraformMás de 1.000 proveedores con cobertura API profunda y versionada
IdempotenciaA nivel de tarea — cada módulo debe escribirse de forma idempotenteNativa — plan/apply siempre converge al estado declarado
Curva de aprendizajeBaja — las tareas YAML son legibles; no se requiere nuevo lenguajeModerada — sintaxis HCL + modelo mental de estado/plan a aprender
¿Requiere agente?No — sin agentes, se conecta vía SSHNo — Terraform se ejecuta en la máquina de control, llama a las APIs de la nube
Cuándo usar ambosAnsible configura software en infraestructura que Terraform ha aprovisionadoTerraform aprovisiona recursos; Ansible gestiona la configuración del SO y la aplicación

Ventajas y desventajas

AspectoVentajasDesventajas
Arquitectura sin agentesSin software que instalar en nodos de destino; funciona con SSH existenteLa sobrecarga SSH limita el rendimiento a escala muy grande (más de 10.000 nodos)
Playbooks YAMLAutomatización legible y auto-documentadaLa lógica compleja (bucles, condicionales) se vuelve verbose en YAML
Módulos idempotentesSeguro de re-ejecutar; corrección de deriva sin efectos secundariosLa idempotencia depende de la calidad del módulo; los módulos shell/command no son inherentemente idempotentes
Ansible GalaxyGran ecosistema de roles de la comunidad para software comúnLa calidad de los roles de la comunidad varía; fijar versiones de roles es crítico para la reproducibilidad
Sin archivo de estadoSimple, sin sobrecarga de gestión de estadoSin detección de deriva integrada entre ejecuciones; se requieren herramientas manuales o de terceros
Plantillas Jinja2Generación de configuración dinámica poderosaLa depuración de plantillas es más difícil que el código nativo; los errores aparecen en tiempo de ejecución

Ejemplos de código

# ml_environment_setup.yml
# Ansible playbook to configure a GPU training node for ML workloads.
# Installs CUDA toolkit, cuDNN, Python 3.11, pip packages, and sets up
# a systemd service for the Prometheus node exporter.
#
# Usage:
# ansible-playbook -i inventory.ini ml_environment_setup.yml
#
# inventory.ini example:
# [gpu_training_nodes]
# 10.0.1.10 ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/ml-key.pem
# 10.0.1.11 ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/ml-key.pem

---
- name: Configure GPU training nodes for ML workloads
hosts: gpu_training_nodes
become: true # Run tasks as root via sudo
vars:
cuda_version: "12.1"
python_version: "3.11"
pip_packages:
- torch==2.3.0
- torchvision==0.18.0
- torchaudio==2.3.0
- numpy==1.26.4
- pandas==2.2.2
- scikit-learn==1.4.2
- mlflow==2.13.0
- evidently==0.4.30
- prometheus-client==0.20.0
node_exporter_version: "1.8.1"
ml_user: "mlops"
ml_workdir: "/opt/ml"

handlers:
- name: restart node_exporter
ansible.builtin.systemd:
name: node_exporter
state: restarted
daemon_reload: true

tasks:
# --- System prerequisites ---

- name: Update apt package cache
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600 # Skip update if cache is less than 1 hour old

- name: Install system dependencies
ansible.builtin.apt:
name:
- build-essential
- git
- wget
- curl
- htop
- nvtop # GPU monitoring in terminal
- python{{ python_version }}
- python{{ python_version }}-dev
- python{{ python_version }}-venv
- python3-pip
state: present

# --- CUDA installation ---

- name: Check if CUDA {{ cuda_version }} is already installed
ansible.builtin.command: nvcc --version
register: nvcc_check
changed_when: false
failed_when: false

- name: Add CUDA repository keyring
ansible.builtin.shell: |
wget -qO /tmp/cuda-keyring.deb \
https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/cuda-keyring_1.1-1_all.deb
dpkg -i /tmp/cuda-keyring.deb
when: cuda_version not in (nvcc_check.stdout | default(''))
args:
creates: /usr/share/keyrings/cuda-archive-keyring.gpg

- name: Install CUDA toolkit {{ cuda_version }}
ansible.builtin.apt:
name: cuda-toolkit-{{ cuda_version | replace('.', '-') }}
state: present
update_cache: true
when: cuda_version not in (nvcc_check.stdout | default(''))

- name: Set CUDA environment variables in /etc/environment
ansible.builtin.lineinfile:
path: /etc/environment
line: "{{ item }}"
state: present
loop:
- 'CUDA_HOME=/usr/local/cuda'
- 'PATH=/usr/local/cuda/bin:$PATH'
- 'LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH'

# --- ML user and workspace ---

- name: Create dedicated ML user
ansible.builtin.user:
name: "{{ ml_user }}"
shell: /bin/bash
home: "/home/{{ ml_user }}"
create_home: true
state: present

- name: Create ML working directory
ansible.builtin.file:
path: "{{ ml_workdir }}"
state: directory
owner: "{{ ml_user }}"
group: "{{ ml_user }}"
mode: "0755"

# --- Python virtual environment and packages ---

- name: Create Python virtual environment
ansible.builtin.command:
cmd: python{{ python_version }} -m venv {{ ml_workdir }}/venv
creates: "{{ ml_workdir }}/venv/bin/python"
become_user: "{{ ml_user }}"

- name: Upgrade pip in virtual environment
ansible.builtin.pip:
name: pip
state: latest
virtualenv: "{{ ml_workdir }}/venv"
become_user: "{{ ml_user }}"

- name: Install ML Python packages
ansible.builtin.pip:
name: "{{ pip_packages }}"
virtualenv: "{{ ml_workdir }}/venv"
state: present
become_user: "{{ ml_user }}"

- name: Write requirements.txt for reproducibility
ansible.builtin.copy:
dest: "{{ ml_workdir }}/requirements.txt"
content: "{{ pip_packages | join('\n') }}\n"
owner: "{{ ml_user }}"
group: "{{ ml_user }}"
mode: "0644"

# --- Prometheus Node Exporter for infrastructure monitoring ---

- name: Check if node_exporter is already installed
ansible.builtin.stat:
path: /usr/local/bin/node_exporter
register: node_exporter_stat

- name: Download Prometheus node_exporter {{ node_exporter_version }}
ansible.builtin.get_url:
url: "https://github.com/prometheus/node_exporter/releases/download/v{{ node_exporter_version }}/node_exporter-{{ node_exporter_version }}.linux-amd64.tar.gz"
dest: /tmp/node_exporter.tar.gz
mode: "0644"
when: not node_exporter_stat.stat.exists

- name: Extract and install node_exporter
ansible.builtin.unarchive:
src: /tmp/node_exporter.tar.gz
dest: /tmp
remote_src: true
when: not node_exporter_stat.stat.exists

- name: Copy node_exporter binary to /usr/local/bin
ansible.builtin.copy:
src: "/tmp/node_exporter-{{ node_exporter_version }}.linux-amd64/node_exporter"
dest: /usr/local/bin/node_exporter
mode: "0755"
remote_src: true
when: not node_exporter_stat.stat.exists
notify: restart node_exporter

- name: Create node_exporter systemd service
ansible.builtin.copy:
dest: /etc/systemd/system/node_exporter.service
content: |
[Unit]
Description=Prometheus Node Exporter
After=network.target

[Service]
User=nobody
ExecStart=/usr/local/bin/node_exporter \
--collector.systemd \
--collector.processes
Restart=on-failure

[Install]
WantedBy=multi-user.target
mode: "0644"
notify: restart node_exporter

- name: Enable and start node_exporter
ansible.builtin.systemd:
name: node_exporter
enabled: true
state: started
daemon_reload: true

# --- Verification ---

- name: Verify GPU is visible to CUDA
ansible.builtin.command: nvidia-smi
register: nvidia_smi_output
changed_when: false
failed_when: nvidia_smi_output.rc != 0

- name: Print GPU info
ansible.builtin.debug:
var: nvidia_smi_output.stdout_lines

- name: Verify PyTorch can see the GPU
ansible.builtin.command:
cmd: "{{ ml_workdir }}/venv/bin/python -c \"import torch; print('CUDA available:', torch.cuda.is_available()); print('GPU count:', torch.cuda.device_count())\""
register: torch_check
changed_when: false
become_user: "{{ ml_user }}"

- name: Print PyTorch GPU availability
ansible.builtin.debug:
var: torch_check.stdout_lines

Recursos prácticos

  • Documentación de Ansible — Documentación oficial que cubre playbooks, módulos, roles, inventario y mejores prácticas.
  • Ansible Galaxy — Centro comunitario para roles y colecciones de Ansible reutilizables, incluyendo roles para controladores GPU de NVIDIA, Docker y Kubernetes.
  • Jeff Geerling — Ansible para DevOps — Libro completo y repositorio GitHub que cubre Ansible desde los conceptos básicos hasta los patrones de producción.
  • Colección NVIDIA Ansible — Colección oficial de NVIDIA Ansible para gestionar instalaciones de controladores GPU, CUDA y NCCL.
  • Guía de mejores prácticas de Ansible — Consejos y trucos oficiales sobre estructura de directorios, gestión de variables y optimización del rendimiento.

Ver también