Route53 と roadworker を使って VPC 環境の内部DNSを構築する

## 概要

AWS の VPC 環境の内部DNSに Route53 を利用するように設定して、Codenize.tools の roadworker を使って EC2 インスタンスの tag.Name で ssh したりできるようにしてみたのでその時の環境構築の時のメモです

## 構築環境

- Amazon Linux AMI release 2017.09
- ruby 2.3.5p376 (2017-09-14 revision 59905) [x86_64-linux-gnu]
- roadwork 0.5.9.beta5

## Route53 で VPC の内部DNSを構築

参考: https://cloudpack.media/9573

### VPC でプライベートホストゾーンを有効化

http://docs.aws.amazon.com/ja_jp/Route53/latest/DeveloperGuide/hosted-zones-private.html

```
aws ec2 modify-vpc-attribute \
  --vpc-id vpc-XXXXXXXX \
  --enable-dns-hostnames '{"Value":true}'

aws ec2 modify-vpc-attribute \
  --vpc-id vpc-XXXXXXXX \
  --enable-dns-support '{"Value":true}'
```

### Route53 でプライベートホストゾーンを作成

```
aws route53 create-hosted-zone \
  --name my-sample-private-hosted-zone.internal \
  --vpc 'VPCRegion="ap-northeast-1",VPCId="vpc-XXXXXXXX"' \
  --caller-reference 2017-12-14-00:00 \
  --hosted-zone-config 'PrivateZone="true"'
```

### DHCP オプションを作成して VPC に関連付け

```
aws ec2 create-dhcp-options --dhcp-configuration '[
  {
    "Key": "domain-name-servers",
    "Values": ["AmazonProvidedDNS"]
  },
  {
    "Key": "domain-name",
    "Values": ["my-sample-private-hosted-zone.internal"]
  }
]'
aws ec2 associate-dhcp-options --dhcp-options-id dopt-XXXXXXXX --vpc-id vpc-XXXXXXXX # --dhcp-options-id は aws ec2 create-dhcp-options の返り値の "DhcpOptionsId" の値
```

## roadworker で EC2 インスタンスの tag.Name を自動で Route53 のレコードに追加できるようにする

