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

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

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

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

※"CloudWatchAgentAdminPolicy"はCloudWatch Agentのコンフィグを再利用するためSSMパラメータストアに書き込むための権限が含まれるため、コンフィグの書き込み完了以降は"CloudWatchAgentServerPolicy"に変更することが推奨されています。

## 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='CloudWatchAgentInstanceRole'
# 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/CloudWatchAgentInstanceRole.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": "CloudWatchAgentInstanceRole",
        "RoleId": "AROAJTMDKBJBF5JL7MNZ6",
        "Arn": "arn:aws:iam::xxxxxxxxxxxx:role/CloudWatchAgentInstanceRole",
        "CreateDate": "2019-02-02T13:56:13Z",
        "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="CloudWatchAgentAdminPolicy"
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",
    "CloudWatchAgentAdminPolicy"
]
```

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

変数の設定

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

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

```sh:コマンド
aws iam create-instance-profile \
  --instance-profile-name ${IAM_INSTANCE_PROFILE_NAME}
```
```json:Output例
{
    "InstanceProfile": {
        "Path": "/",
        "InstanceProfileName": "CloudWatchAgentInstanceProfile",
        "InstanceProfileId": "AIPAI37ND6UTLOF7QWRBY",
        "Arn": "arn:aws:iam::xxxxxxxxxxxx:instance-profile/CloudWatchAgentInstanceProfile",
        "CreateDate": "2019-02-02T14:47:25Z",
        "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-00eba20147c23b77e",
        "InstanceId": "i-0f6392abb1e0eba21",
        "IamInstanceProfile": {
            "Arn": "arn:aws:iam::xxxxxxxxxxxx:instance-profile/CloudWatchAgentInstanceProfile",
            "Id": "AIPAI37ND6UTLOF7QWRBY"
        },
        "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-0f6392abb1e0eba21",
        "PingStatus": "Online",
        "LastPingDateTime": 1549184853.467,
        "AgentVersion": "2.3.372.0",
        "IsLatestVersion": false,
        "PlatformType": "Linux",
        "PlatformName": "Amazon Linux",
        "PlatformVersion": "2",
        "ResourceType": "EC2Instance",
        "IPAddress": "172.31.35.234",
        "ComputerName": "ip-172-31-35-234.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の設定

## 設定ファイルの作成

### EC2インスタンスにログイン
CloudWatch AgentがインストールされているEC2インスタンスにSSHログインします。

### (オプション) 一般的な設定を変更
プロキシ設定、認証情報(IAMロールをつけている場合は不要)などを設定する場合は以下のファイルを編集します。今回は省略。

```sh:コマンド
vi 　/opt/aws/amazon-cloudwatch-agent/etc/common-config.toml
```

### ウィザードを利用して設定ファイルを作成する

設定ウィザードを起動します。

```sh:コマンド
sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-config-wizard
```

ウィザードに剃ってパラメータを決定していきます。(各パラメータの選択の意図は後日元気のあるときに追記)

```:例
=============================================================
= Welcome to the AWS CloudWatch Agent Configuration Manager =
=============================================================
On which OS are you planning to use the agent?
1. linux
2. windows
default choice: [1]:
1
Trying to fetch the default region based on ec2 metadata...
Are you using EC2 or On-Premises hosts?
1. EC2
2. On-Premises
default choice: [1]:
1
Do you want to turn on StatsD daemon?
1. yes
2. no
default choice: [1]:
1
Which port do you want StatsD daemon to listen to?
default choice: [8125]
8125
What is the collect interval for StatsD daemon?
1. 10s
2. 30s
3. 60s
default choice: [1]:
1
What is the aggregation interval for metrics collected by StatsD daemon?
1. Do not aggregate
2. 10s
3. 30s
4. 60s
default choice: [4]:
1
Do you want to monitor metrics from CollectD?
1. yes
2. no
default choice: [1]:
1
Do you want to monitor any host metrics? e.g. CPU, memory, etc.
1. yes
2. no
default choice: [1]:
1
Do you want to monitor cpu metrics per core? Additional CloudWatch charges may apply.
1. yes
2. no
default choice: [1]:
1
Do you want to add ec2 dimensions (ImageId, InstanceId, InstanceType, AutoScalingGroupName) into all of your metrics if the info is available?
1. yes
2. no
default choice: [1]:
1
Would you like to collect your metrics at high resolution (sub-minute resolution)? This enables sub-minute resolution for all metrics, but you can customize for specific metrics in the output json file.
1. 1s
2. 10s
3. 30s
4. 60s
default choice: [4]:
2
Which default metrics config do you want?
1. Basic
2. Standard
3. Advanced
4. None
default choice: [1]:
3
Current config as follows:
{
	"metrics": {
		"append_dimensions": {
			"AutoScalingGroupName": "${aws:AutoScalingGroupName}",
			"ImageId": "${aws:ImageId}",
			"InstanceId": "${aws:InstanceId}",
			"InstanceType": "${aws:InstanceType}"
		},
		"metrics_collected": {
			"collectd": {
				"metrics_aggregation_interval": 0
			},
			"cpu": {
				"measurement": [
					"cpu_usage_idle",
					"cpu_usage_iowait",
					"cpu_usage_user",
					"cpu_usage_system"
				],
				"metrics_collection_interval": 10,
				"resources": [
					"*"
				],
				"totalcpu": false
			},
			"disk": {
				"measurement": [
					"used_percent",
					"inodes_free"
				],
				"metrics_collection_interval": 10,
				"resources": [
					"*"
				]
			},
			"diskio": {
				"measurement": [
					"io_time",
					"write_bytes",
					"read_bytes",
					"writes",
					"reads"
				],
				"metrics_collection_interval": 10,
				"resources": [
					"*"
				]
			},
			"mem": {
				"measurement": [
					"mem_used_percent"
				],
				"metrics_collection_interval": 10
			},
			"netstat": {
				"measurement": [
					"tcp_established",
					"tcp_time_wait"
				],
				"metrics_collection_interval": 10
			},
			"statsd": {
				"metrics_aggregation_interval": 0,
				"metrics_collection_interval": 10,
				"service_address": ":8125"
			},
			"swap": {
				"measurement": [
					"swap_used_percent"
				],
				"metrics_collection_interval": 10
			}
		}
	}
}
Are you satisfied with the above config? Note: it can be manually customized after the wizard completes to add additional items.
1. yes
2. no
default choice: [1]:
1
Do you have any existing CloudWatch Log Agent (http://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/AgentReference.html) configuration file to import for migration?
1. yes
2. no
default choice: [2]:
2
Do you want to monitor any log files?
1. yes
2. no
default choice: [1]:
1
Log file path:
/var/log/messages
Log group name:
default choice: [messages]

Log stream name:
default choice: [{instance_id}]

Do you want to specify any additional log files to monitor?
1. yes
2. no
default choice: [1]:
2
Saved config file to /opt/aws/amazon-cloudwatch-agent/bin/config.json successfully.
Current config as follows:
{
	"logs": {
		"logs_collected": {
			"files": {
				"collect_list": [
					{
						"file_path": "/var/log/messages",
						"log_group_name": "messages",
						"log_stream_name": "{instance_id}"
					}
				]
			}
		}
	},
	"metrics": {
		"append_dimensions": {
			"AutoScalingGroupName": "${aws:AutoScalingGroupName}",
			"ImageId": "${aws:ImageId}",
			"InstanceId": "${aws:InstanceId}",
			"InstanceType": "${aws:InstanceType}"
		},
		"metrics_collected": {
			"collectd": {
				"metrics_aggregation_interval": 0
			},
			"cpu": {
				"measurement": [
					"cpu_usage_idle",
					"cpu_usage_iowait",
					"cpu_usage_user",
					"cpu_usage_system"
				],
				"metrics_collection_interval": 10,
				"resources": [
					"*"
				],
				"totalcpu": false
			},
			"disk": {
				"measurement": [
					"used_percent",
					"inodes_free"
				],
				"metrics_collection_interval": 10,
				"resources": [
					"*"
				]
			},
			"diskio": {
				"measurement": [
					"io_time",
					"write_bytes",
					"read_bytes",
					"writes",
					"reads"
				],
				"metrics_collection_interval": 10,
				"resources": [
					"*"
				]
			},
			"mem": {
				"measurement": [
					"mem_used_percent"
				],
				"metrics_collection_interval": 10
			},
			"netstat": {
				"measurement": [
					"tcp_established",
					"tcp_time_wait"
				],
				"metrics_collection_interval": 10
			},
			"statsd": {
				"metrics_aggregation_interval": 0,
				"metrics_collection_interval": 10,
				"service_address": ":8125"
			},
			"swap": {
				"measurement": [
					"swap_used_percent"
				],
				"metrics_collection_interval": 10
			}
		}
	}
}
Please check the above content of the config.
The config file is also located at /opt/aws/amazon-cloudwatch-agent/bin/config.json.
Edit it manually if needed.
Do you want to store the config in the SSM parameter store?
1. yes
2. no
default choice: [1]:
1
What parameter store name do you want to use to store your config? (Use 'AmazonCloudWatch-' prefix if you use our managed AWS policy)
default choice: [AmazonCloudWatch-linux]

Trying to fetch the default region based on ec2 metadata...
Which region do you want to store the config in the parameter store?
default choice: [ap-northeast-1]

Which AWS credential should be used to send json config to parameter store?
1. ASIATWB6MCKPTDHEQ3IX(From SDK)
2. Other
default choice: [1]:
1
Successfully put config to parameter store AmazonCloudWatch-linux.
Program exits now.
```

上記のウィザードによってローカルの設定ファイル<code>/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.toml
</code>に設定が保存されます。
また、パラメータストアに設定ファイルを保存する指定をした場合はそちら(デフォルトパラメータ名はAmazonCloudWatch-linux)にもJson形式で保存されます。これにより、2台目以降はウィザードを実行せずにパラメータストアを参照して構成できます。


## collectdのインストール
このままCloudWatch Agentを起動するとcollectdがないと怒られるのでインストールします。

```sh:コマンド
sudo amazon-linux-extras install -y epel
sudo yum install collectd
```

```:Output例
省略
```

## CloudWatch Agentの構成、起動
SSM経由でCloudWatch Agentを構成、起動します。

### 変数の設定

```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": "91cae632-5469-4917-84f6-a939abda9f97",
        "DocumentName": "AmazonCloudWatch-ManageAgent",
　　　　　　　　(...省略...)
        "RequestedDateTime": 1549190668.591,
        "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にカスタムメトリクスのmem_used_percentが取得されています。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/345960/33aa05c7-4d8a-ea1e-d625-e423c74e4317.png)

### ログ
CloudWatch Logsに/var/log/messagesのログが取得されています。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/345960/fd6768f2-230e-8f04-a1c7-1cd01024ec36.png)

# Next Step
2台目以降はパラメータストアに保存した設定を簡略化できるためそちらも試す。
"CloudWatchAgentAdminPolicy"は設定ファイル作成/更新時以外は不要のため"CloudWatchAgentServerPolicy"に変更する。

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