EC2のメトリクス取得等をCloudWatch Agentへ移行する

CloudWatchでシステム監視を実装するために、EC2リソースのメトリクスやログをCloudWatchに転送している、ということはよくあると思います。
私も同様の理由で以前より稼働しているEC2インスタンスのメトリクス、ログを下記の方法でCloudWatchに転送していたのですが、最近ようやくではありますが、CloudWatch Agentに移行、集約する機会がありましたので、事前に検証しておきました。

# 移行前に取得していた情報

EC2インスタンスは起動しただけでデフォルト取得される情報(CPU使用率等)とカスタムで設定しなければ取得できない情報(メモリ、ストレージ使用率等)があります。CloudWatch Agentは後者の情報取得方法を1つに集約したものとなっており、以前は下記の通りそれぞれ別の方法で取得していました。

## 環境

- Region: Tokyo
- OS: Redhat Enterprise Linux 7.4(実際に作業した古めの環境に合わせてます)
- IAM: 対象EC2インスタンスのロールには、事前に以下管理ポリシーをアタッチしています。
    - CloudWatchAgentServerPolicy (対象インスタンスでCloudWatch Agentを使用するために必要なポリシー)
    - AmazonSSMManagedInstanceCore (対象インスタンスをSystemsManagerで色々管理するために必要なポリシー)

### メモリ使用率、ストレージ使用率

下記のようにAWSより提供されているスクリプトをcronで定期実行し、CloudWatchに転送していました。

```bash
#/etc/crontab
*/5 * * * * root /usr/local/src/aws-scripts-mon/mon-put-instance-data.pl --mem-used-incl-cache-buff --mem-util --mem-used --mem-avail --disk-space-util --disk-path=/ --from-cron
```

AWSマネジメントコンソールでCloudWatchメトリクスを確認すると、カスタム名前空間に"System/Linux"という名前空間が新たに作成され、そちらにスクリプトで取得しているメトリクスが蓄積されていきます。

![cw_metrics_before.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152958/9283abf1-0d98-b3b1-f166-974e2e6f31e9.png)
![cw_metrics_graph_before.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152958/5f2ac05a-5938-81c2-142a-b315e4fa95ec.png)

### /var/log/messagesなど各種ログ

CloudWatch Logs Agent (**※CloudWatch Agentではありません！**)をインストールし、awslogs.confで対象ログを指定することでCloudWatchにログを転送していました。**awslogs**という名前のデーモンとして稼働しています。"/var/log/messages"を転送する際のawslog.confは以下の通り設定していました。

```bash
#/var/awslogs/etc/awslogs.conf
[/var/log/messages]
datetime_format = %Y-%m-%d %H:%M:%S
file = /var/log/messages
buffer_duration = 5000
log_stream_name = {hostname}
initial_position = start_of_file
log_group_name = /var/log/messages
```

こちらもAWSマネジメントコンソールで確認すると、awslogs.confで指定したロググループ名の中に指定したログストリーム(今回はホスト名)が作成され、こちらにログが転送されていきます。

![awslogs01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152958/467e8658-13a4-b0ca-846e-04f882c239be.png)

# CloudWatch Agentへ移行する

上記の通り、メモリ使用率、ストレージ使用率などのメトリクス、/var/log/messagesなどのログは、それぞれスクリプトとawslogsを使用してCloudWatchに転送していましたが、これらをCloudWatch Agentに統合します。が、ここでCloudWatch Agentでできることについて少しだけ触れておきます。

- メモリ、ストレージ使用率等のメトリクス取得やログの転送設定を一つに集約できる  
こちらは先程から触れている通り、今まで個々に実装していた仕組みをCloudWatch Agent1つに集約できます。よくあるリソース監視で使うメモリ、ストレージ使用率などのメトリクス以外にもTCPコネクション数やswap使用量なども取得できます。
- procstatプラグインを使ってプロセス監視できる  
今回は触れていませんが、configでprocstatプラグインを指定し監視対象プロセスを定義すれば、対象プロセスの起動数がCloudWatchメトリクスとして作成されるため、そこからAlermを定義すればプロセス監視が実装できてしまいます。
- StatsD、CollectDを使用してインスタンスやアプリケーションの統計情報を取得できる  
StatsD、CollectDを使用すれば、インスタンスのメトリクスにおいてもまたアプリケーションにおいても様々な統計情報を取得することができそうです。

