
Se você ainda configura servidor na mão, entrando por SSH, editando arquivo, instalando pacote e repetindo tudo na máquina seguinte, o Ansible é a ferramenta que aposenta esse ritual. Você descreve em arquivos YAML como cada servidor deve ficar, e o Ansible aplica isso por SSH, sem instalar agente nenhum nas máquinas. Pode rodar mil vezes: o resultado é o mesmo. Neste guia você vai instalar o Ansible no Ubuntu, montar um projeto com inventário, rodar comandos avulsos e escrever um playbook com variável, template e handler. Todos os comandos foram testados com o ansible-core 2.21 num laboratório com três servidores Ubuntu 24.04.
Como nasceu o Ansible
O primeiro commit do Ansible, com a mensagem “Genesis.”, é de 23 de fevereiro de 2012, feito por Michael DeHaan. Ele conhecia o problema de perto: na Red Hat, tinha criado o Cobbler, de provisionamento de servidores, e participado do Func, de execução remota de comandos. As ferramentas de gerência da época, como Puppet e Chef, exigiam um agente instalado em cada servidor e uma linguagem própria. O Ansible nasceu com três princípios que valem até hoje: sem agente (só SSH e Python, que já existem em qualquer Linux), YAML legível em vez de linguagem de programação e curva de aprendizado curta.
O nome vem da ficção científica. Ansível é o comunicador instantâneo entre planetas que Ursula K. Le Guin inventou em 1966, depois popularizado por O Jogo do Exterminador, de Orson Scott Card. A metáfora combina: comandar máquinas distantes na hora, sem intermediários.
O projeto virou empresa, a AnsibleWorks, depois Ansible Inc., e foi comprado pela Red Hat em outubro de 2015, que o mantém open source. Hoje o motor fica no pacote ansible-core, e o pacote ansible junta o motor com dezenas de collections da comunidade, os conjuntos de módulos para cada tecnologia.
Como o Ansible trabalha
O Ansible é instalado numa única máquina, a máquina de controle: o seu notebook ou um servidor de administração. Os servidores gerenciados só precisam de SSH e Python 3, que o Ubuntu e o Debian já trazem. Nada de agente, nada de serviço extra rodando neles.

Para cada tarefa, o Ansible abre SSH nos servidores em paralelo (cinco por vez, por padrão), envia um pequeno programa Python, o módulo, executa, recebe o resultado em JSON e apaga o arquivo temporário. O módulo compara o estado atual com o que você pediu e só mexe se for preciso.

