Skip to content

NAT Issues: Port Allocation and Exhaustion

Tại sao NAT Troubleshooting Quan Trọng

Cloud NAT cho phép các VM tạo kết nối outbound ra internet mà không cần IP public. Tuy nhiên, việc phân bổ port (port allocation) phía sau NAT có thể trở thành điểm nghẽn (bottleneck): nếu VM cạn kiệt port khả dụng (exhaust available ports), các kết nối outbound sẽ bị lỗi.

Các sự cố về NAT thường biểu hiện dưới dạng:

  • Lỗi "Too many open files" từ các kết nối outbound.
  • Lỗi connection timeout chập chờn.
  • Hiệu năng hệ thống bị suy giảm khi tải (load) tăng cao.
  • Lỗi "connection refused" chập chờn mặc dù port thực tế vẫn khả dụng.

Cơ chế bên trong: Kiến trúc Cloud NAT

Mô hình Distributed Software-Defined

Tài liệu GCP ghi rõ:

Cloud NAT cấu hình phần mềm Andromeda điều phối VPC để cung cấp tính năng source network address translation (SNAT) cho các tài nguyên.

Các điểm chính:

  1. Phân tán (Distributed) — NAT xảy ra ở mức host (host level), không phải thông qua một gateway tập trung (centralized gateway).
  2. Định nghĩa bằng phần mềm (Software-defined) — Không có thiết bị NAT chuyên dụng (NAT appliance), tính năng được tích hợp trực tiếp vào Andromeda.
  3. Managed service — GCP tự động scale hệ thống.

Chiến lược Phân bổ Port (Port Allocation Strategy)

Khi VM khởi tạo một kết nối outbound ra internet:

Source: VM (10.0.1.2:54321)
Destination: Google (8.8.8.8:443)

NAT Translation:
  Từ: 10.0.1.2:54321
  Tới: 35.192.1.100:12345  ← IP:port external được map

Return traffic:
  Từ: 8.8.8.8:443
  Tới: 35.192.1.100:12345

NAT thực hiện ngược lại:
  Tới: 10.0.1.2:54321

Cơ chế phân bổ port:

  1. Phân bổ theo VM (Per-VM allocation) — NAT phân bổ một lượng port cố định cho mỗi VM.
  2. External IP pool — Bể chứa (pool) các IP external được cấu hình trong NAT gateway.
  3. Dải Port (Port range) — Dải port động từ 1024-65535 (khoảng ~64k port trên mỗi IP external).

Phân bổ mặc định:

Số VM trên mỗi IP external: Thay đổi động (GCP tự động tối ưu hóa)
Số port trên mỗi VM: Động, được chia sẻ từ NAT pool

Tái sử dụng Port (Port Reuse)

Các port không bị chiếm dụng vĩnh viễn. Khi kết nối đóng lại:

Đóng kết nối → Port được đánh dấu là khả dụng → Port được tái sử dụng cho kết nối mới

Status TIME_WAIT — Port không thể tái sử dụng ngay lập tức sau khi đóng (theo tiêu chuẩn giao thức TCP):

Đóng kết nối tại thời điểm T=0
Thời gian TIME_WAIT: Thường là 60 giây
Port khả dụng lại tại thời điểm: T=60s+

Trong status TIME_WAIT, port vẫn bị đánh dấu là "đang sử dụng" (taken) để tránh các packet cũ đi nhầm vào kết nối mới

Cơ chế xảy ra lỗi Cạn kiệt Port (Port Exhaustion)

Khi nào Port bị cạn kiệt

IP External: 35.192.1.100
Các port khả dụng: 1024-65535 (~64k)

VM A khởi tạo các kết nối liên tục:
- Kết nối 1: port 1024 → 8.8.8.8:443
- Kết nối 2: port 1025 → 8.8.8.8:80
- ...
- Kết nối 64511: port 65535 → ...

Toàn bộ port đã bị chiếm dụng! Các kết nối mới từ VM A sẽ bị thất bại.

Kịch bản thực tế

VM chạy load test liên tục gọi đến external API:
- Tốc độ kết nối: 10 connections/giây
- Thời gian kết nối trung bình: 5 giây
- Số kết nối đồng thời: ~50

Lượng port tiêu thụ:
- Port đã dùng: 50
- Port khả dụng trong NAT pool: hơn 60,000
- Headroom (dư lượng): Rất nhiều

Nhưng nếu kịch bản thay đổi:
- Tốc độ kết nối: 100 connections/giây
- Thời gian kết nối trung bình: 2 phút
- Số kết nối đồng thời: ~12,000
- Port đã dùng: 12,000
- Port khả dụng: 64,000 (hoặc ít hơn nếu bị chia sẻ với các VM khác)
- Headroom: Bắt đầu thiếu hụt!

