Lỗi gặp phải
Bạn SSH vào EC2 instance, thử cài package, gọi API bên ngoài, hay chỉ đơn giản là ping 8.8.8.8 — và nhận được:
$ curl https://api.example.com
curl: (7) Failed to connect to api.example.com port 443: No route to host
$ ping 8.8.8.8
ping: connect: No route to host
$ yum update
Could not retrieve mirrorlist... connect: No route to host
2 giờ sáng. Deploy đang bị kẹt. Instance đang chạy, security group trông ổn, nhưng mọi kết nối ra ngoài đều thất bại. Không có gì kết nối được internet.
Nguyên nhân gốc rễ
Các EC2 instance bên trong VPC mặc định không có bất kỳ kết nối internet nào. Traffic cần một đường ra rõ ràng. Hai kịch bản sau chiếm 95% trường hợp:
- Subnet công khai, không có Internet Gateway (IGW): VPC chưa gắn IGW, hoặc bảng định tuyến thiếu entry
0.0.0.0/0 → igw-xxxxxxxx. - Subnet riêng tư, không có NAT Gateway: Instance không có IP công khai và không có NAT Gateway để chuyển tiếp traffic ra ngoài.
Còn một biến thể thứ ba: IGW hoặc NAT Gateway đã tồn tại, nhưng bảng định tuyến của subnet không trỏ vào đó. Dù thế nào, VPC đang bị cô lập khỏi internet.
Chẩn đoán trước
Chưa cần động vào gì cả. Hãy xác định xem bạn đang gặp loại lỗi nào.
1. Kiểm tra xem instance có IP công khai không
# Từ bên trong instance
curl -s http://169.254.169.254/latest/meta-data/public-ipv4
# Không trả về gì hoặc "404" = không được gán IP công khai
2. Kiểm tra bảng định tuyến qua AWS CLI
# Tìm subnet mà instance đang thuộc về
aws ec2 describe-instances \
--instance-ids i-0abc123def456 \
--query 'Reservations[].Instances[].SubnetId' \
--output text
# Tìm bảng định tuyến của subnet đó
aws ec2 describe-route-tables \
--filters Name=association.subnet-id,Values=subnet-0xxxxxxx \
--query 'RouteTables[].Routes'
# Tìm route như sau:
# { "DestinationCidrBlock": "0.0.0.0/0", "GatewayId": "igw-..." }
# Nếu thiếu, đó là vấn đề của bạn.
3. Kiểm tra xem IGW đã được gắn vào VPC chưa
aws ec2 describe-internet-gateways \
--filters Name=attachment.vpc-id,Values=vpc-0xxxxxxx \
--query 'InternetGateways[].InternetGatewayId'
# Mảng rỗng [] = chưa có IGW nào được gắn
Cách sửa A — Subnet công khai: Gắn Internet Gateway
Nếu instance của bạn nằm trong subnet công khai và nên có IP công khai, đây là cách sửa.
Bước 1: Tạo và gắn IGW
# Tạo gateway
IGW_ID=$(aws ec2 create-internet-gateway \
--query 'InternetGateway.InternetGatewayId' \
--output text)
echo "Created IGW: $IGW_ID"
# Gắn vào VPC của bạn
aws ec2 attach-internet-gateway \
--internet-gateway-id $IGW_ID \
--vpc-id vpc-0xxxxxxx
Bước 2: Thêm route mặc định vào bảng định tuyến
# Lấy ID bảng định tuyến của subnet công khai
RTB_ID=$(aws ec2 describe-route-tables \
--filters Name=association.subnet-id,Values=subnet-0xxxxxxx \
--query 'RouteTables[].RouteTableId' \
--output text)
# Thêm route 0.0.0.0/0 trỏ đến IGW
aws ec2 create-route \
--route-table-id $RTB_ID \
--destination-cidr-block 0.0.0.0/0 \
--gateway-id $IGW_ID
Bước 3: Đảm bảo instance có IP công khai
Đã khởi động mà không có IP công khai? Bạn cần gắn một Elastic IP vào:
# Cấp phát một Elastic IP
EIP_ALLOC=$(aws ec2 allocate-address --domain vpc \
--query 'AllocationId' --output text)
# Liên kết với instance của bạn
aws ec2 associate-address \
--instance-id i-0abc123def456 \
--allocation-id $EIP_ALLOC
Cách sửa B — Subnet riêng tư: Thêm NAT Gateway
Các instance trong subnet riêng tư — app server, background worker, Lambda trong VPC — không bao giờ có IP công khai. Chúng cần một NAT Gateway đặt trong subnet công khai để chuyển tiếp traffic ra ngoài.
Bước 1: Tạo NAT Gateway trong subnet công khai
# Cấp phát Elastic IP cho NAT Gateway
EIP_ALLOC=$(aws ec2 allocate-address --domain vpc \
--query 'AllocationId' --output text)
# Tạo NAT Gateway trong subnet CÔNG KHAI (không phải subnet riêng tư)
NAT_ID=$(aws ec2 create-nat-gateway \
--subnet-id subnet-PUBLIC-0xxxxxxx \
--allocation-id $EIP_ALLOC \
--query 'NatGateway.NatGatewayId' \
--output text)
echo "NAT Gateway: $NAT_ID"
# Mất khoảng 60 giây để sẵn sàng
aws ec2 wait nat-gateway-available --nat-gateway-ids $NAT_ID
Bước 2: Cập nhật bảng định tuyến của subnet riêng tư
# Lấy bảng định tuyến của subnet riêng tư
PRIVATE_RTB=$(aws ec2 describe-route-tables \
--filters Name=association.subnet-id,Values=subnet-PRIVATE-0xxxxxxx \
--query 'RouteTables[].RouteTableId' \
--output text)
# Định tuyến toàn bộ traffic ra ngoài qua NAT Gateway
aws ec2 create-route \
--route-table-id $PRIVATE_RTB \
--destination-cidr-block 0.0.0.0/0 \
--nat-gateway-id $NAT_ID
Cách sửa C — Route đã có nhưng trỏ sai đích
Bảng định tuyến đã có entry 0.0.0.0/0 — nhưng đang trỏ đến gateway đã bị xóa hoặc ngắt kết nối. Hãy thay thế thay vì thêm một route trùng lặp:
aws ec2 replace-route \
--route-table-id $RTB_ID \
--destination-cidr-block 0.0.0.0/0 \
--gateway-id $IGW_ID # hoặc --nat-gateway-id $NAT_ID cho subnet riêng tư
Kiểm tra sau khi sửa
# SSH vào instance và kiểm tra kết nối ra ngoài
curl -I https://www.google.com
# Kỳ vọng: HTTP/2 200
ping -c 3 8.8.8.8
# Kỳ vọng: 3 packets transmitted, 3 received
# Xác nhận bảng định tuyến đã đúng
aws ec2 describe-route-tables \
--route-table-ids $RTB_ID \
--query 'RouteTables[].Routes[?DestinationCidrBlock==`0.0.0.0/0`]'
Lưu ý về Security Group
Gateway đã được cấu hình, route đúng, nhưng vẫn thấy No route to host? Kiểm tra quy tắc outbound của security group. Mặc định cho phép toàn bộ traffic ra ngoài, nhưng thường có ai đó đã khóa chặt lại vào một thời điểm nào đó rồi quên mất:
aws ec2 describe-security-groups \
--group-ids sg-0xxxxxxx \
--query 'SecurityGroups[].IpPermissionsEgress'
# Cần có rule như:
# { "IpProtocol": "-1", "IpRanges": [{ "CidrIp": "0.0.0.0/0" }] }
Phòng ngừa
Chín trong mười trường hợp xảy ra vì ai đó đã tạo VPC thủ công — hoặc để Terraform/CloudFormation dựng lên — rồi quên cấu hình gateway hoặc bảng định tuyến. Một vài thói quen giúp bạn tránh lặp lại lần sau:
- VPC Reachability Analyzer trong AWS Console phân tích toàn bộ đường đi của mạng và chỉ ra chính xác traffic bị chặn ở đâu. Không cần gửi gói tin thực, không cần đoán mò.
- Gắn tag cho subnet
tier=public/tier=privatetrong Terraform. Khi xem lại cấu hình VPC vài tuần sau, bạn sẽ thấy ngay subnet nào cần route qua IGW và subnet nào qua NAT. - Trong Terraform, luôn kết hợp
aws_subnetvớiaws_route_table_associationtường minh. Dựa vào main route table là một cái bẫy âm thầm — mọi subnet mới đều kế thừa nó, khiến một thay đổi ở main table ảnh hưởng dây chuyền đến những thứ bạn không hề chạm vào. - Quy hoạch CIDR quan trọng. Các subnet chồng chéo hoặc kích thước không hợp lý gây ra các vấn đề định tuyến tinh vi rất khó debug. Công cụ trên trình duyệt như ToolCraft's Subnet Calculator rất tiện để tính toán CIDR cho VPC ngay từ đầu — mọi thứ chạy hoàn toàn trên máy bạn.
Terraform tối giản nhưng hoạt động đúng
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
}
resource "aws_route_table_association" "public" {
subnet_id = aws_subnet.public.id
route_table_id = aws_route_table.public.id
}

