TL;DR — Resumo Rápido

Domine Ansible playbooks e roles para automacao de infraestrutura. Cobre inventario, modulos, Jinja2, ansible-vault, tratamento de erros e deploy LAMP.

ANSIBLE — PLAYBOOKS, ROLES E VAULT Inventario INI / YAML group_vars/ host_vars/ Playbook plays / tarefas handlers / vars Motor Ansible Roles / Modulos Vault / Jinja2 SSH webservers nginx / php dbservers mysql / backup loadbalancers haproxy / certs ansible-vault Segredos criptografados AES-256 Roles tasks / handlers templates / files defaults / meta Modelo push sem agentes — o inventario impulsiona tudo

Gerenciar infraestrutura em escala sem automacao e uma receita para deriva de configuracao, etapas esquecidas e incidentes as 3 da manha. Os playbooks e roles do Ansible oferecem uma maneira estruturada e repetivel de definir exatamente como cada servidor deve estar e aplicar esse estado de forma idempotente em toda a sua frota. Este guia cobre o fluxo de trabalho completo do Ansible — desde gerenciamento de inventario e arquitetura de playbooks ate roles, templates Jinja2, segredos com ansible-vault e tratamento de erros — com um deploy real de pilha LAMP como exemplo final.

Pre-requisitos

  • Ansible instalado em um no de controle (Ubuntu 22.04+ ou qualquer maquina Linux/macOS)
  • Acesso SSH baseado em chave a um ou mais hosts gerenciados
  • Python 3 em todos os hosts alvo (pre-instalado na maioria das distribuicoes)
  • Familiaridade basica com sintaxe YAML e linha de comando Linux

Arquitetura do Ansible: O Modelo Push

O Ansible usa uma arquitetura push sem agentes. Seu no de controle envia configuracoes aos nos gerenciados via SSH. Nao ha nenhum daemon para executar, nenhum banco de dados de estado e nada para instalar nos alvos alem de um interpretador Python funcional.

A cadeia de execucao e: Inventario → Playbook → Modulos.

  1. O Ansible le o inventario para determinar quais hosts apontar
  2. Analisa o playbook para construir uma lista ordenada de tarefas
  3. Para cada tarefa, copia um pequeno modulo Python ao host remoto via SSH, executa-o, captura o resultado e remove o modulo
  4. Os resultados (ok / changed / failed / skipped) sao agregados e exibidos no terminal

Gerenciamento de Inventario

Formato INI vs YAML

# inventory.ini
[webservers]
web1.example.com
web2.example.com ansible_port=2222

[dbservers]
db1.example.com
db2.example.com

[production:children]
webservers
dbservers

[all:vars]
ansible_user=deployer
ansible_ssh_private_key_file=~/.ssh/id_ed25519
# inventory.yml
all:
  vars:
    ansible_user: deployer
    ansible_ssh_private_key_file: ~/.ssh/id_ed25519
  children:
    webservers:
      hosts:
        web1.example.com:
        web2.example.com:
          ansible_port: 2222
    dbservers:
      hosts:
        db1.example.com:
        db2.example.com:

group_vars e host_vars

projeto/
  inventory.ini
  group_vars/
    all.yml
    webservers.yml
    dbservers.yml
  host_vars/
    web1.example.com.yml
  site.yml
# group_vars/webservers.yml
http_port: 80
https_port: 443
document_root: /var/www/html
worker_processes: 4

Inventario dinamico com o plugin aws_ec2

# aws_ec2.yml
plugin: amazon.aws.aws_ec2
regions:
  - us-east-1
filters:
  instance-state-name: running
  "tag:Environment": production
keyed_groups:
  - key: tags.Role
    prefix: role

Estrutura de um Playbook

- name: Configurar servidores web
  hosts: webservers
  become: true
  vars:
    app_name: myapp
    app_port: 8080

  tasks:
    - name: Instalar nginx
      apt:
        name: nginx
        state: present
        update_cache: true

    - name: Implantar config do nginx a partir do template
      template:
        src: templates/nginx.conf.j2
        dest: /etc/nginx/nginx.conf
        owner: root
        group: root
        mode: "0644"
      notify: Reiniciar nginx

    - name: Garantir que nginx esta ativo e habilitado
      service:
        name: nginx
        state: started
        enabled: true

  handlers:
    - name: Reiniciar nginx
      service:
        name: nginx
        state: restarted

Condicionais com when

- name: Instalar apache2 (somente Debian)
  apt:
    name: apache2
    state: present
  when: ansible_os_family == "Debian"

- name: Instalar httpd (somente Red Hat)
  yum:
    name: httpd
    state: present
  when: ansible_os_family == "RedHat"

Loops com loop

- name: Instalar pacotes necessarios
  apt:
    name: "{{ item }}"
    state: present
  loop:
    - nginx
    - curl
    - ufw
    - fail2ban

