Solucionando Conflitos de Precedência de Variáveis em Configurações Ansible

Diagnostique conflitos de precedência de variáveis Ansible com verificações práticas para inventário, funções, fatos, includes e variáveis extras.

Solucionando Conflitos de Precedência de Variáveis em Configurações Ansible

Problemas de precedência de variáveis geralmente se manifestam como uma pergunta simples: "Por que o Ansible usou aquele valor?" Uma porta é 8080 quando você esperava 80. Uma função implanta a versão 1.6 mesmo que o playbook diga 1.5. Um job de CI passa -e environment=prod, e de repente metade da sua estrutura cuidadosa de inventário deixa de importar.

A correção raramente é memorizar cada linha da tabela de precedência do Ansible. A correção é restringir as possíveis fontes, inspecionar o valor no host afetado e mover a variável para a camada correta. Este guia foca nesse fluxo de trabalho.

Entendendo a Precedência de Variáveis no Ansible

O Ansible avalia as variáveis em uma ordem específica, conhecida como ordem de precedência de variáveis. O valor que aparece mais tarde nesta lista substitui qualquer valor definido anteriormente para a mesma variável. É essencial lembrar dessa ordem ao solucionar problemas.

Aqui está uma maneira simplificada de pensar sobre fontes comuns, da mais fácil de substituir para a mais difícil:

  1. Defaults de Função (Role Defaults): Variáveis definidas no arquivo defaults/main.yml de uma função. Estas têm a precedência mais baixa e são destinadas a valores padrão que podem ser facilmente substituídos.
  2. Variáveis de Inventário (todos ou grupo): Variáveis definidas em arquivos de inventário usando a palavra-chave vars: para grupos específicos ou todos os hosts.
  3. Variáveis de Inventário (host): Variáveis definidas diretamente para um host específico dentro do arquivo de inventário.
  4. Variáveis do Playbook: Variáveis definidas usando a palavra-chave vars: diretamente dentro de um playbook.
  5. Variáveis de Função (Role Variables): Variáveis definidas no arquivo vars/main.yml de uma função. Elas têm precedência maior que os defaults.
  6. Include Vars e Arquivos Vars: Variáveis carregadas explicitamente por um play ou tarefa.
  7. Variáveis de Tarefa, Variáveis de Bloco, Resultados Registrados e Fatos: Estas podem afetar tarefas posteriores e podem ser fáceis de perder porque vivem dentro do fluxo de execução.
  8. Variáveis Set Fact: Variáveis definidas usando o módulo set_fact têm alta precedência para a execução atual.
  9. Variáveis Extras (Extra Vars): Variáveis passadas na linha de comando usando -e ou --extra-vars são intencionalmente muito fortes e substituem quase tudo.

Este é um modelo de trabalho, não a tabela completa. A documentação oficial do Ansible tem a lista exaustiva, incluindo parâmetros de função, parâmetros de include, comportamento de plugins de inventário e outros casos extremos. Para depuração em produção, compare seu caso com as regras oficiais de precedência de variáveis.

Cenários Comuns de Conflito de Variáveis e Soluções

Vamos examinar alguns cenários comuns onde conflitos de precedência de variáveis podem ocorrer e como diagnosticá-los e resolvê-los.

Cenário 1: Variáveis de Grupo vs. Variáveis de Host

Frequentemente, você pode definir uma configuração geral para um grupo de servidores (ex.: app_servers) e depois uma configuração específica para um servidor dentro desse grupo (ex.: webserver01).

Exemplo de Inventário (inventory.ini):

[app_servers]
webserver01.example.com
webserver02.example.com

[databases]
dbserver01.example.com

[app_servers:vars]
http_port = 8080

[webserver01.example.com:vars]
http_port = 80

Resultado Esperado: Para webserver01.example.com, http_port deve ser 80. Para webserver02.example.com, que está em app_servers mas não está definido especificamente, http_port deve ser 8080.

Problema: Se http_port não estiver se comportando como esperado, o provável problema é um mal-entendido sobre qual definição o Ansible está captando.

Passos de Diagnóstico:

  • Use o módulo debug: Adicione uma tarefa debug em seu playbook para mostrar explicitamente o valor da variável.

    - name: Exibir http_port
      debug:
        msg: "O http_port para este host é {{ http_port }}"
    
  • Use ansible-inventory --host <hostname>: Este utilitário de linha de comando mostra todas as variáveis associadas a um host específico, incluindo sua precedência.

    ansible-inventory --host webserver01.example.com --list --yaml
    

    Procure pela variável http_port e observe onde ela está definida. A saída geralmente indica a origem da variável.

Solução: Neste caso, as variáveis de host ([webserver01.example.com:vars]) têm precedência maior que as variáveis de grupo ([app_servers:vars]), então http_port = 80 substituirá corretamente http_port = 8080 para webserver01.example.com.

Cenário 2: Variáveis do Playbook vs. Variáveis de Função

