AWS/EC2 インスタンスのホスト名を固定する (Route53 使用 Ver)

y.kimuraAWS,EC2,Route53

こんにちは、R&D グループの y.kimura です。

今回は Amazon Web Service の EC2 に Route53 を利用して、
お好きなホスト名でアクセスできるようにしようというお話です。

AWS に固定ホスト名でアクセスするには、以下の方法が有名です。

  1. アクセスする端末の hosts ファイルを書き換える
    メリット: 余計な費用が掛からない
    デメリット: EC2 側の変更に追従するのが面倒
     
  2. EC2 の Elastic IPを割り当てる
    メリット: 5 つまでなら無料で割り当てられる
    デメリット: 5 つ以上 EC2 インスタンスに割り当てると、申請+追加料金が必要
     
  3. Route53 に登録する
    メリット: EC2 インスタンス数の制限が無いに等しい
    デメリット: 元々使用していれば問題ないですが、そうでない場合はRoute53 をこれだけのために利用する手間と費用がかかる
     

それぞれ一長一短ありますが、
私の関わっているプロジェクトで実際に採用している 3 番目の方法をご紹介します。

やることはいたって単純で、次の通りになります。

EC2インスタンスの起動/再起動時に次の処理を実行するスクリプトを走らせる。

  1. EC2インスタンス自身のメタデータを取得する
  2. メタデータ(もしくはタグ情報)を元にホスト名を決定する
  3. ホスト名を Route53 から削除する(古いホスト名)
  4. ホスト名を Route53 に登録する(レコード名=ホスト名 の CNAME に PublicDNS を登録)

 

EC2 の API は、様々な言語の SDK が用意されていますが、今回は Ruby の SDK を使用します。
Ruby 本体のバージョンは 2.1.1、AWS-SDK のバージョンは 2014 年 2 月時点での最新 (1.34.1) です。

Route53 の設定、EC2 インスタンスの作成は実行済み、
EC2 インスタンスの OS は CentOS6.4 64bit とします。

1. Ruby のインストール

rvm を使ってインストールするのが簡単ですね。せっかくなので現在の最新版の 2.1.1 をインストールします。

$ curl -L https://get.rvm.io | bash -s stable

一旦ログアウトして再度ログイン。rvm コマンドが使用できるようになります。

rvm で ruby 本体をインストールする前に、epel のリポジトリをインストール

# wget http://ftp-srv2.kddilabs.jp/Linux/distributions/fedora/epel/6/x86_64/epel-release-6-8.noarch.rpm
# rpm -Uvh epel-release-6-8.noarch.rpm

必要なパッケージをインストール

# yum install -y patch libyaml-devel libffi-devel glibc-headers gcc-c++ glibc-devel readline-devel zlib-devel openssl-devel autoconf automake libtool bison

ruby 本体のインストール

$ rvm install 2.1.1

続いて AWS-SDK のインストール

$ gem install --no-ri --no-doc aws-sdk

これで準備完了です。

2. スクリプトを設置

次の ruby スクリプトを ruby をインストールしたユーザの home に設置します。

hostname_to_route53.rb

#!/usr/bin/env ruby
require 'rubygems'
require 'aws-sdk'

## 設定項目

# AWS のアクセスキーIDとシークレットアクセスキー
ACCESS_KEY_ID     = "アクセスキーID"
SECRET_ACCESS_KEY = "シークレットアクセスキー"

# EC2のリージョン=Asia Pacific/Tokyo
EC2_ENDPOINT = "ec2.ap-northeast-1.amazonaws.com"

# Route53のHosted-zone id
HOSTED_ZONE_ID = "Hosted Zone ID"

# ホスト名の元になるドメイン名
# 最後にピリオドを必ず付けること!
DOMAIN_NAME = "ec2.tricorn-labs.jp."

# TTL
TTL = 300

## 実行コード

SELF_META_URL  = "http://169.254.169.254/latest/meta-data"

# EC2インスタンスのメタデータ
MY_PUBLIC_DNS  = `curl #{SELF_META_URL}/public-hostname 2>/dev/null`
MY_PUBLIC_IP   = `curl #{SELF_META_URL}/public-ipv4     2>/dev/null`
MY_INSTANCE_ID = `curl #{SELF_META_URL}/instance-id     2>/dev/null`

# APIキー、秘密鍵の設定
AWS.config({:access_key_id => ACCESS_KEY_ID, :secret_access_key => SECRET_ACCESS_KEY})

# APIキーに対応したアカウントのEC2インスタンスコレクション
ec2 = AWS::EC2.new(:ec2_endpoint => EC2_ENDPOINT)

# このEC2インスタンス情報の取得
my_ins = ec2.instances[MY_INSTANCE_ID]

