Well-Architected 6 pillar và kiến trúc tham chiếu shop-app hoàn chỉnh

6 câu hỏi~60 phútTừ khóa: Well-Architected Framework, Operational Excellence, Security, Reliability, Performance Efficiency, Cost Optimization, Sustainability, reference architecture, Well-Architected Tool, resourcegroupstaggingapi, SAA: Tổng hợp 4 domain

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

3. Giải pháp: 6 pillar, mỗi pillar soi vào shop-app

PillarCâu hỏi cốt lõiNguyên tắc thiết kế chínhTrên shop-appDomain SAA
Operational ExcellenceChạ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 dayTerraform/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ẩnXuyên suốt
SecurityBả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 + OACD3 Secure 30%
ReliabilityChị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 automationMulti-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 EfficiencyDù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 OptimizationTrả đú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 labD4 Cost-Optimized 20%
SustainabilityGiả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 impactRight-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ợpKhô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:

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àiPillar chính
Account, IAM user shop-admin, role ShopAdminRole, MFA, CLI profile shop-lab, Budget đầu tiên22.1Security, Cost
Policy evaluation, AssumeRole, resource policy, permission boundary, Organizations/SCP22.2Security
shop-vpc, subnet, IGW/NAT, route table, SG chain, VPC endpoint, Flow Logs22.3Security, Cost
EC2 t3.micro, AMI, user data chạy shop-app.jar, EBS, IMDSv2, pricing22.4Performance, Cost
shop-alb, shop-tg, health check, shop-asg min 2 max 4, scaling policy22.5Reliability
S3 shop-app-assets/logs, storage class, lifecycle, OAC, presigned URL, event → Lambda22.6Reliability, Cost
RDS shop-db Multi-AZ, Read Replica, backup/PITR, RDS Proxy22.7Reliability
DynamoDB shop-sessions, shop-carts, TTL, on-demand, DAX, Streams22.8Performance
ElastiCache shop-cache, cache strategy, session store22.9Performance
Route 53 record, health check, failover; CloudFront, OAC, ACM22.10Performance, Reliability
SNS shop-order-topic, SQS shop-order-events + DLQ, EventBridge shop-bus22.11Reliability
Lambda shop-image-resize, shop-order-notifier (Java 21, SnapStart), API Gateway, Step Functions22.12Performance, Cost
ECR shop-app, ECS shop-cluster/shop-app-svc/shop-app-task, Fargate vs EC2, EKS22.13Operational Excellence
KMS alias/shop-key, Secrets Manager shop/db, Parameter Store, rotation22.14Security
CloudWatch /shop/app, alarm, dashboard, Logs Insights, CloudTrail, Config, X-Ray22.15Operational Excellence
EBS vs EFS vs FSx, Storage Gateway, DataSync (nếu shop-app cần shared file)22.16Performance
Kinesis, Athena trên log S3, Glue, QuickSight (phân tích đơn hàng)22.17Performance
RTO/RPO, 4 chiến lược DR, AWS Backup, multi-region, migration 7R, DMS22.18Reliability
Pricing model, Savings Plans/RI/Spot, tag, Cost Explorer, Budget, Compute Optimizer22.19Cost
WAF shop-waf, Shield, GuardDuty, Inspector, Macie, Security Hub, Cognito, Identity Center22.20Security
Bản đồ dịch vụ tổng quan, chọn nơi chạy app, IRSAdevops 05Tổ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ạnComputeDataHAChi phí/tháng (ước, không Free Tier)Khi nào
Lab / MVP1 EC2 t3.micro public hoặc 1 Fargate taskRDS Single-AZ db.t3.micro, không cacheKhông; backup hàng ngàyVài chục USD; Free Tier gần như phủ hết trừ NAT/ALBHọ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 ALBRDS Multi-AZ, ElastiCache 1 replica, DynamoDB on-demandMulti-AZ; Backup + Pilot Light DRKhoả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
ScaleECS/EKS nhiều service, Graviton, Savings Plans + SpotAurora PostgreSQL + Read Replica, ElastiCache cluster mode, DAXMulti-AZ 3 AZ; Warm Standby multi-regionNghìn USD trở lênTraffic lớn, SLA chặt
Serverless-firstAPI Gateway + Lambda (SnapStart), Step FunctionsDynamoDB, Aurora Serverless v2, S3Mặc định multi-AZ; Global Tables cho multi-regionTheo request; rẻ khi traffic thấp/không đềuTraffic 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

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:

PillarEC2 ASG (shop-asg)ECS Fargate (shop-app-svc)
Operational ExcellenceBạn patch OS, AMI, SSM agent, JDK; deploy bằng instance refresh hoặc CodeDeployKhông có OS để patch; deploy = task definition mới, rolling/blue-green sẵn; cùng image với local Docker (docker 02)
SecurityPhải lo IMDSv2, SSH/SSM, hardening AMI; Inspector quét EC2Task role per service (hạt mịn hơn instance role); không SSH; Inspector quét image ECR
ReliabilityASG thay instance hỏng; khởi động jar ~30–60 giây sau boot OS ~1 phútService scheduler thay task; task start ~30–60 giây; không có "node" để chết
PerformanceChọn instance type tự do (Graviton, burstable), tận dụng JVM warm lâu dàivCPU/memory theo bậc; CPU không burst; Graviton có (ARM64)
Costt3.micro Free Tier 12 tháng; Savings Plans/Spot linh hoạt; rẻ hơn Fargate khi chạy đầy tải 24/7Khô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
SustainabilityDễ 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:

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ầnGiảm thế nào mà vẫn ReliableKhi 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 InternetDev/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-AZGiữ Multi-AZ, giảm size nhờ cache; dùng Reserved DB Instance giảm ~40–60%; cân nhắc Aurora để standby làm readerLab, dev, hệ thống nội bộ chấp nhận RTO vài giờ; có PITR và automated backup bật
ElastiCache replicaCache mất thì app vẫn chạy (chậm hơn, hit RDS) nếu code có fallback → replica là tùy chọnKhi cache chỉ là cache, không là session store duy nhất
ALBKhông bỏ được trong production (TLS, health check, multi-AZ). Lab: 1 EC2 public + Elastic IPLab, 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

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 labViệc cần làm (bài)
OPS: Operations as code?Dựng bằng CLI tayChuyển sang Terraform/CloudFormation (devops 04)
SEC: Detect and investigate events?CloudTrail có, GuardDuty tắt sau labBậ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 restoreRestore 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

Chi phí & thời gian

Chuẩn bị

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)

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

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ệnCost: đá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