AWS CLIで Web サイトを構築、管理、運用する(16日目)

16日目！

今回は、ついに EC2 を立ち上げます。
14日目、15日目はこのためにありました・・・！


# 16日目の要約
仮想マシンを立ち上げて、動的なコンテンツを表示できるようにするよ！

# AWS CLI の準備
このあたりをみて、好きなバージョンとお使いのOSにあった環境設定をしてくださいね。
なんなら、 AWS CloudShell で実行するのも楽でよいと思います。
この記事シリーズは、AWS CloudShell で実行し、実行例を載せています。

バージョン１
https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/install-cliv1.html

バージョン２
https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/install-cliv2.html

# 概要
最新の AMI を使って、EC2インスタンスを立ち上げます。さらに、名前解決できるようにしてCloudFrontのディストリビューションとしても登録するよ！

# さあ、やってみよう！
## 最新の Amazon Linux 2 の AMI ID を確認する
EC2 インスタンスを作成、起動するには、Amazon Machine Image(AMI)が必要です。どの AMI を使用するのか特定するために、 AMI ID を確認します。

最新の Amazon Linux 2 の AMI ID を特定するには、Simple Systems Manager のコマンドである、 ssm get-parameter を実行します。

```sh
aws ssm get-parameter --name /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2 \
```

正常に実行できると、以下のような json が返るので、 Value の値を確認します。
この値が 最新の AMI ID です。

```json
{
    "Parameter": {
        "Name": "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2",
        "Type": "String",
        "Value": "ami-0abaa5b0faf689830",
        "Version": 55,
        "LastModifiedDate": "2021-12-02T04:23:22.207000+00:00",
        "ARN": "arn:aws:ssm:ap-northeast-1::parameter/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2",
        "DataType": "text"
    }
}
```

## EC2 インスタンスを起動する！
AMI ID の特定ができたら、次は、EC2 インスタンスを起動します。
以下の情報が必要になるので、確認をしてください。

* AMI ID
* サブネット ID
* セキュリティグループ ID
* Route53 のホスティッドゾーン ID
* Route53 に登録したいレコード名(ホスト名+これまでに設定したドメイン名)
* インスタンスプロファイル名

まずは、EC2 インスタンスが起動時に実行するユーザーデータを作成します。
流れとしては、インスタンスメタデータにアクセスし、インスタンス側からパブリックIPv4アドレスとインスタンスIDを取得します。
こうすることにより、起動時に動的に情報を取得・更新、Route53 へのレコード登録ができるようになります。

以下を参考にして、お好みのテキストエディタで作成してください。

```sh:uesrdata.sh
#!/bin/bash
HOSTED_ZONE_ID=<Route53 のホスティッドゾーン ID>
RECORD_NAME=<登録したいホスト名>.<ドメイン名>

TOKEN=`curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"`
INSTANCE_ID=`curl -H "X-aws-ec2-metadata-token: $TOKEN" -v http://169.254.169.254/latest/meta-data/instance-id`
IP_ADDRESS=`curl -H "X-aws-ec2-metadata-token: $TOKEN" -v http://169.254.169.254/latest/meta-data/public-ipv4`

yum update -y
yum install -y httpd 
systemctl start httpd
systemctl enable httpd
usermod -a -G apache ec2-user
mkdir -p /var/www/html/ec2
chown -R ec2-user:apache /var/www
chmod 2775 /var/www
find /var/www -type d -exec chmod 2775 {} \;
find /var/www -type f -exec chmod 0664 {} \;
echo "<HTML><HEAD><TITLE>from EC2</TITLE><BODY>This is ${INSTANCE_ID}.</BODY></HTML>" > /var/www/html/ec2/index.html
aws route53 change-resource-record-sets --hosted-zone-id ${HOSTED_ZONE_ID} \
--change-batch "{\"Changes\": [ { \"Action\": \"UPSERT\", \"ResourceRecordSet\": { \"Name\": \"${RECORD_NAME}.\", \"Type\": \"A\", \"TTL\": 3600, \"ResourceRecords\": [{\"Value\": \"${IP_ADDRESS}\"}]}}]}"
```

ユーザーデータのファイルが作成できたら、以下を参考にして、 ec2 run-instances コマンドを実行して EC2 インスタンスを起動します。

```sh
AMI_ID=<AMI の ID>
SUBNET_ID=<サブネットのID>
SECURITY_GROUP_ID=<セキュリティグループのID>
INSTANCE_PROFILE=<インスタンスプロファイル名>

