aws 起動毎にローカル IP をローカル DNS (Route53) の A レコードに突っ込む 

# aws でローカル IP を個別設定しない代わりに名前で返します

## 概要

ローカル DNS を作っておいて、作ったローカル DNS に aws の API でインスタンスが起動する度にインスタンス自身のローカル IP で A レコードを追加（すでにあるときは更新）します。
[cloud-initを使ってRoute53をPrivate Dynamic DNSにする](https://dev.classmethod.jp/cloud/aws/route53-private-dynamic-dns-use-cloudinit/) が動かなかったので完全に動くようにしてます。

ローカルで使うからドメインは何でもよくて以下では次のようにします:

* ドメイン: `example.com`
* インスタンスのローカル IP: `192.168.11.10`
* インスタンスの FQDN: `aws.example.com`

期待する動きは:

```shell-session
$ nslookup aws.example.com
Server:         192.168.11.2
Address:        192.168.11.2#53

Non-authoritative answer:
Name:   aws.example.com
Address: 192.168.11.10
```

## ローカル DNS を構築します

まず aws の API でごちゃごちゃする前に、aws の GUI で、思ったようにローカル DNS が牽けるまで設定しましょう。ローカル DNS の設定は、以下参照すればできます:

* [Route 53でローカルDNSを構築する](https://qiita.com/keitakn/items/4dbf03280868891f551b)
* [VPC での DNS の使用](https://docs.aws.amazon.com/ja_jp/AmazonVPC/latest/UserGuide/vpc-dns.html)

## ローカル DNS に aws-cli から A レコードを追加してみます

### aws-cli が使えるようにします

コマンドラインからまだ aws コマンドを使ったことなかったら、 `aws configure` で aws コマンドの権限をどの IAM ユーザーで動かすのかを設定します。以下参照してください:

* [AWS CLI の設定](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-chap-getting-started.html) 

動かす IAM ユーザーにおいて、Route53 が操作できる権限を与えておきます。次のようなサイトから、ヒントを得ます:

* [Amazon Route 53 で ID ベースのポリシー (IAM ポリシー) を使用する](https://docs.aws.amazon.com/ja_jp/Route53/latest/DeveloperGuide/access-control-managing-permissions.html) 

### A レコードを aws-cli で追加してみます

次のような json を作成します。 `"Name": "aws.example.com"` については、ご自身で予定している FQDN に書き換えてください:

```json:/root/route53update.json
{
  "Comment": "This is a template to register the local IP in the local Route53. Do not remove.",
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "aws.example.com",
        "Type": "A",
        "TTL" : 3600 ,
        "ResourceRecords": [
         {
           "Value": "192.168.11.10"
         }
        ]
      }
    }
  ]
}
```

aws-cli で `route53update.json` を実行します。A レコードが作成されます（すでに A レコードがあれば更新されます）。`Z2X8T5RUOJCD9A` については、ご自身のものを使用します（Route53 の GUI からとれます: 下図赤枠）:
![route53.png](https://qiita-image-store.s3.amazonaws.com/0/209900/0ee4b218-defc-c2ce-103f-5c0ac1d4243e.png)

```shell-session
# aws route53 change-resource-record-sets --hosted-zone-id Z2X8T5RUOJCD9A --change-batch file:////root/route53update.json
{
    "ChangeInfo": {
        "Status": "PENDING",
        "Comment": "This is a template to register the local IP in the local Route53. Do not remove.",
        "SubmittedAt": "2018-07-25T05:58:37.993Z",
        "Id": "/change/C3P3BX1799EVQT"
    }
}
```

`"Status": "PENDING"` で成功しています、30 秒もまてば更新は終わっているでしょう。もう少し詳しく知りたければ: [Route53のAレコードをawscliで更新する](https://qiita.com/ryo0301/items/82bd4fab7f2faefe7ce7)

確認します。コマンドラインでもいいですし、Route53 の GUI でもいいです:

```shell-session
$ nslookup aws.example.com
Server:         192.168.11.2
Address:        192.168.11.2#53

Non-authoritative answer:
Name:   aws.example.com
Address: 192.168.11.10
```
確認が終わったら、追加した A レコードを Route53 GUI で消去しておきます。

## インスタンス起動毎にローカル IP で A レコードを更新するよう設定

下記、 `cloud-config` をインスタンスのユーザーデータに貼り付けます:

* `aws.example.com` については、ご自身で予定している FQDN に書き換えてください
* `Z2X8T5RUOJCD9A` についても、上記を参照しご自身のものに書き換えてください
* `192.168.11.1` は、sed で起動毎に書き換えますので、そのままで
* 終わったらインスタンスを起動し、`aws.example.com` がインスタンスに割り当てられているローカル IP で登録されているのか確認します
 
```yaml:cloud-config
#cloud-config
cloud_final_modules:
 - [scripts-user, always]
write_files:
 - content: |
      {
        "Comment": "This template is to register the local IP in the local Route53. Do not remove.",
        "Changes": [
          {
            "Action": "UPSERT",
            "ResourceRecordSet": {
              "Name": "aws.example.com",
              "Type": "A",
              "TTL" : 3600 ,
              "ResourceRecords": [
               {
                 "Value": "192.168.11.1"
               }
              ]
            }
          }
        ]
      }
   owner: root:root
   path: /root/route53update.json
   permissions: '0644'

runcmd:
 - [sh, -c, 'sed -i -e "s@\"Value\": \"\(\(1\?[0-9][0-9]\?\|2[0-4][0-9]\|25[0-5]\)\.\)\{3\}\(1\?[0-9][0-9]\?\|2[0-4][0-9]\|25[0-5]\)\"@\"Value\": \"`curl http://169.254.169.254/latest/meta-data/local-ipv4`\"@" /root/route53update.json']
 - aws route53 change-resource-record-sets --hosted-zone-id Z2X8T5RUOJCD9A --change-batch file:////root/route53update.json
```
* 正規表現は [sed - How to extract IP address using sed?](https://stackoverflow.com/questions/14928573/sed-how-to-extract-ip-address-using-sed) からパクッてます。

### ユーザーデータの入れ方を説明しときます

1. ec2 の管理画面で起動する度にローカル DNS に登録したいインスタンスを選択しときます
1. インスタンスを停止します（停止しないと 3. のメニューが有効になりません）
1. アクション → インスタンスの設定 → ユーザーデータの表示／変更
![route53-2.png](https://qiita-image-store.s3.amazonaws.com/0/209900/4540e5bc-641f-3b00-3a2e-89a67eabcd79.png)
1. 次の画面が出てくるので、狭いフォームに `cloud-config` をコピペします。先頭行の `#cloud-config` は意味がありますので、削らないでください。
![route53-3.png](https://qiita-image-store.s3.amazonaws.com/0/209900/74c1780b-f486-257c-c771-fa1a6f83dee4.png)
1. 保存してください

### 動作について

* 実行時のログは `/var/log/cloud-init.log`
 * 参考: [AmazonLinuxのcloud-initについての調査メモ](https://qiita.com/toshihirock/items/81d6612511f0d1f5db77)
* `write_files` は最初のインスタンス立ち上げのみ動作します
 * 一度目（Writing to /root/route53update.json）

    ```apache:/var/log/cloud-init.log
Jul 26 02:28:54 cloud-init[2378]: helpers.py[DEBUG]: Running config-write-files using lock (<FileLock using file '/var/lib/cloud/instances/i-0f7cd489327a98760/sem/config_write_files'>)
Jul 26 02:28:54 cloud-init[2378]: util.py[DEBUG]: Writing to /root/route53update.json - wb: [644] 371 bytes
Jul 26 02:28:54 cloud-init[2378]: util.py[DEBUG]: Changing the ownership of /root/route53update.json to 0:0
```
 * 二度目以降（config-write-files already ran）

    ```apache:/var/log/cloud-init.log
Jul 26 02:33:59 cloud-init[2372]: stages.py[DEBUG]: Running module write-files (<module 'cloudinit.config.cc_write_files' from '/usr/lib/python2.7/dist-packages/cloudinit/config/cc_write_files.pyc'>) with freque
ncy once-per-instance
Jul 26 02:33:59 cloud-init[2372]: helpers.py[DEBUG]: config-write-files already ran (freq=once-per-instance)
```
 * `/root/route53update.json` を消去しても **次回のインスタンス起動時に復活はしません**
* runcmd は `/var/lib/cloud/instance/scripts/runcmd` に展開される
  * こちらは起動毎に動作します

    ```apache:/var/log/cloud-init.log
Jul 26 02:36:54 cloud-init[2632]: helpers.py[DEBUG]: Running config-scripts-user using lock (<cloudinit.helpers.DummyLock object at 0x7f735b905390>)
Jul 26 02:36:54 cloud-init[2632]: util.py[DEBUG]: Running command ['/var/lib/cloud/instance/scripts/runcmd'] with allowed return codes [0] (shell=True, capture=False)
Jul 26 02:36:57 cloud-init[2632]: cloud-init[DEBUG]: Ran 1 modules with 0 failures
```
  * runcmd も **デフォルトでは一回こっきり** です。起動毎動作にするための記述を追加しています:

    ```yaml:cloud-config
cloud_final_modules:
 - [scripts-user, always]
```
  * 参考: [
How do I make cloud-init startup scripts run every time my EC2 instance boots?](https://stackoverflow.com/questions/6475374/how-do-i-make-cloud-init-startup-scripts-run-every-time-my-ec2-instance-boots)

### cloud-config を何度も動かす方法

[cloud-init のスクリプトを reboot せずに再実行するには](https://qiita.com/sonots/items/e60261ea28c61e322631) で可能です:

```shell-session
# rm -rf /var/lib/cloud/* &&   cloud-init --debug init --local &&   cloud-init --debug init 2>&1
```

実際に再起動してもう一度実行（実験）を行うには次のようにします（ `write_files` も動作します）:

```shell-session
# rm -rf /var/lib/cloud/*
# reboot
```

### `write-files` と `runcmd` の実行順序は?

`/var/log/cloud-init.log` で常に `write-files` が先らしいということがわかりますが調べ方は次を参照するとよいでしょう:

* [How do I make cloud-init startup scripts run every time my EC2 instance boots?](https://stackoverflow.com/questions/6475374/how-do-i-make-cloud-init-startup-scripts-run-every-time-my-ec2-instance-boots)
* [AmazonLinuxのcloud-initについての調査メモ](https://qiita.com/toshihirock/items/81d6612511f0d1f5db77)