Route 53 routing policy, health check; CloudFront, OAC, ACM, Global Accelerator

6 câu hỏi~50 phútTừ khóa: hosted zone, alias record, routing policy, health check, failover, CloudFront, origin, OAC, cache behavior, invalidation, ACM, us-east-1, Global Accelerator, SAA: Domain 1 Performance / Domain 2 Resilient

Tiếp đi: Route 53 và CloudFront. Hiện shop-app chỉ có địa chỉ ALB dài ngoằng shop-alb-123.ap-southeast-1.elb.amazonaws.com, chạy HTTP. Ảnh sản phẩm thì frontend đọc thẳng từ bucket shop-app-assets, mỗi ảnh khách ở Hà Nội tải mất gần 1 giây. Mình muốn có tên miền shop.example.com, HTTPS, và ảnh tải nhanh. Trên AWS phải ghép những gì?

Bạn đang thiếu đúng ba mảnh "cửa vào" của một hệ thống trên AWS: DNS (Route 53), chứng chỉ TLS (ACM) và CDN (CloudFront). Bài devops 18.5 đã vẽ chúng ở hàng đầu sơ đồ; bài này dựng thật cho shop-app, và đi sâu vào phần đề SAA hỏi nhiều nhất: routing policy của Route 53 và OAC của CloudFront.

1. Bài toán: ba thứ khách hàng nhìn thấy đầu tiên

Hiện tại                                   Mong muốn
──────────────────────────────────────     ──────────────────────────────────────
http://shop-alb-123.ap-southeast-1         https://shop.example.com        (ALB, API + web)
      .elb.amazonaws.com                   https://assets.shop.example.com (ảnh, JS, CSS)
http://shop-app-assets-123.s3              Khách Hà Nội tải ảnh < 100 ms
      .ap-southeast-1.amazonaws.com/x.jpg  Bucket không public
Bucket public-read, ai cũng tải thẳng      Có thể chuyển sang region khác / DR mà
Ảnh 800 KB từ Singapore → Hà Nội ~900 ms   khách không phải đổi link

2. Vấn đề

3. Giải pháp: Route 53 + ACM + CloudFront (+ Global Accelerator khi cần)

