Kamal Fariz Mahyuddin on Ruby on Rails, Ember.js and other web development geekery.

You should follow me on twitter here.

Using Chef Server Indexes as a Simple DNS

I’ve been setting up a small cluster of nodes for a client on Amazon EC2. For each node that is brought up, an internal hostname is attached to it to make it easier to address, for example, db0, db1, admin and so forth. To manage all this, I had been manually editing /etc/hosts on each node whenever new nodes and/or roles are added to the mix. As you can imagine with EC2, it is all too easy to spin up new instances when the need arises, so I needed a more sustainable solution.

The common approach is to run a private internal DNS server. As these machines are configured with Chef, 37signal’s djbdns::autozone recipe looked like a good starting point. It uses Chef Server Indexes to seed djbdns by querying all nodes and extracting the IP Address, FQDN and a custom DNS Aliases attribute. Nodes running chef-client keeps the Chef Server Indexes up to date everytime it connects to the server on a predefined interval. This makes it possible for djbdns to be automatically maintained as new nodes are spun up and added to Chef Server.

I also came across a much simpler solution in Tim Dysinger’s Using Amazon EC2 Metadata as a Simple DNS. In this approach, he crons a query to Amazon EC2’s metadata to rebuild /etc/hosts every hour. I like the simplicity of it (very similar to what I’ve already been doing by hand) and not having to run yet another daemon. However, it required the EC2 secret and key which my client may or may not be willing to share.

The solution I came to is a hybrid of the two. Instead of querying EC2’s metadata, I queried the Chef Server Indexes. Instead of cron, I relied on chef-client’s interval. Instead of djbdns, I used the hosts file.

Here’s how it looks like:


# Cookbook Name:: hosts
# Recipe:: default
# Copyright 2009, Bitfluent
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# See the License for the specific language governing permissions and
# limitations under the License.

hosts     = {}
localhost = nil

search(:node, "*", %w(ipaddress fqdn dns_aliases)) do |n|
  # node own's record, store in localhost
  if n["ipaddress"] == node[:ipaddress]
    localhost = n
    hosts[n["ipaddress"]] = n

template "/etc/hosts" do
  source "hosts.erb"
  mode 0644
  variables(:localhost => localhost, :hosts => hosts)


# Auto-generated by chef for <%= @node[:fqdn] %>
# localhost <%= @localhost["fqdn"] %> <%= (@localhost["dns_aliases"] || []).sort.join(" ") %>
<% @hosts.keys.sort.each do |ip| %>
<%= ip %> <%= @hosts[ip]["fqdn"] %> <%= (@hosts[ip]["dns_aliases"] || []).sort.join(" ") %>
<% end %>

A few things to note:

  • I use a hosts hash because for some reason, the server indexes were returning duplicate nodes
  • I output sorted IPs and dns aliases to keep /etc/hosts stable. Hashes are themselves not sorted. I didn’t want Chef to replace the files if no new nodes or aliases were added.

Happy cooking!

Fork me on GitHub