Ngày 11 tháng 11 năm 2026, AWS phát hành AWS Emulator. Đây là một bản sao các API của AWS, chạy ngay trên máy của bạn (local). Nó miễn phí, không cần tài khoản AWS, mã nguồn thuộc phạm vi công cộng (public domain, giấy phép CC0), và hỗ trợ hơn 200 dịch vụ. Nó ra mắt bảy tháng sau khi LocalStack bắt đầu yêu cầu tài khoản và auth token cho image hiện hành của họ, nên nhiều người đang xem nó là lựa chọn thay thế LocalStack miễn phí mà họ chờ đợi bấy lâu.
Phần lớn bài này là hướng dẫn cài đặt: Docker trên máy Mac hoặc Windows, gói cài cho Linux, các biến môi trường để AWS CLI và boto3 gọi tới nó, Docker Compose, pytest và GitHub Actions. Mọi lệnh trong bài tôi đều tự chạy thử, trên phiên bản 1.0.342. Sau phần cài đặt là những gì nó làm đúng và làm sai khi tôi cho các bài kiểm thử (test) S3 của hai dự án của chính mình chạy trên nó, đặt cạnh LocalStack 4.12, Moto và Floci. Hai dự án đó là luồng upload trong ứng dụng marketplace của tôi, và bucket mà một server Codemagic Patch tự host (self-hosted) dùng để lưu các bundle cập nhật OTA.
AWS Emulator là gì? Tóm tắt trong một phút#
AWS không phát hành sẵn container image. Bạn tự build image từ một Dockerfile chín dòng có nhiệm vụ cài gói Linux của họ, chạy nó trên cổng 9297, rồi đặt vài biến môi trường. Từ đó trở đi, SDK của bạn nói chuyện với trình giả lập (emulator) thay vì với AWS thật, còn code của bạn thì không phải sửa gì:
docker build -t aws-emulator . # the Dockerfile is below
mkdir -p ~/.aws/emulator
docker run --rm -d --name aws-emulator -p 127.0.0.1:9297:9297 \
-e HOME="$HOME" -v "$HOME/.aws/emulator:$HOME/.aws/emulator" \
aws-emulator -p 9297 --telemetry off
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
aws sts get-caller-identity # "Account": "000000000000"Nó sẵn sàng sau khoảng nửa giây, và lúc rảnh chỉ chiếm khoảng 175 MiB bộ nhớ. Mọi dữ liệu đều nằm trong bộ nhớ, nên dừng container là mất sạch.
Có cần tài khoản không? (LocalStack vs AWS Emulator)#
Không. AWS Emulator, Moto và Floci đều chạy được mà không cần đăng ký gì cả. AWS Emulator thậm chí còn không cần thông tin xác thực (credentials) AWS của bạn: mỗi lần khởi động, nó tự tạo một access key riêng và chỉ chấp nhận những request được ký bằng key đó.
Bên đã thay đổi là LocalStack. Từ tháng 3 năm 2026, image hiện hành của họ cần một LOCALSTACK_AUTH_TOKEN, lấy từ một tài khoản miễn phí (gói miễn phí của họ nhắm tới việc dùng cho sở thích cá nhân và mục đích phi thương mại) hoặc từ tài khoản trả phí. Không có token thì container dừng ngay lúc khởi động. Tôi đã thử cả hai: image gắn tag latest (2026.10.7, build ngày 11 tháng 11) thoát với exit code 55, còn bản 4.12, bản phát hành cộng đồng (community) cuối cùng và cũng là phiên bản mà chính LocalStack khuyên nên ghim cố định (pin), thì vẫn khởi động được mà không cần token.
Có một thứ AWS Emulator muốn lấy của bạn, đó là telemetry (dữ liệu đo lường sử dụng), được bật sẵn theo mặc định. AWS nói rằng dữ liệu này không bao giờ chứa request, code hay credentials của bạn, nhưng nếu bạn muốn nó không gửi gì cả thì có ba cách tắt: --telemetry off trên dòng lệnh, EMULATOR_TELEMETRY=off trong biến môi trường, hoặc đoạn sau trong ~/.aws/emulator/config.toml:
[telemetry]
enabled = falseCài đặt AWS Emulator bằng Docker trên macOS hoặc Windows#
Trên máy Mac hoặc Windows thì Docker là con đường phù hợp. Lưu nội dung sau thành file Dockerfile trong một thư mục trống. Đây là Dockerfile do chính AWS cung cấp, lấy từ hướng dẫn Docker của họ:
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"]Nếu dùng chip Apple silicon hay bất kỳ máy ARM nào khác, hãy đổi x86_64.rpm thành aarch64.rpm. Sau đó build và khởi động:
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 offTrên máy Mac của tôi, bước build mất 88 giây và image nặng 1.67 GB. Có ba phần trong dòng docker run đó quan trọng hơn vẻ ngoài của chúng:
-p 127.0.0.1:9297:9297, không phải-p 9297:9297. Cách viết ngắn sẽ mở trình giả lập ra cho cả mạng của bạn truy cập.- Volume và
-e HOME. Lúc khởi động, trình giả lập ghi bốn file vào~/.aws/emulator/9297/: credentials của nó, một fileconfigcủa AWS, chứng chỉ của nó (ca.pem), và một Java trust store. Mount thư mục đó vào đúng cùng đường dẫn bên trong container chính là cách để SDK trên máy host đọc được các file này. - Số cổng xuất hiện ba lần. Nếu đổi cổng thì phải đổi ở mọi chỗ. Trình giả lập ghi số cổng vào các file đó, và hai container dùng chung một thư mục sẽ ghi đè file của nhau.
Cài AWS Emulator trên Linux#
Trên Linux thì không cần Docker. Có sẵn gói Debian và gói RPM:
# Debian / Ubuntu (use aws-emulator_arm64.deb on ARM)
curl -fsSLo aws-emulator.deb https://downloads.aws.com/aws-emulator/packages/aws-emulator/LTS/latest/aws-emulator_amd64.deb
sudo apt-get install -y ./aws-emulator.deb
# Amazon Linux, Fedora, RHEL (aarch64.rpm on ARM)
sudo dnf install -y https://downloads.aws.com/aws-emulator/packages/aws-emulator/LTS/latest/aws-emulator-x86_64.rpm
aws-emulator --telemetry offGói cài nặng 150 MB. Nó chạy ở tiền cảnh (foreground) trên 127.0.0.1:9297 và ghi đúng bốn file như trên vào ~/.aws/emulator/9297/. Nhấn Ctrl+C sẽ dừng nó một cách gọn gàng và xóa luôn các file đó. Khi chạy kiểm thử, hãy thêm --idle-timeout 900 để nó tự tắt sau 15 phút không nhận request nào, như vậy một trình giả lập bị bỏ quên sẽ không nằm chạy suốt cả tuần.
Trỏ AWS CLI và boto3 vào trình giả lập#
Đây là các biến cần đặt. SDK nào đọc biến môi trường AWS chuẩn cũng sẽ tự nhận chúng, nên code đang được kiểm thử không cần khai báo endpoint hay key nào:
E=~/.aws/emulator/9297
export AWS_ENDPOINT_URL=https://127.0.0.1:9297
export AWS_SHARED_CREDENTIALS_FILE=$E/emulator_credentials
export AWS_CONFIG_FILE=$E/config
export AWS_CA_BUNDLE=$E/ca.pem
export AWS_REGION=us-east-1
export AWS_EC2_METADATA_DISABLED=true
export AWS_DISABLE_HOST_PREFIX_INJECTION=true
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN AWS_PROFILE
curl -s --cacert $AWS_CA_BUNDLE $AWS_ENDPOINT_URL/__admin/health # ok
aws sts get-caller-identity # 000000000000Sau đó, boto3 không cần cấu hình gì thêm cũng chạy được:
import boto3
s3 = boto3.client("s3")
s3.create_bucket(Bucket="uploads")
print(s3.list_buckets()["Buckets"])Dòng unset là dòng đừng bỏ. Biến môi trường được ưu tiên hơn file credentials, nên nếu key AWS thật của bạn vẫn còn được export trong shell thì SDK sẽ ký request bằng key đó, trình giả lập từ chối request, và bạn mất mười phút ngồi thắc mắc tại sao. aws sts get-caller-identity là cách kiểm tra nhanh: trình giả lập luôn là tài khoản 000000000000. Nếu bạn thấy số tài khoản của chính mình, tức là bạn đang nói chuyện với AWS thật.
Vài thứ khác tôi đã vấp phải, hoặc suýt vấp:
- Giữ
127.0.0.1trong endpoint, đừng dùnglocalhost. Với địa chỉ IP, các SDK đặt tên bucket trong đường dẫn (path). Vớilocalhost, SDK cho JavaScript, Java và Go lại chuyển tên bucket vào host name (uploads.localhost), và trình giả lập không chấp nhận kiểu đó. - Chỉ có HTTPS, và mỗi lần khởi động là một chứng chỉ mới. boto3, AWS CLI và SDK cho Go đọc
AWS_CA_BUNDLE. Node cầnNODE_EXTRA_CA_CERTStrỏ tới cùng fileca.pemđó, còn Java dùngtruststore.p12thông quaJAVA_TOOL_OPTIONS.AWS_CA_BUNDLEthay thế toàn bộ danh sách chứng chỉ tin cậy của tiến trình (process) đó, nên một bài kiểm thử mà còn gọi tới một API HTTPS thật thì cần một bundle gộp cả hai lại. - Không có cách reset. Không có route quản trị nào để xóa sạch dữ liệu, nên hãy cho mỗi bài kiểm thử một tên bucket riêng, hoặc khởi động lại trình giả lập giữa các bộ kiểm thử (test suite).
- Key thay đổi sau mỗi lần khởi động. Đừng chép chúng vào file
.env. Hãy trỏ tới file credentials. - Nếu ứng dụng của bạn tự tạo client với endpoint được khai báo cụ thể (ví dụ
AWS_S3_ENDPOINT_URLcủa django-storages), hãy lấy giá trị đó từAWS_ENDPOINT_URLluôn, nếu không thì riêng client đó vẫn tiếp tục gọi tới dịch vụ thật.
Chạy AWS Emulator bằng Docker Compose#
Với dự án đã có sẵn compose.yaml, service dưới đây build image từ Dockerfile ở trên, lưu các file của trình giả lập trong thư mục dự án thay vì thư mục home của bạn, và có health check, nên docker compose up --wait chỉ trả về khi nó đã sẵn sàng:
services:
aws:
build: ./aws-emulator # the folder with the Dockerfile
command: ["-p", "9297", "--telemetry", "off"]
ports: ["127.0.0.1:9297:9297"]
environment:
HOME: ${PWD}/.aws-emulator-home
volumes:
- ./.aws-emulator-home/.aws/emulator:${PWD}/.aws-emulator-home/.aws/emulator
mem_limit: 2g
healthcheck:
test: ["CMD", "curl", "-sf", "--cacert", "${PWD}/.aws-emulator-home/.aws/emulator/9297/ca.pem",
"https://127.0.0.1:9297/__admin/health"]
interval: 1s
retries: 30mkdir -p .aws-emulator-home/.aws/emulator
docker compose up -d --wait aws # healthy in about 5 s with the image already built
export E=$PWD/.aws-emulator-home/.aws/emulator/9297 # then the variables from aboveNhớ thêm .aws-emulator-home/ vào .gitignore. Thư mục này chứa key của lần chạy đó.
Dùng AWS Emulator với pytest để kiểm thử S3 local#
Bản thân các bài kiểm thử không có gì dành riêng cho trình giả lập. Thứ tôi thêm vào là một lớp bảo vệ, vì kiểu thất bại tôi lo nhất không phải là một test đỏ (fail), mà là một test xanh (pass) nhưng thực ra đã chạy trên một bucket thật:
# conftest.py
import os
import uuid
import boto3
import pytest
@pytest.fixture(scope="session", autouse=True)
def only_against_the_emulator():
url = os.environ.get("AWS_ENDPOINT_URL", "")
if not url.startswith(("https://127.0.0.1", "http://127.0.0.1")):
pytest.exit(f"AWS_ENDPOINT_URL is {url!r}; refusing to run against real AWS")
@pytest.fixture
def s3():
return boto3.client("s3")
@pytest.fixture
def bucket(s3):
name = f"test-{uuid.uuid4().hex[:12]}" # no reset route, so never reuse a name
s3.create_bucket(Bucket=name)
return nameKhi đó, một bài kiểm thử trông cũng như mọi bài khác:
import os
import requests
def test_presigned_upload_rejects_the_wrong_content_type(s3, bucket):
url = s3.generate_presigned_url(
"put_object", ExpiresIn=300,
Params={"Bucket": bucket, "Key": "media/a.jpg", "ContentType": "image/jpeg"},
)
r = requests.put(url, data=b"<html>", headers={"Content-Type": "text/html"},
verify=os.environ["AWS_CA_BUNDLE"])
assert r.status_code == 403Bài này lấy từ luồng upload của ứng dụng marketplace của tôi, và nó là ví dụ tốt cho thấy vì sao chọn trình giả lập nào lại quan trọng: ba trong năm công cụ tôi thử trả về 200 cho request này. Phần sau sẽ nói kỹ hơn. Nếu mỗi lần thay đổi code bạn chỉ chạy những bài kiểm thử bị ảnh hưởng, thì pytest-testmon vẫn dùng được với thiết lập này mà không phải sửa gì.
Chạy AWS Emulator trên GitHub Actions#
Trên runner của GitHub, bỏ qua Docker và cài thẳng gói Debian. Tiến trình chạy nền được khởi động ở một step sẽ tiếp tục chạy cho tới hết job:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v10
- name: Start AWS Emulator
run: |
curl -fsSLo aws-emulator.deb https://downloads.aws.com/aws-emulator/packages/aws-emulator/LTS/latest/aws-emulator_amd64.deb
sudo apt-get install -y ./aws-emulator.deb
aws-emulator --telemetry off --idle-timeout 900 > emulator.log 2>&1 &
E=$HOME/.aws/emulator/9297
for i in $(seq 30); do
curl -sf --cacert $E/ca.pem https://127.0.0.1:9297/__admin/health && break
sleep 1
done
{
echo "AWS_ENDPOINT_URL=https://127.0.0.1:9297"
echo "AWS_SHARED_CREDENTIALS_FILE=$E/emulator_credentials"
echo "AWS_CONFIG_FILE=$E/config"
echo "AWS_CA_BUNDLE=$E/ca.pem"
echo "AWS_REGION=us-east-1"
echo "AWS_EC2_METADATA_DISABLED=true"
echo "AWS_DISABLE_HOST_PREFIX_INJECTION=true"
} >> "$GITHUB_ENV"
- run: uv run pytestTôi đã chạy đúng các bước này trong một container ubuntu:24.04 mới tinh (dùng gói arm64, vì máy Mac của tôi chạy ARM). Bước cài đặt mất 29 giây, gần như toàn bộ là thời gian tải 150 MB, và health check trả lời trong vòng 4 giây. Đừng thêm bất kỳ credentials AWS thật nào vào job này. Đó là cách rẻ nhất để chắc chắn không có gì trong job chạm tới AWS.
LTS/latest là bản nào được phát hành gần nhất, và như bạn sẽ thấy ở dưới, bản đó thay đổi vài lần mỗi ngày. Nếu muốn CI cho kết quả tái lập được (reproducible), AWS có một trang hướng dẫn cài một phiên bản cụ thể. Còn cách tôi bố trí phần còn lại của pipeline thì bạn xem các pipeline GitHub Actions tôi đang chạy.
Chuyển từ LocalStack sang AWS Emulator#
Nếu các bài kiểm thử của bạn đang chạy trên LocalStack, phần lớn việc chuyển đổi chỉ là thay đổi cấu hình:
| LocalStack | AWS Emulator | |
|---|---|---|
| Endpoint | http://localhost:4566 | https://127.0.0.1:9297 cùng với AWS_CA_BUNDLE |
| Credentials | gì cũng được (test / test) | file nó ghi ra mỗi lần chạy, hoặc --auth off |
| ID tài khoản | 000000000000 | 000000000000 |
| Script khởi tạo | init hook trong /etc/localstack/init/ready.d | không có: tạo tài nguyên trong fixture, sau khi health check xong |
| Image | localstack/localstack (cần token từ sau 4.12) | tự build từ gói của AWS |
Init hook là phần thật sự tốn công nếu bạn dùng chúng nhiều. Tôi thì không, vì mỗi bài kiểm thử tự tạo bucket riêng. Và không có gì còn lại sau khi khởi động lại: trình giả lập giữ mọi thứ trong bộ nhớ.
AWS Emulator bắt được gì trong các bài kiểm thử S3 của tôi, và bỏ sót gì#
Cài đặt là phần dễ. Điều tôi muốn biết là: ở những chỗ code của tôi phụ thuộc vào cách S3 hoạt động, nó có cư xử giống S3 không. Tôi viết 12 phép kiểm tra, mỗi cái assert đúng hành vi của S3 thật theo tài liệu của AWS:
- Sáu cái lấy từ luồng upload của ứng dụng marketplace. Ứng dụng đưa cho trình duyệt một presigned URL (URL ký sẵn), trình duyệt PUT ảnh thẳng vào bucket (nên bucket cần cấu hình CORS), và một sự kiện
ObjectCreatedbáo cho backend biết file đã tới nơi. Đó là luồng trong bài viết của tôi về object notification, chỉ khác là ở đây SQS đóng vai webhook. - Sáu cái lấy từ một bucket của Codemagic Patch. Patch là server cập nhật OTA cho ứng dụng React Native (cách tôi chuyển sang dùng nó). Bucket của nó để public để điện thoại tải được bundle, trừ prefix
_internal/, nơi các bản upload nằm chờ được phát hành (publish), và một bucket policy giữ cho prefix đó ở chế độ riêng tư.
Tôi chạy chúng trên AWS Emulator 1.0.342 (một lần với cấu hình mặc định và một lần với --auth off), LocalStack 4.12 (với cấu hình mặc định, và với S3_SKIP_SIGNATURE_VALIDATION=0), Moto 5.2.6 ở chế độ server, Floci 1.5.5, và Silo, bản fork của MinIO mà bộ cài tự host của Patch dùng.
Không cấu hình nào qua được cả 12 phép kiểm tra; tất cả đều nằm trong khoảng từ 6 đến 8. Tổng điểm không quan trọng bằng việc mỗi công cụ bỏ sót phép kiểm tra nào, nên dưới đây là những chỗ lẽ ra đã gây hại cho tôi, theo thứ tự từ nặng nhất.
AWS Emulator khắt khe nhất về phân quyền. Lấy ví dụ presigned URL mà ứng dụng của tôi ký cho đúng một file JPEG. Với LocalStack 4.12 ở cấu hình mặc định, Moto và Floci, tôi dùng chính URL đó để upload một file HTML và vẫn nhận về 200. Trên một bucket phục vụ file công khai, đó là một lỗ hổng stored XSS chỉ chờ ngày bị khai thác. AWS Emulator từ chối với mã 403, đúng như S3, và tiếp tục từ chối khi URL đã hết hạn. (LocalStack 4.12 bắt được cả hai nếu bạn đặt S3_SKIP_SIGNATURE_VALIDATION=0, còn Floci chỉ bắt được URL hết hạn chứ không phát hiện file bị tráo kiểu.) Nó cũng là công cụ duy nhất biết rằng bucket S3 mới tạo có Block Public Access bật sẵn, nên một bucket policy public sẽ bị từ chối cho tới khi bạn tắt tính năng đó, và là công cụ duy nhất áp một Deny tường minh (explicit) lên cả các request có chữ ký của chính server, chứ không chỉ lên người lạ. Đó đúng là những lỗi qua được mọi bài kiểm thử rồi mới hỏng ở lần triển khai (deploy) thật đầu tiên.
Nó chưa kiểm thử được bất cứ thứ gì trình duyệt làm. Một request CORS preflight (request OPTIONS mà trình duyệt gửi trước một lệnh PUT khác origin) nhận về 501 Not Implemented. Khi bật xác thực (authentication), vốn là mặc định, nó còn từ chối mọi request không có chữ ký, nên một lượt tải ẩn danh từ bucket public sẽ nhận 403 ngay cả khi policy cho phép. Cả hai chỗ thiếu này đều đã được ghi nhận trong repository. Dự án lưu những chỗ còn thiếu đã biết thành từng file trong issues/things-that-do-not-work/, và tính đến ngày 13 tháng 11 có 6,840 file như vậy, trong khi 13,409 file khác đã được chuyển sang things-that-now-work/.