Você pode definir uma configuração na seção vars do seu playbook e também em uma função que o playbook inclui.

Exemplo de Playbook (deploy_app.yml):

--- 
- name: Implantar Aplicação Web
  hosts: webservers
  vars:
    app_version: "1.5"
    db_host: "prod.db.local"
  roles:
    - common
    - webapp

Exemplo de Função (webapp/vars/main.yml):

app_version: "1.6"
db_host: "shared.db.local"

Resultado Esperado: Quando este playbook for executado, quais serão os valores de app_version e db_host?

Passos de Diagnóstico:

  • Módulo debug: Como antes, use o módulo debug para inspecionar os valores.
    - name: Mostrar app_version e db_host
      debug:
        msg: "Versão da App: {{ app_version }}, Host do DB: {{ db_host }}"
    
  • Examine a estrutura da função: Certifique-se de que vars/main.yml é de fato parte da função sendo incluída e que não há outros arquivos vars/main.yml dentro das dependências da função que possam estar tomando precedência.

Solução: De acordo com as regras de precedência, as Variáveis de Função (webapp/vars/main.yml) têm precedência maior que as Variáveis do Playbook (vars: em deploy_app.yml). Portanto:

  • app_version será 1.6.
  • db_host será shared.db.local.

Se você pretendia que as variáveis do playbook tivessem precedência, precisaria mover essas definições para um nível de precedência mais alto, como extra_vars ou usar vars_files com uma precedência mais alta.

Cenário 3: Substituição com extra-vars

As variáveis de linha de comando (extra-vars) têm uma precedência muito alta e podem substituir quase tudo.

Exemplo de Inventário (inventory.ini):

[webservers]
webserver01.example.com

[webservers:vars]
http_port = 8080

Exemplo de Playbook (configure_web.yml):

--- 
- name: Configurar Servidor Web
  hosts: webservers
  tasks:
    - name: Exibir http_port
      debug:
        msg: "O http_port é {{ http_port }}"

Executando o playbook:

  • Sem extra-vars:

    ansible-playbook -i inventory.ini configure_web.yml
    

    Saída: O http_port será 8080 (das variáveis de grupo).

  • Com extra-vars:

    ansible-playbook -i inventory.ini configure_web.yml -e "http_port=80"
    

    Saída: O http_port será 80.

Passos de Diagnóstico: Sempre verifique se extra-vars estão sendo usadas, especialmente em execuções complexas ou orquestradas, pois são uma causa comum de valores inesperados de variáveis.

Solução: Esteja atento ao extra-vars. Se você precisa substituir valores programaticamente ou para execuções específicas, extra-vars é o caminho. Se você não quer que eles substituam, certifique-se de que não estão sendo passados ou ajuste seu playbook/inventário para priorizar outras fontes de variáveis, se necessário (embora isso seja geralmente desencorajado, pois enfraquece a previsibilidade).

Técnicas Avançadas de Solução de Problemas

Ao lidar com problemas complexos de precedência de variáveis, as seguintes técnicas podem ser inestimáveis:

  • ansible-inventory --host: Use para variáveis derivadas do inventário antes da execução do play.

    ansible-inventory -i inventory.ini --host webserver01.example.com --yaml
    

    Isso não mostrará valores criados posteriormente por tarefas, mas é a maneira mais rápida de verificar o comportamento do inventário, group_vars e host_vars.

  • Tarefas debug direcionadas: Use debug dentro do play quando um valor pode vir de uma função, include, resultado registrado ou set_fact.

    - name: Mostrar configurações de aplicação resolvidas
      ansible.builtin.debug:
        msg:
          app_version: "{{ app_version | default('indefinido') }}"
          db_host: "{{ db_host | default('indefinido') }}"
    
  • --skip-tags e --limit: Ao depurar, tente isolar o problema. Execute o playbook com --limit para mirar apenas no host problemático. Use --skip-tags para desabilitar tarefas ou funções que possam estar definindo variáveis inadvertidamente.

  • Ordem de vars_files: Se você está usando vars_files em seu playbook, a ordem importa. O Ansible os carrega na ordem especificada, e arquivos posteriores podem substituir variáveis definidas em arquivos anteriores.

    - name: Implantar App
      hosts: webservers
      vars_files:
        - vars/common_settings.yml
        - vars/environment_specific.yml # Isso substituirá common_settings.yml se as variáveis se sobrepuserem
    

Melhores Práticas para Gerenciar Variáveis

Para minimizar conflitos de precedência de variáveis:

  • Seja Explícito: Evite definir a mesma variável em muitos lugares. Se uma variável é verdadeiramente global, considere group_vars/all.yml ou group_vars/all/.
  • Use Nomes Descritivos: Use nomes claros e únicos para suas variáveis para reduzir a chance de colisões acidentais de nomes.
  • Documente Suas Variáveis: Mantenha o controle de onde variáveis importantes são definidas e qual é seu escopo pretendido.
  • Aproveite os Defaults de Função: Use defaults de função para configurações não críticas que se destinam a ser substituídas. Isso torna as funções mais flexíveis.
  • Entenda a Ordem: Mantenha uma nota mental (ou física!) da ordem de precedência. Quando uma variável não é o que você espera, consulte a ordem.
  • Teste Incrementalmente: Ao introduzir novas definições de variáveis ou modificar as existentes, teste seus playbooks em pequena escala primeiro.

