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

19日目！

18 日目に Auto Scaling をいれ、何かトラブルがあっても自動で再起動してサービスを継続できるようにしました。
しかし、トラブル発生時の原因究明や対策立案をするのに必要なログの採取や該当インスタンスへのアクセスができるようにはなっていません。

今回はそんな、運用作業でよくある内容について設定していきます。

# 19日目の要約
リモートアクセスできるようにするよ！

# 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

# 概要
IAM ロール と VPC エンドポイントを作って、セッションマネージャーでリモートアクセスできるようにします

# さあ、やってみよう！
## IAM ロールを作るために必要な許可を確認する
15日目にご紹介したとおりです。今回は以下のサービスに対するアクション許可を与えます。

* Systems Manager(ssm)
 * UpdateInstanceInformation
 * CreateControlChannel
* SSM Messages
 * CreateDataChannel
 * OpenControlChannel
* Simple Storage Service(S3)
 * GetEncryptionConfiguration   
 * GetObject
* CloudWatch Logs
 * CreateLogGroup
 * CreateLogStream
 * PutLogEvents
 * DescribeLogStreams 

・・・一気に増えましたね💦

## IAM ポリシーを作成する
確認したアクション許可を用いて、ポリシードキュメントを用意し、```iam create-policy``` コマンドで IAM ポリシーを作っていきます。

```sh
aws iam create-policy --policy-name pol-adcale2 --policy-document \
'{"Version": "2012-10-17","Statement": [{"Effect": "Allow","Action": ["ssm:UpdateInstanceInformation","ssmmessages:CreateControlChannel","ssmmessages:CreateDataChannel","ssmmessages:OpenControlChannel","ssmmessages:OpenDataChannel"],"Resource": "*"},{"Effect": "Allow","Action": ["s3:GetEncryptionConfiguration","s3:GetObject"],"Resource": "*"},{"Effect": "Allow","Action": ["logs:CreateLogGroup","logs:CreateLogStream","logs:PutLogEvents","logs:DescribeLogStreams"],"Resource": ["*"]}]}'
```

正常に実行できると、以下のように json が返ってきます。

```json
{
    "Policy": {
        "PolicyName": "pol-adcale2",
        "PolicyId": "ANPA*****************",
        "Arn": "arn:aws:iam::************:policy/pol-adcale2",
        "Path": "/",
        "DefaultVersionId": "v1",
        "AttachmentCount": 0,
        "PermissionsBoundaryUsageCount": 0,
        "IsAttachable": true,
        "CreateDate": "2021-12-18T13:34:11+00:00",
        "UpdateDate": "2021-12-18T13:34:11+00:00"
    }
}
```

```Arn``` の値を確認しておきます。

## IAM ロールを作成する
ポリシーを作成できたら、次は IAM Role を作っていきます。
```iam create-role``` コマンドを実行します。

```sh
aws iam create-role --role-name role-adcale2 --assume-role-policy-document '{"Version": "2012-10-17","Statement": [{"Effect": "Allow","Principal": {"Service": "ec2.amazonaws.com"},"Action": "sts:AssumeRole"}]}'
```

正常に作成できると、以下のような json が返ってきます。

```json
{
    "Role": {
        "Path": "/",
        "RoleName": "role-adcale2",
        "RoleId": "AROA*****************",
        "Arn": "arn:aws:iam::************:role/role-adcale2",
        "CreateDate": "2021-12-18T13:35:18+00:00",
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Principal": {
                        "Service": "ec2.amazonaws.com"
                    },
                    "Action": "sts:AssumeRole"
                }
            ]
        }
    }
}
```

ロール をつくっただけでは、箱を用意しただけなので、```iam attach-role-policy``` コマンドを使ってポリシーを割り当てます。

```sh
aws iam attach-role-policy --role-name role-adcale2 --policy-arn <ポリシーの Arn の値>
```

正常に終了した場合、特に出力されません。

## インスタンスプロファイルを作成する

EC2 に IAM ロール を割り当てる場合、インスタンスプロファイルというものが必要になりますので、```iam create-instance-profile``` コマンドを実行して、インスタンスプロファイルを作っていきます。

