Ubuntu Weekly Recipe

第334回Route 53でダイナミックDNSを運用する

梅雨も明け、本格的な夏を迎えましたが、皆様のサーバーはお元気にお過ごしでしょうか。

VPSやクラウドサービスが気軽に使えるようになりましたが、ファイルサーバーのように、自宅内に置いておく必要のあるサーバーもまだまだありますよね。筆者も自宅に、Ubuntu Serverを何台か動作させています。そしてモバイル全盛のこの時代、インターネットからアクセスできないのでは、自宅サーバーの魅力も半減というものです。そのため、SSHの踏み台サーバーなどを用意して、インターネットから自宅内へアクセスできるように設定している方も多いのではないでしょうか。

インターネットから自宅内部へアクセスするためには、アクセス先としてブロードバンドルーターなどに割り振られたグローバルIPアドレスを知っておく必要があります。ここで問題になるのが、プロバイダから割り振られるグローバルIPアドレスが、常に一定であるとは限らないということです。現在の日本では、ネット回線は繋ぎっぱなしが当たり前になっているため、IPアドレスは大昔のダイアルアップ時代に比べれば、そうそう頻繁に変わることはなくなりました。しかしたとえばルーターを再起動したタイミングなど、ある日突然変化する可能性は存在します[1]⁠。

そこで登場するのがダイナミックDNSです。ダイナミックDNSは、動的に変化したIPアドレスをDNSサーバーに登録することができるサービスです。ダイナミックDNSを利用すれば、IPアドレスの変化を気にすることなく、決まったドメイン名でいつでも自宅にアクセスすることが可能になります。

Route 53でダイナミックDNS

ダイナミックDNS自体は、それほど珍しいサービスではありません。最近の家庭用ブロードバンドルーターには、メーカーやプロバイダの運営するダイナミックDNSサービスに、自身のIPアドレスを登録する機能が実装されているものもあります。しかしこれらはお手軽ではあるものの、

  • 決められたドメインのサブドメインとして登録されるため、自分のドメインが使えない
  • それなりにお金がかかる(場合がある)注2

というデメリットもあります。

そこで筆者はAWSのDNSサービスであるRoute 53を使い、自宅サーバー用のレコードを動的に登録することにしました[3]⁠。

ゾーンの作成

まずドメインのゾーンを作成しましょう。既存のドメインのサブドメインとして運用する場合のように、既にRoute 53上にゾーンが存在し名前解決ができるようになっている場合は、ゾーンIDの確認のみ行ってください。

AWSにログインし、Console Homeから「Route 53」を開きます。次に「Create Hosted Zone」をクリックし、右側のペインにドメイン名を入力したら「Create」をクリックしてください。これでゾーンが作成されます。作成されたゾーンには、自動的に「Hosted Zone ID」というユニークなIDが割り振られています。これは後で使用しますので、どこかに控えておきましょう。また自動的にNSレコードが作成され、ネームサーバーが割り当てられています。このネームサーバーを、ドメインの権威サーバーとして登録しておいてください[4]⁠。これで、レコードを登録する準備が整いました。

図1 ゾーンが作成され、レコードを登録する準備が整った
図1 ゾーンが作成され、レコードを登録する準備が整った

ユーザーの作成とポリシーの定義

AWSのAPIを叩くには、適切な権限が与えられたユーザーアカウントが必要です。AWS Identity and Access Management(IAM)を使い、Route 53の特定のゾーンにのみ変更を加えられる、最小限のアカウントを作成しましょう。

Console Homeから「IAM」を開きます。左ペインの「Users」をクリックすると、現在のユーザー一覧が表示されます。ここで「Create New Users」をクリックします。作成したいユーザー名を入力し、⁠Generate an access key for each User」にチェックを入れて「Create」をクリックしてください。ユーザーが作成され「Access Key ID」「Secret Access Key」が表示されました。このタイミングでホームディレクトリに「.aws-secrets」というファイルを作成し、以下を参考にIDを記述してください。また他人から中身を見られないよう、ファイルのパーミッションは600にしておいてください。