Thuật ngữNghĩaTrên shop-app
Route 53DNS managed, 100% SLA, cũng là registrar (mua domain)Hosted zone example.com
Hosted zoneContainer record của một domain; public (Internet) hoặc private (chỉ trong VPC)Public example.com; private shop.internal cho RDS/cache
RecordA, AAAA, CNAME, MX, TXT, NS, SOA…shop.example.com
Alias recordRecord riêng của Route 53 trỏ tới tài nguyên AWS (ALB, CloudFront, S3 website, API GW, Global Accelerator, record khác). Dùng được ở zone apex (example.com), miễn phí query, tự theo IP đổishop.example.com A alias → ALB
CNAMETrỏ tên sang tên khác; không dùng được ở apex; tính tiền queryDùng khi trỏ ra dịch vụ ngoài AWS
TTLResolver cache record bao lâu; alias dùng TTL của target (60 s với ALB)Thấp (60 s) khi sắp đổi
Routing policyCách Route 53 chọn record khi có nhiều: simple, weighted, latency, failover, geolocation, geoproximity, multivalue, IP-basedFailover (DR), weighted (canary)
Health checkRoute 53 gọi endpoint từ nhiều nơi trên thế giới; fail thì bỏ record đó khỏi câu trả lời/actuator/health qua ALB
ACMAWS Certificate Manager: cấp cert public miễn phí, tự gia hạn, chỉ gắn vào dịch vụ AWS (ALB, CloudFront, API GW), không tải private key về đượcCert *.shop.example.com
DNS validationACM yêu cầu thêm CNAME vào zone để chứng minh sở hữu; Route 53 làm một nút bấm; gia hạn tự động mãiDùng thay email validation
CloudFrontCDN: 600+ edge location, cache nội dung gần người dùng, TLS ở edge, HTTP/3, WAF, ShieldDistribution cho assets và cho ALB
OriginNguồn nội dung: S3 bucket, ALB, EC2, API GW, Lambda URL, bất kỳ HTTP serverS3 shop-app-assets, ALB shop-alb
OAC (Origin Access Control)CloudFront ký request (SigV4) tới S3; bucket policy chỉ cho phép distribution đó; bucket giữ private. Thay cho OAI cũBucket assets không public
Cache behaviorLuật theo path pattern: TTL, cache policy, origin request policy, viewer protocol, allowed methods/images/* cache 1 ngày; /api/* không cache
InvalidationXóa object khỏi cache edge trước TTL; 1.000 path/tháng miễn phíKhi đổi ảnh cùng tên
Origin groupPrimary + secondary origin, failover khi origin trả 5xxBucket Singapore + bucket replica Tokyo
Signed URL / cookieChỉ ai có URL ký mới xem được (nội dung trả phí)Hóa đơn PDF
Global Accelerator2 anycast IP tĩnh, đưa TCP/UDP vào mạng AWS ở edge gần nhất, tới ALB/NLB/EC2 ở nhiều region; không cacheKhi cần IP tĩnh, non-HTTP, failover region giây

4. Code / cấu hình từng bước

Bước 1: Hosted zone (nếu domain mua ở nơi khác, đổi NS của domain sang 4 name server Route 53 trả về).

export AWS_REGION=ap-southeast-1
DOMAIN=example.com
ZONE_ID=$(aws route53 create-hosted-zone --profile shop-lab --name $DOMAIN \
  --caller-reference "shop-$(date +%s)" \
  --hosted-zone-config Comment="shop-app lab" \
  --query 'HostedZone.Id' --output text | sed 's#/hostedzone/##')
aws route53 change-tags-for-resource --profile shop-lab --resource-type hostedzone --resource-id $ZONE_ID \
  --add-tags Key=Project,Value=shop-app Key=Env,Value=lab
aws route53 get-hosted-zone --profile shop-lab --id $ZONE_ID --query 'DelegationSet.NameServers'

Bước 2: Chứng chỉ ACM. Hai cert: một ở ap-southeast-1 cho ALB, một bắt buộc ở us-east-1 cho CloudFront (CloudFront là dịch vụ global, chỉ đọc cert ở N. Virginia; đề thi hỏi câu này rất hay).

# Cert cho ALB (cùng region ALB)
CERT_ALB=$(aws acm request-certificate --profile shop-lab --region ap-southeast-1 \
  --domain-name shop.$DOMAIN --validation-method DNS \
  --tags Key=Project,Value=shop-app Key=Env,Value=lab --query CertificateArn --output text)

# Cert cho CloudFront: PHẢI us-east-1, wildcard cho assets.*
CERT_CF=$(aws acm request-certificate --profile shop-lab --region us-east-1 \
  --domain-name "*.shop.$DOMAIN" --subject-alternative-names shop.$DOMAIN \
  --validation-method DNS \
  --tags Key=Project,Value=shop-app Key=Env,Value=lab --query CertificateArn --output text)

# Lấy CNAME validation và thêm vào zone (lặp cho cả 2 cert)
aws acm describe-certificate --profile shop-lab --region us-east-1 --certificate-arn $CERT_CF \
  --query 'Certificate.DomainValidationOptions[0].ResourceRecord'
# { "Name": "_abc123.shop.example.com.", "Type": "CNAME", "Value": "_xyz.acm-validations.aws." }
cat > acm-validate.json <<EOF
{"Changes":[{"Action":"UPSERT","ResourceRecordSet":{
  "Name":"_abc123.shop.$DOMAIN.","Type":"CNAME","TTL":300,
  "ResourceRecords":[{"Value":"_xyz.acm-validations.aws."}]}}]}
EOF
aws route53 change-resource-record-sets --profile shop-lab --hosted-zone-id $ZONE_ID --change-batch file://acm-validate.json
aws acm wait certificate-validated --profile shop-lab --region us-east-1 --certificate-arn $CERT_CF   # vài phút

Trong Console, ACM có nút Create records in Route 53 làm bước này một lần bấm. Cert ACM tự gia hạn mãi khi CNAME còn trong zone.

Bước 3: HTTPS listener trên ALB (bài 22.5 chỉ có listener 80).

ALB_ARN=$(aws elbv2 describe-load-balancers --profile shop-lab --names shop-alb --query 'LoadBalancers[0].LoadBalancerArn' --output text)
TG_ARN=$(aws elbv2 describe-target-groups --profile shop-lab --names shop-tg --query 'TargetGroups[0].TargetGroupArn' --output text)
aws elbv2 create-listener --profile shop-lab --load-balancer-arn $ALB_ARN \
  --protocol HTTPS --port 443 --certificates CertificateArn=$CERT_ALB \
  --ssl-policy ELBSecurityPolicy-TLS13-1-2-2021-06 \
  --default-actions Type=forward,TargetGroupArn=$TG_ARN

# Listener 80 → redirect 301 sang 443
L80=$(aws elbv2 describe-listeners --profile shop-lab --load-balancer-arn $ALB_ARN --query "Listeners[?Port==\`80\`].ListenerArn" --output text)
aws elbv2 modify-listener --profile shop-lab --listener-arn $L80 \
  --default-actions '[{"Type":"redirect","RedirectConfig":{"Protocol":"HTTPS","Port":"443","StatusCode":"HTTP_301"}}]'

TLS kết thúc ở ALB; ALB → EC2 vẫn HTTP 8080 trong VPC. Spring Boot cần biết request gốc là HTTPS để tạo redirect/cookie đúng:

server:
  forward-headers-strategy: framework   # đọc X-Forwarded-Proto / X-Forwarded-For từ ALB
server.tomcat.remoteip.remote-ip-header: x-forwarded-for
server.tomcat.remoteip.protocol-header: x-forwarded-proto

Bước 4: Alias record trỏ về ALB.

ALB_DNS=$(aws elbv2 describe-load-balancers --profile shop-lab --names shop-alb --query 'LoadBalancers[0].DNSName' --output text)
ALB_ZONE=$(aws elbv2 describe-load-balancers --profile shop-lab --names shop-alb --query 'LoadBalancers[0].CanonicalHostedZoneId' --output text)
cat > alb-alias.json <<EOF
{"Changes":[{"Action":"UPSERT","ResourceRecordSet":{
  "Name":"shop.$DOMAIN","Type":"A",
  "AliasTarget":{"HostedZoneId":"$ALB_ZONE","DNSName":"dualstack.$ALB_DNS","EvaluateTargetHealth":true}}}]}
EOF
aws route53 change-resource-record-sets --profile shop-lab --hosted-zone-id $ZONE_ID --change-batch file://alb-alias.json

EvaluateTargetHealth=true: nếu mọi target trong ALB unhealthy, Route 53 coi record này fail (dùng cho failover ở bước 7 mà không cần tạo health check riêng).

Bước 5: CloudFront + OAC cho bucket assets.

BUCKET=shop-app-assets-$ACCOUNT_ID
OAC_ID=$(aws cloudfront create-origin-access-control --profile shop-lab --origin-access-control-config \
  Name=shop-assets-oac,SigningProtocol=sigv4,SigningBehavior=always,OriginAccessControlOriginType=s3 \
  --query 'OriginAccessControl.Id' --output text)

cat > cf-assets.json <<EOF
{
  "CallerReference": "shop-assets-$(date +%s)",
  "Comment": "shop-app assets",
  "Enabled": true,
  "Aliases": {"Quantity": 1, "Items": ["assets.shop.$DOMAIN"]},
  "DefaultRootObject": "index.html",
  "Origins": {"Quantity": 1, "Items": [{
    "Id": "s3-assets",
    "DomainName": "$BUCKET.s3.$AWS_REGION.amazonaws.com",
    "OriginAccessControlId": "$OAC_ID",
    "S3OriginConfig": {"OriginAccessIdentity": ""}
  }]},
  "DefaultCacheBehavior": {
    "TargetOriginId": "s3-assets",
    "ViewerProtocolPolicy": "redirect-to-https",
    "Compress": true,
    "AllowedMethods": {"Quantity": 2, "Items": ["GET","HEAD"]},
    "CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6"
  },
  "PriceClass": "PriceClass_200",
  "HttpVersion": "http2and3",
  "ViewerCertificate": {"ACMCertificateArn": "$CERT_CF", "SSLSupportMethod": "sni-only",
                        "MinimumProtocolVersion": "TLSv1.2_2021"}
}
EOF
DIST_ID=$(aws cloudfront create-distribution --profile shop-lab --distribution-config file://cf-assets.json \
  --query 'Distribution.Id' --output text)
DIST_DOMAIN=$(aws cloudfront get-distribution --profile shop-lab --id $DIST_ID --query 'Distribution.DomainName' --output text)
aws cloudfront tag-resource --profile shop-lab --resource arn:aws:cloudfront::$ACCOUNT_ID:distribution/$DIST_ID \
  --tags 'Items=[{Key=Project,Value=shop-app},{Key=Env,Value=lab}]'

658327ea-… là managed cache policy CachingOptimized (TTL mặc định 1 ngày, bỏ query string/cookie khỏi cache key). PriceClass_200 bỏ các edge đắt nhất (Nam Mỹ, Úc) nếu khách chủ yếu ở châu Á; PriceClass_100 chỉ Bắc Mỹ + châu Âu thì không hợp vì không có Singapore.

Bucket policy chỉ cho distribution này đọc (xóa public-read nếu có):

cat > bucket-policy.json <<EOF
{"Version":"2012-10-17","Statement":[{
  "Sid":"AllowCloudFrontOAC","Effect":"Allow",
  "Principal":{"Service":"cloudfront.amazonaws.com"},
  "Action":"s3:GetObject","Resource":"arn:aws:s3:::$BUCKET/*",
  "Condition":{"StringEquals":{"AWS:SourceArn":"arn:aws:cloudfront::$ACCOUNT_ID:distribution/$DIST_ID"}}}]}
EOF
aws s3api put-bucket-policy --profile shop-lab --bucket $BUCKET --policy file://bucket-policy.json
aws s3api put-public-access-block --profile shop-lab --bucket $BUCKET \
  --public-access-block-configuration BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true

Bước 6: Alias assets.shop.example.com → CloudFront. Hosted zone ID của CloudFront luôn là Z2FDTNDATAQYW2.

cat > cf-alias.json <<EOF
{"Changes":[{"Action":"UPSERT","ResourceRecordSet":{
  "Name":"assets.shop.$DOMAIN","Type":"A",
  "AliasTarget":{"HostedZoneId":"Z2FDTNDATAQYW2","DNSName":"$DIST_DOMAIN","EvaluateTargetHealth":false}}}]}
EOF
aws route53 change-resource-record-sets --profile shop-lab --hosted-zone-id $ZONE_ID --change-batch file://cf-alias.json

Trong shop-app, URL ảnh trả về cho frontend đổi từ bucket sang CDN, qua cấu hình:

shop:
  assets:
    base-url: ${SHOP_ASSETS_BASE_URL:https://assets.shop.example.com}
@Service
public class ProductImageUrlResolver {
    private final String baseUrl;
    public ProductImageUrlResolver(@Value("${shop.assets.base-url}") String baseUrl) { this.baseUrl = baseUrl; }

    /** key trong S3: images/p-42/main.jpg → https://assets.shop.example.com/images/p-42/main.jpg */
    public String publicUrl(String s3Key) { return baseUrl + "/" + s3Key; }

    /** Khi upload ảnh mới, dùng tên có hash để không cần invalidation: main.a1b2c3.jpg */
    public String versionedKey(String productId, byte[] content) {
        String hash = HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(content)).substring(0, 8);
        return "images/%s/main.%s.jpg".formatted(productId, hash);
    }
}