Nếu tốc độ kết nối đột biến lên 200 connections/giây:
→ Các kết nối mới cần cấp phát port
→ Cạn kiệt port (Port exhausted)
→ Kết nối bị từ chối với lỗi "Cannot assign requested address"

Chẩn đoán lỗi Cạn kiệt Port

Triệu chứng 1: Các lỗi kết nối từ Ứng dụng

Logs ứng dụng hiển thị lỗi:

Error: Cannot assign requested address (EADDRNOTAVAIL)
Error: Too many open files
Error: Connection timeout after N seconds

Triệu chứng 2: Theo dõi qua Monitoring Metrics

Kiểm tra cấu hình NAT:

bash
gcloud compute networks nat-gateways describe my-nat \
  --region=us-central1

Kiểm tra các chỉ số trong Cloud Monitoring:

Chỉ số: networking.googleapis.com/nat_port/used
  Giá trị cao → Sắp xảy ra cạn kiệt port

Chỉ số: networking.googleapis.com/nat_allocations/failed
  Giá trị lớn hơn 0 → Các lượt phân bổ bị lỗi do cạn kiệt port

Triệu chứng trong VPC Flow Logs

VPC Flow Logs có thể hiển thị các kết nối được khởi tạo nhưng không thể hoàn thành:

json
{
  "action": "ACCEPT",
  "bytes_sent": 100,
  "bytes_received": 0,    // ← Có khả năng kết nối bị từ chối
  "start_time": "2026-06-26T15:30:40Z",
  "end_time": "2026-06-26T15:30:41Z"  // ← Thời gian kết nối cực ngắn
}

Quy trình Debug lỗi Cạn kiệt Port

Bước 1: Xác nhận lỗi cạn kiệt

bash
# Từ VM nguồn
gcloud compute ssh source-vm --zone=us-central1-a

# Kiểm tra qua netstat
netstat -tn | wc -l
# Nếu giá trị gần 60,000+, nhiều khả năng bị cạn kiệt port

# Hoặc sử dụng ss (tối ưu hiệu năng hơn)
ss -tn | wc -l

# Kiểm tra các socket ở status TIME_WAIT
ss -tn state time-wait | wc -l
# Nếu chỉ số này cao, các kết nối đang không được thu hồi đủ nhanh

Bước 2: Xác định kiểu kết nối

bash
# IP đích nào đang tiêu thụ port nhiều nhất?
netstat -tn | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn
# Hiển thị phân phối kết nối theo IP đích

# Kiểm tra process nào đang mở các kết nối
netstat -tnp | grep -v ESTABLISHED | head -20
# (chạy với quyền root để xem tên process)

Bước 3: Kiểm tra cấu hình NAT

bash
# Liệt kê các NAT gateway
gcloud compute networks nat-gateways list

# Xem chi tiết một NAT gateway cụ thể
gcloud compute networks nat-gateways describe my-nat \
  --region=us-central1

# Kiểm tra các thông số:
# - nat_ips: Có bao nhiêu IP external được phân bổ
# - min_ports_per_vm: Số port tối thiểu được dự phòng cho mỗi VM (mặc định là 64)
# - udp_idle_timeout_ms: Thời gian timeout của kết nối UDP
# - tcp_idle_timeout_ms: Thời gian timeout của kết nối TCP

Bước 4: Theo dõi Real-Time Metrics

bash
# Tạo custom dashboard để theo dõi các metric của NAT
gcloud monitoring dashboards create --config-from-file=nat-dashboard.json

# Hoặc kiểm tra trực tiếp trên Cloud Console:
# Navigation → Cloud Monitoring → Dashboards
# Thêm metric: networking.googleapis.com/nat_port/*

Cách xử lý lỗi Cạn kiệt Port

Giải pháp 1: Bổ sung thêm IP External

bash
# Tăng số lượng IP external cho NAT gateway
gcloud compute networks nat-gateways update my-nat \
  --region=us-central1 \
  --nat-ips=ip-1,ip-2,ip-3,ip-4  # Thêm các IP mới

# Càng nhiều IP external = dải port khả dụng càng lớn
# Với 2 IP external, số port khả dụng sẽ là ~128k thay vì 64k

Giải pháp 2: Tăng số Port tối thiểu trên mỗi VM (Min Ports Per VM)

bash
# Đảm bảo mỗi VM nhận được số port tối thiểu lớn hơn
gcloud compute networks nat-gateways update my-nat \
  --region=us-central1 \
  --min-ports-per-vm=2048

# Mặc định là 64, nhưng nếu VM có lượng kết nối lớn, hãy tăng lên 256/512/2048

Giải pháp 3: Tối ưu hóa Connection Timeouts