コマンドで指定するインスタンスプロファイル名は、使用するIAM ロール名と同じにしておくと GUI で作成した場合と同様に管理できるので良いと思います。

```sh
aws iam create-instance-profile --instance-profile-name role-adcale2
```

正常に作成できると、以下のような json が返ってきます。

```json
{
    "InstanceProfile": {
        "Path": "/",
        "InstanceProfileName": "role-adcale2",
        "InstanceProfileId": "AIPA*****************",
        "Arn": "arn:aws:iam::************:instance-profile/role-adcale2",
        "CreateDate": "2021-12-18T13:39:29+00:00",
        "Roles": []
    }
}
```

そして、IAM ロールとインスタンスプロファイルを ```iam add-role-to-instance-profile``` コマンドで紐づけます。

```sh
aws iam add-role-to-instance-profile --instance-profile-name role-adcale2 --role-name role-adcale2
```

正常に終了した場合、特に出力されません。
これで IAM ロールを EC2 インスタンスで利用可能な状態にできました。

## IAM ロール(インスタンスプロファイル) を起動設定に割り当てる
昨日、18日目と同様の作業を行います。
```--iam-instance-profile``` オプションが追加になっています。あとは起動設定の名称以外は18日目と同じで構いません。


```sh
aws autoscaling create-launch-configuration \
--launch-configuration-name lc-web-server2 \
--image-id <18日目に取得した AMI ID> \
--security-groups <sg_web2 の ID> \
--instance-type t2.micro \
--iam-instance-profile "role-adcale2" \
--user-data file://userdata2.sh
```

実行しても何も返りません。

## VPC エンドポイントを作成する
Auto Scaling 化してからは、 プライベートサブネットで EC2 インスタンスが起動するようになっています。しかし、セッションマネージャーでセッションにアクセスするには、以下のいずれかの経路が必要です。

* Internet Gateway(IGW) へのルート
* NAT などへのルート
 * NAT Gateway
 * NAT インスタンス
 * Proxy サーバー ..など
* VPC エンドポイント

1つ目の IGW へのルートですが、これでは、プライベートサブネットがパブリックサブネットになってしまうので❌です。
2つ目の NAT などへのルートは、用件によっては⭕️ですが、今回は選択しません。
ということで、3つ目の VPC エンドポイントを作成します。

この VPC エンドポイントはタイプが2種類存在します。

* ゲートウェイタイプ
* インターフェイスタイプ

本書執筆時点(2021/12/19)では、ゲートウェイタイプを選択できるのは、 S3 と DynamoDB に対する VPC エンドポイントのみです。インターフェイスタイプはサポートしているサービスで利用可能です。

今回、作ることになるのは、以下の4つです

* Systems Manager(SSM)
* SSM Message
* S3
* Cloudwatch Logs

```ec2 create-vpc-endpoint``` コマンドを使って作っていくのですが、 インターフェースタイプの VPC エンドポイントには セキュリティグループを割り当てる必要があるので、Web サーバー用のセキュリティグループからのインバウンド通信とアウトバウンドへのフルオープンなルールを持ったグループを合わせて作ります。

```sh
VPC_ID=<VPCのID>
ROUTE_TABLE_ID=<ルートテーブルのID>
SUBNET_ID1=<1つ目のプライベートサブネットID>
SUBNET_ID2=<2つ目のプライベートサブネットID>
SG_ID=`aws ec2 create-security-group --description "VPC Endpoint SG" --group-name "sg_vpce" --vpc-id ${VPC_ID} | jq -r .GroupId`

aws ec2 authorize-security-group-ingress --group-name sg_vpce \
    --protocol tcp \
    --port 443 \
    --source-group sg_web2

aws ec2 create-vpc-endpoint \
--vpc-endpoint-type Gateway \
--vpc-id ${VPC_ID} \
--service-name com.amazonaws.ap-northeast-1.s3 \
--route-table-ids ${ROUTE_TABLE_ID} \

for service in ssm ssmmessages logs
do
 aws ec2 create-vpc-endpoint \
 --vpc-endpoint-type Interface \
 --vpc-id ${VPC_ID} \
 --service-name com.amazonaws.ap-northeast-1.${service} \
 --subnet-ids "${SUBNET_ID1}" "${SUBNET_ID2}" \
 --security-group-ids ${SG_ID} \
 --private-dns-enabled
done

```

