EIPを持たずにホスト名でアクセスする

# はじめに
AWSの`EC2インスタンス`は、`Elastic IP`(EIP)を付与しなければ、インスタンスの停止＆起動でIPアドレスが変わってしまいます。

また、`EIP`アドレスはクォータ引き上げ申請無しで各リージョン5つまでなので、無計画に使っているとあっという間に使い切ってしまいます。

`EIP`を使用しない場合、停止起動を行う際は、起動後にアクセスする際のアドレスを確認したり手間ですが、EC2インスタンス起動時に`Route53`の登録情報を更新しにいくようにすることで固定の`EIP`を持たなくてもアクセスできるようにします。

# 作業の流れ
Amazon Linux2上に登録する前提で以下の様な方法で進めていきます。

尚、ドメイン名でアクセスする対象となるEC2インスタンスは事前に作成してある前提とします。

1. ドメインの取得
2. Route53にドメインを登録
3. Route53のホストゾーン作成
4. Route53更新用IAMロール作成 
5. EC2インスタンスへのIAMロールのアタッチ
6. EC2インスタンスへのシェル追加
7. Route53更新シェルのsystemd登録
8. EC2インスタンスを停止起動して動作確認

上記1～5はRoute53やIAMポリシー作成権限がある管理用のIAMユーザ等で実施、6～8は実際にドメイン名でアクセスする対象のEC2インスタンスで実施してください。

# ドメインの取得
IPが変わってしまってもアクセスできるように`ドメイン`を取得する必要があります。

