はじめに
2026年11月11日、AWSからAWS Emulatorが公式にリリースされました。AWS SDKやAWS CLIの呼び出しを、AWSアカウントなしで、ローカルやCI上のエミュレーターに向けられるツールです。公式は、実装されているAPIには有効な呼び出しにAWSと同じ結果を、無効な呼び出しには同じエラーを返し、実装されていないAPIは501で失敗させる、と説明しています。追加料金はかかりません。
初めて聞くと、次のような疑問が出ると思います。
- 本当にAWSと同じように動くのか
- 何が動いて、何が動かないのか
- 自分のIaC(CDKやTerraform)は通るのか
特に3つ目は、実務で気になる点です。公式ドキュメントには、IaCツールへの対応はサービスによって異なると書かれています。
「Fidelity for data plane APIs, and support for infrastructure as code tools such as AWS CloudFormation, Terraform, and the AWS CDK, varies by service.」
出典: https://docs.aws.amazon.com/emulator/latest/userguide/fidelity.html
そこで本記事では、IaCがどこまで動くかを主軸に、Windows 11 + Docker上で実際に試した結果をまとめます。あわせて、エミュレーターを初めて知る人でも全体像がつかめるように、エミュレーターそのものの動きも確かめました。
- CDKでリソースを段階的に追加してデプロイし、どこまで通ってどこで止まるかを表にする
- API Gateway + Lambda + DynamoDBのAPIを作り、実際に呼び出して動くことを確かめる
- エラー、未実装のAPI、認証、複数起動、状態の寿命といったエミュレーターの動きを確かめる
- Terraformでも同様に試す
- 公式が公開している忠実度のデータと既知の問題に、結果を突き合わせる
前提として、AWS SDKやAWS CLI、CDK(またはTerraform)の基本的な使い方と、Dockerの基本操作を想定しています。
AWS Emulatorとは
| 項目 | 内容 | 出典 |
|---|---|---|
| 位置づけ | ローカル/CIでAWS APIを試すための公式エミュレーター | 公式(What's New、2026-11-11) |
| 状態 | メモリ上のみ。停止すると消える | 公式 |
| 未実装のAPI | HTTP 501 NotImplemented を返す | 公式 |
| 料金 | 追加料金なし | 公式 |
| 本番利用 | 不可 | 公式 |
| セキュリティテスト | 不可(IAMポリシー、リソースポリシー、KMSキーポリシー、クロスアカウントは対象外) | 公式 |
| Windows/macOS | Dockerで実行。既製イメージはなく、Dockerfileから自前でビルドする | 公式 |
| リージョン/アカウント | 1つのリージョン・1つのアカウントで使う。複数リージョンを試すならエミュレーターを分ける | 公式 |
図の見方: 自分のコードやツールは、環境変数 AWS_ENDPOINT_URL の有無で行き先が変わります。未設定なら実AWSに、https://127.0.0.1:9297 を設定するとローカルのエミュレーターに向かいます。
AWS Emulatorを使うとどのような良い点があるか
検証に入る前に、AWS Emulatorを使うと何が良くなるのかを整理します。AWS所属者の発信(AWS News Blog、AWS幹部のSwami Sivasubramanian氏がWerner Vogels氏のブログに寄せたゲスト投稿、AWS organizationのDEV.to記事)に書かれている使いどころを、本記事の検証で確かめられたものと対応させました。表の出典名と、参照したURLは次のとおりです。いずれも2026-11-11の公開です。
| 表の出典名 | 内容 | URL |
|---|---|---|
| AWS News Blog | Test your applications against AWS locally with AWS Emulator(著者はAWSのsenior specialist solutions architect) | https://aws.amazon.com/blogs/aws/test-your-applications-against-aws-locally-with-aws-emulator/ |
| Swami氏の投稿 | AWS Emulator and the engineer's job when agents write the code(Werner Vogels氏のブログ All Things Distributed への、Swami Sivasubramanian氏のゲスト投稿) | https://www.allthingsdistributed.com/2026/11/aws-emulator-and-the-engineers-job-when-agents-write-the-code.html |
| DEV.to | How emulators might catch what you might miss(AWS organization配下の記事) | https://dev.to/aws/how-emulators-might-catch-what-you-might-miss-34hd |
| 良い点 | AWS側の発信 | 本記事で確認できたこと |
|---|---|---|
| デプロイ待ちなしで試せる | 単体テストを、デプロイなしでローカルで実行できる(AWS News Blog)。デプロイもAWSアカウントも不要なので、変更ごとにテストを回せる(DEV.to) | CDKのデプロイは12〜18秒で完了した。API呼び出しは1回あたり数ミリ秒〜20ミリ秒台だった(1回の計測、実AWSとの比較なし) |
| コードやIaCを変えずに向け先だけ変えられる | 環境変数でエンドポイントを向けるだけで、アプリケーションのコードは変更不要(AWS News Blog) | boto3のコードは環境変数を外せば実AWSでも動く形のまま動いた。CDKとTerraformも、AWS_ENDPOINT_URL の設定だけでデプロイできた |
| モックが見逃す不具合を見つけられる | SQSのメッセージを削除せずに課金する不具合は、モックでは通るが、エミュレーターでは再配信されてテストが失敗する(DEV.to) | AWS形式のエラー(NoSuchKey、ConditionalCheckFailedException など)が返った。Lambdaのロールの権限不足も AccessDeniedException として検出された。SQSの再配信そのものは未検証 |
| 未実装のAPIがすぐ分かる | 未実装のAPIはHTTP 501を返し、テストはすぐ失敗する(AWS News Blog) | 27個のAPIを試して4つが501になった。黙って成功にならないので、気づける |
| 複数のテストやエージェントを並列に動かせる | エージェントごとに独自のコピー(ポート、キー、状態が別)を持ち、共有のテストアカウントが不要(DEV.to) | 2つ目のエミュレーター(9298)を起動し、リソースも認証情報も1つ目から見えないことを確認した |
| コーディングエージェントが検証を繰り返せる | リソースを作り、結果を確認し、再度試すことを、リージョンでのデプロイ待ちなしで行える。デモはKiroでDynamoDBを操作(AWS News Blog、Swami氏の投稿) | 本記事の検証は、エージェントではなく私がCLIで操作した。エージェント経由での動作は未検証 |
| クラウドの費用や共有リソースの競合を避けられる | 実クラウドでのテストは費用がかかり、並行して動くテストが共有リソースを書き換えて競合する(DEV.to) | 追加料金なし(公式の記載)。本記事の検証はすべてエミュレーター上で行い、実AWSにはリソースを作っていない |
| 毎回まっさらな状態から始められる | (今回読んだ発信では言及なし) | 再起動は2秒で、スタックもLambdaも全て消えた。テストの前提を揃えやすい反面、途中で再起動すると消える |
| 使う前に、どこまで一致するかを確認できる | 忠実度のスコアをサービスごとに「合格数/総数」で公開し、テストもGitHubで公開している(AWS News Blog)。AWSと一致しない既知の挙動もリポジトリに公開している(Swami氏の投稿) | 手順5と手順6で、スコアと既知の問題を自分の検証結果と突き合わせた |
AWS側の発信は、いずれも提供元の説明です。本記事の右列は、Windows 11 + Dockerで1回ずつ試した結果であり、実AWSとの比較は行っていません。
一方で、AWS側の発信は、エミュレーターで何でも済むとは言っていません。
- モックは「自分が制御するロジック」に残し、AWSサービスの挙動に依存する部分にエミュレーターを使う、という使い分けが示されています(DEV.to)
- IAMポリシーやリソースポリシーをAWSと同じように評価しないため、セキュリティのテストには使わない(AWS News Blog)
- 1つのエミュレーターは1アカウントとして動き、クロスアカウントやクロスリージョンの挙動はない(AWS News Blog)
なお、これらの良い点は、使うサービスの忠実度に左右されます。「良い点」が自分の構成でも成り立つかは、次章以降の手順で確かめます。
忠実度(fidelity)の読み方
公式は、エミュレーターとAWSの一致度を「忠実度テスト」で測り、サービスごとの合格数/総数を各リリースで公開しています。
| 用語 | 意味 |
|---|---|
| 忠実度テスト | SDKでワークフローを実行し、結果・状態・エラーをAWSと比較する短いプログラム |
| 忠実度スコア | サービスごとの「合格数 / 総数」(例: 40 / 50) |
| コントロールプレーン | 作成・参照・更新・削除・一覧のAPI |
| データプレーン | サービスの主機能(S3のオブジェクトの取得・保存など) |
スコアは同じテストバージョン同士でのみ比較できます。
検証環境
| 項目 | 内容 |
|---|---|
| OS | Windows 11 |
| 実行方法 | Docker 28.0.4(公式のDockerfile) |
| エミュレーター | 1.0.322(Release date 2026.11.10、commit 7753983)。GitHubの最新リリース v1.0.322 と一致 |
| 検証日 | 2026-11-12 |
| IaC | AWS CDK(CLI 2.1148.1、aws-cdk-lib 2.275.0)、Terraform 1.16.2(AWSプロバイダー 6.73.0) |
| その他 | Python 3.12.7、boto3 1.43.91、AWS CLI 2.39.3、Node.js v22.13.1 |
| 起動オプション |
-p 9297 --telemetry off。ポートは 127.0.0.1 に限定して公開、メモリ上限2GiB |
エミュレーターは1日に複数回リリースされているため、結果は検証日とリリースに依存します。
手順1: エミュレーターを起動する
公式のDockerfileでビルドし、docker run で起動します。Dockerfileは公式の内容のまま使いました(x86_64の場合。Armの場合は、rpmの名前を aarch64 に変えます)。ビルド中にrpm署名の NOKEY 警告が出ましたが、そのまま完了しました。
FROM public.ecr.aws/amazonlinux/amazonlinux:2023-minimal
ADD https://downloads.aws.com/aws-emulator/packages/aws-emulator/LTS/latest/aws-emulator-x86_64.rpm /tmp/aws-emulator.rpm
RUN rpm -i /tmp/aws-emulator.rpm && rm /tmp/aws-emulator.rpm \
&& mkdir -p /home/emulator && chown 1000 /home/emulator
USER 1000
ENV HOME=/home/emulator
EXPOSE 9297
ENTRYPOINT ["aws-emulator", "-b", "0.0.0.0"]
docker build -t aws-emulator .
docker run -d --name emu --memory=2g --memory-swap=2g -p 127.0.0.1:9297:9297 `
-v "$env:USERPROFILE\.aws\emulator:/home/emulator/.aws/emulator" `
aws-emulator -p 9297 --telemetry off
起動ログ(docker logs emu)です。
{"timestamp":"2026-11-11T22:33:58.891517Z","level":"INFO","fields":{"message":"aws-emulator emulator starting"},"target":"aws_emulator"}
This emulator collects usage and error telemetry: disabled (via the embedding configuration) — to turn it back on, remove every opt-out setting and restart
{"timestamp":"2026-11-11T22:33:58.944196Z","level":"INFO","fields":{"message":"request authentication enabled","credentials_file":"/home/emulator/.aws/emulator/9297/emulator_credentials"},"target":"aws_emulator_server"}
{"timestamp":"2026-11-11T22:33:58.963607Z","level":"INFO","fields":{"message":"emulator server information","endpoint_url":"https://127.0.0.1:9297","health_check_url":"https://127.0.0.1:9297/__admin/health","config_file":"/home/emulator/.aws/emulator/9297/config","ca_file":"/home/emulator/.aws/emulator/9297/ca.pem","java_truststore_file":"/home/emulator/.aws/emulator/9297/truststore.p12"},"target":"aws_emulator_server"}
{"timestamp":"2026-11-11T22:33:58.963668Z","level":"WARN","fields":{"message":"the emulator listens on 0.0.0.0, not loopback: other machines that can reach port 9297 can call it; bind 127.0.0.1 (-b 127.0.0.1) unless that is intended","bind_address":"0.0.0.0"},"target":"aws_emulator_server"}
ログから、テレメトリが無効になっていること、リクエストの認証が有効なこと、エンドポイントが https://127.0.0.1:9297 であることが分かります。最後の 0.0.0.0 の警告は、Dockerfileの -b 0.0.0.0 で、コンテナの中では全てのアドレスで待ち受けているために出ます。ホスト側は -p 127.0.0.1:9297:9297 で自分のPCにだけ公開しているので、外部から届く状態ではありません。
続いて、公式の手順どおり環境変数でSDK/CLIを向けます。実認証情報の変数は外しておきます。
$env:AWS_ENDPOINT_URL = "https://127.0.0.1:9297"
$env:AWS_SHARED_CREDENTIALS_FILE = "$env:USERPROFILE\.aws\emulator\9297\emulator_credentials"
$env:AWS_CONFIG_FILE = "$env:USERPROFILE\.aws\emulator\9297\config"
$env:AWS_CA_BUNDLE = "$env:USERPROFILE\.aws\emulator\9297\ca.pem"
$env:AWS_EC2_METADATA_DISABLED = "true"
$env:AWS_DISABLE_HOST_PREFIX_INJECTION = "true"
$env:AWS_REGION = "us-east-1"
疎通確認の結果です。
| 確認 | 結果 |
|---|---|
/__admin/health |
ok |
aws sts get-caller-identity |
Account 000000000000、user/emulator
|
| S3の通常名のバケット作成・put/get・削除 | 成功 |
DynamoDB 表名 ab
|
AWSと同じ形式の ValidationException |
S3 amzn-s3-demo-bucket(公式ドキュメントの例の名前) |
InvalidBucketName で失敗 |
最後の行は意外でした。公式ドキュメントの例に使われている名前では、バケットを作成できません。これはエミュレーターの不具合ではなく、S3のバケット名の規則で amzn-s3-demo- で始まる名前が予約されているためです(出典: https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html )。エミュレーターが、この規則までAWSと同じように検証している、ということでもあります。
つまずきやすい点として、公式に次の注意があります。
-
AWS_ENDPOINT_URLが未設定のシェルでテストを実行すると、見つかった認証情報で本物のAWSを呼ぶ - 環境変数の
AWS_ACCESS_KEY_IDなどが残っているとエミュレーターに拒否される - ポートは
-p 127.0.0.1:9297:9297のように限定する(短縮形はネットワーク全体に公開される)
手順2: CDKを段階的にデプロイする
1つのスタックに、-c stage=N でリソースを段階的に追加していきます。
cdk bootstrap
cdk bootstrap aws://000000000000/us-east-1 は17秒で成功しました。作られたCDKToolkitスタックには、S3バケット、ECRリポジトリ、IAMロール(5つ)、SSMパラメータが含まれ、SSMの /cdk-bootstrap/hnb659fds/version も取得できました。
メモ: CDK CLI 2.1148.1とaws-cdk-lib 2.276.0の組み合わせは、ライブラリが未公開のCLI 2.1148.2を要求して失敗しました。これはCDK側の問題で、エミュレーターとは関係ありません。本記事ではlibを2.275.0に固定しています。
段階ごとの結果
| 段階 | リソース | cdk deploy | 実際に動くか |
|---|---|---|---|
| 1 | S3バケット | 成功(17秒) | head-bucket 成功 |
| 2 | DynamoDB(PAY_PER_REQUEST) | 成功(17秒) |
ACTIVE、put-item/get-item 成功 |
| 3 | SQS + SNS + サブスクリプション | 成功(17秒) | SNS publish がSQSに届いた |
| 4 | IAMロール + Lambda(inline、Node 22) | 成功(12秒) | State=Active だが、invoke は exports is not defined で失敗 |
| 5 | EventBridge(rate 5分)→ SQS | 成功(17秒) | 定期実行が発火し、SQSにイベントが届いた |
| 6 | Step Functions(Pass) | 成功(17秒) | start-execution → SUCCEEDED
|
| 7 | CloudWatch Logs + Alarm | 成功(17秒) | ロググループは作成。Alarmは describe-alarms に出ない |
| 8 | Lambda(Code.fromAsset) |
成功(13秒) | invoke 成功。アセットはステージングバケットに公開された |
| 9 | Lambda + API Gateway(REST、GET) | 3回失敗し、4回目で成功(18秒) |
HelloEmulator が返る(下の「動作確認」) |
| 10 | API Gateway → Lambda → DynamoDB(アイテム登録API) | 2回失敗し、3回目で成功(権限を足して再デプロイ) | POST で保存、GET で取得できた(下の「エミュレーターの動きを確かめる」) |
| destroy | スタック削除 | 成功(12秒) | 残存は Lambda 用のロググループのみ(CDKが DeletionPolicy: Retain で作るため。エミュレーターの問題ではない) |
段階8のスタックを、変更なしで再デプロイしたときの出力です。
CdkSampleStack: deploying... [1/1]
✅ CdkSampleStack (no changes)
✨ Deployment time: 0s
Outputs:
CdkSampleStack.BucketName = cdksamplestack-bucket83908e77-db9d1636caba
CdkSampleStack.FnAssetName = CdkSampleStack-FnAssetDC7A43AF-BEA29F2C2421
CdkSampleStack.FnName = CdkSampleStack-Fn9270CBC0-3ADA671FD9E8
CdkSampleStack.LogGroupName = CdkSampleStack-Lg419948D3-9E819B94EA09
CdkSampleStack.QueueUrl = https://sqs.us-east-1.amazonaws.com/000000000000/CdkSampleStack-Queue4A7E3555-148DCCAAE08B
CdkSampleStack.SmArn = arn:aws:states:us-east-1:000000000000:stateMachine:CdkSampleStack-SM934E715A-EFE2C378FC2A
CdkSampleStack.TableName = CdkSampleStack-TableCD117FA1-A02DF716D6D7
CdkSampleStack.TopicArn = arn:aws:sns:us-east-1:000000000000:CdkSampleStack-TopicBFC7AF6E-7C0061677BC0
デプロイしたスタックの内容は、list-stack-resources でも確認できます。段階8の時点で、テンプレートの18個のリソースがすべて CREATE_COMPLETE でした。ただし、前述のとおり、Alarm は CREATE_COMPLETE でも describe-alarms に出ません。
$ aws cloudformation list-stack-resources --stack-name CdkSampleStack --query "StackResourceSummaries[].[ResourceType,ResourceStatus]" --output table
---------------------------------------------------------
| ListStackResources |
+-----------------------------------+-------------------+
| AWS::CloudWatch::Alarm | CREATE_COMPLETE |
| AWS::S3::Bucket | CREATE_COMPLETE |
| AWS::CDK::Metadata | CREATE_COMPLETE |
| AWS::Lambda::Function | CREATE_COMPLETE |
| AWS::Lambda::Function | CREATE_COMPLETE |
| AWS::Logs::LogGroup | CREATE_COMPLETE |
| AWS::IAM::Role | CREATE_COMPLETE |
| AWS::Logs::LogGroup | CREATE_COMPLETE |
| AWS::IAM::Role | CREATE_COMPLETE |
| AWS::Logs::LogGroup | CREATE_COMPLETE |
| AWS::SQS::Queue | CREATE_COMPLETE |
| AWS::SNS::Subscription | CREATE_COMPLETE |
| AWS::SQS::QueuePolicy | CREATE_COMPLETE |
| AWS::Events::Rule | CREATE_COMPLETE |
| AWS::StepFunctions::StateMachine | CREATE_COMPLETE |
| AWS::IAM::Role | CREATE_COMPLETE |
| AWS::DynamoDB::Table | CREATE_COMPLETE |
| AWS::SNS::Topic | CREATE_COMPLETE |
+-----------------------------------+-------------------+
$ aws cloudwatch describe-alarms
{
"MetricAlarms": [],
"CompositeAlarms": [],
"LogAlarms": []
}
止まった、または気になった点
| 分類 | 内容 | 該当 |
|---|---|---|
| 未実装(501) | 今回のIaCでは遭遇しなかった。別に27個のAPIを試すと4つが501だった(後述) | なし |
| 状態遷移 | DynamoDB は ACTIVE になった |
なし |
| CloudFormationが成功なのに使えない | Alarm がCREATE_COMPLETEなのに describe-alarms に出ない。直接 put-metric-alarm したアラームは一覧に出る |
段階7 |
| Lambda inline コード | inline の exports.handler が index.mjs として扱われ、exports is not defined。Code.fromAsset の index.js では動く |
段階4 |
| 出力値の形式 | SQSのQueueUrlやAPI GatewayのURLが実AWSのホスト名(sqs.us-east-1.amazonaws.com、*.execute-api.us-east-1.amazonaws.com)の形式。エンドポイントが上書きされるのでCLI/SDKの操作は通るが、このURLをcurlやブラウザで開くと実AWSに向かう | 段階3、段階9 |
| Lambda Function URL |
addFunctionUrl で作る権限リソースが、「InvokedViaFunctionUrl プロパティを適用しない」というメッセージで CREATE_FAILED になり、ロールバックした |
段階9(最初の試行) |
| ステートメントIDが長すぎる |
LambdaRestApi の既定(proxy+ のプロキシ)が作る AWS::Lambda::Permission が、「statementId は100文字以下」の検証エラーで失敗した。ステートメントIDにスタック名と長い論理IDが入るため。GETを1つだけ定義する構成や、短いIDに変えて回避した。公式リポジトリの既知issueに同じ問題が記録されている(手順6) |
段階9、段階10 |
| ステージ名の重複 | 同じスタックに2つ目のREST APIを prod ステージで作ると、prod already exists in stack で CREATE_FAILED。ステージ名を変えて回避した。AWSではステージはAPIごとの名前だが、エミュレーターではスタック内で重複を許さない。実AWSでの挙動は未確認 |
段階10 |
| ロールバック後の再deploy | CDKが作るロググループは Retain のため、失敗のロールバックでも残り、再deployで「ロググループが既に存在する」(AlreadyExists) になった。手で削除して再deployした。CDKとCloudFormationの仕様で、エミュレーター固有ではない |
段階9、段階10 |
| Lambdaの権限不足 | 段階10で grantReadWriteData を書き忘れ、LambdaがDynamoDBに AccessDeniedException で拒否された。エミュレーターがLambdaのロールの権限不足を検出した。コードの誤りを見つけるのに役立つ |
段階10 |
Lambda inline コードは、実AWSで同じコードがどう動くかを確認していないため、エミュレーターの差かどうかは断定できません。
動作確認: HelloEmulator が返るか
デプロイが成功しても、動くとは限りません。そこで、Lambdaが HelloEmulator を返し、API Gatewayから呼べることを確認しました。
Lambdaのコード(lambda/hello.js)です。
exports.handler = async () => ({
statusCode: 200,
headers: { 'Content-Type': 'text/plain' },
body: 'HelloEmulator',
});
CDKでは、このLambdaを LambdaRestApi に接続し、ルート(/)に GET を1つだけ定義しています。
| 呼び出し方 | 結果 |
|---|---|
aws lambda invoke |
StatusCode 200、レスポンスのbodyが HelloEmulator
|
aws apigateway test-invoke-method |
status 200、body HelloEmulator
|
curl(Host ヘッダーにAPIのホスト名を指定、接続先はエミュレーター) |
HTTP/1.1 200 OK、content-type: text/plain、本文 HelloEmulator
|
curl(/restapis/<id>/prod/_user_request_/ のパス形式) |
404 UnknownService(このパスでは到達できない) |
curlの実行例です。API Gatewayの呼び出しは、エミュレーターの1つのエンドポイントに対し、Host ヘッダーで対象のAPIを指定します。
curl.exe -i --cacert $env:AWS_CA_BUNDLE `
-H "Host: <restApiId>.execute-api.us-east-1.amazonaws.com" `
https://127.0.0.1:9297/prod/
HTTP/1.1 200 OK
content-type: text/plain
content-length: 13
x-amzn-requestid: 863869a5-ede1-4e56-bf04-151a017052da
date: Wed, 11 Nov 2026 22:58:41 GMT
HelloEmulator
CDKが出力するURL(https://<id>.execute-api.us-east-1.amazonaws.com/prod/)をそのまま開くと実AWSに向かいます。エミュレーターに送るには、上のように接続先を127.0.0.1にして Host ヘッダーでAPIを指定します。
手順3: エミュレーターの動きを確かめる
ここまでは「IaCがデプロイできるか」でした。AWS Emulatorを初めて知る人のために、エミュレーターそのものの動きも、いくつかの観点で確かめました。以降の例は、すべて手順1の環境変数を設定したシェルで実行しています。
1. コードを変えずにエミュレーターへ向く
アプリのコードにエンドポイントも認証情報も書かず、環境変数だけで向け先が決まります。次のPython(boto3)は、環境変数を外せばそのまま本物のAWSでも動くコードです。
import boto3, uuid
s3 = boto3.client("s3"); ddb = boto3.client("dynamodb"); sqs = boto3.client("sqs")
sfx = uuid.uuid4().hex[:8]
s3.create_bucket(Bucket=f"demo-{sfx}")
s3.put_object(Bucket=f"demo-{sfx}", Key="a.txt", Body=b"hello")
print(s3.get_object(Bucket=f"demo-{sfx}", Key="a.txt")["Body"].read())
ddb.create_table(TableName=f"demo-{sfx}", BillingMode="PAY_PER_REQUEST",
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}])
ddb.put_item(TableName=f"demo-{sfx}", Item={"id": {"S": "1"}})
実行結果(1回の計測、ローカルのDocker上、単位はミリ秒)です。
| 操作 | 時間 |
|---|---|
| S3 create_bucket | 18.5 |
| S3 put_object | 3.9 |
| S3 get_object | 2.9 |
| DynamoDB create_table(直後に ACTIVE) | 4.5 |
| DynamoDB put_item | 2.6 |
| SQS create_queue | 22.0 |
| SQS send_message | 1.9 |
| SQS receive_message | 1.8 |
公式は「ミリ秒で返る」としています。今回の計測でも、1回の呼び出しは数ミリ秒から20ミリ秒台でした。実AWSとの比較はしていません。
2. 無効な呼び出しには、AWS形式のエラーを返す
| 呼び出し | 返ったエラー |
|---|---|
| S3 存在しないキーの取得 | NoSuchKey: The specified key does not exist. |
| S3 存在しないバケットの head-bucket | 404 Not Found |
| SQS 存在しないキューの URL 取得 | AWS.SimpleQueueService.NonExistentQueue |
| DynamoDB 存在しないテーブルの describe | ResourceNotFoundException: Requested resource not found: Table: nope not found |
| DynamoDB 条件付き書き込みが条件に合わない | ConditionalCheckFailedException |
| Lambda 存在しない関数の取得 | ResourceNotFoundException: Function not found: arn:aws:lambda:...:function:nope |
モックのように「呼べば成功する」のではなく、実装されているAPIは検証とエラーまで実行します。実AWSとの一致までは確認していませんが、エラーコードはAWSのものと同じ形式です。
3. 実装されていないAPIは、はっきり失敗する
公式は、未実装のAPIは HTTP 501 を返すと書いています。27個のAPIを試したところ、4つが501でした。
| API | 結果 |
|---|---|
athena list-work-groups |
501 NotImplemented(operation ListWorkGroups is scaffolded but not implemented in this emulator) |
translate list-text-translation-jobs |
501 |
comprehend list-entity-recognizers |
501 |
quicksight list-dashboards |
501 |
| 残り23個 | エラーなしで応答(挙動がAWSと一致するかは未確認) |
「エラーなしで返った」ことは、挙動が正しいことを意味しません。ただ、未実装のAPIが黙って成功にならず、テストがすぐ失敗として気づける点は、501の利点です。
4. 認証が必要で、起動ごとに変わる
| 呼び出し | 結果 |
|---|---|
| 署名なしのリクエスト(curl) | 403 UnrecognizedClientException: The security token included in the request is invalid.
|
| 適当なキーで署名したリクエスト | AccessDenied: Sign the request with the emulator credentials |
| エミュレーター再起動のあと | 認証情報のファイルが書き換わる(起動のたびに新しいキーを作る) |
これは、他のWebページやローカルのプログラムが、知らないうちにエミュレーターを呼ぶことを防ぐための設計だと公式に書かれています。
5. 複数のエミュレーターは独立している
ポートを分けて2つ目のエミュレーター(9298)を起動し、確認しました。
| 操作 | 結果 |
|---|---|
| 1つ目(9297)にバケットを作る | 9297 にだけ存在する |
| 2つ目(9298)のバケット一覧 | 空(9297 のリソースは見えない) |
| 2つ目にバケットを作る | 9298 にだけ存在し、9297 には見えない |
| 1つ目の認証情報で2つ目を呼ぶ |
AccessDenied(キーが別) |
エージェントごと、CIジョブごと、ブランチごとにエミュレーターを起動すれば、互いのリソースは見えません。
6. 状態はメモリ上だけで、再起動すると消える
| 再起動前 | 再起動後 |
|---|---|
| スタック2つ(CDKToolkit、CdkSampleStack)、バケット3つ、テーブル3つ | すべて0(スタックもLambdaも消えた) |
再起動は2秒で完了しました。毎回まっさらな状態から始められる反面、テストの途中で再起動すると全て消えます。永続化はありません(公式の記載どおり)。
7. サービスをまたぐアプリが動く(API Gateway → Lambda → DynamoDB)
アイテムを登録・取得する小さなAPIを作りました。POST で受け取った値をLambdaがDynamoDBに保存し、GET で取り出します。
const { DynamoDBClient, PutItemCommand, GetItemCommand } = require('@aws-sdk/client-dynamodb');
const ddb = new DynamoDBClient({});
exports.handler = async (event) => {
if (event.httpMethod === 'POST') {
const { id, text } = JSON.parse(event.body || '{}');
await ddb.send(new PutItemCommand({ TableName: process.env.TABLE_NAME, Item: { id: { S: id }, text: { S: text } } }));
return { statusCode: 201, body: JSON.stringify({ saved: id }) };
}
const id = event.pathParameters.k;
const r = await ddb.send(new GetItemCommand({ TableName: process.env.TABLE_NAME, Key: { id: { S: id } } }));
return r.Item ? { statusCode: 200, body: JSON.stringify({ id, text: r.Item.text.S }) } : { statusCode: 404, body: '{"message":"not found"}' };
};
Lambdaの中からエミュレーターのDynamoDBを呼ぶには、環境変数を明示する必要がありました。エミュレーターは、Lambdaの実行環境にエンドポイントも認証情報も自動では渡しません。確認した値は次のとおりです。
| 項目 | 内容 |
|---|---|
| Lambdaの実行環境 | Node.js(process.version は v22.0.0)。DynamoDBのクライアント(@aws-sdk/client-dynamodb)を読み込めた |
| 既定の環境変数 |
AWS_ENDPOINT_URL なし、認証情報なし |
| 追加した環境変数 |
AWS_ENDPOINT_URL=https://127.0.0.1:9297、AWS_SHARED_CREDENTIALS_FILE、AWS_CONFIG_FILE、NODE_EXTRA_CA_CERTS(いずれもコンテナ内のパス) |
最初のデプロイでは、DynamoDBへの権限付与(grantReadWriteData)を書き忘れました。すると、エミュレーターは、Lambdaのロールの権限不足を検出して、500を返しました。
AccessDeniedException: User: arn:aws:sts::000000000000:assumed-role/CdkSampleStack-AppServiceRole.../CdkSampleStack-App... is not authorized to perform: dynamodb:PutItem on resource: arn:aws:dynamodb:us-east-1:000000000000:table/... because no identity-based policy allows the dynamodb:PutItem action
権限付与を追加して再デプロイしたあとの結果です。
| リクエスト | 結果 |
|---|---|
POST /v1/i({"id":"item-1","text":"HelloEmulator from DynamoDB"}) |
201 Created、{"saved":"item-1"}
|
GET /v1/i/item-1
|
200 OK、{"id":"item-1","text":"HelloEmulator from DynamoDB"}
|
GET /v1/i/nope
|
404 Not Found、{"message":"not found"}
|
aws dynamodb scan(CLIから直接) |
1件、item-1 が保存されている |
これで、リクエスト → API Gateway → Lambda → DynamoDB → 取得、までの一連が動くことを確認できました。流れを図にまとめます。
ただし、公式は、IAMポリシーなどのセキュリティのテストにエミュレーターを使わないよう求めています。今回の権限エラーはエミュレーターが検出してくれましたが、AWSと同じ判定とは限りません(公式は、評価の仕方がAWSとは異なると書いています)。
手順4: Terraformで試す
S3バケットとDynamoDBテーブルの2つを、AWS_ENDPOINT_URL の環境変数だけでエミュレーターに向けました。プロバイダー側には skip_credentials_validation、skip_requesting_account_id、skip_metadata_api_check、s3_use_path_style を設定しています。
| 操作 | 結果 |
|---|---|
| init | 成功 |
| apply | 成功(13秒)。DynamoDBの作成に6秒 |
| destroy | 成功(6秒) |
| apply 直後の plan | DynamoDB で差分が出る(on_demand_throughput の max_read_request_units/max_write_request_units が -1 -> null)。1 to change |
| 再度 apply |
UpdateTable が ValidationException で失敗 |
この差分は、公式リポジトリの既知issueに記録されています(手順6)。原因の切り分けとして、describe-table を直接叩くと、OnDemandThroughput に MaxReadRequestUnits: -1、MaxWriteRequestUnits: -1 が返りました。プロバイダーは、設定していない項目がこの値で返ってくることを差分として扱い、更新を試みますが、更新内容が空のため UpdateTable がエラーになったと考えられます。実AWSでは未設定のテーブルでこの項目がどう返るか、確認していません。
terraform apply の直後に terraform plan を実行したときの出力です。
$ terraform plan
aws_dynamodb_table.t: Refreshing state... [id=tf-emu-sample-table]
aws_s3_bucket.b: Refreshing state... [id=tf-emu-sample-bucket-20261112]
Terraform will perform the following actions:
# aws_dynamodb_table.t will be updated in-place
~ resource "aws_dynamodb_table" "t" {
id = "tf-emu-sample-table"
name = "tf-emu-sample-table"
tags = {}
# (13 unchanged attributes hidden)
- on_demand_throughput {
- max_read_request_units = -1 -> null
- max_write_request_units = -1 -> null
}
# (3 unchanged blocks hidden)
}
Plan: 0 to add, 1 to change, 0 to destroy.
この状態で、もう一度 terraform apply を実行すると、次のエラーで失敗します。
Error: updating AWS DynamoDB Table (tf-emu-sample-table): operation error DynamoDB: UpdateTable, https response error StatusCode: 400, RequestID: (省略), api error ValidationException: At least one of ProvisionedThroughput, BillingMode, UpdateStreamEnabled, GlobalSecondaryIndexUpdates, SSESpecification, ReplicaUpdates, MultiAccountReplicaReady, ReplicaTransitRoleArn, MultiRegionConsistency, DeletionProtectionEnabled, OnDemandThroughput, WarmThroughput or TableClass is required
手順5: 忠実度スコアと突き合わせる
リリース v1.0.322 には、スコアのデータ scores.json.gz(commit 7753983、集計日時 2026-11-11 00:09 UTC)が添付されています。中身は忠実度テスト1件ごとの合否(pass/fail/skipped)とサービスの一覧です。公式がまとめた表ではないため、本記事ではサービスごとに合否を数え直しました。
- 全体: 42,857件(skipped を除く)のうち 24,243件が pass(56.6%)
- 集計方法: 対象サービスが1つだけのテストについて、サービスごとに pass/総数を数えた(複数サービスをまたぐテストは含めていない)
- 注意: 同じテストバージョン同士でのみ比較できる(公式の記載)。私の集計は公式のスコア表ではない
| サービス | pass / 総数 | 割合 | 今回のCDK/Terraformでの結果 |
|---|---|---|---|
| SQS | 299 / 333 | 90% | SNS経由の配信まで動作 |
| ECR | 233 / 287 | 81% | bootstrap で作成された |
| S3 | 965 / 1,220 | 79% | 作成・put/get・アセット公開が動作 |
| DynamoDB | 807 / 1,044 | 77% | CDKは動作。Terraformは再applyが失敗 |
| SNS | 210 / 284 | 74% | 動作 |
| Step Functions | 432 / 589 | 73% | 実行が成功 |
| CloudWatch | 621 / 848 | 73% | Alarm が CFN 経由で見えない |
| CloudWatch Logs | 466 / 642 | 73% | ロググループ作成は動作 |
| IAM | 488 / 691 | 71% | ロール作成は動作 |
| STS | 94 / 169 | 56% | get-caller-identity は動作 |
| Lambda | 365 / 787 | 46% | asset は動作、inline は失敗 |
| SSM | 92 / 452 | 20% | bootstrap のパラメータ取得は動作 |
| CloudFormation | 414 / 2,612 | 16% | 今回のスタックは全て動作 |
スコアが低くても、今回の単純なスタックは動きました。CloudFormationの16%は、テストの多くが複雑な構成(サービスをまたぐもの、ロールバックや更新を伴うもの)を対象にしているためだと考えられますが、内訳までは確認していません。「割合が低い=自分の構成が動かない」とは限らないので、使うAPIの範囲で個別に試すのが確実です。
手順6: 公式リポジトリの既知の問題と突き合わせる
aws/aws-emulator の issues/ には、既知の差異が1件ずつファイルになっています。things-that-do-not-work/(未解決)と things-that-now-work/(解決済み)の2つのフォルダに分かれています。今回の検証で見つけた差異が、すでに記録されているかを調べました。
調べ方は次のとおりです。
- リポジトリの
issues/だけをスパースチェックアウトした(HEADは、今回使ったリリース v1.0.322 と同じコミット 7753983) - 件数は、未解決が6,544件、解決済みが12,708件
- 全ファイルを、今回の差異に関するキーワードで全文検索し、該当ファイルを読んだ
結果です。
| 今回の観察 | 既知のissue | 判定 |
|---|---|---|
DynamoDB の on_demand_throughput が -1 で返り、Terraform で差分が出続ける |
20261108T011128Z-dynamodb-2-net-new-causes-describetable-invents-an.md(未解決)。「DescribeTable が、最大値を要求していないテーブルにも OnDemandThroughput の -1/-1 を返す。CloudFormation、Terraform などのドリフト検出が、そのようなテーブルごとに差分を報告する」と記載 |
一致 |
AWS::Lambda::Permission の statementId が100文字を超える |
20261105T132157Z-cloudformation-8-net-new-root-causes-in-the-684719.md(未解決、原因1)と 20261109T152222Z-apigateway-cloudformation-lambda-cfn-generated-lambda-permission-statementid.md(未解決)。「CloudFormation が生成するステートメントIDを100文字以内に切り詰めない。CDKの RestApi/LambdaRestApi のスタックがすべて失敗する」と記載 |
一致 |
CloudFormation 経由の CloudWatch Alarm が describe-alarms に出ない |
20261023T113357Z-cloudformation-reports-create-complete-for-logs-cloudwatch-ecs.md(未解決)。トレースに、CFNでデプロイした Lambda + Alarm の describe_alarms が空になる確認が含まれる |
一致 |
Lambda のインラインコード(Code.fromInline)が exports is not defined で失敗 |
該当するissueなし。関連として、Node.js ランタイムの実行の忠実度を扱うissueが複数ある。index.handler が CommonJS(exports.handler)でも動くべきという記載(human-lambda-invoke-must-run-nodejs20-x-and-nodejs22-x-handlers.md)はあるが、インライン配置のファイル名についての記載は見つからなかった |
なし(未報告の可能性) |
Lambda Function URL(addFunctionUrl)の権限が InvokedViaFunctionUrl で失敗 |
該当するissueなし。Function URL に関する別のissue(例: 20261029T233239Z-lambda-function-url-configs-return-a-default-invokemode.md)はあるが、InvokedViaFunctionUrl を扱うものはない |
なし(未報告の可能性) |
同じスタックの2つ目のREST APIが prod ステージで失敗(prod already exists in stack) |
該当するissueなし。already exists in stack は、別の用途(物理名の衝突時のメッセージ文言)で複数のissueに登場するが、ステージ名の重複を扱うものはない |
なし(未報告の可能性) |
読み取れること:
- 見つけた差異のうち3件は、すでに公式リポジトリに記録されていました。うち2件(
on_demand_throughput、statementId)には、AWS側の挙動(「CloudFormationはIDを100文字以内にする」「OnDemandThroughputは省略される」)が書かれています。これは私が実AWSで確かめたことではなく、issueの記載です。 - 残りの3件(インライン、Function URL、ステージ名の重複)は、全文検索では該当が見つかりませんでした。未報告の可能性があります。公式は、差異の報告をGitHubのIssuesで受け付けています。
注意点:
- issueファイルは、テストの実行結果から起こされた差異の記録で、一定時点のコミットに対して書かれたものです(ファイル内にそのコミットを示す注記があります)。今のリリースで同じ問題が起きるとは限りません。
- 実際に、古さが分かる例がありました。未解決フォルダの
20261106T052537Z-apigatewayv2-lambda-logs-the-nodejs22-x-lambda-runtime.mdは「nodejs22.x のランタイムにAWS SDK v3が同梱されない」としていますが、今回のリリースでは、Lambdaから@aws-sdk/client-dynamodbを読み込めて動きました。未解決フォルダにあっても、すでに直っていることがあります。 - 全文検索は、キーワードに依存します。検索語に合わない表現で書かれたissueは、見落としている可能性があります。
実務での使い方
| 観点 | 内容 |
|---|---|
| 向いている | IaCの構文エラー、リソース間の参照、基本的な作成・削除、CIでのスモークテスト、SDKを使うアプリの結合テスト(API → Lambda → DynamoDBまで確認できた) |
| 向かない | IAMやリソースポリシーの検証(公式が対象外としている。今回Lambdaの権限不足は検出できたが、AWSと同じ判定とは限らない) |
| 注意: ステータス | 「スタックが成功」だけで判断せず、作成されたリソースを別途describeや実呼び出しで確かめる(Alarmの例) |
| 注意: 接続先 |
AWS_ENDPOINT_URL が空のシェルで実行すると実AWSに向かう。CDKが出力するURLは実AWSのホスト名なので、そのまま開かない |
| 注意: Lambda | Lambdaの中から他のサービスを呼ぶなら、エンドポイントと認証情報の環境変数を明示する。インラインコードは動かなかったので、アセットで配る |
| 注意: 状態 | メモリ上のみ。再起動で全て消える。テストごとに作り直す前提で使う |
| 運用 | エミュレーターのリリース、検証日、使うサービスの忠実度スコアを記録しておく。1日に複数回リリースされるため、結果は変わりうる |
さいごに
AWS Emulatorは、リリース直後の時点でも、CDKのデプロイから、API Gateway、Lambda、DynamoDBをまたぐ処理まで、想定していたより広い範囲を動かせました。一方で、CloudFormationのステータスが成功でも、Alarmのように実際には使えないリソースがありました。デプロイの成否だけで判断せず、作成されたリソースの動作まで確認することが、運用の前提になります。用途としては、CIでの結合テストや、AIエージェントに安全に試行させる環境が向いており、IAMなどの権限の検証は、引き続き実AWSで行う必要があります。エミュレーターは1日に複数回更新されているため、本記事で確認した差異も、今後変わる可能性があります。ご自身の構成で試し、公式の記録にない差異を見つけた場合は、GitHubのIssuesに報告することが、改善につながります。なお、検証はすべてエミュレーター上で行っており、実AWSとの比較は行っていません。
参考文献
- AWS Emulator is now available for building and testing AWS applications locally(2026-11-11)
https://aws.amazon.com/about-aws/whats-new/2026/11/aws-emulator-now-available/ - AWS Emulator ドキュメント
https://docs.aws.amazon.com/emulator - Fidelity in AWS Emulator
https://docs.aws.amazon.com/emulator/latest/userguide/fidelity.html - Pointing your AWS SDK at AWS Emulator
https://docs.aws.amazon.com/emulator/latest/userguide/point-sdk-to-emulator.html - Security in AWS Emulator
https://docs.aws.amazon.com/emulator/latest/userguide/security.html - Getting started with Docker
https://docs.aws.amazon.com/emulator/latest/userguide/getting-started-docker.html - aws/aws-emulator(GitHub)
https://github.com/aws/aws-emulator - aws/aws-emulator のリリース v1.0.322(忠実度のデータ scores.json.gz を添付)
https://github.com/aws/aws-emulator/releases/tag/v1.0.322 - General purpose bucket naming rules(Amazon S3)
https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html

