Ansible para desenvolvedores: provisionamento de servidor sem Kubernetes

1. Por que Ansible em vez de Kubernetes para times pequenos?

Quando um time de desenvolvimento tem poucos servidores e aplicações de baixa complexidade, o Kubernetes frequentemente representa overkill. Orquestrar contêineres com dezenas de pods, services e ingress controllers exige infraestrutura dedicada (etcd, control plane, workers) e conhecimento profundo de conceitos como service mesh e network policies. Para um time pequeno que precisa provisionar três ou quatro servidores web, um banco de dados e alguns workers CRON, essa complexidade é desnecessária.

O Ansible resolve o problema com simplicidade radical: agente zero. O único requisito é SSH funcionando nos servidores alvo. Não há instalação de daemon, nem gerenciamento de clusters, nem curva de aprendizado de YAML complexo com múltiplas camadas de abstração. Com um laptop e acesso SSH, qualquer desenvolvedor pode provisionar um servidor completo em minutos.

Casos de uso ideais para Ansible incluem:
- Deploys de aplicações monolíticas ou com poucos microsserviços
- Servidores que executam tarefas agendadas (CRONs)
- Aplicações stateful que precisam de armazenamento persistente local
- Ambientes de desenvolvimento e homologação com poucos recursos

2. Setup mínimo: do zero ao primeiro playbook

A instalação do Ansible é trivial em qualquer sistema Unix-like. No Linux/macOS ou WSL:

# Instalação via pip (recomendado para desenvolvedores)
pip install ansible

# Verificação
ansible --version

A estrutura de diretórios recomendada para projetos pequenos:

meu-projeto-ansible/
├── inventory/
│   └── hosts
├── playbooks/
│   └── setup-server.yml
├── roles/
│   ├── nginx/
│   └── app/
└── ansible.cfg

O arquivo ansible.cfg básico:

[defaults]
inventory = inventory/hosts
host_key_checking = False
remote_user = deploy

Primeiro comando ad-hoc para testar conectividade:

ansible all -m ping

Se o servidor responder com "ping": "pong", o ambiente está pronto.

3. Inventário e variáveis: organizando servidores como código

Inventário estático simples (inventory/hosts):

[webservers]
web01 ansible_host=192.168.1.10 ansible_user=root
web02 ansible_host=192.168.1.11

[databases]
db01 ansible_host=192.168.1.20

[all:vars]
ansible_python_interpreter=/usr/bin/python3

Para ambientes maiores, inventários dinâmicos (ex.: AWS EC2) podem ser usados com scripts ou plugins.

Variáveis organizadas em group_vars/:

# group_vars/webservers.yml
nginx_port: 8080
app_domain: "meusite.com.br"
ssl_email: "admin@meusite.com.br"

# group_vars/all.yml
timezone: "America/Sao_Paulo"
ntp_servers:
  - a.ntp.br
  - b.ntp.br

O Ansible coleta automaticamente ansible_facts sobre cada host (sistema operacional, endereços IP, memória disponível). Use-os em playbooks com {{ ansible_facts['os_family'] }}.

4. Playbooks práticos: provisionamento de servidor web

Playbook completo para instalar Nginx, configurar virtual host e deploy de aplicação Node.js:

