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


2 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 }}"