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 removerignore_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.