Playbooks e roles no Ansible

1. Introdução ao Ansible no Ecossistema DevOps

O Ansible é uma ferramenta de automação de TI que segue os princípios de Infraestrutura como Código (IaC). Diferentemente de soluções como Terraform, que focam em provisionamento declarativo de recursos, ou Shell Scripts, que são procedurais e frágeis, o Ansible oferece uma abordagem declarativa e idempotente, ideal para gerenciar configurações em ambientes com Docker e Kubernetes.

Sua arquitetura agentless é um diferencial significativo: utiliza SSH (ou WinRM para Windows) e Python como runtime, dispensando a instalação de agentes nos nós gerenciados. Em cenários conteinerizados, isso significa que você pode gerenciar hosts Docker ou clusters Kubernetes diretamente, sem necessidade de agentes dentro dos containers. Os módulos de conexão (connection: local, docker, kubectl) permitem interagir com daemons Docker e APIs Kubernetes de forma nativa.

Comparativamente, enquanto Terraform é excelente para provisionar infraestrutura (criar clusters, redes, volumes), o Ansible brilha na configuração e orquestração pós-provisionamento. Shell Scripts, por sua vez, carecem de idempotência e facilidade de manutenção que os playbooks Ansible oferecem.

2. Estrutura e Sintaxe de Playbooks Ansible

Playbooks são arquivos YAML que definem a automação. Seus componentes essenciais incluem:

  • hosts: alvo da execução (grupos de inventário ou localhost)
  • tasks: sequência de ações usando módulos
  • modules: unidades funcionais (ex: docker_container, k8s)
  • variables: dados configuráveis
  • handlers: tarefas especiais acionadas por notificações

Exemplo prático: Playbook para provisionar containers Docker

---
- name: Provisionar container Nginx
  hosts: docker_hosts
  vars:
    container_name: web_app
    image_name: nginx:alpine
    host_port: 8080

  tasks:
    - name: Garantir que o container esteja rodando
      docker_container:
        name: "{{ container_name }}"
        image: "{{ image_name }}"
        state: started
        ports:
          - "{{ host_port }}:80"
        restart_policy: always

Para Kubernetes, módulos específicos como k8s, helm e kubectl permitem gerenciar recursos diretamente:

- name: Criar Deployment no Kubernetes
  hosts: localhost
  tasks:
    - name: Aplicar Deployment
      k8s:
        state: present
        definition:
          apiVersion: apps/v1
          kind: Deployment
          metadata:
            name: app-deployment
            namespace: production
          spec:
            replicas: 3
            selector:
              matchLabels:
                app: my-app
            template:
              metadata:
                labels:
                  app: my-app
              spec:
                containers:
                  - name: app
                    image: myregistry/app:latest
                    ports:
                      - containerPort: 3000

3. Gerenciamento de Variáveis e Factos

O Ansible oferece múltiplos escopos de variáveis, permitindo controle granular:

  • host_vars/group_vars: variáveis específicas por host ou grupo
  • extra_vars: passadas via linha de comando (-e)
  • variáveis de role: definidas dentro de defaults/main.yml (baixa prioridade) ou vars/main.yml (alta prioridade)

Os factos (facts) são informações coletadas automaticamente sobre os sistemas gerenciados. Em ambientes Docker/K8s, podemos usar módulos como docker_host_info ou k8s_cluster_info para obter dados dinâmicos:

- name: Coletar informações do cluster Kubernetes
  k8s_cluster_info:
    kubeconfig: /etc/kubernetes/admin.conf
  register: cluster_info

- name: Exibir versão do cluster
  debug:
    msg: "Versão do Kubernetes: {{ cluster_info.version }}"

O uso de set_fact e register permite capturar saídas de comandos para uso posterior:

- name: Obter lista de pods
  shell: kubectl get pods -o json
  register: pods_json

- name: Extrair nomes dos pods
  set_fact:
    pod_names: "{{ (pods_json.stdout | from_json).items | map(attribute='metadata.name') | list }}"

4. Criação e Estrutura de Roles Ansible

Roles são a forma mais organizada de estruturar playbooks reutilizáveis. A anatomia de uma role segue esta estrutura de diretórios:

roles/
  minha_role/
    tasks/
      main.yml        # Tarefas principais
    handlers/
      main.yml        # Handlers
    templates/        # Templates Jinja2
    files/            # Arquivos estáticos
    vars/
      main.yml        # Variáveis de alta prioridade
    defaults/
      main.yml        # Variáveis de baixa prioridade (sobrescritas)
    meta/
      main.yml        # Dependências e metadados

Boas práticas de organização incluem modularizar roles por funcionalidade (ex: docker_setup, k8s_deploy, monitoring), especialmente em ambientes multi-container.

Exemplo: Role para deploy de aplicação com Docker Compose

# roles/docker_compose_app/tasks/main.yml
---
- name: Copiar docker-compose.yml
  template:
    src: docker-compose.yml.j2
    dest: /opt/app/docker-compose.yml

- name: Iniciar serviços
  docker_compose:
    project_src: /opt/app
    state: present

Para Kubernetes, a role pode criar Deployments, Services e ConfigMaps:

# roles/k8s_app/tasks/main.yml
---
- name: Criar ConfigMap
  k8s:
    state: present
    definition: "{{ lookup('template', 'configmap.yml.j2') }}"

