How to Access hostvars for Ansible Host Groups: Solving the “host not found” Error in /etc/hosts Management


11 views

When working with Ansible to manage host file entries across server groups, many engineers encounter the frustrating "host not found" error when trying to reference hostvars. Let's break down a proper solution with working examples.

Given your inventory file:

[server_list]
server1
server2

Here's the fixed implementation that properly accesses hostvars:

- name: Add entries to /etc/hosts
  lineinfile:
    dest: /etc/hosts
    line: "{{ hostvars[item]['ansible_eth1']['ipv4']['address'] }} {{ hostvars[item]['ansible_hostname'] }}"
  loop: "{{ groups['server_list'] }}"

1. Removed the nested curly braces inside the hostvars reference
2. Replaced deprecated with_items with modern loop
3. Simplified the variable access pattern

For more complex scenarios, consider using a template:

- name: Template /etc/hosts
  template:
    src: hosts.j2
    dest: /etc/hosts

With corresponding template (hosts.j2):

{% for host in groups['server_list'] %}
{{ hostvars[host]['ansible_eth1']['ipv4']['address'] }} {{ hostvars[host]['ansible_hostname'] }}
{% endfor %}

If you still encounter issues:

- debug:
    var: hostvars[item]
  loop: "{{ groups['server_list'] }}"

- debug:
    var: hostvars

1. Ensure gather_facts: true is set (default)
2. Verify network interface names match (eth1 might be ens192 on modern systems)
3. Consider using ansible_default_ipv4.address instead of interface-specific references


When working with Ansible's dynamic inventory and group variables, you might encounter the frustrating "host not found" error when trying to access hostvars for items in a host group. This typically happens when:

  • Using the with_items iterator with groups['group_name']
  • Trying to reference variables through nested Jinja2 templates
  • Mixing old-style {{ }} syntax with variable interpolation

The issue stems from incorrect quoting and template nesting. Here's the proper way to structure your task:

- name: Update /etc/hosts with cluster members
  lineinfile:
    dest: /etc/hosts
    line: "{{ hostvars[item]['ansible_eth1']['ipv4']['address'] }} {{ hostvars[item]['ansible_hostname'] }}"
  with_items: "{{ groups['server_list'] }}"

Your original attempt had several syntax issues:

  1. The nested quotes hostvars[\" {{ item }} \"] create invalid Jinja2 syntax
  2. The with_items directive shouldn't use the groups lookup directly
  3. Modern Ansible prefers the full jinja2 expression for variable interpolation

For more complex scenarios, consider this pattern using set_fact:

- name: Gather host information
  set_fact:
    cluster_hosts: |
      {% for host in groups['server_list'] %}
      {{ hostvars[host]['ansible_eth1']['ipv4']['address'] }} {{ hostvars[host]['ansible_hostname'] }}
      {% endfor %}

- name: Deploy hosts file
  blockinfile:
    path: /etc/hosts
    block: "{{ cluster_hosts }}"
    marker: "# {mark} ANSIBLE MANAGED BLOCK - SERVER LIST"

Since network interface names can vary (eth0, ens192, etc.), use this more robust approach:

- name: Find primary interface IP
  set_fact:
    primary_ip: >-
      {% set iface = (ansible_interfaces | difference(['lo']) | first) %}
      {{ hostvars[item][('ansible_' + iface)]['ipv4']['address'] }}
  
- name: Add hosts entries
  lineinfile:
    dest: /etc/hosts
    line: "{{ primary_ip }} {{ hostvars[item]['ansible_hostname'] }}"
  with_items: "{{ groups['server_list'] }}"
  • Always use become: yes when modifying system files
  • Include validation with validate parameter
  • Consider using templates instead of lineinfile for complex files
  • Add idempotency checks with creates or when conditions
- name: Ensure hosts file consistency
  template:
    src: templates/hosts.j2
    dest: /etc/hosts
    validate: '/usr/sbin/ipcalc -c %s'
    backup: yes
  vars:
    cluster_nodes: "{{ groups['server_list'] | map('extract', hostvars) | list }}"