Mackerelの公式プラグインをAWS Lambdaで実行してAmazon RDS for MySQL を監視する

Mackerelの公式プラグイン集や公式チェックプラグイン集は、各種ミドルウェアの詳細監視ができたり、AWS Integrationを使わなくてもRDSやELBなどのマネージドサービスのメトリック情報を取得することができたりと、大変便利なものです。

github.com

このプラグインAWSのEC2など mackerel-agentがインストールできる環境からは簡単に使うことができるのですが、例えばAWS上に Web-アプリケーション-DB の3層アーキテクチャを構成している場合に、EC2がAutoScaling対象のWebサーバやアプリケーションサーバしかないことがあり、DB(RDS)を監視するのにこのプラグインをどのEC2から使うか、少々悩ましいことがあります。

  • AutoScaling対象のEC2全てからプラグインを実行する場合
    • チェックプラグインの場合、RDSに異常が発生するとAutoScaling対象のEC2の台数分(重複して)アラートが上がってしまう
  • AutoScaling対象のEC2とは別に、RDS監視用のEC2を用意する場合
    • 監視用EC2に障害が発生するとRDSの監視が止まってしまうので、冗長構成にするなど監視用EC2の可用性を高める工夫をしなければならない
    • 監視用EC2自体の監視も必要になるため、Mackerel監視対象のホスト数が増えてしまう = コストが増えてしまう ことになる

そこで、この便利なプラグインAWS Lambdaから定期的に実行できれば、安く*1、アラートも重複せず、安定してRDSなどのマネージドサービスを監視できるのでは、と考えました。

この記事では、MySQLのメトリックを取得する mackerel-plugin-mysql を使ってAmazon RDS for MySQL を監視する例をまとめます。

事前準備

AWSやMackerelのアカウントは取得済みとします。
またAWS上にすでにシステムは稼働しており、監視対象の RDS for MySQLの構築やその他セキュリティグループ等の設定は済んでいるものとします。

公式プラグインのバイナリを用意する

AWS Lambdaで実行できる形式のバイナリファイルを作成するため、一時的にEC2を立ち上げます。
まずはAWS公式サイトの情報を元に、AWS Lambdaの実行基盤となっているAmazon LinuxのAMIイメージを探します。 2017/10/15 時点だと amzn-ami-hvm-2017.03.1.20170812-x86_64-gp2 です。

docs.aws.amazon.com

当該AMIを使ってEC2インスタンスを起動します。

  • インスタンスタイプは t2.micro で十分です
  • VPCやネットワーク、セキュリティグループについては、監視対象のRDSと通信できるようになっている必要があります

起動したEC2にSSHでログインし、mackerel-agent, 公式プラグイン集 mackerel-agent-plugins をインストールします。

$ curl -fsSL https://mackerel.io/file/script/amznlinux/setup-all-yum.sh | MACKEREL_APIKEY='<YOUR API KEY>' sh
$ sudo yum install mackerel-agent-plugins

Mackerelのプラグインが正しくインストールされたかを確認するため、手動でpluginのコマンドを実行してみます。
-host オプションで指定するRDSのエンドポイントはAWSマネジメントコンソールのRDSのメニューから確認してください。

$ mackerel-plugin-mysql -host=sample-db.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com -username=<USER_NAME> -password=<PASSWORD>

以下のように、{metric name}\t{metric value}\t{epoch seconds} (\t はタブ文字) の形式でたくさんの情報が標準出力に表示されれば成功です。

mysql.innodb_buffer_pool.pool_size      38208.000000    1507986274
mysql.innodb_buffer_pool.database_pages 2717.000000     1507986274
mysql.innodb_buffer_pool.free_pages     35484.000000    1507986274
mysql.innodb_buffer_pool.modified_pages 0.000000        1507986274
mysql.innodb_row_lock_waits.Innodb_row_lock_waits       0.000000        1507986274
mysql.innodb_adaptive_hash_index.hash_index_cells_total 1238989.000000  1507986274
(中略)
mysql.innodb_semaphores.spin_waits      0.000000        1507986274
mysql.innodb_semaphores.spin_rounds     0.000000        1507986274
mysql.innodb_semaphores.os_waits        0.000000        1507986274

MackerelにRDS用のホストを登録し、メトリックを投稿する

先ほど立ち上げたAmazonLinuxのEC2に、Mackerel の CLIツールである mkr コマンドをインストールします。

$ sudo yum install mkr

Mackerelに監視対象のRDSをホストとして登録します。
以下の例ではホスト名をtest-RDS、Mackerel上のロール名を TEST:DB としてホストを作成しています。

$ mkr create test-RDS -R TEST:DB
   created SAMPLE12345

成功するとHostIDが表示されるので、これを控えておきます。 今回だと SAMPLE12345 です(実際には11文字のランダム文字列)。
Mackerelの画面でもホストとサービス・ロールが登録されていることが確認できます。

f:id:kmiya_bbm:20171014223900p:plain

続いて今登録したホストのカスタムメトリックとしてRDS上のMySQLのメトリック情報を投稿します。 先ほど動作確認に使ったコマンドの結果をパイプで mkr throw に渡してMackerelに投稿します。

$ mackerel-plugin-mysql -host=sample-db.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com -username=<USER_NAME> -password=<PASSWORD> | mkr throw --host SAMPLE12345

以下のような結果が標準出力に表示されれば成功です。