Instalando no Ubuntu
Há três caminhos, e a diferença é a versão:
- Repositório do Ubuntu: o mais simples. No Ubuntu 24.04 ele traz o pacote
ansible9.2, com ansible-core 2.16. Serve para aprender. - PPA oficial do projeto: versão atual. Em setembro de 2026, o PPA entrega o
ansible14.4.0, com ansible-core 2.21.4, para o Ubuntu 24.04 e o 26.04. Foi o que usamos nos testes. - pipx: isola o Ansible do Python do sistema e funciona em qualquer distribuição.
# Opção 1: repositório do Ubuntu
sudo apt update
sudo apt install -y ansible
# Opção 2: PPA oficial (versão atual)
sudo apt install -y software-properties-common
sudo add-apt-repository --yes --update ppa:ansible/ansible
sudo apt install -y ansible
# Opção 3: pipx
sudo apt install -y pipx
pipx install --include-deps ansible
pipx ensurepath # abra um novo terminal depois deste comando
ansible --version
ansible [core 2.21.4]
config file = /etc/ansible/ansible.cfg
...
python version = 3.12.3 (main, Aug 31 2026, 10:18:26) [GCC 13.3.0] (/usr/bin/python3)
jinja version = 3.1.2
A estrutura de um projeto
O Ansible roda de qualquer diretório, mas vale adotar a estrutura convencional desde o primeiro dia:
meu-projeto/
├── ansible.cfg # configuração do projeto
├── inventory.ini # quais máquinas gerenciar
├── site.yml # playbook principal
├── group_vars/ # variáveis por grupo de hosts
│ └── webservers.yml
├── host_vars/ # variáveis de um host específico
│ └── web01.yml
└── roles/ # partes reutilizáveis
└── nginx/
├── tasks/main.yml
├── handlers/main.yml
├── templates/
├── files/
└── defaults/main.yml
- ansible.cfg: configurações do projeto. O Ansible usa o arquivo do diretório atual antes do global, em
/etc/ansible/ansible.cfg. - Inventário: a lista de máquinas, organizada em grupos.
- Playbook: o roteiro em YAML, “nestes hosts, garanta estas tarefas”.
- group_vars e host_vars: valores que mudam por grupo ou por máquina, separados da lógica.
- Roles: playbooks empacotados para reaproveitar em outros projetos.
Inventário, chave SSH e o primeiro ping
Crie o projeto com três servidores em dois grupos. Troque os IPs pelos seus:
mkdir meu-projeto && cd meu-projeto
cat > inventory.ini <<'EOF'
[webservers]
web01 ansible_host=192.168.64.2
web02 ansible_host=192.168.64.3
[dbservers]
db01 ansible_host=192.168.64.4
[all:vars]
ansible_user=ubuntu
ansible_python_interpreter=/usr/bin/python3
EOF
cat > ansible.cfg <<'EOF'
[defaults]
inventory = inventory.ini
EOF
ansible-inventory --graph
@all:
|--@ungrouped:
|--@webservers:
| |--web01
| |--web02
|--@dbservers:
| |--db01
A linha ansible_python_interpreter fixa o Python usado nos servidores. Sem ela, o ansible-core 2.21 funciona, mas imprime um aviso em cada host dizendo que usou o Python “descoberto” e que isso pode mudar se outro Python for instalado.
O Ansible entra nos servidores com a sua chave SSH. Se você ainda não tem uma, gere e copie para cada servidor:
ssh-keygen -t ed25519
for ip in 192.168.64.2 192.168.64.3 192.168.64.4; do ssh-copy-id ubuntu@$ip; done
O ssh-copy-id também pede para você confirmar a chave de cada servidor na primeira conexão. Se você usar outro caminho para distribuir a chave, o Ansible vai parar com Host key verification failed, e é bom que pare: é a proteção contra servidor falso. Para registrar as chaves de uma vez, sem desligar a checagem:
ssh-keyscan -H 192.168.64.2 192.168.64.3 192.168.64.4 >> ~/.ssh/known_hosts
Confira as chaves se o servidor não for de laboratório. Muito tutorial manda colocar host_key_checking = False no ansible.cfg; evite em produção. E se colocar qualquer opção, cuidado com comentários: no ansible.cfg, o # só vale no começo da linha. No nosso teste, a linha host_key_checking = True # comentário foi lida como False, porque o Ansible pegou o texto inteiro como valor, não reconheceu como verdadeiro e desligou a checagem sem avisar. Comentário na mesma linha só com ;, ou numa linha própria.
Agora o teste de comunicação:
ansible all -m ansible.builtin.ping
web01 | SUCCESS => {
"changed": false,
"ping": "pong"
}
web02 | SUCCESS => { ... }
db01 | SUCCESS => { ... }
O módulo ping não é o ping de rede (ICMP): ele confirma que o Ansible entra por SSH e executa Python do outro lado, que é o que importa. Sem servidor à mão? Tudo funciona contra a própria máquina com ansible localhost -m ansible.builtin.ping, que usa conexão local.
Comandos ad-hoc
Antes de escrever playbooks, dá para resolver muita coisa com comandos avulsos. -m escolhe o módulo, -a passa os argumentos e --become executa com sudo do outro lado:
ansible all -a "uptime -p" # comando em todos
ansible webservers -m ansible.builtin.apt \
-a "name=htop state=present update_cache=true" --become # instala pacote
ansible db01 -m ansible.builtin.setup -a "filter=ansible_distribution*" # fatos
ansible-doc ansible.builtin.apt # documentação do módulo
Rode o comando do htop duas vezes. Na primeira, os hosts aparecem como CHANGED; na segunda, como SUCCESS, porque o pacote já estava lá. Se o seu usuário pede senha no sudo, acrescente -K (--ask-become-pass) para o Ansible perguntar. E prefira os módulos específicos ao shell: o apt sabe dizer se mudou algo, e um comando solto não sabe.
O primeiro playbook
Agora um roteiro completo: instalar o Nginx nos servidores web, publicar uma página gerada a partir de um template com variável, manter o serviço ativo e recarregá-lo só quando a página mudar. Primeiro a variável do grupo, em group_vars/webservers.yml:
---
site_titulo: "Servidor gerenciado pelo Ansible"
O template, em templates/index.html.j2, usa a sintaxe Jinja2. inventory_hostname e ansible_facts são preenchidos pelo próprio Ansible:
<!doctype html>
<html lang="pt-br">
<head><meta charset="utf-8"><title>{{ site_titulo }}</title></head>
<body>
<h1>{{ site_titulo }}</h1>
<p>Host: {{ inventory_hostname }} ({{ ansible_facts["distribution"] }} {{ ansible_facts["distribution_version"] }})</p>
</body>
</html>
E o playbook, site.yml:
---
- name: Configurar servidores web
hosts: webservers
become: true
tasks:
- name: Instalar o Nginx
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
cache_valid_time: 3600
- name: Publicar a página inicial
ansible.builtin.template:
src: templates/index.html.j2
dest: /var/www/html/index.html
owner: root
group: root
mode: "0644"
notify: Recarregar o Nginx
- name: Garantir o Nginx ativo e habilitado no boot
ansible.builtin.service:
name: nginx
state: started
enabled: true
handlers:
- name: Recarregar o Nginx
ansible.builtin.service:
name: nginx
state: reloaded
Os nomes dos módulos aparecem completos, como ansible.builtin.apt. É o formato recomendado desde o Ansible 2.10: deixa claro de qual collection vem cada módulo. O cache_valid_time evita rodar apt update de novo se ele foi feito na última hora.
ansible-playbook site.yml --syntax-check
ansible-playbook site.yml
TASK [Instalar o Nginx] ********************************************************
changed: [web01]
changed: [web02]
TASK [Publicar a página inicial] ***********************************************
changed: [web01]
changed: [web02]
TASK [Garantir o Nginx ativo e habilitado no boot] *****************************
changed: [web02]
changed: [web01]
RUNNING HANDLER [Recarregar o Nginx] *******************************************
changed: [web02]
changed: [web01]
PLAY RECAP *********************************************************************
web01 : ok=5 changed=4 unreachable=0 failed=0 skipped=0 ...
web02 : ok=5 changed=4 unreachable=0 failed=0 skipped=0 ...
Idempotência: rode de novo
Execute o mesmo comando outra vez e olhe o resumo:
web01 : ok=4 changed=0 unreachable=0 failed=0 ...
web02 : ok=4 changed=0 unreachable=0 failed=0 ...
changed=0. Essa é a idempotência, o coração do Ansible: ele não “executa comandos”, ele garante estados. O Nginx já estava instalado, a página já era a certa, o serviço já estava ativo, e nada foi tocado. O handler também não rodou, porque nenhuma tarefa mudou.
Simular antes de aplicar: --check e --diff
Mude o título em group_vars/webservers.yml para “Servidor web do LinuxPro” e simule, só no web01:
ansible-playbook site.yml --check --diff --limit web01
TASK [Publicar a página inicial] ***********************************************
--- before: /var/www/html/index.html
+++ after: /root/.ansible/tmp/ansible-local-586tfwktpl6/tmp5owga2ot/index.html.j2
@@ -1,8 +1,8 @@
<!doctype html>
<html lang="pt-br">
-<head><meta charset="utf-8"><title>Servidor gerenciado pelo Ansible</title></head>
+<head><meta charset="utf-8"><title>Servidor web do LinuxPro</title></head>
<body>
- <h1>Servidor gerenciado pelo Ansible</h1>
+ <h1>Servidor web do LinuxPro</h1>
<p>Host: web01 (Ubuntu 24.04)</p>
</body>
</html>
changed: [web01]
O --check não altera nada, e o --diff mostra exatamente o que mudaria. Aplicando de verdade, só a página e o handler aparecem como changed, e o Nginx é recarregado uma única vez.
Dois limites do --check, vistos nos testes:
- Num servidor limpo, ele pode falhar no meio. Na simulação, o Nginx não é instalado de verdade, então a tarefa seguinte, a do serviço, termina com
Could not find the requested service nginx. É esperado: a simulação vale mais para revisar mudanças em servidores que já estão configurados. - O módulo
aptprecisa do pacotepython3-aptpara simular. O Ubuntu Server já o traz; imagens mínimas, como as de contêiner, não, e o erro dizpython3-apt must be installed to use check mode. Numa execução normal, o próprio módulo instala o pacote.
Transformando o playbook em role
Quando o playbook cresce, as tarefas viram uma role, que pode ser usada em qualquer projeto:
ansible-galaxy role init roles/nginx
O comando cria a estrutura em roles/nginx/. Mova as tarefas para roles/nginx/tasks/main.yml, o handler para roles/nginx/handlers/main.yml e o template para roles/nginx/templates/. Dentro da role, o src do template passa a ser só index.html.j2, porque o Ansible procura na pasta templates/ da própria role. O site.yml fica curto:
---
- name: Configurar servidores web
hosts: webservers
become: true
roles:
- nginx
Rodando de novo, as tarefas aparecem com o prefixo da role (nginx : Instalar o Nginx) e o resultado continua changed=0: mudou a organização, não o estado dos servidores.
Próximos passos
Com esta base, os caminhos naturais são:
- Continuação: em Usuários no Linux com Ansible: chaves SSH, sudo e histórico no Git, o projeto vai para o Git e passa a criar, bloquear e remover contas, com login por chave SSH sem senha e acesso sudo. O código completo, testado no Ubuntu 24.04 e no Debian 13, está no repositório jniltinho/ansible-linux-users.
- Ir para um cenário real: o guia Automatizando servidores Linux com Ansible mostra firewall, segredos com
ansible-vaulte atualização em ondas. - Explorar os módulos na documentação oficial ou com
ansible-doc -l. - Carreira: se automação virar profissão, o Ansible é peça central da certificação LPI DevOps Tools Engineer.
Instale, faça o ping, rode o playbook duas vezes e veja o changed=0: nesse momento fica claro por que tanta gente parou de configurar servidor na mão.