- name: Aplicar Deployment
  k8s:
    state: present
    definition: "{{ lookup('template', 'deployment.yml.j2') }}"

5. Templates e Configurações Dinâmicas com Jinja2

O Jinja2 é o motor de templates do Ansible, essencial para gerar configurações dinâmicas. Exemplos práticos:

Template para Dockerfile:

# templates/Dockerfile.j2
FROM {{ base_image }}:{{ base_tag }}
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE {{ app_port }}
CMD ["python", "app.py"]

Template para manifesto Kubernetes com lógica condicional:

# templates/deployment.yml.j2
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ app_name }}-deployment
  namespace: {{ namespace | default('default') }}
spec:
  replicas: {{ replicas | default(2) }}
  selector:
    matchLabels:
      app: {{ app_name }}
  template:
    metadata:
      labels:
        app: {{ app_name }}
    spec:
      containers:
        - name: {{ app_name }}
          image: {{ image_registry }}/{{ image_name }}:{{ image_tag }}
          ports:
            - containerPort: {{ container_port }}
{% if healthcheck_enabled %}
          livenessProbe:
            httpGet:
              path: /health
              port: {{ container_port }}
            initialDelaySeconds: 30
{% endif %}

Para ambientes multi-serviço, loops em templates são poderosos:

# templates/docker-compose.yml.j2
version: '3'
services:
{% for service in services %}
  {{ service.name }}:
    image: {{ service.image }}
    ports:
      - "{{ service.host_port }}:{{ service.container_port }}"
{% endfor %}

6. Handlers, Tags e Estratégias de Execução

Handlers são tarefas especiais executadas apenas quando notificadas. Úteis para reiniciar containers após mudanças de configuração:

tasks:
  - name: Atualizar configuração do Nginx
    template:
      src: nginx.conf.j2
      dest: /etc/nginx/nginx.conf
    notify: restart_nginx

handlers:
  - name: restart_nginx
    docker_container:
      name: nginx_proxy
      image: nginx:alpine
      state: started
      restart: yes

Tags permitem execução seletiva de tarefas:

- name: Atualizar imagens Docker
  docker_image:
    name: "{{ item }}"
    source: pull
  loop:
    - nginx:latest
    - redis:alpine
  tags: [update_images, never]

- name: Executar apenas atualização de imagens
  # ansible-playbook playbook.yml --tags update_images

Estratégias de execução controlam como as tarefas são aplicadas em múltiplos nós:

  • linear (padrão): todas as tarefas em todos os hosts antes de avançar
  • free: cada host executa independentemente
  • serial: executa em lotes (útil para rolling updates em K8s)
- name: Rolling update em cluster Kubernetes
  hosts: k8s_nodes
  serial: 2
  tasks:
    - name: Atualizar kubelet
      ...

7. Integração com Docker e Kubernetes na Prática

Playbook para build, tag e push de imagens Docker:

---
- name: Pipeline de build Docker
  hosts: localhost
  tasks:
    - name: Build da imagem
      docker_image:
        name: myapp
        tag: "{{ version }}"
        build:
          path: ./app
          dockerfile: Dockerfile.prod
        source: build
        state: present

    - name: Login no registry
      docker_login:
        registry_url: "{{ docker_registry }}"
        username: "{{ docker_user }}"
        password: "{{ docker_pass }}"

    - name: Push da imagem
      docker_image:
        name: myapp
        repository: "{{ docker_registry }}/myapp:{{ version }}"
        push: yes
        source: local

Role para deploy completo no Kubernetes:

# roles/k8s_deploy/tasks/main.yml
---
- name: Criar Namespace
  k8s:
    name: "{{ namespace }}"
    api_version: v1
    kind: Namespace
    state: present

- name: Aplicar ConfigMap
  k8s:
    state: present
    definition: "{{ lookup('template', 'configmap.yml.j2') }}"

- name: Criar Deployment
  k8s:
    state: present
    definition: "{{ lookup('template', 'deployment.yml.j2') }}"

- name: Expor Service
  k8s:
    state: present
    definition: "{{ lookup('template', 'service.yml.j2') }}"

Para gerenciar Helm charts:

- name: Instalar NGINX Ingress via Helm
  helm:
    name: nginx-ingress
    chart_ref: ingress-nginx/ingress-nginx
    release_namespace: ingress-nginx
    create_namespace: yes
    values:
      controller.service.type: LoadBalancer

8. Boas Práticas, Testes e Pipeline CI/CD

Versionamento: Armazene playbooks e roles em Git, seguindo estrutura padrão do Ansible Galaxy.

Testes com Molecule: Ferramenta para validar roles em containers Docker:

# molecule/default/molecule.yml
dependency:
  name: galaxy
driver:
  name: docker
platforms:
  - name: instance
    image: geerlingguy/docker-ubuntu2204-ansible:latest
provisioner:
  name: ansible
verifier:
  name: ansible

Pipeline CI/CD com GitLab CI:

# .gitlab-ci.yml
stages:
  - test
  - deploy

test_role:
  stage: test
  script:
    - pip install molecule docker
    - molecule test

deploy_k8s:
  stage: deploy
  script:
    - ansible-playbook -i inventory/production deploy.yml
  only:
    - main

Boas práticas adicionais incluem: usar ansible-lint para validação de sintaxe, criptografar variáveis sensíveis com ansible-vault, e manter inventários separados por ambiente (dev, staging, production).


Referências