`Route53`でドメインを取得しても良いですし、[お名前.com](https://www.onamae.com/)等で取得しても良いです。

今回は`Route53`で独自ドメインを取得する方法を記載します。

## Route53での独自ドメイン取得
`Route53`の画面より、**登録済みドメイン**→**ドメインの登録**から独自ドメインの取得が可能です。

本画面から登録するのはドメイン自体の登録となるため、EC2インスタンスに割り当てるホスト名を**www.example.com**という名前にする場合、ドメイン名は**example.com**で取得してください。

![eip01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/93d7d73a-af6a-f278-1673-2b41da39e441.jpeg)

### ドメイン取得可否確認
**ドメイン名の選択**画面より、お好きなドメイン名を入力し、`チェック`をクリックすることで、取得可能なドメインの候補が表示されるので、取得したいドメイン名を指定して`カートに入れる`→画面下の`続行`で次に進みます。

![eip02.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/c0401727-d77d-6365-9b53-d83834b8060b.jpeg)

### 連絡先の入力
ドメイン管理者の情報を入力し、`続行`で進みます。

![eip03.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/0720ec7b-155a-fc27-e89b-49fc071415d0.jpeg)

`ドメインのお問い合わせ詳細`で入力したメールアドレスに検証のためのメールが送付されるため、URLをクリックして検証を完了させておきましょう。

入力した連絡先の確認やドメインの自動更新有無、規約等のチェックを行い、`注文を完了`をクリックすることでドメイン取得が完了します。

取得完了までしばらく時間がかかるので、焦らず待ちましょう。

![eip04.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/d2173ab6-9d59-3f49-fcf2-a2856b3f50f8.jpeg)

尚、`注文を完了`させた時点で**費用が発生する**ので、注意しましょう。

## Route53での独自ドメイン取得（aws cliで実施した場合）
`aws cli`でのドメイン取得は試していないので、違っているかもしれませんが、今後の自分メモとして残しておきます。

### ドメイン取得可否確認
ドメインが取得可能かを確認。`Route53`はグローバルのサービスのため、リージョンの指定は`us-east-1`としましょう。

取得可能の場合は`AVAILABLE`、取得不可の場合は`UNAVAILABLE`と表示されるので、`UNAVAILABLE`と表示された場合は別のドメイン名にして繰り返しチェックしましょう。

```shell:ドメイン取得確認
aws route53domains check-domain-availability --region us-east-1 --domain-name [ドメイン名]
```

### 連絡先の入力
画面での入力のように対話形式で行えないため、予め**json形式**で連絡先を作成します。

書式はRoute 53 API Referenceの[RegisterDomain](https://docs.aws.amazon.com/ja_jp/Route53/latest/APIReference/API_domains_RegisterDomain.html)の`Request Syntax`に記載があります。

なお、`Request Syntax`に書かれているすべての項目に対して記載する必要はなく、最低限`Request Parameters`で`Required`と書かれている項目のみ記載すればOKです。

以下GUIで入力した項目と同等の**json形式**ファイル。

```json:連絡先jsonファイルの作成
{
   "AdminContact": { 
      "AddressLine1": "string",
      "AddressLine2": "string",
      "City": "string",
      "ContactType": "string",
      "CountryCode": "string",
      "Email": "string",
      "FirstName": "string",
      "LastName": "string",
      "OrganizationName": "string",
      "PhoneNumber": "string",
      "State": "string",
      "ZipCode": "string"
   },
   "AutoRenew": boolean,
   "DomainName": "string",
   "DurationInYears": number,
}
```

# Route53のホストゾーン作成
`Route53`で正引きできるようにするため、先ほど取得したドメイン名で**ホストゾーンの作成**を行います。

## ホストゾーンの作成
先ほど取得したドメイン名でホストゾーンを作成します。

尚、作成のために`caller-reference`の設定が必須となるため、以下の様に現在時刻を設定、また、後ほど必要となるため、`HostedZoneId`を控えておきます。

```shell:ホストゾーンの作成
aws route53 create-hosted-zone --name [ドメイン名] --caller-reference `date +%Y-%m-%d_%H-%M-%S` --hosted-zone-config Comment="[コメント]"
```

# Route53更新用IAMロール作成
`EC2インスタンス`から`Route53`の情報を更新する必要があるので、`Route53`の更新権限を持つ`IAMロール`を作成します。

`インスタンスプロファイル`、`IAMロール`の詳細は以下を参照してください。

「[インスタンスプロファイルを理解するついでにポリシーとロールを整理する。](https://qiita.com/sakai00kou/items/a4b96dcfa6bb3e656cd9)」

なお、`IAMロール`を割り当てる`EC2インスタンス`ではIAMポリシー、IAMロール作成ができないので、CLIで実施する場合は、作成できる権限を持つユーザで行って下さい。

## 前準備
IAMロール作成に必要な構成を以下に記載します。

|構成|設定|
|:--|:--|
|IAMポリシー名|Route53UpdatePolicy|
|IAMロール名|Route53UpdateRole|
|インスタンスプロファイル名|Route53UpdateRole|
|IAMポリシーファイルパス|/home/ec2-user/r53policy.json|
|Assumeロールファイルパス|/home/ec2-user/assume.json|

前回の「[インスタンスプロファイルを理解するついでにポリシーとロールを整理する。](https://qiita.com/sakai00kou/items/a4b96dcfa6bb3e656cd9)」の記事と異なり、`IAMロール名`と`インスタンスプロファイル名`が同じになっていますが、前回の記事でも記載している通り、**IAMロール名とインスタンスプロファイル名を同じにしないとAWSコンソールからインスタンスプロファイルが削除できない**ため、今回は同じにしています。

## IAMポリシーの作成
EC2インスタンスから自分のホスト名が登録されているRoute53のホストゾーンに対してのみ変更権限を持つIAMポリシーファイルを作成します。

`[ホストゾーンID]`の部分は先ほど作成したホストゾーンのIDを記載して下さい。

```json:IAMポリシーファイル
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "route53:ChangeResourceRecordSets",
            "Resource": "arn:aws:route53:::hostedzone/[ホストゾーンID]"
        }
    ]
}
```

作成したIAMポリシーファイルからIAMポリシーを作成します。
`create-policy`実行後に表示される`Arn`は控えて下さい。

```shell:IAMポリシーの作成
aws iam create-policy --policy-name Route53UpdatePolicy --policy-document file:///home/ec2-user/r53policy.json
```

## IAMロールの作成
`Assumeロール`は[前回](https://qiita.com/sakai00kou/items/a4b96dcfa6bb3e656cd9)と同じデフォルト設定で作成します。

```json:IAMロールファイル
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": "sts:AssumeRole",
            "Effect": "Allow",
            "Principal": {
                "Service": "ec2.amazonaws.com"
            }
        }
    ]
}
```

```shell:IAMロールの作成
aws iam create-role --role-name Route53UpdateRole --assume-role-policy-document file:///home/ec2-user/assume.json
```

## IAMロールとIAMポリシーの紐付け
先ほど控えた`Arn`を使ってIAMロールとIAMポリシーを紐付けます。

```shell:IAMロールとIAMポリシーの紐付け
aws iam attach-role-policy --role-name Route53UpdateRole --policy-arn arn:aws:iam::xxxxxxxxxxxx:policy/Route53UpdatePolicy
```

## インスタンスプロファイルの作成とIAMロールの紐付け
EC2インスタンスとIAMロールを紐付けるための`インスタンスプロファイル`を作成します。

```shell:インスタンスプロファイルの作成
aws iam create-instance-profile --instance-profile-name Route53UpdateRole
```

```shell:インスタンスプロファイルとIAMロールの紐付け
aws iam add-role-to-instance-profile --instance-profile-name Route53UpdateRole --role-name Route53UpdateRole
```

## インスタンスプロファイルとEC2インスタンスの紐付け
**起動時にRoute53の更新を行う対象となるEC2インスタンス**に今まで作成してきたIAMポリシーを紐付けます。

EC2インスタンスIDは以下コマンドやAWSコンソールから確認しておいて下さい。

```shell:EC2インスタンスIDの確認
aws ec2 describe-instances
```

```shell:インスタンスプロファイルとIAMロールの紐付け
aws ec2 associate-iam-instance-profile --instance-id [インスタンスID] --iam-instance-profile Name=Route53UpdateRole
```

# EC2インスタンスへのシェル追加
ここからは実際にホスト名でアクセスするEC2インスタンスで作業します。

最終的に`systemd`に登録をして起動時に呼び出すようにするため、以下のパスに各ファイルを作成します。

|ファイル名|パス|
|:--|:--|
|Route53更新シェル|/usr/local/sbin/update_route53.sh|
|変数ファイル|/etc/sysconfig/update_r53|

以下作業はsudo等でroot権限になってから実施します。

## Route53更新シェルの作成
Route53更新シェルを`/usr/local/sbin/update_route53.sh`に作成します。

```shell:/usr/local/sbin/update_route53.sh
#!/bin/bash
# 環境変数設定
BATCH_FILE="r53.json"
PUBLIC_IP=$(curl -s http://169.254.169.254/latest/meta-data/public-ipv4)

# Route53更新用バッチファイル生成
cat << _EOF_ > /tmp/${BATCH_FILE}
{
  "Comment" : "",
  "Changes" : [
    {
      "Action" : "UPSERT",
      "ResourceRecordSet" : {
        "Name" : "${HOST_NAME}",
        "Type" : "A",
        "TTL" : ${TTL},
        "ResourceRecords" : [
          {
            "Value": "${PUBLIC_IP}"
          }
        ]
      }
    }
  ]
}
_EOF_

# Route53更新、後片付け
aws route53 change-resource-record-sets --hosted-zone-id ${HOSTED_ZONE_ID} --change-batch file:///tmp/r53.json
rm -f /tmp/${BATCH_FILE}
```

実行権限などの設定も行っておきます。

```shell:実行権限の付与
sudo chmod 755 /usr/local/sbin/update_route53.sh
```

## 変数ファイルの作成
先ほど作成した**Route53更新用バッチファイル**実行時に読み込む変数ファイルを作成します。

自身の環境に合わせて記載するようにして下さい。

|変数名|説明|
|:--|:--|
|HOSTED_ZONE_ID|更新対象のドメインを登録しているRoute53のHosted Zone ID|
|HOST_NAME|更新対象のホスト名|
|TTL|Time To Live値（今回は300秒に設定）|

```shell:/etc/sysconfig/update_r53
# Zone情報
HOSTED_ZONE_ID="xxxxxxxxxxxxxx"
HOST_NAME="www.example.com"
TTL="300"
```

# Route53更新シェルのsystemd登録
**EC2インスタンス**起動時に作成した**Route53更新シェル**を実行するようにするため、`systemd`に登録します。

|ファイル名|パス|
|:--|:--|
|systemd用ファイル|/usr/lib/systemd/system/update_r53.service|

## systemd用ファイル作成
以下の様に`systemd`用ファイルを作成します。

パスを変えているようであれば、先ほど作成した**Route53更新シェル**、**変数ファイル**のパスを自身の環境に合わせて記載してください。

```systemd:/usr/lib/systemd/system/update_r53.service
[Unit]
Description=Route53 update service
After=network-online.target

[Service]
Restart=no
Type=oneshot
RemainAfterExit=yes
EnvironmentFile=/etc/sysconfig/update_r53
ExecStart=/usr/local/sbin/update_route53.sh

[Install]
WantedBy=network-online.target
```

## 自動起動の設定
起動時に`update_r53.service`が実行されるように自動起動設定を行います。

```shell:自動起動の設定
systemctl enable update_r53.service
```

```shell:自動起動の確認
systemctl is-enabled update_r53.service
```

## 更新確認
設定が完了したら登録した`update_r53.service`を起動してAレコードが登録されるか確認してみます。

```shell:update_r53.serviceの起動
systemctl start update_r53.service
```

以下は先ほどIAMロール等を作成したユーザで実施して、Aレコードが登録されているかを確認します。
Aレコードが存在しない場合はどこかで失敗しているので権限等を確認しましょう。

```shell:Aレコードの確認
aws route53 list-resource-record-sets --hosted-zone-id [HOSTED_ZONE_ID]
```

Aレコードが登録されていれば設定が問題なくできているため、登録したホスト名でアクセスしてログインできれば終了です。

# おわりに
今回の場合、ドメイン名の取得には多少費用が掛かりますが、一度取得すればEC2インスタンスが増えたとしてもAレコードを増やすだけで済みますし、固定の`EIP`は複数人のテスト環境で使用しているとすぐに上限に達してしまうため、`EIP`を節約するためにも有用かと思います。