EC2: instance type, AMI, user data, EBS, instance store, placement group, pricing model
Tiếp đi: EC2. Mình muốn chạy shop-app.jar trên một máy EC2 trong shop-vpc. Mở trang chọn instance thấy t3.micro, m6i.large, c7g.xlarge, r6g… không biết chọn cái nào cho một app Spring Boot. Và lần nào tạo instance mình cũng SSH vào cài Java bằng tay, chắc có cách tự động?
Hai câu hỏi đúng là hai nửa của EC2: chọn máy và làm máy tự dựng. Ở bài 18.5 ta đã so EC2 với ECS/EKS/Lambda để chọn nơi chạy; bài này giả định đã chọn EC2 (hoặc cần hiểu EC2 vì ECS trên EC2, EKS node, ASG đều đứng trên nó) và đi vào bên trong.
1. Bài toán: một máy chạy shop-app.jar, không ai phải SSH
Mục tiêu bài này:
shop-vpc / shop-public-a
┌──────────────────────────────────────────┐
│ EC2 t3.micro, Amazon Linux 2023 │
│ user data: cài Java 21, tải jar từ S3, │
│ tạo systemd service │
│ role: shop-app-ec2-role (đọc S3, SSM) │
│ :8080 /actuator/health → {"status":"UP"}│
│ EBS gp3 8 GB (root) + snapshot │
└──────────────────────────────────────────┘
Bài 22.5 nhân máy này lên bằng Launch Template + ASG sau ALB.
2. Vấn đề với cách "tạo rồi SSH vào cài"
- Máy là snowflake: không ai nhớ đã cài gì. Thay máy (bài 22.5 ASG làm việc đó liên tục) là phải cài lại tay.
- Mở port 22 ra Internet, quản lý key pair
.pemchia nhau trong team. - Chọn instance theo cảm giác: thừa RAM thì tốn tiền, thiếu thì OOM; không biết vì sao t3 "lúc nhanh lúc chậm".
- Dữ liệu ghi vào đĩa nào, mất khi nào, backup thế nào: không rõ.
3. Giải pháp: các khối của EC2 và thuật ngữ đề thi
Instance type: đọc tên là biết máy.
m6i.large
│ ││ └── size: nano, micro, small, medium, large, xlarge, 2xlarge … (mỗi bậc gấp đôi vCPU/RAM)
│ │└───── thuộc tính: i = Intel, a = AMD, g = Graviton (ARM), d = có NVMe local, n = network cao
│ └────── generation: số càng lớn càng mới, thường rẻ hơn trên mỗi đơn vị hiệu năng
└──────── family: mục đích
| Family | Tỷ lệ vCPU:RAM | Dùng cho | Ví dụ với shop-app |
|---|---|---|---|
| T (t3, t3a, t4g) burstable | 1:4 nhưng CPU theo credit | Dev/test, traffic thấp, lab | t3.micro cho lab (Free Tier); t4g.small cho staging |
| M general purpose | 1:4 | Web/app server cân bằng | m7g.large cho shop-app production |
| C compute optimized | 1:2 | CPU nặng: encode, batch, gateway nhiều TLS | Job resize ảnh nếu không dùng Lambda |
| R memory optimized | 1:8 | Cache, DB trong RAM, JVM heap lớn | Redis tự quản, Elasticsearch |
| X / High memory | 1:16+ | SAP HANA, DB khổng lồ | Không |
| I / D storage optimized | NVMe/HDD local lớn | NoSQL tự quản, data warehouse, Kafka broker | Kafka tự quản (bài 8) |
| P / G / Inf / Trn accelerated | GPU / chip AI | ML training/inference, render | Không |
| Hpc | Network cực thấp latency | Mô phỏng khoa học | Không |
Cho một Spring Boot bình thường: bắt đầu từ M hoặc T, ưu tiên Graviton (g) nếu image/jar không phụ thuộc native x86 (JVM chạy ARM rất tốt, rẻ hơn khoảng 20% cho cùng hiệu năng). JVM cần RAM thật: heap + metaspace + thread stack + off-heap ≈ 1.5–2× -Xmx. shop-app với -Xmx512m chạy được trên 1 GB (t3.micro) cho lab, production nên 2 GB trở lên.
AMI (Amazon Machine Image): ảnh đĩa gốc + metadata (kiến trúc, loại virtualization, block device). AMI là regional: muốn dùng ở region khác phải copy. Ba nguồn: AWS cung cấp (Amazon Linux 2023, Ubuntu, Windows), Marketplace, và AMI tự tạo ("golden AMI": đã cài Java, agent, cấu hình, để máy mới khởi động nhanh). Tìm AMI mới nhất đúng cách là qua SSM public parameter, không hardcode ID (ID đổi theo region và theo tháng):
aws ssm get-parameters \
--names /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64 \
--query 'Parameters[0].Value' --output text --profile shop-lab
# ami-0c1234567890abcde (Graviton: ...-arm64)
User data: script chạy một lần khi instance khởi động lần đầu (bởi cloud-init), với quyền root. Đây là nơi "cài Java, tải jar, tạo service". Giới hạn 16 KB; log ở /var/log/cloud-init-output.log. Thứ dài hơn thì user data chỉ tải script từ S3 rồi chạy. Muốn chạy mỗi lần boot thì phải dùng cloud-init #cloud-boothook hoặc systemd, user data mặc định không chạy lại.
Instance metadata (IMDS): endpoint http://169.254.169.254/latest/meta-data/ mà chính instance gọi để biết về mình: instance ID, AZ, private IP, và credential tạm của role. SDK lấy credential ở đây. IMDSv2 đòi token qua PUT trước (chống SSRF kiểu Capital One 2019); luôn đặt HttpTokens=required.
TOKEN=$(curl -sX PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 300")
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/instance-id
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/shop-app-ec2-role
# { "AccessKeyId": "ASIA...", "SecretAccessKey": "...", "Token": "...", "Expiration": "..." }
Instance profile: "vỏ" để gắn một IAM role vào EC2. Console tạo ngầm, CLI phải tạo tường minh (create-instance-profile + add-role-to-instance-profile). Đặt tên profile trùng tên role cho đỡ nhầm.
Lưu trữ:
| EBS | Instance store | EFS (bài 22.16) | |
|---|---|---|---|
| Bản chất | Đĩa mạng, tách khỏi instance, trong một AZ | NVMe/SSD gắn vật lý vào host | File system NFS chia sẻ, nhiều AZ |
| Dữ liệu khi stop/terminate | Giữ (root volume mặc định xóa khi terminate, tùy DeleteOnTermination) | Mất khi stop, terminate, hoặc host hỏng | Giữ, độc lập instance |
| Hiệu năng | gp3: 3 000 IOPS/125 MB/s nền, mua thêm tới 16 000 IOPS; io2 Block Express tới 256 000 IOPS | Hàng triệu IOPS, latency thấp nhất | Scale theo kích thước, latency cao hơn EBS |
| Gắn vào | Một instance (Multi-Attach chỉ io1/io2, cluster FS) | Instance đó | Hàng nghìn instance cùng lúc |
| Backup | Snapshot incremental lên S3; copy sang region khác; làm AMI | Tự lo | AWS Backup |
| Dùng cho | Root, database, dữ liệu bền | Cache, scratch, buffer, Kafka với replication | Upload chia sẻ, CMS, home dir |
Loại EBS: gp3 (mặc định, tách IOPS khỏi dung lượng, nên dùng thay gp2), io2/io2 Block Express (database cần IOPS cao, 99.999% durability), st1 (HDD throughput: log, big data), sc1 (HDD lạnh, rẻ nhất). Thi: "16 000 IOPS trở xuống → gp3; cao hơn hoặc cần sub-ms → io2; throughput tuần tự rẻ → st1; boot volume → chỉ SSD (gp/io)".
Placement group: gợi ý cho AWS xếp instance ở đâu.
| Loại | Xếp | Dùng cho | Từ khóa đề |
|---|---|---|---|
| Cluster | Cùng rack, cùng AZ, network 10–100 Gbps, latency thấp nhất | HPC, ML training, in-memory grid | "low latency", "high network throughput", "tightly coupled" |
| Spread | Mỗi instance một rack riêng, tối đa 7 instance/AZ | Vài máy quan trọng không được chết cùng lúc (3 node ZooKeeper, master DB) | "minimize correlated failures", "small number of critical instances" |
| Partition | Chia thành partition (tối đa 7/AZ), mỗi partition rack riêng, nhiều instance/partition | Kafka, HDFS, Cassandra: biết partition để đặt replica | "large distributed workload", "Hadoop/Kafka/Cassandra" |
Pricing model (chi tiết chiến lược ở bài 22.19; đây là định nghĩa cần thuộc):
| Model | Cam kết | Giảm | Khi nào |
|---|---|---|---|
| On-Demand | Không, tính theo giây (Linux, tối thiểu 60 giây) | 0% | Lab, workload mới chưa đoán được, ngắn hạn |
| Spot | Không; AWS thu hồi với báo trước 2 phút khi cần capacity | Tới 90% | Batch, CI, xử lý ảnh, stateless có thể gián đoạn; không cho database |
| Reserved Instance | 1 hoặc 3 năm, một instance family/region (Standard) hoặc đổi được (Convertible) | Tới 72% | Workload ổn định, biết rõ loại máy; RDS/ElastiCache/OpenSearch chỉ có RI |
| Savings Plans | 1 hoặc 3 năm, cam kết USD/giờ; Compute SP linh hoạt cả EC2/Fargate/Lambda, EC2 Instance SP theo family+region | Tới 72% (EC2 Instance), 66% (Compute) | Thay RI cho EC2 trong hầu hết trường hợp hiện đại |
| Dedicated Host / Instance | Thuê cả máy vật lý / instance trên host riêng | Đắt hơn | Giấy phép theo socket/core (Windows, Oracle BYOL), tuân thủ "không chia host" |
| Capacity Reservation | Giữ chỗ capacity trong AZ, trả On-Demand, không cần cam kết dài | 0% (có thể kết hợp SP) | Chắc chắn có máy lúc sale/DR; đề: "guarantee capacity in a specific AZ" |
4. Cấu hình từng bước
Bước 1: Role và instance profile shop-app-ec2-role. Quyền: đọc jar và ảnh từ S3, ghi CloudWatch Logs, và AmazonSSMManagedInstanceCore để vào máy bằng Session Manager thay SSH.
{
"Version": "2012-10-17",
"Statement": [
{ "Sid": "ReadArtifactsAndAssets", "Effect": "Allow",
"Action": ["s3:GetObject", "s3:ListBucket"],
"Resource": ["arn:aws:s3:::shop-app-assets-ACCOUNT_ID", "arn:aws:s3:::shop-app-assets-ACCOUNT_ID/*"] },
{ "Sid": "WriteLogs", "Effect": "Allow",
"Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents", "logs:DescribeLogStreams"],
"Resource": "arn:aws:logs:ap-southeast-1:ACCOUNT_ID:log-group:/shop/app:*" }
]
}
Bước 2: User data cài Java 21 và chạy jar như systemd service.
#!/bin/bash
set -euxo pipefail
dnf install -y java-21-amazon-corretto-headless
useradd -r -s /sbin/nologin shop || true
mkdir -p /opt/shop && cd /opt/shop
# tải jar từ S3 bằng credential của role (không có key nào trong script)
BUCKET=shop-app-assets-$(curl -s -H "X-aws-ec2-metadata-token: $(curl -sX PUT http://169.254.169.254/latest/api/token -H 'X-aws-ec2-metadata-token-ttl-seconds: 60')" \
http://169.254.169.254/latest/dynamic/instance-identity/document | grep -oP '"accountId"\s*:\s*"\K[0-9]+')
aws s3 cp s3://$BUCKET/releases/shop-app.jar /opt/shop/shop-app.jar
chown -R shop:shop /opt/shop
cat > /etc/systemd/system/shop-app.service <<'UNIT'
[Unit]
Description=shop-app Spring Boot
After=network-online.target
Wants=network-online.target
[Service]
User=shop
WorkingDirectory=/opt/shop
Environment=SPRING_PROFILES_ACTIVE=aws
Environment=JAVA_TOOL_OPTIONS=-Xmx512m -XX:+UseSerialGC -XX:MaxRAMPercentage=60
ExecStart=/usr/bin/java -jar /opt/shop/shop-app.jar
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
UNIT
systemctl daemon-reload
systemctl enable --now shop-app
Điểm hay bị hỏi: Restart=always để app crash tự dậy (ASG chỉ thay máy khi máy hỏng hoặc health check ALB fail, không biết process chết); JAVA_TOOL_OPTIONS để JVM tự đọc; -XX:MaxRAMPercentage thay cho Xmx cố định khi đổi instance size. Trên t3.micro 1 GB, Serial GC hoặc G1 với heap nhỏ đều ổn; production nhiều core thì để G1 mặc định.
Bước 3: Java đọc cấu hình từ môi trường AWS. Trên EC2 không cần cấu hình gì cho credential. Khi app cần biết "mình là instance nào" (để log, metric), dùng SDK thay vì gọi IMDS tay:
// software.amazon.awssdk:imds
import software.amazon.awssdk.imds.Ec2MetadataClient;
@Configuration
public class InstanceInfoConfig {
@Bean
public String instanceId() {
try (Ec2MetadataClient imds = Ec2MetadataClient.create()) { // IMDSv2 mặc định
return imds.get("/latest/meta-data/instance-id").asString();
} catch (Exception e) {
return "local"; // chạy local không có IMDS
}
}
}
// Dùng: logging.pattern thêm %X{instanceId}, hoặc CloudWatch metric dimension InstanceId
Và đọc jar/ảnh từ S3 trong code: S3Client.create(), không truyền credential; chain tìm IMDS. Timeout mặc định của chain với IMDS là 1 giây, nên ở local app khởi động chậm vài giây khi SDK thử IMDS: đặt AWS_EC2_METADATA_DISABLED=true ở local để bỏ qua.
5. Cách hoạt động: vòng đời một instance
run-instances
│ chọn host vật lý trong AZ, gắn ENI (private IP từ subnet; public IP nếu subnet cấp),
│ tạo EBS root từ snapshot của AMI, gắn instance profile
▼
pending ──► running
│ cloud-init chạy user data (một lần) ── log /var/log/cloud-init-output.log
│ IMDS cung cấp credential role (xoay tự động ~mỗi 6 giờ)
│ CloudWatch: CPUUtilization, NetworkIn/Out, StatusCheckFailed (mặc định 5 phút; detailed 1 phút tính tiền)
│ 2 status check: System (host/hardware AWS) + Instance (OS/network của bạn)
▼
stop ──► stopping ──► stopped EBS giữ; RAM, instance store, public IP (không EIP) mất
│ không tính tiền compute, vẫn tính EBS; có thể đổi instance type ở đây
▼
start ──► running (host MỚI, public IP mới nếu không có EIP, user data KHÔNG chạy lại)
hibernate ──► stopped (RAM ghi vào EBS root, start lại là tiếp tục, giới hạn RAM ≤ 150 GB)
terminate ──► shutting-down ──► terminated root EBS xóa (DeleteOnTermination=true), EBS khác giữ
có "termination protection" để chặn xóa nhầm
Hai hệ quả thực tế: (1) muốn sửa user data phải stop → edit → start, và sửa xong nó cũng không chạy lại, nên user data phải idempotent và máy nên thay chứ không sửa; (2) reboot (restart OS) không đổi host, không mất instance store, không đổi IP; stop/start thì có. Đề thi: "instance needs to move to a new underlying host (hardware degraded)" → stop/start, không phải reboot.
EBS snapshot là incremental: lần đầu copy toàn bộ block đã dùng, lần sau chỉ block thay đổi; xóa snapshot giữa chuỗi không hỏng các snapshot sau (AWS giữ block cần thiết). Snapshot nằm ở S3 (bạn không thấy bucket), có thể copy sang region/account khác, mã hóa khi copy, và tạo AMI từ snapshot. Data Lifecycle Manager (DLM) hoặc AWS Backup tự tạo và xóa snapshot theo lịch; đề thi: "automate EBS snapshots with retention" → DLM/AWS Backup.
6. So sánh: những cặp hay ra đề
| Cặp | Khác nhau | Chọn khi |
|---|---|---|
| Stop/Start vs Reboot | Stop/Start: host mới, IP public mới, instance store mất, user data không chạy. Reboot: cùng host, giữ mọi thứ | Hardware degraded / đổi instance type: stop/start. Chỉ restart OS: reboot |
| EBS vs Instance store | Bền, mạng, snapshot vs tạm, local, nhanh nhất | Dữ liệu phải sống qua stop: EBS. Scratch/cache cực nhanh: instance store |
| gp3 vs io2 | gp3 tới 16k IOPS, rẻ; io2 tới 64k (Block Express 256k), durability 99.999%, Multi-Attach | Hầu hết: gp3. DB IOPS rất cao / Multi-Attach: io2 |
| Spot vs On-Demand | Rẻ tới 90% nhưng thu hồi báo 2 phút vs ổn định | Spot: batch, CI, một phần ASG stateless. On-Demand: phần "base", DB, stateful |
| RI vs Savings Plans | RI gắn instance type/region (Convertible đổi được); SP cam kết tiền/giờ, Compute SP áp cả Fargate/Lambda | EC2 mới: SP. RDS/ElastiCache/Redshift: chỉ có RI |
| Dedicated Host vs Dedicated Instance | Host: thấy socket/core, kiểm soát đặt instance, BYOL theo socket. Instance: host riêng nhưng không thấy chi tiết | License theo socket/core: Host. Chỉ cần "không chia host với khách khác": Instance |
| Cluster vs Spread vs Partition | Gần nhau nhất / xa nhau nhất (≤7 per AZ) / theo nhóm | HPC / vài node quan trọng / Kafka-Hadoop |
| SSH + key pair vs SSM Session Manager | Mở 22, quản .pem vs không port, IAM + CloudTrail audit, ghi lại phiên | Hiện đại: Session Manager. Đề: "no inbound ports, audit all sessions" → SSM |
| Public IP vs Elastic IP | Đổi khi stop/start vs cố định, thuộc tài khoản | Server cần IP cố định không qua ALB: EIP. Có ALB/Route 53: không cần EIP |
| EC2 Instance Connect vs Session Manager | Đẩy SSH key tạm 60 giây qua API, vẫn cần port 22 (hoặc Instance Connect Endpoint) vs không cần SSH | Cần SSH thật (scp, tunnel kiểu cũ): Instance Connect. Còn lại: SSM |
7. Khi nào dùng / không dùng EC2 cho shop-app
- Dùng: cần kiểm soát OS/JVM (agent, kernel tuning, GPU), app chưa container hóa, license theo core, chạy phần mềm bên thứ ba cần VM, hoặc đơn giản là team quen VM và app ít (1–3 service).
- Không: nhiều microservice (ECS/EKS quản lý tốt hơn, bài 22.13), job ngắn theo sự kiện (Lambda, 22.12), hoặc không muốn vá OS. Lưu ý: dù chọn ECS trên EC2 hay EKS, kiến thức bài này (instance type, EBS, Spot, user data) vẫn áp dụng cho node.
- Không chạy database trên EC2 khi RDS đáp ứng được: bạn ôm backup, patch, failover (bài 22.7).
Mình chưa yên tâm chuyện đĩa. Nếu mình stop instance rồi start lại thì file log và ảnh upload tạm ở /opt/shop/uploads còn không? Instance store là gì mà "mất dữ liệu"? Và snapshot EBS khác AMI chỗ nào?
Câu trả lời phụ thuộc vào đĩa đó là loại gì. Với t3.micro thì chắc chắn là EBS (dòng T không có instance store), nên /opt/shop/uploads còn sau stop/start. Nhưng hiểu rõ từng loại thì sau này không bất ngờ.
1. Ba tình huống với EBS root
stop → start : EBS giữ nguyên. Mất: RAM, public IP (không EIP), instance store nếu có.
reboot : giữ hết, kể cả instance store.
terminate : root volume XÓA nếu DeleteOnTermination=true (mặc định cho root);
volume gắn thêm (data volume) mặc định GIỮ → thành volume "available" vẫn tính tiền!
Bẫy chi phí số 1: terminate instance mà data volume còn lại ở trạng thái available, tính tiền hàng tháng không ai thấy. Dọn bằng aws ec2 describe-volumes --filters Name=status,Values=available.
Và lưu ý thiết kế: dù EBS giữ dữ liệu, ảnh upload không nên nằm trên EBS của một máy. Bài 22.5 chạy 2 máy: ảnh ở máy A, request đến máy B là 404. Ảnh thuộc về S3 (bài 22.6); EBS chỉ cho OS, app, và dữ liệu "của máy đó".
2. Instance store (ephemeral)
Là SSD/NVMe gắn vật lý vào host chạy instance của bạn (các type có chữ d: m6id, c6gd, i3, i4i…). Vì gắn vào host, khi instance stop hay terminate, hoặc host hỏng, AWS đưa instance lên host khác và đĩa cũ không theo. Dữ liệu mất là đúng thiết kế. Đổi lại: hàng triệu IOPS, latency micro-giây, không tốn thêm tiền ngoài giá instance. Dùng cho: cache cục bộ, buffer, temp của Spark/Kafka khi đã có replication. Không snapshot được. Trên AL2023, instance store không tự mount; phải format và mount trong user data mỗi lần boot.
3. Snapshot, volume, AMI: quan hệ
EBS volume (đang gắn, trong AZ-a)
│ create-snapshot (incremental, lưu trên S3, regional, không bị trói AZ)
▼
Snapshot snap-0abc ──► create-volume --availability-zone ap-southeast-1b (đưa dữ liệu sang AZ khác)
│ ──► copy-snapshot --source-region ... --destination-region us-east-1 (DR)
│ ──► copy-snapshot --encrypted (mã hóa volume chưa mã hóa)
▼
register-image (hoặc create-image từ instance): snapshot + metadata boot = AMI
──► run-instances từ AMI: máy mới có đúng đĩa đó (golden AMI, nhanh hơn user data cài từ đầu)
- Snapshot = bản sao một volume tại một thời điểm. Để backup, clone, đổi AZ, đổi type (gp2 → gp3 cũng làm được trực tiếp bằng
modify-volumekhông cần snapshot). - AMI = một hay nhiều snapshot + thông tin để boot (kernel, kiến trúc, block device mapping). Để tạo instance mới.
create-imagemặc định reboot instance để đĩa nhất quán;--no-rebootnhanh hơn nhưng có thể thiếu dữ liệu chưa flush. - EBS volume bị trói trong một AZ; snapshot thì regional. Chuyển instance sang AZ khác = snapshot → volume ở AZ mới (hoặc AMI → launch ở AZ mới).
- Mã hóa: bật EBS encryption by default cho region, mọi volume/snapshot mới dùng KMS key (bài 22.14). Volume đã tạo không mã hóa thì không bật tại chỗ được: snapshot → copy có mã hóa → volume mới.
4. Snapshot tốn tiền thế nào
Theo GB lưu trên S3 của phần dữ liệu thay đổi, không theo kích thước volume. Lab snapshot 8 GB root dùng ~2 GB thực chỉ vài cent/tháng, nhưng chuỗi snapshot hằng ngày cho DB 500 GB thì đáng kể; EBS Snapshots Archive rẻ hơn ~75% cho snapshot giữ trên 90 ngày (khôi phục mất 24–72 giờ). Free Tier: 1 GB snapshot/tháng trong 12 tháng đầu.
Mình để ý t3.micro có "CPU credits". Spring Boot khởi động 30–40 giây ăn 100% CPU, rồi chạy bình thường. Hết credit thì sao, app bị dừng à? Có nên bật chế độ unlimited không, và khi nào thì nên bỏ dòng T sang dòng M?
Không, app không bị dừng. Hết credit thì CPU bị kẹp xuống mức baseline (với t3.micro là 10% của 2 vCPU, tức tương đương 0.2 vCPU). App vẫn chạy, chỉ chậm. Đây là lý do "t3 lúc nhanh lúc chậm" mà nhiều người không giải thích được.
1. Cơ chế credit
t3.micro: 2 vCPU, baseline 10% mỗi vCPU → kiếm 12 credit/giờ, tối đa tích 288 credit (24 giờ)
1 credit = 1 vCPU chạy 100% trong 1 phút
Khởi động Spring Boot: ~2 vCPU × 100% × 0.75 phút ≈ 1.5 credit ← rẻ, không đáng lo
Chạy idle 5% CPU ← dưới baseline, tích credit
Traffic tăng, 60% CPU liên tục ← tiêu ~1 credit/phút, 288 credit cạn sau ~5 giờ
Hết credit → kẹp 10% → latency tăng vọt, health check có thể timeout → ALB đánh unhealthy
Instance mới được cấp launch credit (30 credit/vCPU cho t2; t3 thì dùng cơ chế unlimited mặc định) nên khởi động không phải vấn đề. Vấn đề là tải đều cao hơn baseline trong nhiều giờ.
Theo dõi: metric CPUCreditBalance và CPUCreditUsage trong CloudWatch (namespace AWS/EC2). Đặt alarm khi CPUCreditBalance < 50. Một instance "CPU 10% mà app chậm" với credit balance = 0 là chẩn đoán xong.
2. Unlimited mode
t3 mặc định unlimited: hết credit vẫn burst, và bạn trả thêm cho phần vượt (tính theo vCPU-giờ, khoảng 0.05 USD/vCPU-giờ cho Linux, kiểm tra pricing). Tốt khi burst hiếm; nếu liên tục vượt baseline thì tiền phụ thu có thể cao hơn giá một instance M cùng cỡ. Chuyển về standard:
aws ec2 modify-instance-credit-specification --profile shop-lab \
--instance-credit-specifications "InstanceId=$INSTANCE_ID,CpuCredits=standard"
# hoặc lúc run-instances: --credit-specification CpuCredits=standard
Lab nên để standard để không có hóa đơn bất ngờ; nếu bị kẹp, bạn thấy ngay và hiểu vì sao.
3. Khi nào rời dòng T
| Dấu hiệu | Hành động |
|---|---|
| Trung bình CPU dưới baseline, có đỉnh ngắn | T là đúng, rẻ nhất |
| CPU trung bình > baseline nhiều giờ/ngày, credit về 0 hằng ngày | Sang M cùng cỡ (m7g.medium/large) hoặc tăng size T |
| Phụ thu unlimited > chênh giá T→M | Sang M |
| Cần hiệu năng dự đoán được cho SLO (production) | M/C/R; T chỉ cho dev/test và dịch vụ phụ |
Compute Optimizer (bài 22.19) nhìn 14 ngày metric và gợi ý thẳng "t3.micro → t4g.small" hay "→ m7g.medium". Thi: "application performance degrades after several hours of sustained load on T instances" → CPU credit cạn; đáp án là M/C family hoặc unlimited, tùy đề nhấn cost hay performance.
4. JVM trên máy nhỏ
Trên 1 GB RAM: -Xmx512m hoặc -XX:MaxRAMPercentage=60, để OS và metaspace còn chỗ, nếu không OOM killer giết java và systemd restart mãi. Bật -XX:+UseSerialGC hoặc để JVM tự chọn (dưới 2 CPU/2 GB JVM tự dùng Serial). Spring Boot 3 với AOT/CDS (-XX:SharedArchiveFile) giảm thời gian khởi động và CPU lúc boot; container hóa sau này còn có CRaC. Không cần cho lab, cần khi scale-out phải nhanh.
Spot rẻ tới 90% mà shop-app là stateless. Mình chạy một instance Spot cho production được không? Nếu không thì Spot hợp với việc gì trong shop-app, và khi bị thu hồi thì mình xử lý thế nào?
Một instance Spot duy nhất cho production thì không: bạn đổi 70–90% tiền lấy khả năng website biến mất bất kỳ lúc nào với 2 phút báo trước. Nhưng Spot hợp với một phần capacity sau ALB và với các job nền của shop-app. Bài 22.19 bàn chiến lược mua; ở đây là cơ chế.
1. Spot hoạt động thế nào
Mỗi (instance type, AZ) có một Spot pool với giá thay đổi chậm theo cung cầu (không còn kiểu đấu giá).
Bạn đặt max price (mặc định = On-Demand). AWS thu hồi khi: cần capacity cho On-Demand, hoặc giá > max price.
Thu hồi: gửi "interruption notice" 2 phút trước, qua IMDS và EventBridge; EC2 Rebalance Recommendation báo sớm hơn khi pool có rủi ro.
Nguyên tắc: đa dạng hóa pool. ASG hoặc EC2 Fleet với 5–10 instance type tương đương (m6i.large, m6a.large, m5.large, m7g.large…) ở nhiều AZ; chiến lược price-capacity-optimized chọn pool ít bị thu hồi và rẻ. Một pool bị thu hồi, ASG lấy máy từ pool khác.
2. Xử lý khi bị thu hồi
# Trong máy: poll IMDS (mỗi 5 giây), 404 là chưa có thông báo
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/spot/instance-action
# {"action": "terminate", "time": "2025-01-10T09:12:00Z"}
# Hoặc EventBridge rule: source aws.ec2, detail-type "EC2 Spot Instance Interruption Warning"
# → Lambda/SSM Run Command: deregister khỏi target group, drain, graceful shutdown
Với shop-app sau ALB: bật Capacity Rebalancing trong ASG để ASG tự tạo máy thay trước khi máy cũ bị thu hồi; ALB deregistration delay (mặc định 300 giây, giảm xuống 30) cho request đang xử lý xong; Spring Boot bật server.shutdown=graceful và spring.lifecycle.timeout-per-shutdown-phase=20s để không cắt request giữa chừng. Kết quả: người dùng không nhận lỗi, chỉ có máy thay nhau.
3. Spot hợp và không hợp với gì trong shop-app
| Workload | Spot? | Vì sao |
|---|---|---|
| shop-app web tier sau ALB, ASG min 2 | Một phần: base On-Demand 2 máy + phần scale-out Spot (mixed instances policy) | Luôn còn máy On-Demand khi Spot bị thu hồi hàng loạt |
| Job resize ảnh, sinh báo cáo đêm, re-index search | Có, 100% | Chạy lại được, checkpoint theo batch |
| CI runner, môi trường test | Có | Gián đoạn chỉ tốn thời gian |
| Kafka consumer xử lý order | Cẩn thận: được nếu idempotent, commit offset sau khi xử lý | Thu hồi giữa batch → rebalance, message xử lý lại |
| PostgreSQL, Redis tự quản, ZooKeeper | Không | Stateful, mất quorum, recovery dài |
Cách khác để dùng Spot mà không tự xử lý: Spot Fleet / EC2 Fleet (quản lý nhiều pool), ECS capacity provider và Karpenter trên EKS (tự chọn Spot cho pod chịu được gián đoạn), AWS Batch (job queue trên Spot, retry tự động). Và Spot Blocks (chạy 1–6 giờ không bị thu hồi) đã ngừng cho khách mới; đề thi mới không còn coi là đáp án.
4. Câu trả lời cho shop-app
Production: ASG với OnDemandBaseCapacity=2, OnDemandPercentageAboveBaseCapacity=0 (phần vượt 100% Spot), 6 instance type, Capacity Rebalancing bật. Lab bài này: một t3.micro On-Demand trong Free Tier là đủ; bài 22.5 cấu hình mixed instances.
Cho mình bài lab để tự tay làm phần này nhé: chạy shop-app.jar trên một EC2 trong shop-public-a bằng user data, có instance profile, vào máy không cần SSH, và thử snapshot EBS.
Lab 22.4: shop-app.jar trên EC2 t3.micro với user data, instance profile và EBS snapshot
Kết quả: một máy trong shop-public-a tự cài Java 21, tự tải jar từ S3 (hoặc tự dựng placeholder nếu bạn chưa có jar), chạy như systemd service, trả /actuator/health. Role và bucket tạo ở đây được bài 22.5 dùng tiếp cho Launch Template.
Mục tiêu
- Role + instance profile
shop-app-ec2-rolevới quyền đọcs3://shop-app-assets-$ACCOUNT_ID, ghi log group/shop/app, vàAmazonSSMManagedInstanceCore. - Instance
shop-app-1t3.micro, AL2023, IMDSv2 bắt buộc, credit standard, trongshop-public-a, SGshop-app-sg(+ rule tạm 8080 từ IP của bạn). curl http://<public-ip>:8080/actuator/healthtrả 200{"status":"UP"}mà không ai SSH vào máy.- Một EBS snapshot của root volume được tạo, gắn tag, và một volume phục hồi từ snapshot gắn vào máy đọc được.
Chi phí & thời gian
- t3.micro: 750 giờ/tháng Free Tier (tài khoản đủ điều kiện), ngoài đó khoảng 0.013 USD/giờ. Public IPv4: tính theo giờ (khoảng 0.005 USD/giờ) kể cả trong Free Tier có một phần miễn phí. EBS gp3 8 GB + volume phục hồi 8 GB: trong 30 GB Free Tier. Snapshot: vài GB, trong 1 GB Free Tier thì vượt một ít, vài cent.
- Nếu
shop-vpcvẫn còn NAT từ lab 22.3: lab này không cần NAT (máy ở public subnet). Xóa NAT nếu không dùng. - Thời gian: 60–75 phút.
Chuẩn bị
- Lab 22.1 và 22.3 xong:
source ~/shop-lab.env && source ~/shop-lab/shop-vpc.env(cần$VPC_ID,$PUBLIC_A_ID,$APP_SG_ID). - Session Manager plugin cho AWS CLI (cài theo trang hướng dẫn AWS; kiểm tra
session-manager-plugin --version). - Tùy chọn: file
shop-app.jarbuild từ project (Spring Boot 3, Java 21, cóspring-boot-starter-actuator,server.port=8080). Không có cũng được: user data tự dựng placeholder. mkdir -p ~/shop-lab/ec2 && cd ~/shop-lab/ec2.
Các bước
Bước 1: Bucket và jar.
BUCKET=shop-app-assets-$ACCOUNT_ID
aws s3 mb s3://$BUCKET --region $AWS_REGION --profile shop-lab
aws s3api put-public-access-block --bucket $BUCKET --profile shop-lab \
--public-access-block-configuration BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
aws s3api put-bucket-tagging --bucket $BUCKET --profile shop-lab \
--tagging 'TagSet=[{Key=Project,Value=shop-app},{Key=Env,Value=lab}]'
# nếu có jar:
aws s3 cp target/shop-app.jar s3://$BUCKET/releases/shop-app.jar --profile shop-lab
# không có: bỏ qua, user data sẽ tự tạo placeholder
Console: S3 → Create bucket (tên duy nhất toàn cầu, region Singapore, Block all public access bật), upload vào thư mục releases/. Chi tiết S3 ở bài 22.6.
Bước 2: Role và instance profile.
cat > trust-ec2.json <<'EOF'
{ "Version": "2012-10-17", "Statement": [{ "Effect": "Allow",
"Principal": { "Service": "ec2.amazonaws.com" }, "Action": "sts:AssumeRole" }] }
EOF
cat > shop-app-ec2-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{ "Sid": "ReadAssets", "Effect": "Allow", "Action": ["s3:GetObject", "s3:ListBucket"],
"Resource": ["arn:aws:s3:::${BUCKET}", "arn:aws:s3:::${BUCKET}/*"] },
{ "Sid": "WriteLogs", "Effect": "Allow",
"Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents", "logs:DescribeLogStreams"],
"Resource": "arn:aws:logs:${AWS_REGION}:${ACCOUNT_ID}:log-group:/shop/app:*" }
]
}
EOF
aws iam create-role --role-name shop-app-ec2-role --assume-role-policy-document file://trust-ec2.json \
--tags Key=Project,Value=shop-app Key=Env,Value=lab --profile shop-lab
aws iam put-role-policy --role-name shop-app-ec2-role --policy-name ShopAppEc2Inline \
--policy-document file://shop-app-ec2-policy.json --profile shop-lab
aws iam attach-role-policy --role-name shop-app-ec2-role \
--policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore --profile shop-lab
aws iam create-instance-profile --instance-profile-name shop-app-ec2-role --profile shop-lab
aws iam add-role-to-instance-profile --instance-profile-name shop-app-ec2-role --role-name shop-app-ec2-role --profile shop-lab
sleep 10
Console: IAM → Roles → Create role → AWS service → EC2; Console tự tạo instance profile cùng tên. Thi: "EC2 cannot access S3 although role has permission" → quên gắn instance profile, hoặc SCP/bucket policy chặn.
Bước 3: User data. Script tự phát hiện có jar trên S3 không; không có thì dựng placeholder bằng jwebserver (có sẵn trong JDK 21) trả /actuator/health.
cat > user-data.sh <<EOF
#!/bin/bash
set -euxo pipefail
exec > >(tee -a /var/log/shop-app-bootstrap.log) 2>&1
dnf install -y java-21-amazon-corretto-headless
useradd -r -s /sbin/nologin shop || true
mkdir -p /opt/shop
BUCKET=${BUCKET}
if aws s3 ls s3://\$BUCKET/releases/shop-app.jar >/dev/null 2>&1; then
aws s3 cp s3://\$BUCKET/releases/shop-app.jar /opt/shop/shop-app.jar
EXEC="/usr/bin/java -jar /opt/shop/shop-app.jar"
else
# placeholder: web server tĩnh trả /actuator/health giống Spring Boot
mkdir -p /opt/shop/www/actuator/health
echo '{"status":"UP","placeholder":true}' > /opt/shop/www/actuator/health/index.html
echo "shop-app placeholder on \$(hostname)" > /opt/shop/www/index.html
EXEC="/usr/bin/jwebserver -b 0.0.0.0 -p 8080 -d /opt/shop/www"
fi
chown -R shop:shop /opt/shop
cat > /etc/systemd/system/shop-app.service <<UNIT
[Unit]
Description=shop-app
After=network-online.target
Wants=network-online.target
[Service]
User=shop
WorkingDirectory=/opt/shop
Environment=SPRING_PROFILES_ACTIVE=aws
Environment=JAVA_TOOL_OPTIONS=-XX:MaxRAMPercentage=60
ExecStart=\$EXEC
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
UNIT
systemctl daemon-reload
systemctl enable --now shop-app
sleep 3 && curl -sf http://localhost:8080/actuator/health && echo "shop-app UP"
EOF
cat user-data.sh | head -20 # kiểm tra BUCKET đã được thay bằng tên thật
Heredoc ngoài không có nháy để ${BUCKET} được điền; các \$ giữ lại cho script chạy trên máy. AL2023 có sẵn AWS CLI v2 và SSM agent nên không cần cài.
Bước 4: Rule tạm cho 8080 từ IP của bạn. Theo thiết kế, shop-app-sg chỉ nhận từ ALB; bài này chưa có ALB nên mở tạm từ IP nhà bạn, bước dọn sẽ gỡ.
MY_IP=$(curl -s https://checkip.amazonaws.com)
aws ec2 authorize-security-group-ingress --group-id $APP_SG_ID --profile shop-lab \
--ip-permissions "IpProtocol=tcp,FromPort=8080,ToPort=8080,IpRanges=[{CidrIp=${MY_IP}/32,Description=TEMP lab 22.4 my laptop}]"
Console: EC2 → Security Groups → shop-app-sg → Edit inbound rules → Add rule, Source "My IP".
Bước 5: Chạy instance.
AMI_ID=$(aws ssm get-parameters --names /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64 \
--query 'Parameters[0].Value' --output text --profile shop-lab)
INSTANCE_ID=$(aws ec2 run-instances --profile shop-lab \
--image-id $AMI_ID --instance-type t3.micro \
--subnet-id $PUBLIC_A_ID --security-group-ids $APP_SG_ID \
--iam-instance-profile Name=shop-app-ec2-role \
--user-data file://user-data.sh \
--metadata-options HttpTokens=required,HttpEndpoint=enabled \
--credit-specification CpuCredits=standard \
--block-device-mappings 'DeviceName=/dev/xvda,Ebs={VolumeSize=8,VolumeType=gp3,Encrypted=true,DeleteOnTermination=true}' \
--tag-specifications \
'ResourceType=instance,Tags=[{Key=Name,Value=shop-app-1},{Key=Project,Value=shop-app},{Key=Env,Value=lab}]' \
'ResourceType=volume,Tags=[{Key=Name,Value=shop-app-1-root},{Key=Project,Value=shop-app},{Key=Env,Value=lab}]' \
--query 'Instances[0].InstanceId' --output text)
echo $INSTANCE_ID
echo "export INSTANCE_ID=$INSTANCE_ID" >> ~/shop-lab/shop-vpc.env
aws ec2 wait instance-running --instance-ids $INSTANCE_ID --profile shop-lab
PUBLIC_IP=$(aws ec2 describe-instances --instance-ids $INSTANCE_ID --profile shop-lab \
--query 'Reservations[0].Instances[0].PublicIpAddress' --output text)
echo $PUBLIC_IP
Không có --key-name: máy này không có SSH key, cố ý. Console: EC2 → Launch instance: Name, AMI Amazon Linux 2023, t3.micro, Key pair "Proceed without a key pair", Network: shop-vpc / shop-public-a / Auto-assign public IP Enable / Select existing SG shop-app-sg; Storage 8 GB gp3 Encrypted; Advanced details: IAM instance profile shop-app-ec2-role, Metadata version V2 only (token required), Credit specification Standard, User data dán script.
Bước 6: Chờ user data chạy (1–3 phút) và kiểm tra từ ngoài.
for i in $(seq 1 30); do
curl -s -m 3 http://$PUBLIC_IP:8080/actuator/health && break || { echo "chờ... ($i)"; sleep 10; }
done
# {"status":"UP"} ← jar thật
# {"status":"UP","placeholder":true} ← placeholder
Bước 7: Vào máy bằng Session Manager, không SSH.
aws ssm describe-instance-information --profile shop-lab \
--query 'InstanceInformationList[?InstanceId==`'$INSTANCE_ID'`].PingStatus' --output text # Online
aws ssm start-session --target $INSTANCE_ID --profile shop-lab
Trong phiên:
sudo systemctl status shop-app --no-pager | head -5
sudo tail -20 /var/log/shop-app-bootstrap.log # output user data
sudo tail -5 /var/log/cloud-init-output.log
ss -lntp | grep 8080 # 0.0.0.0:8080 java
TOKEN=$(curl -sX PUT http://169.254.169.254/latest/api/token -H "X-aws-ec2-metadata-token-ttl-seconds: 60")
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/info # InstanceProfileArn shop-app-ec2-role
curl -s http://169.254.169.254/latest/meta-data/instance-id # 401: IMDSv1 bị chặn, đúng ý
aws sts get-caller-identity # assumed-role/shop-app-ec2-role/i-... (credential từ IMDS, không có key nào trên máy)
aws s3 ls s3://shop-app-assets-$(aws sts get-caller-identity --query Account --output text)/ # được
aws s3 ls # AccessDenied: role không có ListAllMyBuckets, đúng least privilege
exit
Console: EC2 → Instances → shop-app-1 → Connect → Session Manager → Connect. CloudTrail ghi StartSession với danh tính của bạn; có thể bật ghi lại nội dung phiên vào S3/CloudWatch trong Systems Manager → Session Manager → Preferences.
Bước 8: EBS snapshot và phục hồi.
ROOT_VOL_ID=$(aws ec2 describe-instances --instance-ids $INSTANCE_ID --profile shop-lab \
--query 'Reservations[0].Instances[0].BlockDeviceMappings[0].Ebs.VolumeId' --output text)
SNAP_ID=$(aws ec2 create-snapshot --volume-id $ROOT_VOL_ID --description "shop-app-1 root after bootstrap" \
--tag-specifications 'ResourceType=snapshot,Tags=[{Key=Name,Value=shop-app-1-root-snap},{Key=Project,Value=shop-app},{Key=Env,Value=lab}]' \
--query SnapshotId --output text --profile shop-lab)
aws ec2 wait snapshot-completed --snapshot-ids $SNAP_ID --profile shop-lab # 1-3 phút
aws ec2 describe-snapshots --snapshot-ids $SNAP_ID --profile shop-lab \
--query 'Snapshots[0].{state:State,size:VolumeSize,encrypted:Encrypted,start:StartTime}'
# Phục hồi thành volume mới ở CÙNG AZ với instance, gắn vào máy làm đĩa thứ hai
AZ=$(aws ec2 describe-instances --instance-ids $INSTANCE_ID --profile shop-lab \
--query 'Reservations[0].Instances[0].Placement.AvailabilityZone' --output text)
RESTORE_VOL_ID=$(aws ec2 create-volume --snapshot-id $SNAP_ID --availability-zone $AZ --volume-type gp3 \
--tag-specifications 'ResourceType=volume,Tags=[{Key=Name,Value=shop-app-1-restore},{Key=Project,Value=shop-app},{Key=Env,Value=lab}]' \
--query VolumeId --output text --profile shop-lab)
aws ec2 wait volume-available --volume-ids $RESTORE_VOL_ID --profile shop-lab
aws ec2 attach-volume --volume-id $RESTORE_VOL_ID --instance-id $INSTANCE_ID --device /dev/sdf --profile shop-lab
Trong phiên SSM: lsblk thấy đĩa mới (nvme1n1); vì nó là bản sao root có cùng UUID, mount bằng sudo mkdir /mnt/restore && sudo mount -o nouuid /dev/nvme1n1p4 /mnt/restore (số partition xem trong lsblk), ls /mnt/restore/opt/shop thấy jar/www. Đây là cách cứu file từ máy hỏng: snapshot → volume → gắn vào máy khác. Console: EC2 → Volumes → chọn → Actions → Create snapshot; Snapshots → Actions → Create volume from snapshot; Volumes → Attach.
Kiểm tra kết quả
curl -s http://$PUBLIC_IP:8080/actuator/health # {"status":"UP"...}
aws ec2 describe-instances --instance-ids $INSTANCE_ID --profile shop-lab --query 'Reservations[0].Instances[0].{
type:InstanceType, az:Placement.AvailabilityZone, subnet:SubnetId, profile:IamInstanceProfile.Arn,
imdsv2:MetadataOptions.HttpTokens, credits:CpuOptions, key:KeyName}'
# key: null (không SSH key), imdsv2: required, profile: .../shop-app-ec2-role
aws ec2 describe-instance-credit-specifications --instance-ids $INSTANCE_ID --profile shop-lab --query 'InstanceCreditSpecifications[0].CpuCredits' # standard
aws ec2 describe-snapshots --owner-ids self --filters Name=tag:Project,Values=shop-app --profile shop-lab --query 'Snapshots[].{id:SnapshotId,state:State}'
aws cloudwatch get-metric-statistics --namespace AWS/EC2 --metric-name CPUCreditBalance \
--dimensions Name=InstanceId,Value=$INSTANCE_ID --statistics Average --period 300 \
--start-time $(date -u -d '-30 min' +%FT%TZ) --end-time $(date -u +%FT%TZ) --profile shop-lab --query 'Datapoints[-1].Average'
Thử thêm
- Stop/start và quan sát.
aws ec2 stop-instances→wait instance-stopped→start-instances: public IP đổi,/opt/shopvẫn còn, service tự chạy lại nhờenable, user data không chạy lại (xem/var/log/shop-app-bootstrap.logkhông có dòng mới). Gắn EIP nếu muốn IP cố định:allocate-address+associate-address, nhớ release sau. - Đổi instance type. Khi đang stopped:
aws ec2 modify-instance-attribute --instance-id $INSTANCE_ID --instance-type t3.small; start lại và xem RAM gấp đôi. Đổi về t3.micro để ở trong Free Tier. - Tạo AMI và launch từ AMI.
aws ec2 create-image --instance-id $INSTANCE_ID --name shop-app-golden-$(date +%Y%m%d) --no-reboot; run-instances từ AMI đó không cần user data cài Java (đã có sẵn), khởi động nhanh hơn. Nhớ deregister AMI và xóa snapshot của nó khi dọn. - Giết process, xem systemd cứu. Trong SSM:
sudo pkill -9 -f shop-app, sau 5 giâysystemctl statuslại active. Đây là lớp tự phục hồi mức process; ASG (bài 22.5) là lớp mức máy. - Mô phỏng hết credit.
sudo dnf install -y stress-ng && stress-ng --cpu 2 --timeout 20mrồi nhìnCPUCreditBalancegiảm vàCPUUtilizationbị kẹp sau khi về 0 (mất khá lâu với balance ban đầu; đủ để thấy xu hướng).
Dọn tài nguyên
Giữ lại cho bài 22.5: role/instance profile shop-app-ec2-role, bucket với jar, shop-vpc. Xóa: instance (tốn giờ Free Tier và public IP), volume phục hồi, snapshot, rule SG tạm, AMI thử nghiệm nếu có.
source ~/shop-lab/shop-vpc.env
# 1. Gỡ volume phục hồi rồi xóa (volume "available" tính tiền âm thầm)
aws ec2 detach-volume --volume-id $RESTORE_VOL_ID --profile shop-lab
aws ec2 wait volume-available --volume-ids $RESTORE_VOL_ID --profile shop-lab
aws ec2 delete-volume --volume-id $RESTORE_VOL_ID --profile shop-lab
# 2. Terminate instance (root volume tự xóa vì DeleteOnTermination=true)
aws ec2 terminate-instances --instance-ids $INSTANCE_ID --profile shop-lab
aws ec2 wait instance-terminated --instance-ids $INSTANCE_ID --profile shop-lab
# 3. Snapshot
aws ec2 delete-snapshot --snapshot-id $SNAP_ID --profile shop-lab
# 4. AMI thử nghiệm (nếu đã tạo): deregister rồi xóa snapshot của nó
for ami in $(aws ec2 describe-images --owners self --filters Name=name,Values='shop-app-golden-*' --query 'Images[].ImageId' --output text --profile shop-lab); do
SNAPS=$(aws ec2 describe-images --image-ids $ami --query 'Images[0].BlockDeviceMappings[].Ebs.SnapshotId' --output text --profile shop-lab)
aws ec2 deregister-image --image-id $ami --profile shop-lab
for s in $SNAPS; do aws ec2 delete-snapshot --snapshot-id $s --profile shop-lab; done
done
# 5. EIP nếu đã thử: aws ec2 release-address --allocation-id eipalloc-xxx --profile shop-lab
# 6. Rule SG tạm từ laptop
aws ec2 revoke-security-group-ingress --group-id $APP_SG_ID --profile shop-lab \
--ip-permissions "IpProtocol=tcp,FromPort=8080,ToPort=8080,IpRanges=[{CidrIp=${MY_IP}/32}]"
# 7. Kiểm kê: không còn volume mồ côi, không còn instance
aws ec2 describe-volumes --filters Name=status,Values=available --profile shop-lab --query 'Volumes[].VolumeId'
aws ec2 describe-instances --filters Name=tag:Project,Values=shop-app Name=instance-state-name,Values=running,stopped --profile shop-lab --query 'Reservations[].Instances[].InstanceId'
sed -i '/INSTANCE_ID/d' ~/shop-lab/shop-vpc.env
Nếu bỏ hẳn cả chương: thêm aws s3 rb s3://$BUCKET --force, gỡ role (remove-role-from-instance-profile → delete-instance-profile → detach-role-policy, delete-role-policy → delete-role) và chạy cleanup-vpc.sh của lab 22.3.
Phỏng vấn và đề thi SAA hỏi về EC2 thì sao?
Q1. Bạn chọn instance type cho một ứng dụng Spring Boot thế nào?
🇻🇳 Tiếng Việt:
Em đọc tên instance: family cho mục đích, generation, hậu tố cho CPU. Spring Boot web thường cân bằng CPU và RAM nên bắt đầu ở dòng M, ưu tiên Graviton vì JVM chạy ARM tốt và rẻ hơn. RAM tính bằng heap cộng khoảng 50–100% cho metaspace, thread và off-heap. Dòng T chỉ dùng cho dev/test vì CPU theo credit, tải đều cao sẽ bị kẹp xuống baseline. Sau đó em dùng Compute Optimizer và metric thật để chỉnh cỡ.
🇬🇧 English:
I read the instance name: the family for purpose, the generation, and the suffix for the CPU. A Spring Boot web service is usually balanced between CPU and memory, so I start with the M family and prefer Graviton because the JVM runs well on ARM at a lower price. I size memory as heap plus roughly 50–100% for metaspace, threads and off-heap. T instances are for dev and test only, since their CPU is credit-based and sustained load gets throttled to the baseline. Then I right-size with Compute Optimizer and real metrics.
Q2. Làm sao để instance mới tự cài và chạy app mà không SSH? Credential cho app lấy ở đâu?
🇻🇳 Tiếng Việt:
Em dùng user data: script cloud-init chạy một lần lúc boot đầu, cài Java, tải jar từ S3 và tạo systemd service với Restart=always. Hoặc nhanh hơn là golden AMI đã cài sẵn. Credential không có trong script: em gắn IAM role qua instance profile, SDK và CLI tự lấy credential tạm từ IMDSv2 và tự xoay. Vào máy khi cần thì dùng Session Manager, không mở port 22, mọi phiên đều có trong CloudTrail.
🇬🇧 English:
I use user data: a cloud-init script that runs once at first boot, installs Java, downloads the jar from S3 and creates a systemd service with Restart=always. Or, for faster startup, a golden AMI with everything preinstalled. There are no credentials in the script: I attach an IAM role through an instance profile, and the SDK and CLI fetch temporary credentials from IMDSv2 and rotate them automatically. When I need a shell I use Session Manager, with no port 22 open and every session logged in CloudTrail.
Q3. EBS và instance store khác nhau thế nào? Snapshot dùng để làm gì?
🇻🇳 Tiếng Việt:
EBS là đĩa mạng bền, trói trong một AZ, giữ dữ liệu qua stop/start và có thể snapshot. Instance store là đĩa gắn vật lý vào host, nhanh nhất nhưng mất khi stop, terminate hay host hỏng, nên chỉ cho cache và dữ liệu tạm. Snapshot là bản sao incremental của volume lưu trên S3, mang tính regional: dùng để backup, tạo volume ở AZ khác, copy sang region khác cho DR, mã hóa volume cũ, và làm AMI. Lịch snapshot em giao cho Data Lifecycle Manager hoặc AWS Backup.
🇬🇧 English:
EBS is durable network storage bound to one AZ; it survives stop/start and can be snapshotted. Instance store is physically attached to the host, the fastest option, but it is lost on stop, terminate or host failure, so it is only for caches and scratch data. A snapshot is an incremental, regional copy of a volume stored in S3: used for backups, creating volumes in another AZ, copying to another region for DR, encrypting an unencrypted volume, and building AMIs. I schedule snapshots with Data Lifecycle Manager or AWS Backup.
Q4. Khi nào dùng Spot, Reserved, Savings Plans, On-Demand?
🇻🇳 Tiếng Việt:
On-Demand cho workload mới hoặc ngắn hạn chưa đoán được. Spot cho việc chịu được gián đoạn như batch, CI, phần scale-out stateless; em đa dạng hóa instance type và xử lý thông báo thu hồi 2 phút bằng graceful shutdown. Phần tải ổn định em cam kết bằng Savings Plans, linh hoạt hơn Reserved Instance vì cam kết theo tiền mỗi giờ và áp được cả Fargate, Lambda; Reserved Instance còn dùng cho RDS, ElastiCache vì không có Savings Plans. Database và stateful không bao giờ chạy Spot.
🇬🇧 English:
On-Demand for new or short-lived workloads I cannot predict yet. Spot for interruption-tolerant work such as batch jobs, CI and the stateless scale-out portion; I diversify instance types and handle the two-minute interruption notice with graceful shutdown. For the steady baseline I commit with Savings Plans, which are more flexible than Reserved Instances because the commitment is in dollars per hour and also covers Fargate and Lambda; Reserved Instances remain for RDS and ElastiCache, which have no Savings Plans. Databases and stateful services never run on Spot.
Góc thi SAA-C03
Câu 1: Một ứng dụng mô phỏng tài chính chạy trên nhiều EC2 trao đổi dữ liệu liên tục, yêu cầu độ trễ mạng giữa các node THẤP NHẤT và băng thông cao nhất. Cấu hình nào phù hợp?
A. Spread placement group trải trên 3 AZ.
B. Cluster placement group trong một AZ với instance hỗ trợ enhanced networking.
C. Partition placement group với 7 partition.
D. Instance ở nhiều region với Global Accelerator.
Đáp án: B. Cluster đặt instance gần nhau trên cùng rack trong một AZ, cho latency thấp nhất và throughput cao nhất; đúng từ khóa "tightly coupled HPC". A: spread tách xa nhau để giảm lỗi tương quan, ngược mục tiêu. C: partition cho hệ phân tán lớn như Hadoop, không tối ưu latency. D: đa region tăng latency.
Câu 2: Một công ty chạy job xử lý ảnh hằng đêm trên EC2, mất khoảng 4 giờ, có thể chạy lại từ checkpoint nếu bị gián đoạn. Giải pháp nào TIẾT KIỆM CHI PHÍ NHẤT?
A. On-Demand instance chạy 4 giờ mỗi đêm.
B. Reserved Instance 1 năm.
C. Spot instance với nhiều instance type trong một Auto Scaling group, xử lý interruption notice để checkpoint.
D. Dedicated Host để đảm bảo capacity.
Đáp án: C. Job chịu gián đoạn, có checkpoint, chạy ngắn → Spot giảm tới 90%. A: đắt hơn nhiều. B: RI trả cho 24 giờ/ngày trong khi chỉ dùng 4 giờ. D: đắt nhất, không liên quan.
Câu 3: Một database tự quản trên EC2 cần volume 2 TB với 40 000 IOPS ổn định và độ bền cao nhất. Loại EBS nào đáp ứng?
A. General Purpose SSD gp3 với IOPS cấu hình 40 000.
B. Provisioned IOPS SSD io2 Block Express với 40 000 IOPS.
C. Throughput Optimized HDD st1.
D. Instance store NVMe trên instance dòng i4i.
Đáp án: B. gp3 tối đa 16 000 IOPS nên A không đạt. io2 Block Express cấp tới 256 000 IOPS và độ bền 99.999%. C: HDD cho throughput tuần tự, IOPS thấp. D: nhanh nhưng mất dữ liệu khi stop/host hỏng, không phù hợp "độ bền cao nhất" cho database.
Cách nhớ ngắn gọn
- Tên instance: family.generation+suffix.size; Spring Boot → M (Graviton), T chỉ dev/test vì credit.
- Máy tự dựng: AMI + user data (một lần) + instance profile + IMDSv2; vào máy bằng Session Manager.
- Stop/start: host mới, IP mới, instance store mất, EBS còn. Reboot: giữ hết.
- EBS: gp3 mặc định, io2 cho IOPS cao; snapshot incremental, regional, làm AMI. Volume "available" sau terminate = tiền âm thầm.
- Pricing: On-Demand (thử) · Spot (gián đoạn được) · Savings Plans (base EC2/Fargate/Lambda) · RI (RDS/ElastiCache) · Dedicated Host (license).
| Từ khóa trong đề | Nghĩ đến |
|---|---|
| lowest latency between nodes / HPC / tightly coupled | Cluster placement group |
| small number of critical instances, avoid correlated failure | Spread placement group |
| Hadoop / Kafka / Cassandra on EC2 | Partition placement group |
| fault-tolerant, batch, can be interrupted, lowest cost | Spot |
| steady-state, commit 1–3 years, flexible across Fargate/Lambda | Compute Savings Plans |
| software license per socket/core, BYOL | Dedicated Host |
| guarantee capacity in an AZ for an event | On-Demand Capacity Reservation |
| > 16 000 IOPS, sub-millisecond, Multi-Attach | io2 / io2 Block Express |
| temporary data, highest IOPS, can be lost | Instance store |
| run script at launch / install software automatically | User data (cloud-init) hoặc golden AMI |
| no SSH, no inbound ports, audited shell access | SSM Session Manager |
| instance performance drops after hours of sustained load | T-family CPU credits cạn → M/C hoặc unlimited |
| move instance to new hardware / change instance type | Stop → modify → Start |
| automate snapshots with retention | Data Lifecycle Manager / AWS Backup |
Tự kiểm tra
User data chạy khi nào? Sửa user data rồi start lại thì có chạy không?
Chỉ một lần ở lần boot đầu (cloud-init). Sửa xong start lại cũng không chạy, trừ khi dùng cloud-boothook hoặc tự cấu hình. Nên coi máy là thứ để thay, không để sửa.
IMDSv2 khác IMDSv1 chỗ nào và vì sao bắt buộc?
IMDSv2 đòi lấy token bằng PUT (có TTL, hop limit) trước khi đọc metadata; chặn tấn công SSRF đọc credential role qua một request GET đơn giản như vụ Capital One 2019. Đặt HttpTokens=required.
Stop/start khác reboot ở điểm nào?
Stop/start đưa instance lên host mới: public IP đổi (nếu không EIP), instance store mất, có thể đổi instance type. Reboot cùng host, giữ mọi thứ.
Volume gắn thêm (không phải root) còn lại gì sau khi terminate?
Mặc định DeleteOnTermination=false cho volume gắn thêm, nên nó thành volume available, vẫn tính tiền. Phải xóa tay hoặc đặt cờ lúc gắn.
t3.micro bị chậm sau vài giờ tải cao dù CPU chỉ 10%: nguyên nhân?
CPU credit cạn, instance bị kẹp xuống baseline 10%/vCPU. Kiểm tra CPUCreditBalance. Giải pháp: unlimited (trả thêm), tăng size, hoặc sang dòng M.
🧪 Thực hành bài này: Basic: Nền tảng: IAM + VPC + EC2 (chưa triển khai) · lab labs/aws