[アップデート] Amazon RDS イベントサブスクリプションを通じた SNS イベントにメッセージ属性が含まれるようになりました

[アップデート] Amazon RDS イベントサブスクリプションを通じた SNS イベントにメッセージ属性が含まれるようになりました

もう Lambda 関数を挟んで独自にメッセージ属性を付与してあげる必要は無くなりました。
Clock Icon2022.11.17

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

コンバンハ、千葉(幸)です。

RDS イベントサブスクリプションを通じた SNS イベントにメッセージ属性(Meesage Attributes)が含まれるようになりました。

メッセージ属性が含まれるようになったということは、間に Lambda 関数などを挟まなくても SNS サブスクリプションフィルターが使えるようになったということです。

何が変わったのか

いろいろ詰め込んで描いてみた絵が以下です。

今回のアップデートを理解するためにはいくつか押さえておくべき事項があるので、順に説明します。

  • RDS イベントサブスクリプションと EventBridge イベント
  • RDS イベントサブスクリプションを通じた SNS イベントの中身
  • SNS サブスクリプションフィルターポリシー

RDS イベントサブスクリプションと EventBridge イベント

RDS では、「DB インスタンスが起動した」「フェールオーバーが発生した」といったイベントが記録されます。そしてそれらは「DBインスタンス」「サブネットグループ」といったソースタイプと、「バックアップ」「メンテナンス」といったカテゴリに分類されます。

RDS イベントの通知を行う手法としては以下 2 種類があり、両者で扱われるイベントの中身の構造は異なります。

  • RDS イベントサブスクリプション
  • EventBridge イベント

今回のアップデートが関係するのは前者のイベントサブスクリプションのみです。

RDS イベントサブスクリプションを通じた SNS イベントの中身

RDS イベントサブスクリプションは SNS トピックと関連付けて使用します。SNS トピックはメールアドレスや Lambda 関数といったエンドポイントにサブスクライブしてイベントを発行します。

RDS イベントサブスクリプションを通じて発行される SNS イベントの中身は以下の構造になっています。

{
      "Records": [
        {
          "EventVersion": "1.0",
          "EventSubscriptionArn": "arn:aws:sns:us-east-2:123456789012:rds-lambda:21be56ed-a058-49f5-8c98-aedd2564c486",
          "EventSource": "aws:sns",
          "Sns": {
            "SignatureVersion": "1",
            "Timestamp": "2019-01-02T12:45:07.000Z",
            "Signature": "tcc6faL2yUC6dgZdmrwh1Y4cGa/ebXEkAi6RibDsvpi+tE/1+82j...65r==",
            "SigningCertUrl": "https://sns.us-east-2.amazonaws.com/SimpleNotificationService-ac565b8b1a6c5d002d285f9598aa1d9b.pem",
            "MessageId": "95df01b4-ee98-5cb9-9903-4c221d41eb5e",
            "Message": "{\"Event Source\":\"db-instance\",\"Event Time\":\"2019-01-02 12:45:06.000\",\"Identifier Link\":\"https://console.aws.amazon.com/rds/home?region=eu-west-1#dbinstance:id=dbinstanceid\",\"Source ID\":\"dbinstanceid\",\"Event ID\":\"http://docs.amazonwebservices.com/AmazonRDS/latest/UserGuide/USER_Events.html#RDS-EVENT-0002\",\"Event Message\":\"Finished DB Instance backup\"}",
            "MessageAttributes": {},
            "Type": "Notification",
            "UnsubscribeUrl": "https://sns.us-east-2.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-2:123456789012:test-lambda:21be56ed-a058-49f5-8c98-aedd2564c486",
            "TopicArn":"arn:aws:sns:us-east-2:123456789012:sns-lambda",
            "Subject": "RDS Notification Message"
          }
        }
      ]
    }

13 行目がメッセージの中身です。SNS から E メール宛にメッセージを発行する際にはこの中身のみが送信されます。

14 行目が今回のアップデートに関連するメッセージ属性です。