Đặt Cache-Control: public, max-age=31536000, immutable khi upload (PutObjectRequest.cacheControl(...)) với file có hash trong tên; CloudFront và trình duyệt cache 1 năm, đổi ảnh = đổi tên, không tốn invalidation.

Bước 7: Health check + failover record (chuẩn bị DR, bài 22.18). Primary là ALB Singapore, secondary là một trang "đang bảo trì" tĩnh trên S3/CloudFront:

HC_ID=$(aws route53 create-health-check --profile shop-lab --caller-reference "shop-hc-$(date +%s)" \
  --health-check-config Type=HTTPS,FullyQualifiedDomainName=shop.$DOMAIN,Port=443,ResourcePath=/actuator/health,RequestInterval=30,FailureThreshold=3 \
  --query 'HealthCheck.Id' --output text)
aws route53 change-tags-for-resource --profile shop-lab --resource-type healthcheck --resource-id $HC_ID \
  --add-tags Key=Name,Value=shop-alb-health Key=Project,Value=shop-app

cat > failover.json <<EOF
{"Changes":[
 {"Action":"UPSERT","ResourceRecordSet":{"Name":"www.shop.$DOMAIN","Type":"A",
   "SetIdentifier":"primary-sg","Failover":"PRIMARY","HealthCheckId":"$HC_ID",
   "AliasTarget":{"HostedZoneId":"$ALB_ZONE","DNSName":"dualstack.$ALB_DNS","EvaluateTargetHealth":true}}},
 {"Action":"UPSERT","ResourceRecordSet":{"Name":"www.shop.$DOMAIN","Type":"A",
   "SetIdentifier":"secondary-static","Failover":"SECONDARY",
   "AliasTarget":{"HostedZoneId":"Z2FDTNDATAQYW2","DNSName":"$MAINT_DIST_DOMAIN","EvaluateTargetHealth":false}}}
]}
EOF
aws route53 change-resource-record-sets --profile shop-lab --hosted-zone-id $ZONE_ID --change-batch file://failover.json

5. Cách hoạt động

Khách ở Hà Nội mở https://shop.example.com/products/42 (HTML có <img src="https://assets.shop.example.com/images/p-42/main.a1b2c3.jpg">)

① DNS
   resolver ISP ──► Route 53 (anycast, 100% SLA)
     shop.example.com        A alias → shop-alb (trả IP hiện tại của ALB, TTL 60)
     assets.shop.example.com A alias → d1234.cloudfront.net (trả IP edge GẦN NHẤT: Hà Nội)

② Trang HTML / API                          ③ Ảnh
   Hà Nội ──TLS(ACM ap-southeast-1)──► shop-alb      Hà Nội ──TLS(ACM us-east-1)──► CloudFront edge Hà Nội
            :443 → :8080 EC2 (private)                        │ cache hit? (key = host+path)
            X-Forwarded-Proto: https                          ├─ HIT  → trả ngay (~20 ms)          ~98%
                                                              └─ MISS → edge → regional cache →
                                                                        SigV4 (OAC) → S3 Singapore
                                                                        → lưu edge, trả về (~300 ms, 1 lần)
   Bucket policy: chỉ Principal cloudfront.amazonaws.com + SourceArn = distribution này
   → curl thẳng s3.amazonaws.com/... → 403

④ Failover (www.shop.example.com)
   Route 53 health checker (15+ vùng) ──30 s──► https://shop.example.com/actuator/health
   ≥ 18% checker báo OK → PRIMARY healthy → trả ALB
   3 lần fail liên tiếp (90 s) → PRIMARY unhealthy → trả SECONDARY (trang bảo trì CloudFront)
   Hồi phục → tự quay lại PRIMARY

