DevOps Admin

Guia Completo de Ansible Avançado: Roles, Templates Jinja2 e Ansible Vault Já leu

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: para tarefas, para notificações, para arquivos Jinja2, para variáveis privadas e para valores padrão. Você também encontrará para arquivos estáticos, para dependências e para validação. Criando sua primeira role Para começar, use o comando . Isso gera a estrutura completa automaticamente. Vamos criar uma role para instalar e configurar Nginx: Dentro de , adicione: Em , defina valores padrão: Em , adicione variáveis que sobrescrevem defaults: Em , defina ações acionadas

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.

Referências


Artigos relacionados