VPC: subnet, route table, IGW/NAT, Security Group, VPC endpoint, peering, Transit Gateway
Tiếp đi: VPC. Lúc trước mình tạo EC2 bằng wizard trong default VPC, database cũng có public IP luôn. Giờ muốn dựng mạng riêng cho shop-app: app trong private subnet, database không ra Internet, sau này gắn ALB. Mà subnet, route table, IGW, NAT cứ rối vào nhau. Dựng từ đầu thì đi theo thứ tự nào?
Rối là bình thường, vì VPC có khoảng 8 loại đối tượng và chúng tham chiếu nhau. Nhưng chỉ cần nhớ một thứ tự dựng và một câu hỏi ("gói tin từ A tới B đi qua những gì?") là mọi thứ xếp vào chỗ. Bài này dựng shop-vpc, mạng mà toàn bộ các bài sau (EC2, ALB, RDS, ElastiCache, ECS) đều đặt vào.
1. Bài toán: shop-app cần ba "vùng" mạng
Internet
│
▼
[Vùng public] ALB nhận HTTPS từ người dùng; NAT Gateway cho máy bên trong ra ngoài
│
▼
[Vùng app] EC2 / ECS chạy shop-app.jar :8080 — không có public IP, chỉ ALB gọi được
│
▼
[Vùng data] RDS PostgreSQL :5432, Redis :6379 — chỉ app gọi được, không bao giờ ra Internet
Và phải sống qua việc một data center hỏng: mỗi vùng trải trên hai Availability Zone (AZ) ap-southeast-1a và ap-southeast-1b.
2. Vấn đề với default VPC
- Default VPC chỉ có public subnet: mọi subnet đều có route ra Internet Gateway và tự gán public IP. Database đặt vào đó là lộ ra ngoài, chỉ còn trông vào Security Group.
- CIDR
172.31.0.0/16giống nhau ở mọi account/region → sau này peering với VPC khác bị trùng dải, không nối được. - Không có lớp "private": muốn app ra Internet (tải package, gọi API bên thứ ba) mà không có IP public thì không có đường.
- Không có quy ước tên, không có tag → khó dọn, khó tính tiền.
3. Giải pháp: thiết kế shop-vpc
Các khối xây dựng và thuật ngữ đề thi:
| Thành phần | Là gì | Điểm cần nhớ |
|---|---|---|
| VPC | Mạng ảo riêng trong một region, một dải CIDR IPv4 (/16 đến /28), có thể thêm dải phụ và IPv6 | Region-scoped. CIDR không đổi được, chỉ thêm. Mặc định có DNS resolver tại .2 của dải |
| Subnet | Một dải con của VPC, nằm trong đúng một AZ | AWS giữ 5 IP mỗi subnet (.0 network, .1 router, .2 DNS, .3 dự phòng, .255 broadcast). /24 = 251 IP dùng được |
| Route table | Bảng định tuyến gắn vào subnet; subnet không gắn thì dùng main route table | "Public subnet" = subnet mà route table có 0.0.0.0/0 → igw. Không có cờ nào khác định nghĩa public/private |
| Internet Gateway (IGW) | Cửa ra Internet hai chiều, gắn 1-1 với VPC, scale tự động, miễn phí | Làm 1:1 NAT giữa public IP và private IP của instance |
| NAT Gateway | Dịch vụ quản lý cho máy private ra Internet (một chiều: trong ra), đặt trong public subnet, cần Elastic IP | Không trong Free Tier: tính theo giờ + GB. Nằm trong một AZ; muốn HA phải mỗi AZ một cái |
| Elastic IP (EIP) | IPv4 public cố định thuộc tài khoản | Tính tiền mọi public IPv4 (kể cả đang dùng) từ 2024; EIP không gắn vào gì càng tốn |
| Security Group (SG) | Firewall stateful gắn vào ENI (instance, ALB, RDS, Lambda…); chỉ có rule Allow; tham chiếu được SG khác | Mặc định: chặn mọi inbound, cho mọi outbound. Đã so với NACL ở bài 18.5 |
| Network ACL | Firewall stateless ở mức subnet, có Allow/Deny theo số thứ tự | Để mặc định (allow all) trừ khi cần chặn cứng IP ở cả subnet |
| ENI (Elastic Network Interface) | Card mạng ảo: có private IP, MAC, SG. Instance, ALB, NAT, RDS, Lambda trong VPC đều là ENI | SG gắn vào ENI, không gắn vào subnet |
| VPC endpoint | Đường nội bộ tới dịch vụ AWS không qua Internet: Gateway (S3, DynamoDB; miễn phí; là route) hoặc Interface (PrivateLink, ENI có IP trong subnet; tính tiền) | Câu hỏi "private subnet access S3 without NAT" → Gateway endpoint |
| VPC peering | Nối hai VPC 1-1 bằng private IP; không transitive; CIDR không được trùng | 3 VPC = 3 peering; 10 VPC = 45 peering |
| Transit Gateway (TGW) | Hub nối nhiều VPC, VPN, Direct Connect; transitive; có route table riêng | Tính tiền theo attachment + GB. Câu "connect 50 VPCs" → TGW |
Thiết kế cụ thể của shop-vpc (các bài sau dùng đúng tên này):
shop-vpc 10.0.0.0/16 (65 536 IP; lab chỉ dùng 4 subnet /24, chừa chỗ cho sau)
ap-southeast-1a ap-southeast-1b
┌─────────────────────────┐ ┌─────────────────────────┐
public │ shop-public-a 10.0.1.0/24│ │ shop-public-b 10.0.2.0/24│
│ ALB ENI, NAT Gateway │ │ ALB ENI │
└────────────┬────────────┘ └─────────────────────────┘
│ rt: shop-public-rt (0.0.0.0/0 → igw)
private ┌─────────────────────────┐ ┌─────────────────────────┐
│ shop-private-a 10.0.11.0/24│ │ shop-private-b 10.0.12.0/24│
│ EC2/ECS shop-app, RDS, │ │ EC2/ECS shop-app, RDS │
│ Redis │ │ standby, Redis replica │
└─────────────────────────┘ └─────────────────────────┘
rt: shop-private-rt (0.0.0.0/0 → nat-gw ở public-a; S3 → vpce gateway)
Security Group (tham chiếu nhau, không dùng CIDR nội bộ):
shop-alb-sg : in 80, 443 từ 0.0.0.0/0
shop-app-sg : in 8080 từ shop-alb-sg
shop-db-sg : in 5432 từ shop-app-sg
shop-cache-sg : in 6379 từ shop-app-sg
Ba quyết định thiết kế đáng nói:
- Chừa dải. /16 với 4 subnet /24 là "lãng phí" có chủ ý: sau này thêm subnet cho EKS (cần nhiều IP cho pod), subnet riêng cho database, hay AZ thứ ba mà không phải đổi gì.
- Một NAT cho lab, hai NAT cho production. NAT là thứ tốn tiền nhất trong lab; một cái ở public-a đủ để học. Production cần mỗi AZ một NAT và route table riêng mỗi AZ, nếu không AZ-a sập là máy ở AZ-b cũng mất đường ra Internet (đề thi hay hỏi đúng chỗ này).
- SG tham chiếu SG. Viết "db cho phép từ shop-app-sg" thay vì "từ 10.0.11.0/24": thêm instance, đổi subnet, scale ASG đều không phải sửa rule.
4. Cấu hình từng bước: thứ tự dựng
Thứ tự này là thứ tự phụ thuộc: cái sau cần ID của cái trước. Lab bên dưới có đầy đủ lệnh; ở đây là khung để nhớ.
1. create-vpc → vpc-id ; bật enableDnsHostnames
2. create-subnet ×4 → subnet-id ; map-public-ip-on-launch cho 2 public
3. create-internet-gateway → igw-id ; attach-internet-gateway vào vpc
4. create-route-table (public) → rtb-id ; create-route 0.0.0.0/0 → igw ; associate 2 public subnet
5. allocate-address → eipalloc-id
6. create-nat-gateway (public-a) → nat-id ; wait nat-gateway-available (~2 phút)
7. create-route-table (private) → rtb-id ; create-route 0.0.0.0/0 → nat ; associate 2 private subnet
8. create-vpc-endpoint s3 gateway → vpce-id ; gắn vào private route table
9. create-security-group ×4 → sg-id ; authorize-security-group-ingress (tham chiếu nhau)
Xóa thì đảo ngược thứ tự: NAT (đợi xóa xong) → EIP → endpoint → SG (từ cái phụ thuộc trước) → route table → subnet → detach + delete IGW → VPC.
Terraform cho thực tế. CLI để hiểu; dự án thật dùng module terraform-aws-modules/vpc/aws (đã học Terraform ở bài 18.4):
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0"
name = "shop-vpc"
cidr = "10.0.0.0/16"
azs = ["ap-southeast-1a", "ap-southeast-1b"]
public_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
private_subnets = ["10.0.11.0/24", "10.0.12.0/24"]
enable_nat_gateway = true
single_nat_gateway = var.env == "lab" # prod: false, one_nat_gateway_per_az = true
enable_dns_hostnames = true
tags = { Project = "shop-app", Env = var.env }
}
Java: app không biết gì về VPC, nhưng cấu hình phải đúng địa chỉ nội bộ.
# application-aws.yml của shop-app chạy trong shop-private-a
spring:
datasource:
url: jdbc:postgresql://${DB_HOST}:5432/shop # DB_HOST = endpoint RDS, resolve ra 10.0.11.x
username: ${DB_USER}
password: ${DB_PASSWORD} # từ Secrets Manager (bài 22.14)
data:
redis:
host: ${REDIS_HOST} # endpoint ElastiCache, 10.0.12.x
server:
port: 8080
forward-headers-strategy: framework # đọc X-Forwarded-For/Proto từ ALB
Hai điểm Java dev hay vấp: (1) RDS endpoint là tên DNS public nhưng từ trong VPC nó resolve ra private IP nhờ enableDnsHostnames + enableDnsSupport; tắt hai cờ này là app không resolve được. (2) Từ máy local bạn không nối được RDS trong private subnet, đó là đúng thiết kế; debug qua bastion/SSM port forwarding, không mở RDS ra public.
5. Cách hoạt động: lần theo ba gói tin
(a) Người dùng → shop-app
Internet ─► IGW ─► ENI của ALB ở shop-public-a (10.0.1.37, có public IP)
SG shop-alb-sg: 443 từ 0.0.0.0/0 ✔
ALB ─► target 10.0.11.20:8080 (cùng VPC, route "10.0.0.0/16 local" có sẵn)
SG shop-app-sg: 8080 từ shop-alb-sg ✔ (kiểm tra theo SG nguồn, không theo IP)
Response quay về: SG stateful, tự cho
(b) shop-app → api.payment-partner.com (Internet)
10.0.11.20 ─► route table shop-private-rt: 0.0.0.0/0 → nat-0abc
─► NAT Gateway ENI 10.0.1.250 (shop-public-a) đổi source IP thành EIP 13.x.x.x
─► route table shop-public-rt: 0.0.0.0/0 → igw ─► Internet
NAT nhớ cổng nguồn, response quay về đúng 10.0.11.20. Ngược lại từ Internet không mở được kết nối vào.
(c) shop-app → S3 (tải ảnh)
10.0.11.20 ─► DNS s3.ap-southeast-1.amazonaws.com → prefix list pl-xxxx của S3
─► route table: pl-xxxx → vpce-0s3 (gateway endpoint) ─► S3, không qua NAT, không mất tiền NAT
Bucket policy có thể đòi aws:SourceVpce = vpce-0s3 (lỗi Deny ở bài 22.2 là đây)
Thứ tự kiểm tra một gói tin (thứ tự để debug): route table của subnet nguồn → NACL subnet nguồn (out) → SG nguồn (out) → SG đích (in) → NACL subnet đích (in) → app có lắng nghe không.
6. So sánh: những cặp hay nhầm
| Cặp | Khác nhau | Chọn |
|---|---|---|
| IGW vs NAT Gateway | IGW hai chiều cho máy có public IP; NAT một chiều (trong ra) cho máy không có public IP. NAT vẫn cần IGW phía sau | Public subnet: IGW. Private subnet cần ra Internet: NAT |
| NAT Gateway vs NAT instance | Gateway: quản lý, 5–100 Gbps, không SG, HA trong AZ. Instance: EC2 tự quản, rẻ cho lab, có SG, phải tắt source/dest check, là SPOF | Thi: NAT Gateway trừ khi đề nhấn "lowest cost" cho lab hoặc cần "bastion + NAT trên cùng máy" |
| Gateway endpoint vs Interface endpoint | Gateway: chỉ S3 và DynamoDB, miễn phí, là entry trong route table, chỉ dùng từ trong VPC. Interface (PrivateLink): hầu hết dịch vụ, ENI có private IP, tính tiền theo giờ + GB, dùng được từ on-prem qua VPN/DX | S3/DynamoDB từ VPC: gateway. S3 từ on-prem, hoặc SQS/Secrets Manager/ECR/CloudWatch: interface |
| Peering vs Transit Gateway | Peering: 1-1, không transitive, miễn phí tạo (tính data xuyên AZ/region), CIDR không trùng. TGW: hub-spoke, transitive, route table riêng, tính tiền attachment | 2–3 VPC: peering. Nhiều VPC, hybrid, nhiều account: TGW |
| SG vs NACL | ENI/stateful/allow-only vs subnet/stateless/allow+deny | SG là chính; NACL để chặn IP xấu hoặc tuân thủ |
| Site-to-Site VPN vs Direct Connect | VPN: qua Internet, IPsec, vài phút dựng, 1.25 Gbps/tunnel. DX: đường riêng, ổn định, tuần/tháng để dựng, 1–100 Gbps | Nhanh/rẻ: VPN. Băng thông lớn, độ trễ ổn định: DX (+ VPN làm backup) |
| Public subnet vs Private subnet | Chỉ khác route 0.0.0.0/0: → igw là public, → nat (hoặc không có) là private | Đừng tìm checkbox "private" trong Console, không có |
7. Khi nào dùng / không dùng
- Tự dựng VPC: mọi workload thật. Default VPC chỉ để thử nhanh vài phút.
- NAT Gateway: khi máy private cần ra Internet (patch, API bên thứ ba). Nếu chỉ cần gọi dịch vụ AWS → thay bằng endpoint, tiết kiệm.
- Nhiều AZ: luôn, với tối thiểu 2; ALB bắt buộc ≥ 2 subnet ở 2 AZ.
- Không: đặt RDS trong public subnet "để debug từ laptop"; dùng CIDR default
172.31.0.0/16; dùng cùng CIDR cho mọi VPC trong công ty (sau không peering được).
Shop-app trong private subnet chủ yếu chỉ gọi S3 (ảnh), ECR (pull image), Secrets Manager và CloudWatch Logs. Mình nghe nói VPC endpoint cho đi "đường nội bộ" không qua NAT. Nhưng có hai loại gateway và interface, mình không biết loại nào cho dịch vụ nào, và cấu hình thế nào để app tự đi đường đó.
Đúng, và đây là một trong những tối ưu đáng làm nhất trên AWS: bỏ phần lớn traffic ra khỏi NAT, đồng thời an toàn hơn vì dữ liệu không ra Internet. Hai loại endpoint khác nhau ở cách chúng "móc" vào mạng.
1. Gateway endpoint: một dòng trong route table
aws ec2 create-vpc-endpoint --vpc-id $VPC_ID \
--vpc-endpoint-type Gateway \
--service-name com.amazonaws.ap-southeast-1.s3 \
--route-table-ids $PRIVATE_RT_ID \
--tag-specifications 'ResourceType=vpc-endpoint,Tags=[{Key=Name,Value=shop-s3-endpoint},{Key=Project,Value=shop-app},{Key=Env,Value=lab}]' \
--profile shop-lab
Sau lệnh này route table có thêm dòng pl-6fa54006 (S3 prefix list) → vpce-xxx. Mọi request tới IP của S3 ở region này đi thẳng. Chỉ S3 và DynamoDB có loại này, miễn phí, và không cần đổi gì ở app: DNS vẫn là s3.ap-southeast-1.amazonaws.com. Hạn chế: chỉ dùng từ trong chính VPC (không qua peering, VPN, TGW), và chỉ cho S3 cùng region.
Có thể gắn endpoint policy để giới hạn endpoint này chỉ đi tới bucket của bạn:
{ "Statement": [{ "Effect": "Allow", "Principal": "*",
"Action": ["s3:GetObject", "s3:PutObject", "s3:ListBucket"],
"Resource": ["arn:aws:s3:::shop-app-assets-111111111111", "arn:aws:s3:::shop-app-assets-111111111111/*",
"arn:aws:s3:::shop-app-logs-111111111111/*"] }] }
Cẩn thận với Amazon Linux: dnf tải package từ repo đặt trên S3, nên endpoint policy quá chặt làm dnf install hỏng. Thêm resource của repo (arn:aws:s3:::al2023-repos-ap-southeast-1-*/*) hoặc để policy mở cho S3 và siết ở bucket policy.
2. Interface endpoint (PrivateLink): một ENI có IP trong subnet
Tạo cho mỗi dịch vụ, mỗi AZ một ENI (chọn subnet private-a và private-b):
com.amazonaws.ap-southeast-1.ecr.api ← gọi API ECR (GetAuthorizationToken…)
com.amazonaws.ap-southeast-1.ecr.dkr ← docker pull layer
com.amazonaws.ap-southeast-1.secretsmanager
com.amazonaws.ap-southeast-1.logs ← CloudWatch Logs
com.amazonaws.ap-southeast-1.ssm, ssmmessages, ec2messages ← Session Manager (bài 22.4)
(ECR pull còn cần S3 gateway endpoint vì layer nằm trên S3)
aws ec2 create-vpc-endpoint --vpc-id $VPC_ID \
--vpc-endpoint-type Interface \
--service-name com.amazonaws.ap-southeast-1.secretsmanager \
--subnet-ids $PRIVATE_A_ID $PRIVATE_B_ID \
--security-group-ids $ENDPOINT_SG_ID \ # SG cho 443 từ shop-app-sg
--private-dns-enabled \
--tag-specifications 'ResourceType=vpc-endpoint,Tags=[{Key=Name,Value=shop-secrets-endpoint},{Key=Project,Value=shop-app},{Key=Env,Value=lab}]' \
--profile shop-lab
Cờ quan trọng: --private-dns-enabled. Khi bật, tên secretsmanager.ap-southeast-1.amazonaws.com từ trong VPC resolve ra IP của ENI endpoint (10.0.11.x) thay vì IP public. App và SDK không đổi một dòng. Không bật thì phải dùng tên DNS riêng của endpoint (vpce-xxx.secretsmanager...) và cấu hình endpointOverride trong SDK, phiền và dễ quên.
Interface endpoint tính tiền theo giờ cho mỗi ENI (mỗi AZ) cộng theo GB. Với traffic nhỏ, NAT có thể rẻ hơn 6–7 endpoint; với traffic lớn (pull image hàng ngày, log nhiều) thì endpoint rẻ hơn hẳn. Tính cho từng dịch vụ bằng pricing page, đừng đoán. Bài 22.19 có góc nhìn chi phí; ở đây là góc kỹ thuật.
3. Bảng chọn
| Dịch vụ | Loại endpoint | Ghi chú |
|---|---|---|
| S3, DynamoDB | Gateway (miễn phí) | S3 cũng có interface endpoint cho on-prem/peering, hiếm cần |
| ECR (api + dkr), ECS, Secrets Manager, SSM, CloudWatch Logs/Monitoring, SQS, SNS, KMS, STS, Lambda, API Gateway… | Interface | Mỗi dịch vụ một endpoint, mỗi AZ một ENI |
| Dịch vụ của bên thứ ba / VPC khác expose qua NLB | Interface (PrivateLink endpoint service) | Cách "bán" API nội bộ cho VPC khác mà không peering, không trùng CIDR cũng được |
4. Khi nào vẫn cần NAT
Gói OS từ repo ngoài AWS, API bên thứ ba (payment gateway, gửi SMS), Maven Central nếu build trên máy private. Nếu shop-app chỉ gọi dịch vụ AWS, bạn có thể xóa NAT và dùng SSM Session Manager (qua endpoint) để vào máy. Lab bên dưới làm S3 gateway endpoint; interface endpoint để bài 22.13 (ECS) khi thật sự cần pull image từ private subnet.
Team analytics có VPC riêng (analytics-vpc, 10.1.0.0/16) và muốn đọc dữ liệu từ Redis replica của shop-app qua private IP. Hai VPC nối nhau thì dùng peering hay Transit Gateway? Nếu sau này có thêm VPC thứ ba, thứ tư thì sao? Và nếu VPC của họ cũng là 10.0.0.0/16 thì có nối được không?
Ba câu, ba câu trả lời ngắn: hai VPC thì peering; từ bốn, năm VPC trở lên hoặc có VPN/on-prem thì Transit Gateway; CIDR trùng thì không peering được, phải đi đường khác (PrivateLink hoặc đánh lại địa chỉ).
1. Peering: nối dây giữa hai VPC
shop-vpc 10.0.0.0/16 ◄──── pcx-0abc ────► analytics-vpc 10.1.0.0/16
Việc phải làm ở CẢ HAI bên (hay quên bên thứ hai):
1. create-vpc-peering-connection (requester) → accept-vpc-peering-connection (accepter; có thể khác account/region)
2. route table shop-private-rt : 10.1.0.0/16 → pcx-0abc
route table analytics-private-rt : 10.0.0.0/16 → pcx-0abc
3. SG shop-cache-sg: in 6379 từ 10.1.5.0/24 (hoặc từ SG của họ nếu cùng region: tham chiếu được SG xuyên peering)
4. (tùy chọn) bật DNS resolution qua peering để resolve hostname private của nhau
Tính chất phải nhớ cho đề thi: không transitive. Nếu analytics-vpc còn peering với ml-vpc, thì shop-vpc không tự thấy ml-vpc; phải tạo peering shop↔ml riêng. N VPC full mesh cần N(N−1)/2 kết nối: 5 VPC = 10, 10 VPC = 45. Peering không tính phí tạo, chỉ tính data truyền xuyên AZ/region.
2. Transit Gateway: một hub, nhiều nhánh
┌────────────────────┐
shop-vpc ──────┤ ├────── analytics-vpc
ml-vpc ────────┤ Transit Gateway ├────── shared-services-vpc (DNS, AD)
VPN on-prem ───┤ (route table TGW) ├────── Direct Connect gateway
└────────────────────┘
Mỗi VPC: 1 attachment (ENI ở mỗi AZ) + route 10.0.0.0/8 → tgw-0abc trong route table của VPC
TGW route table: quyết định nhánh nào thấy nhánh nào (có thể cô lập prod/dev)
Transitive, tập trung, có thể chia sẻ sang account khác qua RAM (Resource Access Manager), nối được Site-to-Site VPN và Direct Connect, hỗ trợ multicast và inter-region peering giữa các TGW. Đổi lại tính tiền theo giờ mỗi attachment và theo GB, và thêm một "hop" nên độ trễ nhích lên chút. Với hai VPC trong lab, TGW là thừa.
3. CIDR trùng
Peering và TGW đều dựa trên route theo đích; hai bên cùng 10.0.0.0/16 thì route "10.0.0.0/16 → đâu?" không có nghĩa. Lựa chọn:
- PrivateLink: team analytics không cần "vào" VPC của bạn; bạn đặt NLB trước Redis (hoặc trước API đọc dữ liệu), tạo endpoint service, họ tạo interface endpoint trong VPC của họ. Hai VPC có thể trùng CIDR hoàn toàn. Đây cũng là cách đúng về bảo mật: chỉ expose một dịch vụ, không expose cả mạng.
- Thêm secondary CIDR cho một bên (ví dụ 100.64.0.0/16) và đặt tài nguyên cần nối vào đó.
- Private NAT Gateway để dịch địa chỉ giữa hai dải trùng, phức tạp, ít dùng.
Đây là lý do công ty nên có IP plan: mỗi VPC một dải riêng, ví dụ shop 10.0/16, analytics 10.1/16, ml 10.2/16, và dùng IPAM (VPC IP Address Manager) để cấp phát. Default VPC 172.31.0.0/16 ở mọi nơi là ví dụ về việc không có plan.
4. Chọn cho tình huống của bạn
Hai VPC, cùng region, CIDR không trùng, chỉ cần đọc Redis: peering + SG cho 6379 từ dải của họ. Nhưng hãy cân nhắc PrivateLink hoặc một API đọc riêng thay vì cho team khác nối thẳng vào cache của bạn; nối thẳng tức là mọi thay đổi cấu trúc dữ liệu Redis của bạn đều ảnh hưởng tới họ.
Mình đã mở 8080 trong shop-app-sg rồi mà target của ALB vẫn báo unhealthy, và từ một EC2 khác curl vào app cũng treo. Trong VPC thì debug mạng theo thứ tự nào cho khỏi mò?
"Treo" (timeout) và "bị từ chối" (connection refused) là hai triệu chứng khác nhau, và chúng chỉ ngay hướng tìm:
curl: (28) Connection timed out → gói tin bị DROP đâu đó: SG, NACL, route, hoặc máy đích không tồn tại
curl: (7) Connection refused → gói tin TỚI máy nhưng không có gì nghe port đó: app chưa chạy / sai port / bind 127.0.0.1
1. Checklist theo đường đi của gói tin
- App có nghe đúng port và interface không? Vào máy (SSM Session Manager) rồi
ss -lntp | grep 8080. Thấy127.0.0.1:8080là app chỉ nhận local; Spring Boot mặc định0.0.0.0, nhưngserver.addresshay Docker-p 127.0.0.1:8080:8080có thể đổi.curl localhost:8080/actuator/healthngay trên máy phải UP trước đã. - SG của đích: rule 8080 nguồn là gì? Nếu nguồn là
shop-alb-sgthì chỉ ALB vào được;curltừ EC2 khác bị drop là đúng thiết kế, không phải lỗi. Lỗi hay gặp: mở 8080 nhưng từ CIDR laptop, trong khi ALB có IP trong 10.0.1.x/10.0.2.x.aws ec2 describe-security-groups --group-ids $APP_SG_ID --profile shop-lab \ --query 'SecurityGroups[].IpPermissions[].{port:FromPort,src:UserIdGroupPairs[].GroupId,cidr:IpRanges[].CidrIp}' - SG của nguồn: outbound bị thu hẹp không? Mặc định allow all, nhưng nếu ai đó siết outbound của ALB thì ALB không gọi ra được target.
- NACL của hai subnet: nếu đã sửa NACL, nhớ nó stateless: inbound 8080 và outbound ephemeral 1024–65535 phải mở. Để mặc định thì bỏ qua bước này.
- Route table: cùng VPC luôn có route
local, không cần sửa. Khác VPC (peering) thì kiểm tra route hai chiều. - Health check của target group: path
/actuator/health, porttraffic-port, mã 200. App trả 200 nhưng chậm hơn timeout 5 giây (JVM đang khởi động) cũng unhealthy; tăngHealthCheckTimeoutSeconds/intervalhoặc dùng/actuator/health/readiness(bài 22.5).
2. Hai công cụ thay cho đoán
VPC Reachability Analyzer: bạn chỉ nguồn (ENI của ALB hoặc instance) và đích (ENI instance, port 8080), AWS phân tích cấu hình (không gửi gói tin thật) và nói chính xác hop nào chặn: "Security group sg-xxx has no inbound rule for port 8080 from…". Tính tiền theo mỗi lần phân tích (khoảng vài chục cent, kiểm tra pricing).
aws ec2 create-network-insights-path --source $ALB_ENI_ID --destination $APP_ENI_ID \
--destination-port 8080 --protocol tcp --profile shop-lab --query NetworkInsightsPath.NetworkInsightsPathId
aws ec2 start-network-insights-analysis --network-insights-path-id nip-xxx --profile shop-lab
aws ec2 describe-network-insights-analyses --network-insights-analysis-ids nia-xxx --profile shop-lab \
--query 'NetworkInsightsAnalyses[].{reachable:NetworkPathFound,why:Explanations[].{code:ExplanationCode,sg:SecurityGroup.GroupId}}'
VPC Flow Logs: ghi metadata mọi kết nối (src, dst, port, ACCEPT/REJECT) ở mức ENI/subnet/VPC vào CloudWatch Logs hoặc S3. Không có payload. Dòng REJECT tới 10.0.11.20:8080 là bằng chứng SG/NACL chặn; không có dòng nào là gói tin chưa tới (route/DNS sai).
aws ec2 create-flow-logs --resource-type VPC --resource-ids $VPC_ID \
--traffic-type REJECT --log-destination-type cloud-watch-logs \
--log-group-name /shop/vpc-flow-logs --deliver-logs-permission-arn $FLOW_LOGS_ROLE_ARN \
--max-aggregation-interval 60 --profile shop-lab
# Logs Insights: filter dstPort = 8080 and action = "REJECT" | stats count() by srcAddr
Flow Logs tính tiền theo lượng log ghi (CloudWatch Logs ingestion), nên lab chỉ bật REJECT và tắt sau khi xong.
3. Thứ tự rút gọn để nhớ
refused → nhìn app (ss -lntp, bind address, port)
timeout → SG đích (nguồn đúng chưa?) → NACL (nếu đã đụng) → route (nếu khác VPC) → Reachability Analyzer
unhealthy nhưng curl OK → health check path/port/timeout/thời gian khởi động JVM
Cho mình bài lab để tự tay làm phần này nhé: dựng shop-vpc đủ 2 AZ với public/private subnet, IGW, NAT, route table, 4 Security Group, và có script dọn để không bị tính tiền.
Lab 22.3: Dựng shop-vpc hai AZ bằng CLI, lưu ID cho các bài sau
Đây là lab nền cho các bài 22.4 → 22.13: EC2, ALB, RDS, ElastiCache, ECS đều đặt vào mạng này. Ta làm theo script: mỗi bước ghi ID vào file ~/shop-lab/shop-vpc.env, các lab sau chỉ cần source file đó. Cuối lab có cleanup-vpc.sh để xóa đúng thứ tự; bạn có thể dựng và xóa nhiều lần.
Mục tiêu
shop-vpc10.0.0.0/16 với 4 subnet đúng tên, đúng AZ, đúng CIDR như bảng thiết kế.shop-public-rtcó route0.0.0.0/0 → igw;shop-private-rtcó route0.0.0.0/0 → natvàS3 prefix list → vpce.- 4 Security Group tham chiếu nhau đúng chuỗi ALB → app → db/cache.
- Một instance test trong private subnet
curlđược ra Internet (qua NAT) và tải được từ S3 (qua endpoint), không có public IP.
Chi phí & thời gian
- VPC, subnet, route table, IGW, SG, S3 gateway endpoint: miễn phí.
- NAT Gateway: không trong Free Tier, tính theo giờ (khoảng 0.05–0.06 USD/giờ ở Singapore) + theo GB. Để quên một tháng ≈ 40 USD. Elastic IP và mọi public IPv4: tính theo giờ (khoảng 0.005 USD/giờ). Instance test
t3.micro: Free Tier 750 giờ/tháng nếu tài khoản đủ điều kiện. - Làm hết trong một buổi và chạy cleanup, chi phí dưới 0.5 USD. Thời gian: 60–90 phút.
Chuẩn bị
- Lab 22.1 xong:
source ~/shop-lab.envcho$ACCOUNT_ID,$AWS_REGION, profileshop-lab. mkdir -p ~/shop-lab/vpc && cd ~/shop-lab/vpc.- Kiểm tra region còn quota:
aws ec2 describe-vpcs --profile shop-lab --query 'length(Vpcs)'(mặc định 5 VPC/region).
Các bước
Bước 1: VPC và DNS.
TAGS='{Key=Project,Value=shop-app},{Key=Env,Value=lab}'
ENV_FILE=~/shop-lab/shop-vpc.env
VPC_ID=$(aws ec2 create-vpc --cidr-block 10.0.0.0/16 \
--tag-specifications "ResourceType=vpc,Tags=[{Key=Name,Value=shop-vpc},$TAGS]" \
--query Vpc.VpcId --output text --profile shop-lab)
aws ec2 modify-vpc-attribute --vpc-id $VPC_ID --enable-dns-hostnames '{"Value":true}' --profile shop-lab
aws ec2 modify-vpc-attribute --vpc-id $VPC_ID --enable-dns-support '{"Value":true}' --profile shop-lab
echo "export VPC_ID=$VPC_ID" > $ENV_FILE
echo $VPC_ID
Console: VPC → Your VPCs → Create VPC → VPC only, CIDR 10.0.0.0/16, tag Name. Sau đó Actions → Edit VPC settings → Enable DNS hostnames. (Console có "VPC and more" dựng mọi thứ một lần; ta không dùng để hiểu từng mảnh.)
Bước 2: Bốn subnet.
mk_subnet() { # name cidr az
aws ec2 create-subnet --vpc-id $VPC_ID --cidr-block $2 --availability-zone $3 \
--tag-specifications "ResourceType=subnet,Tags=[{Key=Name,Value=$1},$TAGS]" \
--query Subnet.SubnetId --output text --profile shop-lab
}
PUBLIC_A_ID=$(mk_subnet shop-public-a 10.0.1.0/24 ap-southeast-1a)
PUBLIC_B_ID=$(mk_subnet shop-public-b 10.0.2.0/24 ap-southeast-1b)
PRIVATE_A_ID=$(mk_subnet shop-private-a 10.0.11.0/24 ap-southeast-1a)
PRIVATE_B_ID=$(mk_subnet shop-private-b 10.0.12.0/24 ap-southeast-1b)
# public subnet: instance tạo ra tự có public IP
for s in $PUBLIC_A_ID $PUBLIC_B_ID; do
aws ec2 modify-subnet-attribute --subnet-id $s --map-public-ip-on-launch --profile shop-lab
done
cat >> $ENV_FILE <<EOF
export PUBLIC_A_ID=$PUBLIC_A_ID
export PUBLIC_B_ID=$PUBLIC_B_ID
export PRIVATE_A_ID=$PRIVATE_A_ID
export PRIVATE_B_ID=$PRIVATE_B_ID
EOF
Console: VPC → Subnets → Create subnet, chọn VPC, thêm 4 subnet với AZ và CIDR; sau đó với 2 public: Actions → Edit subnet settings → Enable auto-assign public IPv4. Lưu ý tên AZ ap-southeast-1a ở tài khoản bạn có thể map sang data center khác tài khoản người khác (AZ ID apse1-az1 mới là cố định), không sao cho lab.
Bước 3: Internet Gateway.
IGW_ID=$(aws ec2 create-internet-gateway \
--tag-specifications "ResourceType=internet-gateway,Tags=[{Key=Name,Value=shop-igw},$TAGS]" \
--query InternetGateway.InternetGatewayId --output text --profile shop-lab)
aws ec2 attach-internet-gateway --internet-gateway-id $IGW_ID --vpc-id $VPC_ID --profile shop-lab
echo "export IGW_ID=$IGW_ID" >> $ENV_FILE
Console: VPC → Internet gateways → Create → Actions → Attach to VPC.
Bước 4: Route table public.
PUBLIC_RT_ID=$(aws ec2 create-route-table --vpc-id $VPC_ID \
--tag-specifications "ResourceType=route-table,Tags=[{Key=Name,Value=shop-public-rt},$TAGS]" \
--query RouteTable.RouteTableId --output text --profile shop-lab)
aws ec2 create-route --route-table-id $PUBLIC_RT_ID \
--destination-cidr-block 0.0.0.0/0 --gateway-id $IGW_ID --profile shop-lab
aws ec2 associate-route-table --route-table-id $PUBLIC_RT_ID --subnet-id $PUBLIC_A_ID --profile shop-lab
aws ec2 associate-route-table --route-table-id $PUBLIC_RT_ID --subnet-id $PUBLIC_B_ID --profile shop-lab
echo "export PUBLIC_RT_ID=$PUBLIC_RT_ID" >> $ENV_FILE
Console: VPC → Route tables → Create → tab Routes → Edit routes → Add 0.0.0.0/0 → Internet Gateway → tab Subnet associations → Edit → chọn 2 public. Từ giây này hai subnet đó là "public".
Bước 5: Elastic IP + NAT Gateway (bắt đầu tính tiền).
EIP_ALLOC_ID=$(aws ec2 allocate-address --domain vpc \
--tag-specifications "ResourceType=elastic-ip,Tags=[{Key=Name,Value=shop-nat-eip},$TAGS]" \
--query AllocationId --output text --profile shop-lab)
NAT_ID=$(aws ec2 create-nat-gateway --subnet-id $PUBLIC_A_ID --allocation-id $EIP_ALLOC_ID \
--connectivity-type public \
--tag-specifications "ResourceType=natgateway,Tags=[{Key=Name,Value=shop-nat-a},$TAGS]" \
--query NatGateway.NatGatewayId --output text --profile shop-lab)
echo "export EIP_ALLOC_ID=$EIP_ALLOC_ID" >> $ENV_FILE
echo "export NAT_ID=$NAT_ID" >> $ENV_FILE
echo "Đợi NAT available (1-3 phút)..."
aws ec2 wait nat-gateway-available --nat-gateway-ids $NAT_ID --profile shop-lab && echo "NAT OK"
Console: VPC → NAT gateways → Create: subnet shop-public-a, Connectivity public, Allocate Elastic IP. NAT đặt trong public subnet (nó cần ra IGW), phục vụ private subnet. Đặt nhầm vào private subnet là lỗi kinh điển: NAT tạo được nhưng không có đường ra.
Bước 6: Route table private + S3 gateway endpoint.
PRIVATE_RT_ID=$(aws ec2 create-route-table --vpc-id $VPC_ID \
--tag-specifications "ResourceType=route-table,Tags=[{Key=Name,Value=shop-private-rt},$TAGS]" \
--query RouteTable.RouteTableId --output text --profile shop-lab)
aws ec2 create-route --route-table-id $PRIVATE_RT_ID \
--destination-cidr-block 0.0.0.0/0 --nat-gateway-id $NAT_ID --profile shop-lab
aws ec2 associate-route-table --route-table-id $PRIVATE_RT_ID --subnet-id $PRIVATE_A_ID --profile shop-lab
aws ec2 associate-route-table --route-table-id $PRIVATE_RT_ID --subnet-id $PRIVATE_B_ID --profile shop-lab
S3_VPCE_ID=$(aws ec2 create-vpc-endpoint --vpc-id $VPC_ID --vpc-endpoint-type Gateway \
--service-name com.amazonaws.${AWS_REGION}.s3 --route-table-ids $PRIVATE_RT_ID \
--tag-specifications "ResourceType=vpc-endpoint,Tags=[{Key=Name,Value=shop-s3-endpoint},$TAGS]" \
--query VpcEndpoint.VpcEndpointId --output text --profile shop-lab)
echo "export PRIVATE_RT_ID=$PRIVATE_RT_ID" >> $ENV_FILE
echo "export S3_VPCE_ID=$S3_VPCE_ID" >> $ENV_FILE
aws ec2 describe-route-tables --route-table-ids $PRIVATE_RT_ID --profile shop-lab \
--query 'RouteTables[].Routes[].{dst:DestinationCidrBlock,pl:DestinationPrefixListId,via:join(`,`,[GatewayId,NatGatewayId][?@!=null])}' --output table
# 10.0.0.0/16 local | 0.0.0.0/0 nat-xxx | pl-xxxx vpce-xxx
Console: tạo route table, route 0.0.0.0/0 → NAT Gateway, gắn 2 private subnet. Endpoint: VPC → Endpoints → Create: AWS services, tìm s3 loại Gateway, chọn VPC và tick route table shop-private-rt.
Bước 7: Bốn Security Group.
mk_sg() { # name description
aws ec2 create-security-group --group-name $1 --description "$2" --vpc-id $VPC_ID \
--tag-specifications "ResourceType=security-group,Tags=[{Key=Name,Value=$1},$TAGS]" \
--query GroupId --output text --profile shop-lab
}
ALB_SG_ID=$(mk_sg shop-alb-sg "shop-app ALB: 80/443 from Internet")
APP_SG_ID=$(mk_sg shop-app-sg "shop-app instances: 8080 from ALB")
DB_SG_ID=$(mk_sg shop-db-sg "shop-app RDS: 5432 from app")
CACHE_SG_ID=$(mk_sg shop-cache-sg "shop-app Redis: 6379 from app")
aws ec2 authorize-security-group-ingress --group-id $ALB_SG_ID --profile shop-lab \
--ip-permissions \
"IpProtocol=tcp,FromPort=80,ToPort=80,IpRanges=[{CidrIp=0.0.0.0/0,Description=HTTP}]" \
"IpProtocol=tcp,FromPort=443,ToPort=443,IpRanges=[{CidrIp=0.0.0.0/0,Description=HTTPS}]"
aws ec2 authorize-security-group-ingress --group-id $APP_SG_ID --profile shop-lab \
--ip-permissions "IpProtocol=tcp,FromPort=8080,ToPort=8080,UserIdGroupPairs=[{GroupId=$ALB_SG_ID,Description=from ALB}]"
aws ec2 authorize-security-group-ingress --group-id $DB_SG_ID --profile shop-lab \
--ip-permissions "IpProtocol=tcp,FromPort=5432,ToPort=5432,UserIdGroupPairs=[{GroupId=$APP_SG_ID,Description=from app}]"
aws ec2 authorize-security-group-ingress --group-id $CACHE_SG_ID --profile shop-lab \
--ip-permissions "IpProtocol=tcp,FromPort=6379,ToPort=6379,UserIdGroupPairs=[{GroupId=$APP_SG_ID,Description=from app}]"
cat >> $ENV_FILE <<EOF
export ALB_SG_ID=$ALB_SG_ID
export APP_SG_ID=$APP_SG_ID
export DB_SG_ID=$DB_SG_ID
export CACHE_SG_ID=$CACHE_SG_ID
EOF
cat $ENV_FILE
Console: EC2 → Security Groups → Create: chọn VPC shop-vpc, inbound rule; ở ô Source gõ sg- để chọn SG khác thay cho CIDR. Không đụng outbound (mặc định allow all). Không dùng default SG của VPC cho bất kỳ thứ gì.
Bước 8: Instance test trong private subnet (kiểm tra NAT và endpoint). Vào máy bằng SSM Session Manager, không cần SSH, không cần port 22, không cần public IP. Cần role có AmazonSSMManagedInstanceCore (role này bài 22.4 dùng tiếp).
cat > trust-ec2.json <<'EOF'
{ "Version": "2012-10-17", "Statement": [{ "Effect": "Allow",
"Principal": { "Service": "ec2.amazonaws.com" }, "Action": "sts:AssumeRole" }] }
EOF
aws iam create-role --role-name shop-net-test-role --assume-role-policy-document file://trust-ec2.json \
--tags Key=Project,Value=shop-app Key=Env,Value=lab --profile shop-lab
aws iam attach-role-policy --role-name shop-net-test-role \
--policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore --profile shop-lab
aws iam create-instance-profile --instance-profile-name shop-net-test-role --profile shop-lab
aws iam add-role-to-instance-profile --instance-profile-name shop-net-test-role --role-name shop-net-test-role --profile shop-lab
sleep 10 # instance profile cần vài giây để lan
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)
TEST_ID=$(aws ec2 run-instances --image-id $AMI_ID --instance-type t3.micro \
--subnet-id $PRIVATE_A_ID --security-group-ids $APP_SG_ID \
--iam-instance-profile Name=shop-net-test-role \
--metadata-options HttpTokens=required \
--tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=shop-net-test},$TAGS]" \
--query 'Instances[0].InstanceId' --output text --profile shop-lab)
aws ec2 wait instance-running --instance-ids $TEST_ID --profile shop-lab
aws ec2 describe-instances --instance-ids $TEST_ID --profile shop-lab \
--query 'Reservations[].Instances[].{private:PrivateIpAddress,public:PublicIpAddress}'
# public: null ← đúng: private subnet không có public IP
# Đợi SSM agent đăng ký (1-2 phút; agent ra Internet qua NAT để nối SSM)
aws ssm describe-instance-information --profile shop-lab --query 'InstanceInformationList[].{id:InstanceId,ping:PingStatus}'
aws ssm start-session --target $TEST_ID --profile shop-lab # cần Session Manager plugin cho CLI
Trong phiên shell trên máy:
curl -s -m 5 https://checkip.amazonaws.com # in ra EIP của NAT → ra Internet qua NAT OK
curl -s -m 5 -o /dev/null -w '%{http_code}\n' https://s3.ap-southeast-1.amazonaws.com # 405/403 là nối được
dig +short s3.ap-southeast-1.amazonaws.com # IP trong prefix list của S3
traceroute -n -m 3 s3.ap-southeast-1.amazonaws.com 2>/dev/null | head -3 # không đi qua 10.0.1.x (NAT)
exit
Console: EC2 → Instances → chọn → Connect → Session Manager. Nếu "Session Manager" mờ: role chưa gắn hoặc agent chưa ra được Internet (NAT/route sai), đó chính là bài test.
Kiểm tra kết quả
source ~/shop-lab/shop-vpc.env
# 1. Subnet đúng AZ, CIDR, cờ public IP
aws ec2 describe-subnets --filters Name=vpc-id,Values=$VPC_ID --profile shop-lab \
--query 'Subnets[].{name:Tags[?Key==`Name`]|[0].Value,cidr:CidrBlock,az:AvailabilityZone,publicIp:MapPublicIpOnLaunch}' --output table
# 2. Public RT có igw, private RT có nat + vpce
aws ec2 describe-route-tables --filters Name=vpc-id,Values=$VPC_ID --profile shop-lab \
--query 'RouteTables[].{name:Tags[?Key==`Name`]|[0].Value,routes:Routes[].join(`→`,[to_string(DestinationCidrBlock||DestinationPrefixListId), to_string(GatewayId||NatGatewayId)])}'
# 3. NAT available, có EIP
aws ec2 describe-nat-gateways --filter Name=vpc-id,Values=$VPC_ID --profile shop-lab \
--query 'NatGateways[].{state:State,eip:NatGatewayAddresses[0].PublicIp,subnet:SubnetId}'
# 4. SG chain: app chỉ từ alb, db chỉ từ app
aws ec2 describe-security-groups --group-ids $APP_SG_ID $DB_SG_ID $CACHE_SG_ID --profile shop-lab \
--query 'SecurityGroups[].{name:GroupName,from:IpPermissions[].UserIdGroupPairs[].GroupId|[0],port:IpPermissions[0].FromPort}'
# 5. Instance test: không public IP, SSM Online, curl ra ngoài OK (đã làm ở bước 8)
Thử thêm
- Xóa route NAT rồi curl lại.
aws ec2 delete-route --route-table-id $PRIVATE_RT_ID --destination-cidr-block 0.0.0.0/0→ trong phiên SSM đang mở,curl checkip.amazonaws.comtimeout, nhưngcurl s3.ap-southeast-1.amazonaws.comvẫn được (endpoint). Thêm route lại bằngcreate-route ... --nat-gateway-id. Phiên SSM đang mở vẫn sống vì kết nối đã thiết lập; mở phiên mới sẽ không được. - Chặn bằng NACL. Tạo NACL mới deny
0.0.0.0/0port 443 outbound, gắn vào shop-private-a → mọi thứ HTTPS chết, kể cả SSM. Gỡ bằng cách gắn lại NACL default. Cảm nhận "stateless + áp cả subnet". - Reachability Analyzer. Tạo path từ ENI của instance test tới
8.8.8.8port 443; phân tích sẽ vẽ đường instance → route → NAT → IGW. Tốn một ít tiền mỗi lần phân tích. - Flow Logs REJECT. Bật như mục trước, từ instance test
curl 10.0.11.200:5432(không có gì) rồi xem log REJECT xuất hiện sau 1–2 phút. Tắt khi xong.
Dọn tài nguyên
Hai mức: (a) kết thúc buổi học nhưng còn làm bài sau: chỉ terminate instance test và xóa NAT + release EIP (thứ tốn tiền), giữ VPC/subnet/RT/SG (miễn phí); bài sau tạo lại NAT bằng lệnh ở bước 5–6. (b) bỏ hẳn: chạy script dưới.
# (a) tắt phần tốn tiền, giữ khung
source ~/shop-lab/shop-vpc.env
aws ec2 terminate-instances --instance-ids $TEST_ID --profile shop-lab
aws ec2 wait instance-terminated --instance-ids $TEST_ID --profile shop-lab
aws ec2 delete-nat-gateway --nat-gateway-id $NAT_ID --profile shop-lab
aws ec2 wait nat-gateway-deleted --nat-gateway-ids $NAT_ID --profile shop-lab # 1-2 phút
aws ec2 release-address --allocation-id $EIP_ALLOC_ID --profile shop-lab
aws ec2 delete-route --route-table-id $PRIVATE_RT_ID --destination-cidr-block 0.0.0.0/0 --profile shop-lab
#!/usr/bin/env bash
# (b) ~/shop-lab/vpc/cleanup-vpc.sh : xóa toàn bộ shop-vpc theo đúng thứ tự phụ thuộc
set -u
source ~/shop-lab/shop-vpc.env
P="--profile shop-lab"
echo "1. instances trong VPC"
IDS=$(aws ec2 describe-instances --filters Name=vpc-id,Values=$VPC_ID Name=instance-state-name,Values=pending,running,stopping,stopped \
--query 'Reservations[].Instances[].InstanceId' --output text $P)
[ -n "$IDS" ] && aws ec2 terminate-instances --instance-ids $IDS $P >/dev/null && aws ec2 wait instance-terminated --instance-ids $IDS $P
echo "2. NAT gateway + EIP (tốn tiền, xóa trước)"
for n in $(aws ec2 describe-nat-gateways --filter Name=vpc-id,Values=$VPC_ID Name=state,Values=available,pending --query 'NatGateways[].NatGatewayId' --output text $P); do
aws ec2 delete-nat-gateway --nat-gateway-id $n $P >/dev/null
aws ec2 wait nat-gateway-deleted --nat-gateway-ids $n $P
done
for a in $(aws ec2 describe-addresses --filters Name=tag:Project,Values=shop-app --query 'Addresses[].AllocationId' --output text $P); do
aws ec2 release-address --allocation-id $a $P
done
echo "3. VPC endpoints"
VPCES=$(aws ec2 describe-vpc-endpoints --filters Name=vpc-id,Values=$VPC_ID --query 'VpcEndpoints[].VpcEndpointId' --output text $P)
[ -n "$VPCES" ] && aws ec2 delete-vpc-endpoints --vpc-endpoint-ids $VPCES $P >/dev/null
echo "4. Security groups (gỡ rule tham chiếu trước, xóa theo thứ tự phụ thuộc)"
for sg in $CACHE_SG_ID $DB_SG_ID $APP_SG_ID $ALB_SG_ID; do
PERMS=$(aws ec2 describe-security-groups --group-ids $sg --query 'SecurityGroups[0].IpPermissions' $P)
[ "$PERMS" != "[]" ] && aws ec2 revoke-security-group-ingress --group-id $sg --ip-permissions "$PERMS" $P >/dev/null
done
for sg in $CACHE_SG_ID $DB_SG_ID $APP_SG_ID $ALB_SG_ID; do aws ec2 delete-security-group --group-id $sg $P; done
echo "5. Route tables (gỡ association, xóa; main RT tự đi theo VPC)"
for rt in $PUBLIC_RT_ID $PRIVATE_RT_ID; do
for assoc in $(aws ec2 describe-route-tables --route-table-ids $rt --query 'RouteTables[0].Associations[?!Main].RouteTableAssociationId' --output text $P); do
aws ec2 disassociate-route-table --association-id $assoc $P
done
aws ec2 delete-route-table --route-table-id $rt $P
done
echo "6. Subnets"
for s in $PUBLIC_A_ID $PUBLIC_B_ID $PRIVATE_A_ID $PRIVATE_B_ID; do aws ec2 delete-subnet --subnet-id $s $P; done
echo "7. IGW"
aws ec2 detach-internet-gateway --internet-gateway-id $IGW_ID --vpc-id $VPC_ID $P
aws ec2 delete-internet-gateway --internet-gateway-id $IGW_ID $P
echo "8. VPC"
aws ec2 delete-vpc --vpc-id $VPC_ID $P && echo "shop-vpc deleted" && rm -f ~/shop-lab/shop-vpc.env
echo "9. IAM test role (miễn phí, nhưng dọn cho gọn)"
aws iam remove-role-from-instance-profile --instance-profile-name shop-net-test-role --role-name shop-net-test-role $P
aws iam delete-instance-profile --instance-profile-name shop-net-test-role $P
aws iam detach-role-policy --role-name shop-net-test-role --policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore $P
aws iam delete-role --role-name shop-net-test-role $P
echo "Kiểm kê còn gì gắn tag Project=shop-app:"
aws resourcegroupstaggingapi get-resources --tag-filters Key=Project,Values=shop-app --query 'ResourceTagMappingList[].ResourceARN' $P
chmod +x cleanup-vpc.sh && ./cleanup-vpc.sh. Nếu delete-vpc báo DependencyViolation, còn ENI nào đó (ALB, RDS, Lambda của bài sau) đang nằm trong VPC: aws ec2 describe-network-interfaces --filters Name=vpc-id,Values=$VPC_ID để tìm. Thứ không được quên: NAT Gateway và EIP; mọi thứ khác trong lab này miễn phí.
Phỏng vấn và đề thi SAA hỏi về VPC và networking thì sao?
Q1. Public subnet và private subnet khác nhau ở đâu? Máy trong private subnet ra Internet bằng gì?
🇻🇳 Tiếng Việt:
Khác nhau duy nhất ở route table: subnet có route 0.0.0.0/0 trỏ tới Internet Gateway là public, còn lại là private. Máy trong private subnet không có public IP; muốn ra Internet thì route 0.0.0.0/0 trỏ tới NAT Gateway đặt trong public subnet, NAT đổi địa chỉ nguồn sang Elastic IP và chỉ cho kết nối từ trong ra. Với dịch vụ AWS như S3 em dùng VPC endpoint để không qua NAT.
🇬🇧 English:
The only difference is the route table: a subnet whose route table sends 0.0.0.0/0 to an Internet Gateway is public, everything else is private. Instances in a private subnet have no public IP; to reach the Internet their route table sends 0.0.0.0/0 to a NAT Gateway placed in a public subnet, which translates the source address to an Elastic IP and only allows outbound-initiated connections. For AWS services like S3 I use VPC endpoints so that traffic never goes through NAT.
Q2. Bạn thiết kế Security Group cho ứng dụng ba tầng thế nào?
🇻🇳 Tiếng Việt:
Mỗi tầng một Security Group và tham chiếu nhau thay vì dùng CIDR: SG của ALB mở 80/443 từ Internet; SG của app mở 8080 chỉ từ SG của ALB; SG của database mở 5432 chỉ từ SG của app. Scale thêm instance hay đổi subnet không phải sửa rule. SG là stateful nên không cần rule cho chiều trả về. NACL để mặc định, chỉ dùng khi cần chặn cứng một dải IP ở mức subnet.
🇬🇧 English:
One Security Group per tier, referencing each other instead of CIDR ranges: the ALB group allows 80/443 from the Internet; the app group allows 8080 only from the ALB group; the database group allows 5432 only from the app group. Adding instances or changing subnets requires no rule changes. Security Groups are stateful, so no return-traffic rules are needed. I leave NACLs at their defaults and only use them to hard-block IP ranges at the subnet level.
Q3. Gateway endpoint và interface endpoint khác nhau thế nào?
🇻🇳 Tiếng Việt:
Gateway endpoint chỉ có cho S3 và DynamoDB, miễn phí, là một entry trong route table trỏ prefix list của dịch vụ tới endpoint, chỉ dùng được từ trong VPC đó. Interface endpoint dựa trên PrivateLink, tạo ENI có private IP trong subnet, hỗ trợ hầu hết dịch vụ và cả dịch vụ của bên thứ ba, tính tiền theo giờ và GB, và dùng được từ on-prem qua VPN hay Direct Connect. Bật private DNS thì ứng dụng không cần đổi endpoint.
🇬🇧 English:
A gateway endpoint exists only for S3 and DynamoDB, is free, and is a route table entry that sends the service's prefix list to the endpoint; it only works from inside that VPC. An interface endpoint is based on PrivateLink, creates an ENI with a private IP in your subnet, supports most services and third-party services, is billed per hour and per GB, and is reachable from on-premises over VPN or Direct Connect. With private DNS enabled the application needs no endpoint change.
Q4. Khi nào dùng VPC peering, khi nào Transit Gateway?
🇻🇳 Tiếng Việt:
Peering nối hai VPC một-một, không transitive, CIDR không được trùng, không phí tạo; hợp khi chỉ vài VPC. Transit Gateway là hub trung tâm, transitive, có route table riêng để cô lập, nối được VPN và Direct Connect, chia sẻ qua nhiều account; hợp khi nhiều VPC hoặc hybrid, đổi lại tính tiền theo attachment và GB. Nếu CIDR trùng thì cả hai đều không được, em dùng PrivateLink để expose đúng một dịch vụ.
🇬🇧 English:
Peering connects two VPCs one-to-one, is not transitive, requires non-overlapping CIDRs and has no setup fee; it fits a handful of VPCs. Transit Gateway is a central hub, transitive, with its own route tables for isolation, connects VPN and Direct Connect, and can be shared across accounts; it fits many VPCs or hybrid setups, at a per-attachment and per-GB cost. If CIDRs overlap neither works, so I use PrivateLink to expose just one service.
Góc thi SAA-C03
Câu 1: Ứng dụng chạy trên EC2 trong private subnet ở 2 AZ cần tải bản cập nhật từ Internet. Yêu cầu: tiếp tục hoạt động khi một AZ hỏng, không cho kết nối từ Internet vào. Giải pháp nào có TÍNH SẴN SÀNG CAO NHẤT?
A. Một NAT Gateway trong public subnet của AZ-a; route table private của cả hai AZ trỏ tới nó.
B. Một NAT instance trong mỗi AZ với script failover.
C. Một NAT Gateway trong public subnet của mỗi AZ; mỗi AZ có route table private riêng trỏ tới NAT Gateway cùng AZ.
D. Gắn Internet Gateway vào private subnet và gán Elastic IP cho từng instance.
Đáp án: C. NAT Gateway chỉ HA trong một AZ; muốn chịu lỗi AZ phải có một cái mỗi AZ và route table riêng. A: AZ-a hỏng thì AZ-b mất đường ra. B: NAT instance tự quản, script failover là thêm việc và điểm hỏng. D: biến private thành public, vi phạm yêu cầu không cho kết nối vào.
Câu 2: EC2 trong private subnet ghi log vào S3 bucket cùng region. Công ty muốn traffic không đi qua Internet và CHI PHÍ THẤP NHẤT. Chọn giải pháp?
A. Tạo NAT Gateway và route 0.0.0.0/0 tới nó.
B. Tạo S3 gateway VPC endpoint và thêm vào route table của private subnet.
C. Tạo S3 interface VPC endpoint trong mỗi AZ với private DNS.
D. Thiết lập Site-to-Site VPN tới S3.
Đáp án: B. Gateway endpoint cho S3 miễn phí, traffic đi mạng nội bộ AWS. A: qua NAT là tính tiền và đi ra Internet về mặt định tuyến. C: interface endpoint hoạt động nhưng tính tiền theo giờ, không phải chi phí thấp nhất khi chỉ cần từ trong VPC. D: không có khái niệm VPN tới S3.
Câu 3: Một công ty có 40 VPC ở nhiều account và một trung tâm dữ liệu on-prem nối qua Direct Connect. Họ cần mọi VPC nói chuyện được với nhau và với on-prem, ÍT công việc vận hành nhất.
A. Tạo VPC peering full mesh giữa 40 VPC và một VPN từ mỗi VPC về on-prem.
B. Tạo một Transit Gateway, attach 40 VPC và Direct Connect gateway; chia sẻ TGW qua AWS RAM cho các account.
C. Dùng PrivateLink giữa mọi cặp VPC.
D. Gom tất cả tài nguyên vào một VPC duy nhất.
Đáp án: B. TGW là hub transitive, một attachment mỗi VPC, nối Direct Connect, chia sẻ xuyên account qua RAM. A: 780 peering và 40 VPN, không quản được. C: PrivateLink expose dịch vụ, không phải kết nối mạng chung. D: không thực tế với nhiều account và phá cô lập.
Cách nhớ ngắn gọn
- Thứ tự dựng: VPC → subnet → IGW → public RT → EIP + NAT → private RT → endpoint → SG. Xóa ngược lại; NAT và EIP là thứ tốn tiền.
- Public/private chỉ khác route 0.0.0.0/0: igw hay nat.
- SG tham chiếu SG theo chuỗi ALB → app → db. SG stateful, NACL stateless.
- S3/DynamoDB: gateway endpoint (miễn phí). Dịch vụ khác: interface endpoint (PrivateLink, tính tiền).
- 2–3 VPC: peering (không transitive). Nhiều VPC/hybrid: Transit Gateway. CIDR trùng: PrivateLink.
| Từ khóa trong đề | Nghĩ đến |
|---|---|
| private subnet needs outbound Internet, highly available | NAT Gateway mỗi AZ + route table mỗi AZ |
| access S3/DynamoDB without traversing Internet, lowest cost | Gateway VPC endpoint |
| access SQS/Secrets Manager/ECR privately; from on-prem | Interface endpoint (PrivateLink) |
| expose a service to other VPCs, overlapping CIDR | PrivateLink endpoint service (NLB) |
| connect many VPCs, transitive, hybrid | Transit Gateway |
| two VPCs, simple, no transitive | VPC peering |
| block a specific IP range at subnet level | NACL deny rule |
| which component is dropping traffic | Reachability Analyzer; log kết nối: VPC Flow Logs |
| dedicated, consistent bandwidth to on-prem | Direct Connect (+ VPN backup) |
Tự kiểm tra
Điều gì làm một subnet thành "public"?
Route table gắn vào nó có route 0.0.0.0/0 → Internet Gateway (và thường bật auto-assign public IP). Không có cờ "public" nào khác.
NAT Gateway đặt ở subnet nào và vì sao?
Public subnet, vì NAT cần route ra IGW để chuyển gói tin của máy private ra Internet. Private subnet trỏ route 0.0.0.0/0 tới NAT đó.
Vì sao rule SG nên tham chiếu SG khác thay vì CIDR?
Instance mới trong ASG, đổi subnet, hay IP đổi đều tự khớp; không phải sửa rule. Và rule mô tả ý định ("từ ALB") thay vì chi tiết mạng.
Peering shop-vpc ↔ analytics-vpc và analytics-vpc ↔ ml-vpc; shop-vpc gọi ml-vpc được không?
Không. Peering không transitive. Cần peering trực tiếp shop ↔ ml hoặc chuyển sang Transit Gateway.
Thứ tự xóa shop-vpc để không bị DependencyViolation?
Instance/ENI → NAT (đợi deleted) → EIP → endpoint → SG (gỡ rule tham chiếu trước) → route table (disassociate) → subnet → detach + delete IGW → VPC.
🧪 Thực hành bài này: Basic: Nền tảng: IAM + VPC + EC2 (chưa triển khai) · lab labs/aws