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 にホスト名を自動登録する
このあたりについては、すでにいろいろな方が書かれているので、下記などを参考にすれば出来るかと思います。
ただ、上記だと、停止時にDNS登録を消してくれないので、どんどん登録が増えてしまいます。下記などを参考に、停止時に自動で DNS登録を削除することも忘れずに...
下記みたいな感じのものを /etc/init.d/dns-ctrl.sh として配置する感じですね。
で、問題は、/var/lib/instance_config で、環境変数を用意してあげるところです。
#!/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に紐づいているのかわからないのです。名前で判断しようにも、同じ 内部ドメイン名で作っているので判別しようがありません。
逆に、VPC側の attributes を取得しても、Hosted Zone がわかりません。
- describe-vpc-attribute http://docs.aws.amazon.com/cli/latest/reference/ec2/describe-vpc-attribute.html
どうやら、Hosted Zone の詳細のところにしか、VPCの情報はないようです。
ということで、ちょっと面倒ですが、Hosted Zoneのリストを取得し、それぞれの Hosted Zone情報を取得した上で、自身のインスタンスが配置された VPC ID と比較することで、ようやく 登録先の Hosted Zone を判別することが出来ました。
ruby でのサンプルコードは下記のようになります。
#! /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登録に必要な情報が揃います。
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" みたいな感じで、プライベートサブネットのインスタンスにログイン出来て便利です。
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
いやぁ〜 便利な世の中になったもんですね!