# たまにメタデータの取得に失敗する 念のためチェック
raise "#{MY_INSTANCE_ID.to_s} was not found on your EC2" unless my_ins.exists?

sub_domain = my_ins.tags['sub-domain'] ? my_ins.tags['sub-domain'] : my_ins.instance_id
hostname = "#{sub_domain}.#{DOMAIN_NAME}"
r53 = AWS::Route53::Client.new()

rrsets = AWS::Route53::HostedZone.new(HOSTED_ZONE_ID).rrsets
rrset = rrsets[hostname, 'CNAME']

# レコードセットの削除
begin
  cur_rec = rrset.resource_records[0][:value]
rescue => error
  cur_rec = ''
end

if !cur_rec.empty? then
  batch = AWS::Route53::ChangeBatch.new(HOSTED_ZONE_ID)
  batch << AWS::Route53::DeleteRequest.new(hostname, 'CNAME', :ttl => TTL, :resource_records =>[{:value => cur_rec}])
  batch.call
end

# レコードセットの登録
batch = AWS::Route53::ChangeBatch.new(HOSTED_ZONE_ID)
batch << AWS::Route53::CreateRequest.new(hostname, 'CNAME', :ttl => TTL, :resource_records => [{:value => MY_PUBLIC_DNS }])
batch.call

なお、設定するアクセスキーID・シークレットアクセスキーに対応する IAM ユーザの権限としては以下が割り当てられていれば動作します (不要なものもあるかもしれませんが)。

  • ec2
    • DescribeInstanceAttribute
    • DescribeInstanceStatus
    • DescribeInstances
    • DescribeTags
  • route53
    • * (全ての権限)

では、スクリプトに実行権限を付けましょう

$ chmod 700 hostname_to_route53.rb

EC2インスタンスに、「sub-domain」というタグが設定されていると、
そちらをサブドメインとして使用します。
タグがない場合は、インスタンスID (i-XXXXXXXX) をサブドメインとして使用します。

スクリプト内の DOMAIN_NAME が 「ec2.tricorn-labs.jp」となっており、「sub-domain」タグに「web01」が設定されている EC2 インスタンスで実行した場合、Route53 に登録されるホスト名は、「web01.ec2.tricorn-labs.jp」となります。
※「tricorn-labs.jp」というドメインは実際には存在しません。

複数の EC2 インスタンスで使用する場合は、
「sub-domain」タグの値が重複しないよう注意してください。

さて試しに実行してみましょう。

$ ./hostname_to_route53.rb

マネジメントコンソールの Route53 で確認すると、新たにレコードが登録されていることがわかります。

CNAME に PublicDNS を登録するため、
同一リージョンの EC2 同士でホスト名を使用して通信した場合、
PrivateIP に変換されるため外部ネットワークを経由せずに通信することができます。

3. サーバ起動時に実行する

最後に、EC2 の PublicIP,PublicDNS が変更される
「Stop」⇒「Start」時に自動的にスクリプトを実行するようにします。

以下の様な init スクリプトを用意します。
ruby をインストールしたユーザを仮に ec2-user、スクリプトのパスを /home/${USER}/aws-tools/hostname_to_route53.rb としていますが、適宜変更して下さい。

r53

#!/bin/sh
#
#    /etc/rc.d/init.d/r53
#
# Register hostname to Route53
#
# chkconfig: 345 44 56
#
USER=ec2-user
SCRIPT=/home/${USER}/aws-tools/hostname_to_route53.rb

start() {
    su -l $USER -c "$SCRIPT"
    return $?
}

stop()
{
    return 0
}

restart() {
    stop
    start
}

case "$1" in
    start)
        start
        ;;
    stop)
        stop
        ;;
    restart)
        restart
        ;;
    *)
        echo "Usage: $0 {start|stop|restart}"
        RETVAL=2
esac
exit $RETVAL

/etc/init.d/ 以下に設置し、実行権限を付与した上で、chkconfig で起動時に実行されるよう登録します。

# mv r53 /etc/init.d/
# chown root:root /etc/init.d/r53
# chmod 700 /etc/init.d/r53
# chkconfig r53 on

さて、準備が出来ましたので試しに EC2 インスタンスを「Stop」アンド「Start」してみます。
しばらく待つと、Route53 にホスト名が登録されます!

このように、EC2 インスタンスの boot 時自動的に
Route53のレコードを更新するような仕組みを入れた AMI を作成しておけば、
PublicDNSは長すぎる!
IPだと覚えにくい!
うかつにインスタンスを停止できない!(するな)
などの悩みから解消されます。

y.kimuraAWS,EC2,Route53

Posted by y.kimura