はじめに
こんばんは、mirukyです。
2026年11月11日、AWSから公式のローカルエミュレーター、AWS Emulatorが出ました。AWSが自分で作っていて、アカウントも料金もいらないとなると、さすがに試したくなります。
今回は、API GatewayからStep Functions、S3まで経由する注文処理をAWS Emulatorに載せ、motoやFlociなど5つのOSSと、どこまで同じように動くかを比べます。検証はすべて手元のDockerで行ったので、AWSの料金は0円です。
目次
- AWS Emulatorって、どんなもの?
- LocalStackの今と、5つのOSS
- 注文処理を、手元で通しで動かしたい
- AWS Emulatorを動かしましょう
- 注文処理をAWS Emulatorに載せてみた
- 5つのOSSと比べる
- 結局、どれを選ぶ?
1. AWS Emulatorって、どんなもの?
1-1. AWSのAPIを、手元の1つのプロセスで受ける
AWS CLIで aws s3 mb を打つと、普段はリクエストがAWSのS3へ飛んでいきます。この行き先を環境変数で手元に向け、AWSの代わりに応答を返すプログラムが、AWS Emulatorです。作ったバケットやテーブルは手元のメモリに置かれ、エミュレーターを止めるとすべて消えます。
中では1つのプロセスが署名の確認からLambdaの実行までを受け持っています。
リクエストは自己署名の証明書を使った https://127.0.0.1:9297 で受け、起動のたびに作られる専用の鍵で署名していないものは断ります。サービスごとの処理はSDKと同じAWSのAPIモデルから作られていて、リポジトリにはサービスごとのコードが400以上並んでいます。まだ作られていない操作を呼ぶと、返ってくるのはHTTP 501の NotImplemented です。
| 項目 | 内容 |
|---|---|
| 提供開始 | 2026年11月11日(一般提供) |
| ライセンス | CC0 1.0(vendor/ 以下はApache 2.0) |
| 実装 | Rust |
| 動かし方 | LinuxはRPMとDebianのパッケージ。macOSとWindowsは公式のDockerfileで自分でイメージを作る |
| 使用状況の送信 | 既定でオン。--telemetry off か EMULATOR_TELEMETRY=off で止まる |
ドキュメントの制約の節にはIAMのポリシーをAWSと同じようには評価しない、と書かれています。どのアカウントIDとリージョンで送っても受け付けるものの、リソースをアカウントやリージョンごとに区別しないので、1つのエミュレーターには1つのアカウントとリージョンで送る決まりです。AWSとの答え合わせはグローバルなサービスを除いて米国西部(オレゴン)リージョンで行われていて、IAMの扱いがどこまでAWSに近いかは5章で確かめます。
1-2. コードを書いているのは、AIのエージェント
AWS Emulatorのコードは、AIのエージェントが書いて保守しています。AWSのブログによると、Amazon Bedrock AgentCore、AWS CodePipeline、AWS CodeCommitの上でエージェントが作り続けているそうです。GitHubのリポジトリはプルリクエストを受け付けておらず、違いを見つけたらIssueで知らせます。
リポジトリを開くと、issues/ の下に、まだ動かないもの(things-that-do-not-work/)と直ったもの(things-that-now-work/)が1件ずつファイルになって並んでいて、なかなか生々しいです。数えてみると、まだ動かないものが6,500件あまり、直ったものはその倍近くありました。リリースも1日に何度も出る日があり、今回試したのは11月11日に出たv1.0.322です。
AWSとの違いの大きさを測っているのが、再現度(fidelity)のテストです。AWSで正しさを確かめた小さなプログラムを流し、同じ結果になるかを数える仕組みで、合格数はリリースごとに公開されます。v1.0.322に添付された scores.json を集計すると、通信の形式を確かめるテストと振る舞いのテストを合わせて、4万件あまりのうち合格は半分強でした。今回使うサービスの振る舞いのテストを、そのサービス単独のものと、ほかのサービスとまたがるものとで比べてみます。
単独のテストでは多くのサービスが7割を超えているのに、ほかのサービスとまたがると、ほとんどが半分から6割台に下がります。いちばん下がったのはCloudFormationで、78%から24%まで落ちていました。ドキュメントでも、リソースを作る・取得する・消す側の操作(コントロールプレーン)がいちばんAWSに近く、データを流す側の操作やCloudFormationの対応はサービスによって差がある、と説明されています。サービスをいくつも経由する構成ほど、この差が結果に出そうです。
2. LocalStackの今と、5つのOSS
2-1. LocalStackは認証トークンが必須に
ローカルでAWSを動かすOSSといえば、長くLocalStackでした。そのLocalStackは2026年3月23日の2026.03.0で無料版と有料版のイメージを1つにまとめ、起動に認証トークンを求めるようになりました。GitHubの localstack/localstack も同じ日にアーカイブされています。非商用なら無料のHobbyプランがありますが、アカウントの登録は必要です。
手元で localstack/localstack:latest(2026.10.7)をトークンなしで起動すると、資格情報が見つからないと言って終了しました。
今回はアカウントを作らずに比べたかったので、LocalStackは外し、アカウントなしで動く5つのOSSを相手にしました。
2-2. 比べる5つのOSS
LocalStackの変更の前後から、置き換えをうたうOSSがいくつも公開されています。motoだけは2013年からある古株で、テストのプロセスの中でAWSを差し替える使い方が中心です。ほかの4つは2026年に入ってから公開されたもので、LocalStackと同じ4566番で待ち受けます。
| ライセンス | 実装 | 既定のポート | 版 | |
|---|---|---|---|---|
| AWS Emulator | CC0-1.0 | Rust | 9297(HTTPS) | 1.0.322 |
| Floci | MIT | Java(Quarkus) | 4566 | 2.4.0 |
| MiniStack | MIT | Python | 4566 | 1.5.40 |
| fakecloud | AGPL-3.0 | Rust | 4566 | 0.51.0 |
| moto | Apache-2.0 | Python | 5000 | 5.2.6.dev(Docker の latest) |
| kumo | MIT | Go | 4566 | 0.33.1 |
表の中でいちばん違いが出たのは、Lambdaのコードをどこで動かすかでした。同じ python3.13 の関数でも、エミュレーターによって動く場所が変わります。
FlociとfakecloudはDockerのソケットを借りて、AWS公式のLambdaのイメージでコンテナを起動します。motoも同じ仕組みで、使うイメージはコミュニティ製の ghcr.io/shogo82148/lambda-python でした。本物に近い反面、Dockerが必須です。AWS EmulatorはDockerを使わず、自分のプロセスの中で関数を動かします。kumoは関数を動かさず、Runtime APIで呼び出しを渡すだけなので、受け取る側は自分で用意します。MiniStackは既定では自分のコンテナの中のPythonで動かし、設定でDockerにも切り替えられる作りです。
3. 注文処理を、手元で通しで動かしたい
3-1. 注文を受けてから、領収書を残すまで
ネットショップの注文処理を、デプロイする前に手元で通しで確かめたいとします。API Gatewayで注文を受け、DynamoDB Streams、EventBridge、Step Functionsを経て在庫の確保と決済をし、確定した注文には領収書をS3に保存する構成です。在庫切れや決済の失敗はStep FunctionsのCatchで拾い、注文を FAILED にして失敗用のキューへ送ります。
サービスからサービスへの受け渡しを多めにしてあります。1-2節のスコアでも、エミュレーターの差が出やすいのはそこだったからです。
構成はCloudFormationのテンプレート1本(30リソース)で書き、実際のAWSにもそのままデプロイできる書き方であることを cfn-lint で確かめました。DynamoDBのテーブルと、Step Functionsの在庫の確保のステートを抜き出します。
OrdersTable:
Type: AWS::DynamoDB::Table
Properties:
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: orderId
AttributeType: S
KeySchema:
- AttributeName: orderId
KeyType: HASH
# 追加された注文をストリームで変更の通知の関数へ渡す
StreamSpecification:
StreamViewType: NEW_IMAGE
"ReserveStock": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": { "FunctionName": "${ReserveStockFunction}", "Payload.$": "$" },
"ResultSelector": { "result.$": "$.Payload" },
"ResultPath": "$.reservestock",
"Retry": [{ "ErrorEquals": ["TransientError"], "IntervalSeconds": 1, "MaxAttempts": 2, "BackoffRate": 2.0 }],
"Catch": [{ "ErrorEquals": ["States.ALL"], "ResultPath": "$.error", "Next": "MarkFailed" }],
"Next": "ChargePayment"
}
注文の状態を CONFIRMED や FAILED に変えるステートは、Lambdaを挟まず、Step FunctionsからDynamoDBを直接更新する連携(arn:aws:states:::dynamodb:updateItem)にしました。このステートには "ResultPath": null を付けて、DynamoDBの応答を捨て、受け取った注文の情報をそのまま次のSNSの通知へ渡します。6章では、この1行で結果が変わるエミュレーターが出てきます。
3-2. 流す注文は4種類
成功の流れだけでなく、RetryとCatchまで動くかを確かめたいので、商品コードと数量を変えた4種類の注文を流します。
| 注文 | 在庫の確保 | 決済 | 期待する結果 |
|---|---|---|---|
| BOOK-1を2個 | 成功 | 成功 |
CONFIRMED、S3に領収書 |
| FLAKY-1を1個 | 1回目だけ一時的なエラー | 成功 | Retryで CONFIRMED、試行回数は2 |
| SOLD-OUT-1を1個 | 在庫切れのエラー | 呼ばれない | Catchで FAILED、失敗用のキューへ |
| BOOK-2を20個 | 成功 | 数量の上限を超えてエラー | Catchで FAILED、失敗用のキューへ |
在庫の確保の関数は、試行回数を注文の項目に数えながら、商品コードに応じてエラーを出します。
import os, boto3
ddb = boto3.client("dynamodb", endpoint_url=os.environ.get("EMULATOR_ENDPOINT") or None)
class OutOfStock(Exception): pass
class TransientError(Exception): pass
def handler(event, context):
# 試行回数を注文の項目に数え、FLAKY- の商品は1回目だけ一時的なエラーにする
n = ddb.update_item(TableName=os.environ["TABLE"], Key={"orderId": {"S": event["orderId"]}},
UpdateExpression="ADD attempts :one", ExpressionAttributeValues={":one": {"N": "1"}},
ReturnValues="UPDATED_NEW")["Attributes"]["attempts"]["N"]
if event["sku"].startswith("SOLD-OUT"):
raise OutOfStock(event["sku"])
if event["sku"].startswith("FLAKY") and int(n) < 2:
raise TransientError(f"attempt {n}")
return {"reserved": event["qty"], "attempts": int(n)}
EMULATOR_ENDPOINT は、関数の中の接続先をエミュレーターが差し替えてくれないときのための逃げ道です。結局、今回はどのエミュレーターでも空のままで動きました。
4. AWS Emulatorを動かしましょう
4-1. イメージのビルドと起動
macOS(Apple silicon)とcolimaのDocker 29.5.2で動かします。ビルド済みのイメージは配られていないので、公式のDockerfileから作ります。中身はAmazon Linux 2023の上にRPMを入れるだけの短いものです。Apple siliconではRPMのファイル名を aarch64 のものに替えます。
FROM public.ecr.aws/amazonlinux/amazonlinux:2023-minimal
# Apple silicon では aarch64 の RPM を使う(Intel なら x86_64)
ADD https://downloads.aws.com/aws-emulator/packages/aws-emulator/LTS/latest/aws-emulator-aarch64.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"]
# イメージを作る(2026-11-12 時点で展開後 1.63GB)
docker build -t aws-emulator .
# 鍵と証明書を手元と共有するディレクトリを作ってから起動する
mkdir -p ~/.aws/emulator
docker run --rm -d --name aws-emulator --memory=2g --memory-swap=2g -p 127.0.0.1:9297:9297 \
-e HOME="$HOME" -v "$HOME/.aws/emulator:$HOME/.aws/emulator" \
aws-emulator -p 9297 --telemetry off
ポートは -p 127.0.0.1:9297:9297 のように手元のアドレスに絞ります。-p 9297:9297 にすると、同じネットワークのほかのマシンからも呼べてしまうので、ドキュメントでもはっきり止められています。使用状況の送信は、今回は --telemetry off で止めました。
4-2. AWS CLIをエミュレーターへ向けましょう
起動すると、~/.aws/emulator/9297/ に、その回だけ使える鍵、SDK用の設定ファイル、証明書ができます。ドキュメントの手順どおりに環境変数を並べ、本物の資格情報は消しておきます。
# AWS CLI と SDK の接続先を AWS Emulator に向ける
export AWS_ENDPOINT_URL=https://127.0.0.1:9297
export AWS_SHARED_CREDENTIALS_FILE=~/.aws/emulator/9297/emulator_credentials
export AWS_CONFIG_FILE=~/.aws/emulator/9297/config
export AWS_CA_BUNDLE=~/.aws/emulator/9297/ca.pem
export AWS_EC2_METADATA_DISABLED=true
export AWS_DISABLE_HOST_PREFIX_INJECTION=true
export AWS_REGION=us-east-1
export AWS_DEFAULT_REGION=us-east-1
# 本物の資格情報が残っていると、エミュレーターに拒否される
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN AWS_PROFILE
見慣れないのは AWS_DISABLE_HOST_PREFIX_INJECTION ですかね。一部の操作で、SDKは接続先のホスト名に data- などを付け足すので、そのままだと data-127.0.0.1 のような存在しないホストへ送られてしまいます。それを止める設定です。準備ができたら、ヘルスチェックと get-caller-identity で行き先を確かめます。
アカウントIDが 000000000000 なら、リクエストはエミュレーターに届いています。ここに自分のアカウントIDが出たら、本物のAWSに送っているので、環境変数を見直してください。
5. 注文処理をAWS Emulatorに載せてみた
5-1. CloudFormationは、DynamoDBのテーブルでロールバック
まずは、実際のAWSと同じ手順で、テンプレートを aws cloudformation deploy に渡します。
注文テーブルの作成は streamSpecification.streamEnabled がnullだという検証エラーで失敗し、スタックはロールバックしました。CloudFormationの AWS::DynamoDB::Table の StreamSpecification に書けるのは StreamViewType などで、StreamEnabled はそもそも存在しないプロパティです。一方、DynamoDBの CreateTable のAPIでは StreamEnabled が必須です。ソースを確かめると、AWS Emulatorはテンプレートの StreamSpecification をそのまま CreateTable に渡していて、ここで値が欠けていました。
Streamsの設定を外したテンプレートでも試すと、今度はEventBridgeのルールが、バスが存在しないという理由で失敗しました。バスだけのテンプレートを流すと、スタックは CREATE_COMPLETE になるのに、list-event-buses にそのバスは出てきません。こちらは、CloudFormationで作ったイベントバスが見えない問題として、2026年10月25日に issues/ へ登録済みでした。
それでも、CloudFormationそのものは動きます。テーブルのストリームを外し、ルールを既定のイベントバスに付け替えると、Lambda、Step Functions、HTTP API、SNS、SQS、IAMロールなど残りの28リソースは CREATE_COMPLETE になりました。今回の構成で止まったのは、ストリームを有効にしたテーブルと、カスタムのイベントバスの2か所です。
5-2. SDKで組むなら、作成直後の状態を待ちましょう
CloudFormationの代わりに、同じ構成をboto3で1つずつ作るスクリプト(deploy.py)を用意しました。テンプレートと同じリソースを、テンプレートと同じ権限で作るだけのものです。
HTTP APIの呼び出し先は ApiEndpoint に https://127.0.0.1:9297/_aws/execute-api/<API ID> の形でそのまま返ってきます。実はこのスクリプト、ほかのOSSでは通っていた手抜きが、AWS Emulatorでは4つ通りませんでした。
作った直後のLambdaの関数は Pending で、呼べるのは Active になってからです。DynamoDBのテーブルも作った直後は CREATING で、書き込むと失敗します。イベントソースマッピングを作るときには、関数の実行ロールにストリームやSQSからデータを受け取る権限があるかを確かめられ、SNSからSQSへの配信もキューのポリシーでSNSからの送信を許可するまで届きませんでした。
4つとも、実際のAWSと同じ振る舞いです。ウェイターで待ち、ロールとキューに権限を付ければ通ります。言い換えると、ほかのOSSで通っていた書き方のほうが、本番では落ちる書き方だった、みたいな感じです。
5-3. 注文がStep Functionsの手前で止まる理由
準備ができたので、BOOK-1の注文を送ります。
API GatewayからLambda、DynamoDBへの書き込み、Streamsから変更の通知のLambda、EventBridgeのルールまでは動き、ステートマシンの実行も自動で始まりました。ところが注文は PENDING のままで、実行は States.Runtime で失敗しています。エラーの文は、呼び出しの連鎖がすでに3段の深さなので、在庫の確保のLambdaを呼ばなかった、という内容でした。
ソースを確かめると、Step Functionsの実装に MAX_CALL_CHAIN_DEPTH = 3 という定数があり、コメントにはエミュレーター側の制限でAWSの制限ではない、とはっきり書かれていました。AWS Emulatorは呼び出しを自分のプロセスの中で処理するので、Streams、EventBridge、Step Functionsと連なるたびに、待っている処理が1段ずつ重なっていきます。それを抑えるための定数です。
States.Runtime は、実際のAWSでもCatchの States.ALL で拾えない種類のエラーです。そのため注文は PENDING のまま残り、失敗用のキューも空のままでした。
5-4. 手で起動して、続きを確かめましょう
連鎖の途中から始めればどうなるかを確かめるため、同じ注文で、ステートマシンを start-execution から直接起動しました。
今度は在庫の確保と決済が動き、注文は CONFIRMED になりました。止まったのは、確定のあとのSNSへの通知です。4種類の注文を同じように流すと、確定する注文はSNSの通知で止まり、失敗する注文はCatchの先の最後のステートまで進みました。
FLAKY-1はRetryで2回目に成功し、SOLD-OUT-1とBOOK-2はCatchで FAILED になって、失敗用のキューに2件届きました。DynamoDBやSQSを直接呼ぶ連携も含めて、ステートマシンの解釈そのものは実際のAWSと同じ結果です。
SNSが止まったのは、Step Functionsから直接呼べるサービスの一覧に入っていないからでした。ソースで確かめた連携先は、Lambda、DynamoDB、SQS、EventBridgeなどの12個でした。SNSへの連携は2026年10月27日に issues/ へ登録されています。SNSに手で発行すると、SQSからLambda、S3への領収書の保存までは1秒ほどで動いたので、止まるのはStep FunctionsとSNSのあいだだけです。
5-5. Lambdaの中身はRustPython
SDKで組んでいる途中、関数のエラーのログに、ZIPに入れた覚えのない /var/task/botocore/client.py が出てきました。気になってソースを確かめると、AWS EmulatorはPythonのLambdaをRustPython(Rustで書かれたPythonの処理系)で、Node.jsのLambdaをQuickJSで、自分のプロセスの中で動かしていました。boto3も、AWS Emulatorが用意した、同じ使い方ができる代わりのモジュールです。
実行環境を調べる小さな関数を python3.13 で作り、AWS EmulatorとFlociで呼んでみます。
python3.13 を指定しても、中身はRustPythonの 3.14.0.alpha でした。外部へのHTTP通信は届かず、sqlite3 と ssl は import で失敗し、boto3のページネーターも用意されていません。同じ関数をFlociで呼ぶと、AWS公式のLambdaのイメージの上で動きます。
FlociはCPython 3.13.18で、どの項目もそのまま動きました。MiniStack、fakecloud、motoもCPythonです。AWS Emulatorで関数まで試すなら、外部のAPIを呼ぶ処理とページネーターを使う処理は、動かない前提で組んでおくのが良いかと思います。ソースではネイティブのライブラリをプロセスに取り込む仕組みも外されているので、C拡張を含むライブラリ(numpyやpydantic-coreなど)も動かないはずです(ここは試していません)。その代わり、Dockerがなくても関数を実行でき、起動も一瞬です。
5-6. 実行ロールの権限は、ちゃんと拒否された
1-1節で触れたとおり、ドキュメントにはIAMのポリシーをAWSと同じようには評価しないとあります。そこで、在庫の確保の関数の実行ロールから dynamodb:UpdateItem を外して、ステートマシンを起動しました。
注文は FAILED になり、assumed-role/shop-lambda/reserve_stock には dynamodb:UpdateItem の権限がない、と実際のAWSと同じ形のエラーが返ってきました。ソースによると、関数の中からの呼び出しは、実行ロールのセッションとして評価されています。変更の通知の関数から events:PutEvents を外したときも、同じ形の AccessDeniedException でイベントの送信が断られ、ステートマシンは起動しませんでした。
すべてのポリシーの評価がAWSと同じになるとは限らないので、ドキュメントのとおり、セキュリティの検証には使わないほうが安心です。それでも、実行ロールの権限の付け忘れのような、ありがちな失敗は手元で拾えました。ドキュメントの注意書きより、一歩踏み込んだ振る舞いです。
6. 5つのOSSと比べる
6-1. 区間ごとの結果
同じテンプレートと同じ4種類の注文を、5つのOSSにも流しました。CloudFormationが通らないmotoとkumoは、AWS Emulatorと同じくSDKで構成を組みました。自動で起動しない区間は手でステートマシンを起動して、その先の区間も確かめています。motoのStep Functionsは既定ではステートを実行しないので、moto-api/config で execute_state_machine を有効にしました。
手を加えずに全区間が通ったのは、FlociとMiniStackの2つです。どちらもテンプレートのコードを1行も変えずに、HTTP APIからS3の領収書まで動きました。AWS Emulatorはステートマシンの解釈やIAM、作成直後の状態の扱いでは実際のAWSに近い一方で、サービスをいくつも経由させると、連鎖の深さや未対応の連携に当たります。1-2節のスコアの傾向がそのまま出た形です。
6-2. 全区間が動いたFloci
Flociは起動にDockerのソケットを渡すだけで、Lambdaのコンテナまで自分で用意してくれます。APIの呼び出し先はLocalStackと同じ形の http://<API ID>.execute-api.localhost.localstack.cloud:4566 です。
注文から2秒ほどで CONFIRMED になり、S3に領収書が保存されました。成功の流れが通ったので、次はCatchの側です。在庫切れのSOLD-OUT-1を流します。
Catchで FAILED になり、失敗用のキューには OutOfStock のエラーが届いています。1つだけ気をつけたいのが、CloudFormationの出力の ApiEndpoint です。実際のAWSと同じ https://<API ID>.execute-api.us-east-1.amazonaws.com の形で返るので、手元から呼ぶときは上の形に置き換えます。
6-3. CREATE_COMPLETEなのに、何も作られていない
kumoのCloudFormationは、もっと注意が要りました。
スタックは CREATE_COMPLETE なのに、テーブルも関数もステートマシンも作られていません。CIでデプロイの成否だけを確認していると、成功したと思い込んでしまいます。Lambdaも2-2節の図のとおり自分では動かさないので、今回は標準ライブラリだけで小さな実行役を書き、AWS_LAMBDA_RUNTIME_API に関数ごとの宛先を渡して起動しました。
fakecloudはテンプレートに直接書いたコード(Code.ZipFile)を、ZIPファイルとして展開しようとして失敗しました。
コードをZIPにしてS3に置く形(sam package と同じ形)に変えると、デプロイから関数の実行までは通りました。ところが、SNSの通知の中身から注文IDが消えていて、領収書の保存で止まります。原因は3-1節で付けた "ResultPath": null でした。
実際のAWSでは、ResultPath を null にするとタスクの結果は捨てられ、入力がそのまま出力になります。最小のステートマシンに同じ入力を流して、4つで比べました。
Floci、MiniStack、motoは入力をそのまま返し、fakecloudだけがDynamoDBの応答で置き換えていました。fakecloudで ResultPath: null を使うステートマシンを試すときは、この1点だけ実際のAWSと結果が変わります。
6-4. OSSのIAMの評価は、既定でオフ
5-6節と同じく、在庫の確保の関数から dynamodb:UpdateItem を外したテンプレート(template-broken.yaml)を、Flociに流しました。
既定の設定では、本来は拒否されるはずの注文が CONFIRMED で通ってしまいます。FLOCI_SERVICES_IAM_ENFORCEMENT_ENABLED=true を付けて起動し直すと、同じ注文が FAILED になりました。fakecloudの FAKECLOUD_IAM=strict、MiniStackの AUTH=true でも同じく FAILED になります。権限を戻した正しいテンプレートでは、評価をオンにしてもFlociとMiniStackは全区間が通りました。
AWS Emulatorは何も設定しなくても拒否したので、既定の振る舞いはOSSと逆です。OSSで権限の漏れを拾いたいなら、評価をオンにした設定を用意しておくのが良さそうです。
6-5. 起動時間とメモリ
起動の速さと、待っているあいだのメモリも測りました。起動時間は、AWS Emulatorはヘルスチェック、ほかはSQSの ListQueues が応答するまでの5回の中央値です。
起動はどれも1秒を切っていて、CIで毎回立ち上げても困らない速さです。AWS Emulatorは400以上のサービスを1つのプログラムに入れているぶん、メモリとイメージが大きめでした。ただ、Floci、fakecloud、motoはこれとはべつに、1GB前後のLambdaのイメージを取得します。関数まで動かすなら、合計の差はかなり縮まります。
6-6. motoの本来の出番はテストの中
motoにはサーバーとして動かす以外に、Pythonのテストの中でAWSを差し替える使い方があります。むしろこちらが本来の出番です。在庫の確保の関数を、モックしたDynamoDBに対して直接呼ぶテストを書きました。
import importlib, json, os, sys
import boto3, pytest
from moto import mock_aws
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "functions"))
@pytest.fixture
def table():
# テストごとに、メモリ上の DynamoDB に空のテーブルを作る
os.environ.update(AWS_DEFAULT_REGION="us-east-1", AWS_ACCESS_KEY_ID="test", AWS_SECRET_ACCESS_KEY="test", TABLE="orders")
with mock_aws():
boto3.client("dynamodb").create_table(TableName="orders", BillingMode="PAY_PER_REQUEST",
AttributeDefinitions=[{"AttributeName": "orderId", "AttributeType": "S"}],
KeySchema=[{"AttributeName": "orderId", "KeyType": "HASH"}])
yield boto3.client("dynamodb")
def load(name):
# クライアントはモジュールの import 時に作られるので、モックの中で import し直す
return importlib.reload(importlib.import_module(name))
def test_flaky_fails_once(table):
stock = load("reserve_stock")
table.put_item(TableName="orders", Item={"orderId": {"S": "o-1"}, "sku": {"S": "FLAKY-1"}})
with pytest.raises(stock.TransientError):
stock.handler({"orderId": "o-1", "sku": "FLAKY-1", "qty": 1}, None)
assert stock.handler({"orderId": "o-1", "sku": "FLAKY-1", "qty": 1}, None)["attempts"] == 2
Dockerを使わずに、3件のテストが0.5秒ほどで終わりました。サーバーとしてのmotoは6-1節でつまずきが多かったものの、関数1つの振る舞いを確かめるなら、いちばん手軽です。
7. 結局、どれを選ぶ?
やりたいことから逆引きできるよう、ここまでの結果を表にしました。
| やりたいこと | 候補 | 理由 |
|---|---|---|
| サービスをいくつも経由する構成を、手元で通しで動かす | Floci、MiniStack | 今回の30リソースの構成が、コードを変えずに全区間で動いた |
| 1つ1つのAPIの振る舞いを、AWSに近い形で確かめる | AWS Emulator | 作成直後の状態、イベントソースの権限の確認、実行ロールの評価がAWSと同じだった |
| Pythonの関数をユニットテストする | moto(プロセスの中) | Dockerなしで1秒もかからない |
| IAMの権限の漏れを手元で拾う | AWS Emulator、またはIAMの評価をオンにしたFloci、fakecloud、MiniStack | OSSは既定ではオフ |
3章の注文処理に当てはめると、構成全体の通し確認はFlociかMiniStack、1つ1つの関数とAPIの呼び方はAWS Emulator、関数の細かい分岐はmoto、という組み合わせが今回の結果に合っています。AWS Emulatorは、AWSが自ら作り、AWSとの違いを issues/ と再現度のスコアで公開しながら直し続けています。2026年11月12日の時点では呼び出しの連鎖とSNSの連携で止まりましたが、この2つが直れば表は書き換わるはずです。
AWS Emulatorを試すなら、今回つまずいた点を先に知っておくと手戻りが減ります。ストリームを有効にしたテーブルやカスタムのイベントバスを使う構成は、SDKで組みます。作成直後の Pending や CREATING はウェイターで待つのが確実です。コマンドラインの --idle-timeout は、使われないまま時間がたったエミュレーターを止める設定です。AWS CLI 2.35.0以降なら、AIのコーディングエージェントに aws agent-toolkit add-skill --skill-name aws-emulator --region us-east-1 でスキルを入れ、エミュレーターの準備をエージェントに任せることもできます。
なお、今回のFlociのように全区間が動いたとしても、エミュレーターの結果は手元での確認にとどめ、本番の前には実際のAWSでも一度流します。
おわりに
ここまでお付き合いいただきありがとうございます。
AWS Emulatorは1つ1つのAPIの振る舞いが実際のAWSに近く、IAMの拒否まで返してくれた一方、サービスを長く経由する構成は連鎖3段の定数とSNSの連携で止まり、今回の注文処理を通しで動かせたのはFlociとMiniStackでした。エージェントが毎日直しているので、連鎖の定数が外れたら、同じ構成をもう一度流してみたいところです。
ではまた、お会いしましょう。
参考リンク
AWS Emulator
- AWS Emulator is now available for building and testing AWS applications locally - AWS
- Test your applications against AWS locally with AWS Emulator - AWS News Blog
- What is AWS Emulator? - AWS Emulator User Guide
- Getting started with Docker - AWS Emulator User Guide
- Getting started with an AI coding agent - AWS Emulator User Guide
- Pointing your AWS SDK at AWS Emulator - AWS Emulator User Guide
- Fidelity in AWS Emulator - AWS Emulator User Guide
- aws/aws-emulator - GitHub
- aws/aws-emulator-evaluation-suite - GitHub
LocalStack
OSSのエミュレーター
- floci-io/floci - GitHub
- ministackorg/ministack - GitHub
- faiscadev/fakecloud - GitHub
- sivchari/kumo - GitHub
- getmoto/moto - GitHub
- Non-Python SDK's / Server Mode - Moto

























