Ngôn ngữ hiển thị:
Cứ tầm 2h sáng, bạn lại thấy điện thoại rung lên với cái tin Slack đáng ghét: "site die rồi anh ơi". Mở SSH lên xem, EC2 vẫn chạy, CPU 100%, MySQL nuốt hết RAM, mà restart xong tầm 15 phút sau lại sập tiếp. Quen quen đúng không? Trường hợp này mình gặp ít nhất một lần ở mỗi startup mình từng đi qua. Vấn đề không nằm ở size con server, mà ở chỗ kiến trúc của bạn đang chỉ là "deploy được", chứ chưa phải "production-grade". Bài này mình sẽ vẽ lại cái blueprint kiến trúc AWS mà mình dùng cho app quy mô vừa và nhỏ — đủ nhẹ để không đốt tiền, đủ chuẩn để ngủ ngon.
Mình định nghĩa "vừa và nhỏ" theo trải nghiệm thực chiến chứ không theo sách: app có dưới 50.000 MAU, traffic peak dưới 500 req/s, team 2–8 dev, ngân sách hạ tầng dưới 300$/tháng. Đa số SaaS B2B mới ra, e-commerce nội địa, internal tool cho doanh nghiệp đều rơi vào ngưỡng này.
Khổ nỗi là, ai cũng nghĩ vì "app mình còn nhỏ" nên cứ một con EC2 cài cả Nginx, Node, MySQL, Redis là xong. Mình hiểu — chính mình cũng từng làm thế. Cho đến khi một bản update Node đụng vào MySQL config, downtime 4 tiếng, mất 12 khách. Lúc đó mới ngộ ra: tách tầng (separation of concerns) không phải vì "chuẩn", mà vì khi mỗi tầng có thể restart độc lập, bạn sẽ ngủ ngon hơn rất nhiều.
Một kiến trúc production thực thụ cần đáp ứng 3 thứ: high availability (một zone chết, app vẫn sống), horizontal scale (traffic peak là bật thêm máy, không phải mua thêm RAM), và tách biệt môi trường (dev/staging/prod không dùng chung database). Nghe có vẻ overkill cho app nhỏ, nhưng tin mình đi, ba thứ đó tiết kiệm cho bạn nhiều đêm hơn là tiền.
Mình sẽ mô tả từng tầng theo đường đi của một request, từ lúc user gõ URL cho đến lúc data về tới database. Cứ hình dung như nước chảy qua nhiều cái lưới lọc, mỗi lưới có một nhiệm vụ.
Tầng 1 — DNS & CDN. Route 53 đứng đầu, làm nhiệm vụ phân giải tên miền. Phía sau là CloudFront — CDN của AWS — cache toàn bộ static assets (HTML, CSS, JS, image). Mọi request ảnh/JS đều không chạm đến server gốc của bạn, latency từ Mỹ về Việt Nam chỉ còn 30–50ms thay vì 200ms. Tầng này gần như free khi traffic dưới 1TB/tháng.
Tầng 2 — WAF & Load Balancer. AWS WAF chặn các pattern tấn công cơ bản: SQL injection, XSS, IP blacklist. Ngay sau đó là Application Load Balancer (ALB), nhiệm vụ phân phối request đến nhiều EC2 instance ở các Availability Zone khác nhau. ALB cũng làm SSL termination — bạn cài cert ở đây, EC2 phía sau chỉ chạy HTTP, đỡ tốn CPU encryption.
Tầng 3 — Compute. Đây là chỗ code của bạn thực sự chạy. Mình khuyên dùng EC2 + Auto Scaling Group (ASG) với 2 instance t3.small ở 2 AZ khác nhau làm baseline, scale lên 6 instance khi CPU vượt 70%. Nếu team đã quen Docker thì chuyển sang ECS Fargate cũng được, đỡ phải lo về AMI và patching OS. Production phải nằm trong private subnet — không có public IP, chỉ ALB ở public subnet được phép gọi vào.
Tầng 4 — Data. RDS Multi-AZ cho database chính (MySQL/PostgreSQL). Multi-AZ nghĩa là AWS dựng sẵn một replica đồng bộ ở AZ khác, khi primary chết, RDS tự failover trong 60–120 giây. Thêm ElastiCache Redis cho session và cache nóng — giảm tải database 70–80% là chuyện thường.
Tầng 5 — Storage & Async. S3 chứa file user upload, ảnh sản phẩm, log. SQS cho các job nền (gửi email, sinh report). Việc tách job nền ra khỏi web request là điều mà mình thấy nhiều team bỏ qua, dẫn đến tình trạng API timeout vì đang chờ gửi 500 cái email.
Phần này mình đi vào chi tiết. Giả sử bạn đang dùng một Node.js app, framework là Express hoặc NestJS. Các bước tương tự áp dụng được cho PHP/Laravel hoặc Python/Django, chỉ khác port và runtime.
Bước 1 — VPC layout. Tạo VPC với CIDR 10.0.0.0/16, chia 6 subnet: 2 public (cho ALB, NAT Gateway), 2 private app (cho EC2/ECS), 2 private data (cho RDS, ElastiCache). Public subnet phải có route đi Internet Gateway, private app subnet route qua NAT Gateway để pull image/package từ npm, private data subnet thì không có route ra Internet luôn.
# Tạo VPC nhanh bằng AWS CLI
aws ec2 create-vpc --cidr-block 10.0.0.0/16 --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=prod-vpc}]'
# Bật DNS hostname để các service có domain nội bộ
aws ec2 modify-vpc-attribute --vpc-id vpc-xxx --enable-dns-hostnamesLưu ý NAT Gateway tốn khoảng 32$/tháng và 0.045$/GB traffic ra. Nếu app bạn pull docker image lớn (>500MB) mỗi lần deploy, hãy cân nhắc VPC Endpoint cho ECR và S3 — đỡ tiền NAT đáng kể.
Bước 2 — Security Group, quan trọng hơn bạn nghĩ. SG là firewall ở tầng instance. Quy tắc vàng: ALB-SG cho phép 80/443 từ 0.0.0.0/0; APP-SG chỉ cho phép port app (vd 3000) từ ALB-SG, không phải từ IP; DB-SG chỉ cho phép 3306 từ APP-SG. Khi bạn reference SG-to-SG thay vì IP, scale ra bao nhiêu instance cũng không phải sửa rule.
Nói thật thì lỗi mình thấy nhiều nhất ở các bạn junior là mở 0.0.0.0/0 cho cả DB-SG để "dev cho dễ". Bot quét port 3306 trên IPv4 chỉ mất 4 tiếng để tìm ra bạn — kinh nghiệm xương máu.
Bước 3 — Launch Template & Auto Scaling Group. Tạo Launch Template với AMI Amazon Linux 2023, user-data script tự pull code từ S3 hoặc GitHub Actions artifact, chạy npm ci, start app với pm2. Sau đó tạo ASG dùng template này, min=2, max=6, desired=2, gắn target group của ALB.
# user-data tiêu biểu
#!/bin/bash
yum update -y
curl -fsSL https://rpm.nodesource.com/setup_20.x | bash -
yum install -y nodejs git
npm install -g pm2
cd /home/ec2-user
aws s3 cp s3://my-deploy-bucket/app.tar.gz .
tar xzf app.tar.gz && cd app
npm ci --omit=dev
pm2 start ecosystem.config.js
pm2 startup systemd -u ec2-user --hp /home/ec2-userMẹo: nhớ thêm CloudWatch Agent trong user-data để log /var/log/app.log chảy thẳng vào CloudWatch Logs. Không có log tập trung thì khi 4 EC2 cùng chạy, bạn debug bằng cách nào?
Bước 4 — RDS Multi-AZ. Tạo RDS instance class db.t4g.small (ARM Graviton, rẻ hơn x86 25%), bật Multi-AZ. Quan trọng: tạo parameter group riêng, không dùng default — vì default không cho sửa, và bạn sẽ cần sửa max_connections, innodb_buffer_pool_size. Bật automated backup, retention 7 ngày là đủ cho startup.
Đừng quên enable Performance Insights — free 7 ngày retention, cho bạn nhìn rõ query nào ăn CPU. Một lần mình tìm ra cái N+1 query ăn 60% CPU chỉ trong 5 phút nhờ tool này.
Bước 5 — ElastiCache Redis & CloudFront. Redis dùng cluster mode disabled cho đơn giản, cache.t4g.micro đủ cho phần lớn use case. CloudFront tạo distribution trỏ về ALB làm origin, behavior cho /static/* cache TTL 1 năm, behavior cho /api/* không cache. Bật Origin Shield nếu CloudFront đang gửi quá nhiều request về origin.
Mẹo xương máu: luôn để CloudFront forward header
Hostvề origin, không thì ALB sẽ trả về cert không khớp. Mình mất 2 tiếng debug cái này.
Một là vụ NAT Gateway một AZ. Để tiết kiệm 30$, nhiều team chỉ tạo 1 NAT Gateway ở 1 AZ rồi route cả hai private subnet qua đó. AZ đó chết một phát là cả app sập, dù bạn có Multi-AZ ở compute. Nguyên tắc: HA phải end-to-end, không có chỗ nào single AZ. Hoặc cứ tạo 2 NAT, hoặc dùng NAT Instance tự dựng để tiết kiệm.
Hai là RDS không có read replica. Multi-AZ chỉ giải quyết failover, không giải quyết scale read. Khi traffic đọc tăng, primary nghẽn ngay. Nguyên tắc: report, analytic, search luôn đẩy sang read replica. Code app phải có cơ chế tách connection — Sequelize, Prisma, TypeORM đều support sẵn read/write split.
Ba là bỏ qua AWS Budget Alert. Set budget 250$/tháng, alert ở 80% và 100%, gửi vào email + Slack. Mình từng quên tắt một con r5.4xlarge sau khi load test — hôm sau bill 180$ đập vào mặt. Từ đó không bao giờ quên Budget Alert nữa.
Q: Mình có nên dùng EKS thay ECS cho app nhỏ?A: Không, trừ khi team đã có DevOps chuyên trách. EKS control plane 73$/tháng, cấu hình network policy phức tạp, debug khó. ECS Fargate đơn giản hơn nhiều và đủ cho 95% startup. Khi nào team trên 20 dev và có nhu cầu service mesh thì hãy nghĩ đến EKS.
Q: Chi phí thực tế của blueprint này bao nhiêu mỗi tháng?A: Cụ thể: 2 EC2 t3.small (~25$), ALB (~18$), RDS db.t4g.small Multi-AZ (~50$), ElastiCache t4g.micro (~12$), NAT Gateway x2 (~64$), CloudFront + S3 + Route 53 (~10$). Tổng tầm 180–220$/tháng với traffic dưới 5TB. Bật Savings Plan 1 năm thì giảm thêm 30%.
Q: Khi nào nên migrate từ kiến trúc này sang microservice?A: Khi codebase quá lớn để 1 team nắm hết, hoặc khi có 2+ team cùng đụng vào 1 repo gây conflict liên tục. Không phải vì traffic. Mình thấy app monolith chạy ngon ở 100k MAU rất nhiều — đừng tách microservice chỉ vì nghe nói hay.
Q: Có nên dùng AWS Lambda cho web API thay vì EC2?A: Tốt nếu API truy cập thấp và không đều (vd internal tool). Còn web public traffic cao, Lambda gặp cold start (200–800ms) khiến UX kém. Mình thường mix: web chính trên EC2/ECS, các job cron/webhook trên Lambda.
Q: Làm sao test failover Multi-AZ trước khi lên production?A: RDS có nút "Reboot with failover" trong console, click cái sẽ trigger failover thật. Trên ECS thì stop 1 task ở AZ A, observe app vẫn xử lý request qua AZ B. Cứ làm trên staging trước, biết được downtime thực bao lâu thì mới tự tin lên prod.
Có một câu mình hay nói với mấy bạn em trong team: "kiến trúc production không phải để đẹp, mà để ngủ ngon". Blueprint này không phải invent ra cái gì mới — nó chỉ là tổng hợp các pattern AWS Well-Architected Framework, gói gọn lại ở quy mô phù hợp với startup. Bạn có thể bỏ ElastiCache nếu chưa cần cache, có thể bỏ ALB và để CloudFront trỏ thẳng vào single EC2 nếu là MVP thật sự — quan trọng là biết đang bỏ cái gì và đổi lại được cái gì.
Cái khó nhất khi setup AWS production không phải về kỹ thuật, mà về kỷ luật: chia VPC đúng từ đầu, đặt SG đúng từ đầu, gắn tag cost đúng từ đầu. Sau 6 tháng app đông, sửa mấy thứ đó tốn gấp 10 lần lúc setup. Đầu tư 2 tuần dựng cho đúng còn hơn đốt 6 tháng vá víu.