%awsSecretAccessKeys = (
   "任意の名前" => {
      id => "アクセスキーID",
      key => "シークレットキーID",
   },
);
図2 ユーザーが作成され「Access Key ID」「Secret Access Key」が表示された
図2 ユーザーが作成され「Access Key ID」と「Secret Access Key」が表示された

作成したばかりのユーザーには、権限が付与されていません。⁠Permissions」タブ内の「Attach User Policy」をクリックすると、ポリシーを設定するダイアログが開き、テンプレートからポリシーを選択したり、あるいはカスタマイズしたポリシーを作成することができます。Route 53にフルアクセスできるポリシーと、読み取り専用アクセスのポリシーがテンプレートに存在するのですが、どちらも今回の目的には合いませんので、ここでは「Policy Generator」で作成することにします。

「Policy Generator」では、必要な項目を選択するだけでポリシーを生成してくれます。特定のDNSゾーンに対するリソースの読み取りと変更を可能にするためには、以下の内容を設定してください。指定したゾーンにおいて、レコードの一覧表示とレコードの変更が可能になります。

EffectAllow
AWS ServiceRoute 53
ActionsChangeResourceRecordSets, ListResourceRecordSets
ARNarn:aws:route53:::hostedzone/ゾーン作成時にメモしたゾーンID

「Add Statement」をクリックしてポリシーを追加したら、⁠Continue」をクリックします。作成したポリシーは、⁠Simulate Policy」でテストすることができます。必要な権限が付与されているか、また不要な権限が許可されていないか、チェックしておくと良いでしょう。

図3 ⁠Permissions」タブ内の「Attach User Policy」をクリックする
図3 「Permissions」タブ内の「Attach User Policy」をクリックする
図4 必要な権限が付与されているか、また不要な権限が許可されていないかをチェックする
図4 必要な権限が付与されているか、また不要な権限が許可されていないかをチェックする

dnscurl.plを使う

これでゾーンと、アクセス用のユーザーが作成できました。それではいよいよ家庭内のUbuntu Serverから、Route 53を制御して行きましょう。現在のグローバルIPアドレスを調べ、変更があった場合はレコードを更新するプログラムを作成し、これをアクセスの対象となる家庭内サーバーで一定時間ごとに起動させるのが基本戦略となります。

コマンドからAWSのAPIを叩くためにはAWSコマンドラインインターフェイスが存在しますが、今回はRoute 53だけを制御できれば良いため、シンプルさを重視してdnscurl.plを使うことにしました。これはcurlを使ってRoute 53のAPIを叩くための、Perlで書かれたラッパーです。このスクリプトを動作させるためにはPerl、curlの他に、以下のモジュールが必要になります。Ubuntu 14.04 LTS Serverの場合、libdigest-hmac-perlパッケージを追加でインストールする必要があるかもしれません。

  • Digest::HMAC_SHA1
  • FindBin
  • MIME::Base64
  • Getopt::Long
  • File::Temp
  • File::Basename
  • Fcntl
  • IO::Handle
$ sudo apt-get install libdigest-hmac-perl

以下のようにdnscurl.plスクリプトをダウンロード[5]したら、実行権限を付与しておきましょう。

$ wget http://awsmedia.s3.amazonaws.com/catalog/attachments/dnscurl.pl
$ chmod +x dnscurl.pl

リソースの一覧を取得する

dnscurl.plでゾーンの情報を取得するには、以下のようにコマンドを実行します。鍵の名前には.aws-secrets内で指定した任意の名前を、ゾーンIDにはゾーンを作成した際に控えたIDを入力してください。これまでの手順に間違いがなければ、応答としてゾーンのレコードの一覧がXMLとして返ってきます。もしもエラーが返ってくるような場合は、エラーの内容を見て、これまでの手順を確認してください。たとえば「No hosted zone found with ID」と言われた場合は、ゾーンIDが間違っていないか確認してください。⁠AccessDenied」と言われたならば、IAMのポリシーを確認しましょう。

$ dnscurl.pl --keyname (鍵の名前) -- -H "Content-Type: text/xml; charset=UTF-8" -X GET https://route53.amazonaws.com/2013-04-01/hostedzone/(ゾーンID)/rrset

