主にEC2内部用のDNSサーバ - ddns

※後継のMurakumoを開発中です。
https://bitbucket.org/winebarrel/murakumo/
http://d.hatena.ne.jp/winebarrel/20111202/p1

某勉強会でddnsのことを話したのですが、ほっとくと忘れ去られそうなので記事にまとめたいと思います。

これは何?

主にEC2での利用を想定した内部向けDNSサーバです。
ddnsはDynamic DNS…ではなくDistributed DNSの略です。直訳すると分散DNS
ひどすぎる名前ですね。最悪です。gem pushしてから後悔しました。
まあネーミングセンスがないのは昔からです。

一般的なDNSサーバと違って、ホスト名の情報を分散管理するのが特徴です。

ホスト名の分散管理って?

各サーバにDNSノードを常駐させて、ホスト名とIPアドレスを分散管理します。


具体的には、まずこんな感じで各サーバでDNSノードを起動します。


shell> ddns -H node-01 -P 53 -d start

まだクラスタリングされてないので、個々のノードで取得できるサーバ名は自分のだけです。

shell> ddnsctl -L
IP Address Timestamp Hostname
--------------- -------------------------- ---------------------------
10.146.46.58 2011/05/29 21:34:57.104933 node-01
shell> dig node-01 | grep "^[^;]"
node-01. 16000 IN A 10.146.46.58


なので適当にノードをつないでやります。


shell> ddnsctl -A 10.146.11.83

いずれかのノードに接続すれば、自ノードの情報はクラスタ全体に拡散します。
(CassandraでおなじみのGossipプロトコルです)


拡散が完了すれば、どのノードでもクラスタ全体の情報をとることができるようになります。


node-04> ddnsctl -L
IP Address Timestamp Hostname
--------------- -------------------------- ---------------------------
10.146.30.185 2011/05/29 21:50:45.267134 node-04
10.146.11.83 2011/05/29 21:50:45.625610 node-02
10.146.23.13 2011/05/29 21:50:45.181730 node-03
10.146.46.58 2011/05/29 21:50:44.971659 node-01
node-04> dig node-01 | grep "^[^;]"
node-01. 16000 IN A 10.146.46.58
node-04> dig node-02 | grep "^[^;]"
node-02. 16000 IN A 10.146.11.83


各アプリケーションはDNSの問い合わせを、自サーバのDSNノードの対して行います。


もしいずれかのノードが死んだ場合、そのノードの情報は10秒ほどで他のノードから削除されます。


node-01> ddnsctl -L
IP Address Timestamp Hostname
--------------- -------------------------- ---------------------------
10.146.46.58 2011/05/29 22:02:59.470274 node-01
10.146.11.83 2011/05/29 22:02:59.854440 node-02
10.146.23.13 2011/05/29 22:02:59.319566 node-03
node-01> dig node-04 | grep "^[^;]"
ap-northeast-1.compute.internal. 1800 IN SOA ns0.ap-northeast-1.compute.internal. hostmaster.amazon.com. 2007101600 3600 3600 3600 1800

なんでこんなん作ったの?

某社ではresolv.confに「nameserver」を複数並べて一応冗長化しているのですが…

  • キャッシュされるのでフェイルオーバー時にApacheのリロードとかが必要になる
  • cronでresolv.confを更新しているので、ダウンタイムが分単位
  • cron設定の管理がめんどい
  • いっそ各サーバにDNS用のロードバランサーを入れようかと思ったが、udpを振り分けてくれるロードバランサーが見つからない(LVSをのぞく)
  • とかとかとか…

hostsを更新してもいいんですが、nscdの設定とかではまりそうだなぁ…とかいろいろもやもやしたので、作ってみました。

プロダクションレベル?

残念ながらNoです。
勉強会の2〜3日前にgem pushして、他の人が発表している最中にバージョンがあがっているという始末です。
おまけに、この記事を書いていてTTLが調整できないことに気がつきました。なおします。直しました

負荷的な話では、20インスタンスぐらいまでは試しましたが、できれば数千インスタンスで試験したいところです。
「ミドルウェアの試験したいからインスタンスの上限をあげてください」と申請したら、あげてもらえるんでしょうか…?

とはいえ、内部DNS冗長化は頭の痛い問題ですし、個人的には時間を割いて取り組もうかと思ってます。
パッチ・バグ報告は大歓迎です。

もう少し実用的な設定

ついでに、EC2でddnsを稼働させる場合のもう少し実用的な設定について書きます。


Gossipプロトコルで使っているポートはデフォルトでudp:10870なので、iptablesに穴を開けてやります。


iptables -A IN_ETH0 -i eth0 -p udp -m udp -s 10.0.0.0/8 --dport 10870 -j ACCEPT


resolv.confでDNSノードを参照するようにします。元のresolv.confはddnsから参照するので、別名で保存します。


cp -pi /etc/resolv.conf /etc/resolv.conf.ddns
echo 'nameserver 127.0.0.1' > /etc/resolv.conf


resolv.confはほっとくとdhclient-scriptに書き換えられてしまうので、以下の修正を加えます。

--- /sbin/dhclient-script       2011-04-13 07:30:32.000000000 +0900
+++ /sbin/dhclient-script       2011-05-29 19:16:04.000000000 +0900
@@ -68,8 +68,13 @@
     for nameserver in $new_domain_name_servers; do
       echo nameserver $nameserver >> $rscf
     done
-    change_resolv_conf $rscf
-    rm -f $rscf
+    #change_resolv_conf $rscf
+    #rm -f $rscf
+    rscf4ddns=`mktemp /tmp/XXXXXX`;
+    echo 'nameserver 127.0.0.1' > $rscf4ddns
+    change_resolv_conf $rscf4ddns
+    mv -f $rscf /etc/resolv.conf.ddns
+    rm -f $rscf $rscf4ddns
   fi
 }


rc.localに以下の処理を加えます。

# for ddns
rm -f /var/run/DDNS/DDNS.pid
DDNS_ADDRESS=$(wget -q -O- http://169.254.169.254/latest/meta-data/local-ipv4)
DDNS_HOSTNAME=$(wget -q -O- http://169.254.169.254/latest/user-data)

if [ $? -eq 0 -a -n "$DDNS_ADDRESS" -a -n "$DDNS_HOSTNAME" ]
then
  /usr/local/bin/ddns \
    -H $DDNS_HOSTNAME \
    -P 53 \
    -a $DDNS_ADDRESS \
    -r /etc/resolv.conf.ddns \
    -d start
fi


これをAMI化しUser Dataでホスト名を渡してやれば、インスタンス起動時に渡したホスト名でDNSノードが起動するようになります。
あとは適当な初期ノードにつなげてやれば、内部DNSとして機能するようになります。

ec2-describe-instancesでタグ情報とってくるとか、生きているインスタンスIPアドレスを取得してつなぎにいくとか、さらにいろいろ工夫はできると思います。


さて、TTLの件を直すか…