Uma Rotina de Depuração Que Realmente Funciona

Quando uma variável está errada, não comece movendo-a. Primeiro, prove de onde vem o valor atual.

Eu geralmente começo com a menor execução possível:

ansible-playbook -i inventory.ini deploy_app.yml --limit webserver01.example.com --check -vv

--limit remove o ruído de outros hosts. --check é útil quando o play suporta, embora nem todo módulo possa prever mudanças completamente. -vv dá mais contexto sem transformar a saída em uma parede de detalhes internos. Se o valor ainda estiver confuso, adicione uma tarefa de depuração temporária imediatamente antes da tarefa que se comporta incorretamente.

Coloque o debug perto da tarefa com falha. Um valor pode mudar durante um play, especialmente se a função usa include_vars, set_fact ou register. Uma tarefa de depuração no topo do play pode mostrar o valor correto, enquanto uma tarefa de depuração dentro da função mostra o valor depois de ter sido sobrescrito.

Por exemplo:

- name: Mostrar app_version antes da renderização do template
  ansible.builtin.debug:
    var: app_version

- name: Renderizar configuração do app
  ansible.builtin.template:
    src: app.conf.j2
    dest: /etc/app/app.conf

Se a saída do debug estiver correta, mas a saída do template estiver errada, o problema pode estar dentro do template, não na precedência. Talvez o template referencie app.version em vez de app_version, ou um filtro padrão esconda um valor indefinido:

version={{ app_version | default('latest') }}

Essa linha pode fazer uma variável ausente parecer um valor deliberado. Defaults são úteis, mas podem esconder erros quando usados para configurações obrigatórias.

Em seguida, inspecione o inventário:

ansible-inventory -i inventory.ini --host webserver01.example.com --yaml
ansible-inventory -i inventory.ini --graph

A visão do host mostra as variáveis de inventário mescladas que o Ansible vê antes da execução da tarefa. A visão do gráfico mostra a associação ao grupo. A associação ao grupo é importante porque um host pode herdar variáveis de múltiplos grupos. Se dois grupos irmãos definem a mesma variável, o resultado depende do carregamento do inventário e das regras de prioridade do grupo. Nessa situação, confiar no acaso é um problema de manutenção.

Se você realmente precisa que um grupo ganhe sobre outro, use ansible_group_priority na fonte do inventário que define os grupos. Melhor ainda, evite a colisão e escolha um nome de variável que reflita a intenção:

nginx_listen_port: 80
app_healthcheck_port: 8080

Isso é mais claro do que um http_port genérico reutilizado em funções não relacionadas.

Desconfie de extra-vars. Em sistemas CI/CD, valores são frequentemente injetados por templates de pipeline ou scripts wrapper. Pesquise na definição do job por -e, --extra-vars e arquivos passados com a sintaxe @:

ansible-playbook site.yml -e @release-vars.yml -e app_version=1.6

Extra vars são feitos para serem forçados. Se um pipeline passa app_version=1.6, não espere que o inventário ou defaults de função o substituam. A correção mais limpa é parar de passar o valor quando ele não deve ser forçado, ou renomeá-lo para algo intencionalmente específico da execução, como release_app_version.

Funções merecem cuidado especial. defaults/main.yml é para valores que o chamador deve substituir. vars/main.yml é para valores que a função principalmente possui. Se você colocar configurações comuns em vars/main.yml, os usuários da função terão dificuldade em alterá-las a partir do inventário ou das variáveis do play. Em muitas funções reais, mover um valor de vars/main.yml para defaults/main.yml é a correção certa porque restaura o contrato da função.

Também fique atento a loops include_vars:

- name: Carregar configurações de ambiente
  ansible.builtin.include_vars:
    file: "vars/{{ env }}.yml"

Este é um padrão útil, mas significa que o valor depende de env, do conteúdo do arquivo incluído e do ponto no play onde o include é executado. Se env vem de extra vars, o include pode carregar silenciosamente um arquivo diferente do esperado.

O hábito mais confiável a longo prazo é dar a cada variável um lar:

  • Defaults de função para comportamento ajustável da função.
  • Inventário e group_vars para diferenças de ambiente e host.
  • Variáveis do play para valores locais a um play.
  • Variáveis registradas para saída de comando que pertence à execução atual.
  • Extra vars para substituições deliberadas pontuais, especialmente entradas de release.

Quando uma variável não se encaixa em um desses lares, pause antes de adicioná-la. A maioria dos bugs de precedência começa como uma conveniência: "Vou definir aqui por enquanto." Três meses depois, ninguém lembra qual "aqui" vence.