Post

Estação de Trabalho como Código (Parte 10): Provisionamento com Ansible e Docker

Aprenda a usar Ansible para provisionar e configurar máquinas virtuais com Docker pré-instalado. Este tutorial cobre instalação do Ansible, criação de playbooks, organização em roles, integração com Terraform e como usar Docker Compose para orquestrar containers.

Estação de Trabalho como Código (Parte 10): Provisionamento com Ansible e Docker

Tutorial Anterior: Estação de Trabalho como Código (Parte 9): Testando e Validando Imagens com Testes Automatizados

Introdução

Até agora, criamos máquinas virtuais com imagens personalizadas usando Packer e as testamos. Mas em um ambiente real, você frequentemente precisa fazer configurações adicionais após a VM ser iniciada: instalar aplicações específicas, configurar serviços, gerenciar arquivos e muito mais. É aqui que entra o Ansible, um orquestrador de configuração agnóstico que permite automatizar essas tarefas de forma declarativa.

Além disso, vamos integrar o Docker em nosso fluxo de trabalho, permitindo que você execute aplicações em containers dentro de suas máquinas virtuais. Isso oferece isolamento, portabilidade e escalabilidade.

Observação: Esta parte é essencial para automatizar configurações pós-provisionamento. Se você preferir apenas provisionar VMs sem configuração adicional, pode pular para a Parte 11. No entanto, recomendamos completar esta parte para entender automação profissional.

Objetivos desta Parte

  • Entender o que é Ansible e como funciona
  • Instalar o Ansible no Ubuntu 24.04
  • Criar um inventário Ansible para gerenciar hosts
  • Desenvolver playbooks Ansible para provisionar máquinas virtuais
  • Organizar configurações em roles reutilizáveis
  • Instalar e configurar Docker via Ansible
  • Usar Docker Compose para orquestrar containers
  • Integrar Ansible com Terraform para provisionamento automatizado

Pré-requisitos

  • Conclusão da Parte 9 desta série
  • Máquinas virtuais funcionando com SSH acessível
  • Conhecimento básico de YAML
  • Conhecimento básico de Docker (opcional)

A quem se destina

Este tutorial é ideal para:

  • DevOps Engineers: Que precisam automatizar configurações
  • SysAdmins: Que querem gerenciar múltiplas máquinas
  • Desenvolvedores: Que precisam de ambientes reproduzíveis
  • Profissionais de TI: Que querem implementar IaC completo

Pré-conhecimento: Conhecimento básico de Terraform, SSH e linha de comando (coberto nas partes anteriores) é recomendado.

Tempo Estimado

120-150 minutos

Isso inclui:

  • Leitura e compreensão: ~20 min
  • Instalação do Ansible: ~10 min
  • Preparação de estrutura: ~15 min
  • Criação de playbooks: ~40 min
  • Execução de playbooks: ~20 min
  • Testes e validação: ~15 min

Dica Útil: Playbooks podem ser reutilizados para múltiplas máquinas, economizando tempo no futuro.


Entendendo Ansible

Antes de começar, é importante entender os conceitos fundamentais.

O que é Ansible?

Ansible é uma ferramenta de automação de infraestrutura que permite gerenciar múltiplas máquinas de forma centralizada. Diferente de ferramentas como Puppet ou Chef, Ansible é agnóstico (não requer agente) e usa SSH para comunicação.

Como Ansible Funciona?

O fluxo de trabalho do Ansible é:

  1. Inventário: Define quais hosts gerenciar
  2. Playbook: Define tarefas a executar
  3. Módulos: Executam ações específicas
  4. Handlers: Reagem a mudanças
  5. Roles: Organizam playbooks reutilizáveis

Componentes Principais

ComponenteDescrição
InventárioLista de hosts a gerenciar
PlaybookArquivo YAML com tarefas
TaskAção individual a executar
ModulePlugin que executa ação
HandlerAção reativa a mudanças
RoleConjunto de tarefas reutilizáveis
VariableDados dinâmicos
TemplateArquivo com variáveis

