BLOGTIMES
2013/10/25

Route53 を使ってダイナミック DNS を構築する

  aws  dns 
このエントリーをはてなブックマークに追加

フリーのダイナミック DNS サービスとしては 古参のdyndnsNo-IP などの多数のサービスがありますが、フリー版のアカウントだと定期的にログインしないと登録したレコードが expire されてしまいます。「もうちょっとでレコードが expire されますよー」という英文のメールが来るたびにログインするのが面倒になってきたので、有料版にアップグレードしようかと思いましたが、AWS の DNS サービスである Route53 で一手間かけると同様の DDNS が格安で運用できるようなので、ちょっとチャレンジしてみました。

AWS を使ったことがあれば、比較的簡単に設定できると思います。
今回の構築にあたっては下記のサイトが非常に参考になりました。

以下、構築メモ。

下準備

今回はRoute53 を外部からアップデートするスクリプトとして dnscurl.pl というファイルを使うので、これを動かすために必要となる各種のソフトウェアを予めインストールしておきます。今回の作業は CentOS 6.x で行いました。

yum install -y perl perl-Digest-HMAC perl-XML-XPath

スクリプト配置ディレクトリの作成

スクリプト類を配置するディレクトリを作成し、必要となるファイルをダウンロードした後、パーミッションを変更しておきます。

mkdir -p /path/to/route53ddns cd /path/to/route53ddns wget http://awsmedia.s3.amazonaws.com/catalog/attachments/dnscurl.pl wget https://gist.github.com/kkosuge/1960895/raw/3428523159465cc4f591b3ae3e706dbf3e991205/route53DynDNS.bash chmod 755 dnscurl.pl route53DynDNS.bash

Route 53 の設定

AWS にログインし Route 53 Management Consoleを開いて DDNS に使うゾーンを作成します。今回は DDNS 専用のサブドメインを作って登録したので、使うゾーンは下記のような SOA と NS レコードしかないシンプルなものになりました。設定が完了するとドメイン名の隣に作成したゾーンの Hosted Zone ID というものが表示されているはずなので、これをメモしておきます。

Amazon Route53 - Route53 を使ってダイナミック DNS を構築する

IAM の設定

セキュリティを考えると、このゾーンをアップデートするのに必要な最小限の権限を持った専用の ID を作成することが好ましいので、IAM Management Consoleを開いて、適当な新しいアカウントを作成し、Permissions の User Policies で下記のポリシーだけを割り当てておきます。文中の(ここにメモしたゾーンIDを書く)の部分については先ほどメモした、Hosted Zone IDの値を書き込んでください。

Route53DDNS Policy

{ "Statement":[ { "Action":[ "route53:ChangeResourceRecordSets", "route53:GetHostedZone", "route53:ListResourceRecordSets" ], "Effect":"Allow", "Resource":[ "arn:aws:route53:::hostedzone/(ここにメモしたゾーンIDを書く)" ] }, { "Action":[ "route53:ListHostedZones" ], "Effect":"Allow", "Resource":[ "*" ] }, { "Effect":"Allow", "Action":[ "route53:GetChange" ], "Resource":"arn:aws:route53:::change/*" } ] }

このポリシーに作成にあたっては 「Tate Eskew » Automating Dynamic DNS updating with AWS Instances and Route53」を参考にしました。

Access Key ID と Secret Access Key

ポリシーの割り当てが済んだら IAM の Security Credentials から Access Key ID と Secret Access Key を取得しておきます。
このあたりは Route 53 以外の AWS のサービス利用時と同様です。

取得が済んだらサーバーのコンソールに戻り、取得した Access Key ID と Secret Access Key が格納されたファイルを作ります。
他人に見られないようにパーミッションが 600 しておきましょう。

touch .aws-secrets chmod 600 .aws-secrets vi .aws-secrets # 下記の内容を挿入

.aws-secrets

%awsSecretAccessKeys = ( "my-aws-account" => { id => "XXXXXXXXXXXXXXXXXXXX", key => "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", } )

dnscurl.pl のテスト

Route 53 への疎通が問題ないかを確認するために下記のコマンドを打ち込んでみます。
問題がなければ HostedZones というゾーンの一覧が表示されるはずです。

./dnscurl.pl --keyname my-aws-account -- -H "Content-Type: text/xml; charset=UTF-8" https://route53.amazonaws.com/2010-10-01/hostedzone

