VPC内 Private DNSに TAGを使って自身の名前を自動登録する


VPCを利用した開発環境
----

VPC と Route53 の Private DNS をうまく組み合わせると、内部的にはまったく同一の開発環境を複数立てることが出来て便利です。Route53 の Private DNS は、同じ名前の Hosted Zone をいくつでも作る事が出来るので、例えば、"dev.example.com" と "demo.example.com" 用にそれぞれ VPC を作りつつ、VPC内部のホストは Private DNSを利用して どちらも "webapp.example.com.internal" という名前をつけた Internal ELB経由でアクセスする　といったことが可能になります。

しかし、実際に内部のホストにログインして何かをしようと思うと、デフォルトで着いている ip-xx-xx-xx みたいな名前ではツライ。。といって、固定の webapp-01.example.com.internal とかつけると、AutoScaling のときなどに困ります。DHCPのアドレスなどを利用して、webapp-123.dev.example.com.internal などの名前を起動時に自動で Private DNS に登録して欲しいところ。

インスタンス起動時に Route53 にホスト名を自動登録する 
------

このあたりについては、すでにいろいろな方が書かれているので、下記などを参考にすれば出来るかと思います。

* [AWSで構築した環境にありがちなシェルスクリプトたち まとめ：Route53の自動登録](http://dev.classmethod.jp/cloud/aws/aws-shellscript-summary/#route53-auto-register)

ただ、上記だと、停止時にDNS登録を消してくれないので、どんどん登録が増えてしまいます。下記などを参考に、停止時に自動で DNS登録を削除することも忘れずに...

* [EC2の起動時・停止時に処理を実行する方法](http://www.infoscoop.org/blogjp/2014/06/04/ec2-boot-stop-shell/)

下記みたいな感じのものを /etc/init.d/dns-ctrl.sh として配置する感じですね。
で、問題は、/var/lib/instance_config で、環境変数を用意してあげるところです。

```bash:dns-ctrl.sh
#!/bin/bash                                                                                                        
# chkconfig: 2345 90 10                                                                                            
# description: add this instance to Route53                                                                        

source /var/lib/instance_config

LAST_IP=`echo ${IP_ADDRESS} | cut -d "." -f 4`
HOST_NAME=`echo ${HOST_NAME_BASE}-${LAST_IP}`
# for Full IP                                                                                                      
# HOST_NAME=`echo ${HOST_NAME_BASE}-${IP_ADDRESS} | sed -e 's/\./\-/g'`                                            

name="dns-ctrl"
lock_file="/var/lock/subsys/${name}"

case "$1" in
    start)
        touch ${lock_file}
        BATCH_JSON='{                                                                                                      
          "Changes": [                                                                                                     
            { "Action": "UPSERT",                                                                                          
              "ResourceRecordSet": {                                                                                       
                "Name": "'${HOST_NAME}'.'${VPC_ENV}'.'${DOMAIN_NAME}'",                                                    
                "Type": "A",                                                                                               
                "TTL" : 60,                                                                                                
                "ResourceRecords": [                                                                                       
                  { "Value": "'${IP_ADDRESS}'" }                                                                           
                ]                                                                                                          
              }                                                                                               
            }                                                                                                              
          ]                                                                                                                
        }'
        aws route53 change-resource-record-sets --hosted-zone-id ${HOSTED_ZONE_ID}  --change-batch "${BATCH_JSON}"
        ;;

    stop)
        rm -f ${lock_file}
        BATCH_JSON='{                                                                                                      
          "Changes": [                                                                                                     
            { "Action": "DELETE",                                                                                          
              "ResourceRecordSet": {                                                                                       
                "Name": "'${HOST_NAME}'.'${VPC_ENV}'.'${DOMAIN_NAME}'",                                                    
                "Type": "A",                                                                                               
                "TTL" : 60,                                                                                                
                "ResourceRecords": [                                                                                       
                  { "Value": "'${IP_ADDRESS}'" }                                                                           
                ]                                                                                                          
              }                                                                                                            
            }                                                                                                              
          ]                                                                                                                
        }'
        aws route53 change-resource-record-sets --hosted-zone-id ${HOSTED_ZONE_ID}  --change-batch "${BATCH_JSON}"
        ;;
esac

exit

```


Hosted Zone を判別する
---------
さて、今回、困ったのがこの部分。Route53 に自動登録をする際には、登録先の Hosted Zone IDを指定する必要があります。同じ AMI を利用してインスタンスを立ち上げつつ、デプロイされた環境に応じて登録して欲しいので、何らかの方法で　この Hosted Zone IDを自動で取得する必要がありました。

ところが、Route53の list-hosted-zones を使って Hosted Zone のリストを取得してみても、どのVPCに紐づいているのかわからないのです。名前で判断しようにも、同じ 内部ドメイン名で作っているので判別しようがありません。

* list-hosted-zones http://docs.aws.amazon.com/cli/latest/reference/route53/list-hosted-zones.html

逆に、VPC側の attributes を取得しても、Hosted Zone がわかりません。

* describe-vpc-attribute http://docs.aws.amazon.com/cli/latest/reference/ec2/describe-vpc-attribute.html


どうやら、Hosted Zone の詳細のところにしか、VPCの情報はないようです。

* get-hosted-zone http://docs.aws.amazon.com/cli/latest/reference/route53/get-hosted-zone.html

ということで、ちょっと面倒ですが、Hosted Zoneのリストを取得し、それぞれの Hosted Zone情報を取得した上で、自身のインスタンスが配置された VPC ID と比較することで、ようやく 登録先の Hosted Zone を判別することが出来ました。

ruby でのサンプルコードは下記のようになります。

```ruby:sample.rb
#! /usr/bin/env ruby                                                                                               
require 'aws-sdk'
require 'net/http'

base_uri = URI.parse 'http://instance-data'
instance_id = az = ""

Net::HTTP.start(base_uri.host, base_uri.port) { |http|
    instance_id = http.get('/latest/meta-data/instance-id').body
    az = http.get('/latest/meta-data/placement/availability-zone').body
}
region = az[0..-2]

ec2 = Aws::EC2::Client.new(region: region)
instance = ec2.describe_instances({instance_ids:[instance_id]}).reservations.first.instances.first

route53 = Aws::Route53::Client.new(region: region)
hosted_zones = route53.list_hosted_zones.hosted_zones
hosted_zone_id = ""

hosted_zones.each do | hosted_zone |
  hosted_zone_id = hosted_zone.id
  if (vpcs = route53.get_hosted_zone(id: hosted_zone_id).vp_cs[0])
    if (vpcs.vpc_id) && (vpcs.vpc_id == instance.vpc_id)
      break;
    end
  end
end

hosted_zone_id = hosted_zone_id.gsub("/hostedzone/","")
puts "Hosted Zone ID: " + hosted_zone_id;
```

タグから 環境名やRoleを取得する
------

上記の sample.rb に加えて、下記のように VPCのタグから環境名、インスタンスのタグから Role を取得し、インスタンスのプライベートIPアドレスをあわせれば、DNS登録に必要な情報が揃います。

```ruby:sample.rb
vpc = ec2.describe_vpcs(vpc_ids: [instance.vpc_id]).vpcs[0]
env = ""
vpc.tags.each do | tag |
  if (tag.key == 'env')
    env = tag.value
  end
end

role = ""
instance.tags.each do | tag |
  if (tag.key == 'role')
    role = tag.value
  end
end

puts "Env: " + env
puts "Role: " + role
puts "IP: " + instance.private_ip_address
```


まとめ
----

上記のような方法で、起動時に自動で Private DNSの情報や タグ名などを取得した上で、Route 53 に登録したり、シェルプロンプトを切り替えることが出来るようになります。

ついでに、踏み台サーバに fumidai.dev.example.com みたいな名前をつけておいたうえで、.ssh/config に下記みたいな定義を加えておけば、"ssh webapp-123.dev.example.com.internal" みたいな感じで、プライベートサブネットのインスタンスにログイン出来て便利です。

```:.ssh/config
Host *.dev.example.com.internal
     ProxyCommand ssh fumidai.dev.example.com nc %h 22

Host *.demo.example.com.internal
     ProxyCommand ssh fumidai.demo.example.com nc %h 22
```
いやぁ〜　便利な世の中になったもんですね！