Dynamic hosts file using Chef

Note: This post is quite old. There are probably better methods for this now.

There are a number of ways to setup your infrastructure so that you can refer to machines by hostname. I currently prefer the “dynamically generated hosts file” approach because it’s simple to understand and setting up a DNS server is intimidating (as well as a single point of failure).

Shlomo Swidler has a great article comparing different DNS configurations as well as some sample code for dynamically updating hosts files. However, if you’re already using Chef you can achieve the same thing with a very simple cookbook.

First, create a new cookbook:

$ cd chef-repo/
$ rake new_cookbook COOKBOOK=hosts CB_PREFIX=site-

Place the following in site-cookbooks/hosts/recipes/default.rb:

# Find all nodes, sorting by Chef ID so their
# order doesn't change between runs.
hosts = search(:node, "*:*", "X_CHEF_id_CHEF_X asc")

template "/etc/hosts" do
  source "hosts.erb"
  owner "root"
  group "root"
  mode 0644
  variables(
    :hosts => hosts,
    :fqdn => node[:fqdn],
    :hostname => node[:hostname]
  )
end

Then create the template, site-cookbooks/hosts/templates/default/hosts.erb:

127.0.0.1   localhost

<% @hosts.keys.sort.each do |fqdn| %>
<%= @hosts[fqdn][:ipaddress] %> <%= fqdn %> <%= @hosts[fqdn][:hostname] %>
<% end %>

# The following lines are desirable for IPv6 capable hosts
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
ff02::3 ip6-allhosts

Add the recipe to your node’s run_list and run chef-client and your /etc/hosts should contain all the Chef nodes on your network. Note that one downside to this approach is that updates will be slow (since chef-client only runs every 30 minutes by default).

Now what if you wanted to point at a specific service, like having chef.example.com point at your Chef master? Just search for it in your recipe (and add it to the variables list):

chef_server = search(:node, 'run_list:recipe\[chef\:\:server\]')

And add a line to the template:

# Chef master server
<%= @chef_server[:ipaddress] %> chef.example.com

Sure beats setting up a DNS server!