<?xml version="1.0"?>
<ListResourceRecordSetsResponse xmlns="https://route53.amazonaws.com/doc/2013-04-01/"><ResourceRecordSets><ResourceRecordSet><Name>example.com.</Name><Type>A</Type><TTL>300</TTL><ResourceRecords><ResourceRecord><Value> (省略)

レコードを登録する

前述のコマンドが問題なく動作していることが確認できたら、いよいよコマンドラインからレコードの登録を行いましょう。レコードを登録する場合は、以下のようにコマンドを実行します。

$ dnscurl.pl --keyname (鍵の名前) -- -H "Content-Type: text/xml; charset=UTF-8" -X POST --upload-file (XMLファイル) https://route53.amazonaws.com/2013-04-01/hostedzone/(ゾーンID)/rrset

リソースの取得と異なるのは、GETではなくPOSTメソッドを実行している点と、アップロードファイルとしてXMLファイルを指定している点です。dnscurl.plで情報の登録、更新を行う際には、その情報をXMLとして作成し、POSTでアップロードする必要があるのです。たとえば「example.com」ゾーンに「home.example.com」のAレコードを新規登録するには、以下のようなXMLファイルを使います。Valueには、自宅のグローバルIPアドレスを入れてください。またTTLは任意の値に変更して構いません。

<?xml version="1.0" encoding="UTF-8"?>
<ChangeResourceRecordSetsRequest xmlns="https://route53.amazonaws.com/doc/2013-04-01/">
  <ChangeBatch>
    <Changes>
      <Change>
        <Action>CREATE</Action>
        <ResourceRecordSet>
          <Name>home.example.com.</Name>      ← ドメイン名
          <Type>A</Type>                      ← レコードのタイプ
          <TTL>300</TTL>                      ← レコードのTTL
          <ResourceRecords>
            <ResourceRecord>
              <Value>aaa.bbb.ccc.ddd</Value>  ← IPアドレス
            </ResourceRecord>
          </ResourceRecords>
        </ResourceRecordSet>
      </Change>
    </Changes>
  </ChangeBatch>
</ChangeResourceRecordSetsRequest>

dnscurl.plを実行した結果、以下のような応答が返ってくればレコードの登録は成功です。digコマンドなどを使って、名前解決ができるか確認してみましょう。

<?xml version="1.0"?>
<ChangeResourceRecordSetsResponse xmlns="https://route53.amazonaws.com/doc/2013-04-01/">
  <ChangeInfo>
    <Id>/change/C2RAxxxxxxxxxx</Id>
    <Status>PENDING</Status>
    <SubmittedAt>2014-07-20T08:25:15.480Z</SubmittedAt>
  </ChangeInfo>
</ChangeResourceRecordSetsResponse>

レコードを変更する

ダイナミックDNSを実現するためには、IPアドレスが変更された時にレコードを変更できなければなりません。レコードの変更は登録と同じコマンドで実現できるのですが、アップロードするXMLが少々異なります。具体的に言うと「古いレコードを削除してから、新しい情報でレコードを作り直す」という処理が必要になる点です。そのためのXMLは以下のようになります。Changeノードが2つになり、CREATEの前にDELETEというアクションが増えている以外は、レコードの登録時とまったく同じです。アップロードするためのコマンドも同一です。

<?xml version="1.0" encoding="UTF-8"?>
<ChangeResourceRecordSetsRequest xmlns="https://route53.amazonaws.com/doc/2013-04-01/">
  <ChangeBatch>
    <Changes>
      <Change>
        <Action>DELETE</Action>                 ← 古いレコードを削除する
        <ResourceRecordSet>
          <Name>home.example.com.</Name>
          <Type>A</Type>
          <TTL>300</TTL>
          <ResourceRecords>
            <ResourceRecord>
              <Value>aaa.bbb.ccc.ddd</Value>    ← 現在DNSに登録されている古いIPアドレス
            </ResourceRecord>
          </ResourceRecords>
        </ResourceRecordSet>
      </Change>
      <Change>
        <Action>CREATE</Action>                ← レコードを作り直す
        <ResourceRecordSet>
          <Name>home.example.com.</Name>
          <Type>A</Type>
          <TTL>300</TTL>
          <ResourceRecords>
            <ResourceRecord>
              <Value>www.xxx.yyy.zzz</Value>    ← 新しいIPアドレス
            </ResourceRecord>
          </ResourceRecords>
        </ResourceRecordSet>
      </Change>
    </Changes>
  </ChangeBatch>