上記のサンプルでは中に値が含まれていません。これが従来までの SNS イベントの中身でしたが、アップデートにより RDS イベントに即した値が入るようになりました。

SNS サブスクリプションフィルターポリシー

SNS トピックのサブスクリプションではフィルターポリシーを使用してメッセージをフィルタリングできます。このサブスクリプションフィルターポリシーの評価対象は SNS メッセージに含まれるメッセージ属性です。

今回のアップデートにより RDS イベント(を通じた SNS イベント)にメッセージ属性が含まれるようになったため、このフィルタリング機能が使えるようになった、というのが大きなポイントです。

間に Lambda 関数を挟まなくてもイベントをフィルタリングできる!

RDS イベントサブスクリプションフィルターを作成する際には「クラスター」「インスタンス」といったソースタイプを選択します。その上で、以下を指定できます。

  • ソース:
    • ソースタイプに合致するすべてのソース
    • 1つ以上の任意のソース
  • イベントカテゴリ:
    • すべてのイベントカテゴリ
    • 1つ以上の任意のカテゴリ

そのため、いちばん狭い範囲で指定しても「特定のソースの特定のイベントカテゴリに属するイベントはすべて通知」という状態になります。

特定のイベントは除外したい、あるいは特定のイベントのみ通知したい、という場合は別の手段でフィルタリングする必要があります。

従来はそれを以下のような構成で行なうのが一般的でした。

(アップデート前は SNS イベントにメッセージ属性が含まれなかったので、)間に Lambda 関数を挟み、Lambda 関数によって独自のメッセージ属性を付与して後段の SNS トピックに渡す、という方式です。以下のエントリに実装例があります。

今回のアップデートにより中間の SNS トピック・Lambda 関数を除外する構成が期待できます。ただ、後述しますがメッセージ属性に含まれる情報は限定的です。フィルタリング条件がそれに合致しない場合は引き続き Lambda 関数の登板が必要です。

やってみた

今回は以下の構成で試してみます。

RDS イベントを通じた SNS イベントのメッセージの中身の確認、サブスクリプションフィルタリングポリシーを設定した際の挙動の確認に重きを置きます。

SNS トピックの作成

まずは適当な SNS トピックを作成します。タイプをスタンダードにし、名称だけ指定して作成しました。

RDS_Event_SNS_topic_create

Lambda 関数の作成

続いて SNS トピックのサブスクリプション先の Lambda 関数を作成します。SNS トピックとの連携を想定した設計図sns-message-pythonというものが用意されているのでそれを活用します。

関数名を指定し、トリガーに先ほど作成した SNS トピックを選択、コードを修正した上で作成を行います。

RDS_Events_Lambda_function_create

ちなみに変更前のデフォルトのコードはこんな感じ。

import json

print('Loading function')


def lambda_handler(event, context):
    #print("Received event: " + json.dumps(event, indent=2))
    message = event['Records'][0]['Sns']['Message']
    print("From SNS: " + message)
    return message

変更後のものはこれです。受信したイベントメッセージをそのままダンプする内容です。

import json

def lambda_handler(event, context):
    print(json.dumps(event))
    return event

設計図から Lambda 関数を作成した場合はテストイベントも用途に応じたものが用意されているのが地味に嬉しいです。

RDS イベントサブスクリプションの作成

後ろのコンポーネントの準備ができたのでイベントサブスクリプションを作成していきます。

サブスクリプション名、ターゲットの SNS トピック、ソースを指定し作成します。

RDS_Event_create_event_subscription

今回はソースタイプをクラスターとし、すべてのクラスターとイベントカテゴリを対象にしてみました。

RDS イベントを発生させる

たまたま稼働中の DB クラスターがあったので、それを停止(一時停止)させてみます。

しばらくすると RDS イベント→ SNS トピック→ Lambda 関数が実行され、CloudWatch Logs に以下のログが記録されていました。

2022-11-17T00:52:05.198+09:00	START RequestId: fc0dd4fd-38b1-49e0-90ca-9cb408cf37c6 Version: $LATEST