--auth off đổi nửa này lấy nửa kia. Khi tắt xác thực, đọc ẩn danh chạy được và bucket policy được đánh giá đúng, nên cả sáu phép kiểm tra của Patch đều đạt. Đó là cấu hình duy nhất làm được điều này. Nhưng khi đó presigned URL cũng không còn được kiểm tra, nên nhóm kiểm tra luồng upload tụt xuống chỉ còn đạt hai. Chạy hai trình giả lập trên hai cổng khác nhau, một cái bật xác thực và một cái tắt, sẽ bao được cả hai nửa, dù như vậy lại thêm một thứ phải giải thích cho người tiếp theo đọc file CI.
Nó thay đổi rất nhanh. Các bản v1.0.290 đến v1.0.342 ra đời chỉ trong ba ngày, bản nào cũng kèm một danh sách sửa lỗi dài (AWS nói rằng chính các coding agent đã xây dựng nó, và nhìn tốc độ là thấy), và một trong các bản sửa ngày 11 tháng 11 là lỗi “presigned GET refused with 403” trong S3. Bất cứ ô nào trong bảng của tôi cũng có thể khác đi vào tuần sau, theo chiều tốt lên hay xấu đi. Hãy ghim phiên bản trong CI và chỉ nâng cấp khi đã chủ động cân nhắc.