bash
# Giảm idle timeout để giải phóng port nhanh hơn
gcloud compute networks nat-gateways update my-nat \
  --region=us-central1 \
  --tcp-idle-timeout-ms=300000  # 5 phút (mặc định là 30 phút)

# Timeout nhanh hơn = port được giải phóng sớm hơn = có nhiều port khả dụng hơn
# Tuy nhiên: Có thể làm đóng sớm các kết nối đang rảnh (idle)

Giải pháp 4: Áp dụng Connection Pooling / Tái sử dụng kết nối

Khắc phục ở mức ứng dụng (Application level):

Thay vì tạo kết nối mới cho mỗi request:

python
# XẤU: Tạo kết nối mới cho mỗi request
for request in requests:
    conn = socket.socket()
    conn.connect((host, port))
    conn.sendall(request)
    conn.close()  # Đóng kết nối ngay lập tức

# TỐT: Sử dụng Connection pooling
pool = urllib3.PoolManager(num_pools=10, maxsize=50)
for request in requests:
    response = pool.urlopen('GET', url)

Connection pooling duy trì các kết nối persistent lâu dài, giúp giảm thiểu tần suất xoay vòng port (port churn).

Giải pháp 5: Phân bổ tải ra nhiều VM khác nhau

bash
# Thay vì dồn tải vào một VM duy nhất,
# sử dụng managed instance group để phân bổ tải đều ra các VM

gcloud compute instance-groups managed create api-client-group \
  --zone=us-central1-a \
  --template=api-client-template \
  --size=5

# 5 VM, mỗi VM sẽ nhận được lượng port phân bổ riêng biệt
# Tổng số port khả dụng: 64k * 5 = 320k
# Có nhiều headroom hơn

Hướng dẫn Giám sát NAT Best Practices

1. Thiết lập Cảnh báo (Alerts)

bash
# Tạo cảnh báo khi cạn kiệt port
gcloud monitoring policies create \
  --notification-channels=EMAIL_CHANNEL \
  --metric-type=networking.googleapis.com/nat_port/used \
  --condition-threshold=50000  # Cảnh báo nếu sử dụng > 50k ports

Thiết lập cảnh báo sớm trước khi thực sự cạn kiệt port.

2. Định kỳ lưu lại Baseline Metrics

Theo dõi biểu đồ sử dụng port thông thường:

Giờ cao điểm (Peak hours): 20,000 ports
Giờ thấp điểm (Off-peak): 5,000 ports
Headroom khả dụng: 40,000 ports

Xu hướng: Nếu có xu hướng tăng dần, hãy lên kế hoạch tăng IP trước khi chạm ngưỡng giới hạn

3. Ghi nhận lịch sử kết nối (Connection Patterns)

Kích hoạt Cloud Logging cho các kết nối outbound:

bash
# Kích hoạt VPC Flow Logs
gcloud compute networks subnets update default \
  --region=us-central1 \
  --enable-flow-logs

# Truy vấn logs để theo dõi các kết nối đích
# Xác định các IP đích đang tiêu thụ lượng port lớn nhất

Trường hợp đặc biệt: Google APIs (Cloud Storage, BigQuery)

Các Google API (như Cloud Storage, BigQuery) được kết nối qua Private Google Access, không đi qua NAT.

VM → Private Google Access (không tiêu thụ port NAT)
Nhưng: VM → External (8.8.8.8) sẽ sử dụng port NAT

Ý nghĩa: Các lệnh gọi Outbound Google API sẽ không tiêu thụ port của NAT. Chỉ các kết nối tới external hoặc on-premises mới sử dụng các port này.

Hướng dẫn Sizing NAT cho Production

Tính toán kích thước (Sizing) cho NAT Gateway

Đối với các workload có lượng kết nối outbound lớn:

Ước tính:
- Số kết nối đồng thời ở đỉnh: 5,000
- Số port cho mỗi kết nối: 1 (điển hình)
- Lượng port cần thiết: 5,000
- Cộng thêm 50% buffer dự phòng: 7,500
- Cộng thêm buffer cho TIME_WAIT (trễ 60s): cộng thêm tương ứng

Số lượng IP external cần thiết: ceil(7,500 / 64,000) = 1 IP
Khuyến nghị kèm buffer: 2 IP

Cấu hình Template mẫu

bash
gcloud compute networks nat-gateways create production-nat \
  --router=production-router \
  --region=us-central1 \
  --nat-ips=ip-address-1,ip-address-2 \
  --auto-allocate-nat-external-ips \
  --min-ports-per-vm=512 \
  --tcp-idle-timeout-ms=600000 \  # 10 phút
  --udp-idle-timeout-ms=300000    # 5 phút

References