Um Guia Prático para Depurar Módulos Shell e Command com Falha

Depure falhas do Ansible shell e command com exemplos de register, stdout, stderr, rc, failed_when e changed_when.

Um Guia Prático para Depurar Módulos Shell e Command com Falha

Os módulos command e shell do Ansible são úteis quando não existe um módulo específico para a tarefa, mas podem ser complicados de depurar. Uma tarefa com falha pode mostrar apenas um código de retorno, a menos que você capture a saída do comando manualmente.

Este guia mostra como depurar módulos shell e command com falha, verificando rc, stdout e stderr, e usando failed_when e changed_when para fazer o Ansible relatar o resultado real.


Command vs. Shell: Entendendo a Diferença

Antes de mergulhar na depuração, é vital entender a diferença fundamental entre os dois módulos, pois o ambiente de execução impacta os modos de falha.

ansible.builtin.command

Este módulo executa o comando diretamente, ignorando o ambiente shell padrão. Isso o torna mais seguro e previsível, pois evita recursos do shell como interpolação de variáveis, globbing, pipes (|) e redirecionamento (>).

Melhor Prática: Use command sempre que a tarefa for simples e não exigir recursos do shell.

ansible.builtin.shell

Este módulo executa o comando através do shell padrão do host remoto (/bin/sh ou equivalente). Isso é necessário para operações complexas, variáveis de ambiente ou ao usar sintaxe shell padrão (ex.: cd /tmp && ls -l).

Aviso: Como shell depende do ambiente, é mais propenso a falhas imprevisíveis relacionadas à configuração do PATH, variáveis de ambiente ocultas ou citações complexas.

A Anatomia de uma Falha de Comando no Ansible

Por padrão, o Ansible determina o sucesso ou falha de uma tarefa do módulo command ou shell com base no código de retorno (RC) do processo.

Código de Retorno (RC) Interpretação
rc = 0 Sucesso (A tarefa continua)
rc != 0 Falha (A tarefa para imediatamente, host marcado como falha)

No entanto, essa verificação simples muitas vezes não captura a nuance de scripts do mundo real. Um comando pode retornar um RC de 0 mas ainda produzir um resultado indesejado (uma falha lógica), ou um comando pode retornar um RC não zero esperado (ex.: grep retorna 1 se não encontrar correspondências).

Para lidar com essas nuances, devemos capturar a saída e controlar condicionalmente o estado de falha.

Passo 1: Capturando a Saída do Comando com register

O primeiro passo para uma depuração eficaz é capturar todos os fluxos de saída disponíveis em uma variável do Ansible usando a palavra-chave register. Isso permite inspecionar o código de retorno, a saída padrão e o erro padrão.

Para evitar que o playbook pare imediatamente em um código de retorno não zero durante os testes iniciais, muitas vezes é útil usar temporariamente ignore_errors: yes.

- name: Executar um comando potencialmente não confiável e capturar resultados
  ansible.builtin.shell: | 
    /usr/local/bin/check_config.sh 2>&1 || exit 1
  register: cmd_output
  ignore_errors: yes  # Permitir temporariamente RC != 0 para prosseguir

Uma vez registrada, a variável cmd_output conterá várias chaves úteis, principalmente:

  • cmd_output.rc: O código de retorno inteiro.
  • cmd_output.stdout: O fluxo de saída padrão.
  • cmd_output.stderr: O fluxo de erro padrão.
  • cmd_output.failed: Um booleano indicando se o Ansible considera a tarefa como falha no momento.

Passo 2: Inspecionando Dados Capturados com debug

Use o módulo debug imediatamente após a tarefa com falha para inspecionar o conteúdo da variável registrada. Isso ajuda a distinguir entre uma verdadeira falha técnica (ex.: comando não encontrado) e uma falha lógica (ex.: script executou mas relatou um erro interno).

- name: Exibir saída capturada completa para depuração
  ansible.builtin.debug:
    var: cmd_output
    # Use 'when' para mostrar apenas se a tarefa falhou, limpando a saída
  when: cmd_output.failed is defined and cmd_output.failed

- name: Destacar conteúdo do stderr
  ansible.builtin.debug:
    msg: "STDERR capturado: {{ cmd_output.stderr }}"
  when: cmd_output.stderr | length > 0