- name: Criar diretorios da aplicacao
  file:
    path: "{{ item.path }}"
    state: directory
    owner: "{{ item.owner }}"
    mode: "{{ item.mode }}"
  loop:
    - { path: /var/www/myapp, owner: www-data, mode: "0755" }
    - { path: /var/log/myapp, owner: www-data, mode: "0750" }

Templates Jinja2

{# templates/nginx.conf.j2 #}
user www-data;
worker_processes {{ worker_processes | default('auto') }};

http {
    server {
        listen {{ http_port }};
        server_name {{ server_name }};
        root {{ document_root }};

        {% if enable_ssl | default(false) %}
        listen {{ https_port }} ssl;
        ssl_certificate {{ ssl_cert_path }};
        {% endif %}
    }
}

Tags para execucao seletiva

# Executar apenas tarefas com a tag 'config'
ansible-playbook site.yml --tags config

# Ignorar tarefas com a tag 'install'
ansible-playbook site.yml --skip-tags install

Modulos Essenciais

ModuloPropositoParametros chave
apt / yumGerenciamento de pacotesname, state, update_cache
copyCopiar arquivos estaticossrc, dest, owner, mode
templateImplantar templates Jinja2src, dest, owner, mode
serviceGerenciar servicos do sistemaname, state, enabled
userGerenciar contas de usuarioname, groups, shell, state
fileCriar dirs, links simbolicospath, state, owner, mode
lineinfileEditar linhas em um arquivopath, regexp, line, state
commandExecutar um comandoargv, creates
shellComandos shell com pipescmd, chdir
gitClonar ou atualizar reporepo, dest, version
docker_containerGerenciar containers Dockername, image, state, ports
debugImprimir valores de variaveismsg, var

Estrutura de Roles

ansible-galaxy init roles/webserver
roles/webserver/
  tasks/
    main.yml
  handlers/
    main.yml
  templates/
    nginx.conf.j2
  files/
    index.html
  vars/
    main.yml
  defaults/
    main.yml
  meta/
    main.yml

defaults vs vars

  • defaults/main.yml: Baixa precedencia. Os chamadores podem facilmente substituir estes valores.
  • vars/main.yml: Alta precedencia. Estes substituem a maioria das outras fontes de variaveis.
# roles/webserver/defaults/main.yml
http_port: 80
https_port: 443
worker_processes: auto
document_root: /var/www/html
enable_ssl: false
# roles/webserver/tasks/main.yml
- name: Instalar nginx
  apt:
    name: nginx
    state: present
    update_cache: true

- name: Criar diretorio raiz do documento
  file:
    path: "{{ document_root }}"
    state: directory
    owner: www-data
    group: www-data
    mode: "0755"

- name: Implantar configuracao do nginx
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
  notify: Reiniciar nginx

Usar roles em um playbook

- name: Configurar nivel web
  hosts: webservers
  become: true
  roles:
    - role: common
    - role: security
    - role: webserver
      vars:
        http_port: 80
        enable_ssl: true
        document_root: /var/www/myapp

Ansible Galaxy para Roles da Comunidade

ansible-galaxy install geerlingguy.mysql
ansible-galaxy install -r requirements.yml
# requirements.yml
roles:
  - name: geerlingguy.nginx
    version: "3.2.0"
collections:
  - name: community.docker
    version: ">=3.4.0"

Ansible Vault para Gerenciamento de Segredos

ansible-vault create group_vars/all/vault.yml
ansible-vault edit group_vars/all/vault.yml
ansible-vault encrypt secrets.yml
ansible-vault encrypt_string 'MinhaSenha123' --name 'db_password'
# group_vars/all/vars.yml (texto simples, no git)
db_host: db1.example.com
db_password: "{{ vault_db_password }}"
# group_vars/all/vault.yml (criptografado, no git)
vault_db_password: "SuperSecreto123!"
vault_api_key: "abcdef1234567890"
ansible-playbook site.yml --vault-password-file ~/.vault_pass

Tratamento de Erros

ignore_errors e failed_when

- name: Verificar se a aplicacao esta instalada
  command: which myapp
  register: myapp_check
  ignore_errors: true
  changed_when: false

- name: Instalar aplicacao apenas se ausente
  apt:
    name: myapp
    state: present
  when: myapp_check.rc != 0

- name: Executar script de migracao
  command: /opt/myapp/migrate.sh
  register: migration_result
  failed_when: "'ERROR' in migration_result.stdout"
  changed_when: "'Changes applied' in migration_result.stdout"

block / rescue / always

- name: Implantar aplicacao com rollback
  block:
    - name: Parar servico da aplicacao
      service:
        name: myapp
        state: stopped

    - name: Implantar nova versao
      git:
        repo: https://github.com/org/myapp.git
        dest: /opt/myapp
        version: "{{ app_version }}"

    - name: Iniciar servico da aplicacao
      service:
        name: myapp
        state: started

  rescue:
    - name: Reverter para versao anterior
      git:
        repo: https://github.com/org/myapp.git
        dest: /opt/myapp
        version: "{{ previous_version }}"

    - name: Notificar equipe sobre falha
      debug:
        msg: "Deploy de {{ app_version }} falhou. Revertido para {{ previous_version }}."

  always:
    - name: Registrar tentativa de deploy
      shell: echo "{{ app_version }} tentado em $(date)" >> /var/log/deployments.log
      changed_when: false

Boas Praticas de Idempotencia

  • Prefira modulos em vez de shell/command: modulos verificam estado antes de agir
  • Use creates com command: pula o comando se o arquivo ja existir
  • Defina changed_when: false em comandos que apenas leem estado
  • Use lineinfile em vez de shell echo >> para modificar arquivos de configuracao
  • Evite apt: update_cache: true em cada tarefa: use cache_valid_time: 3600

Configuracao do ansible.cfg

[defaults]
inventory = ./inventory.ini
remote_user = deployer
private_key_file = ~/.ssh/id_ed25519
host_key_checking = False
retry_files_enabled = False
stdout_callback = yaml
roles_path = ./roles:~/.ansible/roles
forks = 10

[privilege_escalation]
become = True
become_method = sudo
become_user = root

Depuracao e Testes

ansible-playbook site.yml --check --diff
ansible-playbook site.yml -vvv
ansible-playbook site.yml --syntax-check
- name: Mostrar variaveis resolvidas
  debug:
    msg: "App: {{ app_name }}, Porta: {{ app_port }}"

- name: Capturar e inspecionar saida de comando
  command: systemctl status nginx
  register: nginx_status
  ignore_errors: true
  changed_when: false

- name: Imprimir saida do nginx
  debug:
    var: nginx_status.stdout_lines

Comparacao de Ferramentas

RecursoAnsibleTerraformPuppetChefSaltStack
Uso principalGerenc. config + orquestracaoProvisionamento infraGerenc. configGerenc. configConfig + exec remota
ArquiteturaSem agente (SSH)Sem agente (API)Com agenteCom agenteCom ou sem agente
LinguagemYAMLHCLPuppet DSLRubyYAML / Python
Rastreamento estadoSem estadoArquivo tfstatePuppetDBChef serverSalt mine
Curva aprendizadoBaixaMediaAltaAltaMedia
IdempotenteSimSimSimSimSim

Ansible e Terraform sao complementares: use Terraform para criar recursos na nuvem, depois Ansible para configura-los.

Exemplo Real: Deploy de Pilha LAMP

# lamp.yml
- name: Implantar pilha LAMP
  hosts: webservers
  become: true
  vars:
    php_version: "8.3"
    mysql_root_password: "{{ vault_mysql_root_password }}"
    mysql_db_name: myapp
    mysql_db_user: appuser
    mysql_db_password: "{{ vault_mysql_db_password }}"

  tasks:
    - name: Instalar Apache e pacotes PHP
      apt:
        name:
          - apache2
          - "libapache2-mod-php{{ php_version }}"
          - "php{{ php_version }}"
          - "php{{ php_version }}-mysql"
          - "php{{ php_version }}-curl"
          - "php{{ php_version }}-mbstring"
        state: present
        update_cache: true

    - name: Instalar servidor MySQL
      apt:
        name:
          - mysql-server
          - python3-pymysql
        state: present

    - name: Iniciar e habilitar MySQL
      service:
        name: mysql
        state: started
        enabled: true

    - name: Criar banco de dados da aplicacao
      mysql_db:
        login_user: root
        login_password: "{{ mysql_root_password }}"
        name: "{{ mysql_db_name }}"
        state: present

    - name: Habilitar mod_rewrite do Apache
      apache2_module:
        name: rewrite
        state: present
      notify: Reiniciar Apache

    - name: Clonar aplicacao do git
      git:
        repo: "{{ app_repo }}"
        dest: /var/www/html
        version: "{{ app_version }}"
        force: true

    - name: Definir propriedade do diretorio web
      file:
        path: /var/www/html
        owner: www-data
        group: www-data
        recurse: true

    - name: Garantir que Apache esta iniciado e habilitado
      service:
        name: apache2
        state: started
        enabled: true

  handlers:
    - name: Reiniciar Apache
      service:
        name: apache2
        state: restarted
ansible-playbook -i inventory.ini lamp.yml --vault-password-file ~/.vault_pass

Resumo

  • A arquitetura push sem agentes do Ansible significa que voce so precisa de acesso SSH para comecar a automatizar
  • O inventario (INI, YAML ou dinamico) direciona todo o apontamento de hosts; group_vars e host_vars manteem as variaveis organizadas
  • Os playbooks encadeiam plays, tarefas, handlers e templates Jinja2 em um fluxo de trabalho declarativo e legivel
  • As roles empacotam tarefas, handlers, templates e defaults em unidades reutilizaveis e compartilhaveis
  • ansible-vault criptografa segredos em repouso; combine arquivos vault com arquivos de variaveis em texto simples para manter o historico do git limpo
  • block/rescue/always fornece tratamento estruturado de erros e logica de rollback para deploys em producao
  • Sempre valide com --check --diff e -vvv antes de executar em producao

Artigos Relacionados