ニフクラ ブログ

ニフクラ/FJ Cloud-Vやクラウドの技術について、エンジニアが語るブログです。

サーバーレスでRDB/NASをMackerel監視する

はじめまして。ニフティクラウドでエンジニアをしている世良です。 主にニフティクラウドRDBを担当しています。

今回はニフティクラウドスクリプトとタイマーの機能を利用し、サーバーレスでニフティクラウドRDB/NASの監視データをMackerelに連携する方法を紹介します。RDB/NASをMackerelの監視対象とすることで、Mackerelの便利な機能を活用することが可能になります。

RDBのMackerel監視例

RDB/NASの監視とMackerelの概要

過去に紹介したRDB/NASの監視

ニフティクラウドのRDBやNASを利用する上で、 監視は欠かせない ものです。監視の方法としては、過去に当ブログの記事 「ニフティクラウドRDBの新機能を利用し、DBサーバーの監視システムを迅速に構築する方法」 にてご紹介した、標準のイベント通知機能をご利用いただく方法や、APIを利用した監視システムを構築する方法があります。今回はこれらとは別の手法として、Mackerelに連携して監視する仕組みを紹介いたします。

Mackerelについて

Mackerelは、株式会社はてなから提供されているサーバー監視サービスです。ニフティクラウドとも連携しており、ニフティクラウド経由でご利用いただく際は、「Free(ニフティクラウド特別)プラン」も用意されております。ニフティクラウド上でのMackerelのご利用については、当ブログの「Mackerelでニフティクラウドのサーバーを監視しよう」もご確認ください。

RDB/NASをMackerelで監視するには

通常のニフティクラウド上で作成したサーバーであれば、Mackerelのエージェントをインストールすることで、簡単にMackerelによる監視設定を行うことができます。しかし、ニフティクラウドRDBやNASの場合、Mackerelのエージェントをインストールすることができないため、通常のサーバーと同様の手順では、Mackerelの監視対象とすることができません。なぜなら、ニフティクラウドRDBやNASは、利用者によるシェルログイン等を制限されているからです。

その代わり、ニフティクラウドRDB/NASでは、 モニタリングAPI を実行することで対象のモニタリング情報の取得が可能です。そのため、API実行結果を取得し、Mackerelの「サービスメトリック」として連携するようなプログラム等を作成すれば、監視データをMackerelに連携することができます。

サーバーレスなプログラム実行

監視データをMackerelに連携するプログラムを作成し、定期的に実行させる場合、一般的には実行用のサーバーを用意する必要があります。ただ、サーバーを作成し、実行用の環境を作成し、継続的に運用していく…となると、サーバー自体の費用や、サーバーのメンテナンスにコストがかかってしまいます。

そこで、今回はサーバーレスなプログラムの実行環境であるニフティクラウドスクリプトと、スクリプトを定期実行できるニフティクラウドタイマーの機能を利用して、 サーバーを一切構築せず に、MackerelにRDB/NASの監視データを連携する方法をご紹介します。

構築例

今回構築する仕組みの概要は下記のようになります。

構築例の概要図

以下で具体的な構築例を説明させていただきます。

Mackerel側の設定

