Artigo 29 — Ansible: Configurando Servidores de Forma Declarativa
Módulo 5 · Infraestrutura como Código Prof. Ricardo Matos — Dominando DevOps & Cloud em 1 Ano
O Que o Terraform Não Faz
O Terraform é excelente em provisionar infraestrutura — criar servidores, configurar redes, provisionar bancos de dados. Mas depois que o servidor existe, o Terraform não é a ferramenta ideal para gerenciar o que acontece dentro dele. Instalar pacotes, copiar arquivos de configuração, criar usuários, iniciar serviços, aplicar patches de segurança — essas são responsabilidades de uma ferramenta de gerenciamento de configuração.
O Ansible é a ferramenta mais adotada para essa função. Ele conecta a servidores via SSH, sem necessidade de agente instalado, e executa uma série de tarefas declaradas em arquivos YAML chamados playbooks. O objetivo é garantir que o estado interno de cada servidor corresponda ao que foi declarado — independentemente do estado inicial da máquina.
A divisão de responsabilidades entre Terraform e Ansible é clara e complementar: o Terraform cria e gerencia a infraestrutura como um todo, o Ansible configura e mantém o estado interno dos servidores que o Terraform criou.
Instalação e Conceitos Fundamentais
# No Ubuntu/Debian
sudo apt update
sudo apt install -y software-properties-common
sudo add-apt-repository --yes --update ppa:ansible/ansible
sudo apt install -y ansible
# Via pip — recomendado para obter a versão mais recente
pip install ansible --break-system-packages
# Verifica a instalação
ansible --version
Antes de aprofundar, é importante estabelecer os quatro conceitos centrais do Ansible:
Inventory — o arquivo que lista os servidores que o Ansible vai gerenciar, organizados em grupos. É o mapa dos alvos de execução.
Playbook — o arquivo principal de automação. Descreve quais tarefas devem ser executadas em quais servidores. É onde a lógica de configuração vive.
Task — uma unidade de trabalho dentro de um playbook. Cada task usa um módulo do Ansible para executar uma ação específica — instalar um pacote, copiar um arquivo, reiniciar um serviço.
Role — uma estrutura de organização que agrupa tasks, variáveis, templates e handlers relacionados em uma unidade reutilizável. Roles são para o Ansible o que módulos são para o Terraform.
O Arquivo de Inventory
O inventory informa ao Ansible quais servidores existem e como se conectar a eles. Pode ser um arquivo estático ou gerado dinamicamente a partir de APIs de nuvem.
Inventory estático em formato INI:
# inventory/hosts.ini
# Grupo de servidores web
[webservers]
web01 ansible_host=54.123.45.10 ansible_user=ubuntu
web02 ansible_host=54.123.45.11 ansible_user=ubuntu
# Grupo de bancos de dados
[databases]
db01 ansible_host=10.0.1.50 ansible_user=ubuntu
# Grupo de servidores de cache
[cache]
redis01 ansible_host=10.0.1.60 ansible_user=ubuntu
# Grupo que agrega outros grupos
[producao:children]
webservers
databases
cache
# Variáveis aplicadas a todos os servidores do grupo webservers
[webservers:vars]
ansible_ssh_private_key_file=~/.ssh/id_ed25519
nginx_port=80
app_port=3000
# Variáveis aplicadas a todos os hosts
[all:vars]
ansible_python_interpreter=/usr/bin/python3
Inventory em YAML — mais legível para configurações complexas:
# inventory/hosts.yml
all:
vars:
ansible_python_interpreter: /usr/bin/python3
ansible_ssh_private_key_file: ~/.ssh/id_ed25519
children:
webservers:
hosts:
web01:
ansible_host: 54.123.45.10
ansible_user: ubuntu
nginx_port: 80
web02:
ansible_host: 54.123.45.11
ansible_user: ubuntu
nginx_port: 80
vars:
app_port: 3000
databases:
hosts:
db01:
ansible_host: 10.0.1.50
ansible_user: ubuntu
postgres_version: "16"
cache:
hosts:
redis01:
ansible_host: 10.0.1.60
ansible_user: ubuntu
producao:
children:
webservers:
databases:
cache:
Testando a conectividade com o inventory:
# Testa conexão com todos os hosts
ansible all -i inventory/hosts.yml -m ping
# Testa apenas os webservers
ansible webservers -i inventory/hosts.yml -m ping
# Executa um comando ad-hoc em todos os servidores
ansible all -i inventory/hosts.yml -m command -a "uptime"
# Executa com sudo
ansible webservers -i inventory/hosts.yml -m command -a "df -h" --become
O Primeiro Playbook
Um playbook é um arquivo YAML que define uma ou mais plays. Cada play associa um conjunto de tasks a um grupo de hosts:
# playbooks/configurar-servidor-web.yml
---
- name: Configura servidor web com Nginx e Docker
hosts: webservers
become: true # equivalente ao sudo
gather_facts: true # coleta informações do sistema antes de executar
vars:
nginx_version: "1.24.*"
app_user: "deploy"
app_dir: "/opt/minha-api"
docker_compose_version: "2.24.0"
tasks:
- name: Atualiza o cache do apt
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600 # não atualiza se cache tem menos de 1 hora
- name: Instala pacotes essenciais
ansible.builtin.apt:
name:
- curl
- wget
- git
- unzip
- htop
- ufw
- fail2ban
- python3-pip
state: present
- name: Instala o Nginx
ansible.builtin.apt:
name: "nginx={{ nginx_version }}"
state: present
- name: Garante que o Nginx está iniciado e habilitado
ansible.builtin.systemd:
name: nginx
state: started
enabled: true
- name: Cria o usuário de deploy
ansible.builtin.user:
name: "{{ app_user }}"
shell: /bin/bash
create_home: true
groups: docker
append: true
- name: Cria o diretório da aplicação
ansible.builtin.file:
path: "{{ app_dir }}"
state: directory
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0755"
- name: Copia o arquivo de configuração do Nginx
ansible.builtin.template:
src: templates/nginx.conf.j2
dest: /etc/nginx/sites-available/minha-api
owner: root
group: root
mode: "0644"
notify: Recarrega Nginx
- name: Habilita o site no Nginx
ansible.builtin.file:
src: /etc/nginx/sites-available/minha-api
dest: /etc/nginx/sites-enabled/minha-api
state: link
notify: Recarrega Nginx
- name: Remove a configuração padrão do Nginx
ansible.builtin.file:
path: /etc/nginx/sites-enabled/default
state: absent
notify: Recarrega Nginx
handlers:
- name: Recarrega Nginx
ansible.builtin.systemd:
name: nginx
state: reloaded
Os handlers são tasks especiais que só são executados quando notificados por outras tasks e apenas uma vez ao final do playbook — mesmo que múltiplas tasks os notifiquem. São o mecanismo correto para reiniciar serviços após mudanças de configuração.
Templates com Jinja2
O Ansible usa o motor de templates Jinja2 para gerar arquivos de configuração dinâmicos. Variáveis do playbook, do inventory e de facts do sistema ficam disponíveis nos templates:
# templates/nginx.conf.j2
server {
listen {{ nginx_port | default(80) }};
server_name {{ ansible_hostname }};
access_log /var/log/nginx/{{ app_user }}-access.log;
error_log /var/log/nginx/{{ app_user }}-error.log;
location / {
proxy_pass http://127.0.0.1:{{ app_port }};
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_cache_bypass $http_upgrade;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
location /health {
access_log off;
proxy_pass http://127.0.0.1:{{ app_port }}/health;
}
# Configurações de segurança
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
{% if ssl_enabled | default(false) %}
listen 443 ssl;
ssl_certificate /etc/ssl/certs/{{ ansible_hostname }}.crt;
ssl_certificate_key /etc/ssl/private/{{ ansible_hostname }}.key;
ssl_protocols TLSv1.2 TLSv1.3;
{% endif %}
}
Roles: Organizando Automações Reutilizáveis
Quando os playbooks crescem, roles são a solução para organizar e reutilizar código. A estrutura de uma role segue uma convenção rígida que o Ansible reconhece automaticamente:
roles/
└── docker/
├── tasks/
│ ├── main.yml # Ponto de entrada das tasks
│ ├── install.yml # Tasks de instalação
│ └── configure.yml # Tasks de configuração
├── handlers/
│ └── main.yml # Handlers da role
├── templates/
│ └── daemon.json.j2 # Templates de configuração
├── files/
│ └── docker-cleanup.sh # Arquivos estáticos
├── vars/
│ └── main.yml # Variáveis fixas da role
├── defaults/
│ └── main.yml # Variáveis com valores padrão
└── meta/
└── main.yml # Metadados e dependências da role
Implementação completa da role de Docker:
# roles/docker/defaults/main.yml
---
docker_edition: "ce"
docker_version: "5:25.0.0-1~ubuntu.22.04~jammy"
docker_compose_version: "2.24.0"
docker_users: []
docker_daemon_options:
log-driver: "json-file"
log-opts:
max-size: "100m"
max-file: "3"
storage-driver: "overlay2"
# roles/docker/tasks/main.yml
---
- name: Inclui tasks de instalação
ansible.builtin.import_tasks: install.yml
- name: Inclui tasks de configuração
ansible.builtin.import_tasks: configure.yml
# roles/docker/tasks/install.yml
---
- name: Remove versões antigas do Docker
ansible.builtin.apt:
name:
- docker
- docker-engine
- docker.io
- containerd
- runc
state: absent
- name: Instala dependências do repositório Docker
ansible.builtin.apt:
name:
- ca-certificates
- curl
- gnupg
- lsb-release
state: present
update_cache: true
- name: Cria diretório para keyrings
ansible.builtin.file:
path: /etc/apt/keyrings
state: directory
mode: "0755"
- name: Adiciona a chave GPG oficial do Docker
ansible.builtin.apt_key:
url: https://download.docker.com/linux/ubuntu/gpg
keyring: /etc/apt/keyrings/docker.gpg
state: present
- name: Adiciona o repositório do Docker
ansible.builtin.apt_repository:
repo: >
deb [arch={{ ansible_architecture }}
signed-by=/etc/apt/keyrings/docker.gpg]
https://download.docker.com/linux/ubuntu
{{ ansible_distribution_release }} stable
state: present
filename: docker
- name: Instala o Docker Engine
ansible.builtin.apt:
name:
- "docker-{{ docker_edition }}={{ docker_version }}"
- docker-ce-cli
- containerd.io
- docker-buildx-plugin
- docker-compose-plugin
state: present
update_cache: true
- name: Garante que o Docker está iniciado e habilitado
ansible.builtin.systemd:
name: docker
state: started
enabled: true
# roles/docker/tasks/configure.yml
---
- name: Copia configuração do Docker daemon
ansible.builtin.template:
src: daemon.json.j2
dest: /etc/docker/daemon.json
owner: root
group: root
mode: "0644"
notify: Reinicia Docker
- name: Adiciona usuários ao grupo docker
ansible.builtin.user:
name: "{{ item }}"
groups: docker
append: true
loop: "{{ docker_users }}"
when: docker_users | length > 0
- name: Instala o Docker Compose standalone
ansible.builtin.get_url:
url: "https://github.com/docker/compose/releases/download/v{{ docker_compose_version }}/docker-compose-linux-{{ ansible_architecture }}"
dest: /usr/local/bin/docker-compose
mode: "0755"
owner: root
group: root
# roles/docker/handlers/main.yml
---
- name: Reinicia Docker
ansible.builtin.systemd:
name: docker
state: restarted
{# roles/docker/templates/daemon.json.j2 #}
{{ docker_daemon_options | to_nice_json }}
Um Playbook Completo Usando Roles
# playbooks/site.yml
---
- name: Configura servidores base
hosts: all
become: true
gather_facts: true
roles:
- role: base
vars:
timezone: "America/Sao_Paulo"
ntp_servers:
- "0.br.pool.ntp.org"
- "1.br.pool.ntp.org"
- name: Configura servidores web
hosts: webservers
become: true
roles:
- role: docker
vars:
docker_users:
- ubuntu
- deploy
- role: nginx
vars:
nginx_port: 80
app_port: 3000
- role: aplicacao
vars:
app_image: "ghcr.io/minha-empresa/minha-api:{{ app_version }}"
- name: Configura servidores de banco de dados
hosts: databases
become: true
roles:
- role: postgres
vars:
postgres_version: "16"
postgres_max_connections: 200
postgres_shared_buffers: "256MB"
Variáveis e Precedência
O Ansible tem um sistema de precedência de variáveis com 22 níveis. Os mais relevantes na prática, do menor para o maior:
role defaults → valores padrão das roles — facilmente sobrescritos
inventory vars → variáveis definidas no inventory
playbook vars → variáveis definidas no playbook
role vars → variáveis fixas das roles
extra vars (-e) → variáveis passadas na linha de comando — maior precedência
Usar a precedência corretamente torna as configurações flexíveis sem sacrificar clareza:
# Sobrescreve variáveis na linha de comando — útil para deploys
ansible-playbook \
-i inventory/hosts.yml \
playbooks/site.yml \
-e "app_version=1.5.0" \
-e "ambiente=producao"
Ansible Vault: Criptografando Segredos
Variáveis sensíveis — senhas, tokens, chaves — nunca devem ficar em texto puro nos arquivos do Ansible. O Ansible Vault criptografa arquivos ou strings individuais usando AES-256:
# Cria um arquivo de variáveis criptografado
ansible-vault create inventory/group_vars/all/vault.yml
# Edita um arquivo vault existente
ansible-vault edit inventory/group_vars/all/vault.yml
# Criptografa um arquivo existente
ansible-vault encrypt inventory/group_vars/databases/vars.yml
# Descriptografa para visualização
ansible-vault view inventory/group_vars/all/vault.yml
# Criptografa apenas uma string
ansible-vault encrypt_string 'senha_super_secreta' --name 'db_password'
Estrutura recomendada para variáveis com vault:
# inventory/group_vars/databases/vars.yml — não criptografado
postgres_user: app
postgres_db: producao
postgres_port: 5432
# inventory/group_vars/databases/vault.yml — criptografado com vault
vault_postgres_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
66386439653236336462626566653061373932...
# inventory/group_vars/databases/vars.yml
# Referencia a variável do vault com um nome sem prefixo vault_
postgres_password: "{{ vault_postgres_password }}"
Executando playbooks com vault:
# Solicita a senha interativamente
ansible-playbook playbooks/site.yml --ask-vault-pass
# Lê a senha de um arquivo (útil em CI/CD)
echo "minha-senha-vault" > .vault-password
chmod 600 .vault-password
ansible-playbook playbooks/site.yml --vault-password-file .vault-password
# Variável de ambiente
export ANSIBLE_VAULT_PASSWORD_FILE=.vault-password
ansible-playbook playbooks/site.yml
Idempotência: O Princípio Central
A idempotência é o princípio que garante que executar o mesmo playbook múltiplas vezes produz sempre o mesmo resultado — sem efeitos colaterais cumulativos. Instalar o Nginx uma segunda vez não deve causar erro. Criar um usuário que já existe não deve falhar.
A maioria dos módulos do Ansible é idempotente por design — o módulo apt verifica se o pacote já está instalado antes de tentar instalá-lo, o módulo file verifica se o arquivo já existe com as permissões corretas antes de modificá-lo. É responsabilidade do autor do playbook garantir que tasks personalizadas com command ou shell também sejam idempotentes:
# Não idempotente — executa sempre, mesmo se já foi feito
- name: Inicializa o banco de dados
ansible.builtin.command: npm run db:migrate
# Idempotente — cria um arquivo de flag após a primeira execução
- name: Verifica se o banco já foi inicializado
ansible.builtin.stat:
path: /opt/app/.db-initialized
register: db_initialized
- name: Inicializa o banco de dados
ansible.builtin.command: npm run db:migrate
when: not db_initialized.stat.exists
- name: Marca banco como inicializado
ansible.builtin.file:
path: /opt/app/.db-initialized
state: touch
when: not db_initialized.stat.exists
O Que Vem a Seguir
O próximo artigo fecha o Módulo 5 combinando Terraform e Ansible em um fluxo de trabalho unificado — provisionar a infraestrutura com Terraform e em seguida configurá-la com Ansible, formando um pipeline completo de IaC do zero ao servidor pronto para receber a aplicação.
Referências para Aprofundamento
Documentação oficial - Ansible Documentation — docs.ansible.com — Documentação completa do Ansible, incluindo referência de todos os módulos disponíveis, guias de roles e boas práticas. - Ansible Galaxy — galaxy.ansible.com — Repositório público de roles prontas. Equivalente ao Terraform Registry para o ecossistema Ansible. Contém roles mantidas pela comunidade para as ferramentas mais comuns.
Boas práticas - Ansible Best Practices — docs.ansible.com — Guia oficial de boas práticas cobrindo estrutura de projetos, nomenclatura e organização de inventories. - Jeff Geerling — Ansible for DevOps — Livro amplamente recomendado pela comunidade, com exemplos práticos de automação de servidores. O autor mantém dezenas de roles públicas no Ansible Galaxy.
Prática - Ansible Molecule — molecule.readthedocs.io — Framework para testes de roles Ansible. Permite testar roles em containers Docker ou máquinas virtuais antes de aplicá-las em produção.
Artigo 29 de 52 · Módulo 5 — Infraestrutura como Código Prof. Ricardo Matos · Série Dominando DevOps & Cloud em 1 Ano
you asked
Continue