Bucket của Patch: một điều kiện trong policy chỉ có ý nghĩa trên S3 thật#
Đây là phần tôi không lường trước. Bộ cài tự host của Patch giữ mọi thứ trong một bucket duy nhất, và policy mà nó áp cho storage đi kèm là kiểu hiển nhiên nhất: ai cũng đọc được, trừ những gì nằm dưới _internal/:
{
"Version": "2012-10-17",
"Statement": [
{ "Effect": "Allow", "Principal": "*", "Action": "s3:GetObject",
"Resource": "arn:aws:s3:::example-ota/*" },
{ "Effect": "Deny", "Principal": "*", "Action": "s3:GetObject",
"Resource": "arn:aws:s3:::example-ota/_internal/*" }
]
}Trên Silo, khi ký bằng credentials admin, policy này làm đúng như những gì nó ghi: người lạ không đọc được _internal/, còn lệnh copy của chính server từ _internal/ sang prefix public (bước “publish”) vẫn chạy qua. Trên S3 thật, một Deny tường minh với "Principal": "*" áp dụng cho tất cả mọi người, kể cả credentials của chính bạn, nên cùng policy đó sẽ chặn luôn bước publish của server. AWS Emulator từ chối lệnh copy đó với mã 403, đúng như quy tắc đánh giá policy của AWS mô tả cách S3 xử lý.
Đội ngũ bảo trì Patch rõ ràng biết chuyện này, vì policy mẫu cho AWS S3 của họ thêm một điều kiện vào Deny:
"Condition": { "StringNotEquals": { "aws:PrincipalAccount": "YOUR_AWS_ACCOUNT_ID" } }Có dòng đó, cả AWS Emulator (chạy với --auth off để kiểm thử được cả nửa ẩn danh) lẫn Moto đều cho worker publish, trong khi vẫn từ chối các lượt đọc ẩn danh vào _internal/. Nhưng chỉ AWS Emulator bắt được phiên bản thiếu dòng đó. Moto, LocalStack 4.12 và Floci đều cho lệnh copy đi qua dù có hay không có điều kiện, nên công cụ nào trong số đó cũng sẽ để tôi đưa một policy lỗi lên production. Đó là lý lẽ rõ ràng nhất tôi tìm được cho việc kiểm thử policy trên trình giả lập khắt khe nhất hiện có. Chép một policy từ cấu hình MinIO sang S3 mà thiếu điều kiện đó thì sẽ chẳng có gì báo lỗi cho tới lần publish thật đầu tiên.
(Server Patch của riêng tôi lưu bundle trên Tigris, nơi hoàn toàn không có bucket policy, nên tôi để _internal/ trong một bucket private riêng. S3_INTERNAL_BUCKET trong cấu hình của Patch sinh ra chính là cho việc này. Tôi cũng có viết bài khách mời có trả phí về Patch cho Codemagic. Bài này không nằm trong số đó, và không ai trả tiền cho nó.)
Giờ tôi sẽ dùng cái nào#
| Thời gian sẵn sàng | Bộ nhớ khi rảnh | Image | Tài khoản | |
|---|---|---|---|---|
| AWS Emulator 1.0.342 | 0.4–0.5 s | 176 MiB | 1.67 GB (bạn tự build) | không cần |
| LocalStack 4.12 | 0.9–2.2 s | 90 MiB | 1.89 GB | không cần với 4.12, các bản sau cần token |
| Moto 5.2.6 (server) | 0.7–0.8 s | 45 MiB | 560 MB | không cần |
| Floci 1.5.5 | 0.3–0.4 s | 11 MiB | 292 MB | không cần |
Đo trên máy Mac của tôi trong Docker, mỗi công cụ ba lần khởi động nguội (cold start). Không công cụ nào chậm tới mức đáng bận tâm.
Với kiểm thử backend, tôi sẽ bắt đầu bằng AWS Emulator, vì những lỗi nó bắt được (phân quyền, chữ ký, Block Public Access) là những lỗi mà nếu không có nó thì chỉ lộ ra trên production, và đây cũng là công cụ duy nhất trong danh sách vừa miễn phí, vừa đang được cập nhật, lại do chính những người làm ra S3 phát triển. Cho tới khi CORS preflight được hỗ trợ, tôi sẽ giữ một job nhỏ chạy trên Floci cho các phép kiểm tra liên quan tới trình duyệt (preflight, và tải ẩn danh từ prefix public). LocalStack 4.12 với chế độ kiểm tra chữ ký nghiêm ngặt cũng đạt các phép này, nhưng nó đã bị đóng băng và sẽ không nhận bản sửa lỗi nào nữa. Nếu bài kiểm thử của bạn viết bằng Python và bạn không muốn chạy container, các mock chạy ngay trong tiến trình (in-process) của Moto vẫn là lựa chọn nhẹ nhất. Chỉ đừng trông cậy vào nó cho các phép kiểm tra presigned URL.
Bộ 12 phép kiểm tra này đủ ngắn để bạn viết lại cho bucket của mình trong một buổi chiều, và tôi khuyên bạn nên làm vậy trước khi tin vào bất kỳ trình giả lập nào, kể cả AWS Emulator. Mỗi công cụ trong bảng trượt ở những phép kiểm tra khác nhau, và đó chính là những chỗ hổng mà bài kiểm thử của bạn sẽ lặng lẽ cho qua.
