Tại Sao Tôi Đổi LocalStack Sang Floci — Và Không Quay Đầu Lại

Tại Sao Tôi Đổi LocalStack Sang Floci — Và Không Quay Đầu Lại

LocalStack bắt đầu chặn CI bằng auth token. Đây là cách tôi dời daily AWS dev loop sang Floci — khởi động nhanh hơn, MIT license, không cần đăng ký.


Mục lục

CI của tôi vỡ vào một sáng thứ Ba. Xanh suốt một năm, đột nhiên đỏ chót không rõ lý do. Tôi kéo log xuống — LOCALSTACK_AUTH_TOKEN missing. À. Cái deadline 23 tháng 3 đã đến và tôi đã quên mất.

Ba tiếng sau, tôi đã dời cả stack khỏi LocalStack và chuyển sang Floci. Cùng endpoint, cùng boto3 client, không cần tài khoản vendor, ít YAML hơn. Đây là báo cáo thực địa từ một engineer vừa migrate một dự án AWS cỡ vừa và sống sót để kể lại.

Câu chuyện “đám mây bỏng ngô”

Floci là một AWS local emulator. Cùng phân khúc với LocalStack — chạy S3, SQS, DynamoDB, Lambda, RDS ngay trên laptop thay vì một AWS account thật. Nó bind vào port 4566, đúng port mà LocalStack dùng, nên phần lớn code hiện có của bạn chạy được mà không phải sửa.

Tên Floci lấy từ cirrocumulus floccus — những đám mây nhỏ hình bỏng ngô lúc hoàng hôn. Triết lý thiết kế đi theo đúng cái tên: nhỏ, nhanh, không rườm rà. Nó là một Quarkus Native binary được compile bằng GraalVM, nghĩa là nó khởi động trong đúng khoảnh khắc bạn chớp mắt.

# One container, no account, no auth token, no sign-up wall
docker run -d --name floci \
  -p 4566:4566 \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -e FLOCI_DEFAULT_REGION=us-east-1 \
  hectorvent/floci:latest

Cái mount docker.sock đó là thứ cho phép Floci spawn Lambda runtime container thật. Bỏ nó ra và lambda.invoke() sẽ lịch sự từ chối làm bất cứ điều gì hữu ích.

Floci đối đầu LocalStack, nhìn một phát là thấy

Tôi không giả vờ đây là trận đấu công bằng về coverage — LocalStack Ultimate hỗ trợ 110+ service còn Floci ship với 35. Nhưng ở mọi chiều khác mà tôi quan tâm hàng ngày, các con số kể một câu chuyện rất thẳng thắn.

Tiêu chíFlociLocalStack Community (2026)
Cold start~24 ms~3.3 s
Idle memory~13 MiB~143 MiB
Image size~90 MB~1.0 GB
Số service35Hobby hạn chế; bản trả tiền 110+
Auth token cho CIKhông bao giờBắt buộc từ 23/3/2026
LicenseMIT, miễn phí vĩnh viễnHobby miễn phí phi thương mại; Starter $39/tháng
Số SDK test pass1.850+ công khaiNội bộ, không công bố

Hobby tier của LocalStack vẫn miễn phí cho cá nhân, nhưng cấm dùng thương mại và bắt tạo account. Đó mới là phần khó chịu nhất: CI runner giờ cần token thật, gắn với một con người thật, và token đó hành xử như mật khẩu mà bạn không xoay tua được nếu không làm vỡ build.

Dev loop hàng ngày của tôi, gói gọn trong một docker-compose

Đây là file tôi drop vào mỗi dự án mới. Không có gì lạ — chỉ là cái hình dạng tôi hạ cánh sau khi mất quá nhiều buổi tối vật lộn với container networking.

# docker-compose.yml — local AWS for the whole team
services:
  floci:
    image: hectorvent/floci:latest
    ports:
      - "4566:4566"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock  # Lambda needs this
      - ./.floci-data:/app/data                    # Persist S3/Dynamo between runs
    environment:
      FLOCI_DEFAULT_REGION: us-east-1
      FLOCI_STORAGE_MODE: hybrid
      FLOCI_SERVICES_LAMBDA_DOCKER_NETWORK: bridge

Phần SDK call nhàm chán vẫn y hệt như trước:

import boto3

# Dummy creds — Floci validates SigV4 but doesn't care about the secret itself
s3 = boto3.client(
    "s3",
    endpoint_url="http://localhost:4566",
    region_name="us-east-1",
    aws_access_key_id="test",
    aws_secret_access_key="test",
)

s3.create_bucket(Bucket="my-local-bucket")

Cái bẫy tôi dính hai lần trước khi học được: quên .gitignore thư mục .floci-data/. DynamoDB ghi nhanh đến chóng mặt và PR tiếp theo của bạn biến thành 400 file binary nhiễu loạn.

Lambda thực sự chạy code của bạn

Lambda của LocalStack Community trước đây thực thi handler trong một process rút gọn — đủ cho unit test nhẹ nhàng, nhưng không đủ để trả lời câu “wheel Python 3.13 của tôi có load được không.” Lambda của Floci spawn một Docker container thật cho mỗi invocation, đó chính là lý do bạn phải mount docker.sock.

# Package and deploy locally, same shape as real AWS
zip -r function.zip handler.py requirements/
aws --endpoint-url=http://localhost:4566 lambda create-function \
  --function-name hello \
  --runtime python3.13 \
  --role arn:aws:iam::000000000000:role/lambda-exec \
  --handler handler.lambda_handler \
  --zip-file fileb://function.zip