aws ec2 run-instances \
    --image-id ${AMI_ID} \
    --instance-type t2.micro \
    --subnet-id ${SUBNET_ID} \
    --iam-instance-profile "Name=${INSTANCE_PROFILE}" \
    --security-group-ids ${SECURITY_GROUP_ID} \
    --user-data file://userdata.sh
```

正常に実行・起動できると json が返りますが長いのでこの記事では割愛します。
念のため、```InstanceId``` の値を確認してください。

ちなみに、 ec2 run-instances コマンドの ```--iam-instance-profile``` オプションは ```Name``` と ```Arn``` を指定する必要があるような記載がマニュアルにあります。
しかし、実際に両方指定して実行すると、以下のようなエラーが返りますご注意ください。

```sh
An error occurred (InvalidParameterCombination) when calling the RunInstances operation: The parameter 'iamInstanceProfile.name' may not be used in combination with 'iamInstanceProfile.arn'
```

これで、EC2 インスタンスが起動しました。
起動出来たら動作確認として、 curl コマンドでアクセスしたくなりますが、セキュリティグループのインバウンドルールに入っているのは CloudFront からのIPアドレス群のみです。
そのため、動作確認はディストリビューションの設定を変えてからです。

## CloudFront のディストリビューションに EC2 をオリジンとして登録する
これまで何回も実施したディストリビューションの変更手順と同じです。
復習を兼ねてみていきましょう。

1. cloudfront get-distribution-config コマンドで現状の設定を取得する
2. ETag の値を確認する
3. 取得した json ファイルに対して、設定変更を加える
4. cloudfront update-distribution コマンドを実行して更新する

### ディストリビューションの現状の設定を確認する
cloudfront get-distribution-config コマンドを実行して、現在のディストリビューションの設定を出力します。

```sh
aws cloudfront get-distribution-config --id <CloudFront の Distribution ID> > distribution_ec2_tmp.json
```

次に、必要な部分だけ抽出して更新用の元ファイルにします。jq コマンドを使うので、必要に応じてインストールしてください。AWS CloudShell であれば標準でインストール済みです。

```sh
cat distribution_ec2_tmp.json | jq .DistributionConfig > distribution_ec2.json
```

### ETag の値を確認する
ディストリビューションの設定変更時に必要になる、 ETag の値を確認します。

```sh
cat distribution_ec2_tmp.json | grep ETag
```

以下のように出力されます。

```json
    "ETag": "**************",
```

### json ファイルを編集する
以下を編集します。

* Origins
* CacheBehaviors

#### Origins
取得してきたディストリビューションには、S3バケットをオリジンとする設定が入っていると思われます。
そのため、2つ目として、EC2 インスタンスをオリジンとして登録します。

* Quantity の値を2にします
* 以下の内容を Items のS3 バケットをオリジンとする定義の前に書き込みます。

```json
      {
        "Id": "<Route53 に登録したEC2のレコード>",
        "DomainName": "<Route53 に登録したEC2のレコード>",
        "OriginPath": "",
        "CustomHeaders": {
          "Quantity": 0
        },
        "CustomOriginConfig": {
          "HTTPPort": 80,
          "HTTPSPort": 443,
          "OriginProtocolPolicy": "http-only",
          "OriginSslProtocols": {
            "Quantity": 3,
            "Items": [
              "TLSv1",
              "TLSv1.1",
              "TLSv1.2"
            ]
          },
          "OriginReadTimeout": 30,
          "OriginKeepaliveTimeout": 5
        },
        "ConnectionAttempts": 3,
        "ConnectionTimeout": 10,
        "OriginShield": {
          "Enabled": false
        }
      },
