ELB & Auto Scaling: shop-app không còn phụ thuộc một EC2
Tiếp đi: Load Balancer và Auto Scaling. Bài trước mình đã có một EC2 t3.micro chạy shop-app.jar trong shop-public-a. Hôm qua mình chạy sudo reboot để thử, website chết đúng 2 phút và không ai biết. Giờ muốn chạy ít nhất 2 máy và có gì đó tự thay máy hỏng.
Đây chính là bước chuyển từ "một server" sang "một nhóm server" và là trọng tâm của Domain 2 – Design Resilient Architectures trong đề SAA. Hai mảnh ghép: Elastic Load Balancing (ELB) chia request cho nhiều instance, và Auto Scaling Group (ASG) giữ số instance đúng mong muốn, tự thay máy hỏng, tự tăng giảm theo tải.
1. Bài toán: shop-app chạy trên một EC2
Sau bài 22.4 EC2, kiến trúc đang là:
Internet
|
Elastic IP 13.x.x.x ──→ EC2 shop-app (t3.micro, shop-public-a)
└─ shop-app.jar :8080
└─ /actuator/health
Người dùng gõ thẳng IP (hoặc DNS trỏ vào IP). Mọi thứ phụ thuộc đúng một máy trong đúng một Availability Zone.
2. Vấn đề
- Single point of failure: reboot, lỗi hardware, hay AZ
ap-southeast-1acó sự cố là shop-app mất hoàn toàn. - Không ai thay máy: EC2 chết lúc 2 giờ sáng, tới sáng mới biết. Không có "control loop" nào canh số instance.
- Deploy có downtime: muốn cập nhật jar phải dừng app ngay trên máy đang phục vụ.
- Không scale: flash sale tải tăng 5 lần, một
t3.microkhông gánh được; mua máy to hơn (vertical) thì ngày thường lại thừa. - Lộ EC2 ra Internet: instance có public IP, mở port 8080 cho cả thế giới, TLS phải tự cấu hình trên app.
3. Giải pháp: ALB phía trước + ASG phía sau
Internet
|
┌────────▼────────┐
│ shop-alb │ Application Load Balancer (public, 2 AZ)
│ listener :80/443│ SG: shop-alb-sg (80/443 từ 0.0.0.0/0)
└────────┬────────┘
│ forward
┌────────▼────────┐
│ shop-tg │ Target group: HTTP:8080, health /actuator/health
└───┬─────────┬───┘
│ │
┌─────────▼──┐ ┌───▼────────┐
│ EC2 (1a) │ │ EC2 (1b) │ shop-asg: min 2, desired 2, max 4
│ private-a │ │ private-b │ launch template shop-lt
│ shop-app-sg│ │ shop-app-sg│ (8080 chỉ từ shop-alb-sg)
└────────────┘ └────────────┘
Các thành phần và thuật ngữ đề thi cần nắm chắc:
| Thành phần | Vai trò | Từ khóa đề thi |
|---|---|---|
| Load balancer | Điểm vào duy nhất có DNS name (shop-alb-123.ap-southeast-1.elb.amazonaws.com), trải trên nhiều AZ | internet-facing vs internal, cross-zone load balancing |
| Listener | Cổng + giao thức LB lắng nghe (HTTP:80, HTTPS:443). Mỗi listener có rules | listener rule, host/path-based routing, redirect 80→443 |
| Target group | Nhóm đích (instance / IP / Lambda / ALB) + health check | target type, deregistration delay (connection draining), slow start |
| Health check | LB gọi /actuator/health định kỳ; fail N lần thì ngừng gửi traffic | healthy/unhealthy threshold, interval, matcher 200 |
| Launch template | "Bản thiết kế" instance: AMI, type, SG, user data, IAM role | launch template (mới) vs launch configuration (deprecated) |
| Auto Scaling Group | Giữ desired instance trong khoảng [min, max], trải nhiều subnet/AZ | min/max/desired, AZ rebalancing, lifecycle hook, instance refresh |
| Scaling policy | Luật tăng/giảm desired | target tracking, step, simple, scheduled, predictive |
Ba loại ELB hiện còn trong đề (Classic Load Balancer đã là legacy, không nên chọn):
| ALB | NLB | GWLB | |
|---|---|---|---|
| Layer | 7 (HTTP/HTTPS/gRPC) | 4 (TCP/UDP/TLS) | 3 (IP packet, GENEVE 6081) |
| Routing | Path, host, header, query string, method | Theo port/protocol; giữ nguyên IP client | Chuyển mọi gói sang appliance (firewall, IDS) |
| Static IP | Không (DNS name, IP đổi) → đặt sau Global Accelerator nếu cần | Có, 1 static IP / Elastic IP mỗi AZ | Không |
| Hiệu năng | Ms-level, đủ cho web/API | Hàng triệu req/s, latency rất thấp | Transparent |
| Target | Instance, IP, Lambda, ALB không | Instance, IP, ALB | Instance, IP |
| TLS | Terminate, ACM cert, SNI nhiều cert | TLS listener terminate hoặc pass-through | Không |
| Dùng cho shop-app | REST API, web, redirect, WAF | Game/IoT TCP, cần IP cố định whitelist, PrivateLink | Tích hợp firewall bên thứ ba |
Những tính năng ALB mà đề thi và dự án thật hay cần (đều cấu hình trên listener hoặc target group, không đụng vào code):
| Tính năng | Ý nghĩa với shop-app | Ghi chú |
|---|---|---|
| Listener rule theo path/host/header/query | Một ALB phục vụ /api/* → shop-tg, /admin/* → admin-tg, static.shop.example.com → fixed response hoặc redirect sang CloudFront | Tối đa 100 rule/listener mặc định; rule có priority, default rule cuối cùng |
| Weighted target groups | Rule forward 90% vào shop-tg-blue, 10% vào shop-tg-green → canary / blue-green | Có thể bật stickiness theo target group để user không nhảy giữa 2 version |
| Redirect / fixed-response | Listener 80 redirect 301 sang 443; trả 503 "maintenance" không cần backend | Không tốn target |
| Authenticate-OIDC / Cognito | ALB tự làm login với Cognito hoặc Keycloak (OIDC) trước khi forward, gửi claim qua header x-amzn-oidc-data | Hữu ích cho trang admin nội bộ; app public vẫn nên tự validate JWT (Resource Server) |
| Cross-zone load balancing | Node ALB ở AZ 1a có thể gửi tới target ở 1b → tải đều dù số instance mỗi AZ lệch | ALB: luôn bật, miễn phí. NLB: tắt mặc định, bật thì tính phí data liên AZ |
| Slow start | Target mới nhận tải tăng dần trong 30–900s, cho JVM warm-up (JIT, pool) | Rất hợp với Spring Boot; đặt 60–120s |
| Access log → S3 | Mỗi request: client IP, latency từng chặng, target, status | Dùng Athena query khi điều tra 5xx (bài 22.17) |
| Idle timeout | Mặc định 60s; app có long-polling/SSE cần tăng | Phải nhỏ hơn keep-alive timeout của Tomcat để tránh 502 lẻ tẻ |
| HTTP/2, gRPC, mTLS | ALB nói HTTP/2 với client, HTTP/1.1 với target (hoặc gRPC end-to-end) | mTLS listener xác thực client cert ở ALB |
| Lambda target | Một path trong cùng ALB trỏ sang Lambda | Cách trộn EC2 và serverless không cần API Gateway |
Ví dụ hai rule hay dùng ngay cho shop-app:
# Redirect 80 → 443 (sau khi có cert ở bài 22.10)
aws elbv2 modify-listener --listener-arn $LISTENER_80_ARN \
--default-actions 'Type=redirect,RedirectConfig={Protocol=HTTPS,Port=443,StatusCode=HTTP_301}' --profile shop-lab
# Canary: 90/10 giữa hai target group trên listener 443
aws elbv2 create-rule --listener-arn $LISTENER_443_ARN --priority 10 \
--conditions Field=path-pattern,Values='/api/*' \
--actions 'Type=forward,ForwardConfig={TargetGroups=[{TargetGroupArn='$TG_BLUE',Weight=90},{TargetGroupArn='$TG_GREEN',Weight=10}],TargetGroupStickinessConfig={Enabled=true,DurationSeconds=600}}' \
--profile shop-lab
4. Cấu hình từng bước (CLI + Java)
Bước 1: Security group. Nguyên tắc chain: Internet → shop-alb-sg → shop-app-sg. Instance không nhận request trực tiếp từ Internet nữa.
export AWS_REGION=ap-southeast-1
export ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text --profile shop-lab)
VPC_ID=$(aws ec2 describe-vpcs --filters Name=tag:Name,Values=shop-vpc \
--query 'Vpcs[0].VpcId' --output text --profile shop-lab)
ALB_SG=$(aws ec2 create-security-group --group-name shop-alb-sg \
--description "ALB 80/443 from internet" --vpc-id $VPC_ID \
--tag-specifications 'ResourceType=security-group,Tags=[{Key=Name,Value=shop-alb-sg},{Key=Project,Value=shop-app},{Key=Env,Value=lab}]' \
--query GroupId --output text --profile shop-lab)
aws ec2 authorize-security-group-ingress --group-id $ALB_SG \
--protocol tcp --port 80 --cidr 0.0.0.0/0 --profile shop-lab
# shop-app-sg đã có từ bài 22.4: chỉ cho 8080 từ ALB SG, bỏ rule 8080 từ 0.0.0.0/0 nếu còn
APP_SG=$(aws ec2 describe-security-groups --filters Name=group-name,Values=shop-app-sg \
--query 'SecurityGroups[0].GroupId' --output text --profile shop-lab)
aws ec2 authorize-security-group-ingress --group-id $APP_SG \
--protocol tcp --port 8080 --source-group $ALB_SG --profile shop-lab
Bước 2: Target group. Health check trỏ vào Actuator (xem Actuator).
TG_ARN=$(aws elbv2 create-target-group --name shop-tg \
--protocol HTTP --port 8080 --vpc-id $VPC_ID --target-type instance \
--health-check-protocol HTTP --health-check-path /actuator/health \
--health-check-interval-seconds 15 --health-check-timeout-seconds 5 \
--healthy-threshold-count 2 --unhealthy-threshold-count 3 \
--matcher HttpCode=200 \
--tags Key=Project,Value=shop-app Key=Env,Value=lab \
--query 'TargetGroups[0].TargetGroupArn' --output text --profile shop-lab)
# Deregistration delay: thời gian chờ request đang chạy xong trước khi gỡ target (mặc định 300s)
aws elbv2 modify-target-group-attributes --target-group-arn $TG_ARN \
--attributes Key=deregistration_delay.timeout_seconds,Value=30 --profile shop-lab
Bước 3: ALB + listener. ALB bắt buộc ít nhất 2 subnet ở 2 AZ khác nhau, và phải là public subnet nếu internet-facing.
PUB_A=$(aws ec2 describe-subnets --filters Name=tag:Name,Values=shop-public-a --query 'Subnets[0].SubnetId' --output text --profile shop-lab)
PUB_B=$(aws ec2 describe-subnets --filters Name=tag:Name,Values=shop-public-b --query 'Subnets[0].SubnetId' --output text --profile shop-lab)
ALB_ARN=$(aws elbv2 create-load-balancer --name shop-alb \
--type application --scheme internet-facing --ip-address-type ipv4 \
--subnets $PUB_A $PUB_B --security-groups $ALB_SG \
--tags Key=Project,Value=shop-app Key=Env,Value=lab \
--query 'LoadBalancers[0].LoadBalancerArn' --output text --profile shop-lab)
aws elbv2 create-listener --load-balancer-arn $ALB_ARN \
--protocol HTTP --port 80 \
--default-actions Type=forward,TargetGroupArn=$TG_ARN --profile shop-lab
Khi có domain và ACM cert (bài 22.10), thêm listener 443 và đổi listener 80 thành Type=redirect sang HTTPS.
Bước 4: Launch template. Tận dụng user data từ bài 22.4; app phải lấy jar và chạy được không cần người gõ lệnh, vì ASG sẽ tạo instance lúc không ai ngồi đó.
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 <<'UNIT'
[Unit]
Description=shop-app
After=network.target
[Service]
Environment=SPRING_PROFILES_ACTIVE=aws
ExecStart=/usr/bin/java -jar /opt/shop-app.jar
Restart=always
[Install]
WantedBy=multi-user.target
UNIT
systemctl enable --now shop-app
EOF
sed -i "s/ACCOUNT_ID/$ACCOUNT_ID/" user-data.sh
AMI_ID=$(aws ssm get-parameter --name /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64 \
--query Parameter.Value --output text --profile shop-lab)
aws ec2 create-launch-template --launch-template-name shop-lt \
--tag-specifications 'ResourceType=launch-template,Tags=[{Key=Project,Value=shop-app},{Key=Env,Value=lab}]' \
--launch-template-data "{
\"ImageId\": \"$AMI_ID\",
\"InstanceType\": \"t3.micro\",
\"IamInstanceProfile\": {\"Name\": \"shop-app-ec2-role\"},
\"SecurityGroupIds\": [\"$APP_SG\"],
\"UserData\": \"$(base64 -w0 user-data.sh)\",
\"TagSpecifications\": [{\"ResourceType\": \"instance\",
\"Tags\": [{\"Key\":\"Name\",\"Value\":\"shop-app\"},{\"Key\":\"Project\",\"Value\":\"shop-app\"},{\"Key\":\"Env\",\"Value\":\"lab\"}]}]
}" --profile shop-lab
Không có AssociatePublicIpAddress: instance nằm trong private subnet, ra Internet qua NAT (bài 22.3). Role shop-app-ec2-role cần quyền đọc bucket để tải jar.
Bước 5: Auto Scaling Group. Chú ý --health-check-type ELB: ASG dùng kết quả health check của target group để quyết định thay instance, thay vì chỉ nhìn EC2 status check.
PRIV_A=$(aws ec2 describe-subnets --filters Name=tag:Name,Values=shop-private-a --query 'Subnets[0].SubnetId' --output text --profile shop-lab)
PRIV_B=$(aws ec2 describe-subnets --filters Name=tag:Name,Values=shop-private-b --query 'Subnets[0].SubnetId' --output text --profile shop-lab)
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 120 \
--tags Key=Project,Value=shop-app,PropagateAtLaunch=true Key=Env,Value=lab,PropagateAtLaunch=true \
--profile shop-lab
Bước 6: Scaling policy (target tracking). "Giữ CPU trung bình quanh 50%". ASG tự tạo 2 CloudWatch alarm (cao/thấp).
aws autoscaling put-scaling-policy --auto-scaling-group-name shop-asg \
--policy-name shop-cpu-50 --policy-type TargetTrackingScaling \
--target-tracking-configuration '{
"PredefinedMetricSpecification": {"PredefinedMetricType": "ASGAverageCPUUtilization"},
"TargetValue": 50.0
}' \
--estimated-instance-warmup 120 --profile shop-lab
Với app web, metric ALBRequestCountPerTarget (số request/instance) thường phản ánh tải đúng hơn CPU, đặc biệt khi app chờ DB/IO nhiều.
Bước 7: Spring Boot phía sau ALB. Ba thứ hay quên:
# application-aws.yml
server:
port: 8080
forward-headers-strategy: framework # tin X-Forwarded-For / X-Forwarded-Proto từ ALB
shutdown: graceful # chờ request đang xử lý khi SIGTERM
spring:
lifecycle:
timeout-per-shutdown-phase: 25s # < deregistration delay 30s
management:
endpoint:
health:
probes:
enabled: true
group:
readiness:
include: readinessState, db # ALB nên dùng /actuator/health/readiness
endpoints:
web:
exposure:
include: health,info,prometheus
forward-headers-strategy: không có nó,request.getRemoteAddr()là IP của ALB,redirectsinh URLhttp://dù user vàohttps://.- Graceful shutdown + deregistration delay: ALB ngừng gửi request mới, app xử lý nốt request cũ, rồi mới tắt. Không có cặp này thì mỗi lần scale-in là một đợt lỗi 502.
- Health check phải nhẹ và nói thật: trả 200 chỉ khi app thực sự sẵn sàng (DB pool kết nối được). Health check gọi API nặng là tự DDoS mình.
5. Cách hoạt động
Một request đi qua ALB:
Client ──DNS──→ shop-alb-xxx.elb.amazonaws.com (trả 2 IP, mỗi AZ 1 node ALB)
│
▼
ALB node (AZ 1a) ── listener :80 rule: default forward shop-tg
│ cross-zone LB (ALB mặc định bật): chọn target ở 1a hoặc 1b
│ thêm header X-Forwarded-For: <client ip>, X-Forwarded-Proto: http
▼
EC2 10.0.12.23:8080 (private-b) ── Spring MVC xử lý ── 200 OK
│
▼
ALB trả về client, ghi access log (nếu bật) vào S3
Vòng lặp của ASG (control loop, giống ReplicaSet trong Kubernetes):
mỗi ~phút:
actual = số instance InService
if actual < desired: launch (chọn AZ ít instance hơn → cân bằng AZ)
if actual > desired: terminate theo termination policy
(mặc định: AZ nhiều nhất → launch template cũ → gần giờ tính tiền)
với mỗi instance: nếu ELB báo unhealthy quá grace period → terminate + launch mới
scaling policy:
CloudWatch alarm CPU > 50% (3 datapoint) → desired += N (scale out)
CloudWatch alarm CPU < 50% - margin, lâu → desired -= 1 (scale in, thận trọng)
warm-up 120s: instance mới chưa tính vào metric → tránh scale out dồn dập
Thứ tự thời gian khi một instance chết:
t=0 app crash (OOM) → /actuator/health timeout
t=15s health check fail #1
t=30s fail #2
t=45s fail #3 → target Unhealthy → ALB ngừng gửi request (user không còn thấy lỗi)
t=~60s ASG thấy ELB unhealthy → đánh dấu Unhealthy → terminate
t=~90s launch instance mới, user data chạy (cài Java, tải jar) ≈ 60–90s
t=~3m instance mới Healthy → desired 2 lại đủ 2
Trong 3 phút đó, instance còn lại gánh 100% tải. Đó là lý do min 2 và trải 2 AZ là tối thiểu cho production; đề thi dùng cụm từ "highly available" là ám chỉ điểm này.
Metric CloudWatch cần nhìn (namespace AWS/ApplicationELB, dimension LoadBalancer và TargetGroup). Đây cũng là nền cho alarm shop-alb-5xx-high ở bài 22.15:
| Metric | Ý nghĩa | Khi nào lo |
|---|---|---|
HealthyHostCount / UnHealthyHostCount | Số target healthy theo từng AZ | Healthy < 2 → mất HA; alarm ngay |
HTTPCode_Target_5XX_Count | 5xx do app trả | Bug, DB lỗi; đọc log app |
HTTPCode_ELB_5XX_Count | 5xx do ALB tự sinh (502/503/504) | 502: app đóng kết nối sớm / keep-alive lệch; 503: không còn target healthy; 504: target quá idle timeout |
TargetResponseTime (p99) | Thời gian từ ALB gửi tới nhận byte đầu | Tăng dần theo RequestCount → thiếu instance hoặc DB pool đầy |
RequestCountPerTarget | Request trung bình mỗi instance | Metric tốt cho target tracking của app web |
ActiveConnectionCount, RejectedConnectionCount | Kết nối đang mở / bị từ chối vì target hết chỗ | Rejected > 0 → tăng max của ASG hoặc Tomcat max-connections |
GroupInServiceInstances (AWS/AutoScaling) | Số instance ASG đang phục vụ | Phải bật metric collection của ASG (enable-metrics-collection) |
Một quy tắc đọc nhanh: Target_5XX là lỗi của dev Java, ELB_5XX là lỗi của hạ tầng/cấu hình (SG, timeout, không còn target). Phỏng vấn hỏi "502 lẻ tẻ sau ALB thì debug sao" là đang kiểm tra bạn có phân biệt được hai metric này.
6. So sánh
Các loại scaling policy — đề SAA rất hay hỏi "chọn policy nào":
| Policy | Cách hoạt động | Chọn khi đề nói |
|---|---|---|
| Target tracking | Đặt mục tiêu (CPU 50%, 1000 req/target), AWS tự tính thêm/bớt bao nhiêu | "simplest", "least operational overhead", tải biến đổi tự nhiên |
| Step scaling | Nhiều bậc: CPU 60–70% +1, 70–85% +2, >85% +4 | Cần kiểm soát tinh theo mức alarm |
| Simple scaling | Một alarm → một hành động, có cooldown chờ xong mới xét tiếp | Hiếm khi là đáp án đúng (cũ) |
| Scheduled | Đặt desired theo lịch (8h sáng lên 4, 22h xuống 2) | "predictable traffic", "every Monday 9 AM", "known schedule" |
| Predictive | ML học lịch sử 14 ngày, scale trước khi tải đến | "recurring pattern", "proactively", cần cả scheduled-like và adaptive |
Health check: EC2 vs ELB
| EC2 status check | ELB health check | |
|---|---|---|
| Kiểm tra | Hypervisor/network của instance | HTTP tới /actuator/health của app |
| Phát hiện | Máy chết, mất mạng | App treo, OOM, DB mất kết nối mà máy vẫn sống |
| ASG dùng | Mặc định | Phải bật --health-check-type ELB |
Scale out vs scale up, ASG vs K8s HPA
| Vertical (scale up) | Horizontal (ASG) | K8s HPA + Cluster Autoscaler | |
|---|---|---|---|
| Đơn vị | Đổi instance type | Thêm/bớt EC2 | Thêm/bớt Pod; node do CA/Karpenter |
| Downtime | Phải stop/start | Không | Không |
| Thời gian phản ứng | Phút + thủ công | 2–4 phút (boot + user data) | Giây (image đã pull) + phút nếu cần node |
| Yêu cầu app | Không | Stateless, session ngoài (Redis/DynamoDB) | Stateless, container |
7. Khi nào dùng / không
- ALB + ASG: web/API stateless cần HA và chịu tải thay đổi; muốn deploy không downtime bằng instance refresh (thay dần instance theo launch template version mới) hoặc blue/green đổi target group. Xem thêm CD zero downtime.
- NLB: TCP/UDP thuần, cần IP tĩnh để khách hàng whitelist, latency cực thấp, hoặc làm đầu vào cho PrivateLink.
- Không cần ASG: workload stateful một node (DB tự dựng, Jenkins master) — nhưng vẫn có thể dùng ASG min=max=1 chỉ để tự thay máy hỏng; đây là mẹo đề thi hay hỏi ("self-healing single instance").
- Không dùng ALB khi chạy Lambda + API Gateway (gateway đã làm việc đó), hoặc chỉ là job batch không nhận request.
- Lifecycle hook khi cần "làm gì đó" trước khi instance vào/ra:
autoscaling:EC2_INSTANCE_LAUNCHING(chờ warm cache, đăng ký vào hệ thống khác) vàEC2_INSTANCE_TERMINATING(đẩy log còn lại lên S3, hoàn tất job đang chạy). Instance đứng ở trạng tháiPending:Wait/Terminating:Waitcho tới khi bạn gọicomplete-lifecycle-actionhoặc hết heartbeat timeout (mặc định 1 giờ). Đề thi: "must upload logs before instance is terminated" → lifecycle hook, không phải cron.
Mình chạy 2 instance xong thì user cứ bị đăng xuất ngẫu nhiên. Mình thấy ALB có nút stickiness, bật lên là xong đúng không?
Bật stickiness thì triệu chứng biến mất, nhưng bạn đang che bệnh chứ chưa chữa. Hãy phân biệt hai cách:
Sticky session (ALB stickiness): ALB gắn cookie AWSALB (duration-based) hoặc dùng cookie của app (app-based, ví dụ JSESSIONID) để ghim user vào một target.
aws elbv2 modify-target-group-attributes --target-group-arn $TG_ARN --attributes \
Key=stickiness.enabled,Value=true \
Key=stickiness.type,Value=lb_cookie \
Key=stickiness.lb_cookie.duration_seconds,Value=3600 --profile shop-lab
Hệ quả:
user A,B,C,D,E ghim vào EC2-1 (ngẫu nhiên lúc đầu)
user F ghim vào EC2-2
→ tải lệch: EC2-1 90% CPU, EC2-2 nhàn rỗi → ASG scale out thêm EC2-3 nhưng user cũ vẫn dính EC2-1
→ EC2-1 bị ASG thay (unhealthy) → A..E mất session cùng lúc
Cách đúng: externalize session để instance nào cũng trả lời được. Trên AWS có hai lựa chọn hay gặp:
| ElastiCache Redis + Spring Session | DynamoDB shop-sessions | JWT stateless | |
|---|---|---|---|
| Thay đổi code | Thêm spring-session-data-redis, gần như không sửa code | Spring Session chưa có store chính thức; tự viết hoặc dùng lib cộng đồng | Đổi mô hình auth |
| Latency | Sub-ms trong VPC | Vài ms | 0 (không gọi store) |
| Chi phí vận hành | Cluster Redis (không Free Tier) | Serverless, pay per request, TTL tự xóa | Không có store; revoke khó |
| Bài liên quan | 22.9, Redis cache | 22.8 | JWT |
Với shop-app, bài 22.9 sẽ bật Spring Session Redis. Còn bây giờ, bật stickiness tạm để lab chạy được là chấp nhận được, miễn bạn hiểu nó không phải đích đến. Trong đề SAA, khi câu hỏi nói "users are randomly logged out after adding instances", đáp án đúng gần như luôn là ElastiCache/DynamoDB để lưu session, không phải sticky session; sticky session chỉ đúng khi đề nói rõ "legacy application cannot be modified".
Hai thắc mắc nữa. Một là hóa đơn tháng này có dòng "LoadBalancerUsage" dù mình gần như không có traffic. Hai là ASG của mình cứ tạo instance mới rồi khoảng 2 phút sau terminate, lặp đi lặp lại, log không thấy gì.
Cả hai đều rất hay gặp.
1. ALB tính tiền theo giờ, không phải theo traffic. Mỗi ALB tính phí giờ tồn tại (khoảng vài chục USD/tháng nếu để cả tháng, kiểm tra pricing page của region) cộng với LCU (Load Balancer Capacity Unit) theo số kết nối/băng thông/rule. Free Tier chỉ cho 750 giờ ALB trong 12 tháng đầu của tài khoản mới; hết thời hạn hoặc tạo 2 ALB là trả tiền ngay dù 0 request. Vì vậy trong lab luôn xóa ALB khi xong, và trong dự án thật, nhiều service nên dùng chung một ALB với listener rule theo host/path thay vì mỗi service một ALB.
shop-alb :443
├─ rule host api.shop.example.com, path /orders/* → shop-tg
├─ rule host api.shop.example.com, path /payments/* → payment-tg
└─ default → web-tg
2. Vòng lặp "launch → terminate" = health check fail trong grace period quá ngắn. Diễn biến:
launch → instance Pending → InService (ASG tính giờ grace period 120s)
user data: dnf install java (40s) + tải jar (10s) + Spring Boot start (30–40s) + Hibernate/Flyway (20s)
→ tổng ~110–130s, có khi quá 120s
→ ALB health check vẫn fail khi hết grace → ASG: Unhealthy → terminate → launch lại → ...
Cách tìm nguyên nhân (đây là thứ bạn nên nhớ hơn cả cách sửa):
# Lịch sử ASG: lý do terminate
aws autoscaling describe-scaling-activities --auto-scaling-group-name shop-asg \
--max-items 5 --query 'Activities[].[StartTime,StatusCode,Cause]' --output table --profile shop-lab
# → "...ELB health check failed..." hoặc "...taking EC2 instance out of service in response to ELB system health check failure"
# Trạng thái từng target và lý do
aws elbv2 describe-target-health --target-group-arn $TG_ARN --profile shop-lab
# "Reason": "Target.ResponseCodeMismatch" → app trả 503 (DB chưa kết nối) hay 401 (Actuator bị Security chặn!)
# "Reason": "Target.Timeout" → SG không cho ALB vào 8080, hoặc app chưa lên
Sửa theo nguyên nhân:
- Tăng
--health-check-grace-periodlên 300s, hoặc tốt hơn: bake AMI đã cài Java và jar (bài 22.4) để user data chỉ cònsystemctl start; boot 30s thay vì 2 phút, và scale out cũng nhanh hơn. /actuator/healthbị Spring Security trả 401: thêm.requestMatchers("/actuator/health/**").permitAll().- Matcher: nếu health trả 503 lúc DB down là đúng ý (không muốn nhận traffic), nhưng ASG sẽ thay instance dù lỗi nằm ở DB, thay mãi không hết. Khi đó dùng
/actuator/health/livenesscho ASG và/readinesscho ALB không làm được (ALB chỉ có một health check mỗi target group), nên thực tế team thường: ALB check readiness; ASG grace period dài; alarm riêng cho DB.
Cho mình bài lab để tự tay làm phần này nhé.
Lab 22.5: shop-alb + shop-asg min 2 max 4 trải 2 AZ, tự thay instance hỏng
Mục tiêu
- Có DNS của
shop-albtrả JSON{"status":"UP"}từ/actuator/health, request luân phiên rơi vào 2 instance ở 2 AZ. shop-asggiữ đúng 2 instance, mỗi private subnet một máy; terminate tay một máy thì trong vòng 3–5 phút có máy thay.- Target tracking CPU 50% hoạt động: tạo tải giả làm desired tăng lên 3–4 và tự về 2 khi hết tải.
- Tất cả tài nguyên gắn tag
Project=shop-app,Env=labvà được xóa hết ở cuối.
Chi phí & thời gian
- ALB: không miễn phí ngoài 750 giờ Free Tier 12 tháng đầu. Ước tính khoảng 0.03 USD/giờ + LCU (ở lab gần 0). Để 2 giờ ≈ 0.06 USD.
- EC2
t3.micro× 2–4: trong Free Tier 750 giờ/tháng (chỉ cộng dồn 1 instance); 2 máy chạy 2 giờ là 4 instance-giờ, thường vẫn trong hạn. Nếu đã hết Free Tier, khoảng 0.013 USD/giờ/máy. - NAT Gateway từ bài 22.3 nếu còn bật: khoảng 0.06 USD/giờ + data, là khoản đắt nhất trong lab này. Cân nhắc tắt NAT và cho launch template tạm dùng public subnet nếu muốn tiết kiệm (xem Thử thêm).
- Thời gian: 60–90 phút. Luôn kiểm tra pricing page của
ap-southeast-1.
Chuẩn bị
- Lab 22.3:
shop-vpc, 4 subnet, IGW, NAT Gateway, route table đúng. Lab 22.4: roleshop-app-ec2-role(instance profile cùng tên),shop-app-sg, fileshop-app.jarđã upload lêns3://shop-app-assets-$ACCOUNT_ID/releases/(nếu chưa có bucket, tạo nhanh:aws s3 mb s3://shop-app-assets-$ACCOUNT_ID --profile shop-lab; bài 22.6 sẽ hoàn thiện). - Role
shop-app-ec2-rolecó policys3:GetObjecttrên bucket đó vàAmazonSSMManagedInstanceCoređể vào máy bằng Session Manager (không mở SSH). - Shell có
awsCLI v2, biếnAWS_REGION,ACCOUNT_IDnhư bước 1 ở trên.
Các bước
Bước 1 – SG, target group, ALB, listener: chạy đúng các lệnh ở mục 4 (Bước 1–3). Lưu lại biến ALB_ARN, TG_ARN, APP_SG, ALB_SG.
Console: EC2 → Load Balancers → Create → Application Load Balancer → chọn VPC shop-vpc, 2 public subnet, SG shop-alb-sg → Listener HTTP:80 → Create target group (instance, HTTP 8080, health path /actuator/health).
ALB_DNS=$(aws elbv2 describe-load-balancers --names shop-alb \
--query 'LoadBalancers[0].DNSName' --output text --profile shop-lab)
echo $ALB_DNS
# state phải là active (mất ~2 phút)
aws elbv2 describe-load-balancers --names shop-alb --query 'LoadBalancers[0].State.Code' --output text --profile shop-lab
Bước 2 – Launch template: chạy Bước 4 ở mục 4. Kiểm tra:
aws ec2 describe-launch-template-versions --launch-template-name shop-lt \
--query 'LaunchTemplateVersions[0].LaunchTemplateData.[ImageId,InstanceType,IamInstanceProfile.Name]' \
--output text --profile shop-lab
Console: EC2 → Launch Templates → Create: AMI Amazon Linux 2023, t3.micro, không chọn subnet (ASG quyết định), SG shop-app-sg, Advanced → IAM instance profile shop-app-ec2-role, User data dán script.
Bước 3 – ASG: chạy Bước 5 ở mục 4. Theo dõi:
watch -n 10 "aws autoscaling describe-auto-scaling-groups --auto-scaling-group-names shop-asg \
--query 'AutoScalingGroups[0].Instances[].[InstanceId,AvailabilityZone,LifecycleState,HealthStatus]' \
--output table --profile shop-lab"
Chờ cả 2 dòng InService / Healthy, mỗi dòng một AZ (ap-southeast-1a, ap-southeast-1b).
Console: EC2 → Auto Scaling Groups → Create: launch template shop-lt, VPC shop-vpc, chọn shop-private-a + shop-private-b → Attach to existing load balancer → shop-tg → bật "Turn on Elastic Load Balancing health checks", grace 120 → min 2 / desired 2 / max 4 → tags.
Bước 4 – Scaling policy: chạy Bước 6 ở mục 4. Xem alarm ASG tự tạo:
aws cloudwatch describe-alarms --alarm-name-prefix TargetTracking-shop-asg \
--query 'MetricAlarms[].[AlarmName,StateValue,Threshold]' --output table --profile shop-lab
Console: Auto Scaling Groups → shop-asg → Automatic scaling → Create dynamic scaling policy → Target tracking → Average CPU 50, warm-up 120.
Kiểm tra kết quả
# 1. Health qua ALB
curl -s http://$ALB_DNS/actuator/health
# {"status":"UP"}
# 2. Request chia cho 2 instance: thêm endpoint trả hostname, hoặc xem header/metadata
for i in $(seq 1 6); do curl -s http://$ALB_DNS/actuator/info | head -c 80; echo; done
# (nếu info có build info giống nhau, dùng access log hoặc CloudWatch RequestCountPerTarget)
aws elbv2 describe-target-health --target-group-arn $TG_ARN \
--query 'TargetHealthDescriptions[].[Target.Id,Target.AvailabilityZone,TargetHealth.State]' \
--output table --profile shop-lab
# 2 dòng, 2 AZ khác nhau, State = healthy
# 3. Metric RequestCount trên ALB (sau vài phút)
ALB_SUFFIX=${ALB_ARN#*loadbalancer/}
aws cloudwatch get-metric-statistics --namespace AWS/ApplicationELB --metric-name RequestCount \
--dimensions Name=LoadBalancer,Value=$ALB_SUFFIX \
--start-time $(date -u -d '-15 min' +%FT%TZ) --end-time $(date -u +%FT%TZ) \
--period 300 --statistics Sum --profile shop-lab
Thử thêm
A. Giết một instance, xem ASG thay.
VICTIM=$(aws autoscaling describe-auto-scaling-groups --auto-scaling-group-names shop-asg \
--query 'AutoScalingGroups[0].Instances[0].InstanceId' --output text --profile shop-lab)
aws ec2 terminate-instances --instance-ids $VICTIM --profile shop-lab
# Trong lúc đó, gọi liên tục: không được có lỗi (hoặc chỉ 1–2 lỗi trong ~15s đầu trước khi health check phát hiện)
while true; do curl -s -o /dev/null -w "%{http_code} " http://$ALB_DNS/actuator/health; sleep 1; done
aws autoscaling describe-scaling-activities --auto-scaling-group-name shop-asg --max-items 3 \
--query 'Activities[].[StartTime,Description,StatusCode]' --output table --profile shop-lab
# "Terminating EC2 instance: i-..." rồi "Launching a new EC2 instance: i-..."
Ghi lại: bao nhiêu giây từ terminate đến khi target mới healthy? Đó chính là "thời gian tự hồi phục" bạn sẽ nói trong phỏng vấn.
B. Tạo tải để scale out. Vào một instance qua Session Manager và đốt CPU:
aws ssm start-session --target $VICTIM_NEW --profile shop-lab
# trong máy:
sudo dnf install -y stress-ng && stress-ng --cpu 2 --timeout 600
Sau 3–5 phút, alarm TargetTracking-shop-asg-AlarmHigh chuyển ALARM, desired tăng lên 3 rồi 4. Dừng stress, chờ ~15 phút (scale in cố ý chậm hơn scale out) để desired về 2. Quan sát instance nào bị terminate: ASG ưu tiên AZ có nhiều instance hơn.
C. Instance refresh (deploy jar mới không downtime). Upload jar mới lên S3, tạo launch template version mới (hoặc giữ version nếu user data luôn tải bản mới nhất), rồi:
aws autoscaling start-instance-refresh --auto-scaling-group-name shop-asg \
--preferences '{"MinHealthyPercentage": 50, "InstanceWarmup": 120}' --profile shop-lab
aws autoscaling describe-instance-refreshes --auto-scaling-group-name shop-asg --profile shop-lab
ASG thay từng 50% instance, chờ healthy mới thay tiếp. Đây là rolling update "kiểu AWS" cho EC2.
Dọn tài nguyên
Thứ tự quan trọng: ASG (kéo về 0 trước) → ALB listener/ALB → target group → launch template → SG. ALB và NAT Gateway là hai thứ tốn tiền từng giờ nếu quên.
# 1. ASG: về 0 rồi xóa (force-delete để không phải chờ)
aws autoscaling update-auto-scaling-group --auto-scaling-group-name shop-asg \
--min-size 0 --desired-capacity 0 --profile shop-lab
aws autoscaling delete-auto-scaling-group --auto-scaling-group-name shop-asg --force-delete --profile shop-lab
# chờ instance terminated
aws ec2 describe-instances --filters Name=tag:Project,Values=shop-app Name=instance-state-name,Values=running,shutting-down \
--query 'Reservations[].Instances[].InstanceId' --output text --profile shop-lab
# 2. ALB (listener xóa theo) và target group
aws elbv2 delete-load-balancer --load-balancer-arn $ALB_ARN --profile shop-lab
sleep 30
aws elbv2 delete-target-group --target-group-arn $TG_ARN --profile shop-lab
# 3. Launch template
aws ec2 delete-launch-template --launch-template-name shop-lt --profile shop-lab
# 4. SG của ALB (shop-app-sg giữ lại cho bài sau; nếu xóa, phải gỡ rule tham chiếu trước)
aws ec2 revoke-security-group-ingress --group-id $APP_SG --protocol tcp --port 8080 --source-group $ALB_SG --profile shop-lab
aws ec2 delete-security-group --group-id $ALB_SG --profile shop-lab
# 5. Kiểm tra không còn gì tốn tiền
aws elbv2 describe-load-balancers --query 'LoadBalancers[].LoadBalancerName' --profile shop-lab
aws resourcegroupstaggingapi get-resources --tag-filters Key=Project,Values=shop-app \
--query 'ResourceTagMappingList[].ResourceARN' --output text --profile shop-lab
Nếu bạn dừng lab ở đây và chưa làm bài 22.6/22.7 ngay, cân nhắc xóa cả NAT Gateway (bài 22.3) và tạo lại sau; nó không nằm trong Free Tier.
Phỏng vấn và đề thi SAA hỏi về ELB và Auto Scaling thì sao?
Q1. ALB và NLB khác nhau thế nào, bạn chọn gì cho REST API Spring Boot?
🇻🇳 Tiếng Việt:
ALB làm việc ở Layer 7: hiểu HTTP, route theo path/host/header, terminate TLS bằng ACM, gắn WAF, có thể trỏ tới Lambda. NLB ở Layer 4: chuyển TCP/UDP, giữ IP nguồn, có static IP mỗi AZ, latency rất thấp và chịu hàng triệu kết nối. REST API Spring Boot em chọn ALB vì cần path routing, redirect HTTPS và WAF. NLB em dùng khi cần IP cố định để đối tác whitelist, giao thức không phải HTTP, hoặc làm đầu vào PrivateLink.
🇬🇧 English:
ALB works at Layer 7: it understands HTTP, routes by path, host or header, terminates TLS with ACM certificates, integrates with WAF and can target Lambda. NLB works at Layer 4: it forwards TCP/UDP, preserves the client IP, provides a static IP per AZ and handles millions of connections with very low latency. For a Spring Boot REST API I pick ALB because I need path-based routing, HTTPS redirect and WAF. I use NLB when partners need a fixed IP to whitelist, for non-HTTP protocols, or as the entry point for PrivateLink.
Q2. ASG phát hiện và thay instance hỏng ra sao? Vì sao app của bạn phải stateless?
🇻🇳 Tiếng Việt:
ASG chạy một vòng lặp giữ số instance bằng desired. Em bật health check type ELB để ASG dùng kết quả HTTP health check của target group, vì EC2 status check chỉ biết máy sống, không biết app treo. Khi target unhealthy quá grace period, ASG terminate và launch instance mới từ launch template, user data tự cài và chạy app. Vì instance có thể bị thay bất kỳ lúc nào, app không được giữ state cục bộ: session để ở Redis hoặc DynamoDB, file upload lên S3, log đẩy ra CloudWatch.
🇬🇧 English:
The ASG runs a control loop that keeps the instance count equal to the desired capacity. I enable the ELB health check type so the ASG uses the target group's HTTP health check, because the EC2 status check only tells us the VM is alive, not that the app is healthy. When a target stays unhealthy past the grace period, the ASG terminates it and launches a replacement from the launch template, and user data installs and starts the app automatically. Since any instance can be replaced at any time, the app must be stateless: sessions in Redis or DynamoDB, uploads in S3, logs shipped to CloudWatch.
Q3. Deploy phiên bản mới cho ASG mà không downtime bằng cách nào?
🇻🇳 Tiếng Việt:
Có hai cách em hay dùng. Một là instance refresh: tạo launch template version mới (AMI hoặc jar mới), ASG thay dần instance với MinHealthyPercentage, chờ healthy rồi thay tiếp; kết hợp graceful shutdown của Spring Boot và deregistration delay của target group để không rớt request. Hai là blue/green: tạo ASG + target group mới, dùng listener rule weighted để chuyển dần traffic, có lỗi thì đổi weight về 0. Cách hai an toàn hơn nhưng tốn gấp đôi instance trong lúc deploy.
🇬🇧 English:
I use two approaches. First, instance refresh: publish a new launch template version with the new AMI or jar, and the ASG replaces instances gradually, respecting MinHealthyPercentage and waiting for health checks; combined with Spring Boot graceful shutdown and the target group's deregistration delay, no request is dropped. Second, blue/green: create a new ASG and target group, shift traffic with weighted listener rules, and roll back by setting the weight to zero. Blue/green is safer but doubles instance cost during the deployment.
Q4. Scale out thì nhanh, vì sao scale in phải cẩn thận hơn?
🇻🇳 Tiếng Việt:
Scale out thiếu máy chỉ tốn tiền, scale in sai thì rớt request và dao động liên tục. Em đặt warm-up để instance mới chưa tính vào metric, dùng target tracking vì nó scale in chậm hơn scale out có chủ ý, đặt deregistration delay và graceful shutdown để request đang chạy xong trước khi máy tắt, và bật scale-in protection cho instance đang chạy job dài hoặc dùng lifecycle hook để flush trước khi terminate.
🇬🇧 English:
Over-provisioning on scale out only costs money, but a wrong scale in drops requests and causes flapping. I set an instance warm-up so new instances do not skew the metric, use target tracking because it deliberately scales in more slowly than it scales out, configure deregistration delay plus graceful shutdown so in-flight requests finish, and use scale-in protection or lifecycle hooks for instances running long jobs that need to flush state before termination.
Góc thi SAA-C03
Câu 1: Web app Spring Boot trên EC2 sau ALB. Khi flash sale, người dùng báo bị đăng xuất ngẫu nhiên sau khi ASG thêm instance. Công ty muốn giải quyết với LEAST operational overhead và vẫn scale được. Chọn gì?
A. Bật sticky session trên target group
B. Lưu session vào Amazon ElastiCache for Redis bằng Spring Session
C. Tăng min size của ASG bằng max size để không còn scale
D. Chuyển sang NLB để giữ kết nối TCP
Đáp án: B. Externalize session để mọi instance đọc được. A che triệu chứng, gây lệch tải và mất session khi instance bị thay; chỉ đúng khi đề nói app legacy không sửa được. C bỏ luôn auto scaling, tốn tiền và không giải quyết instance bị thay khi hỏng. D NLB không liên quan tới session HTTP.
Câu 2: Một ứng dụng xử lý giao dịch cần IP tĩnh để ngân hàng đối tác whitelist, giao thức TCP tùy biến trên port 9000, hàng trăm nghìn kết nối với latency thấp nhất. Load balancer nào phù hợp?
A. Application Load Balancer
B. Classic Load Balancer
C. Network Load Balancer với Elastic IP mỗi AZ
D. Gateway Load Balancer
Đáp án: C. Từ khóa: static IP, TCP tùy biến, latency thấp → NLB. A ALB chỉ HTTP/HTTPS/gRPC và không có IP tĩnh. B CLB là legacy, hiếm khi là đáp án đúng. D GWLB dùng để chèn appliance bảo mật bên thứ ba, không phải LB cho app.
Câu 3: Hệ thống báo cáo nội bộ có tải tăng vọt đúng 8:00 sáng mỗi ngày làm việc và giảm sau 10:00. Target tracking hiện tại phản ứng chậm, người dùng chờ 5 phút đầu giờ. Giải pháp MOST cost-effective?
A. Tăng min size ASG lên bằng số instance giờ cao điểm
B. Thêm scheduled scaling action đặt desired capacity cao hơn lúc 7:45 và giảm lúc 10:15
C. Đổi sang simple scaling với cooldown ngắn
D. Đổi instance type lớn hơn
Đáp án: B. Tải "predictable, known schedule" → scheduled scaling, chuẩn bị máy trước giờ tải. A tốn tiền cả ngày. C vẫn reactive, không hết độ trễ boot. D vertical, tốn và vẫn có downtime khi đổi. Nếu đề nói "recurring pattern but not fixed time" thì đáp án là predictive scaling.
Cách nhớ ngắn gọn
- ALB = Layer 7 (HTTP, path/host, WAF, Lambda). NLB = Layer 4 (TCP/UDP, static IP, siêu nhanh). GWLB = appliance. CLB = bỏ.
- ASG = control loop: min ≤ desired ≤ max, trải ≥ 2 AZ, health check type ELB, grace period đủ cho app boot.
- Policy: biến đổi tự nhiên → target tracking; lịch cố định → scheduled; mẫu lặp → predictive; cần bậc → step.
- Stateless là điều kiện để scale: session → Redis/DynamoDB, file → S3.
- Scale in cần: warm-up, graceful shutdown, deregistration delay, lifecycle hook.
| Từ khóa đề | Nghĩ đến |
|---|---|
| path-based / host-based routing, WAF, redirect HTTPS | ALB |
| static IP, Elastic IP, TCP/UDP, millions of requests, PrivateLink | NLB |
| third-party firewall / IDS appliances, GENEVE | Gateway Load Balancer |
| highly available, replace unhealthy instances, multiple AZs | ASG min ≥ 2, 2 AZ, ELB health check |
| known schedule / every day at 9 AM | Scheduled scaling |
| recurring pattern, proactively | Predictive scaling |
| randomly logged out, session lost | ElastiCache / DynamoDB session store (không phải stickiness) |
| in-flight requests dropped during scale in | Deregistration delay (connection draining), lifecycle hook |
Tự kiểm tra
Vì sao ASG nên dùng --health-check-type ELB thay vì mặc định EC2?
EC2 status check chỉ biết máy còn chạy. ELB health check gọi /actuator/health, phát hiện app treo, OOM, mất DB dù máy vẫn sống, và ASG mới thay được instance đó.
ALB tính tiền theo gì? Lab xong phải xóa thứ gì đầu tiên?
Theo giờ tồn tại + LCU, không phụ thuộc traffic; Free Tier chỉ 750 giờ trong 12 tháng đầu. Xóa ASG (về 0) rồi ALB; nhớ cả NAT Gateway.
Instance của ASG cứ launch rồi terminate sau 2 phút. Nguyên nhân hay gặp và lệnh nào để xem lý do?
Health check fail trong grace period quá ngắn (user data cài Java + boot Spring quá 120s), hoặc Actuator bị Security chặn trả 401, SG chưa mở 8080 từ ALB SG. Xem aws autoscaling describe-scaling-activities và aws elbv2 describe-target-health.
Deregistration delay và graceful shutdown của Spring Boot liên quan nhau thế nào?
Khi scale in, ALB ngừng gửi request mới và chờ deregistration delay (ví dụ 30s) cho request đang chạy; app nhận SIGTERM, graceful shutdown chờ ≤ timeout-per-shutdown-phase (đặt nhỏ hơn 30s) để xử lý xong rồi tắt. Thiếu một trong hai là có 502.
Phân biệt HTTPCode_Target_5XX và HTTPCode_ELB_5XX.
Target_5XX là lỗi app trả về (bug, DB). ELB_5XX là ALB tự sinh: 502 (target đóng kết nối / keep-alive lệch), 503 (không còn target healthy), 504 (quá idle timeout). Debug 502 lẻ tẻ: so idle timeout ALB với keep-alive của Tomcat, kiểm tra graceful shutdown.
Khi nào chọn NLB thay ALB?
Cần IP tĩnh/Elastic IP để whitelist, giao thức TCP/UDP không phải HTTP, latency cực thấp và hàng triệu kết nối, hoặc làm endpoint cho PrivateLink. ALB có thể là target của NLB khi cần cả hai.
🧪 Thực hành bài này: Intermediate: Ứng dụng HA: ALB + ASG + RDS + S3 + SQS (chưa triển khai) · lab labs/aws