## CloudWatch Agentの導入

まず対象インスタンスにCloudWatch Agentをインストールします。Systems Managerを使ったインストール方法等もありますが、今回はウィザードを確認しながら行いたいので、コマンドラインでインストールしてみます。

### ダウンロード/インストール

wgetでRPMパッケージをダウンロード後、インストールします。

```bash
$ wget https://s3.amazonaws.com/amazoncloudwatch-agent/redhat/amd64/latest/amazon-cloudwatch-agent.rpm
$ sudo rpm -U ./amazon-cloudwatch-agent.rpm
create group cwagent, result: 0
create user cwagent, result: 0
```

### 設定ウィザードの実行

いくつかのブロックに分けて説明します。
最初はAgentをインストールしたOSやホストの確認があります。

```
$ 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]:

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]:

Which user are you planning to run the agent?
1. root
2. cwagent
3. others
default choice: [1]:
```

続いて、StatsD、CollectD使う/使わないや使う場合の設定値について確認が入ります。

```
Do you want to turn on StatsD daemon?
1. yes
2. no
default choice: [1]:

Which port do you want StatsD daemon to listen to?
default choice: [8125]

What is the collect interval for StatsD daemon?
1. 10s
2. 30s
3. 60s
default choice: [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]:

Do you want to monitor metrics from CollectD?
1. yes
2. no
default choice: [1]:
```

取得するメトリクスについて確認です。"Which default metrics config do you want?"でプランを指定すると、指定したプランに応じたメトリクスを取得するよう設定してくれます。また、ここで1度configをプレビュー表示してくれます。

```
Do you want to monitor any host metrics? e.g. CPU, memory, etc.
1. yes
2. no
default choice: [1]:

Do you want to monitor cpu metrics per core? Additional CloudWatch charges may apply.
1. yes
2. no
default choice: [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]:

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]:

Which default metrics config do you want?
1. Basic
2. Standard
3. Advanced
4. None
default choice: [1]:

Current config as follows:
{
	"agent": {
		"metrics_collection_interval": 60,
		"run_as_user": "root"
	},
	"metrics": {
		"append_dimensions": {
			"AutoScalingGroupName": "${aws:AutoScalingGroupName}",
			"ImageId": "${aws:ImageId}",
			"InstanceId": "${aws:InstanceId}",
			"InstanceType": "${aws:InstanceType}"
		},
		"metrics_collected": {
			"collectd": {
				"metrics_aggregation_interval": 60
			},
			"disk": {
				"measurement": [
					"used_percent"
				],
				"metrics_collection_interval": 60,
				"resources": [
					"*"
				]
			},
			"mem": {
				"measurement": [
					"mem_used_percent"
				],
				"metrics_collection_interval": 60
			},
			"statsd": {
				"metrics_aggregation_interval": 60,
				"metrics_collection_interval": 10,
				"service_address": ":8125"
			}
		}
	}
}
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]:
```

プレビューに満足したら、次はログ転送について確認です。
初めに、今CloudWatch Log Agent使ってて設定ファイルあるならインポートしようか？と聞いてくれますので、対象ファイルを指定しインポートさせました。awslogs.confには"/var/log/messages"の設定が入っていたので、そちらがインポートされCloudWatch Agent用の設定ファイルに追記されます。
また、このタイミングで新たに"/var/log/httpd/access_log"も転送対象として追加してみました。
再度configのプレビューが表示され、"logs"の設定が追加されていることが確認できます。