# playbooks/setup-web.yml
---
- name: Provisionamento do servidor web
  hosts: webservers
  become: yes
  vars:
    app_port: 3000
    app_user: nodeapp

  tasks:
    - name: Atualizar cache de pacotes
      apt:
        update_cache: yes
        cache_valid_time: 3600
      when: ansible_facts['os_family'] == "Debian"

    - name: Instalar Nginx
      apt:
        name: nginx
        state: present

    - name: Criar diretório da aplicação
      file:
        path: "/opt/{{ app_user }}"
        state: directory
        owner: "{{ app_user }}"
        group: "{{ app_user }}"
        mode: '0755'

    - name: Copiar arquivos da aplicação
      copy:
        src: ../app/
        dest: "/opt/{{ app_user }}/"
        owner: "{{ app_user }}"
        group: "{{ app_user }}"
        mode: '0644'

    - name: Configurar serviço systemd para Node.js
      template:
        src: templates/app.service.j2
        dest: /etc/systemd/system/app.service
      notify: restart app

    - name: Configurar virtual host Nginx
      template:
        src: templates/nginx.conf.j2
        dest: /etc/nginx/sites-available/meusite
      notify: restart nginx

    - name: Habilitar site no Nginx
      file:
        src: /etc/nginx/sites-available/meusite
        dest: /etc/nginx/sites-enabled/meusite
        state: link

    - name: Obter certificado SSL (Let's Encrypt)
      command: >
        certbot --nginx -d {{ app_domain }}
        --non-interactive --agree-tos
        --email {{ ssl_email }}
      when: ssl_enabled | default(false)

  handlers:
    - name: restart nginx
      systemd:
        name: nginx
        state: restarted

    - name: restart app
      systemd:
        name: app
        state: restarted

Template do serviço systemd (templates/app.service.j2):

[Unit]
Description=Minha Aplicação Node.js
After=network.target

[Service]
Type=simple
User={{ app_user }}
WorkingDirectory=/opt/{{ app_user }}
ExecStart=/usr/bin/node /opt/{{ app_user }}/server.js
Restart=always
Environment=NODE_ENV=production
Environment=PORT={{ app_port }}

[Install]
WantedBy=multi-user.target

5. Gerenciamento de configuração com roles e templates

Roles reutilizáveis organizam playbooks complexos. Estrutura de uma role nginx:

roles/nginx/
├── tasks/
│   └── main.yml
├── templates/
│   └── nginx.conf.j2
├── handlers/
│   └── main.yml
├── vars/
│   └── main.yml
└── defaults/
    └── main.yml

Exemplo de tasks/main.yml da role nginx:

---
- name: Instalar Nginx
  package:
    name: nginx
    state: present

- name: Configurar Nginx
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
  notify: restart nginx

- name: Remover configuração default
  file:
    path: /etc/nginx/sites-enabled/default
    state: absent
  notify: restart nginx

Templates Jinja2 permitem configurações dinâmicas. Exemplo (templates/nginx.conf.j2):

server {
    listen {{ nginx_port }};
    server_name {{ app_domain }};

    location / {
        proxy_pass http://127.0.0.1:{{ app_port }};
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    location /static/ {
        alias /opt/{{ app_user }}/static/;
        expires 30d;
    }
}

Handlers garantem que serviços sejam reiniciados apenas quando a configuração mudar, economizando tempo e evitando downtime desnecessário.

6. Segurança e boas práticas no provisionamento

O ansible-vault protege dados sensíveis:

# Criptografar arquivo de variáveis
ansible-vault encrypt group_vars/production/vault.yml

# Executar playbook com senha
ansible-playbook playbooks/setup-web.yml --ask-vault-pass

Exemplo de vault.yml:

vault_db_password: "senha_super_secreta"
vault_api_key: "chave_da_api"

Uso de become para escalonamento de privilégios:

- name: Instalar pacote
  become: yes
  become_user: root
  apt:
    name: htop
    state: present

Idempotência é o princípio fundamental: um playbook deve produzir o mesmo resultado ao ser executado múltiplas vezes. Use módulos Ansible (como apt, copy, template) em vez de comandos shell, e sempre verifique estados com state: present ou state: absent.

7. Integração com CI/CD e monitoramento básico

Pipeline GitHub Actions para executar playbooks:

# .github/workflows/deploy.yml
name: Deploy via Ansible

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Instalar Ansible
        run: pip install ansible
      - name: Executar playbook
        run: ansible-playbook playbooks/setup-web.yml -i inventory/production
        env:
          ANSIBLE_VAULT_PASSWORD: ${{ secrets.ANSIBLE_VAULT_PASSWORD }}

Notificações de falha podem ser enviadas via webhook Slack:

- name: Notificar falha
  slack:
    token: "{{ vault_slack_token }}"
    msg: "Falha no deploy do servidor {{ inventory_hostname }}"
    channel: "#deploys"
    username: "Ansible Bot"
  when: ansible_failed_result is defined

Para monitoramento básico, ansible-cmdb gera uma página HTML com informações de todos os servidores:

# Instalar
pip install ansible-cmdb

# Gerar inventário estendido
ansible all -m setup --tree out/
ansible-cmdb out/ > inventory.html

Referências