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

20日目！

18 日目に Auto Scaling をいれ、何かトラブルがあっても自動で再起動してサービスを継続できるようにしました。
19 日目にトラブル発生時の調査用に、セッションマネージャーによるアクセスができるようにしました。
今日は、ログを自動的に CloudWatch Logs に格納するようにして、障害調査に活かせるようにします。

# 20日目の要約
ログを採取できるようにするよ！

# 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

# 概要
CloudWatch Agent を使ってログを CloudWatch Logs に格納するよ！

# さあ、やってみよう！
## CloudWatch Agent をインストールしよう
ひとまず、Auto Scaling で起動しているインスタンスのどちらかで実施します。
```ssm start-session``` コマンドで接続します。

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

接続できたら、以下のコマンドを実行します。
このコマンドで CloudWatch Agent がインストールできます。

```sh
sudo yum install amazon-cloudwatch-agent -y
```

## 採取ログの設定ファイルを用意する
**操作を、AWS CloudShell やこれまで AWS CLI を実行してきたローカル環境などに切り替えます。**

以下を見本に保存したいログファイルのパスや設定を ```collect_list``` に追加していきます。

```json:cloudwatch-logs-config.json
{
   "logs": {
     "logs_collected": {
       "files": {
         "collect_list": [
           {
             "file_path": "/opt/aws/amazon-cloudwatch-agent/logs/amazon-cloudwatch-agent.log",
             "log_group_name": "advent-calendar-logs",
             "log_stream_name": "{instance_id}-amazon-cloudwatch-agent.log",
             "timezone": "UTC"
           },{
             "file_path": "/var/log/messages",
             "log_group_name": "advent-calendar-logs",
             "log_stream_name": "{instance_id}-var-log-messages.log",
             "timezone": "UTC"
           }
         ]
       }
     },
     "log_stream_name": "{instance_id}-unknown-log",
     "force_flush_interval" : 15
   }
}
```

この json ファイルを 任意の s3 バケットに格納します。

```sh
aws s3 cp cloudwatch-logs-config.json s3://<バケット名>/cloudwatch-logs-config.json
```

もしくは、 Systems Manager の Parameter Store に保存するという方法もありますが、今回は、 S3 を経由する方法とします。

## CloudWatch Agent の設定を行う
**操作を、Auto Scaling で起動した EC2 インスタンスで実行します**

先ほど作成した 設定ファイルをダウンロードしてきます。

```sh
aws s3 cp s3://<バケット名>/cloudwatch-logs-config.json /tmp/cloudwatch-logs-config.json
```

次に、CloudWatch Agent を起動するコマンドを実行します。

```sh
sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -s -c file:/tmp/cloudwatch-logs-config.json
```

特に問題なければ、以下のようなログが返ります。

```sh
****** processing amazon-cloudwatch-agent ******
/opt/aws/amazon-cloudwatch-agent/bin/config-downloader --output-dir /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.d --download-source file:/tmp/cloudwatch-logs-config.json --mode ec2 --config /opt/aws/amazon-cloudwatch-agent/etc/common-config.toml --multi-config default
Successfully fetched the config and saved in /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.d/file_cloudwatch-logs-config.json.tmp
Start configuration validation...
/opt/aws/amazon-cloudwatch-agent/bin/config-translator --input /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json --input-dir /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.d --output /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.toml --mode ec2 --config /opt/aws/amazon-cloudwatch-agent/etc/common-config.toml --multi-config default
2021/12/19 14:13:48 Reading json config file path: /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.d/file_cloudwatch-logs-config.json.tmp ...
Valid Json input schema.
I! Detecting run_as_user...
No csm configuration found.
No metric configuration found.
Configuration validation first phase succeeded
/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent -schematest -config /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.toml
Configuration validation second phase succeeded
Configuration validation succeeded
amazon-cloudwatch-agent has already been stopped
Created symlink from /etc/systemd/system/multi-user.target.wants/amazon-cloudwatch-agent.service to /etc/systemd/system/amazon-cloudwatch-agent.service.
Redirecting to /bin/systemctl restart amazon-cloudwatch-agent.service
```

## CloudWatch Logs にログが格納されているか確認する。
**操作を、AWS CloudShell やこれまで AWS CLI を実行してきたローカル環境などに切り替えます。**