```
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]:

What is the file path for the existing cloudwatch log agent configuration file?
default choice: [/var/awslogs/etc/awslogs.conf]

Do you want to monitor any log files?
1. yes
2. no
default choice: [1]:

Log file path:
/var/log/httpd/access_log
Log group name:
default choice: [access_log]

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]:

Saved config file to /opt/aws/amazon-cloudwatch-agent/bin/config.json successfully.
Current config as follows:
{
	"agent": {
		"metrics_collection_interval": 60,
		"run_as_user": "root"
	},
	"logs": {
		"force_flush_interval": 5,
		"logs_collected": {
			"files": {
				"collect_list": [
					{
						"file_path": "/var/log/messages",
						"log_group_name": "/var/log/messages",
						"log_stream_name": "{hostname}",
						"timestamp_format": "%Y-%m-%d %H:%M:%S"
					},
					{
						"file_path": "/var/log/httpd/access_log",
						"log_group_name": "access_log",
						"log_stream_name": "{hostname}"
					}
				]
			}
		}
	},
	"metrics": {
		"append_dimensions": {
			"AutoScalingGroupName": "${aws:AutoScalingGroupName}",
			"ImageId": "${aws:ImageId}",
			"InstanceId": "${aws:InstanceId}",
			"InstanceType": "${aws:InstanceType}"
		},
		"metrics_collected": {
			"collectd": {
				"metrics_aggregation_interval": 60
			},
			"disk": {
				"measurement": [
					"used_percent"
				],
				"metrics_collection_interval": 60,
				"resources": [
					"*"
				]
			},
			"mem": {
				"measurement": [
					"mem_used_percent"
				],
				"metrics_collection_interval": 60
			},
			"statsd": {
				"metrics_aggregation_interval": 60,
				"metrics_collection_interval": 10,
				"service_address": ":8125"
			}
		}
	}
}
```

最後に、作成したconfigをParameter Storeに保存するか聞かれますので、選択します。尚、今回の環境でアタッチしているIAMポリシーではParameter Storeに書き込む権限がありませんので、適宜権限を追加してください。

```
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]:

Program exits now.
```

### CloudWatch Agentの起動

ウィザードを使い"/opt/aws/amazon-cloudwatch-agent/bin/config.json"に設定ファイルが作成されましたので、インストールしたCloudWatch Agentを起動します。

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

ここで下記のようなエラーが出力され起動に失敗することがありますが、これは設定ファイルでcollectdを使うと宣言しているのにインストールされていないため発生しているエラーです。

```
Configuration validation second phase failed
======== Error Log ========
2020-12-17T03:13:43Z E! [telegraf] Error running agent: Error parsing /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.toml, open /usr/share/collectd/types.db: no such file or directory
```

collectdをインストールして再度起動コマンドを実行すれば、正常に起動します。

```bash
$ sudo yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
$ sudo yum install -y collectd
```

起動したらサービスの自動起動を有効化しておきます。

```bash
$ sudo systemctl enable amazon-cloudwatch-agent
```

ここまででCloudWatch Agentの導入が完了しました。

## 導入後の確認

CloudWatch Agentの導入が完了しましたので、AWSマネジメントコンソールでメトリクス、ログが転送されているか確認します。尚、この時点で旧取得方法は停止していません。

### メモリ使用率、ストレージ使用率

下図の通り、カスタム名前空間に"CWAgent"が新規作成されており、指定したメトリクスが取得できています。また旧方法(スクリプトの定期実行)とは別の名前空間となっているため、CloudWatｃh Agent導入後にスクリプトの定期実行を停止すれば、対象メトリクスは切れ目なく保管することができそうです。

![cwagent_metrics.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152958/13c2df9d-307f-8792-6c4f-ea953ca424bc.png)

### /var/log/messagesなど各種ログ

loggerコマンドで出力した行をCloudWatch Insightで抽出してみたところ、転送先のロググループ、ログストリームは同じなので予想通り2重に転送している状態となっていました。awslogsは不要なため、サービスを停止して再度loggerコマンドを実行し、2重に転送していないことを確認しました。

![cwagent_logs.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152958/92c0252b-77bb-8084-cd1c-da927d4f926b.png)

# まとめ

CloudWatch Agentに移行することで、今まで別々の仕組みで実装していたものを1つにまとめることができシンプルになりました。
CloudWatch Agentのインストールやconfigの展開はSystems Managerでも実行可能です。ただconfig作成おいては今回使用したウィザードは非常に分かりやすかったので、複数台に同一の設定を入れるなどの場合はウィザードで作成したconfigをParamater Storeに格納し、その格納した値を使ってSystems Managerで複数台に展開する、なんて方法でもよいですね。

# 参考情報

- [Amazon EC2 Linux インスタンスのメモリとディスクのメトリクスのモニタリング](https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/mon-scripts.html)
- [以前の CloudWatch Logs エージェントを使用して CloudWatch Logs の使用を開始する](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/logs/UsePreviousCloudWatchLogsAgent.html)
- [CloudWatch エージェントを使用して Amazon EC2 インスタンスとオンプレミスサーバーからメトリクスとログを収集する](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/monitoring/Install-CloudWatch-Agent.html)