thrown SAMPLE12345 'custom.mysql.join.Select_full_join      0.000000        1507987901'
thrown SAMPLE12345 'custom.mysql.join.Select_full_range_join        0.000000        1507987901                                           '
thrown SAMPLE12345 'custom.mysql.join.Select_range  0.000000        1507987901'
thrown SAMPLE12345 'custom.mysql.join.Select_range_check    0.000000        1507987901'
(中略)
thrown SAMPLE12345 'custom.mysql.innodb_buffer_pool_read.read_evicted       0.000000        15                                           07987901'
thrown SAMPLE12345 'custom.mysql.innodb_buffer_pool_read.read_random_ahead  0.000000        15                                          07987901'
thrown SAMPLE12345 'custom.mysql.innodb_checkpoint_age.uncheckpointed_bytes 0.000000        15                                          07987901'

Mackerel上で見てもメトリックが投稿されグラフが作成されていることがわかります。

f:id:kmiya_bbm:20171014223725p:plain

ここまでの手順で作成したプラグインや mkrコマンドのバイナリファイルを、普段お使いの開発マシンなどにコピーしておきます。
このAmazonLinux EC2についてはLambda上で実行できる形式のバイナリファイルを作成するのが目的だったので、もう停止・削除してしまって構いません。

AWS Lambdaから実行できるようにする

今までの手順で作成した (Lambdaの実行基盤であるAmazonLinux 上でインストールして生成された) mackerel-plugin-mysql と mkr のバイナリファイルをLambdaのコードと一緒に固めてアップロードし、ハンドラ関数の中でそのバイナリファイルを実行するようなLambdaファンクションを作ります。それをCloudWatch Eventsで定期スケジュール実行すればよいことになります。

今回はCloudWatch EventsやIAM Role等の周辺設定を自動で行うため Serverless Framework (v1.23.0)を使って Node.js 6.10 のLambdaファンクションを実装しました。 Serverless Framework のインストール手順やサービス作成手順等は公式サイトやネット上に情報がありますので割愛します。

(任意のディレクトリ)/
 ├ .gitignore
 ├ bin/
 │ ├ mackerel-plugin-mysql
 │ └ mkr
 ├ handler.js
 ├ package.json
 └ serverless.yml

  • serverless.yml
service: sls-postCustomMetricToMackerel # 任意のサービス名

provider:
  name: aws
  runtime: nodejs6.10
  region: ap-northeast-1 # 監視対象のRDSがあるリージョン

functions:
  mackerel:
    handler: handler.postCustomMetricToMackerel
    name: ${self:service}
    events:
      - schedule: rate(1 minute)  # CloudWatch Eventsで1分毎にスケジュール実行
    environment:
      DBHOST: sample-db.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com  # 監視対象のRDSのエンドポイント
      DBUSER: sampleuser  # 監視対象のRDSの接続ユーザ名
      DBPASS: S@mple      # 接続ユーザのパスワード
      MACKEREL_HOSTID: SAMPLE12345 # 監視対象のRDSのMackerel上のHostID
      MACKEREL_APIKEY: # MackerelのAPI KEY
    vpc:
      securityGroupIds:
        - sg-xxxxxxxx      # 監視対象のRDSにMySQL接続できるセキュリティグループを指定
      subnetIds:
        - subnet-yyyyyyyy  # 監視対象のRDSにMySQL接続できるサブネットを指定
        - subnet-zzzzzzzz  # 監視対象のRDSにMySQL接続できるサブネットを指定

簡単のため、DBの認証情報をそのまま環境変数に渡していますが、実運用上はKMSなどで暗号化して渡し、Lambdaの中で復号して使うのが良いでしょう。

  • handler.js
'use strict';

const exec = require('child_process').exec;

const dbhost = process.env.DBHOST;
const dbuser = process.env.DBUSER;
const dbpass = process.env.DBPASS;
const mackerelhostid = process.env.MACKEREL_HOSTID;
const mackerelApi = process.env.MACKEREL_APIKEY;
const basedir = process.env.LAMBDA_TASK_ROOT;

const cmd = `${basedir}/bin/mackerel-plugin-mysql -host=${dbhost}
               -username=${dbuser} -password=${dbpass}
             | MACKEREL_APIKEY=${mackerelApi}
               ${basedir}/bin/mkr throw --host ${mackerelhostid}`;

module.exports.postCustomMetricToMackerel = (event, context, callback) => {
  let child =  exec(cmd, function(err, stdout, stderr){
    if (!err) {
      console.log('standard out: ' + stdout);
      console.log('standard error: ' + stderr);
      callback(null, 'Success!');
    } else {
      console.log("error code: " + err.code + "err: " + err);
      callback(err);
    }
  });

};

sls deploy コマンドでデプロイすると、LambdaファンクションとそのトリガとなるCloudWatch Events、Lambdaファンクションに割り当てるIAM Roleが作成されます。 AWS Lambdaのコンソールからテスト実行して成功すれば、あとは放っておくだけで1分毎に RDS for MySQL のメトリック情報がMackerelに投稿されます。

f:id:kmiya_bbm:20171015213920p:plain

まとめ

Mackerelの公式プラグインAWS Lambdaで定期実行する仕組みを試作しました。これにより、監視専用のEC2インスタンスを立ち上げることなくRDSなどのマネージドサービスの監視を行うことができました。
ただし、実運用に投入する際はCloudWatch AlarmなどでこのLambdaファンクションが正常に実行されているかなどを監視する必要があります。

*1:AWS Integrationを使わないので、MackerelのFreeプランでも利用できます