マルチアカウント構成でDynamoDB Streamsのイベントを別アカウントのLambdaで処理したい、という場面、割と出てくると思います。自分も最近ちょっとしたSaaS的な構成を考えていて「あ、これクロスアカウントになるじゃん」となって調べたのがきっかけです。
調べてみたら2026年1月にAWSからちゃんとしたアップデートが来ていて、以前と比べてかなりシンプルに設定できるようになっていたのでまとめておきます。PythonでのLambdaハンドラ例も一緒に整理します。
- クロスアカウント連携が従来なぜ面倒だったか
- 2026年1月のアップデートで何が変わったか
- 設定の全体像(アカウントA・B それぞれで何をするか)
- DynamoDB Streams 側のリソースベースポリシー設定
- Lambda側のIAMロール・イベントソースマッピング設定
- PythonでのLambdaハンドラ実装例
- ハマりそうなポイントと注意事項
これまでのクロスアカウント連携はなかなかつらかった
そもそも、AWS Lambda と DynamoDB Streams を組み合わせてクロスアカウントでトリガーする、というのは昔からやろうと思えばできたんですが、正直かなり迂回路が必要でした。
代表的な方法としては:
- DynamoDB Streams → 同アカウントのLambda → Kinesis Data Streams → 別アカウントのLambda、という中継構成を作る
- クロスアカウントIAMロールを使ったカスタムリレーインフラを自前で構築する
「シンプルにデータの変更を別アカウントで処理したいだけなのに…」という気持ちになりがちでした。余談ですが、こういう「やりたいことはシンプルなのに構成が複雑になる」系のAWSあるある、けっこう好きじゃないです(好きじゃないけど向き合う羽目になる)。
2026年1月のアップデートで何が変わったか
2026年1月15日、AWSはLambdaがDynamoDB Streamsへのクロスアカウントアクセス(クロスアカウントのイベントソースマッピング)をサポートすることを発表しました。これにより、DynamoDB Streamにリソースベースポリシーを設定することで、あるアカウントのLambda関数が別アカウントのDynamoDB Streamから直接イベントを読み取れるようになっています。
一言でいうと、DynamoDB Streams にリソースベースポリシーを設定して、クロスアカウントのイベントソースマッピング(ESM)を作れるようになったということです。
これにより:
- 中継用のKinesisやカスタムインフラが不要になる
- 取り込み・フィルタリング・配信・再試行・エラー処理は、同じアカウントのイベントソースマッピングと同様にLambdaが管理してくれる
- クロスアカウントアクセスのための「追加料金」が別途発生するわけではなく、基本的には標準の Lambda 料金が中心(※DynamoDB Streams側の
GetRecords課金は、Lambdaトリガー経由なら課金されない扱い)
この機能は、すべてのAWS Commercialリージョンと、AWS GovCloud (US) リージョンで利用可能です。
全体のアーキテクチャとセットアップの流れ
今回の構成はこういうイメージです。
- アカウントA(111111111111):DynamoDBテーブル+Streams が存在するアカウント(データオーナー側)
- アカウントB(222222222222):Lambdaが存在するアカウント(処理側)
セットアップの大まかな流れはこうです:
- アカウントAのDynamoDBテーブルでStreamsを有効化し、ストリームARNを取得する
- アカウントAのDynamoDB Streamsにリソースベースポリシーを設定し、アカウントB側からの読み取りを許可する
- アカウントBでLambda実行ロールを作成し、必要な権限ポリシーをアタッチする
- アカウントBで、アカウントAのDynamoDB Streamsをイベントソースにしたイベントソースマッピングを作成する
前提として、DynamoDB Streamsのリソースベースポリシーはコンソールからも設定できます(Permissionsタブから作れます)。イベントソースマッピングもコンソールで作成でき、クロスアカウントのStream ARNを「DynamoDB table」の入力欄に貼り付ける形になります。CLIで統一したい場合はもちろんCLIでもOKです。
DynamoDB Streams 側の設定(アカウントA)
Streamsの有効化とARN取得
まずアカウントAでDynamoDBテーブルのStreamsを有効にします。ストリームビュータイプは NEW_AND_OLD_IMAGES にしておくと変更前後の値が両方取れて便利です。
# アカウントAで実行
aws dynamodb update-table \
--table-name MyTable \
--stream-specification StreamEnabled=true,StreamViewType=NEW_AND_OLD_IMAGES \
--region ap-northeast-1
# ストリームARNを確認
aws dynamodb describe-table \
--table-name MyTable \
--query "Table.LatestStreamArn" \
--region ap-northeast-1
ストリームにリソースベースポリシーを設定する
次が今回のキモです。ストリームのリソースベースポリシーで、アカウントB側(Lambdaの実行ロール等)に対して読み取り権限を付与します。
まず、ポリシードキュメントを stream-policy.json として用意します。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::222222222222:role/lambda-cross-account-role"
},
"Action": [
"dynamodb:DescribeStream",
"dynamodb:GetRecords",
"dynamodb:GetShardIterator",
"dynamodb:ListStreams"
],
"Resource": "arn:aws:dynamodb:ap-northeast-1:111111111111:table/MyTable/stream/2026-01-15T00:00:00.000"
}
]
}
ポイントは、プリンシパルにアカウントIDを広く許可するのではなく、特定のロールARNに絞ること。最小権限の原則を守る意味でも、ここはきちんと絞っておくのが安心です。
ポリシーを適用するCLIはこうです(PutResourcePolicy は反映が整うまで少しラグることがあるので、直後に検証して「あれ?」となったら数十秒待つのが無難です)。
# アカウントAで実行
STREAM_ARN="arn:aws:dynamodb:ap-northeast-1:111111111111:table/MyTable/stream/2026-01-15T00:00:00.000"
aws dynamodb put-resource-policy \
--resource-arn "$STREAM_ARN" \
--policy file://stream-policy.json \
--region ap-northeast-1
Lambda側の設定(アカウントB)
Lambda実行ロールの作成と権限付与
アカウントBでLambdaが使うIAMロールを作ります。AWSマネージドポリシー AWSLambdaDynamoDBExecutionRole をアタッチすることで、DynamoDB Streamsの読み取り(DescribeStream/GetRecords/GetShardIterator/ListStreams)とCloudWatch Logs出力に必要な権限がまとまって付与されます。
# アカウントBで実行
# 信頼ポリシー(Lambda用)
cat > trust-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
# ロール作成
aws iam create-role \
--role-name lambda-cross-account-role \
--assume-role-policy-document file://trust-policy.json
# マネージドポリシーをアタッチ
aws iam attach-role-policy \
--role-name lambda-cross-account-role \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaDynamoDBExecutionRole
イベントソースマッピングの作成
Lambda関数のデプロイが済んだら、アカウントBのCLIからアカウントAのStream ARNを指定してイベントソースマッピングを作成します。
# アカウントBで実行
STREAM_ARN="arn:aws:dynamodb:ap-northeast-1:111111111111:table/MyTable/stream/2026-01-15T00:00:00.000"
aws lambda create-event-source-mapping \
--function-name CrossAccountDynamoDBHandler \
--event-source-arn "$STREAM_ARN" \
--starting-position LATEST \
--region ap-northeast-1
--starting-position は LATEST か TRIM_HORIZON が選べます。イベントを取りこぼしたくない場合は TRIM_HORIZON を指定するのが無難で、LATEST だとイベントソースマッピング作成後の新しいレコードから処理が始まる挙動になります。
PythonでのLambdaハンドラ実装例
クロスアカウントといっても、AWS Lambda のハンドラ自体は普通のDynamoDB Streams処理とまったく同じです。event["Records"] を順番に処理するだけ。
import json
import logging
from boto3.dynamodb.types import TypeDeserializer
logger = logging.getLogger()
logger.setLevel(logging.INFO)
deserializer = TypeDeserializer()
def deserialize(dynamo_item: dict) -> dict:
"""DynamoDB形式の型注釈を通常のPython辞書に変換する"""
return {k: deserializer.deserialize(v) for k, v in dynamo_item.items()}
def lambda_handler(event, context):
for record in event["Records"]:
event_name = record["eventName"] # INSERT / MODIFY / REMOVE
dynamodb_data = record["dynamodb"]
new_image = dynamodb_data.get("NewImage")
old_image = dynamodb_data.get("OldImage")
new_item = deserialize(new_image) if new_image else None
old_item = deserialize(old_image) if old_image else None
logger.info(f"EventName: {event_name}")
if event_name == "INSERT":
logger.info(f"新規追加: {json.dumps(new_item, ensure_ascii=False, default=str)}")
# 例: 別サービスへの通知や集計処理
elif event_name == "MODIFY":
logger.info(f"変更前: {json.dumps(old_item, ensure_ascii=False, default=str)}")
logger.info(f"変更後: {json.dumps(new_item, ensure_ascii=False, default=str)}")
# 例: 差分チェックして必要な処理だけ実行
elif event_name == "REMOVE":
logger.info(f"削除: {json.dumps(old_item, ensure_ascii=False, default=str)}")
# 例: 監査ログへの書き込み
return {"statusCode": 200}
TypeDeserializer を使っているのは、DynamoDB Streamsのイベントデータが {"S": "hello"} や {"N": "42"} といったDynamoDB固有の型表現で来るためです。これをそのままアプリ側で扱うのは面倒なので、最初にフラットな辞書に変換しています。
本番では部分的バッチ失敗(Partial Batch Response)の考慮が必要で、AWS Powertools for Lambda(Python対応)の Batch Processor ユーティリティを使うと実装が楽になります。まずはこの基本構造を理解しておくと良いかと思います。
ハマりそうなポイントと注意事項
設定していて気になったところをまとめます。
DynamoDB テーブルとLambdaは同一リージョンである必要がある
クロスアカウントはOKですが、クロスリージョンは不可です。アカウントAのDynamoDB Streamsがap-northeast-1にあるなら、アカウントBのLambdaもap-northeast-1に置く必要があります。マルチリージョン構成を考えている場合はここが制約になります。
課金まわり(DynamoDB Streams側は基本無料、Lambdaは通常課金)
DynamoDB Streamsは GetRecords が課金対象になり得るんですが、Lambdaトリガー(イベントソースマッピング)が行う GetRecords は課金されない扱いです。一方で、Lambdaの実行(呼び出し回数・実行時間など)は通常どおり課金されます。「クロスアカウントだからDynamoDB側に追加請求が増える」みたいな話ではなく、基本はLambda側の実行コストを見積もる感じになるはずです。
CloudTrailは両アカウントに記録される(可能性が高い)
クロスアカウントアクセスは、リソースオーナー側(アカウントA)での監査が重要になるので、CloudTrailのイベントも含めて「誰が何を読んだか」を追える状態にしておくのが安心です。
IAM Access Analyzerで設定を確認しておく
リソースベースポリシーを設定すると、IAM Access Analyzerが外部アクセスの検出結果として上げてくれます。DynamoDBコンソールのPermissionsタブからプレビュー/確認できるので、ポリシーが意図したアクセスのみを許可しているかチェックしておくと安全です。
同じStreamを読むコンシューマ数は増やしすぎない
DynamoDB Streamsの設計上、同時に読むコンシューマ(プロセス)数は、シングルリージョンの通常テーブルなら最大2程度に抑えるのが推奨で、超えるとスロットリングが起きやすくなります。グローバルテーブルの場合はさらに厳しめで、同時リーダーは1に抑えるのが推奨されています。
まとめ
- 2026年1月15日のアップデートで、DynamoDB Streams にリソースベースポリシーを設定して、クロスアカウントのLambdaトリガー(イベントソースマッピング)が組めるようになった
- 以前必要だった中継インフラ(Kinesis経由など)が不要になり、AWS Lambda クロスアカウント DynamoDB Streams 連携の構成がシンプルになった
- DynamoDB側にリソースポリシーを設定 → Lambda実行ロールに
AWSLambdaDynamoDBExecutionRoleをアタッチ → イベントソースマッピングを作成、が基本の流れ - クロスリージョンは不可。同一リージョン内の別アカウントで使う
- PythonのLambdaハンドラは通常のDynamoDB Streams処理と変わらない。
TypeDeserializerを使うと型変換が楽になる - Streamsの「同時コンシューマ数」は増やしすぎるとスロットリングしやすいので注意
自分もまだ本番投入したわけではなく、検証しながら整理した内容なので、もし間違いがあればやさしく教えていただけると助かります。マルチアカウント構成を考えている方の参考になれば!

