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 withgroups['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:
- The nested quotes
hostvars[\" {{ item }} \"]
create invalid Jinja2 syntax - The
with_items
directive shouldn't use the groups lookup directly - 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
orwhen
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 }}"