2026 年 11 月 11 日,AWS 正式釋出了 AWS Emulator。這是一個可讓 AWS SDK 與 AWS CLI 的呼叫,在沒有 AWS 帳戶的情況下,導向本機或 CI 上的模擬器的工具。官方說明指出,對於已實作的 API,合法呼叫會回傳與 AWS 相同的結果;不合法呼叫會回傳相同的錯誤;未實作的 API 則會以 501 失敗。此工具不收取額外費用。
第一次聽到這件事時,應該會浮現以下疑問:
尤其是第三點,在實務上最讓人在意。官方文件也明確寫到,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 環境下實際測試的結果。同時,也為了讓第一次接觸模擬器的人能掌握整體樣貌,我也確認了模擬器本身的行為。
前提是,你已經具備 AWS SDK、AWS CLI、CDK(或 Terraform)與 Docker 的基本操作知識。
項目內容出處定位本機/CI 用來測試 AWS API 的官方模擬器官方(What’s New,2026-11-11)狀態僅存在記憶體中。停止後即消失官方未實作的 API 回傳 HTTP 501 NotImplemented官方費用無額外費用官方正式上線不可用於正式環境官方安全測試不可用(IAM policy、resource policy、KMS key policy、跨帳戶皆不在範圍內)官方 Windows/macOS 透過 Docker 執行。沒有現成映像檔,需從 Dockerfile 自行建置官方 區域/帳戶單一區域、單一帳戶使用;若要測多個區域,需分開使用不同的模擬器官方
圖的看法:你的程式或工具會因為是否設定環境變數 AWS_ENDPOINT_URL 而改變目的地。未設定時會連向真實 AWS;設定為 https://127.0.0.1:9297 時,則會指向本機模擬器。