2022-11-17T00:52:05.199+09:00	{"Records": [{"EventSource": "aws:sns", "EventVersion": "1.0", "EventSubscriptionArn": "arn:aws:sns:ap-northeast-1:012345678910:Publish-to-Lambda:b54a1670-6021-4da7-9a58-86ba7ff436e1", "Sns": {"Type": "Notification", "MessageId": "862f377d-4fd4-5a82-8156-6c9246b1629a", "TopicArn": "arn:aws:sns:ap-northeast-1:012345678910:Publish-to-Lambda", "Subject": "RDS Notification Message", "Message": "{\"Event Source\":\"db-cluster\",\"Event Time\":\"2022-11-16 15:52:04.443\",\"Identifier Link\":\"https://console.aws.amazon.com/rds/home?region=ap-northeast-1#dbclusters:id=database-1\",\"Source ID\":\"database-1\",\"Source ARN\":\"arn:aws:rds:ap-northeast-1:012345678910:cluster:database-1\",\"Event ID\":\"http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_Events.html#RDS-EVENT-0150\",\"Event Message\":\"DB cluster stopped\"}", "Timestamp": "2022-11-16T15:52:05.043Z", "SignatureVersion": "1", "Signature": "qo30sQiOPS8r1(略)3E3YqjrXPw==", "SigningCertUrl": "https://sns.ap-northeast-1.amazonaws.com/SimpleNotificationService-56(略)85.pem", "UnsubscribeUrl": "https://sns.ap-northeast-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:ap-northeast-1:012345678910:Publish-to-Lambda:bss(略)1", "MessageAttributes": {"Resource": {"Type": "String", "Value": "arn:aws:rds:ap-northeast-1:012345678910:cluster:database-1"}, "EventID": {"Type": "String", "Value": "RDS-EVENT-0150"}}}}]}

2022-11-17T00:52:05.213+09:00	END RequestId: fc0dd4fd-38b1-49e0-90ca-9cb408cf37c6

2022-11-17T00:52:05.213+09:00	REPORT RequestId: fc0dd4fd-38b1-49e0-90ca-9cb408cf37c6 Duration: 14.78 ms Billed Duration: 15 ms Memory Size: 128 MB Max Memory Used: 39 MB

Lambda 関数が受け取ったイベントの中身だけをピックアップすると以下の通り。メッセージ属性が含まれていることが分かりますね。

{
  "Records": [
    {
      "EventSource": "aws:sns",
      "EventVersion": "1.0",
      "EventSubscriptionArn": "arn:aws:sns:ap-northeast-1:012345678910:Publish-to-Lambda:b54a1670-6021-4da7-9a58-86ba7ff436e1",
      "Sns": {
        "Type": "Notification",
        "MessageId": "862f377d-4fd4-5a82-8156-6c9246b1629a",
        "TopicArn": "arn:aws:sns:ap-northeast-1:012345678910:Publish-to-Lambda",
        "Subject": "RDS Notification Message",
        "Message": "{\"Event Source\":\"db-cluster\",\"Event Time\":\"2022-11-16 15:52:04.443\",\"Identifier Link\":\"https://console.aws.amazon.com/rds/home?region=ap-northeast-1#dbclusters:id=database-1\",\"Source ID\":\"database-1\",\"Source ARN\":\"arn:aws:rds:ap-northeast-1:012345678910:cluster:database-1\",\"Event ID\":\"http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_Events.html#RDS-EVENT-0150\",\"Event Message\":\"DB cluster stopped\"}",
        "Timestamp": "2022-11-16T15:52:05.043Z",
        "SignatureVersion": "1",
        "Signature": "qo30sQiOPS8r1WBVHFCvyrAnX6+q7FYGZYKga(略)vP1Mnv/D6KYrovs13E3YqjrXPw==",
        "SigningCertUrl": "https://sns.ap-northeast-1.amazonaws.com/SimpleNotificationService-56e(略)385.pem",
        "UnsubscribeUrl": "https://sns.ap-northeast-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:ap-northeast-1:012345678910:Publish-to-Lambda:b54a1(略)ff436e1",
        "MessageAttributes": {
          "Resource": {
            "Type": "String",
            "Value": "arn:aws:rds:ap-northeast-1:012345678910:cluster:database-1"
          },
          "EventID": {
            "Type": "String",
            "Value": "RDS-EVENT-0150"
          }
        }
      }
    }
  ]
}

