Dia 23: Ansible no Linux — Playbooks, Inventários e Idempotência
O Ansible é a ferramenta de automação de configuração mais adoptada em ambientes Linux modernos. Permite gerir centenas de servidores a partir de uma única máquina de controlo, sem instalar agentes nos alvos — usa apenas SSH. Neste dia 23 do curso, vamos cobrir a instalação, a estrutura de inventários, a escrita de playbooks em YAML, os módulos essenciais, o conceito fundamental de idempotência, variáveis com facts, templates Jinja2, roles, e a protecção de segredos com Ansible Vault.
Neste artigo
- O que é o Ansible e por que usar
- Instalação do Ansible
- Inventários — definindo os hosts
- Playbooks — estrutura e YAML
- Módulos Essenciais
- Idempotência — o coração do Ansible
- Variáveis, Facts e Templates Jinja2
- Roles e ansible-galaxy
- Ansible Vault — segredos encriptados
- Erros Comuns
- Checklist de Verificação
- Artigos Relacionados
O que é o Ansible e por que usar
O Ansible é uma ferramenta de automação IT open-source, desenvolvida originalmente por Michael DeHaan e adquirida pela Red Hat em 2015. Diferentemente de ferramentas como Chef ou Puppet, o Ansible é agentless — não precisa de software instalado nos servidores alvo. Toda a comunicação acontece via SSH (ou WinRM para Windows), usando Python no lado remoto para executar os módulos.
As principais vantagens do Ansible para administradores Linux são:
- Sem agentes: SSH já está disponível em praticamente todos os servidores Linux — não há nada para instalar nos hosts geridos.
- Declarativo: descreves o estado desejado (ex: “o nginx deve estar instalado e a correr”) e o Ansible garante que esse estado se concretiza.
- Idempotente: executar o mesmo playbook 10 vezes produz o mesmo resultado que executá-lo 1 vez — não duplica nem corrói configuração.
- YAML legível: os playbooks são ficheiros de texto em YAML, fáceis de ler, escrever e revisar.
- Curva de aprendizagem baixa: em poucas horas tens um playbook funcional a gerir dezenas de servidores.
O Ansible é usado para provisioning de servidores, configuração de serviços, deployment de aplicações, orquestração de upgrades zero-downtime e até apply de patches de segurança em fleet inteiro. Para sysadmins que gerem ambientes heterogéneos (Ubuntu, Debian, RHEL, Rocky, Alma), o Ansible normaliza a gestão via módulos que detectam a distribuição automaticamente (documentação do módulo package).
Instalação do Ansible
O Ansible instala-se apenas na máquina de controlo — o computador de onde vais correr os playbooks. Os hosts geridos não precisam de instalação. A forma mais simples é via gestor de pacotes da distribuição.
Em Ubuntu/Debian, o pacote ansible está nos repositórios oficiais. Em RHEL/Rocky/Alma, o Ansible vem via EPEL ou via pip. A versão mais recente e recomendada instala-se via pip (Python package manager), garantindo acesso à última versão estável independente da distribuição.
# Ubuntu / Debian
sudo apt update
sudo apt install -y ansible
# RHEL / Rocky / AlmaLinux
sudo dnf install -y epel-release
sudo dnf install -y ansible
# Alternativa universal via pip (recomendado para versão mais recente)
python3 -m pip install --user ansible
Depois de instalar, confirma a versão e que o binário está acessível:
ansible --version
# Esperado: ansible [core 2.16.x] ou superior, config file = /etc/ansible/ansible.cfg
O --version mostra a versão do Ansible, o ficheiro de configuração activo, o caminho do inventário por omissão e a localização dos módulos. Guarda esta informação — precisas de saber qual ficheiro de config o Ansible está a ler quando algo não funciona como esperado (ver referência de configuração oficial).
Se o pip não estiver instalado, instala-o primeiro:
# Ubuntu / Debian
sudo apt install -y python3-pip
# RHEL / Rocky / AlmaLinux
sudo dnf install -y python3-pip
Inventários — definindo os hosts
O inventário é o ficheiro onde defines quais servidores o Ansible vai gerir. Pode ser em formato INI (ficheiro hosts.ini) ou YAML. O formato INI é mais comum para inventários simples e é o que vamos usar aqui. O ficheiro por omissão fica em /etc/ansible/hosts, mas podes especificar um caminho personalizado com a flag -i (ver guia de inventários oficial).
Cria um ficheiro hosts.ini num directório de trabalho:
# hosts.ini — inventário simples
[webservers]
web1.example.com ansible_host=192.168.1.10
web2.example.com ansible_host=192.168.1.11
[dbservers]
db1.example.com ansible_host=192.168.1.20
[all:vars]
ansible_user=admin
ansible_ssh_private_key_file=~/.ssh/id_ed25519
Cada secção entre parêntesis rectos é um grupo. Os hosts dentro de cada grupo herdam as variáveis definidas em [grupo:vars]. A secção [all:vars] aplica variáveis a todos os hosts. Os parâmetros mais usados por host são:
ansible_host— endereço IP ou hostname real (se diferente do nome declarado).ansible_user— utilizador SSH para ligar ao host.ansible_ssh_private_key_file— caminho da chave privada SSH.ansible_port— porta SSH (útil se não for a 22 padrão).ansible_become—truepara escalar privilégios via sudo.
Para verificar que o Ansible consegue contactar todos os hosts do inventário, corre um ping ad-hoc:
ansible all -i hosts.ini -m ping
# Esperado: cada host responde "pong" com cor verde se a ligação SSH funcionar
O -m ping chama o módulo ping — não é um ping ICMP, é um teste que verifica que o Python remoto está funcional e que o Ansible consegue executar código no host. Se um host responder com vermelho ou “unreachable”, verifica a conectividade SSH manualmente com ssh [email protected] antes de avançar.
Também podes correr comandos arbitrários via módulo shell ou command sem escrever um playbook:
ansible webservers -i hosts.ini -m command -a "uptime"
# Mostra o uptime de todos os hosts do grupo webservers
ansible dbservers -i hosts.ini -m shell -a "free -h"
# Mostra memória dos hosts do grupo dbservers
A diferença entre command e shell: o command não interpreta variáveis de shell nem pipes ($HOME, |, >); o shell passa o comando através de /bin/sh e interpreta tudo. Prefere command sempre que possível — é mais seguro e previsível.
Playbooks — estrutura e YAML
Um playbook é um ficheiro YAML que descreve uma sequência de tarefas (tasks) a executar num conjunto de hosts. Cada playbook contém pelo menos uma play — que associa um conjunto de hosts a uma lista de tarefas. O YAML é sensível a indentação: usa sempre espaços (nunca tabs) e mantém o mesmo número de espaços por nível (ver introdução a playbooks oficial).
Cria o teu primeiro playbook — instala e inicia o nginx num grupo de webservers:
# playbook.yml — instalar e iniciar nginx
---
- name: Instalar e configurar nginx nos webservers
hosts: webservers
become: true
tasks:
- name: Instalar nginx (Debian/Ubuntu)
apt:
name: nginx
state: present
update_cache: true
when: ansible_os_family == "Debian"
- name: Instalar nginx (RHEL/Rocky/Alma)
dnf:
name: nginx
state: present
when: ansible_os_family == "RedHat"
- name: Garantir que o nginx está a correr
service:
name: nginx
state: started
enabled: true
- name: Abrir porta 80 na firewall (Debian)
ufw:
rule: allow
port: "80"
proto: tcp
when: ansible_os_family == "Debian"
- name: Abrir porta 80 na firewall (RHEL)
firewalld:
service: http
permanent: true
state: enabled
immediate: true
when: ansible_os_family == "RedHat"
Executa o playbook com:
ansible-playbook -i hosts.ini playbook.yml
# Esperado: cada task mostra "changed" (1ª execução) ou "ok" (execuções subsequentes)
Analisando a estrutura do playbook:
---— separador de documento YAML, marca o início do ficheiro.name— nome descritivo da play (aparece no output da execução).hosts: webservers— indica o grupo do inventário onde as tasks vão correr.become: true— escala privilégios com sudo nas tasks (equivalente a--becomena linha de comando).tasks:— lista de tarefas a executar, por ordem.when:— condição que controla se a task executa ou é saltada (skipped).
A variável ansible_os_family é um fact — informação que o Ansible recolhe automaticamente sobre cada host antes de executar as tasks. Permite escrever playbooks que se adaptam à distribuição sem precisar de inventários separados.
Módulos Essenciais
Os módulos são as unidades de execução do Ansible — cada task chama um módulo. Existem centenas de módulos no ansible.builtin (incluídos por omissão) e milhares em coleções externas. Os mais usados para administração Linux são:
| Módulo | Função | Exemplo de uso |
|---|---|---|
| apt | Gestão de pacotes em Debian/Ubuntu | Instalar, remover ou actualizar pacotes .deb |
| dnf | Gestão de pacotes em RHEL/Rocky/Alma | Instalar, remover ou actualizar pacotes .rpm |
| package | Gestão de pacotes universal | Detecta automaticamente apt/dnf/yum |
| service | Gestão de serviços systemd | Iniciar, parar, reiniciar, activar ao boot |
| file | Gestão de ficheiros e directórios | Criar, remover, mudar permissões |
| copy | Copiar ficheiros da máquina de controlo para os hosts | Deploy de ficheiros de configuração |
| template | Gerar ficheiros a partir de templates Jinja2 | Configuração dinâmica com variáveis |
| user | Gestão de utilizadores | Criar, remover, modificar contas |
| group | Gestão de grupos | Criar, remover, modificar grupos |
| lineinfile | Editar uma linha específica num ficheiro | Comentar/descomentar configurações |
| systemd | Gestão avançada de unidades systemd | Daemon-reload, restart, masking |
Exemplo prático com vários módulos — criar um utilizador, copiar um ficheiro e garantir uma directoria:
# modulos.yml — exemplos de módulos essenciais
---
- name: Configurar ambiente de deploy
hosts: webservers
become: true
tasks:
- name: Criar grupo de aplicação
group:
name: deploy
state: present
- name: Criar utilizador de deploy
user:
name: deploy
group: deploy
shell: /bin/bash
home: /home/deploy
create_home: true
- name: Criar directoria de aplicação
file:
path: /opt/app
state: directory
owner: deploy
group: deploy
mode: "0755"
- name: Copiar ficheiro de configuração
copy:
src: files/app.conf
dest: /opt/app/app.conf
owner: deploy
group: deploy
mode: "0644"
backup: true
- name: Garantir que a linha de log está presente
lineinfile:
path: /opt/app/app.conf
regexp: "^log_level="
line: "log_level=info"
state: present
O parâmetro backup: true no módulo copy cria uma cópia do ficheiro original com timestamp antes de o substituir — útil para rollback rápido se a nova configuração tiver problemas.
Idempotência — o coração do Ansible
A idempotência é o conceito mais importante do Ansible. Uma operação é idempotente quando executá-la uma ou múltiplas vezes produz exactamente o mesmo resultado — não há efeitos acumulativos nem laterais. Em termos práticos: se correres um playbook 10 vezes seguidas, o servidor fica no mesmo estado que se o tivesses corrido 1 vez.
Os módulos do Ansible são desenhados para ser idempotentes por omissão. Por exemplo, o módulo apt com state: present verifica se o pacote já está instalado antes de tentar instalá-lo. Se já estiver, a task marca como ok (verde) em vez de changed (amarelo) — não reinstala.
shell e command NÃO são idempotentes por omissão. Se usares shell: echo "x" >> /etc/ficheiro, cada execução acrescenta uma linha nova. Para tornar estes módulos idempotentes, usa creates: ou changed_when:.
Exemplo de task não idempotente vs idempotente:
# NAO IDEMPOTENTE — acrescenta linha a cada execução
- name: Adicionar entrada ao hosts (errado)
shell: echo "10.0.0.5 app.local" >> /etc/hosts
# IDEMPOTENTE — usa lineinfile com regexp
- name: Garantir entrada no hosts (correcto)
lineinfile:
path: /etc/hosts
regexp: "^10\\.0\\.0\\.5\\s+app\\.local"
line: "10.0.0.5 app.local"
state: present
# IDEMPOTENTE com shell — só executa se o ficheiro nao existir
- name: Download de script de setup
shell: curl -sL https://exemplo.com/setup.sh -o /opt/setup.sh
args:
creates: /opt/setup.sh
O parâmetro creates: diz ao Ansible: “só executa este comando se o ficheiro /opt/setup.sh não existir”. Se já existir, a task é saltada — o resultado é idempotente.
Outro padrão útil é o changed_when: para controlar quando uma task marca como “changed”:
- name: Verificar se reboot é necessário
shell: needs-restarting -r 2>/dev/null; echo $?
register: reboot_check
changed_when: false # task de leitura — nunca altera o sistema
- name: Reboot se necessário
reboot:
when: reboot_check.stdout_lines[-1] != "0"
Variáveis, Facts e Templates Jinja2
As variáveis no Ansible podem ser definidas em vários níveis: no inventário, em ficheiros group_vars/ e host_vars/, no próprio playbook, ou via --extra-vars na linha de comando. A precedência é: linha de comando > playbook > host_vars > group_vars > inventário.
Cria a estrutura de directórios para variáveis:
project/
hosts.ini
playbook.yml
group_vars/
webservers.yml
dbservers.yml
host_vars/
web1.example.com.yml
templates/
nginx.conf.j2
files/
app.conf
Exemplo de group_vars/webservers.yml:
# group_vars/webservers.yml
nginx_worker_processes: auto
nginx_max_clients: 1024
nginx_listen_port: 80
app_name: "meu-app"
Os facts são informações que o Ansible recolhe automaticamente sobre cada host no início de cada play: sistema operativo, versão, arquitectura, endereços IP, discos, memória, CPU. Para ver todos os facts de um host:
ansible web1.example.com -i hosts.ini -m setup
# Mostra JSON com centenas de facts: ansible_os_family, ansible_distribution,
# ansible_architecture, ansible_default_ipv4, ansible_memtotal_mb, etc.
Os templates Jinja2 permitem gerar ficheiros de configuração dinâmicos combinando variáveis e facts. Cria um template de configuração nginx:
# templates/nginx.conf.j2
worker_processes {{ nginx_worker_processes }};
events {
worker_connections {{ nginx_max_clients }};
}
http {
server {
listen {{ nginx_listen_port }};
server_name {{ ansible_hostname }}.example.com;
root /var/www/{{ app_name }};
location / {
try_files $uri $uri/ =404;
}
}
}
Usa o módulo template para gerar o ficheiro final no host remoto:
- name: Gerar configuração do nginx
template:
src: templates/nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: "0644"
validate: nginx -t -c %s
notify: reiniciar nginx
# Handlers — só executam quando uma task faz "notify"
handlers:
- name: reiniciar nginx
service:
name: nginx
state: restarted
O validate: nginx -t -c %s testa a sintaxe do ficheiro gerado antes de o aplicar — se a configuração for inválida, a task falha e o ficheiro original não é substituído. O notify acciona o handler “reiniciar nginx”, mas só se a task marcar como changed. Os handlers executam no final da play, e apenas uma vez, mesmo se várias tasks notificarem o mesmo handler.
Roles e ansible-galaxy
Quando os playbooks crescem além de 100-200 linhas, torna-se difícil mantê-los num único ficheiro. As roles são a estrutura de organização do Ansible — dividem a configuração em directórios padronizados com responsabilidades separadas (ver guia de roles oficial).
Cria uma role com ansible-galaxy:
ansible-galaxy init roles/nginx
# Cria a estrutura de directorios padrao:
# roles/nginx/
# tasks/main.yml — tarefas principais
# handlers/main.yml — handlers (restarts, reloads)
# templates/ — templates Jinja2
# files/ — ficheiros para copiar
# vars/main.yml — variaveis da role
# defaults/main.yml — variaveis com defaults
# meta/main.yml — metadata e dependencias
Exemplo de roles/nginx/tasks/main.yml:
# roles/nginx/tasks/main.yml
---
- name: Instalar nginx (Debian)
apt:
name: nginx
state: present
update_cache: true
when: ansible_os_family == "Debian"
- name: Instalar nginx (RHEL)
dnf:
name: nginx
state: present
when: ansible_os_family == "RedHat"
- name: Gerar configuração
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
validate: nginx -t -c %s
notify: reiniciar nginx
- name: Garantir nginx a correr
service:
name: nginx
state: started
enabled: true
Usa a role num playbook — fica limpo e reutilizável:
# site.yml — playbook principal que chama roles
---
- name: Configurar webservers
hosts: webservers
become: true
roles:
- nginx
- firewall
O ansible-galaxy também serve para instalar roles partilhadas pela comunidade a partir do Ansible Galaxy:
# Instalar uma role do Galaxy
ansible-galaxy install geerlingguy.nginx
# Listar roles instaladas
ansible-galaxy list
# Instalar varias roles via requirements.yml
ansible-galaxy install -r requirements.yml
Ansible Vault — segredos encriptados
Os playbooks frequentemente precisam de conter informação sensível: palavras-passe de bases de dados, chaves de API, tokens de acesso. O Ansible Vault permite encriptar ficheiros ou variáveis individuais para que os segredos não fiquem em texto claro no repositório (ver documentação do ansible-vault).
Encripta um ficheiro de variáveis sensíveis:
# Criar ficheiro encriptado novo
ansible-vault create group_vars/webservers/vault.yml
# Pede uma password — o ficheiro fica encriptado com AES-256
# Encriptar ficheiro existente
ansible-vault encrypt group_vars/webservers/secrets.yml
# Ver conteudo encriptado (pede password)
ansible-vault view group_vars/webservers/vault.yml
# Editar ficheiro encriptado
ansible-vault edit group_vars/webservers/vault.yml
Para executar um playbook que usa ficheiros encriptados, fornece a password do vault:
# Pede password interactivamente
ansible-playbook -i hosts.ini site.yml --ask-vault-pass
# Usa ficheiro de password (para automatizacao)
echo "minha_password_secreta" > .vault_pass
chmod 600 .vault_pass
ansible-playbook -i hosts.ini site.yml --vault-password-file .vault_pass
.vault_pass deve ter permissões 600 e NUNCA ser commitado para controlo de versões. Adiciona .vault_pass ao .gitignore. Para CI/CD, usa variáveis de ambiente ou um gestor de segredos externo (HashiCorp Vault, AWS Secrets Manager).
Erros Comuns
| Problema | Causa | Solução |
|---|---|---|
| SSH connection refused / unreachable | Host inacessível, porta SSH errada ou firewall a bloquear | Verifica com ssh user@host manualmente; confirma ansible_port no inventário |
| “sudo: a password is required” | O utilizador remoto precisa de password para sudo | Configura NOPASSWD no sudoers ou usa --ask-become-pass |
| YAML syntax error | Indentação com tabs em vez de espaços ou indentação inconsistente | Usa espaços (nunca tabs); valida com ansible-playbook --syntax-check playbook.yml |
| “fatal: MODULE FAILURE — No module named ‘apt'” | Python 3 não instalado ou não encontrado no host remoto | Instala python3 no host; define ansible_python_interpreter=/usr/bin/python3 |
| Task sempre “changed” mesmo sem alterações | Módulo shell/command não idempotente | Substitui por módulo específico ou adiciona changed_when: false / creates: |
| Variável não definida (undefined) | Variável referenciada no template mas não declarada | Define em defaults/main.yml ou usa {{ var | default('valor') }} |
| Handler não executa após notify | Nome do notify não corresponde ao nome do handler | Verifica que o nome em notify: é exactamente igual ao name: do handler |
| Vault password prompt em CI/CD | --ask-vault-pass não funciona em pipelines não-interactivos |
Usa --vault-password-file com path de variável de ambiente |
Checklist de Verificação
Antes de aplicar um playbook Ansible em produção, confirma cada ponto:
- Verifica a sintaxe YAML: corre
ansible-playbook --syntax-check playbook.ymlantes de executar. - Faz um dry-run: usa
--checkpara simular a execução sem alterar nada (ansible-playbook --check site.yml). - Testa num host de staging: limita o playbook a um host de teste antes de aplicar em todo o fleet (
--limit staging-host). - Verifica a idempotência: executa o playbook duas vezes seguidas — a segunda execução não deve ter nenhuma task “changed”.
- Confirma as chaves SSH: testa
ssh user@hostmanualmente para cada host novo no inventário. - Encripta segredos: garante que nenhuma palavra-passe ou chave está em texto claro — usa
ansible-vault encryptem todos os ficheiros com informação sensível. - Define
become: trueapenas onde necessário: não escalas privilégios em tasks que não precisam — minimiza a superfície de risco. - Versiona tudo em Git: playbooks, inventários, roles e templates devem estar em controlo de versões para auditoria e rollback.
Artigos Relacionados
- Dia 22: Docker Compose — Multi-Service Stacks e Orquestração Básica — Para orquestrar múltiplos containers, o Docker Compose complementa o Ansible no deployment de stacks.
- Dia 24: Git para Sysadmins — Repos, Branches e Tags — Os playbooks e roles do Ansible devem estar em Git; este dia cobre workflows de configuração versionada.
- Dia 6: Gestão de Pacotes em Linux — apt, dnf, Snap e Flatpak — Os módulos apt e dnf do Ansible baseiam-se nos conceitos deste dia.
- Dia 7: Systemd no Linux — Services, Timers e journalctl — O módulo service do Ansible gere unidades systemd; entender o systemd é essencial.
- Dia 10: SSH no Linux — Chaves, Tunnels e Hardening — O Ansible depende inteiramente de SSH; a configuração de chaves é pré-requisito.
- Linux Server Hardening Checklist 2026 — Aplicar hardening via Ansible garante consistência em todo o fleet.