先日、とあるサービスで自前運用していたDNSサーバーをAWSのRoute53へ移設しました。
その移設の際に学んだ下記二点について、大変良い勉強になったので、折角なのでまとめておきます。

  • BINDからRoute53への移設方法
  • DNSのNSレコード移設時のポイント

移設前の環境

移設前のサーバーは他社のマネージドタイプの専用サーバーを契約しており、CentOS5のBINDで稼働していました。
DNSのセカンダリーには、そのマネージドサービスの無料のセカンダリーDNSサービスを利用しており、管理はマスターのみ実施しておりました。
移設が必要なゾーンは1つ。レコードが5000程度ありました。
また、ユーザーさんから申し込みがあった際に、BINDの動的更新を利用してレコードの追加が行われていました。

移設に至った経緯

大きくはCentOS5がEOLになったこと、BINDの脆弱性が多く、DNSの運用コストが課題となっており、自前運用のメリットが殆どなかったことが挙げられます。
そこで、マネージド・サービスであるRoute53への移設することで、BINDの脆弱性対応などのDNS運用コスト、および専用サーバー費用を下げられないかを検討することになりました。

Route53のコスト計算

Route53のコスト計算で最も重要なのは、現在のDNSでどの程度のクエリが発行されているかということになります。
とは言え、BINDのクエリーログの出力はIO負荷が高く、常時オンにしている環境は殆ど無いと思われます。
そこで、一時的にクエリーログをオンにして、そこから単純計算を実施しました。
クエリーログの有効には、named.confに直接記述する方法と、rndcコマンドで一時的に有効にする方法があります。
今回は一時的に出力する目的でしたので、rndcを使って、クエリーログを出力しました。
なお、デフォルトではmessagesにqueryログが出力されます。

query logがoffであることを確認する。

# rndc status
query logging is OFF

query logをonにする。

# rndc querylog

query logがonになっていることを確認する。

# rndc status
query logging is ON

query logを停止する

# rndc querylog

query logがOFFであることを確認する。

# rndc status
query logging is OFF

上記でクエリーログをオンにした時間とクエリーログ数で、概算の月間のクエリー数を算出し、Route53の見積もりを出します。
Route53の見積もりについては、言うまでもなくAWS簡易見積もり計算ツールを利用しています。

結果、専用サーバーの月額費用よりも圧倒的に安価であることがわかったので、Route53への移設を進めることにしました。
※これは私の肌感ですが、AWSのEC2などのVMのサービスと比較すると、Route53だけ突出して安いように思います。

移設の手順

ここで言う移設は、事前準備が終わった状態で、レジストラに登録されているNSレコードまで切り替えが完了するまでになります。
安全に、そして、スムーズに移設するために実施した手順の概要が下記流れになります。

移設作業リリース前

    1. 開発チームにて、既存のDNSとRoute53へ同時にレコードを登録する仕組みの検証
    1. 既存レコードをRoute53へインポートする
    1. 開発チームにて、既存のDNSとRoute53へ同時にレコードを登録する仕組みのリリース
    1. 2〜3までに発生した差分を改めてRoute53へインポート
    1. 既存とAWSのNSレコードのTTLを短くしておく

移設作業リリース

    1. 既存DNSのNSレコードをRoute53へ変更する
    1. レジストラに登録されているDNSをRoute53へ変更する
    1. 2のNSレコードのTTL経過後、問題がなければRoute53のNSレコードのTTLを長くする

上記手順で実施しなかった場合は、いつまでたっても新しいレコードに切り替わらないなどの問題が発生します。
レジストラに登録されたDNSのみを変更した場合、レジストラのNSレコードのTTL分、切り替えに時間を要するキャッシュDNSがあらわれることになります。

BINDのzoneファイルのインポート

BINDをRoute53へ移設する場合、ゾーンファイルを変換してくれるツールと変換したファイルをRoute53へインポートしてくれるツールがあります。
これらの環境を用意するのに、さっくり使えるDockerfileを作って、dockerで作業を進めていきました。
※なお、Route53へのインポートにはAWSのキーが必要なので、AWSキーを用意した上で作業しています。

ちなみにRoute53のウェブのコンソールにも、BINDのゾーンファイルをインポートする機能があります。
しかし、1000レコードまでしか一括でインポートできないのと、繰り返しインポートすることができないという制約があるため、下記ツールを使うことで対応しました。
※AWSのサイトにも1000よりも多いレコードをインポートする場合はbindtoroute53.plを使うように促されています。

AWSキーは下記filesディレクトリ配下に用意して、docker buildすると良いと思います。

$ cat files/.aws-secrets
%awsSecretAccessKeys = (
    "aws-account" => {
        id => "******************",
        key => "******************",
    },
);
FROM centos:6
LABEL inamuu <mailaddress>

