RDS のイベントログを Amazon SNS 経由で Mackerel のグラフアノテーション登録する PoC を作る #mackerel

Amazon RDS のイベントは Amazon SNS 経由で通知出来ます。SNS 通知は AWS Lambda で受けることができます。Lambda は WebAPI を叩くことができます。Mackerel の API を叩いてグラフアノテーションを登録することができます。あとは。。。もうお分かりですね?
2019.12.18

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

この記事は Mackerel Advent Calendar 2019 の 18 日目の記事です。

みなさん、Mackerel で監視してますか!(挨拶
みなさん、Mackerel でAWS インフラを監視してますか!(再挨拶

AWS の Multi-AZ な RDS を運用していると、たまに「あれ、いつのまにか failover している。。。?」と気付くときがありますよね。
Design for failure なので理論上はfailover してても別に大したことはない(はずな)のですが、実際のところは AZ 間通信のレイテンシの差がシビアな環境など、failover した事実に気付きたいユースケースは存在します。

Mackerel で RDS を監視する場合は AWS インテグレーションを使うのが王道と思いますが、その場合だと「イベント」として、直近起きたイベントが表示されます。

知るだけならこれでも十分とも言えるのですが、やっぱりこれがグラフにオーバーレイ表示されてたりするとカッコイイので、以下を組み合わせてそういう環境を作ってみました。

  • Mackerel のグラフアノテーション
  • Amazon RDS のイベント Amazon SNS 通知
  • SNS から通知を受け取って WebAPI をキックする AWS Lambda 関数

簡単に図にするとこんな感じです。

そして最終的に、こんな感じになりました。

実用性は正直わかりませんが、なんかスクショ映えしますね!
今回は RDS をメインターゲットにしていますが、当然他の、Amazon SNS 経由で投稿されるようなイベントを Mackerel へ送信する場合にも応用できると思います。

仕様

ここでは、以下を目的とします。

  • Amazon RDS の特定のインスタンスイベントを、Mackerel のグラフアノテーションに送信する
  • 該当 RDS は既に Mackerel で監視されていて、どこかのサービス・ロールに属しているものとする
  • 送信先のサービス・ロールは上記のものに決め打つ
  • あくまで PoC。本格運用目的ではない

特に 4 番目の理由のため、本来ならパラメータストアを使うべき API キーの保存も環境変数で済ませています。
また、対象となる RDS の構築や、Mackerel での監視はここでは触れません。以下の説明の都合で、既に Mackerel の AWS インテグレーションにて監視が進んでいるものとします。所属しているサービスはRDS、ロールもRDS。グラフアノテーションを投稿するための Mackerel のAPI キーも既に払い出されているものとします(Write 権限が必要です)。

なお今回は、以下の手順は AWS マネジメントコンソールを使って行います。

準備

以下のステップが必要です。

  1. RDS イベントを Lambda へ渡すためのSNS トピックを作成する
  2. RDS 側でイベントサブスクリプションの設定を行う
  3. SNS トピックから受け取ったイベントを Mackerel へ送信するLambda 関数を作成する

それでは順に見ていきましょう。

Amazon SNS トピックを作成する

ここは特に難しいことはありません。名前以外は全てデフォルトでよいので、AWS マネジメントコンソールの「Amazon SNS」 > 「トピック」から、「トピック」の作成をクリックして作成しましょう。

ここではトピック名を「rds-to-lambda」としました。

RDS イベントサブスクリプションの設定

RDS のコンソール画面から「イベントサブスクリプション」>「イベントサブスクリプションの作成」をクリックします。
開いた画面で必要なところをうめましょう。

  • 名前 特に指定はありません。ここでは「rds-to-mackerel」としました。
  • ターゲット > 通知の送信先には ARN を選択。通知先 ARN として先ほど作成した SNS トピックrds-to-lambdaをプルダウンメニューから選択します。
  • ソースタイプ ですが、今回は特定のインスタンスイベントを送信するのが目的なので、インスタンスを選択します。
    • 続いて表示されたメニューで「個別に選択 インスタンス」を選択し、プルダウンメニューでインスタンス名を選択します(今回は「database-1」という RDS を選択しました)。
    • 含まれるイベントのカテゴリは、今回は試験ですしとりあえず全てを選択しています。

最後に「作成」をクリックすれば完成です。

Lambda の作成

こちらが一番の山ですが、最初から用意してある sns-message-python ブループリントを元に作成しました。
全て書くと大変な量になるので、要点だけお伝えします。

  • IAM ロールは CloudWatch Logs への読み書きさえあれば良い(SNS に対する権限は特にいらない)
  • 言語は Python3.8 を選択(趣味です)
  • Mackerel の WebAPI はrequestsモジュールを使って蹴る
  • requestsモジュールは Lambda Layer で別途登録(標準では用意されていません)

最後のrequestsモジュール用の Lambda Layer の話だけ簡単にします。
AWS Lambda の開発になれてらっしゃる方には不要な話とは思いますが、関数独自のモジュールを追加する場合にはふたつの方法があります。関数とモジュールを Zip に同梱する方法と、モジュールは Lambda Layer に分けて別途アップロードする方法です。
前者でも構わないのですが、その場合 Lambda のインラインコードエディタが使えなくなります。CI/CD を組んでいたりする場合は、不用意にコードが変更されると困るために使わない方が身のためですが、今回のような PoC のばあいはこれが使えた方がいろいろと楽なのでそうしました。

Lambda Layer として登録するための Zip ファイルは、Docker をつかって以下のやり方で作成しました。python/ディレクトリの中に全ての必要なモジュールが入っていることが肝要です。また Lambda の実行環境は Linux なので、念のためにそれにあわせておきましょう。

docker run -t --rm -v `pwd`:/home \
    python:3.8.0-slim-buster \
    bash -c 'cd /home && pip install requests -t python' \
  && zip -r9 - python > layer.zip \
  && rm -rf python

実際の AWS Lambda のコードは下記になります。これを適当な名前で保存し、作成した SNS トピックrds-to-lambdaから入力されるように設定してください。

import requests
import json
import os
import dateutil.parser
import time

MACKEREL_APIKEY = os.environ.get ('MACKEREL_APIKEY')
MACKEREL_SERVICE = os.environ.get ('MACKEREL_SERVICE')
MACKEREL_ROLE = os.environ.get ('MACKEREL_ROLE')

mackerel_endpoint_url = 'https://api.mackerelio.com/api/v0/graph-annotations'


def lambda_handler (event, context):
    # SNS から届いたメッセージ部を取り出す
    message_json = json.loads (event ['Records'][0]['Sns']['Message'])

    # 必要な要素を取り出す
    source_id = message_json ['Source ID']
    event_message = message_json ['Event Message']
    event_time = message_json ['Event Time']
    id_link = message_json ['Identifier Link']

    # Event Time からアノテーションの From 時刻を出す
    event_time_unixtime = dateutil.parser.parse (event_time).strftime ("%s")
    now = time.time ()

    # リクエストヘッダの組み立て
    headers = {
        'Content-Type': 'application/json',
        'X-Api-Key': MACKEREL_APIKEY,
    }

    # リクエストボディの組み立て
    data = {
        "title": f"{source_id}: {event_message}",
        "description": f"link: {id_link}",
        "from": int (event_time_unixtime),
        "to": int (now),
        "service": MACKEREL_SERVICE,
        "roles": [MACKEREL_ROLE],
    }

    # Kick Mackerel API
    response = requests.post (mackerel_endpoint_url,
                             headers=headers,
                             data=json.dumps (data))
    return

以下の環境変数を、実環境に合わせて設定してください。

  • MACKEREL_APIKEY Mackerel の API キー
  • MACKEREL_SERVICE ここではRDS
  • MACKEREL_ROLE こちらもRDS

以上です!

適当に RDS を再起動して、Mackerel のロールグラフにアノテーションが追加されるかどうか確認してみて下さい。うまく動かないときには SNS や Lambda のメトリクスをみたり、CloudWatch Logs に送信される Lambda のログをみたりしてみてください。

コスト的な話

AWS を使う以上気になるところですが、この仕組み自体の金額は微々たるものです。具体的には AWS Lambda が動いた回数に依るため、RDS がばたばたあがったり落ちたりを100 万回繰り返さない限り問題はないかと思われます(他に Lambda が動く環境であれば、その限りではありません)。

まとめ

Amazon RDS のイベントを Mackerel のグラフアノテーションに転載する PoC を作りました。今回はただ情報を記載するだけですが、もしこれでメールなどの通知が必要であれば、Lambda を改造してmkrコマンドを呼んだり、checkメトリックとして送信したりするなど工夫をすれば良いことになります。

そうなると Lambda も複雑になったり、ステータスを保存したりと処理も重くなってきます。Mackerel では既に何もしなくてもイベント情報が届いているので、そこからキーワードで通知が上げられるようになると最高ですね!

参考

この記事は以下の情報を参考にしました。

おまけ

Mackerel Day 2 は来週開催です! ぼくは非常に個人的な理由によって参加出来ないのですが、面白そうなネタが聞けそうでいまから資料公開が楽しみです。

Mackerel Day #2 - connpass

'19/12/17 現在でまだ若干の空きがあるようなので、余裕のある方は是非ご検討ください!