試してみたら結構便利だったので [Codenize.tools](https://codenize.tools/) の [roadworker](https://github.com/codenize-tools/roadworker) を使って EC2 インスタンスの tag.Name を自動で Route53 のレコードに追加するようにしてみました

### roadworker 用 EC2 インスタンスを作成

```
aws ec2 run-instances \
  --image-id ami-da9e2cbc \
  --security-group-ids sg-XXXXXX \
  --subnet-id subnet-XXXXXX \
  --count 1 \
  --instance-type m4.medium \
  --key-name sample-keyname
```

(事前に上記のインスタンスに ssh できるようにしておく必要があります)


### 対象インスタンスへ Route53 と EC2 のアクセス権を付与する IAM ポリシーを付与

```
aws iam create-role --role-name roadworker --assume-role-policy-document '{
  "Version": "2012-10-17",
  "Statement": {
    "Effect": "Allow",
    "Principal": {"Service": "ec2.amazonaws.com"},
    "Action": "sts:AssumeRole"
  }
}'
{
    "Role": {
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": {
                "Action": "sts:AssumeRole",
                "Effect": "Allow",
                "Principal": {
                    "Service": "ec2.amazonaws.com"
                }
            }
        },
        "RoleId": "XXXXXXXXXXXXXXXXXXXX",
        "CreateDate": "2017-12-14T00:00:00.000Z",
        "RoleName": "roadworker",
        "Path": "/",
        "Arn": "arn:aws:iam::XXXXXXXXXXXX:role/roadworker"
    }
}

aws iam attach-role-policy --policy-arn arn:aws:iam::aws:policy/AmazonRoute53FullAccess --role-name roadworker
aws iam attach-role-policy --policy-arn arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess --role-name roadworker
aws iam create-instance-profile --instance-profile-name roadworker
aws iam add-role-to-instance-profile --role-name roadworker --instance-profile-name roadworker
aws ec2 associate-iam-instance-profile --instance-id i-XXXXXXXXXXXXXXXXX --iam-instance-profile Name="roadworker"
```

--instance-id i-XXXXXXXXXXXXXXXXX は作成したインスタンスの ID となります

### ruby バージョンアップ & 関連 yum install

サーバに ssh して

```
$ sudo su -
$ yum install -y ruby23-devel gcc libpcap-devel
$ alternatives --config ruby

There are 2 programs which provide 'ruby'.

  Selection    Command
-----------------------------------------------
*+ 1           /usr/bin/ruby2.0
   2           /usr/bin/ruby2.3

Enter to keep the current selection[+], or type selection number: 2

$ gem install bundler
$ bundle exec ruby -v
ruby 2.3.5p376 (2017-09-14 revision 59905) [x86_64-linux-gnu]
```

### roadworker インストール

```
$ mkdir /usr/local/roadworker
$ cd /usr/local/roadworker
$ bundle init
$ echo "gem 'roadworker', '0.5.9.beta5'" >> Gemfile
$ echo "gem 'json'" >> Gemfile # https://github.com/tdiary/tdiary-core/issues/510 の問題が発生するため
$ bundle install
$ vi Routefile # ↓の内容で Routefile を書く
```

`Routefile`

```rb
require 'aws-sdk'
region = "ap-northeast-1"
vpc_id = "vpc-XXXXXXXX"
domain = "my-sample-private-hosted-zone.internal."

hosted_zone "my-sample-private-hosted-zone.internal." do
  vpc region, vpc_id

  resp = Aws::EC2::Client.new(region: region).describe_instances(filters:[{ name: 'vpc-id', values: [vpc_id] }])
  instances = resp.reservations.each_with_object({}) do |reservation, reservations|
    reservations.merge!(reservation.instances.each_with_object({}) do |instance, instances|
      instances[instance.private_ip_address] = instance.tags.find {|tag| tag['key'] == 'Name' }['value']
    end)
  end

  instances.each {|private_ip_address, tagName|
    rrset "#{tagName}.#{domain}", "A" do
      ttl 60
      resource_records private_ip_address
    end
  }
end
```

作成した Routefile を適用

```
$ bundle exec roadwork -a --dry-run --target-zone 'my-sample-private-hosted-zone\.internal' # dry-run
$ bundle exec roadwork -a --target-zone 'my-sample-private-hosted-zone\.internal'
```


### EC2 の tag.Name の値で対象サーバにアクセスできることを確認

数分待ってから DNS が引けることを確認
(↓は roadworker 用 EC2 インスタンスの tag.Name を roadworker-exec-server としていた場合)

```
$ roadworker-exec-server.my-sample-private-hosted-zone.internal | grep -A 1 'ANSWER SECTION'
;; ANSWER SECTION:
roadworker-exec-server.my-sample-private-hosted-zone.internal. 55 IN A 10.0.0.60
```

```
$ ssh roadworker-exec-server
Last login: Thu Dec 14 00:00:00 2017 from XXX.XXX.XXX.XXX

       __|  __|_  )
       _|  (     /   Amazon Linux AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-ami/2017.09-release-notes/
[ec2-user@ip-10-0-0-60 ~]$
```

ssh できるところまで確認できたので、あとは roadwork を cron とかで定期的にで実行するようにしておけばインスタンスが追加されても tag.Name を使って ssh とかできるようになりそうです

注意点としては今回作成した Routefile だと tag.Name が重複する場合は IP が上書きされたりと行ったことがあるので tag.Name で ssh したいインスタンスの tag.Name は重複しないようにする必要があります

## 感想

環境構築で微妙にハマるところがあったけれど Codenize.tools(roadworker) 便利でした