RUN yum -y install \
vim \
perl-CPAN \
gcc \
;\
yum clean all

RUN yes '' | cpan -fi Net::DNS::RR Net::DNS::ZoneFile NetAddr::IP Net::DNS Net::IP Digest::HMAC Digest::SHA1 Digest::MD5 MIME::Base64 Digest::HMAC_SHA1 FindBin MIME::Base64 Getopt::Long File::Temp File::Basename Fcntl IO::Handle Data::GUID

RUN mkdir -p /root/bin/

COPY ./files/ /root/bin/

WORKDIR /root/bin/
RUN curl -OL http://awsmedia.s3.amazonaws.com/catalog/attachments/bindtoroute53.pl \
&& chmod u+x bindtoroute53.pl \
;\
curl -OL http://awsmedia.s3.amazonaws.com/catalog/attachments/dnscurl.pl \
&& chmod u+x dnscurl.pl

CMD ["/sbin/init"]

zoneファイルをXMLへ変換する

事前にRoute53上で、移設予定のゾーンを作成しておきます。

bindtoroute53.plを使うと、BINDのゾーンファイルをXMLに変換することが出来ます。このXMLファイルをRoute53へインポートすることになります。
なお、一度にインポートできるレコードは1000個までの制限があるため、事前にzoneファイルを分割しておくと良いです。
今回は5000レコード近くあったので、5個のファイルに分割して、インポートしていきました。

testinamuu.com.zone.日付で保存したzoneファイルがあったと仮定し、下記のようなコマンドで変換しました。

/root/bin/bindtoroute53.pl --ignore-origin-ns --ignore-soa --origin testinamuu.com < /root/bin/testinamuu.com.zone.$(date +%Y%m%d) > /root/bin/testinamuu.com.xml.$(date +%Y%m%d)

変更するNSやSOAレコードなどは除外するように設定してあります。
作成されたXMLファイルの「Action」を見るとわかるように「CREATE」となっており、そのXMLファイルをインポートすると、レコードが作成されます。

XMLファイルをRoute53へインポートする。

/root/bin/dnscurl.pl --keyfile ./.aws-secrets --keyname aws-account -- -X POST -H "Content-Type: text/xml; charset=UTF-8" --upload-file testinamuu.com.xml.$(date +%Y%m%d) https
://route53.amazonaws.com/2010-10-01/hostedzone/自分のゾーンのID/rrset

上記でエラーが表示されなければ、レコードが登録されているはずです。
digコマンドで、正しくインポートされたか確認して、Route53のDNSで名前解決が行えていれば成功です。

$ dig testinamuu.com @ns-1415.awsdns-48.org.

;; AUTHORITY SECTION:
testinamuu.com.               86400   IN      NS      ns-1415.awsdns-48.org.
testinamuu.com.               86400   IN      NS      ns-1958.awsdns-52.co.uk.
testinamuu.com.               86400   IN      NS      ns-302.awsdns-37.com.
testinamuu.com.               86400   IN      NS      ns-826.awsdns-39.net.

なお、ActionがCREATEの状態で、再度インポートを実施すると、すでにレコードが存在するためエラーになって終了します。
削除する際は、ActionをDELETEにすると削除することが可能です。

バックアップについて

こちらのcli53を使うことで、既存のRoute53のデータをBIND形式でエクスポートすることが可能です。
インポートもできるようですが、インポートについては試していません。
cli53を使って、下記のようにしてバックアップしました。

$ cli53 export testinamuu.com > testinamuu.com.zone

以上が、BINDからRoute53にデータをインポートする手順です。
インポートが行えたら、上記で説明した手順でNSレコードを切り替えることでいつまでもレコードが切り替わらない問題にあたることなく、DNSをRoute53へ移設することが可能です。
なお、この移設にあたり、疑問になった点を最後にまとめておきます。


レジストラと自前DNSのNSレコードはどちらが参照されるのか。

通常、ドメインに対してNSレコードは、レジストラに登録してあるNSと自前DNSに登録している2つが存在することになります。
世の中のDNSクライアントは、基本的にはキャッシュDNSを参照して、名前解決の結果を受け取ります。
では、DNSクライアントがNSレコードを確認した場合、どちらのNSレコードが有効になってキャッシュDNSはキャッシュを返すのでしょうか。

答えはこちらのJPRSが作成したPDFに書いてあります。

子のNSが親のNSよりも優先的に取り扱われる
ただし、これが成立するのは子が子である間だけ
– 親からの委任がなければ子のNSはそもそも無効

だそうです。
つまり、Route53に移設するにあたり、レジストラ側のNSレコードだけを変えると子のNSレコードは無効となり、レジストラ側のNSレコードのTTL分だけ切り替えに時間を要します。
しかし、自前のDNS側でNSレコードをRoute53に変更し、レジストラ側のNSレコードを変更すると、自前のDNSが子のNSとして委任されているので有効になり、NSレコードのTTL後に新しいNSレコードが参照されるようになります。
また、レジストラ側のNSは大抵長い時間(1日から2日)のTTLが設定されていますが、仮に切り替わる前でも委任先の子のNSを参照するので、結果的にはRoute53が参照されるようになります。