Ao inspecionar a saída completa, você pode identificar a mensagem de erro ou padrão específico que indica uma verdadeira falha.

Passo 3: Substituindo o Comportamento de Falha Padrão com failed_when

A condicional failed_when é a ferramenta mais poderosa para depurar e gerenciar resultados complexos de módulos shell. Ela permite definir lógica personalizada, usando expressões Jinja2, para determinar se uma tarefa deve ser marcada como falha, independentemente do código de retorno padrão.

Cenário A: Lidando com um Código de Retorno Não Zero Esperado

Alguns utilitários retornam um código não zero para um resultado esperado. Por exemplo, grep retorna 1 quando não encontra correspondência e maior que 1 para erros reais.

- name: Verificar se uma configuração existe, mas não falhar quando ausente
  ansible.builtin.command: grep -q '^feature_enabled=true' /etc/myapp.conf
  register: grep_result
  failed_when: grep_result.rc > 1
  changed_when: false

Cenário B: Falhando em Erros Lógicos (RC=0, mas Saída Ruim)

Se um script sempre retorna RC=0 mesmo quando ocorre um erro interno, mas imprime uma string de erro específica em stdout ou stderr, use failed_when para capturar essa string.

- name: Validar script de conectividade de banco de dados
  ansible.builtin.shell: /opt/scripts/db_connect_test.sh
  register: db_result
  # Verificar tanto stdout quanto stderr para frases de erro comuns
  failed_when: >
    ('Connection refused' in db_result.stderr) or
    ('Authentication failure' in db_result.stdout)

Cenário C: Combinando Verificações de RC e Saída

Para verificações robustas, combine as verificações de código de retorno e conteúdo usando operadores lógicos (and, or, parênteses).

- name: Verificar logs de implantação
  ansible.builtin.shell: tail -n 50 /var/log/deployment.log
  register: log_check
  # Falhar se o RC for diferente de zero OU se a saída bem-sucedida contiver a palavra 'FATAL'
  failed_when: log_check.rc != 0 or 'FATAL' in log_check.stdout

Dica: Ao usar failed_when, geralmente você deve remover ignore_errors: yes, a menos que queira explicitamente que a falha seja registrada, mas o play continue.

Melhores Práticas para Execução Confiável de Comandos

Para minimizar a necessidade de depuração complexa, siga estes padrões ao escrever tarefas que usam command ou shell:

1. Sempre Use Caminhos Absolutos

Não confie no $PATH do usuário remoto. Sempre especifique o caminho completo para o executável (ex.: /usr/bin/python, não apenas python). Isso evita falhas causadas por ambientes inconsistentes ou diferenças sutis no caminho de execução.

2. Utilize Condicionais em Vez de Lógica Shell

Em vez de usar lógica shell complexa como || ou && dentro do módulo shell, utilize as condicionais nativas do Ansible (when:, failed_when:, changed_when:) e a palavra-chave register. Isso mantém a lógica do playbook transparente e mais fácil de depurar.

3. Controle Explicitamente a Detecção de Mudanças (changed_when)

Por padrão, command e shell marcam uma tarefa como changed se o código de retorno for 0. Se o seu script for executado mas não fizer alterações no sistema (ex.: uma verificação de status simples), você deve definir manualmente quando a tarefa resulta em uma mudança usando changed_when.

- name: Verificar espaço em disco (não deve resultar em 'changed')
  ansible.builtin.command: df -h /data
  changed_when: false

4. Use Módulos de Estado Sempre que Possível

Se você se pegar usando shell para verificar a existência de arquivos, iniciar/parar serviços ou instalar pacotes, pare e procure um módulo Ansible dedicado (ex.: ansible.builtin.stat, ansible.builtin.service, ansible.builtin.package). Módulos dedicados lidam com idempotência e verificação de erros internamente, reduzindo significativamente o esforço de depuração.

Conclusão Final

Quando uma tarefa shell ou command falha, capture o resultado primeiro, inspecione rc, stdout e stderr, depois codifique a condição real de sucesso em failed_when. Uma vez que a tarefa esteja estável, adicione changed_when para que as verificações de status não mostrem falsas alterações em cada execução do playbook.