``` 

#### CacheBehaviors
次にキャッシュビヘイビアの設定を行います。
今回は、キャッシュのTTLをゼロにします。
EC2 インスタンスのユーザーデータの内容を確認された方ならこれでピンとくるかもしれませんが、起動時にインスタンス ID を index.html に書き込んでいます。
この index.html をキャッシュに持ってしまうと、EC2 インスタンスが入れ替わった際にも TTL が切れるまで古いインスタンス ID の情報が返ってしまいます。
それを防ぐために、敢えてゼロとしました。
ちなみにデフォルトの TTL は86,400秒、つまり、24時間です(3,600 秒 * 24時間)。最大TTLは、31,536,000秒なので1年が設定されています。

* 以下の内容を CacheBehaviors の定義を編集します。

```json:編集前
  "CacheBehaviors": {
     "Quantity": 0
   },
```

```json:編集後
  "CacheBehaviors": {
    "Quantity": 1,
    "Items": [
      {
        "PathPattern": "ec2/*",
        "TargetOriginId": "<Route53 に登録したEC2のレコード>",
        "TrustedSigners": {
          "Enabled": false,
          "Quantity": 0
        },
        "TrustedKeyGroups": {
          "Enabled": false,
          "Quantity": 0
        },
        "ViewerProtocolPolicy": "allow-all",
        "AllowedMethods": {
          "Quantity": 2,
          "Items": [
            "HEAD",
            "GET"
          ],
          "CachedMethods": {
            "Quantity": 2,
            "Items": [
              "HEAD",
              "GET"
            ]
          }
        },
        "SmoothStreaming": false,
        "Compress": true,
        "LambdaFunctionAssociations": {
          "Quantity": 0
        },
        "FunctionAssociations": {
          "Quantity": 0
        },
        "FieldLevelEncryptionId": "",
        "ForwardedValues": {
          "QueryString": false,
          "Cookies": {
            "Forward": "none"
          },
          "Headers": {
            "Quantity": 0
          },
          "QueryStringCacheKeys": {
            "Quantity": 0
          }
        },
        "MinTTL": 0,
        "DefaultTTL": 0,
        "MaxTTL": 0
      }
    ]
  },
```

#### 仕上げ
以下のように頭と末尾に情報を入力します。
1行目に波括弧({)があるので、その2行目に対して、 “DistributionConfig”: { を追加します。

```json:distributionconfig.json_頭
{
"DistributionConfig": {
  "CallerReference": 

（省略）
```

次に末尾です。

```json:distributionconfig.json_末尾
(省略)
  "WebACLId": "",
  "HttpVersion": "http2",
  "IsIPV6Enabled": true
 }
}   <-- これを追加する
```
末尾に閉じ波括弧(})を追加します。


### ディストリビューションの設定を更新する
更新用のファイルが準備できたら、cloudfront update-distribution コマンドを実行して、設定変更を行います。

```sh
aws cloudfront update-distribution --id <CloudFront Distribution ID> --cli-input-json file://distribution_ec2.json --if-match 確認したETag の値
```

コマンド実行に成功すると、Distribution の内容が記された json が返されます。
案の定、長いので割愛します💦


## 動作確認
設定変更ができたので、以下の様に curl を実行してみましょう。

```sh
curl https://<CloudFrontのドメイン名>/ec2/index.html
```

すると、以下の様に インスタンスID が入った HTML が返ってきます。
返ってこない場合は、WAF のアノニマスIPアドレスのルールが当たっている可能性があります。
その場合はルールを一時的に解除するか、ローカルのターミナルなどでご確認ください。

```HTML
<HTML><HEAD><TITLE>from EC2</TITLE><BODY>This is (インスタンスID).</BODY></HTML>
```

# まとめ
準備編が2回あり、ついに EC2 インスタンスが起動しました！
さて、明日は、現在1台の EC2 インスタンスの可用性を挙げるべく、冗長構成にしていきます。

お楽しみに♪