Ubuntu Ansible
Configurar um servidor à mão leva uma tarde. Configurar dez servidores iguais à mão leva uma semana, e cada um sai um pouco diferente. O Ansible resolve isso: você descreve num arquivo como os servidores devem ficar, e ele aplica em todos, pelo SSH, quantas vezes precisar.
- Quem é o control node e quem são os hosts.
- Escrever um inventário (a lista de servidores).
- Rodar comandos rápidos em todos os servidores (ad-hoc).
- Escrever e ler um playbook que instala o nginx e cria um usuário.
- O que é idempotência e por que rodar o playbook duas vezes é seguro.
- Testar antes com
--checke--diff.
Pré-requisitos: SSH com chave e sudo. Nos exemplos, a Ana controla dois servidores, servidor1 (192.168.0.21) e servidor2 (192.168.0.22), a partir do notebook. As saídas são ilustrativas.
Conceito: quem manda e quem obedece
| Termo | O que é | No exemplo |
|---|---|---|
| Control node | A máquina onde o Ansible está instalado e de onde você roda os comandos | o notebook da Ana |
| Hosts (nós gerenciados) | Os servidores configurados. Não precisam de Ansible instalado: só SSH e Python | servidor1, servidor2 |
| Inventário | Arquivo com a lista de hosts, em grupos | inventario.ini |
| Módulo | Uma ação pronta: instalar pacote, copiar arquivo, criar usuário… | apt, user, copy |
| Playbook | Arquivo YAML com a lista de tarefas | site.yml |
O Ansible não deixa nenhum programa rodando nos servidores: a cada execução, ele entra por SSH, faz o trabalho e sai.
Instalar e preparar
$ sudo apt install ansible $ ansible --version | head -1 ansible [core 2.19.2] « versão ilustrativa
O Ansible entra nos servidores por SSH. Garanta que a sua chave já funciona sem senha em cada um:
$ ssh-copy-id ana@192.168.0.21 $ ssh-copy-id ana@192.168.0.22 $ ssh ana@192.168.0.21 hostname servidor1
O inventário
# Grupo "web" com dois servidores [web] servidor1 ansible_host=192.168.0.21 servidor2 ansible_host=192.168.0.22 # Variáveis que valem para todo o grupo "web" [web:vars] ansible_user=ana
| Parte | Significado |
|---|---|
[web] | Nome do grupo. Você roda tarefas em grupos inteiros. |
servidor1 | O apelido do host dentro do Ansible. |
ansible_host= | O endereço real (IP ou nome) para o SSH. |
ansible_user= | O usuário do SSH. |
Para não digitar -i inventario.ini toda vez, crie um ansible.cfg na mesma pasta:
[defaults] inventory = inventario.ini
Comandos ad-hoc: uma ação, em todos
Testar a conexão: módulo ping
- ansible
- O comando para ações avulsas (ad-hoc).
- all
- Em quais hosts:
all, um grupo (web) ou um host (servidor1). - -m ping
- O módulo. O
pingdo Ansible testa SSH + Python (não é o ping de rede).
$ ansible all -m ping
servidor1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong" « respondeu: SSH e Python funcionando
}
servidor2 | SUCCESS => {
…
"ping": "pong"
}
Rodar um comando qualquer: -a
$ ansible web -a "uptime" servidor1 | CHANGED | rc=0 >> 10:52:01 up 3 days, 2:10, 0 user, load average: 0.00, 0.01, 0.00 servidor2 | CHANGED | rc=0 >> 10:52:01 up 12 days, 6:31, 0 user, load average: 0.08, 0.03, 0.01
rc=0 é o código de saída do comando em cada servidor (0 = sucesso). CHANGED aparece porque o Ansible não sabe se um comando avulso mudou algo, então assume que sim.
Como administrador: -b e -K
- -b
- become: rode com sudo nos servidores.
- -K
- Pergunte a senha do sudo (ask become pass).
- -m apt -a "name=htop state=present"
- Módulo
aptcom os argumentos: o pacote e o estado desejado.
$ ansible web -b -K -m apt -a "name=htop state=present"
BECOME password:
servidor1 | CHANGED => { … "changed": true … } « instalou
servidor2 | SUCCESS => { … "changed": false … } « já estava instalado: nada a fazer
Playbook: a receita completa
Comandos ad-hoc são para ações rápidas. Para a configuração de verdade, escreva um playbook. Este instala o nginx, garante que ele está rodando, cria um usuário e publica uma página:
- name: Configura os servidores web # um "play": um alvo + tarefas
hosts: web # em quais hosts (grupo do inventário)
become: true # usar sudo
tasks:
- name: Instala o nginx
ansible.builtin.apt:
name: nginx
state: present # "presente": instale se não estiver
update_cache: true # equivale a "apt update" antes
- name: Garante o nginx rodando e ligando no boot
ansible.builtin.service:
name: nginx
state: started
enabled: true
- name: Cria o usuário deploy
ansible.builtin.user:
name: deploy
shell: /bin/bash
groups: www-data
append: true # como o -a do usermod
- name: Publica a página inicial
ansible.builtin.copy:
content: "<h1>Servidor {{ inventory_hostname }}</h1>\n"
dest: /var/www/html/index.html
owner: deploy
mode: "0644"
Repare que cada tarefa descreve um estado ("nginx presente", "serviço iniciado"), e não um comando ("instale o nginx"). {{ inventory_hostname }} é uma variável trocada pelo nome de cada host.
Como no Compose, a hierarquia é feita por espaços (nunca tab). Os itens de tasks começam com - e o que pertence a cada um fica mais para dentro.
Rodar: ansible-playbook
$ ansible-playbook site.yml -K BECOME password: PLAY [Configura os servidores web] ********************************************* TASK [Gathering Facts] ********************************************************* « coleta informações dos hosts ok: [servidor1] ok: [servidor2] TASK [Instala o nginx] ********************************************************* changed: [servidor1] changed: [servidor2] TASK [Garante o nginx rodando e ligando no boot] ******************************* ok: [servidor1] « o pacote já liga o serviço ok: [servidor2] TASK [Cria o usuário deploy] *************************************************** changed: [servidor1] changed: [servidor2] TASK [Publica a página inicial] ************************************************ changed: [servidor1] changed: [servidor2] PLAY RECAP ********************************************************************* servidor1 : ok=5 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 servidor2 : ok=5 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
| Resultado | Significado |
|---|---|
ok | Já estava no estado pedido: nada foi feito |
changed | O Ansible mudou algo para chegar ao estado pedido |
failed | A tarefa deu erro (o host para de receber tarefas) |
unreachable | Não conseguiu entrar por SSH |
skipped | Tarefa pulada por uma condição (when:) |
Idempotência: rodar de novo é seguro
$ ansible-playbook site.yml -K … PLAY RECAP ********************************************************************* servidor1 : ok=5 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 « nada mudou servidor2 : ok=5 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Idempotente quer dizer: rodar uma vez ou cem vezes dá o mesmo resultado. Como as tarefas descrevem estados, a segunda execução encontra tudo certo e não faz nada. Se alguém mudar algo à mão num servidor, a próxima execução corrige.
Testar antes: --check e --diff
Suponha que você mudou, no playbook, o texto da página para Servidor web {{ inventory_hostname }}. Antes de aplicar, veja o que mudaria:
$ ansible-playbook site.yml -K --check --diff … TASK [Publica a página inicial] ************************************************ --- before: /var/www/html/index.html +++ after: /var/www/html/index.html @@ -1 +1 @@ -<h1>Servidor servidor1</h1> +<h1>Servidor web servidor1</h1> changed: [servidor1] « mostraria a mudança, mas não aplicou nada
--check simula sem mudar nada; --diff mostra o que mudaria nos arquivos. Use os dois antes de rodar em produção.
Formas de automatizar a configuração
| Forma | Exemplo | Melhor quando… |
|---|---|---|
| Script Bash + SSH | for h in s1 s2; do ssh $h 'sudo apt install -y nginx'; done | Uma ação simples, uma vez. |
| Ansible ad-hoc | ansible web -b -m apt -a "name=nginx" | Uma ação em muitos servidores, agora. |
| Ansible playbook | ansible-playbook site.yml | Configuração completa e repetível, guardada no Git. |
| cloud-init | user-data | Configuração no primeiro boot de uma máquina nova. |
Quando não funciona
$ ansible all -m ping
servidor2 | UNREACHABLE! => {
"changed": false,
"msg": "Failed to connect to the host via ssh: ssh: connect to host 192.168.0.22 port 22: No route to host",
"unreachable": true
}
O que aconteceu: o SSH não conectou (servidor desligado, IP errado, firewall). Como confirmar: ssh ana@192.168.0.22 à mão. O Ansible só funciona se o SSH funcionar.
$ ansible-playbook site.yml
…
fatal: [servidor1]: FAILED! => {"msg": "Missing sudo password"}
Como resolver: rode com -K para informar a senha do sudo.
$ ansible-playbook site.yml --syntax-check
ERROR! We were unable to read either as JSON nor YAML, these are the errors we got from each:
…
The offending line appears to be:
name: nginx
state: present
^ here
O que aconteceu: indentação desalinhada. O --syntax-check aponta a linha. Alinhe com espaços.
É a primeira conexão com aquele servidor e o SSH quer confirmar a impressão digital. Conecte uma vez à mão com ssh ana@IP e responda yes (veja SSH).
Referência
| Comando / opção | O que faz |
|---|---|
ansible ALVO -m MÓDULO -a "ARGS" | Ação ad-hoc |
ansible-playbook ARQ.yml | Roda um playbook |
-i ARQUIVO | Inventário a usar |
-b / -K | Usar sudo / perguntar a senha do sudo |
--check / --diff | Simular / mostrar diferenças |
--limit servidor1 | Restringe a alguns hosts |
--syntax-check | Só valida o arquivo |
-v, -vvv | Mais detalhes (para depurar) |
ansible-inventory --graph | Mostra os grupos e hosts |
ansible-doc apt | Documentação de um módulo |
Módulos mais usados
| Módulo | Para quê |
|---|---|
apt | Instalar/remover pacotes |
service / systemd | Iniciar, parar e habilitar serviços |
user / group | Contas e grupos |
copy / template | Arquivos (fixos / com variáveis) |
file | Pastas, permissões, links |
lineinfile | Garantir uma linha num arquivo de configuração |
ufw | Regras de firewall (coleção community.general) |
Exercícios
1. Testar todos
2. Simular antes
Complete para simular o playbook sem mudar nada:
3. Estado desejado
Complete a tarefa para garantir que o pacote git esteja instalado:
4. Você rodou o mesmo playbook duas vezes seguidas. O que é esperado na segunda vez?
5. O que precisa existir nos servidores gerenciados?
Resumo
- O Ansible roda no seu computador e configura os servidores por SSH: nada para instalar neles além de Python.
- Inventário lista os hosts;
ansible all -m pingtesta tudo. - Playbooks descrevem estados; por isso são idempotentes e podem rodar sempre.
-b -Kpara sudo;--check --diffantes de produção.- Guarde inventário e playbooks no Git: a configuração dos servidores vira código.