Entendendo Roles: A Estrutura Profissional do Ansible
Uma role no Ansible é um padrão de organização que encapsula lógica de configuração de forma reutilizável e modular. Diferentemente de playbooks lineares, roles oferecem uma estrutura de diretórios padronizada que facilita a manutenção, o versionamento e o compartilhamento de código entre projetos. Quando você trabalha em ambientes corporativos, a diferença entre um playbook monolítico e uma estrutura baseada em roles é a diferença entre caos e profissionalismo.
Uma role segue convenções de diretórios. Ansible procura automaticamente por arquivos em locais específicos: tasks/ para tarefas, handlers/ para notificações, templates/ para arquivos Jinja2, vars/ para variáveis privadas e defaults/ para valores padrão. Você também encontrará files/ para arquivos estáticos, meta/ para dependências e tests/ para validação.
minha_aplicacao/
├── defaults/
│ └── main.yml
├── files/
│ └── aplicacao.conf
├── handlers/
│ └── main.yml
├── meta/
│ └── main.yml
├── tasks/
│ └── main.yml
├── templates/
│ └── nginx.conf.j2
├── tests/
│ ├── inventory
│ └── test.yml
└── vars/
└── main.yml
Criando sua primeira role
Para começar, use o comando ansible-galaxy init nome_da_role. Isso gera a estrutura completa automaticamente. Vamos criar uma role para instalar e configurar Nginx:
ansible-galaxy init webserver
Dentro de webserver/tasks/main.yml, adicione:
---
- name: Instalar Nginx
apt:
name: nginx
state: present
update_cache: yes
when: ansible_os_family == "Debian"
- name: Ativar Nginx
systemd:
name: nginx
enabled: yes
state: started
- name: Copiar configuração Nginx
template:
src: nginx.conf.j2
dest: /etc/nginx/sites-available/default
backup: yes
notify: restart nginx
- name: Validar sintaxe Nginx
command: nginx -t
changed_when: false
Em webserver/defaults/main.yml, defina valores padrão:
---
nginx_port: 80
nginx_user: www-data
nginx_worker_processes: auto
nginx_keepalive_timeout: 65
max_body_size: 20m
Em webserver/vars/main.yml, adicione variáveis que sobrescrevem defaults:
---
nginx_conf_path: /etc/nginx/sites-available/default
nginx_service_name: nginx
Em webserver/handlers/main.yml, defina ações acionadas por notificações:
---
- name: restart nginx
systemd:
name: nginx
state: restarted
- name: reload nginx
systemd:
name: nginx
state: reloaded
Usando roles em playbooks
Para usar a role, crie um playbook site.yml:
---
- hosts: webservers
become: yes
roles:
- webserver
Ou com variáveis inline:
---
- hosts: webservers
become: yes
roles:
- role: webserver
vars:
nginx_port: 8080
max_body_size: 50m
Você também pode passar argumentos condicionalmente:
---
- hosts: all
roles:
- role: webserver
when: inventory_hostname in groups['webservers']
tags:
- web
- setup
Templates Jinja2: Dinamismo em Configurações
Jinja2 é um motor de templates poderoso que permite gerar arquivos de configuração dinamicamente usando variáveis do Ansible. Em vez de manter múltiplas versões estáticas de um arquivo de configuração, você cria um template que se adapta ao contexto de cada host. Isso reduz drasticamente duplicação e erros humanos em ambientes heterogêneos.
A sintaxe Jinja2 usa {{ variavel }} para inserção de variáveis, {% if condicao %} para lógica condicional e {% for item in lista %} para loops. Dentro de um template, você tem acesso a todas as variáveis do Ansible (facts, vars, defaults) e pode aplicar filtros para transformar dados.
Estrutura básica de templates
Crie webserver/templates/nginx.conf.j2:
user {{ nginx_user }};
worker_processes {{ nginx_worker_processes }};
pid /run/nginx.pid;
events {
worker_connections 768;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout {{ nginx_keepalive_timeout }};
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript
application/json application/javascript application/xml+rss;
include /etc/nginx/sites-enabled/*;
}
Lógica condicional em templates
Para adicionar blocos opcionais baseado em variáveis, edite o template:
http {
{% if enable_ssl %}
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
{% endif %}
{% if enable_compression %}
gzip on;
gzip_types text/plain text/css application/json application/javascript;
{% endif %}
{% if custom_headers %}
add_header X-Custom-Header "{{ custom_header_value }}";
{% endif %}
}
Em seu defaults/main.yml:
enable_ssl: false
enable_compression: true
custom_headers: false
Loops em templates
Para gerar múltiplos blocos de configuração, use loops. Crie webserver/templates/vhosts.conf.j2:
{% for vhost in nginx_vhosts %}
server {
listen {{ vhost.port | default(80) }};
server_name {{ vhost.server_name }};
root {{ vhost.root | default('/var/www/html') }};
index {{ vhost.index | default('index.html') }};
{% if vhost.ssl_enabled | default(false) %}
listen 443 ssl;
ssl_certificate {{ vhost.ssl_cert }};
ssl_certificate_key {{ vhost.ssl_key }};
{% endif %}
location / {
try_files $uri $uri/ =404;
}
{% if vhost.proxy_pass %}
location /api {
proxy_pass {{ vhost.proxy_pass }};
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
{% endif %}
}
{% endfor %}
Defina em vars/main.yml:
nginx_vhosts:
- server_name: "exemplo.com"
port: 80
root: "/var/www/exemplo"
proxy_pass: "http://localhost:3000"
- server_name: "api.exemplo.com"
port: 80
root: "/var/www/api"
ssl_enabled: true
ssl_cert: "/etc/ssl/certs/api.crt"
ssl_key: "/etc/ssl/private/api.key"
Filtros Jinja2 essenciais
Jinja2 oferece filtros que transformam dados. Alguns dos mais úteis no contexto Ansible:
{{ variavel | default('valor_padrao') }}
{{ lista | join(', ') }}
{{ string | upper }}
{{ string | lower }}
{{ numero | string }}
{{ dados | to_nice_json }}
{{ caminho | basename }}
{{ string | regex_replace('^(.*)$', 'prefixo_\\1') }}
{{ lista | select('match', 'padrao.*') | list }}
Um exemplo prático no template:
# Comentário gerado em {{ ansible_date_time.iso8601 }}
# Host: {{ inventory_hostname }}
# OS: {{ ansible_distribution }} {{ ansible_distribution_version }}
error_log /var/log/nginx/error.log {{ log_level | default('warn') }};
access_log /var/log/nginx/access.log {% if access_log_format %}{{ access_log_format }}{% else %}combined{% endif %};
Ansible Vault: Segurança para Dados Sensíveis
Ansible Vault é um sistema de criptografia que protege arquivos contendo informações sensíveis como senhas, chaves privadas e tokens de API. Toda organização que trabalha com configuração como código deve usar Vault; armazenar senhas em texto plano em repositórios Git é uma vulnerabilidade grave. O Vault usa criptografia AES-256 e requer uma senha para descriptografar arquivos durante a execução do playbook.
A estratégia é simples: você cria um arquivo Vault contendo valores sensíveis, referencia esses valores em seus playbooks e templates normalmente, e o Ansible descriptografa automaticamente quando necessário. Isso mantém o fluxo de trabalho transparente enquanto protege dados em repouso.
Criando e editando arquivos Vault
Para criar um novo arquivo criptografado:
ansible-vault create credenciais.yml
Você será solicitado a definir uma senha. Então um editor abre para você adicionar conteúdo:
---
db_password: "senhaForte123!@#"
api_key: "sk-proj-1a2b3c4d5e6f7g8h9i0j"
jwt_secret: "seu-jwt-secret-muito-longo-aqui"
backup_passphrase: "backup@2024"
Para editar depois:
ansible-vault edit credenciais.yml
Para visualizar sem editar:
ansible-vault view credenciais.yml
Para mudar a senha:
ansible-vault rekey credenciais.yml
Integrando Vault em playbooks
Inclua o arquivo Vault em seu playbook referenciando-o normalmente:
---
- hosts: database_servers
become: yes
vars_files:
- credenciais.yml
tasks:
- name: Configurar PostgreSQL
postgresql_query:
db: postgres
query: "ALTER USER postgres WITH PASSWORD '{{ db_password }}';"
environment:
PGPASSWORD: "{{ db_password }}"
- name: Gerar arquivo de configuração da aplicação
template:
src: app_config.yml.j2
dest: /etc/app/config.yml
mode: '0640'
owner: app_user
group: app_group
- name: Registrar API key no serviço
uri:
url: "https://api.exemplo.com/register"
method: POST
body_format: json
body:
api_key: "{{ api_key }}"
environment: "production"
Em templates/app_config.yml.j2:
# Configuração gerada automaticamente - não editar manualmente
database:
host: {{ db_host }}
port: {{ db_port }}
username: {{ db_user }}
password: {{ db_password }}
name: {{ db_name }}
api:
endpoint: {{ api_endpoint }}
key: {{ api_key }}
timeout: 30
backup:
enabled: true
passphrase: {{ backup_passphrase }}
retention_days: 30
Executando playbooks com Vault
Para executar um playbook que use arquivos Vault, use a flag --ask-vault-pass:
ansible-playbook site.yml --ask-vault-pass
O Ansible solicitará a senha do Vault. Alternativamente, armazene a senha em um arquivo seguro:
# Crie um arquivo com a senha (tenha cuidado com permissões)
echo "minha-senha-vault" > ~/.vault_password
# Configure permissões restritivas
chmod 600 ~/.vault_password
# Execute o playbook
ansible-playbook site.yml --vault-password-file ~/.vault_password
Ou configure no arquivo ansible.cfg:
[defaults]
vault_password_file = ~/.vault_password
Criptografando valores individuais
Você não precisa criptografar arquivos inteiros. Use ansible-vault encrypt_string para criptografar um valor específico:
ansible-vault encrypt_string 'senhaForte123!@#' --name 'db_password'
Saída:
db_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
32643062306465613666336661666565356165303063666165653132613734623634303834306537
3237353461303061623162343739336666653535666234650a343438333436643631663539383639
31313665633439643363396164313364316437353235383164633761616465616266613264633736
3231656139386234300a346334396632393134626634643461363632616238346264653064383862
6266
Copie e cole esse bloco em seus arquivos YAML:
---
- hosts: all
vars:
db_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
32643062306465613666336661666565356165303063666165653132613734623634303834306537
3237353461303061623162343739336666653535666234650a343438333436643631663539383639
31313665633439643363396164313364316437353235383164633761616465616266613264633736
3231656139386234300a346334396632393134626634643461363632616238346264653064383862
6266
tasks:
- name: Usar senha descriptografada
debug:
msg: "Senha é {{ db_password }}"
Gerenciamento de segurança com Vault
Para manter segurança em equipes, nunca commite o arquivo .vault_password no Git. Use um .gitignore:
.vault_password
*.vault
*.key
Para compartilhar a senha com colegas de trabalho, use um gerenciador de senhas corporativo ou passe de forma segura fora do repositório. Em ambientes de CI/CD, injete a senha como variável de ambiente:
export ANSIBLE_VAULT_PASSWORD_FILE=/run/secrets/vault_password
ansible-playbook site.yml
Em GitHub Actions:
- name: Run Ansible playbook
env:
ANSIBLE_VAULT_PASSWORD: ${{ secrets.ANSIBLE_VAULT_PASSWORD }}
run: |
echo "$ANSIBLE_VAULT_PASSWORD" > /tmp/vault_pass
chmod 600 /tmp/vault_pass
ansible-playbook site.yml --vault-password-file /tmp/vault_pass
rm /tmp/vault_pass
Integrando Roles, Templates e Vault em um Projeto Real
A verdadeira potência emerge quando combinamos esses três componentes. Vamos construir um cenário realista: implantar uma aplicação Node.js com Nginx como proxy reverso, usando uma role bem estruturada, templates dinâmicos e dados sensíveis protegidos.
Estrutura do projeto:
ansible-projeto/
├── ansible.cfg
├── inventario.yml
├── site.yml
├── credenciais.yml (criptografado com Vault)
├── roles/
│ ├── nodejs-app/
│ │ ├── defaults/main.yml
│ │ ├── tasks/main.yml
│ │ ├── templates/
│ │ │ ├── app.env.j2
│ │ │ └── systemd-app.service.j2
│ │ ├── handlers/main.yml
│ │ └── vars/main.yml
│ └── nginx-proxy/
│ ├── defaults/main.yml
│ ├── tasks/main.yml
│ ├── templates/
│ │ └── nginx-app.conf.j2
│ ├── handlers/main.yml
│ └── vars/main.yml
└── group_vars/
└── app_servers.yml
Arquivo ansible.cfg:
[defaults]
inventory = inventario.yml
vault_password_file = ~/.vault_password
roles_path = ./roles
host_key_checking = False
Arquivo inventario.yml:
---
all:
children:
app_servers:
hosts:
app01.exemplo.com:
ansible_user: deploy
app02.exemplo.com:
ansible_user: deploy
Arquivo credenciais.yml (criptografado):
---
db_host: postgres.interno.com
db_user: app_user
db_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
...conteudo_criptografado...
app_secret_key: !vault |
$ANSIBLE_VAULT;1.1;AES256
...conteudo_criptografado...
Arquivo group_vars/app_servers.yml:
---
app_name: minha-app
app_user: app
app_group: app
app_port: 3000
app_version: 1.2.3
nodejs_version: 18
nginx_proxy_port: 80
Arquivo site.yml:
---
- hosts: app_servers
become: yes
vars_files:
- credenciais.yml
pre_tasks:
- name: Atualizar cache do apt
apt:
update_cache: yes
cache_valid_time: 3600
when: ansible_os_family == "Debian"
roles:
- nodejs-app
- nginx-proxy
post_tasks:
- name: Validar status da aplicação
uri:
url: "http://localhost/health"
method: GET
status_code: 200
register: health_check
retries: 3
delay: 5
until: health_check is success
Role nodejs-app - roles/nodejs-app/tasks/main.yml:
---
- name: Instalar Node.js
block:
- name: Adicionar NodeSource repository
shell: |
curl -fsSL https://deb.nodesource.com/setup_{{ nodejs_version }}.x | sudo -E bash -
args:
warn: false
- name: Instalar Node.js
apt:
name: nodejs
state: present
- name: Criar usuário da aplicação
user:
name: "{{ app_user }}"
shell: /bin/bash
home: "/home/{{ app_user }}"
createhome: yes
state: present
- name: Clonar repositório da aplicação
git:
repo: "{{ app_repo_url }}"
dest: "/var/www/{{ app_name }}"
version: "{{ app_version }}"
update: yes
register: app_clone
become_user: "{{ app_user }}"
- name: Instalar dependências Node.js
npm:
path: "/var/www/{{ app_name }}"
state: present
when: app_clone is changed
- name: Gerar arquivo .env
template:
src: app.env.j2
dest: "/var/www/{{ app_name }}/.env"
owner: "{{ app_user }}"
group: "{{ app_group }}"
mode: '0640'
notify: restart app
- name: Criar serviço systemd
template:
src: systemd-app.service.j2
dest: "/etc/systemd/system/{{ app_name }}.service"
owner: root
group: root
mode: '0644'
notify: restart app
- name: Habilitar e iniciar serviço
systemd:
name: "{{ app_name }}"
enabled: yes
state: started
daemon_reload: yes
Template roles/nodejs-app/templates/app.env.j2:
# Arquivo de configuração gerado automaticamente
# Última atualização: {{ ansible_date_time.iso8601 }}
NODE_ENV=production
APP_PORT={{ app_port }}
APP_NAME={{ app_name }}
APP_VERSION={{ app_version }}
# Database
DB_HOST={{ db_host }}
DB_USER={{ db_user }}
DB_PASSWORD={{ db_password }}
DB_NAME={{ app_name }}_prod
# Security
SECRET_KEY={{ app_secret_key }}
SESSION_SECRET={{ app_session_secret | default('change-me') }}
# Features
ENABLE_CACHE={{ enable_cache | default('true') }}
ENABLE_LOGGING={{ enable_logging | default('true') }}
LOG_LEVEL={{ log_level | default('info') }}
Template roles/nodejs-app/templates/systemd-app.service.j2:
[Unit]
Description={{ app_name }} Node.js Application
After=network.target
[Service]
Type=simple
User={{ app_user }}
Group={{ app_group }}
WorkingDirectory=/var/www/{{ app_name }}
ExecStart=/usr/bin/node server.js
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal
Environment="NODE_ENV=production"
Environment="PORT={{ app_port }}"
[Install]
WantedBy=multi-user.target
Role nginx-proxy - roles/nginx-proxy/tasks/main.yml:
---
- name: Instalar Nginx
apt:
name: nginx
state: present
- name: Gerar configuração Nginx
template:
src: nginx-app.conf.j2
dest: "/etc/nginx/sites-available/{{ app_name }}"
owner: root
group: root
mode: '0644'
notify: test and reload nginx
- name: Ativar site Nginx
file:
src: "/etc/nginx/sites-available/{{ app_name }}"
dest: "/etc/nginx/sites-enabled/{{ app_name }}"
state: link
notify: test and reload nginx
- name: Habilitar e iniciar Nginx
systemd:
name: nginx
enabled: yes
state: started
Template roles/nginx-proxy/templates/nginx-app.conf.j2:
# Configuração Nginx para {{ app_name }}
# Gerada em {{ ansible_date_time.iso8601 }} em {{ inventory_hostname }}
upstream {{ app_name }}_backend {
server 127.0.0.1:{{ app_port }};
keepalive 32;
}
server {
listen {{ nginx_proxy_port }};
server_name {{ server_names | join(' ') }};
access_log /var/log/nginx/{{ app_name }}_access.log combined;
error_log /var/log/nginx/{{ app_name }}_error.log warn;
client_max_body_size 50m;
# Health check endpoint
location /health {
access_log off;
proxy_pass http://{{ app_name }}_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
# API e aplicação
location / {
proxy_pass http://{{ app_name }}_backend;
proxy_http_version 1.1;
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_set_header X-Forwarded-Proto $scheme;
proxy_set_header Connection "";
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
{% if enable_gzip | default(true) %}
gzip on;
gzip_types text/plain text/css text/javascript application/json application/javascript;
gzip_min_length 1000;
{% endif %}
}
Para executar:
# Primeira execução - solicita senha do Vault
ansible-playbook site.yml --ask-vault-pass
# Com arquivo de senha (mais seguro em CI/CD)
ansible-playbook site.yml --vault-password-file ~/.vault_password
Conclusão
Dominar Roles, Templates Jinja2 e Ansible Vault transforma você de um usuário básico de Ansible para um profissional capaz de implementar infraestrutura como código em escala corporativa. As três competências trabalham juntas: Roles organizam sua lógica em estruturas reutilizáveis; Templates Jinja2 tornam configurações dinâmicas e adaptáveis; Vault protege dados sensíveis mantendo o fluxo transparente. Esse tripé é a base para automação confiável, auditável e segura.
Na prática, sempre pense em reutilização: uma role bem escrita deve funcionar em múltiplos contextos através de variáveis. Use templates para tudo que varia entre ambientes—nunca commite arquivos de configuração estáticos quando um template pode substituí-lo. E nunca, em hipótese alguma, commite senhas em repositórios; o Vault existe justamente para isso e seu custo de implementação é praticamente zero.