委任と権威

委任と権威とは、パッと見のNSレコードでわかるものなのでしょうか。
答えはNO.
レコード単体では分からないようです。

これもJPRSの資料のP16に書かれており、

DNSの委任は親からのみの一方向
• 子は自分の親の情報を持たない
– 「親の心子知らず」?

だそうです。親から委任先のNSレコードを記載し、そのDNSでNSレコードを自分を指定することで、権威DNSになります。
digで+traceなどをすると、ルートDNSが参照できため、親のDNSなどを参照するのに便利です。

子のNSを別の名前(ドメイン)のRoute53にしても大丈夫か

結論、大丈夫です。

またもや同じJPRSの資料のP13に書かれており、

子のNSが親のものと異なっていても委任できる
– 必ずしも違反ではない(引越し途中など)
– ただし、双方のNSで示されたすべての権威DNSサーバーが
権威を持つ同内容の応答を返す必要あり

とあるので、引っ越し中、同じ結果を返すDNSを用意しておけば問題ありません。

注意点

Route53のように別のドメインであれば、問題は無いですが、同じドメインだった場合はグルーレコードの変更が必要です。
グルーレコードを変更しない場合、ループが発生してしまうのですが、その例えは下記サイトでわかりやすく例えられていたので見てみるとわかりやすかったです。

ループしてしまう場合の例え

つまり、グルーレコードが無いと、inamuu.comのIPアドレスはns.inamuu.comにしかなくて、ns.inamuu.comはinamuu.comのNSレコードを参照しなくちゃいけないというループに陥るからです。
そこで、委任元でNSレコードとAレコードのを登録することで、委任先の権威DNSの情報を受取ることができるようになります。
同じドメインで、自前でDNS運用している際は、グルーレコードにも注意が必要です。

ネガティブキャッシュはSOAレコードのどの値をみればよいか。

ネガティブキャッシュは存在しないレコードへアクセスした際に、レコードが存在しない場合は再度問い合わせないように、一定の時間レコードが存在しないことを保持しておくための時間です。
SOAレコードのminimumはドメインが存在しない場合に保持しておくTTLだそうで、レコードにおいては、RFC2308で定義されており、レコードについては古い実装のDNSでなければSOAのTTL秒待てばネガティブキャッシュが破棄されるようです。

JPRSのリンク

例えば、digでhogemuge.inamuu.comと名前解決すると、下記のようにstatusがNXDOMAINとなり、TTLがカウントダウンされていきます。

$ dig hogemuge.inamuu.com

;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 48947
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 0

;; QUESTION SECTION:
;hogemuge.inamuu.com.           IN      A

;; AUTHORITY SECTION:
inamuu.com.             1799    IN      SOA     inamuu.com. postmaster.inamuu.com. 1484121970 3600 1800 604800 3600

こうなってしまうと、この後にレコードを更新しても、TTLがすぎるまでは存在しないレコードであることを受け取り続けてしまうので、再帰的に上位DNSから名前解決をしていく必要があります。

その他参考にさせていただいサイト

なぜ「DNSの浸透」は問題視されるのか

こちらの記事はもう6年も前の話ではありますが、DNSの浸透が問題される件について、とてもわかりやすく説明されています。

奥が深いDNSサーバーとdigコマンド

一緒に作業したソフトウェアエンジニアの方の記事。
作業してすぐにまとめられてて、とても素晴らしいアウトプット力に脱帽しました。

こちらは弊社の同僚氏が昔自社でやったBINDからのRoute53への移設の話です。

出来る!DNSのお引っ越し!

インポートについては、自前で作成したようですが、それ以外の移設の手順はかなり参考にさせていただき、本人ともランチで話をしながら、教えていただきました笑

ちなみに、こんなスライドも公開していて、

BINDからの卒業というタイトルで、また、BINDを脱却するために色々やられているようです。素晴らしいですね。


以上、久しぶりに長い内容になりましたが大変勉強になったので良い経験でした。
マネージドサービスはそれ自体が商材なので、自前で用意する場合と比較して、高くついてしまう場合があります。
しかし、うまく活用することで、運用などの人的コストを削減することができるサービスであるとも言えます。
自分の目で見て、数値を出して、状況と環境に応じてうまく活用していけたら良いなと改めて感じました。

おしまい。


追記
サードパーティ製ツールとライブラリという名前でAWSのサイトに便利そうなツール一覧があったのでメモ。


追記
社内LTした際の発表スライド
https://speakerdeck.com/kzm0211/sayonarabind