</ChangeResourceRecordSetsRequest>

シェルスクリプトで自動化する

これでコマンドラインからレコードの登録と、変更ができるようになりました。あとはこれをシェルスクリプトにし、cronで定期的に実行すれば良さそうです。おおまかな処理の流れは以下のようになるでしょう。

  1. 自分のグローバルIPアドレスを調べる。
  2. Route 53に登録されているレコードを調べる。
  3. レコードが存在しなかった場合は、新規作成用のXMLを作成してアップロードする。
  4. レコードが存在し、かつ新旧のIPアドレスが同一だった場合は、何もせず終了する。
  5. IPアドレスが変更されていた場合は、更新用のXMLを作成してアップロードする。

筆者は自宅で使用するために、簡単なスクリプトを作成しました[6]⁠。せっかくなのでこのスクリプトの使用方法を説明します。またスクリプト内ではXMLから値を取り出すためにhxselectコマンドを使用しています。html-xml-utilsパッケージがインストールされていない場合は、インストールしておいてください。

$ sudo apt-get install html-xml-utils

まずddns-route53.confを参考に、スクリプト内で使用する鍵の名前やゾーンID、dnscurl.plをインストールしたパスなどを、自分の環境に合わせて設定してください。NEW_RR_VALUEには、現在のグローバルIPアドレスが設定されるようにします。LANの内部からブロードバンドルーターに振られているIPアドレスを確実に知る手軽な方法がないため、アクセス元のIPアドレスを返すタイプの外部サービスをcurlで叩いて、その応答を利用しています[7]⁠。LOGGERはメッセージを出力するためのコマンドです。標準ではechoコマンドを使って標準出力に出力するようにしてありますが、cronで実行するような場合はsyslogにログを残したいこともあるかと思います。そのような場合は「"/usr/bin/logger -p local0.info -t ROUTE53"」などの指定をしても良いでしょう。

# Please check and set your global IP address by any method.
# (e.g. http://ipcheck.ieserver.net/)
NEW_RR_VALUE=$(curl -s -4 http://ipcheck.ieserver.net/)

# Please set the following variables to your environment.
DNSCURL=./dnscurl.pl           # path of dnscurl.pl
KEYNAME=your-key-name          # your key name in .aws-secrets
TTL=300                        # TTL of this record
NAME=www.example.com.          # Target A record
ZONEID=YOURZONEID              # your zone ID
LOGGER=echo                    # send messages program
HOSTEDZONE=https://route53.amazonaws.com/2013-04-01/hostedzone

スクリプト本体であるupdate_record.shに、作成した設定ファイルを引数で渡すことで動作します[8]⁠。これだけで、現在登録されているレコードのチェック、IPアドレスの比較などを行い、レコードが存在しない場合は新規作成、IPアドレスが変化した場合は更新を行ってくれます。サーバーのcrontabに登録して、定期的に実行しておくと良いでしょう。筆者も自宅で動作させていますが、今のところ問題なく動いてくれているようです。またこのスクリプトは想定できるエラーのチェックなどをいくつか省略しているため、腕に覚えのある方は、自分用のスクリプトを自作してみるのも楽しいかもしれません。

$ /home/mizuno/bin/update_record.sh /home/mizuno/ddns_route53.conf.mine
IP address did not change.

AWSのサービスは非常に多岐にわたりますが、その中でもRoute 53はデメリットがなく、このように簡単に利用できるサービスの1つです。Ubuntu Server運用のお共に、独自ドメインとRoute 53を利用してみるのはいかがでしょうか。

おすすめ記事

記事・ニュース一覧