Verificando Pré-Requisitos

Antes de começar, certifique-se de que seu ambiente está pronto.

Passo 1: Verificar Terraform

1
2
3
4
5
# Verifique se Terraform está instalado
$ terraform --version

# Você deve ver:
# Terraform v1.14.5

Passo 2: Verificar LIBVIRT

1
2
3
4
5
# Verifique se libvirtd está rodando
$ systemctl is-active libvirtd

# Você deve ver:
# active

Passo 3: Verificar SSH

1
2
3
4
5
6
# Verifique se SSH está configurado
$ ls -la ~/.ssh/terraform-vms*

# Você deve ver:
# -rw------- terraform-vms
# -rw-r--r-- terraform-vms.pub

Instalando Ansible

Passo 1: Adicionar Repositório

1
2
3
4
5
6
7
8
# Atualize o sistema
$ sudo apt update && sudo apt upgrade -y

# Instale dependências
$ sudo apt install -y software-properties-common

# Adicione repositório Ansible
$ sudo add-apt-repository --yes --update ppa:ansible/ansible

Passo 2: Instalar Ansible

1
2
3
4
5
6
7
8
9
# Instale Ansible e sshpass
$ sudo apt install -y ansible sshpass

# Verifique a instalação
$ ansible --version

# Você deve ver:
# ansible [core 2.20.2]
# ...

Passo 3: Configurar Autocompletar

1
2
3
# Configure autocompletar
$ eval $(register-python-argcomplete ansible)
$ eval $(register-python-argcomplete ansible-playbook)

Criando Estrutura Ansible

Passo 1: Criar Diretórios

1
2
3
4
5
6
7
# Crie a estrutura de diretórios
$ mkdir -p ~/workspace-as-code/ansible/{playbooks,roles,inventory,group_vars,host_vars}
$ cd ~/workspace-as-code/ansible

# Verifique
$ pwd
# /home/ubuntu/workspace-as-code/ansible

Passo 2: Criar Arquivo de Configuração

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Crie arquivo de configuração do Ansible
$ cat > ~/workspace-as-code/ansible/ansible.cfg << 'EOF'
[defaults]
inventory = inventory/hosts.ini
host_key_checking = False
private_key_file = ~/.ssh/terraform-vms
remote_user = debian
roles_path = roles
library = library
filter_plugins = filter_plugins

[inventory]
enable_plugins = ini, yaml, aws_ec2