在進入驗證前,先整理一下使用 AWS Emulator 的好處。AWS 內部的發文(AWS News Blog、AWS 高層 Swami Sivasubramanian 受 Werner Vogels 部落格邀稿的文章、AWS organization 的 DEV.to 文章)所提到的使用情境,我將它們與本次測試實際確認到的結果對照如下。表中的出處名稱與 URL 如下,皆為 2026-11-11 發佈。
表的出處名稱內容URLAWS News BlogTest 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 Guest Post)https://www.allthingsdistributed.com/2026/11/aws-emulator-and-the-engineers-job-when-agents-write-the-code.htmlDEV.toHow emulators might catch what you might miss(AWS organization 底下的文章)https://dev.to/aws/how-emulators-might-catch-what-you-might-miss-34hd好處AWS 端的說法本篇驗證到的結果不需部署等待即可測試可在本機直接執行單元測試,不必部署到 AWS(AWS News Blog)。因為不需要部署,也不需要 AWS 帳戶,所以每次改動都能跑測試(DEV.to)CDK 部署約在 12~18 秒內完成。API 呼叫每次約數毫秒到 20 多毫秒(單次測量,未與真實 AWS 比較)不改程式或 IaC,只改目的地透過環境變數指定端點即可,應用程式本身不用改(AWS News Blog)boto3 程式在拿掉環境變數後,仍可直接在真實 AWS 上運作。CDK 與 Terraform 也只需設定 AWS_ENDPOINT_URL 就能部署可找出 mock 會漏掉的問題例如未刪除 SQS 訊息卻仍完成流程的問題,在 mock 下會通過,但在模擬器下會因為重送而失敗(DEV.to)回傳 AWS 形式的錯誤(NoSuchKey、ConditionalCheckFailedException 等)。Lambda role 權限不足也會被檢出為 AccessDeniedException。SQS 重送本身未驗證未實作的 API 會立刻知道未實作的 API 會回傳 HTTP 501,測試會立即失敗(AWS News Blog)測試 27 個 API,其中 4 個回傳 501。不會悄悄成功,因此能及早發現可讓多個測試或多個 agent 並行執行每個 agent 都有獨立副本(不同埠、不同金鑰、不同狀態),不需要共享測試帳號(DEV.to)啟動了第二個模擬器(9298),確認資源與憑證都看不到第一個模擬器的內容讓 coding agent 反覆驗證可建立資源、確認結果、再重新嘗試,而且不必等待區域部署。我在 DEMO 中用 Kiro 操作 DynamoDB(AWS News Blog、Swami 的投稿)本篇驗證是由我透過 CLI 操作,不是透過 agent。尚未驗證由 agent 操作的行為避免雲端費用與共享資源競爭真實雲端測試會產生成本,而平行測試可能會改到共用資源而互相干擾(DEV.to)官方顯示不收額外費用。本篇驗證全程都在模擬器上進行,沒有在真實 AWS 建立資源每次都能從乾淨狀態開始本次閱讀的發文中未特別提到重新啟動只要 2 秒,所有 stack 與 Lambda 都消失了。雖然有利於讓測試前提一致,但若中途重啟就會全部消失能事先知道哪些地方一致性較高官方會依服務公開忠實度分數,以「合格數/總數」呈現,並在 GitHub 公開測試(AWS News Blog)。也會公開與 AWS 不一致的已知行為(Swami 的投稿)在步驟 5 與步驟 6 中,我將分數與已知問題對照本次結果AWS 端的發文都是提供者自己的說明。本篇右欄內容,是在 Windows 11 + Docker 上各測一次的結果,並沒有與真實 AWS 比較。
不過,AWS 端的發文也沒有說模擬器可以取代一切。
另外,以上優點會受到你所使用服務的忠實度影響。這些「優點」是否能在你的架構中成立,會在後續步驟中驗證。
官方以「忠實度測試」衡量模擬器與 AWS 的一致程度,並在每次發佈時公開各服務的合格數/總數。
用語意義忠實度測試使用 SDK 執行工作流程,並將結果、狀態、錯誤與 AWS 比較的短程式忠實度分數各服務的「合格數/總數」(例如 40 / 50)控制平面建立、查詢、更新、刪除、列出等 API資料平面服務的主要功能(例如 S3 物件的讀寫)分數只能在相同測試版本之間比較。
項目內容作業系統Windows 11執行方式Docker 28.0.4(官方 Dockerfile)模擬器1.0.322(Release date 2026.11.10,commit 7753983)。與 GitHub 最新 release v1.0.322 相同驗證日2026-11-12IaCCDK(CLI 2.1148.1、aws-cdk-lib 2.275.0)、Terraform 1.16.2(AWS provider 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由於模擬器一天會發佈多次,因此結果會依驗證日與版本而不同。
使用官方 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"}
從紀錄可知,telemetry 已停用、請求驗證已啟用、端點是 https://127.0.0.1:9297。最後那個 0.0.0.0 的警告,是因為 Dockerfile 使用 -b 0.0.0.0,所以在容器內是監聽所有位址。主機端則透過 -p 127.0.0.1:9297:9297 只公開到自己的電腦,因此外部無法存取。
接著,依官方步驟用環境變數把 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-identityAccount 000000000000、user/emulatorS3 一般名稱 bucket 的建立、put/get、刪除成功DynamoDB 表名 ab與 AWS 相同格式的 ValidationExceptionS3 amzn-s3-demo-bucket(官方文件範例名稱)以 InvalidBucketName 失敗最後一行讓人意外。官方文件範例中使用的名稱無法建立 bucket。這不是模擬器的 bug,而是因為 S3 bucket 名稱規則中,amzn-s3-demo- 開頭的名稱被保留了(出處:https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html)。這也表示模擬器連這類規則也和 AWS 一樣在驗證。
容易踩雷的地方,官方有以下提醒:
AWS_ENDPOINT_URL 的 shell 中執行測試,會使用找到的憑證去呼叫真正的 AWSAWS_ACCESS_KEY_ID 等,模擬器會拒絕-p 127.0.0.1:9297:9297 這類方式限制;簡寫形式會對整個網路公開在同一個 stack 中,透過 -c stage=N 逐步加入資源。
cdk bootstrap aws://000000000000/us-east-1 在 17 秒內成功。建立出的 CDKToolkit stack 包含 S3 bucket、ECR repository、IAM role(5 個)與 SSM parameter,也成功讀取到 SSM 的 /cdk-bootstrap/hnb659fds/version。
備註:CDK CLI 2.1148.1 與 aws-cdk-lib 2.276.0 的組合會失敗,因為 library 要求尚未發佈的 CLI 2.1148.2。這是 CDK 端的問題,與模擬器無關。本篇固定使用 lib 2.275.0。
階段資源cdk deploy實際可否運作1S3 bucket成功(17 秒)head-bucket 成功2DynamoDB(PAY_PER_REQUEST)成功(17 秒)ACTIVE、put-item/get-item 成功3SQS + SNS + 訂閱成功(17 秒)SNS publish 有送到 SQS4IAM role + Lambda(inline,Node 22)成功(12 秒)State=Active,但 invoke 以 exports is not defined 失敗5EventBridge(每 5 分鐘)→ SQS成功(17 秒)週期性執行觸發,事件送到 SQS6Step Functions(Pass)成功(17 秒)start-execution → SUCCEEDED7CloudWatch Logs + Alarm成功(17 秒)log group 已建立。Alarm 不會出現在 describe-alarms8Lambda(Code.fromAsset)成功(13 秒)invoke 成功。asset 已發佈到 staging bucket9Lambda + API Gateway(REST、GET)失敗 3 次,第 4 次成功(18 秒)回傳 HelloEmulator(見下方「運作確認」)10API Gateway → Lambda → DynamoDB(新增 item API)失敗 2 次,第 3 次成功(補上權限後再部署)POST 可儲存,GET 可取回(見下方「確認模擬器的行為」)destroy刪除 stack 成功(12 秒)僅剩 Lambda 的 log group(CDK 以 DeletionPolicy: Retain 建立,因此不屬於模擬器問題)以下是第 8 階段的 stack,在沒有任何變更下再次部署時的輸出。
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
部署完成的 stack 內容,也可透過 list-stack-resources 確認。到第 8 階段時,template 中的 18 個資源全部都是 CREATE_COMPLETE。不過如前所述,即使是 CREATE_COMPLETE,Alarm 仍不會出現在 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 建立的 alarm 則會出現在清單中第 7 階段Lambda inline code 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)。因為 endpoint 會被覆蓋,所以 CLI / SDK 操作可通過,但若直接用這些 URL 開啟 curl 或瀏覽器,會導向真實 AWS第 3、9 階段Lambda Function URL透過 addFunctionUrl 建立的權限資源,會因「未套用 InvokedViaFunctionUrl 屬性」而在 CREATE_FAILED 失敗並回滾第 9 階段(第一次嘗試)statement ID 太長LambdaRestApi 預設的 proxy+ proxy 會建立 AWS::Lambda::Permission,但因 statementId 超過 100 字而失敗。因為 statementId 內含 stack 名稱與很長的 logical ID。改成只定義一個 GET,或縮短 ID,即可避開。官方 repo 的已知 issue 也記錄了相同問題(步驟 6)第 9、10 階段stage name 重複在同一個 stack 中建立第二個 REST API 並使用 prod stage 時,會因 prod already exists in stack 而 CREATE_FAILED。改用不同的 stage name 即可。AWS 上 stage 是每個 API 各自的名稱,但模擬器不允許 stack 內重複。真實 AWS 的行為未確認第 10 階段 rollback 後再部署CDK 建立的 log group 因為是 Retain,即使失敗 rollback 也會保留;再次部署時會因「log group already exists」而失敗。手動刪除後即可重新部署。這是 CDK 與 CloudFormation 的行為,不是模擬器特有第 9、10 階段Lambda 權限不足第 10 階段漏寫 grantReadWriteData,導致 Lambda 對 DynamoDB 回傳 AccessDeniedException。模擬器有偵測到 Lambda role 權限不足,對找出程式錯誤有幫助第 10 階段Lambda inline code我沒有確認真實 AWS 上相同程式碼會如何運作,因此無法斷定是否為模擬器差異。
即使部署成功,也不代表真的能運作。因此,我確認了 Lambda 是否會回傳 HelloEmulator,以及能否透過 API Gateway 呼叫。
Lambda 程式碼(lambda/hello.js)如下。
exports.handler = async () => ({
statusCode: 200,
headers: { 'Content-Type': 'text/plain' },
body: 'HelloEmulator',
});
在 CDK 中,這個 Lambda 會接到 LambdaRestApi,並只在根路徑(/)定義一個 GET。
呼叫方式結果aws lambda invokeStatusCode 200,回應 body 為 HelloEmulator``aws apigateway test-invoke-methodstatus 200,body 為 HelloEmulator``curl(在 Host header 指定 API 的 hostname,連線目標指向模擬器)HTTP/1.1 200 OK、content-type: text/plain、body 為 HelloEmulator``curl(使用 /restapis/<id>/prod/_user_request_/ 的路徑格式)404 UnknownService(這條路徑無法到達)以下是 curl 的範例。API Gateway 的呼叫方式,是對模擬器的單一 endpoint 發送請求,並透過 Host header 指定目標 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 header 指定 API。
到這裡為止,重點是「IaC 能不能部署」。為了讓第一次接觸 AWS Emulator 的人更了解,我也從幾個面向確認了模擬器本身的行為。以下範例都在步驟 1 設定好環境變數的 shell 中執行。
應用程式的程式碼裡不需要寫 endpoint 或憑證,僅靠環境變數即可決定目的地。下面的 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"}})
實測結果如下(單次測量、在本機 Docker 上、單位為毫秒)。
操作時間S3 create_bucket18.5S3 put_object3.9S3 get_object2.9DynamoDB create_table(隨即變成 ACTIVE)4.5DynamoDB put_item2.6SQS create_queue22.0SQS send_message1.9SQS receive_message1.8官方宣稱會在毫秒內回應。這次測量中,每次呼叫也確實落在數毫秒到 20 多毫秒之間。未與真實 AWS 比較。
呼叫方式回傳的錯誤S3 讀取不存在的 keyNoSuchKey: The specified key does not exist.S3 對不存在的 bucket 執行 head-bucket404 Not FoundSQS 取得不存在 queue 的 URLAWS.SimpleQueueService.NonExistentQueueDynamoDB describe 不存在的 tableResourceNotFoundException: Requested resource not found: Table: nope not foundDynamoDB 不符合條件的條件式寫入ConditionalCheckFailedExceptionLambda 取得不存在的 functionResourceNotFoundException: Function not found: arn:aws:lambda:...:function:nope它不是像 mock 一樣「只要呼叫就成功」,而是對已實作的 API 連同驗證與錯誤處理都會執行。雖然尚未確認與真實 AWS 完全一致,但錯誤代碼的格式與 AWS 相同。
官方寫明,未實作的 API 會回傳 HTTP 501。實測 27 個 API 後,其中 4 個回傳 501。
API結果athena list-work-groups501 NotImplemented(operation ListWorkGroups is scaffolded but not implemented in this emulator)translate list-text-translation-jobs501comprehend list-entity-recognizers501quicksight list-dashboards501其餘 23 個都有正常回應(是否與 AWS 一致未確認)「有回應」不代表行為正確。不過,未實作的 API 不會悄悄成功,而是讓測試立刻失敗,這正是 501 的優點。
呼叫結果未簽章請求(curl)403 UnrecognizedClientException: The security token included in the request is invalid.用隨便的 key 簽章的請求AccessDenied: Sign the request with the emulator credentials模擬器重啟後憑證檔案會改寫(每次啟動都會產生新 key)官方說明這樣設計,是為了避免其他網頁或本機程式在不知情的情況下呼叫模擬器。
我另外用不同的埠啟動第二個模擬器(9298)並確認如下。
操作結果在第一個(9297)建立 bucket只存在於 9297第二個(9298)的 bucket 列表空白(看不到 9297 的資源)在第二個建立 bucket只存在於 9298,9297 看不到用第一個的憑證呼叫第二個AccessDenied(key 不同)若依 agent、CI job、branch 分別啟動模擬器,彼此的資源就不會互相看見。
重啟前重啟後2 個 stack(CDKToolkit、CdkSampleStack)、3 個 bucket、3 張 table全部變成 0(stack 與 Lambda 都消失)重啟在 2 秒內完成。雖然每次都能從乾淨狀態開始,但若在測試中途重啟,所有東西都會消失。沒有持久化(如官方所述)。
我做了一個小型 API,用來新增與查詢 item。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,必須明確指定環境變數。模擬器不會自動把 endpoint 與憑證傳給 Lambda 執行環境。實際確認到的值如下。
項目內容Lambda 執行環境Node.js(process.version 為 v22.0.0)。可成功載入 DynamoDB client(@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 role 權限不足,並回傳 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 policy 等安全性測試。這次的權限錯誤雖然被模擬器抓到了,但不代表與 AWS 的判定完全相同(官方也說明了,評估方式與 AWS 不一樣)。
我只透過 AWS_ENDPOINT_URL 環境變數,就把 S3 bucket 與 DynamoDB table 這兩個資源導向模擬器。provider 端則設定了 skip_credentials_validation、skip_requesting_account_id、skip_metadata_api_check、s3_use_path_style。
操作結果init 成功apply 成功(13 秒)。建立 DynamoDB 花了 6 秒destroy 成功(6 秒)apply 後立即執行的 planDynamoDB 出現差異(on_demand_throughput 的 max_read_request_units / max_write_request_units 由 -1 -> null)。1 to change再次 applyUpdateTable 因 ValidationException 失敗這個差異已記錄在官方 repo 的已知 issue 中(步驟 6)。為了切分原因,直接呼叫 describe-table 時,回傳的 OnDemandThroughput 中有 MaxReadRequestUnits: -1 與 MaxWriteRequestUnits: -1。我推測 provider 會把這些未設定欄位視為差異並嘗試更新,但因為更新內容為空,最後 UpdateTable 失敗。至於真實 AWS 在未設定的 table 上是否會回傳這種值,我沒有確認。
以下是執行 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
release v1.0.322 附帶了分數資料 scores.json.gz(commit 7753983、彙整時間 2026-11-11 00:09 UTC)。內容是每一筆忠實度測試的通過/失敗/略過,以及服務清單。因為官方沒有整理成總表,所以本篇是重新按服務統計合格數。
服務pass / 總數比例本次 CDK/Terraform 結果SQS299 / 33390%經由 SNS 的傳遞也能運作ECR233 / 28781%bootstrap 建立成功S3965 / 1,22079%建立、put/get、asset 發佈可運作DynamoDB807 / 1,04477%CDK 可運作。Terraform 的再次 apply 會失敗SNS210 / 28474%可運作Step Functions432 / 58973%執行成功CloudWatch621 / 84873%Alarm 經由 CFN 建立後無法被看見CloudWatch Logs466 / 64273%log group 建立可運作IAM488 / 69171%role 建立可運作STS94 / 16956%get-caller-identity 可運作Lambda365 / 78746%asset 可運作,inline 失敗SSM92 / 45220%bootstrap 參數可取得CloudFormation414 / 2,61216%本次 stack 全部可運作即使分數低,本次這個單純的 stack 仍然跑得動。CloudFormation 只有 16% 的原因,我推測是因為多數測試都針對較複雜的組合(跨服務、含 rollback 或 update 的情境),但我沒有進一步確認細項。也就是說,「比例低」不等於「你的架構跑不起來」,最穩妥的方式仍是針對你要用的 API 實際測試。
aws/aws-emulator 的 issues/ 目錄中,每個已知差異都以一個檔案記錄。它分成兩個資料夾:things-that-do-not-work/(未解決)與 things-that-now-work/(已解決)。我檢查本次驗證發現的差異是否已經被記錄。
方法如下:
issues/(HEAD 與本次使用的 release v1.0.322 相同,commit 7753983)結果如下。
本次觀察到的情況已知 issue 判定DynamoDB 的 on_demand_throughput 會回傳 -1,導致 Terraform 持續出現差異20261108T011128Z-dynamodb-2-net-new-causes-describetable-invents-an.md(未解決)。內容寫明:「DescribeTable 對沒有要求最大值的 table 仍會回傳 OnDemandThroughput 的 -1/-1。CloudFormation、Terraform 等 drift 檢測會對這類 table 報告差異。」一致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 生成的 statementId 不會截斷到 100 字以內,導致所有 CDK RestApi / LambdaRestApi stack 失敗。」一致經由 CloudFormation 建立的 CloudWatch Alarm 不會出現在 describe-alarms 中20261023T113357Z-cloudformation-reports-create-complete-for-logs-cloudwatch-ecs.md(未解決)。其中的 trace 包含以 CFN 部署的 Lambda + Alarm 在 describe_alarms 中為空的確認一致Lambda 的 inline code(Code.fromInline)以 exports is not defined 失敗沒有找到對應 issue。相關的有多筆處理 Node.js runtime 執行忠實度的 issue;雖然有提到 index.handler 應該能以 CommonJS(exports.handler)運作,但沒有找到針對 inline 檔名的說明沒有(可能未回報)Lambda Function URL(addFunctionUrl)的權限因 InvokedViaFunctionUrl 失敗沒有找到對應 issue。雖然有其他 Function URL 相關 issue(例如 20261029T233239Z-lambda-function-url-configs-return-a-default-invokemode.md),但沒有處理 InvokedViaFunctionUrl 的沒有(可能未回報)同一個 stack 的第二個 REST API 在 prod stage 失敗(prod already exists in stack)沒有找到對應 issue。already exists in stack 這類訊息會出現在其他用途(例如物理名稱衝突)相關 issue,但沒有處理 stage 名稱重複的沒有(可能未回報)可以看出來的是:
on_demand_throughput、statementId)還寫了 AWS 端的行為(「CloudFormation 會將 ID 截到 100 字以內」、「OnDemandThroughput 會被省略」)。這不是我在真實 AWS 上親自驗證的結果,而是 issue 中的描述。注意:
20261106T052537Z-apigatewayv2-lambda-logs-the-nodejs22-x-lambda-runtime.md 指出「nodejs22.x runtime 不會內建 AWS SDK v3」,但本次版本中,Lambda 其實可以成功載入 @aws-sdk/client-dynamodb。也就是說,即使在未解決資料夾裡,問題也可能已經修掉。
AWS_ENDPOINT_URL 的 shell 中執行,會連到真實 AWS。CDK 輸出的 URL 本身就是真實 AWS 的主機名稱,不要直接打開注意:Lambda若在 Lambda 裡呼叫其他服務,請明確設定 endpoint 與憑證環境變數。inline code 不可用,建議改用 asset 注意:狀態只存在記憶體中。重啟就全部消失,應以每次測試都重新建立的前提來使用營運上記錄模擬器版本、驗證日、以及所用服務的忠實度分數。因為一天會發佈多次,結果可能會變動最後AWS Emulator 在剛發佈的階段,就已經能支援比我原先預期更廣的範圍,從 CDK 部署到跨 API Gateway、Lambda、DynamoDB 的流程都能跑。不過,也存在像 CloudWatch Alarm 這樣,CloudFormation 顯示成功但實際上不可用的資源。不能只根據部署成功與否來判斷,還必須確認已建立資源的實際行為,這才是運用時的前提。從用途來看,它很適合拿來做 CI 的整合測試,或讓 AI agent 在相對安全的環境中反覆嘗試;但像 IAM 之類的權限驗證,仍然需要在真實 AWS 上進行。由於模擬器一天會更新多次,本篇確認到的差異未來也可能改變。若你在自己的架構中測到官方記錄之外的差異,將其回報到 GitHub Issues,會有助於改善。另需注意的是,本次驗證全部都在模擬器上進行,沒有與真實 AWS 比較。
[AWS] How far can CDK/Terraform stacks run on AWS Emulator? [AWSEmulator]