0.0% <?xml version="1.0"?> <ListHostedZonesResponse xmlns="https://route53.amazonaws.com/doc/2010-10-01/"><HostedZones><HostedZone><Id>/hostedzone/XXXXXXXXXXXXXX</Id><Name>hoge.example.jp.</Name><CallerReference>XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX</CallerReference><Config><Comment>hoge ddns</Comment></Config></HostedZone></HostedZones><IsTruncated>false</IsTruncated><MaxItems>100</MaxItems></ListHostedZonesResponse>

route53DynDNS.bash の設定

環境に合わせて route53DynDNS.bash を編集します。
ここでは example.jp というゾーンに対して hoge というレコードを追加する設定例になります。
また、CentOS の xpath では -e オプションが使えないようなので、この部分については削除しました。

diff -u route53DynDNS.bash{.org,}

--- route53DynDNS.bash.org 2013-10-25 18:35:51.686855629 +0900 +++ route53DynDNS.bash 2013-10-25 19:23:00.255294392 +0900 @@ -2,18 +2,21 @@ ### User Settings (things you must set) +SCRIPT_DIR=`dirname $0` +cd "$SCRIPT_DIR" + ## Location of the dnscurl.pl script -DNSCurl="/path/to/route53DynDNS/dnscurl.pl" +DNSCurl="./dnscurl.pl" ## The host name you wish to update/create -myHostName="*" +myHostName="hoge" ## Zone/domain in which host (will) reside(s) -myDomainName="example.com" +myDomainName="example.jp" ########################################## ### Things you may want to set ## set the TTL for the new/updated A record -myTTL="600" +myTTL="300" route53API="https://route53.amazonaws.com/2011-05-05" route53APIDoc="https://route53.amazonaws.com/doc/2011-05-05/" @@ -33,7 +36,7 @@ ## Get the Hosted Zone ID from Route 53 hostedZoneIDSearch="ListHostedZonesResponse/HostedZones/HostedZone[Name=\"$myDomainName.\"]/Id" -route53HostedZoneID=`$DNSCurl --keyname my-aws-account -- -s -H "Content-Type: text/xml; charset=UTF-8" -X GET $route53API/hostedzone 2>/dev/null | xpath -e $hostedZoneIDSearch 2>/dev/null |awk -F'[<|>]' '/Id/{print $3}' | cut -d/ -f3` +route53HostedZoneID=`$DNSCurl --keyname my-aws-account -- -s -H "Content-Type: text/xml; charset=UTF-8" -X GET $route53API/hostedzone 2>/dev/null | xpath $hostedZoneIDSearch 2>/dev/null |awk -F'[<|>]' '/Id/{print $3}' | cut -d/ -f3` if [ -z $route53HostedZoneID ]; then echo "Could not find zone '$myDomainName' in route 53" @@ -47,7 +50,7 @@ currentARecordValueSearch="ListResourceRecordSetsResponse/ResourceRecordSets/ResourceRecordSet[Name=\"\\052.$myDomainName.\"]/ResourceRecords/ResourceRecord/Value" fi -currentARecordValue=`$DNSCurl --keyname my-aws-account -- -s -H "Content-Type: text/xml; charset=UTF-8" -X GET $route53API/hostedzone/$route53HostedZoneID/rrset 2>/dev/null | xpath -e $currentARecordValueSearch 2>/dev/null | awk -F'[<|>]' '/Value/{print $3}' | cut -d/ -f3` +currentARecordValue=`$DNSCurl --keyname my-aws-account -- -s -H "Content-Type: text/xml; charset=UTF-8" -X GET $route53API/hostedzone/$route53HostedZoneID/rrset 2>/dev/null | xpath $currentARecordValueSearch 2>/dev/null | awk -F'[<|>]' '/Value/{print $3}' | cut -d/ -f3` ## And if not, set a flag to create the A record if [ -z $currentARecordValue ]; then @@ -64,7 +67,7 @@ currentARecordTTLSearch="ListResourceRecordSetsResponse/ResourceRecordSets/ResourceRecordSet[Name=\"\\052.$myDomainName.\"]/TTL" fi -currentARecordTTL=`$DNSCurl --keyname my-aws-account -- -s -H "Content-Type: text/xml; charset=UTF-8" -X GET $route53API/hostedzone/$route53HostedZoneID/rrset 2>/dev/null | xpath -e $currentARecordTTLSearch 2>/dev/null | awk -F'[<|>]' '/TTL/{print $3}' | cut -d/ -f3` +currentARecordTTL=`$DNSCurl --keyname my-aws-account -- -s -H "Content-Type: text/xml; charset=UTF-8" -X GET $route53API/hostedzone/$route53HostedZoneID/rrset 2>/dev/null | xpath $currentARecordTTLSearch 2>/dev/null | awk -F'[<|>]' '/TTL/{print $3}' | cut -d/ -f3` if [ -z $currentARecordTTL ]; then CREATE_INITIAL=true @@ -165,7 +168,7 @@ ## Do the actual create/update updateResponse=`$DNSCurl --keyname my-aws-account -- -s -H "Content-Type: text/xml; charset=UTF-8" -X POST --upload-file $tmpxml $route53API/hostedzone/$route53HostedZoneID/rrset 2>/dev/null` ## And record the response -updateResponseStatus=`echo $updateResponse | xpath -e 'ChangeResourceRecordSetsResponse/ChangeInfo/Status' 2>/dev/null |awk -F'[<|>]' '/Status/{print $3}' | cut -d/ -f3` +updateResponseStatus=`echo $updateResponse | xpath 'ChangeResourceRecordSetsResponse/ChangeInfo/Status' 2>/dev/null |awk -F'[<|>]' '/Status/{print $3}' | cut -d/ -f3` ## Make sure the response is "PENDING" ## Otherwise, error @@ -177,7 +180,7 @@ fi ## Get the transaction ID -updateResponseID=`echo $updateResponse | xpath -e 'ChangeResourceRecordSetsResponse/ChangeInfo/Id' 2>/dev/null | awk -F'[<|>]' '/Id/{print $3}' | cut -d/ -f3` +updateResponseID=`echo $updateResponse | xpath 'ChangeResourceRecordSetsResponse/ChangeInfo/Id' 2>/dev/null | awk -F'[<|>]' '/Id/{print $3}' | cut -d/ -f3` echo "Got update status ID $updateResponseID with status $updateResponseStatus" echo "Pausing $sleepDelay seconds to allow for sync" @@ -186,7 +189,7 @@ ## Check for status statusCheckResponse=`$DNSCurl --keyname my-aws-account -- -s -H "Content-Type: text/xml; charset=UTF-8" -X GET $route53API/change/$updateResponseID 2>/dev/null` -statusCheckResponseStatus=`echo $statusCheckResponse | xpath -e 'GetChangeResponse/ChangeInfo/Status' 2>/dev/null | awk -F'[<|>]' '/Status/{print $3}' | cut -d/ -f3` +statusCheckResponseStatus=`echo $statusCheckResponse | xpath 'GetChangeResponse/ChangeInfo/Status' 2>/dev/null | awk -F'[<|>]' '/Status/{print $3}' | cut -d/ -f3` if [[ "$statusCheckResponseStatus" != "INSYNC" ]]; then echo "update failed!"