Cache key và vì sao API không nên qua CloudFront mặc định. CloudFront quyết định hit/miss theo cache key (mặc định host + path; thêm header/query/cookie nếu cache policy cho). Nếu bạn đặt ALB sau CloudFront với policy CachingOptimized, request /api/me của user A có thể trả về cho user B (cookie bị bỏ khỏi key). Vì vậy cho ALB origin dùng CachingDisabled + AllViewer origin request policy, chỉ cache behavior /images/*, /static/*. Lợi vẫn có: TLS ở edge, HTTP/3, WAF, giấu ALB, gom hai domain thành một.

Các routing policy (cốt lõi của đề SAA):

PolicyChọn record theoHealth checkTình huống đềTrên shop-app
SimpleMột record, nhiều giá trị trả ngẫu nhiên cảKhôngMột tài nguyên duy nhấtshop.example.com → ALB
WeightedTỉ lệ % bạn đặt (0–255); weight 0 = tắtCóCanary, A/B, chuyển dần sang stack mới, chia tải không đều giữa region90% ALB cũ / 10% ALB mới (ECS, bài 22.13)
LatencyRegion có độ trễ đo được thấp nhất tới resolver của userCó"users worldwide, lowest latency", nhiều regionSingapore vs Tokyo khi mở shop Nhật
FailoverPRIMARY nếu healthy, không thì SECONDARYBắt buộc ở primaryActive-passive DR, trang bảo trìwww → ALB hoặc trang tĩnh
GeolocationQuốc gia / lục địa của user (theo IP)CóNội dung theo nước, tuân thủ luật (data residency), chặn nướcKhách VN → stack VN, khách JP → stack JP, mặc định → VN
GeoproximityKhoảng cách địa lý tới tài nguyên, có bias kéo/đẩy vùng phủ (chỉ qua Traffic Flow)Có"shift traffic from one region to another by adjusting bias"Kéo 20% khách Đông Nam Á sang Tokyo khi Singapore quá tải
Multivalue answerTrả tối đa 8 IP healthy, client chọnCó (từng giá trị)DNS-level load balancing đơn giản, không có ELBVài EC2 public không có ALB
IP-basedCIDR của resolverCóISP cụ thể, mạng công tyHiếm

Mẹo phân biệt hay nhầm: Latency đo độ trễ mạng thật (user ở Úc có thể được đưa về Singapore); Geolocation dựa vị trí hành chính (user ở Úc luôn vào record "Oceania" dù chậm); Geoproximity dựa khoảng cách và cho chỉnh bias.

6. So sánh

Alias recordCNAME record
Trỏ tớiTài nguyên AWS (ALB, CloudFront, S3 website, API GW, GA, Elastic Beanstalk, VPC endpoint, record khác cùng zone)Bất kỳ hostname
Zone apex (example.com)ĐượcKhông (vi phạm RFC)
Tính tiền queryMiễn phíTính
Loại recordA hoặc AAAA (nhìn như A record)CNAME
HealthEvaluateTargetHealthKhông
CloudFrontGlobal Accelerator
TầngHTTP/HTTPS (L7), có cacheTCP/UDP (L4), không cache
IPThay đổi, nhiều2 anycast IP tĩnh (whitelist được)
Tốt choStatic, video, API cacheable, WAF, Lambda@Edge/FunctionsGame, VoIP, MQTT, API non-HTTP, ứng dụng cần IP cố định, failover region trong giây
OriginS3, ALB, bất kỳ HTTPALB, NLB, EC2, Elastic IP ở nhiều region
Đề thi"cache content", "reduce latency for static/dynamic web content", "restrict S3 access to CDN""static IP addresses", "non-HTTP", "deterministic fast regional failover", "gaming/IoT"
ACM public certACM imported / private CAMua cert ngoài cài lên EC2
GiáMiễn phíPrivate CA tính tiền theo thángTốn tiền + công
Gia hạnTự động (DNS validation)Imported: tự lo; Private CA: tự độngTự lo
Gắn vàoALB, NLB, CloudFront, API GW, App Runner… (không export private key)Như trên, hoặc export được (private CA)Bất kỳ, kể cả trong EC2
RegionCùng region với tài nguyên; CloudFront → us-east-1Như trênKhông phụ thuộc
Dùng khiMọi TLS public trên AWSmTLS nội bộ, service mesh, cert cho EC2Khi cần EV cert hoặc dịch vụ không hỗ trợ ACM
Cách bảo vệ S3 originOACOAI (cũ)Bucket public + Referer
Ký requestSigV4, mọi region, SSE-KMS được, PUT/DELETE đượcLegacy, không SSE-KMS, không region mới (opt-in)Không, dễ giả header
Khuyến nghịDùngMigrate sang OACKhông

7. Khi nào dùng / không dùng

Admin upload lại ảnh images/p-42/main.jpg lên S3 (cùng tên) mà trang khách vẫn hiện ảnh cũ, ấn F5 cũng vậy. Mình thấy có nút Invalidation trong CloudFront. Lần nào đổi ảnh cũng phải invalidate à, có tốn tiền không? Và có cách nào không cần không?

Đúng là invalidation giải quyết được, nhưng đó là công cụ chữa cháy, không phải quy trình hàng ngày. Có hai lớp cache đang giữ ảnh cũ: edge của CloudFront (TTL 1 ngày theo CachingOptimized) và trình duyệt (theo Cache-Control mà S3 trả, hoặc heuristic nếu không có). F5 chỉ bỏ cache trình duyệt cho tài nguyên chính, không nhất thiết cho ảnh.

Cách 1: Invalidation (khi cần gấp)

aws cloudfront create-invalidation --profile shop-lab --distribution-id $DIST_ID \
  --paths "/images/p-42/main.jpg"
# hoặc cả thư mục: --paths "/images/p-42/*"   (một path có wildcard vẫn tính là 1 path)
aws cloudfront get-invalidation --profile shop-lab --distribution-id $DIST_ID --id $INV_ID --query 'Invalidation.Status'
# InProgress → Completed (thường < 1 phút)

Chi phí: 1.000 path đầu mỗi tháng miễn phí, sau đó tính tiền theo path (khoảng vài phần nghìn USD/path, xem pricing). Dùng /images/p-42/* là một path, rẻ. Nhưng nó không xóa cache trình duyệt của khách đã xem.

Cách 2: Versioned file name (khuyên dùng, không tốn gì)

Upload mới → tên mới (hash nội dung):   images/p-42/main.a1b2c3.jpg → images/p-42/main.9f8e7d.jpg
DB lưu key mới → HTML trả URL mới → trình duyệt và CloudFront coi là object khác → miss một lần → tươi
File cũ: giữ vài ngày rồi lifecycle rule xóa (bài 22.6), hoặc để đó (rẻ)

Header khi upload:
  Cache-Control: public, max-age=31536000, immutable     ← ảnh có hash: cache 1 năm
  Cache-Control: public, max-age=60                       ← file KHÔNG đổi tên (index.html, manifest): cache ngắn
s3.putObject(PutObjectRequest.builder()
        .bucket(bucket).key(versionedKey)
        .contentType("image/jpeg")
        .cacheControl("public, max-age=31536000, immutable")
        .build(),
    RequestBody.fromBytes(content));

Đây cũng là cách build tool frontend (Vite, webpack) làm với app.3f2a1c.js: chỉ index.html là TTL ngắn, mọi thứ khác bất biến.

Cách 3: Cache policy theo path

Nếu không đổi tên được (ảnh do hệ thống bên thứ ba push cùng tên), tạo cache behavior /images/* với cache policy riêng TTL 5 phút, chấp nhận nhiều request về origin hơn. Hoặc thêm query string ?v=2 và cache policy đưa query vào cache key (CachingOptimized mặc định bỏ query string, nên ?v=2 không có tác dụng nếu bạn không đổi policy; đây là lỗi hay gặp).

InvalidationVersioned nameTTL ngắn
Tốn tiềnSau 1.000 path/thángKhôngTốn request origin
Xóa cache trình duyệtKhôngCó (URL mới)Sau TTL
Dùng khiSự cố, ảnh sai cần gỡ ngayQuy trình chuẩnNội dung đổi thường xuyên không kiểm soát tên

Mình xin cert ACM ở ap-southeast-1, gắn vào ALB được, nhưng khi tạo CloudFront distribution thì ô chọn cert trống trơn, không thấy cert đâu. Mình phải tạo cert ở us-east-1 à? Vậy private key của mình nằm ở Mỹ có vấn đề gì không, và nếu domain mua ở nhà cung cấp Việt Nam thì validate kiểu gì?

Bạn đã tự tìm ra đáp án: CloudFront chỉ dùng cert ACM ở us-east-1. Lý do: CloudFront là dịch vụ global, control plane đặt ở N. Virginia; cert được phân phối tới tất cả edge từ đó. ALB là dịch vụ regional nên cert phải cùng region với ALB. Hệ quả thực tế: một domain thường có hai cert ACM (một per region cho ALB, một us-east-1 cho CloudFront), cả hai miễn phí và tự gia hạn.

Private key có "nằm ở Mỹ" không?

ACM sinh key pair bên trong HSM của AWS, bạn không bao giờ lấy được private key của cert public ACM. Key được dùng để terminate TLS tại edge CloudFront ở mọi nơi (kể cả edge Hà Nội); region us-east-1 chỉ là nơi quản lý metadata. Về tuân thủ, đây là cert public cho TLS, không phải dữ liệu khách hàng; dữ liệu khách vẫn ở Singapore trong S3/RDS. Nếu chính sách công ty yêu cầu kiểm soát key, cách duy nhất là import cert của bạn vào ACM (vẫn phải import ở us-east-1 cho CloudFront) và tự lo gia hạn.

Domain mua ở nhà đăng ký Việt Nam

Có hai cách, chọn một:

Cách A: Chuyển DNS sang Route 53 (khuyên dùng)
  1. Tạo hosted zone example.com trên Route 53 → nhận 4 NS (ns-123.awsdns-15.com, ...)
  2. Vào trang nhà đăng ký VN → đổi Name Server của domain thành 4 NS đó (domain vẫn thuộc bạn, chỉ đổi nơi trả lời DNS)
  3. Chờ lan truyền (vài phút tới 48 giờ, thường < 1 giờ)
  4. ACM → Create records in Route 53 → validate tự động; alias record, failover, health check dùng được hết

Cách B: Giữ DNS ở nhà đăng ký
  1. ACM yêu cầu CNAME _abc.shop.example.com → _xyz.acm-validations.aws
  2. Tự thêm CNAME đó trong bảng DNS của nhà đăng ký (xong rồi ĐỪNG xóa, ACM dùng nó để gia hạn)
  3. Trỏ shop.example.com CNAME → shop-alb-123...elb.amazonaws.com; assets CNAME → d1234.cloudfront.net
  Hạn chế: không alias ở apex (example.com không trỏ được ALB, chỉ www.), không failover/health check của Route 53, tính tiền query ở nhà đăng ký

Kiểm tra lan truyền NS và validation:

dig NS example.com +short                       # phải thấy awsdns nếu dùng cách A
dig CNAME _abc123.shop.example.com +short        # thấy acm-validations.aws
aws acm describe-certificate --profile shop-lab --region us-east-1 --certificate-arn $CERT_CF \
  --query 'Certificate.{Status:Status,Renewal:RenewalEligibility,NotAfter:NotAfter}'

Lỗi hay gặp: validation treo "Pending validation" nhiều giờ vì CNAME thêm sai (nhà đăng ký tự nối thêm .example.com vào value, hoặc bạn dán cả dấu chấm cuối vào ô không cho phép). Dùng dig để xem bản ghi thật trên Internet.

Mình làm đề thử, gặp câu khách hàng doanh nghiệp yêu cầu whitelist IP cố định của shop-app, và failover sang region khác trong vài giây. Mình chọn CloudFront vì nghĩ "edge" là nó, nhưng đáp án là Global Accelerator. Hai cái đều là mạng edge của AWS, khác nhau ở đâu mà đề cứ bắt phân biệt?

Bạn hiểu đúng một nửa: cả hai đều dùng cùng hệ thống edge location và backbone của AWS. Khác nhau ở tầng và mục đích: CloudFront là CDN tầng 7 (hiểu HTTP, cache, cert), Global Accelerator là bộ gia tốc tầng 4 (chuyển TCP/UDP, không hiểu nội dung, không cache). Hai từ khóa trong đề của bạn, "static IP" và "failover in seconds", đều là đặc sản của Global Accelerator.

CloudFront                                          Global Accelerator
──────────────────────────────────────────          ──────────────────────────────────────────
Khách → edge (TLS, cache HIT → trả ngay)            Khách → edge (anycast IP 1 trong 2 IP TĨNH)
         MISS → origin (S3/ALB) qua backbone                 → backbone AWS → endpoint group gần nhất
IP edge thay đổi, hàng nghìn                        2 IP tĩnh toàn cầu, không bao giờ đổi
HTTP/HTTPS, WebSocket                               TCP, UDP bất kỳ (game, VoIP, MQTT, SFTP...)
Cert ACM us-east-1 ở edge                           Không terminate TLS (ALB/NLB phía sau làm)
Failover origin: origin group khi 5xx               Failover region: health check endpoint, ~30 s,
                                                      KHÔNG phụ thuộc DNS TTL / cache của resolver
Cache-able content → giảm tải origin                Mọi request đến endpoint, chỉ nhanh hơn Internet thường
WAF, Shield Advanced, Functions/Lambda@Edge         Shield Standard; traffic dial % mỗi region; client affinity

Vì sao DNS failover không "trong vài giây"

Route 53 failover (bước 7 ở trên) đổi câu trả lời DNS sau khi health check fail (~90 s với interval 30 × 3), nhưng khách còn cache DNS ở resolver ISP, ở OS, ở JVM (networkaddress.cache.ttl). Thực tế mất 1–5 phút để đa số khách chuyển. Global Accelerator dùng cùng IP, chỉ đổi đường đi bên trong mạng AWS, nên khách không cần resolve lại gì: failover ~30 giây hoặc nhanh hơn, deterministic. Đề SAA diễn đạt là "deterministic, fast regional failover" hoặc "without relying on DNS caching".

Trên shop-app dùng khi nào?

Nhu cầuChọn
Web + ảnh cho khách lẻ toàn quốc, cần WAFCloudFront (như bài này)
Đối tác B2B whitelist IP đầu ra của họ tới API shop-appGlobal Accelerator trước ALB (2 IP tĩnh để họ mở firewall)
Mở region Tokyo, muốn khách tự tới region gần và failover nhanhGlobal Accelerator (endpoint group SG + JP) hoặc Route 53 latency + failover (rẻ hơn, chậm hơn)
App mobile dùng MQTT/gRPC over TCP không HTTPGlobal Accelerator
Cả hai: static qua CDN, API cần IP tĩnhCloudFront cho assets + Global Accelerator cho api.

Chi phí: Global Accelerator tính phí cố định theo giờ cho accelerator (khoảng 0.025 USD/giờ) cộng phí data transfer premium theo GB; không có Free Tier. Vì vậy lab bài này không tạo nó, chỉ cần nhớ đặc điểm để chọn đúng trong đề.

Cho mình bài lab để tự tay làm phần này nhé. Mình chưa có domain thật thì làm được những phần nào?

Lab 22.10: Web tĩnh shop-app qua CloudFront + OAC, Route 53 weighted/failover

Lab chia hai phần: Phần A không cần domain (S3 + CloudFront + OAC, dùng tên *.cloudfront.net), Phần B cần domain (hosted zone, ACM, alias, routing policy). Không có domain thì làm Phần A đầy đủ và đọc Phần B; hoặc mua một domain rẻ (khoảng 3–12 USD/năm) ở Route 53 hoặc nhà đăng ký bất kỳ để làm trọn.

Mục tiêu

Chi phí & thời gian

Chuẩn bị

Các bước, Phần A (không cần domain)

Bước 1: Bucket private + nội dung.

export AWS_REGION=ap-southeast-1
ACCOUNT_ID=$(aws sts get-caller-identity --profile shop-lab --query Account --output text)
WEB=shop-app-web-$ACCOUNT_ID
aws s3api create-bucket --profile shop-lab --bucket $WEB --create-bucket-configuration LocationConstraint=$AWS_REGION
aws s3api put-public-access-block --profile shop-lab --bucket $WEB \
  --public-access-block-configuration BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
aws s3api put-bucket-tagging --profile shop-lab --bucket $WEB --tagging 'TagSet=[{Key=Project,Value=shop-app},{Key=Env,Value=lab}]'

mkdir -p web/images
cat > web/index.html <<'EOF'
<!doctype html><html><head><meta charset="utf-8"><title>shop-app</title></head>
<body><h1>shop-app v1</h1><img src="images/logo.png" width="120"></body></html>
EOF
# tạo một ảnh png bất kỳ (hoặc copy ảnh có sẵn) vào web/images/logo.png
aws s3 cp web/index.html s3://$WEB/index.html --profile shop-lab --cache-control "public, max-age=60"
aws s3 cp web/images/logo.png s3://$WEB/images/logo.png --profile shop-lab --cache-control "public, max-age=31536000, immutable"
curl -sI https://$WEB.s3.$AWS_REGION.amazonaws.com/index.html | head -1     # HTTP/1.1 403 Forbidden (đúng ý)

Console: S3 → Create bucket (Block all public access giữ bật) → Upload.

Bước 2: OAC + distribution. Dùng block lệnh ở mục 4 bước 5 với các thay đổi: BUCKET=$WEB, bỏ "Aliases" và "ViewerCertificate" (CloudFront dùng cert mặc định *.cloudfront.net):

OAC_ID=$(aws cloudfront create-origin-access-control --profile shop-lab --origin-access-control-config \
  Name=shop-web-oac,SigningProtocol=sigv4,SigningBehavior=always,OriginAccessControlOriginType=s3 \
  --query 'OriginAccessControl.Id' --output text)
cat > cf-web.json <<EOF
{"CallerReference":"shop-web-$(date +%s)","Comment":"shop-app web lab","Enabled":true,
 "DefaultRootObject":"index.html",
 "Origins":{"Quantity":1,"Items":[{"Id":"s3-web","DomainName":"$WEB.s3.$AWS_REGION.amazonaws.com",
   "OriginAccessControlId":"$OAC_ID","S3OriginConfig":{"OriginAccessIdentity":""}}]},
 "DefaultCacheBehavior":{"TargetOriginId":"s3-web","ViewerProtocolPolicy":"redirect-to-https","Compress":true,
   "AllowedMethods":{"Quantity":2,"Items":["GET","HEAD"]},"CachePolicyId":"658327ea-f89d-4fab-a63d-7e88639e58f6"},
 "PriceClass":"PriceClass_200","HttpVersion":"http2and3"}
EOF
DIST_ID=$(aws cloudfront create-distribution --profile shop-lab --distribution-config file://cf-web.json --query 'Distribution.Id' --output text)
DIST_DOMAIN=$(aws cloudfront get-distribution --profile shop-lab --id $DIST_ID --query 'Distribution.DomainName' --output text)
aws cloudfront tag-resource --profile shop-lab --resource arn:aws:cloudfront::$ACCOUNT_ID:distribution/$DIST_ID \
  --tags 'Items=[{Key=Project,Value=shop-app},{Key=Env,Value=lab}]'
echo $DIST_DOMAIN     # d1a2b3c4.cloudfront.net

Console: CloudFront → Create distribution → Origin domain chọn bucket → Origin access: Origin access control settings → Create new OAC → Viewer protocol Redirect HTTP to HTTPS → Cache policy CachingOptimized → Create. Console sẽ hiện nút Copy policy cho bucket.

Bước 3: Bucket policy cho OAC (block ở mục 4 bước 5, thay $BUCKET bằng $WEB). Chờ deploy:

aws cloudfront wait distribution-deployed --profile shop-lab --id $DIST_ID    # ~5 phút

Bước 4: Test cache.

curl -sI https://$DIST_DOMAIN/ | grep -iE 'HTTP/|x-cache|age:|cache-control|content-encoding'
# HTTP/2 200
# cache-control: public, max-age=60
# x-cache: Miss from cloudfront
curl -sI https://$DIST_DOMAIN/ | grep -i x-cache          # x-cache: Hit from cloudfront
curl -sI https://$DIST_DOMAIN/images/logo.png | grep -iE 'x-cache|cache-control'
curl -s  https://$DIST_DOMAIN/ | grep h1                  # shop-app v1

Bước 5: Đổi nội dung và invalidation.

sed -i 's/v1/v2/' web/index.html
aws s3 cp web/index.html s3://$WEB/index.html --profile shop-lab --cache-control "public, max-age=60"
curl -s https://$DIST_DOMAIN/ | grep h1                   # vẫn v1 (edge cache tới 60 s theo max-age)
INV_ID=$(aws cloudfront create-invalidation --profile shop-lab --distribution-id $DIST_ID --paths "/index.html" --query 'Invalidation.Id' --output text)
aws cloudfront wait invalidation-completed --profile shop-lab --distribution-id $DIST_ID --id $INV_ID
curl -s https://$DIST_DOMAIN/ | grep h1                   # shop-app v2

Các bước, Phần B (cần domain)

Bước 6: Hosted zone (mục 4 bước 1). Nếu domain mua ngoài Route 53, đổi NS ở nhà đăng ký, rồi kiểm tra dig NS $DOMAIN +short thấy awsdns.

Console: Route 53 → Hosted zones → Create hosted zone → Public.

Bước 7: Cert ACM ở us-east-1 cho *.shop.$DOMAIN + validate bằng Route 53 (mục 4 bước 2). Console: ACM (đổi region sang N. Virginia) → Request → DNS validation → Create records in Route 53.

Bước 8: Gắn alias + cert vào distribution.

ETAG=$(aws cloudfront get-distribution-config --profile shop-lab --id $DIST_ID --query ETag --output text)
aws cloudfront get-distribution-config --profile shop-lab --id $DIST_ID --query DistributionConfig > cur.json
jq --arg d "assets.shop.$DOMAIN" --arg c "$CERT_CF" '
  .Aliases = {Quantity:1, Items:[$d]} |
  .ViewerCertificate = {ACMCertificateArn:$c, SSLSupportMethod:"sni-only", MinimumProtocolVersion:"TLSv1.2_2021"}' cur.json > new.json
aws cloudfront update-distribution --profile shop-lab --id $DIST_ID --if-match $ETAG --distribution-config file://new.json > /dev/null
aws cloudfront wait distribution-deployed --profile shop-lab --id $DIST_ID

Rồi alias record (mục 4 bước 6). Test: curl -sI https://assets.shop.$DOMAIN/ | head -1 → HTTP/2 200; openssl s_client -connect assets.shop.$DOMAIN:443 -servername assets.shop.$DOMAIN </dev/null 2>/dev/null | openssl x509 -noout -issuer -dates → issuer Amazon.

Bước 9: Weighted routing 50/50. Tạo distribution thứ hai DIST2 trỏ cùng bucket nhưng DefaultRootObject là index-b.html (upload file có chữ "variant B"); Lưu ý: CloudFront không cho phép một alias (CNAME) xuất hiện ở hai distribution, nên không thể gắn www.shop.$DOMAIN vào cả hai. Weighted demo vì vậy dùng CNAME record thường ở Route 53 trỏ tới hai tên *.cloudfront.net, không cần khai alias trên distribution:

cat > weighted.json <<EOF
{"Changes":[
 {"Action":"UPSERT","ResourceRecordSet":{"Name":"www.shop.$DOMAIN","Type":"CNAME","TTL":30,
   "SetIdentifier":"dist-a","Weight":50,"ResourceRecords":[{"Value":"$DIST_DOMAIN"}]}},
 {"Action":"UPSERT","ResourceRecordSet":{"Name":"www.shop.$DOMAIN","Type":"CNAME","TTL":30,
   "SetIdentifier":"dist-b","Weight":50,"ResourceRecords":[{"Value":"$DIST2_DOMAIN"}]}}
]}
EOF
aws route53 change-resource-record-sets --profile shop-lab --hosted-zone-id $ZONE_ID --change-batch file://weighted.json
for i in $(seq 1 10); do dig +short www.shop.$DOMAIN CNAME @ns-123.awsdns-15.com; done   # xen kẽ 2 tên
aws route53 test-dns-answer --profile shop-lab --hosted-zone-id $ZONE_ID --record-name www.shop.$DOMAIN --record-type CNAME

Lưu ý: vì cert của distribution chỉ phủ tên cloudfront.net (không có alias www), https://www.shop.$DOMAIN sẽ lỗi cert; đây là demo DNS weighted, kiểm tra bằng dig/test-dns-answer. Để chạy HTTPS thật trên weighted bạn dùng 2 ALB (hoặc ALB + ECS ở bài 22.13), mỗi ALB có cert riêng cùng tên.

Console: Route 53 → Hosted zone → Create record → Routing policy Weighted → Weight 50 → Record ID.

Bước 10: Failover + health check (mục 4 bước 7). Không có ALB thì dùng health check kiểu HTTPS tới $DIST_DOMAIN path /index.html làm primary, và một distribution/bucket "maintenance" làm secondary. Để thấy failover: xóa index.html trong bucket primary (health check nhận 403) rồi chờ ~2 phút:

aws route53 get-health-check-status --profile shop-lab --health-check-id $HC_ID \
  --query 'HealthCheckObservations[].StatusReport.Status' | head -5
aws route53 test-dns-answer --profile shop-lab --hosted-zone-id $ZONE_ID --record-name www2.shop.$DOMAIN --record-type A
# RecordData đổi từ tên distribution primary sang maintenance

Console: Route 53 → Health checks → Create; Hosted zone → Create record → Failover → Primary/Secondary → chọn health check.

Kiểm tra kết quả

# 1. Bucket vẫn private, chỉ CloudFront vào được
curl -s -o /dev/null -w "%{http_code}\n" https://$WEB.s3.$AWS_REGION.amazonaws.com/index.html   # 403
curl -s -o /dev/null -w "%{http_code}\n" https://$DIST_DOMAIN/index.html                           # 200

# 2. HTTP → HTTPS redirect, HTTP/2, nén
curl -sI http://$DIST_DOMAIN/ | head -1              # HTTP/1.1 301 Moved Permanently
curl -sI -H 'Accept-Encoding: br' https://$DIST_DOMAIN/ | grep -i content-encoding   # br

# 3. Hit ratio trong CloudWatch (metric CloudFront nằm ở us-east-1)
aws cloudwatch get-metric-statistics --profile shop-lab --region us-east-1 --namespace AWS/CloudFront \
  --metric-name Requests --dimensions Name=DistributionId,Value=$DIST_ID Name=Region,Value=Global \
  --start-time $(date -u -d '-1 hour' +%FT%TZ) --end-time $(date -u +%FT%TZ) --period 300 --statistics Sum \
  --query 'Datapoints[].Sum'

# 4. (Phần B) cert và DNS
dig +short assets.shop.$DOMAIN A | head -3          # IP edge CloudFront (vài IP)
aws acm list-certificates --profile shop-lab --region us-east-1 --query 'CertificateSummaryList[].{D:DomainName,S:Status}'

Thử thêm

Dọn tài nguyên

# 1. Route 53: record (trừ NS/SOA), health check, rồi hosted zone (0.50 USD/tháng nếu quên)
aws route53 list-resource-record-sets --profile shop-lab --hosted-zone-id $ZONE_ID \
  --query "ResourceRecordSets[?Type!='NS' && Type!='SOA']" > rrs.json
# tạo change-batch DELETE từ rrs.json (jq) rồi:
jq '{Changes: [.[] | {Action:"DELETE", ResourceRecordSet: .}]}' rrs.json > del.json
aws route53 change-resource-record-sets --profile shop-lab --hosted-zone-id $ZONE_ID --change-batch file://del.json
aws route53 delete-health-check --profile shop-lab --health-check-id $HC_ID
aws route53 delete-hosted-zone --profile shop-lab --id $ZONE_ID
# Nếu domain mua ngoài: đổi NS về nhà đăng ký (hoặc giữ zone nếu còn dùng)

# 2. CloudFront: disable → chờ deployed → delete (bắt buộc theo thứ tự này)
for D in $DIST_ID $DIST2_ID; do
  ETAG=$(aws cloudfront get-distribution-config --profile shop-lab --id $D --query ETag --output text)
  aws cloudfront get-distribution-config --profile shop-lab --id $D --query DistributionConfig | jq '.Enabled=false' > off.json
  aws cloudfront update-distribution --profile shop-lab --id $D --if-match $ETAG --distribution-config file://off.json > /dev/null
  aws cloudfront wait distribution-deployed --profile shop-lab --id $D
  ETAG=$(aws cloudfront get-distribution --profile shop-lab --id $D --query ETag --output text)
  aws cloudfront delete-distribution --profile shop-lab --id $D --if-match $ETAG
done
OETAG=$(aws cloudfront get-origin-access-control --profile shop-lab --id $OAC_ID --query ETag --output text)
aws cloudfront delete-origin-access-control --profile shop-lab --id $OAC_ID --if-match $OETAG

# 3. ACM (chỉ xóa được khi không còn gắn vào gì)
aws acm delete-certificate --profile shop-lab --region us-east-1 --certificate-arn $CERT_CF
aws acm delete-certificate --profile shop-lab --region ap-southeast-1 --certificate-arn $CERT_ALB 2>/dev/null

# 4. S3
aws s3 rm s3://$WEB --recursive --profile shop-lab
aws s3api delete-bucket --profile shop-lab --bucket $WEB

# 5. ALB listener 443 nếu đã thêm và không còn cần (ALB bản thân vẫn tính giờ; xóa ALB theo bài 22.5)

Thứ tốn tiền nếu quên: hosted zone (0.50 USD/tháng), health check, ALB; CloudFront/S3/ACM gần 0 với lượng lab.

Phỏng vấn và đề thi SAA hỏi về Route 53, CloudFront và ACM thì sao?

Q1. Alias record khác CNAME chỗ nào, vì sao bạn dùng alias cho shop.example.com?

🇻🇳 Tiếng Việt:

CNAME chỉ trỏ tên sang tên khác, không dùng được ở zone apex và bị tính tiền query. Alias là mở rộng của Route 53, trỏ thẳng tới tài nguyên AWS như ALB, CloudFront, S3 website, dùng được ở apex, miễn phí query, tự theo IP khi tài nguyên đổi, và có EvaluateTargetHealth. Em dùng alias A record cho shop.example.com trỏ ALB và cho assets trỏ CloudFront.

🇬🇧 English:

A CNAME only maps one name to another, cannot be used at the zone apex and is billed per query. An alias is a Route 53 extension that points directly at AWS resources such as an ALB, CloudFront or an S3 website; it works at the apex, queries are free, it follows IP changes automatically and supports EvaluateTargetHealth. I use alias A records for shop.example.com to the ALB and for assets to CloudFront.

Q2. Làm sao để bucket S3 chỉ được truy cập qua CloudFront?

🇻🇳 Tiếng Việt:

Bật Block Public Access cho bucket, tạo Origin Access Control cho origin S3 trên distribution, rồi bucket policy chỉ cho principal cloudfront.amazonaws.com với điều kiện AWS:SourceArn bằng ARN distribution. CloudFront ký request bằng SigV4 nên S3 nhận ra, còn truy cập trực tiếp URL S3 sẽ bị 403. OAC thay cho OAI cũ và hỗ trợ SSE-KMS, mọi region.

🇬🇧 English:

Turn on Block Public Access on the bucket, create an Origin Access Control for the S3 origin in the distribution, and set a bucket policy that only allows the cloudfront.amazonaws.com principal with a condition on AWS:SourceArn equal to the distribution ARN. CloudFront signs requests with SigV4 so S3 accepts them, while direct S3 URLs get a 403. OAC replaces the legacy OAI and supports SSE-KMS and all regions.

Q3. Kể các routing policy của Route 53 và tình huống dùng.

🇻🇳 Tiếng Việt:

Simple cho một tài nguyên. Weighted chia theo tỉ lệ, dùng cho canary hoặc chuyển dần. Latency đưa user tới region có độ trễ thấp nhất. Failover active-passive với health check, dùng cho DR và trang bảo trì. Geolocation theo quốc gia của user, cho nội dung địa phương hoặc tuân thủ. Geoproximity theo khoảng cách tới tài nguyên và có bias để kéo đẩy traffic. Multivalue trả nhiều IP healthy như cân bằng tải DNS đơn giản. Với shop-app em dùng failover cho DR và weighted khi chuyển từ EC2 sang ECS.

🇬🇧 English:

Simple for a single resource. Weighted splits traffic by ratio, for canary releases or gradual migration. Latency sends users to the region with the lowest measured latency. Failover is active-passive with health checks, for DR or a maintenance page. Geolocation routes by the user's country, for localized content or compliance. Geoproximity routes by distance to resources with a bias to shift traffic. Multivalue returns several healthy IPs as simple DNS load balancing. In shop-app I use failover for DR and weighted when migrating from EC2 to ECS.

Q4. Cert cho CloudFront và ALB xin ở đâu, gia hạn thế nào?

🇻🇳 Tiếng Việt:

Dùng ACM, miễn phí. Cert cho ALB phải ở cùng region với ALB; cert cho CloudFront bắt buộc ở us-east-1 vì CloudFront là dịch vụ global. Validate bằng DNS, với Route 53 thì một nút tạo CNAME, và chỉ cần giữ CNAME đó ACM sẽ tự gia hạn mãi. Private key không rời khỏi AWS, không cài lên EC2.

🇬🇧 English:

Use ACM, which is free. The ALB certificate must be in the same region as the ALB; the CloudFront certificate must be in us-east-1 because CloudFront is a global service. Validate via DNS, which is one click with Route 53 to create the CNAME, and as long as that CNAME stays, ACM renews automatically forever. The private key never leaves AWS and is never installed on EC2.

Góc thi SAA-C03

Câu 1: Một công ty host website tĩnh trong S3 và phục vụ qua CloudFront. Kiểm toán phát hiện người dùng có thể tải file trực tiếp từ URL S3, bỏ qua CloudFront. Giải pháp nào bảo đảm chỉ CloudFront truy cập được bucket với LEAST operational overhead?
A. Cấu hình bucket policy chỉ cho phép dải IP của CloudFront edge.
B. Tạo Origin Access Control (OAC), gắn vào origin S3 và cập nhật bucket policy chỉ cho phép distribution đó; bật Block Public Access.
C. Bật S3 website hosting và dùng header Referer trong bucket policy.
D. Chuyển bucket vào VPC và dùng VPC endpoint.

Đáp án: B. OAC là cách chuẩn: CloudFront ký request, bucket policy kiểm tra SourceArn. A: dải IP CloudFront thay đổi, phải cập nhật liên tục (overhead cao) và không chặn người khác dùng CloudFront. C: Referer giả được, không bảo mật. D: S3 không nằm trong VPC, và CloudFront cũng không đi qua VPC endpoint; sai kiến trúc.

Câu 2: Một ứng dụng chạy ở hai region, active-passive. Khi region chính gặp sự cố, traffic phải tự chuyển sang region phụ. Ứng dụng dùng Route 53. Cấu hình nào đáp ứng?
A. Weighted routing với weight 100 cho region chính và 0 cho region phụ.
B. Latency routing với health check trên cả hai region.
C. Failover routing: record PRIMARY trỏ region chính kèm health check, record SECONDARY trỏ region phụ.
D. Geolocation routing với record mặc định trỏ region phụ.

Đáp án: C. "Active-passive", "automatically fail over" → Failover policy với health check ở primary. A weight 0 không tự đổi khi sự cố (phải sửa tay). B latency là active-active, chia traffic theo độ trễ, không phải passive. D geolocation chọn theo vị trí user, không theo trạng thái healthy.

Câu 3: Một công ty game cần cung cấp cho khách hàng doanh nghiệp địa chỉ IP tĩnh để whitelist, giao thức UDP, server ở nhiều region, và failover giữa các region trong vài giây mà không phụ thuộc DNS cache. Chọn gì?
A. Amazon CloudFront với nhiều origin.
B. Route 53 latency routing với health check.
C. AWS Global Accelerator với endpoint group ở mỗi region.
D. Network Load Balancer với Elastic IP ở mỗi region.

Đáp án: C. "Static IP", "UDP", "fast deterministic failover without DNS caching" là ba dấu hiệu của Global Accelerator. A CloudFront chỉ HTTP/HTTPS, IP không tĩnh. B phụ thuộc DNS TTL và cache, failover tính bằng phút. D NLB có Elastic IP nhưng chỉ trong một region; chuyển region vẫn cần DNS.

Cách nhớ ngắn gọn

Từ khóa trong đềNghĩ tới
zone apex, point domain root to ALB/CloudFrontAlias record
active-passive, automatic failover, maintenance pageRoute 53 Failover + health check
gradually shift traffic, canary, blue/green at DNSWeighted
lowest latency for global usersLatency routing (hoặc CloudFront/Global Accelerator tùy câu)
route by user country, compliance, localized contentGeolocation
shift traffic between regions by adjusting biasGeoproximity (Traffic Flow)
restrict S3 to CloudFront onlyOAC + bucket policy
custom domain HTTPS on CloudFrontACM cert in us-east-1
static IP, UDP/TCP, deterministic regional failoverGlobal Accelerator
serve private content to paying usersCloudFront signed URL / cookie
internal DNS names inside VPCPrivate hosted zone

Tự kiểm tra

Vì sao không trỏ A record vào IP của ALB?

IP của ALB thay đổi theo thời gian và theo AZ. Dùng alias record trỏ tên DNS của ALB; Route 53 tự trả IP hiện tại.

Cert ACM cho CloudFront phải ở region nào, cho ALB thì sao?

CloudFront: bắt buộc us-east-1. ALB: cùng region với ALB (ở shop-app là ap-southeast-1).

OAC là gì và bucket policy cần điều kiện nào?

Origin Access Control: CloudFront ký request SigV4 tới S3. Bucket policy cho Principal: cloudfront.amazonaws.com, action s3:GetObject, điều kiện AWS:SourceArn = ARN distribution; bucket giữ Block Public Access.

Đổi ảnh cùng tên mà khách vẫn thấy ảnh cũ: cách xử lý lâu dài là gì?

Đặt tên file có hash nội dung và Cache-Control: immutable dài; đổi ảnh = đổi URL. Invalidation chỉ dùng khi cần gỡ gấp và không xóa cache trình duyệt.

Latency, Geolocation và Geoproximity routing khác nhau thế nào?

Latency theo độ trễ mạng đo được; Geolocation theo vị trí hành chính (quốc gia/lục địa) của user; Geoproximity theo khoảng cách địa lý tới tài nguyên, có bias để mở rộng/thu hẹp vùng.