Overriding Ansible Playbook Variables When Defined in Host_vars: A Deep Dive into Precedence Rules


4 views

In Ansible, variable precedence follows a strict hierarchy where some sources override others. When dealing with connection parameters like ansible_connection, it's crucial to understand how playbook variables interact with host-specific definitions.

In your scenario, we have two competing definitions:

# Playbook definition
vars:
  ansible_connection: aws_ssm

# Host_vars/server1.yml
ansible_connection: local

According to Ansible's variable precedence rules, host_vars (at precedence level 13) actually override playbook variables defined in the vars section (level 8). This means your host_vars definition should naturally take precedence.

To confirm which value is being used, you can add this debug task to your playbook:

- hosts: it_servers
  vars:
    ansible_connection: aws_ssm
  tasks:
    - name: Display actual connection method
      debug:
        var: ansible_connection
  roles:
    - nginx
    - mysql

If the output shows local for server1, then the host_vars are working as expected. If not, there might be other factors at play.

Several scenarios could prevent host_vars from overriding playbook vars:

  • The host_vars file is in the wrong location (should be in host_vars/server1.yml relative to your playbook or inventory)
  • The filename doesn't match the hostname exactly
  • There's another variable source with higher precedence (like --extra-vars in your command)

If you need more control, consider these patterns:

1. Using Inventory Variables

[it_servers]
server1 ansible_connection=local
server2
server3

2. Group Variables with Overrides

# group_vars/it_servers.yml
ansible_connection: aws_ssm

# host_vars/server1.yml
ansible_connection: local

3. Conditional Logic in Playbook

- hosts: it_servers
  vars:
    ansible_connection: "{{ 'local' if inventory_hostname == 'server1' else 'aws_ssm' }}"
  roles:
    - nginx
    - mysql

When managing connection methods:

  • Document overrides clearly in your project README
  • Use consistent naming patterns for special-case hosts
  • Consider creating a separate inventory file for local development hosts
  • Test connection methods with ansible -m ping before running full playbooks

When working with Ansible, you might encounter situations where a variable is defined in multiple places, and you need to understand which definition takes precedence. In this case, we're dealing with a playbook that defines ansible_connection: aws_ssm for the it_servers group, but we want server1 to use ansible_connection: local through host_vars.

Ansible's variable precedence follows this order (higher overrides lower):

1. Extra vars (-e in CLI)
2. Task vars (only for the task)
3. Block vars (only for tasks in block)
4. Role and include vars
5. Play vars
6. Play vars_prompt
7. Play vars_files
8. Host facts
9. Host vars from inventory file
10. Host vars from host_vars/
11. Group vars from inventory file
12. Group vars from group_vars/
13. Playbook group_vars/
14. Playbook host_vars/
15. Inventory variables
16. Facts discovered about a host system
17. Role "default" vars

In our scenario, host_vars (priority 10) actually have higher precedence than play vars (priority 5), so your host_vars/server1.yml should naturally override the playbook definition. However, if this isn't happening, there might be other factors at play.

To debug, run this command to see the actual value being used:

ansible server1 -m debug -a "var=ansible_connection"

If host_vars isn't working as expected, consider these alternatives:

1. Using --extra-vars

Override at runtime with highest precedence:

ansible-playbook playbook.yml --extra-vars "ansible_connection=local" --limit server1

2. Using Inventory Variables

Add the connection parameter directly in your inventory:

[it_servers]
server1 ansible_connection=local
server2 ansible_host=192.168.1.2

3. Using Group Variables with Exceptions

Create a special group for servers needing local connection:

# inventory.yml
all:
  children:
    it_servers:
      hosts:
        server1:
          ansible_connection: local
        server2:
    local_servers:
      hosts:
        server1:

To understand why your host_vars aren't working:

# Check effective variables
ansible-inventory --list -i inventory.yml

# Verify host_vars are being loaded
ansible-config dump | grep DEFAULT_HOST_VARS_PATH

For connection parameters specifically:

  • Consider using inventory variables rather than playbook vars
  • Document connection methods clearly in your inventory
  • For complex setups, create separate groups for different connection types

Here's how we might structure this in a production environment:

# inventory.yml
all:
  children:
    aws_ssm_servers:
      vars:
        ansible_connection: aws_ssm
      hosts:
        server2:
        server3:
    
    local_servers:
      vars:
        ansible_connection: local
      hosts:
        server1:

# playbook.yml
- hosts: aws_ssm_servers:local_servers
  tasks:
    - name: Show connection method
      debug:
        msg: "Using {{ ansible_connection }} connection"