Well-Architected 6 pillar và kiến trúc tham chiếu shop-app hoàn chỉnh
Tiếp đi: Well-Architected. Qua 20 bài mình đã dựng rời từng mảnh của shop-app: VPC, ALB, RDS, S3, SQS, Lambda… Giờ mình muốn nhìn cả bức tranh một lần: kiến trúc đầy đủ trông thế nào, và làm sao biết nó "đúng chuẩn" theo cách AWS đánh giá? Sếp mình cũng hay nói "review theo Well-Architected".
Đây là bài khâu lại toàn chương. AWS Well-Architected Framework là bộ câu hỏi và nguyên tắc AWS dùng để đánh giá một kiến trúc, chia thành 6 pillar. Nó cũng chính là xương sống của đề SAA-C03: 4 domain của đề là 4 pillar đầu đổi tên. Bài này đi theo ba phần: (1) 6 pillar với ví dụ cụ thể trên shop-app, (2) kiến trúc tham chiếu hoàn chỉnh và bài nào đã dạy mảnh nào, (3) lab tổng hợp dựng cả stack rồi dọn sạch.
1. Bài toán: shop-app có đủ mảnh nhưng chưa có "bản vẽ"
Những gì đã có sau 20 lab (mỗi thứ dựng riêng, dọn riêng):
22.3 shop-vpc, subnet, IGW, NAT, endpoint 22.11 shop-order-events, shop-order-topic, shop-bus
22.4 EC2 t3.micro chạy shop-app.jar 22.12 shop-image-resize, shop-order-notifier
22.5 shop-alb, shop-tg, shop-asg 22.13 ECR shop-app, shop-cluster, shop-app-svc
22.6 shop-app-assets, shop-app-logs 22.14 shop/db, alias/shop-key, /shop/app/feature-flags
22.7 shop-db Multi-AZ 22.15 /shop/app, shop-alb-5xx-high, shop-overview
22.8 shop-sessions, shop-carts 22.18 AWS Backup plan, DR region
22.9 shop-cache 22.19 tag Project=shop-app, Budget
22.10 shop.example.com, CloudFront, ACM 22.20 shop-waf, GuardDuty, Security Hub
Câu hỏi: ghép lại thì request của khách đi qua đâu? Chỗ nào là SPOF? Chỗ nào tốn tiền vô ích?
Chỗ nào chưa có log? Nếu AZ 1a chết thì sao? Nếu lộ secret thì sao?
2. Vấn đề: không có khung đánh giá thì review chỉ là cảm tính
- Mỗi người review một kiểu: người thiên về bảo mật, người thiên về giá. Không có checklist chung.
- Đánh đổi (trade-off) không được ghi lại: vì sao chọn Multi-AZ mà không multi-region? Vì sao EC2 chứ không Fargate?
- Không đo được tiến bộ: tháng này kiến trúc "tốt hơn" tháng trước ở điểm nào?
- Đề thi SAA chính là một bài review Well-Architected rút gọn: mỗi câu hỏi là một tình huống, bạn phải chọn thiết kế đúng theo pillar mà câu hỏi nhấn ("MOST secure", "MOST cost-effective", "highly available", "LEAST operational overhead").
3. Giải pháp: 6 pillar, mỗi pillar soi vào shop-app
| Pillar | Câu hỏi cốt lõi | Nguyên tắc thiết kế chính | Trên shop-app | Domain SAA |
|---|---|---|---|---|
| Operational Excellence | Chạy, theo dõi, cải tiến hệ thống thế nào? | Operations as code (IaC), thay đổi nhỏ và đảo ngược được, runbook, học từ sự cố, game day | Terraform/CloudFormation cho stack (devops 04); CloudWatch dashboard shop-overview, alarm shop-alb-5xx-high; rolling deploy qua ASG instance refresh / ECS deployment; runbook sự cố GuardDuty (22.20); tag chuẩn | Xuyên suốt |
| Security | Bảo vệ dữ liệu, hệ thống, danh tính? | Identity mạnh, traceability, bảo mật mọi lớp, tự động hóa, mã hóa in transit & at rest, giữ người xa dữ liệu, chuẩn bị cho sự cố | IAM role không access key (22.1–22.2); private subnet, SG chuỗi (22.3); KMS + Secrets Manager (22.14); CloudTrail (22.15); WAF, GuardDuty, Security Hub (22.20); TLS ACM (22.10); S3 block public + OAC | D3 Secure 30% |
| Reliability | Chịu lỗi và phục hồi thế nào? | Tự phục hồi từ lỗi, test recovery, scale ngang, dừng đoán capacity, quản lý thay đổi bằng automation | Multi-AZ: 2 AZ cho ALB/ASG/RDS/ElastiCache (22.5, 22.7, 22.9); SQS + DLQ tách coupling (22.11); health check + ASG thay instance hỏng; AWS Backup, RTO/RPO, DR strategy (22.18); Route 53 failover (22.10) | D2 Resilient 26% |
| Performance Efficiency | Dùng tài nguyên đúng loại, đúng cỡ? | Dùng công nghệ managed, global trong vài phút, serverless, thử nghiệm nhiều, "mechanical sympathy" (chọn đúng công cụ cho dữ liệu) | ElastiCache trước RDS (22.9); DynamoDB cho session/cart (22.8); CloudFront cho assets (22.10); Lambda cho resize ảnh (22.12); Read Replica khi đọc nặng (22.7); chọn instance theo Compute Optimizer (22.19) | D1 High-Performing 24% |
| Cost Optimization | Trả đúng giá cho giá trị nhận được? | Cloud financial management, consumption model, đo hiệu quả, bỏ việc "nặng nhọc không khác biệt", phân tích và quy chi phí | Tag + Cost Explorer + Budget (22.19); Savings Plans cho baseline, Spot cho worker; S3 lifecycle (22.6); Gateway endpoint thay NAT; tắt môi trường lab | D4 Cost-Optimized 20% |
| Sustainability | Giảm tác động môi trường? | Hiểu impact, đặt mục tiêu, tối đa utilization, dùng dịch vụ managed/hiệu quả hơn, giảm downstream impact | Right-size, Graviton (t4g/m7g) cho Java, scale theo nhu cầu thay chạy thừa, S3 lifecycle xóa dữ liệu không cần, region có năng lượng sạch khi hợp | Không có domain riêng; đề thi hầu như không hỏi |
Hai khái niệm đi kèm, đề thi hay dùng tên:
- Well-Architected Tool: dịch vụ miễn phí trong Console để tự review workload theo bộ câu hỏi từng pillar, ra danh sách High Risk Issue (HRI) và Medium Risk Issue, kèm improvement plan. Có Lenses chuyên ngành (Serverless Lens, SaaS Lens, Container Build Lens…).
- General design principles: dừng đoán capacity; test ở quy mô production; tự động hóa để thử nghiệm dễ; cho phép kiến trúc tiến hóa; quyết định dựa trên dữ liệu; cải tiến qua game day.
4. Kiến trúc tham chiếu shop-app hoàn chỉnh
Internet
│
┌─────────▼──────────┐
│ Route 53 │ shop.example.com (alias), health check,
│ hosted zone │ failover record → DR region (22.18)
└─────────┬──────────┘
│
┌──────────────────▼───────────────────┐
│ CloudFront │ cache assets + API; ACM cert us-east-1;
│ ├─ origin 1: S3 shop-app-assets (OAC)│ Shield Standard luôn bật
│ └─ origin 2: shop-alb (custom header)│
└──────────┬───────────────────────────┘
│ WAF web ACL shop-waf (managed rules + rate-login)
│ gắn vào CloudFront hoặc ALB
╔════════════════════════════▼══════════════════════════════════════════════════════════╗
║ VPC shop-vpc 10.0.0.0/16 region ap-southeast-1 ║
║ ║
║ ┌─ public shop-public-a 10.0.1.0/24 ─────┐ ┌─ public shop-public-b 10.0.2.0/24 ─────┐║
║ │ ALB shop-alb (node) NAT GW a │ │ ALB shop-alb (node) NAT GW b │║
║ │ SG shop-alb-sg: 443 từ CloudFront │ │ │║
║ └────────────┬───────────────────────────┘ └────────────┬───────────────────────────┘║
║ │ target group shop-tg :8080, health /actuator/health ║
║ ┌─ private shop-private-a 10.0.11.0/24 ──┐ ┌─ private shop-private-b 10.0.12.0/24 ──┐║
║ │ shop-asg EC2 t3.micro ──or── ECS │ │ shop-asg EC2 t3.micro ──or── ECS │║
║ │ shop-app-svc (Fargate task shop-app-task)│ │ shop-app-svc │║
║ │ role shop-app-ec2-role / task role │ │ SG shop-app-sg: 8080 từ shop-alb-sg │║
║ │ IMDSv2 required, SSM agent │ │ │║
║ │ │ │ │ │ │ │ │ │ │║
║ └────────┼────────────┼──────────┼──────────┘ └────────┼────────────┼──────────┼────────┘║
║ │ │ │ │ │ │ ║
║ ┌────────▼────────────▼──────────▼───────────────────────▼────────────▼──────────▼───────┐║
║ │ data tier (private subnet, SG chỉ từ shop-app-sg) │║
║ │ RDS shop-db PostgreSQL 16 Multi-AZ (primary 1a ⇄ standby 1b) SG shop-db-sg 5432 │║
║ │ └─ Read Replica (tùy chọn) └─ RDS Proxy (nếu Lambda gọi DB) │║
║ │ ElastiCache shop-cache Redis (cluster mode disabled, replica 1b) SG shop-cache-sg │║
║ │ DynamoDB shop-sessions, shop-carts (qua Gateway endpoint, TTL, on-demand) │║
║ └────────────────────────────────────────────────────────────────────────────────────────┘║
║ ║
║ VPC endpoints: S3 + DynamoDB (Gateway, miễn phí); ECR api/dkr, Secrets Manager, CloudWatch ║
║ Logs, SSM, SQS, KMS (Interface) → traffic AWS không qua NAT ║
║ VPC Flow Logs → CloudWatch Logs ║
╚═════════════════════════════════════════════════════════════════════════════════════════╝
│ publish OrderCreated │ upload ảnh (presigned URL)
▼ ▼
SNS shop-order-topic ──fan-out──► SQS shop-order-events ──► Lambda shop-order-notifier (SES/SNS)
│ └─ DLQ shop-order-events-dlq (role shop-lambda-role)
└──────────────────────► EventBridge shop-bus (rule → Step Functions / audit)
S3 shop-app-assets ──event──► Lambda shop-image-resize ──► S3 (prefix thumbnails/)
S3 shop-app-logs ◄── ALB access log, CloudFront log, WAF log (lifecycle → Glacier 90 ngày)
Nền tảng dùng chung:
┌──────────────────────────┐ ┌────────────────────────────┐ ┌─────────────────────────────┐
│ Secrets Manager shop/db │ │ CloudWatch │ │ CloudTrail (org trail) → S3 │
│ Parameter Store │ │ log group /shop/app │ │ AWS Config rules │
│ /shop/app/feature-flags │ │ alarm shop-alb-5xx-high │ │ GuardDuty · Security Hub │
│ KMS alias/shop-key │ │ dashboard shop-overview │ │ Inspector (EC2, ECR) │
│ (RDS, S3, EBS, secret) │ │ X-Ray tracing │ │ AWS Backup plan (RDS, EBS, │
│ ACM cert shop.example.com│ │ Synthetics canary /health │ │ DynamoDB) → vault, cross- │
│ ECR repo shop-app │ │ Logs Insights │ │ region copy cho DR │
└──────────────────────────┘ └────────────────────────────┘ └─────────────────────────────┘
Cost: tag Project=shop-app, Env=prod|lab · Budget · Savings Plans baseline · Spot cho worker
Mỗi mảnh học ở bài nào (đọc lại khi review):
| Mảnh trong sơ đồ | Bài | Pillar chính |
|---|---|---|
| Account, IAM user shop-admin, role ShopAdminRole, MFA, CLI profile shop-lab, Budget đầu tiên | 22.1 | Security, Cost |
| Policy evaluation, AssumeRole, resource policy, permission boundary, Organizations/SCP | 22.2 | Security |
| shop-vpc, subnet, IGW/NAT, route table, SG chain, VPC endpoint, Flow Logs | 22.3 | Security, Cost |
| EC2 t3.micro, AMI, user data chạy shop-app.jar, EBS, IMDSv2, pricing | 22.4 | Performance, Cost |
| shop-alb, shop-tg, health check, shop-asg min 2 max 4, scaling policy | 22.5 | Reliability |
| S3 shop-app-assets/logs, storage class, lifecycle, OAC, presigned URL, event → Lambda | 22.6 | Reliability, Cost |
| RDS shop-db Multi-AZ, Read Replica, backup/PITR, RDS Proxy | 22.7 | Reliability |
| DynamoDB shop-sessions, shop-carts, TTL, on-demand, DAX, Streams | 22.8 | Performance |
| ElastiCache shop-cache, cache strategy, session store | 22.9 | Performance |
| Route 53 record, health check, failover; CloudFront, OAC, ACM | 22.10 | Performance, Reliability |
| SNS shop-order-topic, SQS shop-order-events + DLQ, EventBridge shop-bus | 22.11 | Reliability |
| Lambda shop-image-resize, shop-order-notifier (Java 21, SnapStart), API Gateway, Step Functions | 22.12 | Performance, Cost |
| ECR shop-app, ECS shop-cluster/shop-app-svc/shop-app-task, Fargate vs EC2, EKS | 22.13 | Operational Excellence |
| KMS alias/shop-key, Secrets Manager shop/db, Parameter Store, rotation | 22.14 | Security |
| CloudWatch /shop/app, alarm, dashboard, Logs Insights, CloudTrail, Config, X-Ray | 22.15 | Operational Excellence |
| EBS vs EFS vs FSx, Storage Gateway, DataSync (nếu shop-app cần shared file) | 22.16 | Performance |
| Kinesis, Athena trên log S3, Glue, QuickSight (phân tích đơn hàng) | 22.17 | Performance |
| RTO/RPO, 4 chiến lược DR, AWS Backup, multi-region, migration 7R, DMS | 22.18 | Reliability |
| Pricing model, Savings Plans/RI/Spot, tag, Cost Explorer, Budget, Compute Optimizer | 22.19 | Cost |
| WAF shop-waf, Shield, GuardDuty, Inspector, Macie, Security Hub, Cognito, Identity Center | 22.20 | Security |
| Bản đồ dịch vụ tổng quan, chọn nơi chạy app, IRSA | devops 05 | Tổng quan |
5. Cách hoạt động: một đơn hàng đi qua kiến trúc
1. Khách mở shop.example.com
Route 53 → CloudFront (edge gần nhất) → cache HIT cho /assets/*, MISS cho /api/*
2. POST /api/orders
CloudFront → WAF (rate rule, SQLi) → shop-alb (TLS termination ACM) → shop-tg
→ EC2/ECS trong private subnet (SG chỉ nhận từ ALB)
3. Trong app
session ← ElastiCache / DynamoDB shop-sessions
secret DB ← Secrets Manager shop/db (cache trong app, rotation tự động)
INSERT orders → RDS shop-db primary (1a); sync replicate → standby (1b)
publish OrderCreated → SNS shop-order-topic
4. Hậu kỳ bất đồng bộ
SNS → SQS shop-order-events → Lambda shop-order-notifier gửi email
(lỗi 3 lần → DLQ shop-order-events-dlq → alarm)
SNS → EventBridge shop-bus → rule audit → S3 / Step Functions
5. Quan sát
log JSON → CloudWatch /shop/app → Logs Insights; metric 5xx → alarm shop-alb-5xx-high → SNS
X-Ray trace xuyên ALB → app → RDS → SQS
CloudTrail ghi mọi API; GuardDuty theo dõi; Security Hub chấm điểm
Nếu AZ 1a chết:
ALB node 1b nhận hết; ASG tạo instance mới ở 1b; RDS failover sang standby (~60–120 giây,
DNS endpoint giữ nguyên); ElastiCache promote replica; SQS/SNS/DynamoDB/S3 vốn multi-AZ.
App chỉ thấy vài giây lỗi kết nối DB → retry (HikariCP + Resilience4j).
Nếu region chết: Route 53 health check fail → failover record sang DR region (Pilot Light /
Warm Standby theo 22.18), RDS cross-region read replica promote, S3 CRR.
6. So sánh: biến thể kiến trúc theo giai đoạn
| Giai đoạn | Compute | Data | HA | Chi phí/tháng (ước, không Free Tier) | Khi nào |
|---|---|---|---|---|---|
| Lab / MVP | 1 EC2 t3.micro public hoặc 1 Fargate task | RDS Single-AZ db.t3.micro, không cache | Không; backup hàng ngày | Vài chục USD; Free Tier gần như phủ hết trừ NAT/ALB | Học, demo, chưa có khách |
| Production nhỏ (sơ đồ trên) | ASG 2–4 EC2 hoặc ECS Fargate 2 task, 2 AZ, sau ALB | RDS Multi-AZ, ElastiCache 1 replica, DynamoDB on-demand | Multi-AZ; Backup + Pilot Light DR | Khoảng vài trăm USD (ALB, NAT x2, RDS Multi-AZ, ElastiCache là các khoản lớn) | Có khách trả tiền, chịu được downtime vài phút |
| Scale | ECS/EKS nhiều service, Graviton, Savings Plans + Spot | Aurora PostgreSQL + Read Replica, ElastiCache cluster mode, DAX | Multi-AZ 3 AZ; Warm Standby multi-region | Nghìn USD trở lên | Traffic lớn, SLA chặt |
| Serverless-first | API Gateway + Lambda (SnapStart), Step Functions | DynamoDB, Aurora Serverless v2, S3 | Mặc định multi-AZ; Global Tables cho multi-region | Theo request; rẻ khi traffic thấp/không đều | Traffic thất thường, team nhỏ, chấp nhận giới hạn Lambda |
Không có "kiến trúc đúng" tuyệt đối. Well-Architected yêu cầu bạn ghi lại trade-off: shop-app chọn Multi-AZ không multi-region vì RTO chấp nhận được là vài giờ và chi phí multi-region gấp đôi.
7. Khi nào review Well-Architected / không
- Nên: trước khi go-live, sau mỗi sự cố lớn, mỗi 6–12 tháng, trước khi scale gấp, khi đổi team vận hành. Dùng Well-Architected Tool để có bằng chứng và theo dõi HRI giảm dần.
- Không cần nghi thức đầy đủ: dự án lab cá nhân, PoC sống 2 tuần. Nhưng 5 câu tự hỏi luôn đáng: SPOF ở đâu? Secret ở đâu? Ai được gọi gì? Tắt được gì? Log có không?
- Cạm bẫy: review xong không làm improvement plan; làm "cho có" để đủ điều kiện credit từ AWS Partner; nhầm checklist với tư duy (đề thi hỏi tư duy).
Trong sơ đồ bạn vẽ "EC2 ASG hoặc ECS Fargate". Mình phải chốt một cái cho shop-app chứ? Nếu theo Well-Architected thì cái nào "đúng" hơn, hay cả hai đều được?
Cả hai đều có thể "well-architected"; framework không chấm bạn vì chọn EC2 hay Fargate mà chấm vì bạn có lý do, có đo, có đường lùi. Nhưng để chốt cho shop-app thì mình soi hai lựa chọn qua 6 pillar:
| Pillar | EC2 ASG (shop-asg) | ECS Fargate (shop-app-svc) |
|---|---|---|
| Operational Excellence | Bạn patch OS, AMI, SSM agent, JDK; deploy bằng instance refresh hoặc CodeDeploy | Không có OS để patch; deploy = task definition mới, rolling/blue-green sẵn; cùng image với local Docker (docker 02) |
| Security | Phải lo IMDSv2, SSH/SSM, hardening AMI; Inspector quét EC2 | Task role per service (hạt mịn hơn instance role); không SSH; Inspector quét image ECR |
| Reliability | ASG thay instance hỏng; khởi động jar ~30–60 giây sau boot OS ~1 phút | Service scheduler thay task; task start ~30–60 giây; không có "node" để chết |
| Performance | Chọn instance type tự do (Graviton, burstable), tận dụng JVM warm lâu dài | vCPU/memory theo bậc; CPU không burst; Graviton có (ARM64) |
| Cost | t3.micro Free Tier 12 tháng; Savings Plans/Spot linh hoạt; rẻ hơn Fargate khi chạy đầy tải 24/7 | Không Free Tier; trả theo vCPU-giây + GB-giây; Compute SP áp được; Fargate Spot cho worker; đắt hơn khoảng 20–30% so với EC2 cùng tải nhưng bù bằng giảm công vận hành |
| Sustainability | Dễ chạy thừa (instance to nhưng app nhỏ) | Cỡ task vừa app hơn, utilization cao hơn |
Chốt cho shop-app:
- Lab và 12 tháng Free Tier: EC2 ASG. Rẻ, học được EC2 (đề SAA hỏi EC2 nhiều hơn Fargate).
- Production với team 2–5 dev không có người vận hành riêng: ECS Fargate. Pillar Operational Excellence và Security thắng; chi phí thêm nhỏ hơn lương một người lo patch OS.
- Chuyển khi: image Docker của shop-app đã ổn trong CI (devops 02), đã có ECR, có health check, config qua env/Secrets Manager (12-factor). Đường lùi: giữ launch template shop-lt thêm một release.
- Không chuyển nếu app cần kernel module, GPU đặc thù, hoặc chạy đầy tải 24/7 trên nhiều instance lớn (EC2 + Savings Plans rẻ hơn rõ).
Trong Well-Architected Tool, câu hỏi tương ứng là OPS "How do you evolve operations?" và PERF "How do you select your compute solution?": trả lời bằng bảng trên và ghi ngày review lại, thế là đạt.
Hai thứ tốn tiền nhất trong sơ đồ là 2 NAT Gateway và RDS Multi-AZ. Nếu mình chỉ dùng 1 NAT và RDS Single-AZ để tiết kiệm thì vi phạm pillar Reliability, nhưng dùng cả hai thì vi phạm Cost. Well-Architected xử lý mâu thuẫn này thế nào?
Bạn vừa chạm đúng tinh thần của framework: các pillar luôn đánh đổi với nhau, và framework không bắt bạn tối đa cả 6. Nó bắt bạn (1) biết mình đang đánh đổi gì, (2) quyết định dựa trên yêu cầu nghiệp vụ đo được (RTO/RPO, SLA, doanh thu/giờ), (3) ghi lại và review định kỳ.
1. Quy trình quyết định
Bước 1: Yêu cầu nghiệp vụ shop-app production: downtime 1 giờ mất ~X USD; RTO 15 phút, RPO 5 phút
Bước 2: Tính chi phí rủi ro Single-AZ RDS: AZ down ~vài lần/năm, mỗi lần restore từ snapshot
30–60 phút + mất dữ liệu tới 5 phút (PITR) → vi phạm RTO
→ Multi-AZ đáng tiền.
Bước 3: Tính chi phí phương án Multi-AZ ≈ gấp đôi giá instance DB. 2 NAT ≈ gấp đôi phí giờ NAT.
Bước 4: Tìm cách giảm mà không NAT: traffic AWS đi endpoint → GB qua NAT giảm → 2 NAT nhỏ vẫn rẻ.
hạ tiêu chí RDS: Multi-AZ nhưng size nhỏ hơn + ElastiCache giảm tải;
hoặc Aurora (standby = reader, dùng được, không "nằm không").
Bước 5: Ghi lại ADR (Architecture Decision Record) + ngày review.
2. Riêng từng thứ
| Thành phần | Giảm thế nào mà vẫn Reliable | Khi nào chấp nhận hạ Reliability |
|---|---|---|
| 2 NAT Gateway | Đưa S3/DynamoDB/ECR/Logs/Secrets qua VPC endpoint để NAT chỉ còn traffic Internet thật; production giữ 1 NAT mỗi AZ vì nếu NAT ở 1a chết mà route 1b trỏ sang nó thì app ở 1b mất Internet | Dev/staging/lab: 1 NAT hoặc không NAT (SSM qua endpoint). Ghi rõ "môi trường này không chịu lỗi AZ" |
| RDS Multi-AZ | Giữ Multi-AZ, giảm size nhờ cache; dùng Reserved DB Instance giảm ~40–60%; cân nhắc Aurora để standby làm reader | Lab, dev, hệ thống nội bộ chấp nhận RTO vài giờ; có PITR và automated backup bật |
| ElastiCache replica | Cache mất thì app vẫn chạy (chậm hơn, hit RDS) nếu code có fallback → replica là tùy chọn | Khi cache chỉ là cache, không là session store duy nhất |
| ALB | Không bỏ được trong production (TLS, health check, multi-AZ). Lab: 1 EC2 public + Elastic IP | Lab, demo |
3. Cách đề thi hỏi mâu thuẫn này
Đề luôn cho một tiêu chí thắng: "MOST cost-effective solution that meets the RTO of 1 hour" → chọn phương án rẻ nhất trong số đáp án đạt RTO, không phải rẻ nhất tuyệt đối. "Highly available" trong đề nghĩa là Multi-AZ tối thiểu; nếu đáp án Single-AZ xuất hiện trong câu có chữ này thì loại ngay. Ngược lại câu có "development environment", "can tolerate downtime" thì Multi-AZ là đáp án thừa tiền và bị loại.
Well-Architected Tool trong Console dùng ra sao? Nó có tự quét tài khoản của mình rồi chấm điểm không, hay chỉ là bộ câu hỏi mình tự trả lời?
Chủ yếu là bộ câu hỏi bạn tự trả lời, không phải scanner. Nó không thay Trusted Advisor hay Security Hub; nó buộc bạn suy nghĩ và ghi lại. Có một phần tự động: Tool có thể kéo dữ liệu từ Trusted Advisor (nếu kích hoạt "Trusted Advisor integration") để gợi ý câu trả lời cho một số câu hỏi.
1. Luồng dùng
Well-Architected Tool → Define workload
├─ Name: shop-app-prod, Environment: Production, Region: ap-southeast-1
├─ Lens: AWS Well-Architected Framework (mặc định) + Serverless Lens (nếu có Lambda nhiều)
├─ Trả lời ~50–60 câu, mỗi câu tick các best practice đã làm
│ vd SEC 2 "How do you manage identities for people and machines?"
│ ☑ Rely on a centralized identity provider ☑ Use temporary credentials ☐ Audit and rotate…
├─ Mark "None of these" nếu chưa làm gì → thành HRI
├─ Review → Improvement plan: danh sách HRI/MRI với link hướng dẫn
├─ Milestone: lưu snapshot "v1 trước go-live"; sau khi sửa tạo "v2" để so
└─ Report PDF cho sếp; share workload sang account khác / AWS Partner
# Tool có API; ví dụ tạo workload và liệt kê câu hỏi pillar Reliability
aws wellarchitected create-workload --workload-name shop-app-prod \
--description "shop-app production on ap-southeast-1" \
--environment PRODUCTION --aws-regions ap-southeast-1 \
--review-owner you@example.com --lenses wellarchitected \
--tags Project=shop-app,Env=lab --region $AWS_REGION --profile shop-lab
WL_ID=$(aws wellarchitected list-workloads --query "WorkloadSummaries[?WorkloadName=='shop-app-prod'].WorkloadId" \
--output text --region $AWS_REGION --profile shop-lab)
aws wellarchitected list-answers --workload-id $WL_ID --lens-alias wellarchitected \
--pillar-id reliability --query 'AnswerSummaries[].[QuestionId,QuestionTitle,Risk]' \
--output table --region $AWS_REGION --profile shop-lab
2. Khác bảng tự chấm của bạn chỗ nào
- Câu hỏi được chuẩn hóa: team khác, AWS Partner, auditor đều hiểu "SEC 2" là gì.
- Có milestone để chứng minh tiến bộ; có HRI count để đặt mục tiêu ("về 0 HRI trước go-live").
- Có custom lens: công ty tự viết bộ câu hỏi riêng (ví dụ chuẩn nội bộ về Java/Spring) và review mọi workload theo đó.
- Miễn phí. Không tự fix gì cho bạn.
3. Câu hỏi hay thành HRI với shop-app ở lab
| Câu hỏi (rút gọn) | Hiện trạng lab | Việc cần làm (bài) |
|---|---|---|
| OPS: Operations as code? | Dựng bằng CLI tay | Chuyển sang Terraform/CloudFormation (devops 04) |
| SEC: Detect and investigate events? | CloudTrail có, GuardDuty tắt sau lab | Bật GuardDuty, Security Hub mọi region (22.20) |
| REL: Test reliability (game day)? | Chưa bao giờ tắt AZ thử | Thử thêm ở lab dưới: terminate instance, reboot RDS with failover |
| REL: Plan for DR? | Backup có, chưa test restore | Restore thử theo 22.18, đo RTO thật |
| PERF: Monitor resources? | Dashboard có, chưa có SLO | Đặt SLO và alarm theo observability 02 |
| COST: Govern usage? | Budget có | Thêm Budget Action, tắt lab theo lịch (EventBridge Scheduler stop EC2/RDS ngoài giờ) |
Cho mình bài lab để tự tay làm phần này nhé: dựng cả stack shop-app theo đúng thứ tự từ các lab trước, kiểm tra end-to-end một đơn hàng, chạy Well-Architected review, rồi có một script dọn toàn bộ theo tag để mình không bao giờ bị tính tiền oan nữa.
Lab 22.21: Dựng, kiểm tra và dọn toàn bộ stack shop-app
Mục tiêu
- Dựng đủ các lớp theo checklist thứ tự (network → data → compute → edge → async → observability);
curl https://<alb>/actuator/healthtrả{"status":"UP"}với các component db, redis. - Một
POST /api/orderstạo bản ghi trong RDS, một message đếnshop-order-eventsvà một log line trong/shop/app; đồng thời thấy trace đi qua ALB → app → RDS trong X-Ray (nếu đã bật ở 22.15). - Workload
shop-app-labtrong Well-Architected Tool có ít nhất 2 pillar được trả lời và ghi được số HRI. - Script dọn theo tag
Project=shop-appchạy xong thìresourcegroupstaggingapi get-resourcestrả về rỗng (trừ IAM role/KMS key giữ lại có chủ ý) và Cost Explorer ngày hôm sau về gần 0.
Chi phí & thời gian
- Stack đầy đủ trong production là vài trăm USD/tháng; trong lab chạy 3–4 giờ tốn khoảng 1–3 USD, chủ yếu từ: NAT Gateway (theo giờ + GB), ALB (giờ + LCU), RDS Multi-AZ (Free Tier chỉ Single-AZ), ElastiCache (không Free Tier), Fargate nếu dùng thay EC2, WAF web ACL. Kiểm tra pricing page cho con số hiện tại.
- Cách tiết kiệm trong lab: RDS Single-AZ (bật Multi-AZ chỉ để test failover rồi tắt), 1 NAT, ElastiCache 1 node
cache.t3.micro, bỏ CloudFront/Route 53 nếu không có domain. - Thời gian: 2–3 giờ dựng (RDS và ElastiCache mất 10–15 phút mỗi cái), 30 phút kiểm tra, 30 phút Well-Architected, 15 phút dọn + 24 giờ xác nhận bill.
Chuẩn bị
- Đã làm ít nhất các lab 22.1, 22.3, 22.5, 22.6, 22.7, 22.11, 22.14, 22.15 (có file lệnh/ghi chú ID). Lab này gọi lại các bước đó, không chép lại từng lệnh; chỗ nào có khác biệt mới ghi lệnh.
- Jar
shop-app.jarbuild sẵn và upload lêns3://shop-app-assets-$ACCOUNT_ID/releases/(hoặc image trong ECRshop-app). - Tool:
awsv2,jq,psql(tùy chọn). Một fileenv.shđể lưu ID:
cat > env.sh <<'EOF'
export AWS_REGION=ap-southeast-1
export PROFILE="--profile shop-lab --region ap-southeast-1"
export ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text --profile shop-lab)
export TAGS="Key=Project,Value=shop-app Key=Env,Value=lab"
EOF
source env.sh
Các bước
Bước 1. Lớp nền (22.1, 22.14): IAM role, KMS, secret. Kiểm tra còn tồn tại; nếu đã xóa, chạy lại lab tương ứng.
aws iam get-role --role-name shop-app-ec2-role --query Role.Arn --output text --profile shop-lab
aws iam get-role --role-name shop-lambda-role --query Role.Arn --output text --profile shop-lab
aws kms describe-key --key-id alias/shop-key --query KeyMetadata.KeyState --output text $PROFILE
aws secretsmanager describe-secret --secret-id shop/db --query Name --output text $PROFILE
aws ssm get-parameter --name /shop/app/feature-flags --query Parameter.Value --output text $PROFILE
Console: IAM → Roles; KMS → Customer managed keys; Secrets Manager → Secrets; Systems Manager → Parameter Store.
Bước 2. Network (22.3). Thứ tự bắt buộc: VPC → subnet ×4 → IGW attach → NAT (cần EIP, trong public subnet) → route table public (0.0.0.0/0 → IGW) và private (0.0.0.0/0 → NAT) → associate → SG theo chuỗi → endpoint S3/DynamoDB (Gateway) → Flow Logs.
export VPC_ID=$(aws ec2 describe-vpcs --filters Name=tag:Name,Values=shop-vpc --query 'Vpcs[0].VpcId' --output text $PROFILE)
for N in shop-public-a shop-public-b shop-private-a shop-private-b; do
echo "$N $(aws ec2 describe-subnets --filters Name=tag:Name,Values=$N --query 'Subnets[0].SubnetId' --output text $PROFILE)"
done
for SG in shop-alb-sg shop-app-sg shop-db-sg shop-cache-sg; do
echo "$SG $(aws ec2 describe-security-groups --filters Name=group-name,Values=$SG --query 'SecurityGroups[0].GroupId' --output text $PROFILE)"
done
# Endpoint S3 bắt buộc có để NAT không gánh traffic S3 (22.19)
aws ec2 describe-vpc-endpoints --filters Name=vpc-id,Values=$VPC_ID \
--query 'VpcEndpoints[].[ServiceName,VpcEndpointType,State]' --output table $PROFILE
Nếu thiếu thứ gì, làm lại đúng phần đó của lab 22.3. Ghi các ID vào env.sh (PUB_A, PUB_B, PRIV_A, PRIV_B, SG_ALB, SG_APP, SG_DB, SG_CACHE).
Bước 3. Data tier (22.7, 22.9, 22.8). Chạy song song vì mỗi cái mất 10–15 phút.
# RDS: subnet group private → instance. Lab: Single-AZ; bước "Thử thêm" sẽ bật Multi-AZ để test failover.
aws rds create-db-subnet-group --db-subnet-group-name shop-db-subnets \
--db-subnet-group-description "shop private" --subnet-ids $PRIV_A $PRIV_B --tags $TAGS $PROFILE
DB_PASS=$(aws secretsmanager get-secret-value --secret-id shop/db --query SecretString --output text $PROFILE | jq -r .password)
aws rds create-db-instance --db-instance-identifier shop-db --engine postgres --engine-version 16 \
--db-instance-class db.t3.micro --allocated-storage 20 --storage-type gp3 \
--db-name shop --master-username shop --master-user-password "$DB_PASS" \
--vpc-security-group-ids $SG_DB --db-subnet-group-name shop-db-subnets --no-publicly-accessible \
--storage-encrypted --kms-key-id alias/shop-key --backup-retention-period 1 \
--tags $TAGS $PROFILE
# ElastiCache Redis 1 node (không Free Tier)
aws elasticache create-cache-subnet-group --cache-subnet-group-name shop-cache-subnets \
--cache-subnet-group-description "shop private" --subnet-ids $PRIV_A $PRIV_B --tags $TAGS $PROFILE
aws elasticache create-replication-group --replication-group-id shop-cache \
--replication-group-description "shop redis" --engine redis --cache-node-type cache.t3.micro \
--num-cache-clusters 1 --cache-subnet-group-name shop-cache-subnets --security-group-ids $SG_CACHE \
--at-rest-encryption-enabled --transit-encryption-enabled --tags $TAGS $PROFILE
# DynamoDB (on-demand, Free Tier 25 GB)
aws dynamodb create-table --table-name shop-sessions \
--attribute-definitions AttributeName=sessionId,AttributeType=S \
--key-schema AttributeName=sessionId,KeyType=HASH --billing-mode PAY_PER_REQUEST --tags $TAGS $PROFILE
aws dynamodb update-time-to-live --table-name shop-sessions \
--time-to-live-specification Enabled=true,AttributeName=expiresAt $PROFILE
Console: RDS → Databases → Create; ElastiCache → Redis OSS caches → Create; DynamoDB → Tables → Create.
Bước 4. Storage & async (22.6, 22.11, 22.12).
for B in shop-app-assets-$ACCOUNT_ID shop-app-logs-$ACCOUNT_ID; do
aws s3api head-bucket --bucket $B $PROFILE 2>/dev/null || \
aws s3api create-bucket --bucket $B --create-bucket-configuration LocationConstraint=$AWS_REGION $PROFILE
aws s3api put-public-access-block --bucket $B --public-access-block-configuration \
BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true $PROFILE
aws s3api put-bucket-tagging --bucket $B --tagging "TagSet=[{Key=Project,Value=shop-app},{Key=Env,Value=lab}]" $PROFILE
done
DLQ_URL=$(aws sqs create-queue --queue-name shop-order-events-dlq --tags Project=shop-app,Env=lab --query QueueUrl --output text $PROFILE)
DLQ_ARN=$(aws sqs get-queue-attributes --queue-url $DLQ_URL --attribute-names QueueArn --query Attributes.QueueArn --output text $PROFILE)
Q_URL=$(aws sqs create-queue --queue-name shop-order-events --tags Project=shop-app,Env=lab \
--attributes "{\"VisibilityTimeout\":\"60\",\"RedrivePolicy\":\"{\\\"deadLetterTargetArn\\\":\\\"$DLQ_ARN\\\",\\\"maxReceiveCount\\\":\\\"3\\\"}\"}" \
--query QueueUrl --output text $PROFILE)
Q_ARN=$(aws sqs get-queue-attributes --queue-url $Q_URL --attribute-names QueueArn --query Attributes.QueueArn --output text $PROFILE)
TOPIC_ARN=$(aws sns create-topic --name shop-order-topic --tags Key=Project,Value=shop-app Key=Env,Value=lab --query TopicArn --output text $PROFILE)
aws sns subscribe --topic-arn $TOPIC_ARN --protocol sqs --notification-endpoint $Q_ARN --attributes RawMessageDelivery=true $PROFILE
# Queue policy cho SNS publish: xem lab 22.11 (sqs set-queue-attributes Policy ...)
# Lambda shop-order-notifier với event source mapping từ Q_ARN: theo lab 22.12
Bước 5. Compute (22.4, 22.5): launch template → target group → ALB → listener → ASG.
# user data lấy jar từ S3 (qua Gateway endpoint), secret từ Secrets Manager qua role EC2
cat > user-data.sh <<'EOF'
#!/bin/bash
dnf install -y java-21-amazon-corretto-headless
aws s3 cp s3://shop-app-assets-ACCOUNT_ID/releases/shop-app.jar /opt/shop-app.jar
cat > /etc/systemd/system/shop-app.service <<SVC
[Unit]
Description=shop-app
After=network.target
[Service]
Environment=AWS_REGION=ap-southeast-1
Environment=SPRING_PROFILES_ACTIVE=aws
Environment=SHOP_DB_SECRET=shop/db
Environment=SHOP_REDIS_HOST=REDIS_HOST
Environment=SHOP_ORDER_TOPIC_ARN=TOPIC_ARN
ExecStart=/usr/bin/java -XX:MaxRAMPercentage=70 -jar /opt/shop-app.jar
Restart=always
[Install]
WantedBy=multi-user.target
SVC
systemctl enable --now shop-app
EOF
REDIS_HOST=$(aws elasticache describe-replication-groups --replication-group-id shop-cache \
--query 'ReplicationGroups[0].NodeGroups[0].PrimaryEndpoint.Address' --output text $PROFILE)
sed -i "s/ACCOUNT_ID/$ACCOUNT_ID/; s/REDIS_HOST/$REDIS_HOST/; s|TOPIC_ARN|$TOPIC_ARN|" user-data.sh
AMI=$(aws ssm get-parameter --name /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64 --query Parameter.Value --output text $PROFILE)
aws ec2 create-launch-template --launch-template-name shop-lt --launch-template-data "{
\"ImageId\":\"$AMI\",\"InstanceType\":\"t3.micro\",
\"IamInstanceProfile\":{\"Name\":\"shop-app-ec2-role\"},
\"SecurityGroupIds\":[\"$SG_APP\"],
\"MetadataOptions\":{\"HttpTokens\":\"required\",\"HttpPutResponseHopLimit\":1},
\"UserData\":\"$(base64 -w0 user-data.sh)\",
\"TagSpecifications\":[{\"ResourceType\":\"instance\",\"Tags\":[{\"Key\":\"Project\",\"Value\":\"shop-app\"},{\"Key\":\"Env\",\"Value\":\"lab\"},{\"Key\":\"Name\",\"Value\":\"shop-app\"}]}]
}" --tag-specifications "ResourceType=launch-template,Tags=[{Key=Project,Value=shop-app},{Key=Env,Value=lab}]" $PROFILE
TG_ARN=$(aws elbv2 create-target-group --name shop-tg --protocol HTTP --port 8080 --vpc-id $VPC_ID \
--health-check-path /actuator/health --health-check-interval-seconds 15 --healthy-threshold-count 2 \
--target-type instance --tags $TAGS --query 'TargetGroups[0].TargetGroupArn' --output text $PROFILE)
aws elbv2 modify-target-group-attributes --target-group-arn $TG_ARN \
--attributes Key=deregistration_delay.timeout_seconds,Value=30 $PROFILE
ALB_ARN=$(aws elbv2 create-load-balancer --name shop-alb --subnets $PUB_A $PUB_B --security-groups $SG_ALB \
--tags $TAGS --query 'LoadBalancers[0].LoadBalancerArn' --output text $PROFILE)
aws elbv2 create-listener --load-balancer-arn $ALB_ARN --protocol HTTP --port 80 \
--default-actions Type=forward,TargetGroupArn=$TG_ARN --tags $TAGS $PROFILE
# HTTPS 443 với ACM cert: theo lab 22.10 nếu có domain
aws autoscaling create-auto-scaling-group --auto-scaling-group-name shop-asg \
--launch-template LaunchTemplateName=shop-lt,Version='$Latest' \
--min-size 2 --max-size 4 --desired-capacity 2 --vpc-zone-identifier "$PRIV_A,$PRIV_B" \
--target-group-arns $TG_ARN --health-check-type ELB --health-check-grace-period 180 \
--tags Key=Project,Value=shop-app,PropagateAtLaunch=true Key=Env,Value=lab,PropagateAtLaunch=true $PROFILE
aws autoscaling put-scaling-policy --auto-scaling-group-name shop-asg --policy-name cpu50 \
--policy-type TargetTrackingScaling --target-tracking-configuration \
'{"PredefinedMetricSpecification":{"PredefinedMetricType":"ASGAverageCPUUtilization"},"TargetValue":50.0}' $PROFILE
Console: EC2 → Launch Templates; EC2 → Target Groups; EC2 → Load Balancers; EC2 → Auto Scaling Groups. Nếu dùng ECS Fargate thay EC2: tạo shop-cluster, task definition shop-app-task, service shop-app-svc gắn shop-tg (target type ip) theo lab 22.13.
Bước 6. Edge & bảo vệ (22.10, 22.20). Nếu có domain: ACM cert, listener 443, Route 53 alias, CloudFront với origin ALB + S3 OAC. Không có domain: chỉ gắn WAF.
WEB_ACL_ARN=$(aws wafv2 list-web-acls --scope REGIONAL --query "WebACLs[?Name=='shop-waf'].ARN" --output text $PROFILE)
[ -n "$WEB_ACL_ARN" ] && aws wafv2 associate-web-acl --web-acl-arn $WEB_ACL_ARN --resource-arn $ALB_ARN $PROFILE
Bước 7. Observability (22.15).
aws logs create-log-group --log-group-name /shop/app --tags Project=shop-app,Env=lab $PROFILE 2>/dev/null
aws logs put-retention-policy --log-group-name /shop/app --retention-in-days 7 $PROFILE
ALB_SUFFIX=$(echo $ALB_ARN | sed 's|.*loadbalancer/||')
aws cloudwatch put-metric-alarm --alarm-name shop-alb-5xx-high --namespace AWS/ApplicationELB \
--metric-name HTTPCode_Target_5XX_Count --dimensions Name=LoadBalancer,Value=$ALB_SUFFIX \
--statistic Sum --period 60 --evaluation-periods 3 --threshold 10 --comparison-operator GreaterThanThreshold \
--treat-missing-data notBreaching --alarm-actions $TOPIC_ARN --tags $TAGS $PROFILE
# ALB access log → S3 shop-app-logs (bucket policy cho ELB account region): theo lab 22.15
# Dashboard shop-overview: put-dashboard với widget ALB RequestCount, 5XX, TargetResponseTime, RDS CPU, SQS ApproximateAgeOfOldestMessage
Bước 8. Well-Architected Tool.
aws wellarchitected create-workload --workload-name shop-app-lab \
--description "shop-app full stack lab" --environment PREPRODUCTION \
--aws-regions $AWS_REGION --review-owner you@example.com --lenses wellarchitected \
--tags Project=shop-app,Env=lab $PROFILE
WL_ID=$(aws wellarchitected list-workloads --query "WorkloadSummaries[?WorkloadName=='shop-app-lab'].WorkloadId" --output text $PROFILE)
# Xem câu hỏi Security và chọn best practice đã làm (ví dụ SEC 2)
aws wellarchitected list-answers --workload-id $WL_ID --lens-alias wellarchitected --pillar-id security \
--query 'AnswerSummaries[].[QuestionId,QuestionTitle]' --output table $PROFILE
aws wellarchitected get-answer --workload-id $WL_ID --lens-alias wellarchitected --question-id sec-identity-auth \
--query 'Answer.Choices[].[ChoiceId,Title]' --output table $PROFILE 2>/dev/null || echo "lấy question-id từ lệnh trên"
# update-answer với --selected-choices <choiceId> ... rồi:
aws wellarchitected get-lens-review --workload-id $WL_ID --lens-alias wellarchitected \
--query 'LensReview.RiskCounts' $PROFILE
Console: AWS Well-Architected Tool → Define workload → shop-app-lab → Continue reviewing → trả lời ít nhất pillar Security và Reliability → Save and exit → Improvement plan → Save milestone "sau lab 22.21".
Kiểm tra kết quả
source env.sh
ALB_DNS=$(aws elbv2 describe-load-balancers --names shop-alb --query 'LoadBalancers[0].DNSName' --output text $PROFILE)
# 1. Target healthy (chờ 3–5 phút sau khi ASG tạo instance)
aws elbv2 describe-target-health --target-group-arn $TG_ARN \
--query 'TargetHealthDescriptions[].[Target.Id,TargetHealth.State]' --output table $PROFILE
# 2 dòng "healthy"
# 2. Health end-to-end
curl -s http://$ALB_DNS/actuator/health | jq .
# {"status":"UP","components":{"db":{"status":"UP"},"redis":{"status":"UP"},...}}
# 3. Tạo đơn hàng → RDS + SNS → SQS
curl -s -X POST http://$ALB_DNS/api/orders -H 'Content-Type: application/json' \
-d '{"userId":"u1","items":[{"sku":"SKU-1","qty":2}]}' | jq .
aws sqs get-queue-attributes --queue-url $Q_URL --attribute-names ApproximateNumberOfMessages \
--query Attributes $PROFILE
# "ApproximateNumberOfMessages": "1" (hoặc 0 nếu Lambda shop-order-notifier đã tiêu thụ)
# 4. Log trong /shop/app
aws logs tail /shop/app --since 5m --filter-pattern "OrderCreated" $PROFILE
# 5. WAF đang gắn
aws wafv2 get-web-acl-for-resource --resource-arn $ALB_ARN --query WebACL.Name $PROFILE
# 6. Mọi tài nguyên có tag (dùng cho dọn)
aws resourcegroupstaggingapi get-resources --tag-filters Key=Project,Values=shop-app \
--query 'length(ResourceTagMappingList)' $PROFILE
# Số lớn (30–50)
# 7. Well-Architected risk
aws wellarchitected get-lens-review --workload-id $WL_ID --lens-alias wellarchitected --query 'LensReview.RiskCounts' $PROFILE
# {"HIGH": n, "MEDIUM": m, "NONE": ..., "UNANSWERED": ...}
Thử thêm (game day thu nhỏ, pillar Reliability)
- Giết một instance:
aws ec2 terminate-instances --instance-ids <id của 1 target>; theo dõidescribe-target-health: 1 target biến mất, ALB vẫn trả 200, ASG tạo instance mới trong ~3 phút. Ghi RTO đo được. - RDS failover: bật Multi-AZ
aws rds modify-db-instance --db-instance-identifier shop-db --multi-az --apply-immediately(chờ ~10 phút), rồiaws rds reboot-db-instance --db-instance-identifier shop-db --force-failover. Trong lúc đó chạy vòngcurl /actuator/healthmỗi giây: đếm bao nhiêu giây DB DOWN (thường 30–120 giây). Tắt Multi-AZ lại sau khi thử để tiết kiệm. - Cắt cache: đổi inbound rule của
shop-cache-sgtạm thời bỏ 6379: app còn trả 200 không (có fallback) hay 500 (cache là SPOF)? Đây là câu hỏi REL trong Well-Architected. - Tắt NAT: xóa route 0.0.0.0/0 của private route table; app vẫn lấy jar/secret/log được qua endpoint? Nếu không, bạn biết chính xác traffic nào còn phụ thuộc NAT.
- Sau mỗi thử nghiệm quay lại Well-Architected Tool cập nhật câu REL "How do you test reliability?" và lưu milestone mới.
Dọn tài nguyên: script theo tag, đúng thứ tự
Nguyên tắc: xóa từ ngoài vào trong, từ phụ thuộc tới bị phụ thuộc: WAF/CloudFront/Route 53 → ASG → ALB/TG → ECS → Lambda → RDS/ElastiCache/DynamoDB → SQS/SNS/EventBridge → S3 (phải rỗng) → NAT/EIP → endpoint → SG → subnet → IGW → VPC → log group/alarm/dashboard → Well-Architected workload. Tagging API dùng để liệt kê và xác nhận; lệnh xóa từng dịch vụ vẫn phải gọi đúng API vì Tagging API không xóa được.
cat > teardown-shop-app.sh <<'EOF'
#!/bin/bash
# Dọn toàn bộ tài nguyên tag Project=shop-app trong ap-southeast-1. Chạy: bash teardown-shop-app.sh
set -u
PROFILE="--profile shop-lab --region ap-southeast-1"
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text --profile shop-lab)
TAGF="--tag-filters Key=Project,Values=shop-app"
arns() { aws resourcegroupstaggingapi get-resources $TAGF --resource-type-filters "$1" \
--query 'ResourceTagMappingList[].ResourceARN' --output text $PROFILE | tr '\t' '\n' | grep . ; }
echo "== 0. Tài nguyên hiện có:"; aws resourcegroupstaggingapi get-resources $TAGF --query 'length(ResourceTagMappingList)' $PROFILE
echo "== 1. WAF"
for ARN in $(arns wafv2:regional/webacl); do
NAME=$(echo $ARN | awk -F/ '{print $(NF-1)}'); ID=$(echo $ARN | awk -F/ '{print $NF}')
for LB in $(aws wafv2 list-resources-for-web-acl --web-acl-arn $ARN --resource-type APPLICATION_LOAD_BALANCER --query ResourceArns --output text $PROFILE); do
aws wafv2 disassociate-web-acl --resource-arn $LB $PROFILE; done
aws wafv2 delete-logging-configuration --resource-arn $ARN $PROFILE 2>/dev/null
LOCK=$(aws wafv2 get-web-acl --name $NAME --scope REGIONAL --id $ID --query LockToken --output text $PROFILE)
aws wafv2 delete-web-acl --name $NAME --scope REGIONAL --id $ID --lock-token $LOCK $PROFILE
done
echo "== 2. Auto Scaling (xóa trước ALB để instance được terminate)"
for ASG in $(aws autoscaling describe-auto-scaling-groups --filters Name=tag:Project,Values=shop-app --query 'AutoScalingGroups[].AutoScalingGroupName' --output text $PROFILE); do
aws autoscaling delete-auto-scaling-group --auto-scaling-group-name $ASG --force-delete $PROFILE
done
for LT in $(aws ec2 describe-launch-templates --filters Name=tag:Project,Values=shop-app --query 'LaunchTemplates[].LaunchTemplateId' --output text $PROFILE); do
aws ec2 delete-launch-template --launch-template-id $LT $PROFILE; done
# EC2 lẻ (không trong ASG)
IDS=$(aws ec2 describe-instances --filters Name=tag:Project,Values=shop-app Name=instance-state-name,Values=running,stopped --query 'Reservations[].Instances[].InstanceId' --output text $PROFILE)
[ -n "$IDS" ] && aws ec2 terminate-instances --instance-ids $IDS $PROFILE
echo "== 3. ECS"
for CL in $(aws ecs list-clusters --query clusterArns --output text $PROFILE); do
for SVC in $(aws ecs list-services --cluster $CL --query serviceArns --output text $PROFILE); do
aws ecs update-service --cluster $CL --service $SVC --desired-count 0 $PROFILE >/dev/null
aws ecs delete-service --cluster $CL --service $SVC --force $PROFILE >/dev/null; done
[[ $CL == *shop-cluster ]] && aws ecs delete-cluster --cluster $CL $PROFILE >/dev/null
done
echo "== 4. ALB / listener / target group"
for ALB in $(arns elasticloadbalancing:loadbalancer); do aws elbv2 delete-load-balancer --load-balancer-arn $ALB $PROFILE; done
sleep 30
for TG in $(arns elasticloadbalancing:targetgroup); do aws elbv2 delete-target-group --target-group-arn $TG $PROFILE; done
echo "== 5. Lambda + event source mapping"
for FN in $(arns lambda:function); do
NAME=${FN##*:}
for M in $(aws lambda list-event-source-mappings --function-name $NAME --query 'EventSourceMappings[].UUID' --output text $PROFILE); do
aws lambda delete-event-source-mapping --uuid $M $PROFILE >/dev/null; done
aws lambda delete-function --function-name $NAME $PROFILE
done
echo "== 6. Data tier (RDS/ElastiCache mất 5–10 phút; chạy không chờ)"
for DB in $(aws rds describe-db-instances --query "DBInstances[?starts_with(DBInstanceIdentifier,'shop-')].DBInstanceIdentifier" --output text $PROFILE); do
aws rds modify-db-instance --db-instance-identifier $DB --no-deletion-protection --apply-immediately $PROFILE >/dev/null 2>&1
aws rds delete-db-instance --db-instance-identifier $DB --skip-final-snapshot --delete-automated-backups $PROFILE >/dev/null
done
aws elasticache delete-replication-group --replication-group-id shop-cache $PROFILE >/dev/null 2>&1
for T in $(arns dynamodb:table); do aws dynamodb delete-table --table-name ${T##*/} $PROFILE >/dev/null; done
echo "== 7. Messaging"
for Q in $(arns sqs); do
URL=$(aws sqs get-queue-url --queue-name ${Q##*:} --query QueueUrl --output text $PROFILE); aws sqs delete-queue --queue-url $URL $PROFILE; done
for T in $(arns sns); do aws sns delete-topic --topic-arn $T $PROFILE; done
for R in $(aws events list-rules --query "Rules[?starts_with(Name,'shop-')].Name" --output text $PROFILE); do
IDS=$(aws events list-targets-by-rule --rule $R --query 'Targets[].Id' --output text $PROFILE)
[ -n "$IDS" ] && aws events remove-targets --rule $R --ids $IDS $PROFILE >/dev/null
aws events delete-rule --name $R $PROFILE; done
aws events delete-event-bus --name shop-bus $PROFILE 2>/dev/null
echo "== 8. S3 (xóa object + version rồi bucket)"
for B in shop-app-assets-$ACCOUNT_ID shop-app-logs-$ACCOUNT_ID; do
aws s3api head-bucket --bucket $B $PROFILE 2>/dev/null || continue
aws s3 rm s3://$B --recursive $PROFILE >/dev/null
aws s3api list-object-versions --bucket $B --query '{Objects: [Versions[].{Key:Key,VersionId:VersionId}, DeleteMarkers[].{Key:Key,VersionId:VersionId}][]}' --output json $PROFILE > /tmp/v.json
[ "$(jq '.Objects|length' /tmp/v.json)" != "0" ] && jq '{Objects: .Objects[:1000], Quiet: true}' /tmp/v.json > /tmp/d.json && aws s3api delete-objects --bucket $B --delete file:///tmp/d.json $PROFILE >/dev/null
aws s3api delete-bucket --bucket $B $PROFILE
done
echo "== 9. Chờ RDS/ElastiCache xóa xong trước khi gỡ network"
aws rds wait db-instance-deleted --db-instance-identifier shop-db $PROFILE 2>/dev/null
until ! aws elasticache describe-replication-groups --replication-group-id shop-cache $PROFILE >/dev/null 2>&1; do sleep 20; done
aws rds delete-db-subnet-group --db-subnet-group-name shop-db-subnets $PROFILE 2>/dev/null
aws elasticache delete-cache-subnet-group --cache-subnet-group-name shop-cache-subnets $PROFILE 2>/dev/null
echo "== 10. Network: NAT → EIP → endpoint → ENI chờ → SG → subnet → route table → IGW → VPC"
VPC_ID=$(aws ec2 describe-vpcs --filters Name=tag:Project,Values=shop-app --query 'Vpcs[0].VpcId' --output text $PROFILE)
if [ "$VPC_ID" != "None" ] && [ -n "$VPC_ID" ]; then
for NAT in $(aws ec2 describe-nat-gateways --filter Name=vpc-id,Values=$VPC_ID Name=state,Values=available --query 'NatGateways[].NatGatewayId' --output text $PROFILE); do
aws ec2 delete-nat-gateway --nat-gateway-id $NAT $PROFILE >/dev/null; done
for EP in $(aws ec2 describe-vpc-endpoints --filters Name=vpc-id,Values=$VPC_ID --query 'VpcEndpoints[].VpcEndpointId' --output text $PROFILE); do
aws ec2 delete-vpc-endpoints --vpc-endpoint-ids $EP $PROFILE >/dev/null; done
for FL in $(aws ec2 describe-flow-logs --filter Name=resource-id,Values=$VPC_ID --query 'FlowLogs[].FlowLogId' --output text $PROFILE); do
aws ec2 delete-flow-logs --flow-log-ids $FL $PROFILE >/dev/null; done
echo "chờ NAT và ENI giải phóng (~2 phút)"; sleep 120
for EIP in $(aws ec2 describe-addresses --filters Name=tag:Project,Values=shop-app --query 'Addresses[?AssociationId==null].AllocationId' --output text $PROFILE); do
aws ec2 release-address --allocation-id $EIP $PROFILE; done
for SG in shop-app-sg shop-db-sg shop-cache-sg shop-alb-sg; do # thứ tự: SG bị tham chiếu xóa sau
ID=$(aws ec2 describe-security-groups --filters Name=vpc-id,Values=$VPC_ID Name=group-name,Values=$SG --query 'SecurityGroups[0].GroupId' --output text $PROFILE)
[ "$ID" != "None" ] && aws ec2 delete-security-group --group-id $ID $PROFILE; done
for SN in $(aws ec2 describe-subnets --filters Name=vpc-id,Values=$VPC_ID --query 'Subnets[].SubnetId' --output text $PROFILE); do
aws ec2 delete-subnet --subnet-id $SN $PROFILE; done
for RT in $(aws ec2 describe-route-tables --filters Name=vpc-id,Values=$VPC_ID --query 'RouteTables[?Associations[0].Main!=`true`].RouteTableId' --output text $PROFILE); do
aws ec2 delete-route-table --route-table-id $RT $PROFILE; done
for IGW in $(aws ec2 describe-internet-gateways --filters Name=attachment.vpc-id,Values=$VPC_ID --query 'InternetGateways[].InternetGatewayId' --output text $PROFILE); do
aws ec2 detach-internet-gateway --internet-gateway-id $IGW --vpc-id $VPC_ID $PROFILE
aws ec2 delete-internet-gateway --internet-gateway-id $IGW $PROFILE; done
aws ec2 delete-vpc --vpc-id $VPC_ID $PROFILE
fi
echo "== 11. CloudWatch, Well-Architected"
for LG in /shop/app aws-waf-logs-shop; do aws logs delete-log-group --log-group-name $LG $PROFILE 2>/dev/null; done
aws cloudwatch delete-alarms --alarm-names shop-alb-5xx-high $PROFILE 2>/dev/null
aws cloudwatch delete-dashboards --dashboard-names shop-overview $PROFILE 2>/dev/null
for WL in $(aws wellarchitected list-workloads --query "WorkloadSummaries[?starts_with(WorkloadName,'shop-app')].WorkloadId" --output text $PROFILE); do
aws wellarchitected delete-workload --workload-id $WL --client-request-token $(date +%s%N) $PROFILE; done
echo "== 12. Còn lại (mong đợi chỉ IAM role, KMS key, Secrets Manager, ECR nếu giữ có chủ ý):"
aws resourcegroupstaggingapi get-resources $TAGF --query 'ResourceTagMappingList[].ResourceARN' --output text $PROFILE | tr '\t' '\n'
EOF
bash teardown-shop-app.sh
Giữ lại có chủ ý (gần như không tốn tiền): IAM role, KMS key (khoảng 1 USD/tháng mỗi key, schedule deletion 7 ngày nếu muốn bỏ: aws kms schedule-key-deletion --key-id <id> --pending-window-in-days 7), Secrets Manager shop/db (tính theo secret/tháng, xóa bằng delete-secret --force-delete-without-recovery nếu không dùng nữa), ECR repo (theo GB), cost allocation tag. Hôm sau kiểm tra Cost Explorer theo tag Project=shop-app về gần 0 và Budget không còn alert.
Không nằm trong Free Tier nếu quên: NAT Gateway, ALB, RDS Multi-AZ, ElastiCache, Fargate, WAF, Elastic IP không gắn, EBS/snapshot, GuardDuty/Security Hub sau trial, KMS key, Secrets Manager.
Phỏng vấn và đề thi SAA hỏi về Well-Architected và kiến trúc tổng thể thì sao? Mình sợ bị hỏi "vẽ kiến trúc cho hệ thống X" rồi không biết bắt đầu từ đâu.
Q1. Thiết kế kiến trúc AWS cho một ứng dụng e-commerce Spring Boot cần high availability. Bạn bắt đầu từ đâu?
🇻🇳 Tiếng Việt:
Em bắt đầu từ yêu cầu: RTO/RPO, lượng traffic, dữ liệu nhạy cảm, ngân sách. Rồi vẽ theo lớp: edge dùng Route 53, CloudFront và WAF; VPC hai AZ với public subnet chứa ALB và NAT, private subnet chứa app trên ECS Fargate hoặc EC2 ASG; data tier gồm RDS PostgreSQL Multi-AZ, ElastiCache Redis, DynamoDB cho session và cart, S3 cho ảnh; xử lý bất đồng bộ qua SNS, SQS với DLQ và Lambda; nền tảng gồm Secrets Manager, KMS, CloudWatch, CloudTrail, GuardDuty. Cuối cùng em soi qua sáu pillar Well-Architected và ghi lại trade-off, ví dụ chọn Multi-AZ thay multi-region vì RTO cho phép.
🇬🇧 English:
I start with requirements: RTO/RPO, traffic, sensitive data, budget. Then I design in layers: an edge with Route 53, CloudFront and WAF; a two-AZ VPC with public subnets for the ALB and NAT and private subnets for the app on ECS Fargate or an EC2 Auto Scaling group; a data tier with Multi-AZ RDS PostgreSQL, ElastiCache Redis, DynamoDB for sessions and carts, and S3 for images; asynchronous processing through SNS, SQS with a DLQ and Lambda; and a foundation of Secrets Manager, KMS, CloudWatch, CloudTrail and GuardDuty. Finally I walk through the six Well-Architected pillars and document the trade-offs, for example choosing Multi-AZ over multi-region because the RTO allows it.
Q2. Sáu pillar của Well-Architected là gì và bạn đã áp dụng pillar nào trong dự án?
🇻🇳 Tiếng Việt:
Operational Excellence, Security, Reliability, Performance Efficiency, Cost Optimization và Sustainability. Trong dự án gần nhất em áp dụng Reliability bằng Multi-AZ, health check, queue với DLQ và test failover; Security bằng IAM role thay access key, secret trong Secrets Manager, WAF và GuardDuty; Cost bằng tag chuẩn, Budget, Savings Plans cho baseline và endpoint thay NAT. Em dùng Well-Architected Tool để ghi milestone và theo dõi số high risk issue giảm sau mỗi sprint.
🇬🇧 English:
Operational Excellence, Security, Reliability, Performance Efficiency, Cost Optimization and Sustainability. In my last project I applied Reliability with Multi-AZ, health checks, queues with DLQs and failover tests; Security with IAM roles instead of access keys, secrets in Secrets Manager, WAF and GuardDuty; and Cost with standard tags, Budgets, Savings Plans for the baseline and VPC endpoints instead of NAT. I used the Well-Architected Tool to record milestones and track the number of high-risk issues going down each sprint.
Q3. Khi hai pillar mâu thuẫn, ví dụ Reliability và Cost, bạn quyết định thế nào?
🇻🇳 Tiếng Việt:
Em quy về yêu cầu nghiệp vụ đo được: downtime một giờ mất bao nhiêu, RTO/RPO là gì. Nếu RTO 15 phút thì RDS Multi-AZ đáng tiền; nếu là môi trường dev chấp nhận vài giờ thì Single-AZ với backup là đủ. Sau đó em tìm cách giảm chi phí mà không hạ tiêu chí, ví dụ đưa traffic S3 qua Gateway Endpoint để NAT nhỏ hơn, dùng Reserved Instance cho DB. Quyết định được ghi lại cùng ngày review.
🇬🇧 English:
I reduce it to measurable business requirements: what an hour of downtime costs, what the RTO and RPO are. With a 15-minute RTO, Multi-AZ RDS is worth it; for a dev environment that tolerates hours, Single-AZ with backups is enough. Then I look for savings that do not lower the bar, such as routing S3 traffic through a Gateway Endpoint so the NAT handles less, or Reserved Instances for the database. The decision is documented with a review date.
Góc thi SAA-C03
Câu 1: Một công ty muốn đánh giá kiến trúc workload hiện tại theo best practice của AWS, theo dõi các rủi ro cao theo thời gian và tạo báo cáo cho lãnh đạo, không mất thêm chi phí. Dịch vụ nào phù hợp?
A. AWS Trusted Advisor
B. AWS Well-Architected Tool
C. AWS Security Hub
D. AWS Config conformance pack
Đáp án: B. Well-Architected Tool miễn phí, review theo 6 pillar, lưu milestone, xuất report và improvement plan. A là checklist tự động nhưng không theo pillar/milestone và cost checks cần Business Support. C chỉ về security posture. D kiểm tra compliance cấu hình, không phải review kiến trúc.
Câu 2: Ứng dụng web ba lớp trên AWS: ALB, EC2 trong một AZ, RDS MySQL Single-AZ. Công ty yêu cầu ứng dụng tiếp tục hoạt động khi một AZ gặp sự cố, với thay đổi tối thiểu. Solutions architect nên làm gì? (Chọn HAI)
A. Chuyển EC2 vào Auto Scaling group trải trên nhiều AZ sau ALB
B. Bật Multi-AZ cho RDS
C. Tạo Read Replica ở region khác
D. Đặt CloudFront trước ALB
E. Chuyển RDS sang DynamoDB
Đáp án: A và B. Đúng pillar Reliability: compute nhiều AZ với ASG, database Multi-AZ failover tự động. C là DR/đọc, không tự failover khi AZ hỏng và không phải "thay đổi tối thiểu". D cải thiện performance, không giúp AZ failure. E thay đổi lớn, trái yêu cầu.
Câu 3: Công ty vận hành nhiều account và cần đảm bảo mọi tài nguyên mới đều có tag CostCenter, đồng thời theo dõi chi phí từng team và nhận cảnh báo khi vượt ngân sách. Kết hợp nào đáp ứng tốt nhất?
A. AWS Config rule required-tags + cost allocation tags + AWS Budgets theo tag
B. Trusted Advisor + Cost Explorer
C. Service Control Policy chặn mọi API + CloudTrail
D. Compute Optimizer + Budgets
Đáp án: A. Config rule required-tags (hoặc Tag Policies trong Organizations) phát hiện/ép tag, cost allocation tag cho phép phân bổ, Budgets theo tag filter gửi alert. B không ép tag và không có alert. C chặn mọi API là phá hoại, CloudTrail không phải công cụ cost. D không liên quan đến tag.
Cách nhớ ngắn gọn
- 6 pillar: O-S-R-P-C-S (Ops, Security, Reliability, Performance, Cost, Sustainability). 4 domain đề thi = Security, Reliability (Resilient), Performance, Cost.
- Kiến trúc tham chiếu đọc từ ngoài vào: Route 53 → CloudFront → WAF → ALB (public) → app (private, 2 AZ) → RDS Multi-AZ + cache + DynamoDB + S3; bên cạnh SNS/SQS/Lambda; dưới nền Secrets/KMS/CloudWatch/CloudTrail/GuardDuty.
- Pillar mâu thuẫn → quy về RTO/RPO và tiền; đề thi luôn cho một tiêu chí thắng.
- Dọn: ngoài vào trong, phụ thuộc trước; Tagging API để liệt kê, API dịch vụ để xóa.
| Từ khóa trong đề | Pillar / Dịch vụ |
|---|---|
| "highly available", "survive AZ failure" | Reliability: Multi-AZ ALB/ASG/RDS |
| "LEAST operational overhead", "managed" | Operational Excellence: dịch vụ managed/serverless |
| "MOST secure", "least privilege", "encrypt" | Security: IAM role, KMS, private subnet |
| "improve performance", "reduce latency" | Performance: cache, CloudFront, read replica, DAX |
| "MOST cost-effective" + điều kiện | Cost: đáp án rẻ nhất đạt điều kiện |
| "review architecture against best practices", "high risk issues" | Well-Architected Tool |
| "document trade-offs", "milestones" | Well-Architected Tool milestone / ADR |
Tự kiểm tra
Kể 6 pillar và nói mỗi pillar tương ứng với domain nào của SAA-C03.
Operational Excellence (xuyên suốt), Security (D3), Reliability (D2 Resilient), Performance Efficiency (D1 High-Performing), Cost Optimization (D4), Sustainability (không có domain riêng).
Well-Architected Tool có tự quét tài khoản không?
Không. Nó là bộ câu hỏi tự trả lời theo lens, ra HRI/MRI, improvement plan, milestone, report. Có tích hợp Trusted Advisor để gợi ý, nhưng không tự fix và không thay Security Hub.
Trong kiến trúc tham chiếu, request POST /api/orders đi qua những thành phần nào theo thứ tự?
Route 53 → CloudFront → WAF → ALB (TLS) → target group → app trong private subnet → Secrets Manager (secret DB), ElastiCache/DynamoDB (session), RDS primary → SNS shop-order-topic → SQS shop-order-events → Lambda shop-order-notifier; log vào CloudWatch, trace X-Ray.
Thứ tự dọn stack đúng là gì và vì sao Tagging API không đủ?
Ngoài vào trong: WAF → ASG → ALB/TG → ECS/Lambda → RDS/ElastiCache/DynamoDB → SQS/SNS/EventBridge → S3 rỗng → NAT/EIP/endpoint → SG → subnet → route table → IGW → VPC → CloudWatch. Tagging API chỉ liệt kê và gắn/gỡ tag, không có API xóa; phải gọi API từng dịch vụ.
Khi Reliability và Cost mâu thuẫn, Well-Architected bảo bạn làm gì?
Quy về yêu cầu nghiệp vụ đo được (RTO/RPO, chi phí downtime), chọn phương án đạt yêu cầu với chi phí thấp nhất, tìm cách giảm chi phí không hạ tiêu chí (endpoint, RI), ghi lại quyết định và ngày review.
🧪 Thực hành bài này: Challenge: Kiến trúc tham chiếu + DR + cost (chưa triển khai) · lab labs/aws