CloudWatch Agentをインストールしてゲスト内メトリクスとログファイルデータを収集する (2台目以降)

CloudWatch Agentを起動済みのEC2インスタンス(Amazon Linux 2)にインストールしてゲスト内メトリクスとログファイル内データを収集します。本メモは2台目以降の設定方法です。

[最初の1台目の手順はこちら](https://qiita.com/f-daiki/items/2d98f209aed916789f2e)


# 前提条件
* AWS CLIがインストールされており、クレデンシャル/リージョンが設定されている。
* JSONファイル検証用にjsonlintがインストールされている。
* ログインできるEC2インスタンス(Linux) が起動されていること。今回はAmazon Linux 2。
* EC2インスタンスがインターネットまたはVPCエンドポイント経由でCloudWatch、CloudWatch Logs、SSMエンドポイントにアクセスできる。
* CloudWatch Agnentの1台目の構築時に、設定をSSMパラメータストアに保存している。

# EC2実行用ロールの作成およびEC2インスタンスへの紐付け
RunCommandでCloudWatch Agentをインストール/構成するための"AmazonEC2RoleforSSM"ポリシーおよびCloudWatchAgentのための"CloudWatchAgent<b>Server</b>Policy"ポリシーを持つロールを作成します。さらにEC2インスタンスにロールを紐付けるためにインスタンスプロファイルを作成してロールを付与し、インスタンスに紐付けます。

*"CloudWatchAgent<b>Admin</b>Policy"はCloudWatch Agentのコンフィグを再利用するためSSMパラメータストアに書き込むための権限が含まれるため2台目以降は使用しない。

## Assume Role Documentの作成

### 作業フォルダの作成

```sh:コマンド
ls -d ${HOME}/tmp/

# 存在しない場合
mkdir -p ${HOME}/tmp/
```
 
### 変数の設定

```sh:コマンド
# Assume Role Document ディレクトリ
DIR_IAM_ROLE_DOC="${HOME}/tmp"
# Role名
IAM_ROLE_NAME='CloudWatchAgentInstanceServerRole'
# Principle名
IAM_PRINCIPAL='ec2.amazonaws.com'
```

### ファイル名の設定

```sh:コマンド
FILE_IAM_ROLE_DOC="${DIR_IAM_ROLE_DOC}/${IAM_ROLE_NAME}.json" \
  && echo ${FILE_IAM_ROLE_DOC}
```
    
```sh:Output例
/Users/xxxxx/tmp/CloudWatchAgentInstanceServerRole.json
```

### Assume Role Documentの作成

```sh:コマンド
cat << EOF > ${FILE_IAM_ROLE_DOC}
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": "${IAM_PRINCIPAL}"
      },
      "Effect": "Allow",
      "Sid": ""
    }
  ]
}
EOF

cat ${FILE_IAM_ROLE_DOC}
```

```json:Output例
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Effect": "Allow",
      "Sid": ""
    }
  ]
}
```

jsonlintでjsonファイルが壊れていないかチェックします。問題なければ何も出力されません。

```sh:コマンド
jsonlint -q ${FILE_IAM_ROLE_DOC}
```

## IAMロールの作成

```sh:コマンド
aws iam create-role \
  --role-name ${IAM_ROLE_NAME} \
  --assume-role-policy-document file://${FILE_IAM_ROLE_DOC}
```

```json:Output例
{
    "Role": {
        "Path": "/",
        "RoleName": "CloudWatchAgentInstanceServerRole",
        "RoleId": "AROAJBP27QT6327XBN3VQ",
        "Arn": "arn:aws:iam::xxxxxxxxxxxx:role/CloudWatchAgentInstanceServerRole",
        "CreateDate": "2019-02-09T08:53:09Z",
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Action": "sts:AssumeRole",
                    "Principal": {
                        "Service": "ec2.amazonaws.com"
                    },
                    "Effect": "Allow",
                    "Sid": ""
                }
            ]
        }
    }
}
```

## IAMポリシーのアタッチ

### AmazonEC2RoleforSSM
変数の設定

```sh:コマンド
IAM_POLICY_NAME="AmazonEC2RoleforSSM"
IAM_POLICY_ARN=$( \
  aws iam list-policies \
    --scope AWS \
    --max-items 1000 \
    --query "Policies[?PolicyName==\`${IAM_POLICY_NAME}\`].Arn" \
    --output text \
) \
  && echo "${IAM_POLICY_ARN}"
```
```:Output例
arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM
```

ポリシーをロールへアタッチ

```sh:コマンド
aws iam attach-role-policy \
  --role-name ${IAM_ROLE_NAME} \
  --policy-arn ${IAM_POLICY_ARN}
```

```:Output例
出力なし
```

### CloudWatchAgentAdminPolicy

変数の設定

```sh:コマンド
IAM_POLICY_NAME="CloudWatchAgentServerPolicy"
IAM_POLICY_ARN=$( \
  aws iam list-policies \
    --scope AWS \
    --max-items 1000 \
    --query "Policies[?PolicyName==\`${IAM_POLICY_NAME}\`].Arn" \
    --output text \
) \
  && echo "${IAM_POLICY_ARN}"
```
```:Output例
arn:aws:iam::aws:policy/CloudWatchAgentAdminPolicy
```

ポリシーをロールへアタッチ

```sh:コマンド
aws iam attach-role-policy \
  --role-name ${IAM_ROLE_NAME} \
  --policy-arn ${IAM_POLICY_ARN}
```

```:Output例
出力なし
```

### アタッチされたポリシーの確認

```sh:コマンド
aws iam list-attached-role-policies \
    --role-name ${IAM_ROLE_NAME} \
    --query "AttachedPolicies[].PolicyName"
```

```json:Output例
[
    "AmazonEC2RoleforSSM",
    "CloudWatchAgentServerPolicy"
]
```

### インスタンスプロファイルの作成

変数の設定

```sh:コマンド
IAM_INSTANCE_PROFILE_NAME='CloudWatchAgentInstanceServerProfile'
```

インスタンスプロファイルの作成

```sh:コマンド
aws iam create-instance-profile \
  --instance-profile-name ${IAM_INSTANCE_PROFILE_NAME}
```
```json:Output例
{
    "InstanceProfile": {
        "Path": "/",
        "InstanceProfileName": "CloudWatchAgentInstanceServerProfile",
        "InstanceProfileId": "AIPAJJIV2ATCBNDDRTQXS",
        "Arn": "arn:aws:iam::xxxxxxxxxxxx:instance-profile/CloudWatchAgentInstanceServerProfile",
        "CreateDate": "2019-02-09T08:55:37Z",
        "Roles": []
    }
```

### インスタンスプロファイルへのロールのアタッチ
```sh:コマンド
aws iam add-role-to-instance-profile \
  --instance-profile-name ${IAM_INSTANCE_PROFILE_NAME} \
  --role-name ${IAM_ROLE_NAME}
```
```:Output例
出力なし
```

## EC2インスタンスへのインスタンスプロファイルのアタッチ

### 変数の設定

```sh
INSTANCE_ID="インスタンスプロファイルを紐付けるインスタンスID"
```

### インスタンスへのアタッチ

```sh:コマンド
aws ec2 associate-iam-instance-profile \
    --iam-instance-profile "Name=${IAM_INSTANCE_PROFILE_NAME}" \
    --instance-id ${INSTANCE_ID}
```

```:Output例
{
    "IamInstanceProfileAssociation": {
        "AssociationId": "iip-assoc-086fefee2bcb2a5ac",
        "InstanceId": "i-0ae3a2e7317e5e8d0",
        "IamInstanceProfile": {
            "Arn": "arn:aws:iam::xxxxxxxxxxxx:instance-profile/CloudWatchAgentInstanceServerProfile",
            "Id": "AIPAJJIV2ATCBNDDRTQXS"
        },
        "State": "associating"
    }
}
```

### 確認

```sh:コマンド
aws ec2 describe-iam-instance-profile-associations \
    --query "IamInstanceProfileAssociations[?InstanceId==\`${INSTANCE_ID}\`].State"
```

```json:Output例
[
    "associated"
]
```

# Systems Manager(SSM)からCloudWatch Agentをインストールする

## マネージドインスタンスの登録確認
マネージドインスタンスとして登録されているかを確認します。今回はAmazon Linux 2を使用しているため初めからSSMエージェントがインストールされており、マネージドインスタンスとして認識されます。インストールされないOSの場合やバージョンが古い場合は別途インストール/アップデートが必要です。

```sh:コマンド
aws ssm describe-instance-information \
    --query "InstanceInformationList[?InstanceId==\`${INSTANCE_ID}\`]"
```

```json:Output例
 [
    {
        "InstanceId": "i-0ae3a2e7317e5e8d0",
        "PingStatus": "Online",
        "LastPingDateTime": 1549703111.672,
        "AgentVersion": "2.3.372.0",
        "IsLatestVersion": false,
        "PlatformType": "Linux",
        "PlatformName": "Amazon Linux",
        "PlatformVersion": "2",
        "ResourceType": "EC2Instance",
        "IPAddress": "172.31.30.106",
        "ComputerName": "ip-172-31-30-106.ap-northeast-1.compute.internal"
    }
]
```

## CloudWatch Agentのインストール

### 変数の設定

```sh:コマンド
INSTANCE_ID="インストール先のインスタンスID"
SSM_COMMAND_DOCUMENT_NAME="AWS-ConfigureAWSPackage"
SSM_COMMAND_TARGETS="Key=instanceids,Values=${INSTANCE_ID}"
SSM_COMMAND_PARAMETERS="action=Install, name=AmazonCloudWatchAgent, version=latest"
```

### Run CommandでCloudWatch Agentをインストール

```sh:コマンド
aws ssm send-command \
  --document-name ${SSM_COMMAND_DOCUMENT_NAME} \
  --targets ${SSM_COMMAND_TARGETS} \
  --parameters "${SSM_COMMAND_PARAMETERS}"
```

```:Output例
{
    "Command": {
        "CommandId": "3c303421-1328-4613-9602-d041533b24b1",
        "DocumentName": "AWS-ConfigureAWSPackage",
　　　　　　　　(...省略...)
        "RequestedDateTime": 1549185898.031,
        "Status": "Pending",
        "StatusDetails": "Pending",
　　　　　　　　(...省略...)
}

```

### 確認

```sh:コマンド
SSM_COMMAND_ID="3c303421-1328-4613-9602-d041533b24b1" #上記の結果からコピー
aws ssm list-command-invocations \
    --command-id ${SSM_COMMAND_ID} \
    --instance-id ${INSTANCE_ID} \
    --query "CommandInvocations[].Status" \
    --output text
```

```:Output例
Success
```

# CloudWatch Agentの設定

## collectdのインストール
このままCloudWatch Agentを起動するとcollectdがないと怒られるので、collectdをSSM RunCommandの"AWS-RunShellScript"ドキュメントを使用していインストールします。

### 変数の設定

```sh:コマンド
INSTANCE_ID="インストール先のインスタンスID"
SSM_COMMAND_DOCUMENT_NAME="AWS-RunShellScript"
SSM_COMMAND_TARGETS="Key=instanceids,Values=${INSTANCE_ID}"
SSM_COMMAND_PARAMETERS="commands=sudo amazon-linux-extras install -y epel && sudo yum -y install collectd"
```

### RunCommandでcollectdをインストール
```sh:コマンド
aws ssm send-command \
  --document-name ${SSM_COMMAND_DOCUMENT_NAME} \
  --targets ${SSM_COMMAND_TARGETS} \
  --parameters "${SSM_COMMAND_PARAMETERS}"
```

```:Output例
{
    "Command": {
        "CommandId": "da0bb8b3-9c9a-466f-a7e2-f4543ef11c49",
        "DocumentName": "AWS-RunShellScript",
　　　　　　　　(...省略...)
        "RequestedDateTime": 1549185898.031,
        "Status": "Pending",
        "StatusDetails": "Pending",
　　　　　　　　(...省略...)
}

```

### 確認

```sh:コマンド
SSM_COMMAND_ID="da0bb8b3-9c9a-466f-a7e2-f4543ef11c49" #上記の結果からコピー
aws ssm list-command-invocations \
    --command-id ${SSM_COMMAND_ID} \
    --instance-id ${INSTANCE_ID} \
    --query "CommandInvocations[].Status" \
    --output text
```

```:Output例
Success
```

## CloudWatch Agentの構成、起動
SSM経由でCloudWatch Agentを構成、起動します。1台目を構築した際に設定がSSMパラメータストアに"AmazonCloudWatch-linux"というキーで保存されているためそちらを利用できます。

### 変数の設定

```sh:コマンド
INSTANCE_ID="インストール先のインスタンスID"
SSM_CONFIG_PARAMETER_NAME="AmazonCloudWatch-linux"
SSM_COMMAND_DOCUMENT_NAME="AmazonCloudWatch-ManageAgent"
SSM_COMMAND_TARGETS="Key=instanceids,Values=${INSTANCE_ID}"
SSM_COMMAND_PARAMETERS="action=configure, mode=ec2, optionalConfigurationSource=ssm, optionalConfigurationLocation=${SSM_CONFIG_PARAMETER_NAME}, optionalRestart=yes"
```

### Run CommandでCloudWatch Agentを起動

```sh:コマンド
aws ssm send-command \
  --document-name ${SSM_COMMAND_DOCUMENT_NAME} \
  --targets ${SSM_COMMAND_TARGETS} \
  --parameters "${SSM_COMMAND_PARAMETERS}"
```

```:Output例
{
    "Command": {
        "CommandId": "813175eb-8e52-4ffb-a833-d258eb3e7064",
        "DocumentName": "AmazonCloudWatch-ManageAgent",
　　　　　　　　(...省略...)
        "RequestedDateTime": 1549190668.591,
        "Status": "Pending",
        "StatusDetails": "Pending",
　　　　　　　　(...省略...)
}
```
### 確認

```sh:コマンド
SSM_COMMAND_ID="813175eb-8e52-4ffb-a833-d258eb3e7064" #上記の結果からコピー
aws ssm list-command-invocations \
    --command-id ${SSM_COMMAND_ID} \
    --instance-id ${INSTANCE_ID} \
    --query "CommandInvocations[].Status" \
    --output text
```

```:Output例
Success
```

# 取得されたメトリクス/ログの確認

## メトリクス
CloudWatchにカスタムメトリクスのmem_used_percentが取得されています。<img width="1213" alt="image.png" src="https://qiita-image-store.s3.amazonaws.com/0/345960/d7975e8b-fa6c-589a-d412-90385a0f01bb.png">


### ログ
CloudWatch Logsに/var/log/messagesのログが取得されています。
<img width="1222" alt="image.png" src="https://qiita-image-store.s3.amazonaws.com/0/345960/ff9927ca-7e84-327d-02bf-4e716030d1a8.png">


# まとめ
今回はすでに起動済みのEC2インスタンスに対してCloudWatch Agentをインストールしましたが、EC2インスタンス起動時にインストールするにはUserDataを利用するのが良いでしょう。
[参考: 新しいCloudWatch Agentを有効化したEC2オートスケール環境をCloudFormationで設定してみた](https://dev.classmethod.jp/cloud/aws/autoscalling-with-cloudwatchagent-cfn/)

AMIに仕込む場合はログストリーム名がAMIを作成したインスタンスIDとなってしまうため起動時に再構成が必要後なるようです。
[参考:CloudWatch Agent 仕込み済み AMI からEC2インスタンスを作成するときの注意](https://qiita.com/tonishy/items/6a1c2894df0751daf085)

# 参考
AWS CLIの書き方はJAWS-UG CLI専門支部の過去資料が大変参考になります。
https://jawsug-cli.doorkeeper.jp/