## Auto Scaling グループに新しい起動設定を割り当てる
VPC エンドポイントの作成も行えたので、Auto Sclaing グループに新しい起動設定を割り当てます。
```autoscaling update-auto-scaling-group``` コマンドを実行します。

```sh
aws autoscaling update-auto-scaling-group \
--auto-scaling-group-name asg-web-server \
--launch-configuration-name lc-web-server2
```

実行してもこれといって応答はありません。

## Auto Scaling で起動している EC2 インスタンスを終了させる
各種設定を最新化させるために、 EC2 インスタンスを終了させてしまいます。

```ec2 terminate-instances``` コマンドを実行します。

```sh
aws ec2 terminate-instances \
--instance-ids <1台目の EC2 インスタンスのID> <2台目の EC2 インスタンスのID>
```

※3台以上、該当の Auto Scaling グループから起動しているインスタンスがいる場合はIDを追加して実行する

正常に終了させることができると、以下のような json が出力されます。
この例は2台を一気に終了させたものです。

```json
{
    "TerminatingInstances": [
        {
            "CurrentState": {
                "Code": 32,
                "Name": "shutting-down"
            },
            "InstanceId": "i-*****************",
            "PreviousState": {
                "Code": 16,
                "Name": "running"
            }
        },
        {
            "CurrentState": {
                "Code": 32,
                "Name": "shutting-down"
            },
            "InstanceId": "i-*****************",
            "PreviousState": {
                "Code": 16,
                "Name": "running"
            }
        }
    ]
}
```

## 新しく起動してきた EC2 インスタンスのIDを確認する
Auto Scaling 化されていると、「希望する台数」を維持しようとするので前述のように EC2 インスタンスを**終了**しても新しく起動してきます。

以下のコマンドを実行して、起動している EC2 インスタンスのIDを確認します。

```sh
aws ec2 describe-instances --filters "Name=instance-state-name,Values=running" "Name=tag:aws:autoscaling:groupName,Values=asg-web-server" --query 'Reservations[*].Instances[*].InstanceId'
```

フィルタリング条件にマッチすると以下のように出力されます。

```json
[
    [
        "i-****************1"
    ],
    [
        "i-****************2"
    ]
]
```

このインスタンス ID を使って、リモートアクセスを試みます。

# 動作確認
セッションマネージャーを利用したリモートアクセスを試みます。
実行前に、AWS CLI用の Session Manager プラグインをインストールしておいてください。
> https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html

AWS CloudShell で試す場合は、 [Linux で Session Manager プラグインをインストールする](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html#install-plugin-linux)で、Intel 64-bit(x86_64)の手順を実施ください。

準備ができたら、以下のコマンドでリモートアクセスを試みます。

```sh
aws ssm start-session --target <EC2 インスタンスのID>
```

成功すると、以下のような出力がなされます。sh...の表示がされたら、セッションマネージャーに接続ができた状態です。この ```SessionId``` の値は接続完了時にも使うので確認しておいてください。

```sh

Starting session with SessionId: ********-*****************
sh-4.2$ 
```

```ifconfig -a```コマンドで IP アドレスを確認したり、 ```/var/www/html/ec2/index.html``` の有無や内容確認をするなどしてみてください。

接続が完了したら、terminate-session コマンドでセッションを閉じましょう

```sh
aws ssm terminate-session --session-id <リモートアクセス開始時に表示された SessionId の値>
```

# まとめ
セッションマネージャーと VPC エンドポイントを使うことで踏み台サーバなしにプライベートサブネットで稼働している EC2 インスタンスにも接続できるようになります。
どういった経路で通信しているのかといったことを意識してみるのも面白いと思います。

明日は、ログを CloudWatch Logs に格納するようにします。