So sánh các giải pháp giả lập AWS khi dev local
Khoảng nửa năm trước, team mình nhận dự án refactor một hệ thống microservices. Hệ thống dùng S3 để chứa ảnh user, SQS làm hàng đợi cho background workers và DynamoDB lưu session giỏ hàng. Rắc rối bắt đầu xuất hiện: mỗi dev cần một môi trường test độc lập. Kết quả là hóa đơn AWS tháng đó vọt thêm hơn 400 USD chỉ vì anh em tạo tài nguyên test xong quên xóa.
Tụi mình đã ngồi lại và cân nhắc 3 hướng đi:
- Cách 1: Dùng tài khoản AWS Dev riêng trên Cloud (Sandbox Account): Mỗi lập trình viên một account hoặc IAM user riêng. Tính năng và hành vi giống môi trường production 100%.
- Cách 2: Dùng thư viện mock trong code (như Moto cho Python, aws-sdk-mock cho Node.js): Viết unit test bằng cách can thiệp vào các hàm gọi SDK ở tầng ứng dụng.
- Cách 3: Chạy LocalStack qua Docker Compose: Dựng một container LocalStack trên máy local, cung cấp endpoint tương thích với AWS SDK.
Phân tích ưu nhược điểm từng phương án
Sau 2 sprint thử nghiệm thực tế với cả 3 giải pháp, đây là những điểm cốt lõi team mình ghi nhận:
1. AWS Dev Account trên Cloud
- Ưu điểm: Độ chính xác tuyệt đối. Cơ chế phân quyền IAM, độ trễ mạng và các tính năng mới đều khớp hoàn toàn với thực tế.
- Nhược điểm: Chi phí cao. Bắt buộc phải có kết nối Internet ổn định. Việc dọn dẹp tài nguyên thừa sau mỗi lần test rất mất thời gian. Nếu cấu hình sai policy hoặc để lọt vòng lặp vô tận giữa Lambda và SQS, hóa đơn có thể tăng chóng mặt chỉ sau một đêm.
2. Thư viện Mock trong mã nguồn (Moto, aws-sdk-mock)
- Ưu điểm: Tốc độ thực thi rất nhanh. Không cần cài thêm runtime hay container bên ngoài.
- Nhược điểm: Chỉ hiệu quả khi viết Unit Test. Khi chạy integration test giữa nhiều service—chẳng hạn Service A đẩy event vào SQS, Service B consume message rồi lưu file lên S3—việc mock code trở nên rối rắm và không kiểm tra được luồng network thực tế.
3. LocalStack qua Docker Compose
- Ưu điểm: Giả lập đầy đủ HTTP endpoint của AWS ngay trên máy cá nhân. Code backend giữ nguyên logic, chỉ cần trỏ
endpoint_urlvề localhost. Hoàn toàn miễn phí, hỗ trợ làm việc offline. Dọn dẹp sạch sẽ chỉ với một câu lệnhdocker compose down. - Nhược điểm: Bản Community chỉ hỗ trợ các core service phổ biến. Container LocalStack chiếm khoảng 1GB đến 1.5GB RAM khi hoạt động.
Tại sao LocalStack kết hợp Docker Compose là lựa chọn phù hợp?
Sau nửa năm áp dụng cho 8 lập trình viên và pipeline CI/CD, giải pháp này giải quyết gọn gàng bài toán chi phí lẫn tính tiện dụng. Dev mới onboard chỉ cần clone repo, chạy một lệnh Docker là có sẵn toàn bộ hạ tầng AWS ảo để test từ web, API đến worker.
Các bước triển khai LocalStack với Docker Compose
Bước 1: Viết file docker-compose.yml
Tạo file docker-compose.yml tại thư mục gốc của dự án với nội dung:
version: '3.8'
services:
localstack:
container_name: localstack-dev
image: localstack/localstack:latest
ports:
- "127.0.0.1:4566:4566" # Gateway chính cho tất cả AWS services
- "127.0.0.1:4510-4559:4510-4559" # Dải port cho external services nếu cần
environment:
- DEBUG=1
- DOCKER_HOST=unix:///var/run/docker.sock
- AWS_DEFAULT_REGION=ap-southeast-1
volumes:
- "./localstack_data:/var/lib/localstack"
- "/var/run/docker.sock:/var/run/docker.sock"
Khởi động container ở chế độ chạy nền:
docker compose up -d
Bước 2: Cấu hình AWS CLI để kiểm tra kết nối
LocalStack không kiểm tra tính hợp lệ của credential. Bạn có thể đặt giá trị dummy bất kỳ:
export AWS_ACCESS_KEY_ID=test
export AWS_SECRET_ACCESS_KEY=test
export AWS_DEFAULT_REGION=ap-southeast-1
export LOCALSTACK_URL=http://localhost:4566
Bước 3: Thao tác thực tế với S3, SQS và DynamoDB
1. Thao tác với S3:
# Tạo bucket mới
aws --endpoint-url=$LOCALSTACK_URL s3 mb s3://itfromzero-bucket
# Upload một file lên S3
echo "Hello from LocalStack" > test.txt
aws --endpoint-url=$LOCALSTACK_URL s3 cp test.txt s3://itfromzero-bucket/
# Liệt kê danh sách file
aws --endpoint-url=$LOCALSTACK_URL s3 ls s3://itfromzero-bucket/
2. Thao tác với SQS:
# Tạo hàng đợi SQS
aws --endpoint-url=$LOCALSTACK_URL sqs create-queue --queue-name order-processing-queue
# Gửi message mẫu vào queue
aws --endpoint-url=$LOCALSTACK_URL sqs send-message \
--queue-url http://localhost:4566/000000000000/order-processing-queue \
--message-body '{"order_id": 1024, "status": "pending"}'
# Đọc message từ queue
aws --endpoint-url=$LOCALSTACK_URL sqs receive-message \
--queue-url http://localhost:4566/000000000000/order-processing-queue
3. Thao tác với DynamoDB:
# Tạo bảng Users
aws --endpoint-url=$LOCALSTACK_URL dynamodb create-table \
--table-name Users \
--attribute-definitions AttributeName=UserId,AttributeType=S \
--key-schema AttributeName=UserId,KeyType=HASH \
--billing-mode PAY_PER_REQUEST
# Thêm một bản ghi vào bảng
aws --endpoint-url=$LOCALSTACK_URL dynamodb put-item \
--table-name Users \
--item '{"UserId": {"S": "usr_01"}, "Name": {"S": "Nguyen Van A"}}'
# Query toàn bộ dữ liệu
aws --endpoint-url=$LOCALSTACK_URL dynamodb scan --table-name Users
Mẹo debug JSON nhanh từ terminal
Khi thao tác với DynamoDB hoặc SQS qua CLI, terminal thường trả về chuỗi JSON thô khá rối mắt. Bạn có thể dùng jq trực tiếp trên terminal, hoặc dán response vào toolcraft.app/vi/tools/developer/json-formatter để format và kiểm tra cấu trúc dữ liệu nhanh hơn.
Bước 4: Kết nối ứng dụng Backend (Ví dụ Python với Boto3)
Ứng dụng của bạn chỉ cần nhận diện môi trường dev và gán thêm tham số endpoint_url khi khởi tạo AWS client:
import os
import boto3
is_dev = os.getenv("ENV", "development") == "development"
endpoint_url = "http://localhost:4566" if is_dev else None
# Khởi tạo S3 Client
s3_client = boto3.client(
"s3",
endpoint_url=endpoint_url,
aws_access_key_id=os.getenv("AWS_ACCESS_KEY_ID", "test"),
aws_secret_access_key=os.getenv("AWS_SECRET_ACCESS_KEY", "test"),
region_name="ap-southeast-1"
)
# Lấy danh sách buckets
response = s3_client.list_buckets()
print("Buckets hiện có:", [b['Name'] for b in response.get('Buckets', [])])
Kinh nghiệm thực chiến cần lưu ý
- Tự động tạo sẵn tài nguyên khi khởi động: Mount một thư mục chứa script shell vào
/etc/localstack/init/ready.d/trong container. LocalStack sẽ tự chạy các script này ngay khi sẵn sàng, giúp tự động tạo bucket và queue mà không cần gõ lệnh tay mỗi lầnup. - Quản lý dung lượng volume: Thư mục
./localstack_datagiúp lưu lại state qua các lần restart. Tuy nhiên, sau vài tuần test với dung lượng file lớn, folder này có thể phình lên hàng gigabyte. Hãy xóa bớt nếu thấy container khởi động chậm. - Cổng 4566 là duy nhất: Các phiên bản LocalStack hiện tại gom toàn bộ service về duy nhất cổng
4566qua Edge Router. Bạn không cần nhớ các port riêng lẻ như 4572 hay 4576 của các bản cũ trước đây.

