Pular para o conteúdo principal

Ansible

Definição

Ansible é uma ferramenta de automação de código aberto criada pela Red Hat que lida com gerenciamento de configuração, implantação de aplicações e automação de tarefas em uma frota de servidores usando arquivos YAML simples e legíveis por humanos chamados playbooks. Sua escolha arquitetural definidora é ser sem agente: o Ansible se conecta aos nós gerenciados via SSH (Linux) ou WinRM (Windows) e executa tarefas diretamente, sem necessidade de daemon ou software agente nas máquinas alvo. Isso o torna significativamente mais fácil de adotar do que ferramentas baseadas em agentes — você pode começar a gerenciar servidores existentes sem nenhum software pré-instalado além do Python e de um servidor SSH.

O Ansible opera em um modelo push: um operador executa um playbook a partir de um nó de controle, o Ansible se conecta ao inventário de hosts alvo e executa as tarefas em ordem. As tarefas chamam módulos — unidades de trabalho idempotentes que sabem como instalar pacotes, gerenciar arquivos, iniciar serviços, executar comandos e interagir com APIs de nuvem. Módulos comunitários e oficiais cobrem virtualmente todo gerenciador de pacotes Linux, serviço, provedor de nuvem, dispositivo de rede e aplicação. Os roles agrupam tarefas, arquivos, templates e variáveis relacionados em unidades reutilizáveis e compartilháveis que podem ser publicadas no Ansible Galaxy ou mantidas em repositórios Git internos.

Em contextos de ML e engenharia de dados, o Ansible preenche a lacuna que o Terraform deixa. O Terraform provisiona infraestrutura (cria a instância de GPU, a VPC, o bucket S3); o Ansible configura o que roda nessa infraestrutura (instala a versão correta do CUDA, configura o ambiente Python, configura dependências de treinamento distribuído e garante que as ferramentas de monitoramento de GPU estejam em execução). As duas ferramentas são complementares, não concorrentes: um workflow típico de MLOps usa o Terraform para provisionar recursos de nuvem e o Ansible para inicializar esses recursos em um estado pronto para treinar.

Como funciona

Inventário

O inventário define quais hosts o Ansible gerencia. Um inventário estático é um arquivo INI ou YAML listando nomes de hosts ou endereços IP agrupados por função (por exemplo, [gpu_training_nodes], [model_serving]). Inventários dinâmicos consultam APIs de nuvem (AWS EC2, GCP Compute, Azure VMs) em tempo de execução para construir a lista de hosts a partir da infraestrutura em uso — essencial para ambientes de autoescalonamento. Variáveis de host e grupo definem valores de configuração por host ou por grupo que são referenciados nos playbooks.

Playbooks e tarefas

Um playbook é um arquivo YAML contendo uma ou mais plays. Cada play tem como alvo um grupo de hosts e uma lista de tarefas. Cada tarefa chama um módulo com argumentos e opcionalmente define condições (when), loops (loop) e handlers acionados em caso de mudança. As tarefas são executadas sequencialmente dentro de uma play; as plays podem ser executadas em paralelo entre os hosts. O resultado de cada tarefa é um dos seguintes: ok (nenhuma mudança necessária), changed (mudança foi feita), failed ou skipped. O Ansible imprime um resumo desses resultados após cada execução de playbook.

Roles

Os roles fornecem uma estrutura de diretório padronizada para organizar automação relacionada: tasks/, handlers/, templates/, files/, vars/, defaults/ e meta/. Um role pode ser aplicado a múltiplas plays em múltiplos playbooks, e roles podem depender de outros roles. O Ansible Galaxy hospeda milhares de roles comunitários (por exemplo, geerlingguy.docker, nvidia.nvidia_driver) que podem ser instalados com ansible-galaxy install e usados diretamente nos playbooks.

Variáveis e templating

O Ansible usa o motor de templating Jinja2 em todos os playbooks e arquivos de template. As variáveis podem ser definidas em múltiplos níveis (padrões de role, vars de grupo, vars de host, vars de playbook, vars extras passadas com -e) com uma ordem de precedência clara. Templates (arquivos .j2) geram arquivos de configuração dinamicamente — por exemplo, gerando um arquivo de configuração de treinamento distribuído com o IP correto do nó master, número de GPUs e tamanho de batch para cada ambiente.

Idempotência e handlers

Os módulos do Ansible são projetados para ser idempotentes: executar um playbook várias vezes produz o mesmo estado final sem causar efeitos colaterais indesejados. Se um pacote já estiver instalado na versão correta, a tarefa reporta ok e não faz nada. Handlers são tarefas especiais que são executadas no final de uma play somente se notificadas por uma tarefa que resultou em changed — usadas para reiniciar serviços (como um daemon de treinamento acelerado por CUDA) apenas quando sua configuração realmente muda.