まず、Mackerel上でサービスを作成します。(参考: 「サービス、ロールを作成する - Mackerel ヘルプ」

Mackerel上のサービス作成例

今回は、「niftycloud_rdb」「niftycloud_nas」という名前でサービスを登録しました。 それぞれ、ニフティクラウドRDBとニフティクラウドNAS用のサービスとする想定です。上記は設定例ですので、サービス名は都合に合わせて適宜設定してください。

また、MackerelのAPIキーが連携に必要になるため、控えておきましょう。APIキーはダッシュボードのAPIキータブで確認できます。

MackerelのAPIキー確認例

ニフティクラウドスクリプトの作成

今回、下記のスクリプトを用意いたしました。

const NiftyCloud = require('niftycloud-auth');

const co = require('co');
const moment = require('moment');
const request = require('superagent');

const ACCESS_KEY = '【NiftyCloud AccessKey】';
const SECRET_KEY = '【NiftyCloud SecretKey】';

const MACKEREL_API_KEY = '【Mackerel API Key】';

const SERVICE_SETTING = {
  rdb: {
    endpoints: {
      'east-1': 'https://rdb.jp-east-1.api.cloud.nifty.com',
      'east-2': 'https://rdb.jp-east-2.api.cloud.nifty.com',
      'east-3': 'https://rdb.jp-east-3.api.cloud.nifty.com',
      'west-1': 'https://rdb.jp-west-1.api.cloud.nifty.com',
    },
    metric: {
      timeDifference: 0, // UTC
      api: 'NiftyGetMetricStatistics',
      dimensitonName: 'DBInstanceIdentifier',
      responseName: 'NiftyGetMetricStatisticsResponse',
      responseResultName: 'NiftyGetMetricStatisticsResult',
    },
  },
  nas: {
    endpoints: {
      'east-1': 'https://nas.jp-east-1.api.cloud.nifty.com',
      'east-2': 'https://nas.jp-east-2.api.cloud.nifty.com',
      'east-3': 'https://nas.jp-east-3.api.cloud.nifty.com',
      'west-1': 'https://nas.jp-west-1.api.cloud.nifty.com',
    },
    metric: {
      timeDifference: 9, // JST
      api: 'GetMetricStatistics',
      dimensitonName: 'NASInstanceIdentifier',
      responseName: 'GetMetricStatisticsResponse',
      responseResultName: 'GetMetricStatisticsResult',
    },
  },
};

const MACKEREL_API_SERVICES = 'https://mackerel.io/api/v0/services';

function fetchNiftyCloudMetric(niftycloudService, region, instanceName, metricName, period) {
  return new Promise((resolve, reject) => {
    const service = SERVICE_SETTING[niftycloudService];
    const v4 = new NiftyCloud.V4(
      ACCESS_KEY,
      SECRET_KEY,
      service.endpoints[region]
    );

    const dateFormat = 'YYYY-MM-DD HH:mm';
    const timeDifference = service.metric.timeDifference;
    const start = moment().add(timeDifference, 'hours').subtract(period, 'minutes').format(dateFormat);
    const end = moment().add(timeDifference, 'hours').format(dateFormat);

    v4.post('/', niftycloudService, region, {
      body: {
        Action: service.metric.api,
        MetricName: metricName,
        'Dimensions.member.1.Name': service.metric.dimensitonName,
        'Dimensions.member.1.Value': instanceName,
        StartTime: start,
        EndTime: end,
      },
    }).then((res) => {
      resolve(res.body);
    }).catch((err) => {
      reject(err);
    });
  });
}

function convertMackerelMetric(niftycloudService, mackerelMetricName, metrics) {
  const service = SERVICE_SETTING[niftycloudService];
  const metricResponseName = service.metric.responseName;
  const metricResponseResultName = service.metric.responseResultName;
  const member = metrics[metricResponseName][metricResponseResultName].Datapoints.member;
  return member.map((m) => {
    return {
      name: mackerelMetricName,
      time: moment(m.Timestamp, 'YYYY-MM-DDThh:mm:ssZ').unix(),
      value: Number(m.Sum),
    };
  });
}

function putMackerelTsdb(mackerelService, metrics) {
  return new Promise((resolve, reject) => {
    request.post(`${MACKEREL_API_SERVICES}/${mackerelService}/tsdb`)
      .set('Content-Type', 'application/json')
      .set('X-API-Key', MACKEREL_API_KEY)
      .send(metrics)
      .end((err, res) => {
        if (!err && res.ok) {
          resolve(res);
        } else {
          reject(err);
        }
      });
  });
}

module.exports = (req, res) => {
  /*
  req.body Example
   {
     'niftycloudService': 'rdb',
     'mackerelService': 'niftycloud_rdb',
     'region':'east-1',
     'instanceName':'instanceName',
     'metricNames':['CPUUtilization', 'FreeableMemory'],
     'period':'20'
   }
  */
  const niftycloudService = req.body.niftycloudService;
  const mackerelService = req.body.mackerelService;
  const region = req.body.region;
  const instanceName = req.body.instanceName;
  const metricNames = req.body.metricNames;
  const period = req.body.period;

  co(function* () {
    const metrics = yield metricNames.map(function* (metricName) {
      const niftycloudMetrics = yield fetchNiftyCloudMetric(niftycloudService, region, instanceName, metricName, period);
      const mackerelMetricName = `${metricName}.${instanceName}`;
      const mackerelMetrics = convertMackerelMetric(niftycloudService, mackerelMetricName, niftycloudMetrics);
      yield putMackerelTsdb(mackerelService, mackerelMetrics);
      return mackerelMetrics;
    });
    res.json(metrics);
  }).catch((err) => {
    res.send(err.stack);
  });
};

スクリプト内で設定されているニフティクラウドのアクセスキー( 【NiftyCloud AccessKey】 )およびシークレットキー( 【NiftyCloud SecretKey】 )と、MackerelのAPIキー( 【Mackerel API Key】 )につきましては、利用者のキーを設定してください。

なお、今回のスクリプトでは、east-1, 2, 3とwest-1のRDB/NASに対応しています。リージョンが増えた場合は、適宜エンドポイントの修正が必要となります。

上記スクリプトを、ニフティクラウドスクリプト上に作成します。コントロールパネルから、スクリプトの作成ボタンを押下し、下記のように入力します。

スクリプトの作成例

項目 設定値
スクリプト名 monitor_mackerel.js(任意の名前を設定ください)
スクリプト内容 上記のスクリプトのコード(アクセスキー/シークレットキー/MackerelのAPIキーを設定したもの)
メソッド POST
スクリプトの状態 実行可能
バージョン latest

作成が完了すると、下記のように一覧に表示されます。

スクリプトの一覧例

ニフティクラウドタイマーの作成

次に、スクリプトを実行するためのタイマーを作成します。例では、タイマーの実行間隔は15分毎にしています。

タイマーの作成例

タイマーの実行間隔を設定後、スクリプト実行の設定で、対象となるRDB/NASやMackerelサービスの設定が必要になります。スクリプト実行リクエストのBodyに監視対象の情報を入力します。(Body以外は {} を入力します)

タイマーの設定例

たとえば、east-1のmysqltestという名前のRDBに対して、「CPUUtilization」と「FreeableMemory」のモニタリング値をMackerelに連携する場合は、Bodyに下記のように設定します。

{
  "niftycloudService": "rdb",
  "mackerelService": "niftycloud_rdb",
  "region":"east-1",
  "instanceName":"mysqltest",
  "metricNames":["CPUUtilization", "FreeableMemory"],
  "period":"20"
}

また、east-1のnastestという名前のNASに対して、「FreeStorageSpace」のモニタリング値をMackerelに連携する場合は、Bodyに下記のように設定します。

{
  "niftycloudService": "nas",
  "mackerelService": "niftycloud_nas",
  "region":"east-1",
  "instanceName":"nastest",
  "metricNames":["FreeStorageSpace"],
  "period":"20"
}

スクリプトのBodyに渡す各パラメータの設定内容は下記のとおりです。

パラメータ名 設定内容
niftycloudService ニフティクラウドのサービスID。RDBのときはrdb、NASのときはnas
mackerelService Mackerel上で作成したサービス名
region 対象のRDB/NASのリージョン
instanceName 監視対象とするRDB/NAS名称
metricNames 監視するメトリクスの名称の一覧(配列)「RDBのモニタリングAPI」ないしは「NASのモニタリングAPI」に記載されている MetricName取得対象データ名 を参照ください
period データを連携する期間(分)。(スクリプト実行時刻 - period)~ (スクリプト実行時刻)の期間でデータを取得します

periodにはデータを取得する期間(分)を設定します。タイマーの実行間隔と同じ値だと、スクリプト実行時間などの要因でデータ点が欠ける可能性があります。そのため、例では実行間隔より少し長めの20分に設定しています。

ちなみに、Mackerelに監視データを登録する際、重複したデータはまとめられるので、余分にデータを連携する分には問題ありません。

同様に、RDB/NASごとにタイマーを作成すれば、簡単に必要なモニタリング値をMackerelに連携することができます。

Mackerelで監視

上記で作成したタイマー、スクリプトが実行されると、Mackerel上で下記のように表示されます。

Mackerelの表示例

Mackerelにデータを連携しているため、このデータをもとに監視ルールを設定したり、グラフを外部に連携したりと、Mackerelの便利な機能を活用することも可能になります。

サービスメトリックの上限について

注意すべき点は、プランによって登録可能なサービスメトリックには限りがあることです。現在、「Free(ニフティクラウド特別)プラン」では 5まで、「Standardプラン」では200まで、さらに追加する場合はオプション料金が発生いたします。

上記のスクリプトでRDB/NASのモニタリング値を連携する場合、台数 × モニタリング項目の数だけサービスメトリックを使用します。たとえば、上記のRDBの例だと、1台のRDBに対して「CPUUtilization」と「FreeableMemory」を連携しているため、2つ分のサービスメトリックが必要です。プランに応じたサービスメトリック数にはご注意ください。

まとめ

今回の例では、サーバーを一切構築せずに、ニフティクラウドスクリプトとタイマーを利用して、RDB/NASのモニタリングAPIを実行し、Mackerelに連携することができました。

ニフティクラウドスクリプトでは、 niftycloud-auth というニフティクラウドのAPIを叩くためのライブラリが標準で利用できます。そのため、今回のコードも簡単に作成することができました。また、RDB/NASのモニタリングAPI以外にも、ニフティクラウドスクリプトからニフティクラウドのAPIなどを実行することも可能です。そのため、APIの実行含め、利用者の用途に応じてさまざまなスクリプトを作成・実行することができます。

みなさんもぜひ、ニフティクラウドスクリプトを活用して、便利なスクリプトを作成し、システム構築に生かしていただけばと思います。