[アップデート] Amazon RDS イベントサブスクリプションを通じた SNS イベントにメッセージ属性が含まれるようになりました
コンバンハ、千葉(幸)です。
RDS イベントサブスクリプションを通じた SNS イベントにメッセージ属性(Meesage Attributes)が含まれるようになりました。
メッセージ属性が含まれるようになったということは、間に Lambda 関数などを挟まなくても SNS サブスクリプションフィルターが使えるようになったということです。
何が変わったのか
いろいろ詰め込んで描いてみた絵が以下です。
今回のアップデートを理解するためにはいくつか押さえておくべき事項があるので、順に説明します。
- RDS イベントサブスクリプションと EventBridge イベント
- RDS イベントサブスクリプションを通じた SNS イベントの中身
- SNS サブスクリプションフィルターポリシー
RDS イベントサブスクリプションと EventBridge イベント
RDS では、「DB インスタンスが起動した」「フェールオーバーが発生した」といったイベントが記録されます。そしてそれらは「DBインスタンス」「サブネットグループ」といったソースタイプと、「バックアップ」「メンテナンス」といったカテゴリに分類されます。
- Amazon RDS event categories and event messages - Amazon Relational Database Service
- Amazon RDS event categories and event messages - Amazon Aurora
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 トピックを作成します。タイプをスタンダードにし、名称だけ指定して作成しました。
Lambda 関数の作成
続いて SNS トピックのサブスクリプション先の Lambda 関数を作成します。SNS トピックとの連携を想定した設計図sns-message-python
というものが用意されているのでそれを活用します。
関数名を指定し、トリガーに先ほど作成した SNS トピックを選択、コードを修正した上で作成を行います。
ちなみに変更前のデフォルトのコードはこんな感じ。
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 イベントを発生させる
たまたま稼働中の 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
:イベントの発生元リソースの ARNEventID
:イベント ID
大抵の場合は上記の属性のみでフィルタリングは事足りると思いますが、例えばメッセージなど他の項目でフィルタしたい場合は間に何か挟む必要があります。
サブスクリプションフィルターポリシーを設定する
今回のアップデートの醍醐味であるサブスクリプションフィルターを設定してみます。
作成済みサブスクリプション(Lambda 関数のトリガーに SNS トピックを設定した際に自動的に作成された)の編集画面からポリシーを設定します。
設定した内容は以下の通り。「イベント ID がRDS-EVENT-0151
以外である」というフィルタリング条件です。
{ "EventID": [ { "anything-but": [ "RDS-EVENT-0151" ] } ] }
ちなみにRDS-EVENT-0151
とは DB クラスターが開始されたことを表すイベントです。
先ほど停止した DB クラスターを開始し、開始が完了したら再度停止します。初回の分と合わせて 3 つのイベントが記録されました。
Lambda 関数の実行ログを見ると DB クラスターの起動イベントに対応する実行記録がありません。
きちんとサブスクリプションフィルターが効いていることが確認できました。
今回は 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) がお送りしました。
参考
脚注
- 以前は EventBridge イベントの方には EventID が含まれていなかった気がしますが、現在は含まれていますね。 ↩