[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
pipelining = True
EOF

# Verifique
$ cat ~/workspace-as-code/ansible/ansible.cfg

Passo 3: Criar Inventário

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Crie arquivo de inventário
$ cat > ~/workspace-as-code/ansible/inventory/hosts.ini << 'EOF'
[local]
localhost ansible_connection=local

[docker_hosts]
# Será preenchido dinamicamente por Terraform

[docker_hosts:vars]
ansible_user=debian
ansible_ssh_private_key_file=~/.ssh/terraform-vms
EOF

# Verifique
$ cat ~/workspace-as-code/ansible/inventory/hosts.ini

Criando Playbooks

Passo 1: Criar Playbook Principal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Crie playbook principal
$ cat > ~/workspace-as-code/ansible/playbooks/main.yml << 'EOF'
---
- name: "Configure Docker Host"
  hosts: docker_hosts
  become: yes
  gather_facts: yes

  pre_tasks:
    - name: "Update package cache"
      apt:
        update_cache: yes
        cache_valid_time: 3600

  roles:
    - docker
    - docker-compose

  post_tasks:
    - name: "Display completion message"
      debug:
        msg: "Docker host configuration completed successfully!"
EOF

# Verifique
$ cat ~/workspace-as-code/ansible/playbooks/main.yml

Passo 2: Criar Role Docker

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# Crie estrutura de role
$ mkdir -p ~/workspace-as-code/ansible/roles/docker/{tasks,handlers,templates,files,defaults,vars}

# Crie arquivo de tarefas
$ cat > ~/workspace-as-code/ansible/roles/docker/tasks/main.yml << 'EOF'
---
- name: "Install Docker prerequisites"
  apt:
    name:
      - apt-transport-https
      - ca-certificates
      - curl
      - gnupg
      - lsb-release
    state: present

- name: "Add Docker GPG key"
  apt_key:
    url: https://download.docker.com/linux/debian/gpg
    state: present

- name: "Add Docker repository"
  apt_repository:
    repo: "deb [arch=amd64] https://download.docker.com/linux/debian  stable"
    state: present

- name: "Install Docker"
  apt:
    name:
      - docker-ce
      - docker-ce-cli
      - containerd.io
    state: present

- name: "Start Docker service"
  systemd:
    name: docker
    state: started
    enabled: yes
    daemon_reload: yes

- name: "Add user to docker group"
  user:
    name: ""
    groups: docker
    append: yes

- name: "Verify Docker installation"
  shell: docker --version
  register: docker_version
  changed_when: false

- name: "Display Docker version"
  debug:
    msg: "Docker installed: "
EOF

# Verifique
$ cat ~/workspace-as-code/ansible/roles/docker/tasks/main.yml

Passo 3: Criar Role Docker Compose

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# Crie estrutura de role
$ mkdir -p ~/workspace-as-code/ansible/roles/docker-compose/{tasks,handlers,templates,files,defaults,vars}

# Crie arquivo de tarefas
$ cat > ~/workspace-as-code/ansible/roles/docker-compose/tasks/main.yml << 'EOF'
---
- name: "Download Docker Compose"
  get_url:
    url: "https://github.com/docker/compose/releases/download/v2.20.0/docker-compose-Linux-x86_64"
    dest: /usr/local/bin/docker-compose
    mode: '0755'

- name: "Create symbolic link"
  file:
    src: /usr/local/bin/docker-compose
    dest: /usr/bin/docker-compose
    state: link

- name: "Verify Docker Compose installation"
  shell: docker-compose --version
  register: docker_compose_version
  changed_when: false

- name: "Display Docker Compose version"
  debug:
    msg: "Docker Compose installed: "

- name: "Create docker-compose directory"
  file:
    path: /opt/docker-compose
    state: directory
    mode: '0755'
EOF

# Verifique
$ cat ~/workspace-as-code/ansible/roles/docker-compose/tasks/main.yml

Passo 4: Criar Handlers

1
2
3
4
5
6
7
8
9
10
11
12
# Crie arquivo de handlers para Docker
$ cat > ~/workspace-as-code/ansible/roles/docker/handlers/main.yml << 'EOF'
---
- name: "Restart Docker"
  systemd:
    name: docker
    state: restarted
    daemon_reload: yes
EOF

# Verifique
$ cat ~/workspace-as-code/ansible/roles/docker/handlers/main.yml

Configurando Terraform para Ansible

Passo 1: Criar Arquivo de Inventário Dinâmico

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Crie arquivo Terraform para gerar inventário
$ cat > ~/workspace-as-code/terraform/libvirt/debian12/ansible-inventory.tf << 'EOF'
# Gerar inventário Ansible dinamicamente
resource "local_file" "ansible_inventory" {
  filename = "${path.module}/../../ansible/inventory/hosts.ini"
  content  = templatefile("${path.module}/inventory.tpl", {
    docker_hosts = libvirt_domain.terraform_vm.*.network_interface[0].addresses[0]
  })

  depends_on = [libvirt_domain.terraform_vm]
}
EOF

# Verifique
$ cat ~/workspace-as-code/terraform/libvirt/debian12/ansible-inventory.tf

Passo 2: Criar Template de Inventário

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Crie template de inventário
$ cat > ~/workspace-as-code/terraform/libvirt/debian12/inventory.tpl << 'EOF'
[local]
localhost ansible_connection=local

[docker_hosts]
%{ for host in docker_hosts ~}
${host}
%{ endfor ~}

[docker_hosts:vars]
ansible_user=debian
ansible_ssh_private_key_file=~/.ssh/terraform-vms
EOF

# Verifique
$ cat ~/workspace-as-code/terraform/libvirt/debian12/inventory.tpl

Executando Playbooks

Passo 1: Validar Playbook

1
2
3
4
5
6
7
8
# Navegue para o diretório Ansible
$ cd ~/workspace-as-code/ansible

# Valide a sintaxe do playbook
$ ansible-playbook playbooks/main.yml --syntax-check

# Você deve ver:
# playbook: playbooks/main.yml

Passo 2: Executar em Modo Dry-Run

1
2
3
4
# Execute em modo dry-run (sem fazer mudanças)
$ ansible-playbook playbooks/main.yml --check -i inventory/hosts.ini

# Você verá as mudanças que seriam feitas

Passo 3: Executar Playbook

1
2
3
4
5
6
7
8
9
10
# Execute o playbook
$ ansible-playbook playbooks/main.yml -i inventory/hosts.ini -v

# Você verá progresso como:
# PLAY [Configure Docker Host] ****
# TASK [Update package cache] ****
# ok: [192.168.122.50]
# ...
# PLAY RECAP ****
# 192.168.122.50 : ok=10 changed=5 unreachable=0 failed=0

Passo 4: Verificar Instalação

1
2
3
4
5
6
# Verifique se Docker foi instalado
$ ansible docker_hosts -i inventory/hosts.ini -m shell -a "docker --version"

# Você deve ver:
# 192.168.122.50 | CHANGED | rc=0 >>
# Docker version 24.0.0, build abcdef123

Exemplos Práticos

Exemplo 1: Playbook para Instalar Aplicações

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
---
- name: "Install Applications"
  hosts: docker_hosts
  become: yes

  tasks:
    - name: "Install common packages"
      apt:
        name:
          - git
          - curl
          - wget
          - htop
          - vim
        state: present

    - name: "Install Python packages"
      pip:
        name:
          - docker
          - docker-compose
        state: present

Exemplo 2: Playbook para Configurar Firewall

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
---
- name: "Configure Firewall"
  hosts: docker_hosts
  become: yes

  tasks:
    - name: "Install UFW"
      apt:
        name: ufw
        state: present

    - name: "Enable UFW"
      ufw:
        state: enabled
        policy: deny
        direction: incoming

    - name: "Allow SSH"
      ufw:
        rule: allow
        port: '22'
        proto: tcp

    - name: "Allow HTTP"
      ufw:
        rule: allow
        port: '80'
        proto: tcp

    - name: "Allow HTTPS"
      ufw:
        rule: allow
        port: '443'
        proto: tcp

Exemplo 3: Playbook com Variables

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
---
- name: "Configure with Variables"
  hosts: docker_hosts
  become: yes

  vars:
    packages:
      - git
      - curl
      - wget
    docker_users:
      - debian
      - ubuntu

  tasks:
    - name: "Install packages"
      apt:
        name: ""
        state: present

    - name: "Add users to docker group"
      user:
        name: ""
        groups: docker
        append: yes
      loop: ""

Tabela de Módulos Ansible Comuns

MóduloDescriçãoExemplo
aptGerenciar pacotesapt: name=git state=present
shellExecutar comando shellshell: docker --version
fileGerenciar arquivosfile: path=/tmp/test state=directory
copyCopiar arquivoscopy: src=local dest=/remote
templateUsar templatestemplate: src=app.conf.j2 dest=/etc/app.conf
serviceGerenciar serviçosservice: name=docker state=started
userGerenciar usuáriosuser: name=john groups=docker
gitClonar repositóriosgit: repo=https://... dest=/opt/app
docker_containerGerenciar containersdocker_container: name=web image=nginx
docker_composeUsar docker-composedocker_compose: project_src=/opt/app

Troubleshooting

Erro: “Permission denied (publickey)”

Problema: Ansible não consegue conectar via SSH.

Solução:

1
2
3
4
5
6
7
# Verifique permissões da chave
$ ls -la ~/.ssh/terraform-vms
# Deve ser -rw------- (600)

# Ou especifique a chave
$ ansible-playbook playbooks/main.yml -i inventory/hosts.ini \
  --private-key ~/.ssh/terraform-vms

Erro: “Host unreachable”

Problema: Ansible não consegue alcançar o host.

Solução:

1
2
3
4
5
# Verifique conectividade
$ ping <ip-do-host>

# Ou teste SSH manualmente
$ ssh -i ~/.ssh/terraform-vms debian@<ip-do-host>

Erro: “Module not found”

Problema: Módulo Ansible não está instalado.

Solução:

1
2
3
4
5
# Instale coleção Ansible
$ ansible-galaxy collection install community.docker

# Ou instale via pip
$ pip3 install docker

Playbook muito lento

Problema: Playbook está demorando muito.

Solução:

1
2
3
4
5
6
# Ative pipelining no ansible.cfg
[ssh_connection]
pipelining = True

# Ou execute com paralelismo
$ ansible-playbook playbooks/main.yml -f 10

Docker não inicia após instalação

Problema: Serviço Docker não inicia.

Solução:

1
2
3
4
5
6
7
# Verifique status
$ ansible docker_hosts -i inventory/hosts.ini -m shell \
  -a "systemctl status docker"

# Ou reinicie manualmente
$ ansible docker_hosts -i inventory/hosts.ini -m service \
  -a "name=docker state=restarted"

Dicas e Boas Práticas

Dica 1: Use Roles para Reutilização

1
2
3
4
5
6
# Organize playbooks em roles
roles/
├── docker/
├── docker-compose/
├── firewall/
└── monitoring/

Dica 2: Use Variables para Flexibilidade

1
2
3
4
# Defina variáveis em group_vars
group_vars/
├── docker_hosts.yml
└── local.yml

Dica 3: Use Templates para Configurações

1
2
3
4
5
# Use templates Jinja2
- name: "Configure app"
  template:
    src: app.conf.j2
    dest: /etc/app.conf

Dica 4: Use Handlers para Reações

1
2
3
4
5
6
7
8
9
10
11
12
# Handlers reagem a mudanças
- name: "Update config"
  copy:
    src: config
    dest: /etc/app/config
  notify: "Restart app"

handlers:
  - name: "Restart app"
    service:
      name: app
      state: restarted

Dica 5: Versione Playbooks

1
2
3
# Adicione ao Git
$ git add ansible/
$ git commit -m "feat: add ansible playbooks for docker"

Integrando com workspace-as-code

Passo 1: Organizar Estrutura

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Verifique a estrutura
$ tree ~/workspace-as-code/ansible/

# Você deve ver:
# ~/workspace-as-code/ansible/
# ├── ansible.cfg
# ├── playbooks/
# │   └── main.yml
# ├── roles/
# │   ├── docker/
# │   └── docker-compose/
# ├── inventory/
# │   └── hosts.ini
# ├── group_vars/
# ├── host_vars/
# └── library/

Passo 2: Criar .gitignore

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Crie arquivo .gitignore
$ cat > ~/workspace-as-code/ansible/.gitignore << 'EOF'
# Ansible
*.retry
.ansible/
inventory/hosts.ini

# SSH keys
*.pem
*.key

# Python
__pycache__/
*.py[cod]
*$py.class
EOF

# Verifique
$ cat ~/workspace-as-code/ansible/.gitignore

Passo 3: Versionar no Git

1
2
3
4
5
6
7
8
# Adicione ao Git
$ cd ~/workspace-as-code
$ git add ansible/
$ git commit -m "feat: add ansible playbooks for docker configuration"
$ git push origin main

# Verifique
$ git log --oneline | head -5

Script de Validação

Para verificar se tudo foi configurado corretamente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#!/bin/bash
# Script de validação de Ansible

echo "Validando Ansible..."

# Verificar Ansible
if command -v ansible &> /dev/null; then
    echo "✓ Ansible instalado"
else
    echo "✗ Ansible não instalado"
    exit 1
fi

# Verificar versão
if ansible --version | grep -q "core 2"; then
    echo "✓ Ansible versão OK"
else
    echo "✗ Versão do Ansible incorreta"
    exit 1
fi

# Verificar arquivos
if [ -f "ansible.cfg" ]; then
    echo "✓ Arquivo ansible.cfg existe"
else
    echo "✗ Arquivo ansible.cfg não existe"
    exit 1
fi

# Verificar playbooks
if [ -f "playbooks/main.yml" ]; then
    echo "✓ Arquivo playbooks/main.yml existe"
else
    echo "✗ Arquivo playbooks/main.yml não existe"
    exit 1
fi

# Validar playbook
if ansible-playbook playbooks/main.yml --syntax-check &> /dev/null; then
    echo "✓ Playbook válido"
else
    echo "✗ Playbook inválido"
    exit 1
fi

# Verificar roles
if [ -d "roles/docker" ]; then
    echo "✓ Role docker existe"
else
    echo "✗ Role docker não existe"
    exit 1
fi

echo ""
echo "Validação concluída com sucesso!"

Conclusão

Você aprendeu a usar Ansible para provisionar e configurar máquinas virtuais com Docker. Com Ansible, você pode automatizar configurações complexas de forma declarativa e reutilizável.

O Que Você Alcançou

✓ Entendimento de Ansible e como funciona ✓ Instalação do Ansible ✓ Criação de inventário Ansible ✓ Desenvolvimento de playbooks ✓ Organização em roles reutilizáveis ✓ Instalação e configuração de Docker ✓ Instalação de Docker Compose ✓ Integração com Terraform

Próximos Passos Imediatos

  1. Verifique pré-requisitos:
    1
    2
    
    $ terraform --version
    $ systemctl is-active libvirtd
    
  2. Instale Ansible:
    1
    2
    
    $ sudo apt install -y ansible
    $ ansible --version
    
  3. Crie estrutura:
    1
    2
    
    $ mkdir -p ~/workspace-as-code/ansible/{playbooks,roles,inventory}
    $ cd ~/workspace-as-code/ansible
    
  4. Configure Ansible:
    1
    2
    3
    4
    5
    6
    
    $ cat > ansible.cfg << 'EOF'
    [defaults]
    inventory = inventory/hosts.ini
    host_key_checking = False
    private_key_file = ~/.ssh/terraform-vms
    EOF
    
  5. Crie playbooks:
    1
    
    $ ansible-playbook playbooks/main.yml --syntax-check
    
  6. Execute playbooks:
    1
    
    $ ansible-playbook playbooks/main.yml -i inventory/hosts.ini -v
    
  7. Versione no Git:
    1
    2
    3
    4
    
    $ cd ~/workspace-as-code
    $ git add ansible/
    $ git commit -m "feat: add ansible playbooks"
    $ git push origin main
    

Próximo Tutorial

Com Ansible configurado para provisionar máquinas, o próximo passo é aprender a usar múltiplas nuvens (AWS, Azure, GCP) para expandir sua infraestrutura além do ambiente local.


Recursos Adicionais


Fim da Parte 10

Próxima: Conceitos de Multi-Cloud e Equivalência de Recursos

This post is licensed under CC BY 4.0 by the author.