Quando usar / Quando NÃO usar

Usar quandoEvitar quando
Configurando software em servidores existentes: instalando CUDA, Python, pacotes pip, serviços do sistemaProvisionando nova infraestrutura de nuvem do zero (use o Terraform para isso)
Inicializando nós de treinamento de GPU depois que o Terraform os criaVocê precisa de rastreamento de estado detalhado em centenas de recursos (o Ansible não tem arquivo de estado)
Configurando ambientes de ML consistentes entre máquinas de desenvolvimento, staging e produçãoVocê precisa de grafos de dependência complexos entre recursos de nuvem com ordenação automática
Executando comandos ad-hoc em uma frota de servidores (por exemplo, atualizar um arquivo de configuração em todos)As máquinas alvo não podem ser alcançadas via SSH ou WinRM a partir do nó de controle
Implantando atualizações de aplicações ou distribuindo mudanças de configuração em muitos nósVocê está provisionando recursos nativos de nuvem (VPCs, funções IAM, buckets S3) — use o Terraform
Equipes que precisam de ferramentas IaC de baixa barreira com uma curva de aprendizado superficial em YAMLVocê precisa de execução paralela muito rápida; a sobrecarga SSH do Ansible limita a escalabilidade em milhares de nós

Comparações

CritérioAnsibleTerraform
ParadigmaProcedural com módulos idempotentes — tarefas executam em ordemDeclarativo — descreve o estado desejado, o Terraform computa o diff
Gerenciamento de estadoSem estado — sem rastreamento embutido do que foi aplicado anteriormenteArquivo de estado explícito mapeia configuração para IDs de recursos reais
Caso de uso principalGerenciamento de configuração e implantação de software em hosts existentesProvisionamento de infraestrutura de nuvem (instâncias, redes, armazenamento)
Suporte a provedores de nuvemMódulos de nuvem existem, mas são menos abrangentes que os provedores do Terraform1.000+ provedores com cobertura profunda e versionada de API
IdempotênciaNível de tarefa — cada módulo deve ser escrito de forma idempotenteNativo — plan/apply sempre converge para o estado declarado
Curva de aprendizadoBaixa — tarefas YAML são legíveis; nenhuma nova linguagem necessáriaModerada — sintaxe HCL + modelo mental de estado/plano para aprender
Agente necessárioNão — sem agente, conecta via SSHNão — o Terraform roda na máquina de controle, chama APIs de nuvem
Quando usar juntosO Ansible configura software na infraestrutura que o Terraform provisionouO Terraform provisiona recursos; o Ansible lida com configuração de OS e app

Vantagens e desvantagens

AspectoVantagensDesvantagens
Arquitetura sem agenteNenhum software para instalar nos nós alvo; funciona com SSH existenteSobrecarga SSH limita o desempenho em escala muito grande (10.000+ nós)
Playbooks YAMLAutomação legível e autodocumentada por humanosLógica complexa (loops, condicionais) fica verbosa em YAML
Módulos idempotentesSeguro para re-executar; correção de drift sem efeitos colateraisA idempotência depende da qualidade do módulo; módulos shell/command não são inerentemente idempotentes
Ansible GalaxyGrande ecossistema de roles comunitários para software comumA qualidade dos roles comunitários varia; fixar versões de roles é crítico para reprodutibilidade
Sem arquivo de estadoSimples, sem sobrecarga de gerenciamento de estadoSem detecção embutida de drift entre execuções; ferramentas manuais ou de terceiros necessárias
Templating Jinja2Geração poderosa de configuração dinâmicaA depuração de templates é mais difícil do que código nativo; erros surgem em tempo de execução

Exemplos 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áticos

  • Documentação do Ansible — Documentação oficial cobrindo playbooks, módulos, roles, inventário e melhores práticas.
  • Ansible Galaxy — Hub comunitário para roles e coleções Ansible reutilizáveis, incluindo drivers de GPU NVIDIA, Docker e roles do Kubernetes.
  • Jeff Geerling — Ansible for DevOps — Livro abrangente e repositório GitHub acompanhante cobrindo o Ansible do básico até padrões de produção.
  • Coleção Ansible da NVIDIA — Coleção Ansible oficial da NVIDIA para gerenciar drivers de GPU, CUDA e instalações de NCCL.
  • Guia de melhores práticas do Ansible — Dicas e truques oficiais cobrindo estrutura de diretórios, gerenciamento de variáveis e otimização de desempenho.

Veja também