25th
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:
cookbooks/hosts/recipes/default.rb
# # 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 # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 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 else hosts[n["ipaddress"]] = n end end template "/etc/hosts" do source "hosts.erb" mode 0644 variables(:localhost => localhost, :hosts => hosts) end
cookbooks/hosts/templates/default/hosts.erb
# Auto-generated by chef for <%= @node[:fqdn] %>
#
127.0.0.1 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
hostshash because for some reason, the server indexes were returning duplicate nodes - I output sorted IPs and dns aliases to keep
/etc/hostsstable. Hashes are themselves not sorted. I didn’t want Chef to replace the files if no new nodes or aliases were added.
Happy cooking!