Refatorando playbooks Ansible: da execução imperativa ao padrão declarativo e idempotente
Voltar para blog

Refatorando playbooks Ansible: da execução imperativa ao padrão declarativo e idempotente

26/09/2026 · 4 min · DevOps

Quem gerencia servidores Linux e automações no dia a dia sabe: automatizar tarefas repetitivas é a regra do jogo. No entanto, na pressa de colocar uma rotina para rodar rapidamente, é muito comum cair na armadilha de escrever scripts de shell disfarçados de YAML.

Ao revisar um playbook antigo utilizado para atualizar um agente de monitoramento e aplicar patches nos servidores, identifiquei uma série de antipadrões que tornavam a execução frágil, não idempotente e propensa a falhas silenciosas.

Abaixo, desconstruo os problemas técnicos da abordagem imperativa com command e compartilho o passo a passo da refatoração para um modelo puramente declarativo, resiliente e seguro.


1. O ponto de partida: o playbook legado#

O objetivo da automação era simples:

  1. Atualizar todos os pacotes do sistema operacional.
  2. Baixar a versão mais recente de um pacote .deb customizado a partir de uma URL HTTP.
  3. Instalar o pacote no servidor de destino via dpkg.
  4. Exibir a saída da execução no terminal.

O playbook original estava estruturado da seguinte forma:

# ------------------------------------------------------------------------------
# Script legado: update_srv.yml
# ------------------------------------------------------------------------------

- name: Atualiza pacote dos servidores
  hosts: app_hosts
  become: yes

  tasks:
    - name: Atualizando o pacote
      package:
        name: "*"
        state: latest

    - name: Baixando e atualizando o pacote customizado
      command: wget "https://repo.seusite.com.br/repo/custom-agent.deb?nocache=$(date +%s)" -O /tmp/custom-agent.deb && sudo dpkg -i /tmp/custom-agent.deb
      register: os_release_output

    - name: Exibir o resultado do os-release no console
      debug:
        msg: "{{ os_release_output.stdout_lines }}"

Funcionava? Sim. Mas funcionava do jeito errado.


2. A anatomia dos problemas#

Ao analisar a execução acima, quatro falhas estruturais ficam evidentes:

A. Quebra total de idempotência (dpkg -i vs libapt)#

O Ansible foi concebido para ser declarativo. Ao utilizar o módulo command disparando um dpkg -i, a tarefa sempre retorna o status changed: true em todas as execuções, mesmo se o pacote já estiver instalado na mesmíssima versão. O dpkg bruto não compara metadados com o estado do sistema antes de descompactar arquivos no disco, desperdiçando I/O e ciclos de CPU.

B. Risco na resolução de dependências#

O dpkg é uma ferramenta de baixo nível: se o pacote passar a exigir uma nova biblioteca do sistema (como uma libssl atualizada), ele aborta com erro e deixa o sistema em estado quebrado (unconfigured/broken). O módulo apt do Ansible, por sua vez, conversa diretamente com a libapt-pkg e sabe buscar e instalar dependências ausentes a partir dos repositórios configurados.

C. Falta de gestão de ciclo de vida (processos órfãos)#

Substituir o binário .deb no disco não reinicia automaticamente o processo em execução na memória RAM. Sem recarregar o daemon no systemd, o serviço continua rodando com instruções e descritores de arquivo da versão antiga.

D. Interpolação de shell e redundâncias#

Utilizar interpolações como $(date +%s) dentro de uma task delega a execução ao shell do host de destino. Se o relógio do servidor estiver dessincronizado ou o shell padrão for restritivo, o comando quebra. Além disso, usar sudo dentro de uma task que já possui become: yes é redundante e polui os logs de auditoria.


3. A solução: refatoração técnica declarativa#

Para eliminar esses gargalos, o playbook foi reescrito utilizando os módulos nativos do ecossistema Ansible (apt, get_url, file e handlers):

---
# ------------------------------------------------------------------------------
# Playbook Refatorado: update_srv.yml (Declarativo e Idempotente)
# ------------------------------------------------------------------------------

- name: Upgrade de Infraestrutura e Agente Customizado
  hosts: app_hosts
  become: yes
  gather_facts: yes
  vars:
    agent_repo_url: "https://repo.seusite.com.br/repo/custom-agent.deb"
    tmp_path: "/tmp/custom-agent.deb"

  tasks:
    - name: "[System] Atualizar cache do APT e pacotes instalados"
      apt:
        upgrade: dist
        update_cache: yes
        cache_valid_time: 3600
      register: apt_upgrade_result

    - name: "[Agent] Download do binario com controle de tempo e TLS"
      get_url:
        url: "{{ agent_repo_url }}?nocache={{ ansible_date_time.epoch }}"
        dest: "{{ tmp_path }}"
        mode: '0644'
        force: yes
        timeout: 30
      register: download_status

    - name: "[Agent] Instalacao via Modulo APT Nativo (Gestao de Dependencias)"
      apt:
        deb: "{{ tmp_path }}"
        state: present
      register: agent_install_result
      notify: "Restart Agent Service"

    - name: "[Cleanup] Remocao de artefatos temporarios"
      file:
        path: "{{ tmp_path }}"
        state: absent

    - name: "[Debug] Status da Atualizacao"
      debug:
        msg: 
          - "Status do Upgrade Geral: {{ apt_upgrade_result.changed }}"
          - "Agent Status: {{ agent_install_result.stdout_lines | default('Already up-to-date') }}"

  handlers:
    - name: Restart Agent Service
      service:
        name: custom-agent
        state: restarted
        enabled: yes
      ignore_errors: yes

4. O que mudou por baixo dos panos?#

  1. Idempotência Real com apt: deb={{ tmp_path }}: O módulo apt inspeciona o cabeçalho do pacote temporário e compara com a base em /var/lib/dpkg/status. Se a versão instalada for a mesma, o Ansible marca ok: [host] e não executa a instalação novamente.
  2. Ansible Facts Nativos: A chamada de shell foi substituída por {{ ansible_date_time.epoch }}, o que garante precisão nos carimbos de tempo coletados pelo próprio Ansible.
  3. Transporte Seguro com get_url: Gerencia timeouts, permissões de arquivo (mode: 0644) e permite validação de integridade por hash SHA-256.
  4. Acionamento via Handlers: O serviço custom-agent só é reiniciado se a task de instalação efetivamente alterar o sistema (changed: true). Sem alterações, o serviço não sofre reinicializações desnecessárias.
  5. Limpeza Automática: Uma task do módulo file remove o pacote temporário em /tmp, evitando acúmulo de arquivos residuais.

Dicas de troubleshooting em ambientes reais#

Ao executar playbooks em frotas maiores de servidores, dois problemas de baixo nível costumam aparecer:

lsof /var/lib/dpkg/lock-frontend
strace -f -e trace=process,openat ansible-playbook update_srv.yml -vvv

Boas práticas para automação idempotente#

Automatizar não é apenas encadear comandos bash dentro de arquivos YAML. Utilizar os módulos nativos do Ansible garante controle de estado, tratamento automático de erros, idempotência e previsibilidade operacional em qualquer escala de servidores.

Este artigo foi útil?

Deixe uma reação rápida para apoiar o conteúdo:

CC BY-NC

Este post está licenciado sob CC BY-NC.

Comentários

Participe da discussão abaixo.

0 comentários