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.

Você vai aprender
  • 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 --check e --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

TermoO que éNo exemplo
Control nodeA máquina onde o Ansible está instalado e de onde você roda os comandoso notebook da Ana
Hosts (nós gerenciados)Os servidores configurados. Não precisam de Ansible instalado: só SSH e Pythonservidor1, servidor2
InventárioArquivo com a lista de hosts, em gruposinventario.ini
MóduloUma ação pronta: instalar pacote, copiar arquivo, criar usuário…apt, user, copy
PlaybookArquivo YAML com a lista de tarefassite.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
ParteSignificado
[web]Nome do grupo. Você roda tarefas em grupos inteiros.
servidor1O 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 ping do 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 apt com 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.

Atenção: YAML e espaços

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
ResultadoSignificado
okJá estava no estado pedido: nada foi feito
changedO Ansible mudou algo para chegar ao estado pedido
failedA tarefa deu erro (o host para de receber tarefas)
unreachableNão conseguiu entrar por SSH
skippedTarefa 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

FormaExemploMelhor quando…
Script Bash + SSHfor h in s1 s2; do ssh $h 'sudo apt install -y nginx'; doneUma ação simples, uma vez.
Ansible ad-hocansible web -b -m apt -a "name=nginx"Uma ação em muitos servidores, agora.
Ansible playbookansible-playbook site.ymlConfiguração completa e repetível, guardada no Git.
cloud-inituser-dataConfiguração no primeiro boot de uma máquina nova.

Quando não funciona

UNREACHABLE
$ 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.

"Missing sudo password"
$ ansible-playbook site.yml
…
fatal: [servidor1]: FAILED! => {"msg": "Missing sudo password"}

Como resolver: rode com -K para informar a senha do sudo.

Erro de sintaxe no YAML
$ 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.

"Host key verification failed"

É 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çãoO que faz
ansible ALVO -m MÓDULO -a "ARGS"Ação ad-hoc
ansible-playbook ARQ.ymlRoda um playbook
-i ARQUIVOInventário a usar
-b / -KUsar sudo / perguntar a senha do sudo
--check / --diffSimular / mostrar diferenças
--limit servidor1Restringe a alguns hosts
--syntax-checkSó valida o arquivo
-v, -vvvMais detalhes (para depurar)
ansible-inventory --graphMostra os grupos e hosts
ansible-doc aptDocumentação de um módulo

Módulos mais usados

MóduloPara quê
aptInstalar/remover pacotes
service / systemdIniciar, parar e habilitar serviços
user / groupContas e grupos
copy / templateArquivos (fixos / com variáveis)
filePastas, permissões, links
lineinfileGarantir uma linha num arquivo de configuração
ufwRegras de firewall (coleção community.general)

Exercícios

1. Testar todos

ansible all -m

2. Simular antes

Complete para simular o playbook sem mudar nada:

ansible-playbook site.yml -K

3. Estado desejado

Complete a tarefa para garantir que o pacote git esteja instalado:

ansible.builtin.apt: name: git state:

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 ping testa tudo.
  • Playbooks descrevem estados; por isso são idempotentes e podem rodar sempre.
  • -b -K para sudo; --check --diff antes de produção.
  • Guarde inventário e playbooks no Git: a configuração dos servidores vira código.