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:
- Atualizar todos os pacotes do sistema operacional.
- Baixar a versão mais recente de um pacote
.debcustomizado a partir de uma URL HTTP. - Instalar o pacote no servidor de destino via
dpkg. - 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?#
- Idempotência Real com
apt: deb={{ tmp_path }}: O móduloaptinspeciona 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 marcaok: [host]e não executa a instalação novamente. - 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. - Transporte Seguro com
get_url: Gerencia timeouts, permissões de arquivo (mode: 0644) e permite validação de integridade por hash SHA-256. - Acionamento via Handlers: O serviço
custom-agentsó é 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. - Limpeza Automática: Uma task do módulo
fileremove 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:
- Concorrência e Travas de Lock no APT: Se o
unattended-upgradesou outro processo estiver usando o gerenciador de pacotes, o arquivo/var/lib/dpkg/lock-frontendestará bloqueado. Para auditar quem está segurando a trava:
lsof /var/lib/dpkg/lock-frontend
- Rastreamento de Syscalls com Strace: Se a execução travar no provisionamento e você precisar entender o que o processo do Ansible/Python está aguardando do kernel:
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:
Este post está licenciado sob CC BY-NC.



Comentários
Participe da discussão abaixo.
0 comentários