Amazon EventBridge にイベントを別リージョンに自動でフェイルオーバーするグローバルエンドポイントが追加されました
イベント駆動アーキテクチャでもリージョン障害に対応したい
こんにちは、のんピ(@non____97)です。
皆さんはイベント駆動アーキテクチャでもリージョン障害に対応したいと思ったことはありますか? 私はあります。
Amazon EventBridgeのイベントはリージョン内で閉じています。そのため、EventBridgeでリージョン障害が発生した場合に、別リージョンにシステムをフェイルオーバーしたとしても、障害が発生してからフェイルオーバーが完了するまでに発生したイベントを拾うことができません。
今回、Amazon EventBridge にイベントを別リージョンに自動でフェイルオーバーするグローバルエンドポイントが追加されました。
グローバルエンドポイントを使うことによって、プライマリリージョンに障害が発生した場合にも、イベントがセカンダリリージョンに自動でフェイルオーバーされます。Amazon EventBridgeのアーカイブ機能と組み合わせることで、障害が発生してからフェイルオーバーが完了するまでのイベントをセカンダリリージョンで拾うことができ、サービス中断の影響を最小限にすることが可能です。
いきなりまとめ
- グローバルエンドポイントを使うことで、イベントの配信先をセカンダリリージョンのイベントバスに自動でフェイルオーバーできる
- イベントを常時セカンダリリージョンにレプリケーションすることも可能
- フェイルオーバーするか否かはCloudWatchアラーム (Route 53ヘルスチェック経由) で判断
- グローバルエンドポイントにPutEventsをする場合は、AWS Common Runtime (CRT) ライブラリが必要
- グローバルエンドポイント使用時の追加料金はなし
- グローバルエンドポイントは東京リージョンを含む18リージョンで利用可能
- グローバルエンドポイントは2022/4/25時点ではカスタムイベントのみに対応
- 「S3バケットにオブジェクトが追加された」や「EC2インスタンスが起動した」などの各AWSサービスからのイベントには対応していない
グローバルエンドポイントの特徴
グローバルエンドポイントの仕組み
グローバルエンドポイントの仕組みはAWS公式ブログに記載があります。
グローバルエンドポイントはマネージドのAmazon Route 53 DNSエンドポイントです。グローバルエンドポイントは以下2つの仕組みによって動作します。
- イベントがAmazon EventBridgeに取り込まれてから、ルールの中でターゲットが最初に呼び出されるまでの処理時間のメトリクスである
IngestionToInvocationStartLatency
を用いて、Amazon EventBridgeサービスの状態を判断する - グローバルエンドポイントは
IngestionToInvocationStartLatency
からプライマリリージョンのサービスの状態を判断し、イベントをプライマリリリージョンもしくはセカンダリリージョンのイベントバスにルーティングする
フェイルオーバーのトリガーはプライマリリージョンでIngestionToInvocationStartLatency
を監視しているCloudWatchアラームと連携するRoute 53ヘルスチェックです。
以下はCloudWatchアラームがRoute 53ヘルスチェックを介してフェイルオーバーをトリガーし、グローバルエンドポイントがイベントの配信先をプライマリリージョンのイベントバスからセカンダリリージョンのイベントバスに変更する際の図です。
抜粋 : Introducing global endpoints for Amazon EventBridge | AWS Compute Blog
フェイルオーバーといえば、気になるのはRTOとRPOです。
アラーム設定の規定のガイダンスに従ってグローバルエンドポイントを使うと、RTOとRPOが360秒で最大420秒になるようです。
Recovery Time & Recovery Point Objectives
The Recovery Time Objective (RTO) is the time that it takes for the secondary Region to start receiving events after a failure. For RTO, the time includes time period for triggering CloudWatch alarms and updating statuses for Route 53 health checks. The Recovery Point Objective (RPO) is the measure of the data that will be left unprocessed during a failure. For RPO, the time includes events that are not replicated to the secondary Region and are stuck in the primary Region until the service or Region recovers. With global endpoints, if you follow our prescriptive guidance for alarm configuration, you can expect the RTO and RPO to be 360 seconds with a maximum of 420 seconds.
グローバルエンドポイントはオプションでイベントを常時セカンダリリージョンにレプリケーションすることも可能です。
イベントレプリケーションはグローバルエンドポイントを設定する場合のベストプラクティスとして、以下の2つの理由から有効にすることをお勧めされています。
- グローバルエンドポイントが正しく構成されていることを確認するのに役立つため
- 自動でフェイルバックするには、イベントレプリケーションが必要であるため
- イベントレプリケーションを有効にしていない場合、フェイルバックするためにRoute 53ヘルスチェックを手動で「healthy」にリセットする必要がある
イベントレプリケーションはAmazon EventBridgeのアーカイブ機能と組み合わせることで、システムをフェイルオーバー/フェイルバック完了するまでのイベントのロスを減らすことができます。
Amazon EventBridgeのアーカイブ機能の説明は以下記事をご覧ください。
グローバルエンドポイントの料金
グローバルエンドポイント使用時の追加料金は発生しません。
ただし、2022/4/25時点ではグローバルエンドポイントはカスタムイベントのみに使用できます。そのため、グローバルエンドポイントを使用する場合はPUTしたカスタムイベント分の課金が発生します。
また、イベントレプリケーションを有効化すると、セカンダリリージョンでもカスタムイベント分の課金が発生します。
使用できるリージョン
グローバルエンドポイントは東京リージョン、大阪リージョンを含む18リージョンで利用可能です。
- us-east-1 (バージニア北部)
- us-east-2 (オハイオ)
- us-west-1 (北カリフォルニア)
- us-west-2 (オレゴン)
- ca-central-1 (カナダ中部)
- eu-central-1 (フランクフルト)
- eu-west-1 (アイルランド)
- eu-west-2 (ロンドン)
- eu-west-3 (パリ)
- eu-south-1 (ミラノ)
- eu-north-1 (ストックホルム)
- ap-south-1 (ムンバイ)
- ap-northeast-1 (東京)
- ap-northeast-2 (ソウル)
- ap-northeast-3 (大阪)
- ap-southeast-1 (シンガポール)
- ap-southeast-2 (シドニー)
- sa-east-1 (サンパウロ)
やってみた
検証の構成
検証の構成は以下の通りです。
グローバルエンドポイントを介して、正しくイベントの配信先をセカンダリリージョン(ap-northeast-1
)のイベントバスに自動でフェイルオーバーできるか確認します。
グローバルエンドポイントとCloudWatchアラーム、Route 53ヘルスチェック以外のリソースはAWS CDKでデプロイします。
こちらのコードのリポジトリは以下になります。
npx cdk deploy --all
で各種リソースをデプロイします。
グローバルエンドポイントの作成
それでは、グローバルエンドポイントを作成します。
EventBridgeのコンソールからイベント
-グローバルエンドポイント
-エンドポイントを作成
をクリックします。
まず、エンドポイントの名前とプライマリリージョンとセカンダリリージョンのイベントバスの設定を行います。
名前はglobal-endpoints-test
で、プライマリリージョンはus-east-1
のglobalEndpointsBus
、セカンダリリージョンはap-northeast-1
にしました。
なお、セカンダリリージョンのイベントバスは、プライマリージョンのイベントバスと同じ名前である必要があるので注意が必要です。
グローバルエンドポイントを作成する際には、Route 53ヘルスチェックを指定する必要があります。今回は新しくRoute 53ヘルスチェックを作成します。
新しくRoute 53ヘルスチェックを作成する場合は、新しいヘルスチェック
をクリックします。
すると、CloudFormationのスタック作成画面が表示されます。
今回はTreat missing data asのみデフォルトのbreaking
からmissing
に変更して、スタックの作成
をクリックします。
なお、Treat missing data asで選択できる各値の意味は以下の通りです。
breaking
: 欠落データを不正 (しきい値を超えている)として処理noBreaching
: 欠落データを適正 (しきい値を超えていない) と処理ignore
: 欠落データを無視(アラーム状態を維持する)として処理missing
: 欠落データを見つかりませんとして処理
デフォルトのbreaking
だと、CloudWatchアラームが最初からアラーム状態となり正しく検証ができないので、missing
を選択しました。
スタックの作成が完了すると、CloudWatchアラームとRoute 53ヘルスチェックの2つのリソースが作成されたことが確認できます。
CloudWatchアラームを確認すると、スタック作成時のパラメーターで作成されていることが確認できました。
Route 53ヘルスチェックを確認すると、一緒にCloudFormationで作成されたCloudWatchアラームと連携していることが分かります。
グローバルエンドポイントの作成画面に戻り、ロードすると先ほど作成したRoute 53ヘルスチェックを選択できるようになるので、選択します。
最後にイベントレプリケーションの設定です。今回はせっかくなのでイベントレプリケーションを有効化して、セカンダリリージョン(ap-northeast-1
)にイベントがレプリケーションされるかを確認します。設定に問題がなければ作成
をクリックします。
数十秒ほど待つと、グローバルエンドポイントの作成が完了します。
EventBridgeルールを確認すると、説明がManaged Rule for Global Endpoint Event Replication
とイベントレプリケーション用のEventBridgeルールが作成されていました。
また、イベントレプリケーション用のIAMロール(Amazon_EventBridge_Invoke_Event_Bus_593797116
)のポリシーを確認すると、以下のようになっていました。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "events:PutRule", "events:PutTargets", "events:DeleteRule", "events:RemoveTargets" ], "Resource": "arn:aws:events:*:<AWSアカウントID>:rule/globalEndpointsBus/GlobalEndpointManagedRule-*" }, { "Effect": "Allow", "Action": [ "events:PutEvents" ], "Resource": "arn:aws:events:*:<AWSアカウントID>:event-bus/globalEndpointsBus" }, { "Effect": "Allow", "Action": [ "iam:PassRole" ], "Resource": "arn:aws:iam::<AWSアカウントID>:role/service-role/Amazon_EventBridge_Invoke*", "Condition": { "StringLike": { "iam:PassedToService": "events.amazonaws.com" } } } ] }
イベントレプリケーションの確認
それではイベントレプリケーションの確認を行います。
用意したLambda関数は以下のように指定したイベントバスにuuidと現在のタイムスタンプをPutEventsするシンプルなものです。
import { EventBridgeClient, PutEventsCommand, } from "@aws-sdk/client-eventbridge"; import { v4 as uuidv4 } from "uuid"; export const handler = async (events: { EndpointId: string; }): Promise<void | Error> => { const detail = { id: uuidv4(), date: Date.now(), }; const client = new EventBridgeClient({ region: process.env.AWS_REGION!, }); const command = new PutEventsCommand({ EndpointId: events.EndpointId, Entries: [ { Detail: JSON.stringify(detail), DetailType: "put-events-to-global-endpoints", EventBusName: process.env.EVENT_BUS_NAME!, Source: "global-endpoints-test.non-97.net", }, ], }); const response = await client.send(command); console.log(response); return; };
また、グローバルエンドポイントにPutEventsをする場合は、AWS Common Runtime (CRT) ライブラリが必要です。
- awslabs/aws-crt-python: Python bindings for the AWS Common Runtime
- awslabs/aws-crt-nodejs: NodeJS bindings for the AWS Common Runtime.
- awslabs/aws-crt-java: Java bindings for the AWS Common Runtime
今回はNode.jsで動作させるので、npm i aws-crt
をした後、Lambda関数にバンドルするようにします。
AWS CDKでaws_lambda_nodejs
モジュールを使用する場合は、以下のようにCRTライブラリをバンドルするようにLambda関数を定義します。
new nodejs.NodejsFunction(this, "PutEventsToGlobalEndpointsFunction", { entry: path.join( __dirname, "../src/lambda/handlers/put-events-to-global-endpoints.ts" ), runtime: lambda.Runtime.NODEJS_14_X, bundling: { minify: true, sourceMap: true, nodeModules: ["aws-crt"], }, environment: { EVENT_BUS_NAME: globalEndpointsEventBus.eventBusName, REGION: this.region, NODE_OPTIONS: "--enable-source-maps", }, role: new iam.Role(this, "PutEventsToGlobalEndpointsFunctionIamRole", { assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"), managedPolicies: [ iam.ManagedPolicy.fromAwsManagedPolicyName( "service-role/AWSLambdaBasicExecutionRole" ), new iam.ManagedPolicy( this, "PutEventsToGlobalEndpointsFunctionIamPolicy", { statements: [ new iam.PolicyStatement({ effect: iam.Effect.ALLOW, actions: ["events:PutEvents"], resources: [`arn:aws:events:*:${this.account}:event-bus/*`], }), ], } ), ], }), logRetention: logs.RetentionDays.TWO_WEEKS, timeout: Duration.seconds(60), tracing: lambda.Tracing.ACTIVE, });
CRTライブラリをバンドルしない場合は、Lambda関数実行時に以下のようなエラーが出力されます。
{ "errorType": "Error", "errorMessage": "Cannot find module '@aws-sdk/signature-v4-crt'\nRequire stack:\n- /var/task/index.js\n- /var/runtime/UserFunction.js\n- /var/runtime/index.js\nPlease check if you have installed \"@aws-sdk/signature-v4-crt\" package explicitly. \nFor more information please go to https://github.com/aws/aws-sdk-js-v3#functionality-requiring-aws-common-runtime-crt", "trace": [ "Error: Cannot find module '@aws-sdk/signature-v4-crt'", "Require stack:", "- /var/task/index.js", "- /var/runtime/UserFunction.js", "- /var/runtime/index.js", "Please check if you have installed \"@aws-sdk/signature-v4-crt\" package explicitly. ", "For more information please go to https://github.com/aws/aws-sdk-js-v3#functionality-requiring-aws-common-runtime-crt", " at Function.Module._resolveFilename (internal/modules/cjs/loader.js:902:15)", " at Function.Module._load (internal/modules/cjs/loader.js:746:27)", " at Module.require (internal/modules/cjs/loader.js:974:19)", " at require (internal/modules/cjs/helpers.js:101:18)", " at qf.getSigv4aSigner (/node_modules/@aws-sdk/signature-v4-multi-region/dist-cjs/SignatureV4MultiRegion.js:30:64)", " at qf.sign (/node_modules/@aws-sdk/signature-v4-multi-region/dist-cjs/SignatureV4MultiRegion.js:14:25)", " at null.<anonymous> (/node_modules/@aws-sdk/middleware-signing/dist-cjs/middleware.js:13:31)", " at zu.retry (/node_modules/@aws-sdk/middleware-retry/dist-cjs/StandardRetryStrategy.js:51:46)", " at null.<anonymous> (/node_modules/@aws-sdk/middleware-logger/dist-cjs/loggerMiddleware.js:6:22)", " at Runtime.eW (/src/lambda/handlers/put-events-to-global-endpoints.ts:30:20)" ] }
ちなみに、エラーメッセージ内の@aws-sdk/signature-v4-crt
をインストールしてLambda関数にバンドルしても、CRTライブラリがない場合はLambda関数実行時に以下のようなエラーが出力されます。
{ "errorType": "Error", "errorMessage": "AWS CRT binary not present in any of the following locations:\n\t/bin/native/aws-crt-nodejs\n\t/bin/linux-x64/aws-crt-nodejs", "stack": [ "Error: AWS CRT binary not present in any of the following locations:", "\t/bin/native/aws-crt-nodejs", "\t/bin/linux-x64/aws-crt-nodejs", " at null.<anonymous> (/node_modules/aws-crt/lib/native/binding.js:46:11)", " at /var/task/index.js:1:733", " at null.<anonymous> (/node_modules/aws-crt/lib/native/crt.ts:22:1)", " at /var/task/index.js:1:733", " at null.<anonymous> (/node_modules/aws-crt/lib/index.ts:18:1)", " at /var/task/index.js:1:733", " at null.<anonymous> (/node_modules/@aws-sdk/signature-v4-crt/dist-cjs/CrtSignerV4.js:7:19)", " at /var/task/index.js:1:733", " at null.<anonymous> (/node_modules/@aws-sdk/signature-v4-crt/dist-cjs/index.js:4:22)", " at /var/task/index.js:1:733" ] }
us-east-1
のLambda関数でグローバルエンドポイントのIDをイベントとして、テスト
をクリックします。
{ "EndpointId": "wshsmf4fgu.veo" }
実行時のLambda関数のログは以下のようになっていました。
START RequestId: 57681032-dfcd-4113-b5b0-5e1feaee8e9f Version: $LATEST 2022-04-25T04:52:49.515Z 57681032-dfcd-4113-b5b0-5e1feaee8e9f INFO { '$metadata': { httpStatusCode: 200, requestId: '94d45ad6-f3ee-48ba-90a5-cebf544e0e98', extendedRequestId: undefined, cfId: undefined, attempts: 1, totalRetryDelay: 0 }, Entries: [ { ErrorCode: undefined, ErrorMessage: undefined, EventId: '9c186089-1f7a-0c1b-fe57-78176bc686b4' } ], FailedEntryCount: 0 } END RequestId: 57681032-dfcd-4113-b5b0-5e1feaee8e9f REPORT RequestId: 57681032-dfcd-4113-b5b0-5e1feaee8e9f Duration: 12225.95 ms Billed Duration: 12226 ms Memory Size: 128 MB Max Memory Used: 124 MB Init Duration: 290.12 ms XRAY TraceId: 1-62662914-791bd5a1237d5b7b28068756 SegmentId: 3f1a49030d8b7a2f Sampled: true
Lambda関数実行後にus-east-1
のステートマシンを確認すると、Lambda関数の実行が完了したタイミングでステートマシンが実行されていることが確認できました。
Pass
タスクが受け取ったイベントは以下の通りです。
{ "version": "0", "id": "9c186089-1f7a-0c1b-fe57-78176bc686b4", "detail-type": "put-events-to-global-endpoints", "source": "global-endpoints-test.non-97.net", "account": "<AWSアカウントID>", "time": "2022-04-25T04:52:49Z", "region": "us-east-1", "resources": [ "arn:aws:events:us-east-1:<AWSアカウントID>:endpoint/global-endpoints-test" ], "detail": { "id": "80a5f686-aefd-490a-9ade-b7b4b8371497", "date": 1650862357334 } }
ap-northeast-1
のステートマシンを確認すると、us-east-1
のステートマシンとほぼ同じタイミングでステートマシンが実行されていることが確認できました。
Pass
タスクが受け取ったイベントは、us-east-1
のステートマシンのPass
タスクが受け取ったイベントと全く同じでした。
region
もus-east-1
であり、us-east-1
で発生したイベントがap-northeast-1
にレプリケーションできたことが確認できました。
また、CloudWatchアラームを確認すると、IngestionToInvocationStartLatency
が259.5
と しきい値の30,000
を下回っていることからステータスがOK
となっています。
イベントのフェイルオーバー確認
次にイベントのフェイルオーバー確認を行います。
フェイルオーバーの確認を行う際は、グローバルエンドポイントと関連づいているRoute 53ヘルスチェックでヘルスチェックステータスを反転
にチェックを入れて保存します。
すると、Route 53ヘルスチェックが異常状態に遷移します。
続いて、イベントレプリケーションをしているとフェイルオーバーされたイベントなのか、レプリケーションされたイベントなのかが分からなくなるので、イベントレプリケーションを無効化します。
イベントレプリケーションの無効化後、再度us-east-1
のLambda関数でグローバルエンドポイントのIDをイベントとして、Lambda関数を実行します。
実行時のLambda関数のログは以下のようになっていました。
START RequestId: 65f42599-06da-41d6-b2ce-ac051c36abca Version: $LATEST 2022-04-25T05:14:09.524Z 65f42599-06da-41d6-b2ce-ac051c36abca INFO { '$metadata': { httpStatusCode: 200, requestId: '50935dc0-2b8a-4e6b-8b57-fd84cecaf388', extendedRequestId: undefined, cfId: undefined, attempts: 1, totalRetryDelay: 0 }, Entries: [ { ErrorCode: undefined, ErrorMessage: undefined, EventId: 'a9da377f-2775-b457-c424-89a4ed8e62f5' } ], FailedEntryCount: 0 } END RequestId: 65f42599-06da-41d6-b2ce-ac051c36abca REPORT RequestId: 65f42599-06da-41d6-b2ce-ac051c36abca Duration: 13362.91 ms Billed Duration: 13363 ms Memory Size: 128 MB Max Memory Used: 125 MB Init Duration: 291.98 ms XRAY TraceId: 1-62662e13-41f47b936a98e17c3f78f16a SegmentId: 326f56ba7daddf14 Sampled: true
Lambda関数実行後にus-east-1
のステートマシンを確認すると、新しく実行はされていないようでした。
一方、ap-northeast-1
のステートマシンを確認すると、Lambda関数の実行が完了したタイミングでステートマシンが実行されていることが確認できました。
{ "version": "0", "id": "a9da377f-2775-b457-c424-89a4ed8e62f5", "detail-type": "put-events-to-global-endpoints", "source": "global-endpoints-test.non-97.net", "account": "<AWSアカウントID>", "time": "2022-04-25T05:14:09Z", "region": "ap-northeast-1", "resources": [ "arn:aws:events:us-east-1:<AWSアカウントID>:endpoint/global-endpoints-test" ], "detail": { "id": "17feb162-0f19-489a-8b59-95c661061886", "date": 1650863636220 } }
region
もLambda関数が存在しているus-east-1
ではなく、ap-northeast-1
となっており、イベントがセカンダリリージョンにフェイルオーバーしたことが確認できました。
なお、イベントレプリケーションを有効化してから再度us-east-1
のLambda関数を実行すると、us-east-1
のステートマシンが実行されました。
Pass
タスクが受け取ったイベントは以下の通りで、region
がLambda関数が存在しているus-east-1
ではなく、ap-northeast-1
となっていることから、フェイルオーバーした場合は、イベントレプリケーションの方向がセカンダリリージョンからプライマリリージョンになっていることが分かります。
{ "version": "0", "id": "e2ed6473-afd7-14c0-60da-82a9f9a58968", "detail-type": "put-events-to-global-endpoints", "source": "global-endpoints-test.non-97.net", "account": "<AWSアカウントID>", "time": "2022-04-26T00:30:30Z", "region": "ap-northeast-1", "resources": [ "arn:aws:events:us-east-1:<AWSアカウントID>:endpoint/global-endpoints-test" ], "detail": { "id": "9c86fee9-70dd-43e0-9189-e7391c155569", "date": 1650933016829 } }
リージョンレベルでの障害に対応する場合はグローバルエンドポイントを
Amazon EventBridgeのグローバルエンドポイントを紹介しました。
グローバルエンドポイントを上手に使うことで、イベント駆動型アーキテクチャのシステムの可用性を高めることができそうです。
この記事が誰かの助けになれば幸いです。
以上、AWS事業本部 コンサルティング部の のんピ(@non____97)でした!