レコードのアップデートと定期実行

設定が完了したら、route53DynDNS.bash を起動してレコードが挿入もしくはアップデートされることを確認します。
ポーズの秒数が短いと Status が PENDING から INSYNC に変わらずに失敗したように見えることがあるので、Route 53 のウェブ画面で確認したほうが確実です。

LANG=C ./route53DynDNS.bash

Could not find A RR for hoge.example.jp. Creating initial record Public IP address is: ###.###.###.### Creating A record for hoge.example.jp. to be ###.###.###.### Got update status ID XXXXXXXXXXXXX with status PENDING Pausing 20 seconds to allow for sync Success!

最後に cron などで定期実行するようにすれば OK。

crontab -e

*/30 * * * * LANG=C /path/to/route53ddns/route53DynDNS.bash 2>&1 | logger -i -t route53ddns -p local0.info

    トラックバックについて
    Trackback URL:
    お気軽にどうぞ。トラックバック前にポリシーをお読みください。[policy]
    このエントリへのTrackbackにはこのURLが必要です→https://blog.cles.jp/item/6225
    Trackbacks
    このエントリにトラックバックはありません
    Comments
    愛のあるツッコミをお気軽にどうぞ。[policy]
    古いエントリについてはコメント制御しているため、即時に反映されないことがあります。
    コメントはありません
    Comments Form

    コメントは承認後の表示となります。
    OpenIDでログインすると、即時に公開されます。

    OpenID を使ってログインすることができます。

    Identity URL: Yahoo! JAPAN IDでログイン