```logs describe-log-groups``` コマンドと ```log describe-log-streams``` コマンドを使って、ログが格納されているかを確認します。

```sh
aws logs describe-log-groups --log-group-name-prefix advent-calendar-logs
```

ログが配信され始めていたら、以下のような json が返ります。

```json
{
    "logGroups": [
        {
            "logGroupName": "advent-calendar-logs",
            "creationTime": *************,
            "metricFilterCount": 0,
            "arn": "arn:aws:logs:ap-northeast-1:************:log-group:advent-calendar-logs:*",
            "storedBytes": 0
        }
    ]
}
```

次に、 ```log-describe-log-streams``` コマンドを使って、ログストリームが作られていることを確認します。

```sh
aws logs describe-log-streams --log-group-name advent-calendar-logs
```

正常にログが配信されていれば、ログの定義が２件入っていることを確認できます。

```json
{
    "logStreams": [
        {
            "logStreamName": "i-*****************-amazon-cloudwatch-agent.log",
            "creationTime": *************,
            "firstEventTimestamp": *************,
            "lastEventTimestamp": *************,
            "lastIngestionTime": *************,
            "uploadSequenceToken": "********************************************************",
            "arn": "arn:aws:logs:ap-northeast-1:************:log-group:advent-calendar-logs:log-stream:i-*****************-amazon-cloudwatch-agent.log",
            "storedBytes": 0
        },
        {
            "logStreamName": "i-*****************-var-log-messages.log",
            "creationTime": *************,
            "firstEventTimestamp": *************,
            "lastEventTimestamp": *************,
            "lastIngestionTime": *************,
            "uploadSequenceToken": "********************************************************",
            "arn": "arn:aws:logs:ap-northeast-1:************:log-group:advent-calendar-logs:log-stream:i-*****************-var-log-messages.log",
            "storedBytes": 0
        }
    ]
}
```

これで、Auto Scaling で起動した EC2 インスタンスのうち、1台について ログの自動採取ができるようになりました。

しかし、このインスタンスが障害などで入れ替わってしまったら吹き飛んでしまいます。

なので、起動設定のユーザーデータを書き換えて、もしもの時の準備をしておきます。

## 起動設定を更新する
まずは、ユーザーデータを作ります。

```sh:userdata3.sh
#!/bin/bash
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`

yum install amazon-cloudwatch-agent -y

aws s3 cp s3://<バケット名>/cloudwatch-logs-config.json /tmp/cloudwatch-logs-config.json

echo "<HTML><HEAD><TITLE>from EC2</TITLE><BODY>This is ${INSTANCE_ID}.<br>Enjoy Auto Scaling!!.</BODY></HTML>" > /var/www/html/ec2/index.html

/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -s -c file:/tmp/cloudwatch-logs-config.json

```

ユーザーデータが作成できたら、起動設定を更新します。

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

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

## Auto Scaling グループの設定を変える
起動設定が作成できたら、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-server3
```

こちらもやはり、何も返りません。

## Auto Scaling で起動している EC2 インスタンスのうち未設定のものを終了させる

設定を最新化させるために、 EC2 インスタンスを終了させてしまいます。
ec2 terminate-instances コマンドを実行します。

```sh
aws ec2 terminate-instances \
--instance-ids <CloudWatchLogs 未設定の EC2 インスタンスのID>
```

出力される json は割愛します。

## 動作確認
Auto Scaling で改めて起動してきた EC2 インスタンスのログ情報が CloundWatch Logs に配信されているか確認します。

```sh
aws logs describe-log-streams --log-group-name advent-calendar-logs
```

json は割愛しますが、定義が少なくとも4つ(2種類x台数分)になっていれば設定成功です。

# まとめ
前回のセッションマネージャー、今回の CloudWatch Logs の設定でトラブル発生時にも対応がしやすくなったのではないでしょうか？
また、ログの定義ファイルを S3 に格納したのは Auto Scaling から新たに起動した EC2 インスタンスにとって最新のログ定義が取得できるという作りにするためでした(Systems Manager の パラメータストアでもかまいません)。
EC2 の外部でログを定義し、起動時に取得する作りにしておけば、各環境にいちいち手動でコピーして、設定を反映させるような作業をしないで済みます。

あと、5日。
明日は、コンテンツの更新回りの設定を入れていこうかと思います。

それでは！