Vì đó là container thật, cold start cũng thật. Phản ứng đầu tiên của tôi là khó chịu; phản ứng thứ hai là “ồ, tôi vừa tóm được một bug mà container không import được lxml do glibc lệch version.” Cái bug đó chắc chắn đã ship lên prod nếu không bắt được tại chỗ.

Lỗi kinh điển: chạy Floci bên trong Docker network riêng của nó rồi kỳ vọng Lambda container kết nối được. Hãy set FLOCI_SERVICES_LAMBDA_DOCKER_NETWORK=bridge hoặc đặt tên compose network rõ ràng. Không thì handler của bạn sẽ nhận một ConnectionRefusedError rất là bối rối.

IAM thật sự kiểm tra, không đóng dấu cho qua

Đây là phần tôi không ngờ mình thích. Floci validate chữ ký SigV4 đầy đủ và tôn trọng IAM role assumption trên Lambda, RDS, ElastiCache. LocalStack Community chưa bao giờ enforce IAM nghiêm túc — bạn có thể chạy toàn bộ test suite với quyền root-equivalent và test vẫn xanh trên các policy sẽ nổ tung ngoài production.

# Assume a readonly role — actually validated, not rubber-stamped
sts = boto3.client("sts", endpoint_url="http://localhost:4566", ...)
creds = sts.assume_role(
    RoleArn="arn:aws:iam::000000000000:role/readonly",
    RoleSessionName="local-test",
)["Credentials"]

# Now exercise your code against scoped creds, not admin
s3_readonly = boto3.client(
    "s3",
    aws_access_key_id=creds["AccessKeyId"],
    aws_secret_access_key=creds["SecretAccessKey"],
    aws_session_token=creds["SessionToken"],
    endpoint_url="http://localhost:4566",
)

Tôi đã bắt được hai policy over-privileged theo cách này ngay trong tuần đầu. Một trong số đó là của tôi, và tôi đã đổ lỗi cho team infra suốt nhiều tháng.

Viết integration test có enforce IAM chạy trên Floci trước khi bạn đụng vào production policy. Lỗi bắt ở local là miễn phí; lỗi bắt ở prod là ghi nhận trong audit.

Storage mode: chọn lời nói dối phù hợp

FLOCI_STORAGE_MODE có bốn giá trị và đúng mode phụ thuộc hoàn toàn vào loại test bạn đang chạy. Mặc định cố gắng hợp lý, nhưng “hợp lý” trong unit test và trong manual dev lại là hai chuyện khác nhau.

ModeHành viDùng cho
memoryMọi thứ biến mất sau restartUnit test, CI
hybridCác service stateful (S3, Dynamo) được lưu, phần còn lại in-memoryLocal dev — mặc định
persistentTất cả được lưu xuống diskManual QA, fixture dài hạn
walWrite-ahead log cho độ bền cao nhấtReproduce bug corruption hiếm gặp

Tôi để mặc định hybrid cho dev và memory cho CI. Lần duy nhất bạn muốn wal là khi một flaky test chỉ fail ở lần chạy thứ 400 và bạn cần chụp lại trạng thái đúng tích tắc nó đi sai.

Cái bẫy: set persistent toàn cục trong CI. Pipeline run tiếp theo kế thừa bảng DynamoDB hỏng từ hôm qua và bạn săn một con ma suốt hai tiếng trước khi nhớ kiểm tra cờ storage.

Nơi Floci không phải câu trả lời

Floci không phải siêu tập của LocalStack. Nếu stack của bạn phụ thuộc vào Glue, Athena, SageMaker, hoặc cái đuôi dài của AWS service, bạn vẫn ở lại với LocalStack Ultimate — và thật lòng, với workload kiểu đó, $89/tháng có lẽ tự trả được cho chính nó. Không emulator nào đạt parity 100% với AWS thật, nhưng LocalStack có một thập kỷ edge case đã được vá mà Floci đơn giản là chưa gặp.

Những chỗ khác tôi sẽ cân nhắc hai lần:

  • Bạn đã trả tiền LocalStack rồi. Nếu ngân sách CI hấp thụ được và test đang pass, migrate vì triết lý thôi là việc vô ích.
  • CloudFormation với cross-service wiring nặng. Coverage CFN của Floci đang lớn dần, nhưng intrinsic function và stack lồng sâu vẫn còn khoảng trống.
  • Team phụ thuộc vào Cloud Pods, snapshot, hay dashboard LocalStack. Đó là các tính năng thương mại của LocalStack; Floci không giả vờ có chúng.

Còn lại, cho mọi thứ trong “S3 + SQS + Dynamo + Lambda + RDS” ở giữa — đúng là đa phần công việc AWS thực tế mà tôi làm — Floci giờ là mặc định trung thực.

Một hành động cụ thể: spin Floci lên trên một branch phụ trong tuần này. Trỏ endpoint_url hiện có vào port 4566 trên cùng container image và xem có bao nhiêu test không thèm để ý. Những test để ý đang nói cho bạn điều gì đó thú vị về stack của bạn, và học điều đó bây giờ rẻ hơn rất nhiều so với học lúc auth token sập vào 9 giờ sáng thứ Ba.

Emulate vui vẻ. Và lúc nào đó hãy ngắm hoàng hôn — mấy đám mây hình bỏng ngô đúng là xứng đáng với cái tên.

Nguồn tham khảo

Luồng

0
⌘/Ctrl+Enter để gửiGõ / để xem lệnh · Tab để @nhắc tên