ここのメッセージ属性に含まれる内容について公式ドキュメントでの記述は見つけられなかったのですが、今回は以下のみが含まれていました。

  • Resource:イベントの発生元リソースの ARN
  • EventID:イベント ID

大抵の場合は上記の属性のみでフィルタリングは事足りると思いますが、例えばメッセージなど他の項目でフィルタしたい場合は間に何か挟む必要があります。

サブスクリプションフィルターポリシーを設定する

今回のアップデートの醍醐味であるサブスクリプションフィルターを設定してみます。

作成済みサブスクリプション(Lambda 関数のトリガーに SNS トピックを設定した際に自動的に作成された)の編集画面からポリシーを設定します。

RDS_Event_subscription_filter_policy_Simple_Notification_Service

設定した内容は以下の通り。「イベント ID がRDS-EVENT-0151以外である」というフィルタリング条件です。

{
  "EventID": [
    {
      "anything-but": [
        "RDS-EVENT-0151"
      ]
    }
  ]
}

ちなみにRDS-EVENT-0151とは DB クラスターが開始されたことを表すイベントです。

先ほど停止した DB クラスターを開始し、開始が完了したら再度停止します。初回の分と合わせて 3 つのイベントが記録されました。

RDS_Events_RDS_Management_Console

Lambda 関数の実行ログを見ると DB クラスターの起動イベントに対応する実行記録がありません。

RDS_Events_logs_CloudWatch_Management_Console

きちんとサブスクリプションフィルターが効いていることが確認できました。

今回は Lambda 関数向けのサブスクリプションで試しましたが、E メールをエンドポイントとするサブスクリプションでも同様のフィルタリングが実施できます。

ちなみに:EventBridge イベントの場合

冒頭で「今回のアップデートと関係ない」と述べた EventBridge イベントの内容は以下のようなものです。

{
  "version": "0",
  "id": "68f6e973-1a0c-d37b-f2f2-94a7f62ffd4e",
  "detail-type": "RDS DB Instance Event",
  "source": "aws.rds",
  "account": "123456789012",
  "time": "2018-09-27T22:36:43Z",
  "region": "us-east-1",
  "resources": [
    "arn:aws:rds:us-east-1:123456789012:db:my-db-instance"
  ],
  "detail": {
    "EventCategories": [
      "failover"
    ],
    "SourceType": "DB_INSTANCE",
    "SourceArn": "arn:aws:rds:us-east-1:123456789012:db:my-db-instance",
    "Date": "2018-09-27T22:36:43.292Z",
    "Message": "A Multi-AZ failover has completed.",
    "SourceIdentifier": "rds:my-db-instance",
    "EventID": "RDS-EVENT-0049"
  }
}

中身の構造が SNS イベントものとは異なりますね。とは言え取れる内容にそこまで差は無さそうです。 *1イベントルールでもイベントパターンを用いてフィルタリングができます。後続の処理に合わせてイベントサブスクリプションフィルターと使い分けると良さそうです。

終わりに

RDS イベントサブスクリプションフィルターを通じた SNS イベントにメッセージ属性が含まれるようになり、サブスクリプションフィルターポリシーが使えるようになった、というアップデートでした。

特定のイベントだけフィルタリングしたい、という場合に 間に Lambda 関数を挟まなくてよくなったのは嬉しいですね。逆に「絞りたかったけど間に Lambda 関数を挟みたくないから全量流れてくるのを許容していた」という場合にもフィルタリングをするきっかけになりそうです。

要件に応じてご活用ください。

以上、 チバユキ (@batchicchi) がお送りしました。

参考

脚注

  1. 以前は EventBridge イベントの方には EventID が含まれていなかった気がしますが、現在は含まれていますね。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.