カスタムメトリクスをLambdaとCloudWatch Eventsを使って取得する

# 目的
業務でCentOS 6.8のディスク監視が必要になったため普通にカスタムメトリクスで設定すればよいでしょ、と思っていたらちょっとはまったのでそのメモ書き

# そもそもカスタムメトリクスとは？
EC2は標準でCloudWatchにいくつかのメトリクスを送信します。
その中には、CPU使用率やステータスチェックの結果などがあります。
※詳細は[こちら](http://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/viewing_metrics_with_cloudwatch.html)
ただ、今回監視したいディスクの容量については標準のメトリクスには含まれないため、カスタムメトリクスとなります。

# 構成
カスタムメトリクス用のスクリプトをEC2に仕込んでCRONに登録してもよいのですが、今回は以下のように実現しました。

![image](https://qiita-image-store.s3.amazonaws.com/0/40136/cf6ec9d9-47f8-2a71-e03d-23a6386c05c8.png)

1.CloudWatch Eventsで5分おきにLambdaを呼び出す
2.LambdaでSSM経由でEC2内のカスタムメトリクス用スクリプトをキックする
3.ディスクの使用率をCloudWatchにPUTする

# IAMロールの準備

EC2およびLambdaに権限を与えるためのロールを作成します。

今回はできるだけ権限を絞ろうと思い、必要のない権限を削除していますが（まだ削除できるかもしれない）、以下ドキュメントに従って設定したほうが間違いはないです。

[Amazon EC2 Run Command へのアクセスの委任](http://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/delegate-commands.html)

## EC2用ロールのポリシー
EC2用のロールを作成し、以下ポリシーを与えてください。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Stmt1478653320000",
            "Effect": "Allow",
            "Action": [
                "ssm:DescribeAssociation",
                "ssm:GetDocument",
                "ssm:ListAssociations",
                "ssm:UpdateAssociationStatus",
                "ssm:UpdateInstanceInformation",
                "ec2messages:AcknowledgeMessage",
                "ec2messages:DeleteMessage",
                "ec2messages:FailMessage",
                "ec2messages:GetEndpoint",
                "ec2messages:GetMessages",
                "ec2messages:SendReply",
                "cloudwatch:PutMetricData",
                "ec2:DescribeInstanceStatus"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}
```

# Lambda用IAMロールのポリシー
Lambda用のロールには以下ポリシーを与えてください。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ssm:ListDocuments",
                "ssm:DescribeDocument",
                "ssm:GetDocument",
                "ssm:DescribeInstanceInformation",
                "ssm:SendCommand",
                "ssm:CancelCommand",
                "ssm:ListCommands",
                "ssm:ListCommandInvocations",
                "ec2:DescribeInstanceStatus",
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "*"
        }
    ]
}
```

# EC2の準備
今回**Cent OS6.8**ということでEC2をAWS Marketplaceの「CentOS 6 (x86_64) - with Updates HVM」から作成します。
インスタンス作成時には作成したEC2用ロールの付与を忘れないように！！

## カスタムメトリクスの設定
インスタンスが立ち上がったらターミナルでインスタンスにログインします。

### 6.7から6.8へ
起動後にバージョンを確認すると、

```shell-session:
[root@ip-10-0-0-82 ~]# cat /etc/redhat-release
CentOS release 6.7 (Final)
```

デフォルトではバージョン6.7になっているので、以下コマンドでupdateを行います。

```shell-session:
[root@ip-10-0-0-82 ~]# yum update -y
```

update後のバージョンは6.8になりました。

```shell-session:
[root@ip-10-0-0-82 ~]# cat /etc/redhat-release
CentOS release 6.8 (Final)
```

### 必要パッケージのインストール

はじめは以下の「Red Hat Enterprise Linux」を参考に設定していたのですが、どうもうまくいかずはまりました。

[Amazon EC2 Linux インスタンスのメモリとディスクのメトリクスのモニタリング](https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/mon-scripts.html)

AWSサポートにも問い合わせたところ、

> CentOS　でのサポートはしておらず、AWSからドキュメントとして提供しているものはございません

とのことだったので、スクリプトを自前で用意しようと思ったのですが、以下でいけました。

```shell-session:
[root@ip-10-0-0-82 ~]# yum -y install perl-CPAN perl-libwww-perl openssl-devel perl-Crypt-SSLeay
[root@ip-10-0-0-82 ~]# yum -y install zip unzip wget
```

### CloudWatch Monitoring Scriptsのインストール

現行バージョン（v1.2.1）は動かなかったため、v1.1.0を以下で導入しました。

```shell-session:
[root@ip-10-0-0-82 ~]# mkdir /usr/local/cloudwatch
[root@ip-10-0-0-82 ~]# cd /usr/local/cloudwatch
[root@ip-10-0-0-82 cloudwatch]# wget http://ec2-downloads.s3.amazonaws.com/cloudwatch-samples/CloudWatchMonitoringScripts-v1.1.0.zip
[root@ip-10-0-0-82 cloudwatch]# unzip CloudWatchMonitoringScripts-v1.1.0.zip
[root@ip-10-0-0-82 cloudwatch]# rm CloudWatchMonitoringScripts-v1.1.0.zip
```
### テスト

テストします。

```shell-session:
[root@ip-10-0-0-82 cloudwatch]# cd aws-scripts-mon
[root@ip-10-0-0-82 aws-scripts-mon]# ./mon-put-instance-data.pl --mem-util --mem-used --mem-avail --verify
```

問題なければ

```shell-session:
Verification completed successfully. No actual metrics sent to CloudWatch.
```

が返ってきます。

## SSMエージェントインストール

以下に従いインストールします。

[SSM エージェントのインストール](http://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/install-ssm-agent.html)

今回はバージニアリージョンにインスタンスを立てたので、amazon-ssm-**us-east-1**.としています。
東京リージョンにインスタンスを立てる際は、amazon-ssm-**ap-northeast-1**.としてください。

```shell-session
[root@ip-10-0-0-82 aws-scripts-mon]# cd /tmp
[root@ip-10-0-0-82 tmp]# curl https://amazon-ssm-us-east-1.s3.amazonaws.com/latest/linux_amd64/amazon-ssm-agent.rpm -o amazon-ssm-agent.rpm
[root@ip-10-0-0-82 tmp]# yum install -y amazon-ssm-agent.rpm
[root@ip-10-0-0-82 tmp]# rm amazon-ssm-agent.rpm
```

これでインスタンス内部の設定は終わりなのでログアウトしてOKです。

# Lambdaの準備
CloudWatch EventsをトリガーにEC2にコマンドを実行するLambdaファンクションを作成します。

## 設定
設定値は以下の通りです。

|    項目    |    設定値    |
|:-----------|:-----------|
|Runtime|Node.js 4.3|
|Handler|index.handler|
|Role|作成したLambda用ロール|
|Memory|128 MB|
|Timeout|10 sec|

## コード
コードは以下の通りです。
ディスクだけでなくメモリ等も監視したければ、commandsを変更してください。

```js
'use strict';

const AWS = require('aws-sdk');
const ssm = new AWS.SSM();

exports.handler = (event, context, callback) => {

    const generator  = (function *() {

        try {
            const commands = [
                '/usr/local/cloudwatch/aws-scripts-mon/mon-put-instance-data.pl --disk-space-util --disk-path=/ --from-cron'
            ];

            yield sendCommand(event.instanceIds, commands, generator);

            callback(null, 'success');

        } catch (e) {
            callback(e.message);
        }
    })();

    /* 処理開始 */
    generator.next();

};

function sendCommand(instanceIds, commands, generator) {

    const params = {
        DocumentName: 'AWS-RunShellScript', /* required */
        InstanceIds: instanceIds,
        Parameters: {
            commands: commands,
            executionTimeout: [
                '300'
            ]
        },
        TimeoutSeconds: 600
    };

    ssm.sendCommand(params, function(err, data) {
        if (err) {
            console.log(err, err.stack);
            generator.throw(new Error('send command error : ' + params.InstanceIds));
            return;
       }
       console.log('successful send command : ' + params.InstanceIds);
       generator.next();
    });
}
```


# CloudWatch Eventsの設定

定期的にカスタムメトリクス用スクリプトを実行するためのイベントを作成します。

[CloudWatch] - [ルール] - [ルールの作成]から新しくルールを作成します。

![cloudwatchEvents_001.PNG](https://qiita-image-store.s3.amazonaws.com/0/40136/832b22d4-02ff-c14c-5ba9-d399c2a95f6a.png)

ターゲットでは、作成したLambdaファンクションを指定し、[入力の設定] - [定数 (JSON テキスト)]に以下を入力してください。

```json
{"instanceIds":["i-xxxxxxxx","i-oooooooo"]}
```

i-xxxxxxxx、i-ooooooooにはスクリプトを実行する（SSMエージェントインストール済みの）インスタンスIDを入力します。

# 動作確認
ちゃんと動いているか確認します。

[CloudWatch] - [メトリックス] - [Linux システム]を選択します。

![cloudwatchEvents_002.PNG](https://qiita-image-store.s3.amazonaws.com/0/40136/fe11ad89-42c0-9435-595c-7088212721c4.png)

[Filesystem, InstanceId, MountPath]を選択します。

![cloudwatchEvents_003.PNG](https://qiita-image-store.s3.amazonaws.com/0/40136/82a18e92-68ce-22f5-1ce3-f24094bebfc7.png)

参照したいインスタンスのボリュームを選択するときちんとディスク使用率がPUTされていることがわかります。

![cloudwatchEvents_004.PNG](https://qiita-image-store.s3.amazonaws.com/0/40136/fb5682d1-ffbb-e6e0-8182-32c4e009e206.png)

# 以上

CentOS 6.8での需要はあまりないかもしれませんが、CloudWatch EventsからLambdaを呼び出してカスタムメトリクスを取得する流れは汎用的だと思います。
ポリシー周りで余計なもの等ありましたら（あるはず）ご指摘お願いします。