Port Allocation & Port Exhaustion
Tại Sao Đây Là Phần Quan Trọng Nhất
Port exhaustion là nguyên nhân gốc rễ của phần lớn sự cố Cloud NAT trong production. Nhưng "port exhaustion" là thuật ngữ che giấu một hiện tượng phức tạp hơn: có thể xảy ra exhaustion không phải vì bạn có quá nhiều connections, mà vì TCP TIME_WAIT giữ lại port trong khi connections đã đóng, hoặc vì phân bổ port tĩnh không đồng đều giữa các VM.
Phần này xây dựng mental model chính xác từ gốc: tại sao giới hạn 64.512 ports/IP tồn tại, cơ chế phân bổ hoạt động ra sao, và khi nào cạn kiệt thật sự xảy ra.
Internal Model: Tại Sao Chỉ Có 64.512 Ports/IP
Nguồn Gốc Con Số
Không gian port của TCP và UDP là 16-bit, cho tổng 65.536 giá trị (0 đến 65.535). Cloud NAT không sử dụng toàn bộ:
- Ports 0–1.023: Well-known (privileged) ports — được reserve cho các service như HTTP (80), HTTPS (443), SSH (22). Cloud NAT không dùng dải này để tránh conflict với các service đang lắng nghe.
- Ports 1.024–65.535: Available = 64.512 ports khả dụng per protocol per NAT IP
Theo Google Cloud documentation:
"Each NAT IP address on a Cloud NAT gateway (both Public NAT and Private NAT) offers 64,512 TCP source ports and 64,512 UDP source ports."
Quan trọng: con số 64.512 là per protocol. Một NAT IP có thể cung cấp 64.512 TCP ports VÀ 64.512 UDP ports đồng thời — chúng không chia sẻ pool với nhau.
Port Là Tài Nguyên Hữu Hạn Theo Hướng
Port không chỉ là số từ 1.024 đến 65.535. Trong context NAT, một port được tiêu thụ khi nó là một phần của 5-tuple active connection. Cùng một port number có thể được dùng cho nhiều connection khác nhau nếu destination khác nhau (như sẽ giải thích ở phần 5-tuple bên dưới) — nhưng đây chỉ đúng với endpoint-dependent mapping mặc định.
Port Allocation Modes: Static vs Dynamic
Static Allocation (Public NAT mặc định)
Trong static allocation, admin cấu hình một con số cố định minimum ports per VM. Cloud NAT pre-allocates số port này cho từng VM trong scope ngay khi VM được tạo hoặc khi NAT gateway được cấu hình.
Ví dụ static allocation:
- NAT gateway: 1 external IP
- Minimum ports per VM: 64
- VM scope: 1.000 VMs
→ Mỗi VM nhận 64 ports NGAY KHI NÓ ĐI VÀO SCOPE
→ Tổng ports dùng: 1.000 × 64 = 64.000 / 64.512 available
→ Còn 512 ports dự phòng
→ VM thứ 1.009 sẽ không được cấp port: (1.009 × 64 = 64.576 > 64.512)Đặc điểm của static allocation:
- Pre-allocation: VM được cấp port ngay lập tức, không cần chờ khi có connection thật
- Lãng phí port: VM ít dùng vẫn chiếm đúng số port được allocate
- Predictable: dễ capacity planning vì max VMs = ⌊64.512 / ports-per-VM⌋
- Required cho EIM: Endpoint-Independent Mapping chỉ tương thích với static allocation
Theo mặc định của Public NAT, minimum ports per VM = 64.
Dynamic Port Allocation (DPA)
Dynamic Port Allocation cho phép mỗi VM bắt đầu với minimum và scale lên maximum khi cần. Không có pre-allocation cứng nhắc.
Ví dụ dynamic allocation:
- NAT gateway: 1 external IP
- Minimum ports per VM: 32
- Maximum ports per VM: 1.024
- VM scope: 2.000 VMs
Trạng thái bình thường (VM ít dùng):
→ Mỗi VM dùng ~32 ports
→ Tổng tối đa theo minimum: 2.000 × 32 = 64.000 / 64.512 ✓
Khi một số VM spike:
→ 5 VM spike lên ~400 ports mỗi cái
→ 1.995 VM còn lại vẫn ở ~32 ports
→ Tổng: 5×400 + 1.995×32 = 2.000 + 63.840 = 65.840 → vượt!
→ Cloud NAT sẽ throttle VM nào không thể được cấp thêm portCơ chế doubling của DPA:
Khi một VM tiếp cận ngưỡng port đã được allocate, Cloud NAT tự động gấp đôi số port cấp cho VM đó:
VM bắt đầu với minimum = 32 ports
→ Khi dùng gần hết 32: tự động lên 64 ports
→ Khi dùng gần hết 64: tự động lên 128 ports
→ Khi dùng gần hết 128: tự động lên 256 ports
...
→ Dừng ở maximum (ví dụ 1.024 ports)
Khi usage giảm xuống dưới 50% của allocation hiện tại:
→ Port được deallocate, trả về pool
→ VM giảm xuống level thấp hơnCơ chế doubling này cho phép VM burst mà không cần pre-allocate số lớn cho tất cả VMs.
Ưu điểm DPA:
- Hiệu quả tài nguyên hơn nhiều so với static
- Cho phép heterogeneous workloads (một số VM heavy, nhiều VM nhẹ) dùng chung pool
Hạn chế DPA:
- Không tương thích với EIM (Endpoint-Independent Mapping)
- Việc doubling có delay nhỏ — không instantaneous
- Khó predict chính xác tổng port usage tại bất kỳ thời điểm nào
Theo Google Cloud blog: DPA cho phép giảm số NAT IP cần thiết đáng kể. Ví dụ từ tài liệu: deployment 4.000 instances với DPA chỉ cần 65 external IPs thay vì 254 IPs với static allocation.
Private NAT: Luôn Dynamic
Private NAT (dùng để NAT giữa các private IP ranges) luôn dùng dynamic allocation. Thêm vào đó, Private NAT tự động allocate gấp đôi minimum từ đầu:
"Google Cloud allocates twice the number of required minimum ports per VM for reliability."
Đây là để đảm bảo reliability: nếu một port bị chiếm bởi connection đang trong TIME_WAIT, vẫn còn port khác available trong pool gấp đôi.
Port Math: Công Thức Capacity Planning
Công Thức Cơ Bản
Max VMs supported = ⌊(Số NAT IPs × 64.512) / ports-per-VM-minimum⌋Ví dụ Public NAT static allocation:
| NAT IPs | Ports/VM | Max VMs |
|---|---|---|
| 1 | 64 | ⌊64.512 / 64⌋ = 1.008 |
| 1 | 128 | ⌊64.512 / 128⌋ = 504 |
| 1 | 1.024 | ⌊64.512 / 1.024⌋ = 63 |
| 1 | 4.096 | ⌊64.512 / 4.096⌋ = 15 |
| 2 | 64 | ⌊129.024 / 64⌋ = 2.016 |
| 10 | 64 | ⌊645.120 / 64⌋ = 10.080 |
Ports-per-VM: Chọn Số Bao Nhiêu?
Câu hỏi thực tế là: bao nhiêu port một VM cần?
Một connection dùng một 5-tuple = một port slot trong allocation. Tuy nhiên (xem phần 5-tuple bên dưới), cùng một port có thể phục vụ nhiều connections nếu chúng đến các destination khác nhau.
Rule of thumb thực tế:
- Web services với HTTP/HTTPS: mỗi concurrent outbound connection dùng 1 port. Nếu service mở 100 connections đồng thời đến 5 destinations khác nhau = 100 ports.
- Database connections: mỗi connection pool connection = 1 port
- GKE nodes: mỗi node có thể có hàng chục pod, mỗi pod có connection pool → cần nhiều port hơn raw VM
Minimum ports mặc định của GCP: 64 ports/VM
Con số 64 quá thấp cho hầu hết production workloads. Các giá trị thực tế hơn:
- Workload nhẹ (batch, occasional API calls): 128–256
- Web service với connection pooling: 256–1.024
- GKE node với nhiều pods: 512–4.096
Alias IP và Điều Chỉnh Tự Động
Nếu VM có alias IP ranges (phổ biến trong GKE với VPC-native clusters), Cloud NAT tự động điều chỉnh static allocation minimum:
Effective minimum = max(configured_minimum, 1.024)Với GKE VPC-native nodes có alias IP, mỗi node đại diện cho toàn bộ Pod CIDR của nó. Cloud NAT coi mỗi alias IP range là một "source" riêng, do đó bump minimum lên 1.024 để đảm bảo đủ port cho tất cả Pods trên node.
5-Tuple và Ràng Buộc Tái Sử Dụng Port
Định Nghĩa 5-Tuple
Một "5-tuple" trong context Cloud NAT là tập hợp 5 thành phần xác định duy nhất một connection:
5-tuple = (NAT source IP, NAT source port, dest IP, dest port, protocol)
↑ ↑ ↑ ↑ ↑
external IP assigned port remote IP remote port TCP/UDP
của NAT từ poolPhần "destination 3-tuple" là: (dest IP, dest port, protocol).
Cùng Port, Nhiều Connections
Với endpoint-dependent mapping (mặc định), cùng một NAT source port có thể được dùng đồng thời cho nhiều connections nếu chúng có destination 3-tuple khác nhau:
NAT IP: 34.100.200.50, Port: 44001
Active connections với port này:
34.100.200.50:44001 → 8.8.8.8:53/UDP (DNS query)
34.100.200.50:44001 → 1.1.1.1:53/UDP (DNS query khác dest)
34.100.200.50:44001 → 203.0.113.5:443/TCP (HTTPS đến server A)
34.100.200.50:44001 → 203.0.113.6:443/TCP (HTTPS đến server B)Tất cả 4 connections trên đều dùng cùng port 44001 mà không conflict — vì 5-tuple của chúng khác nhau. Điều này có nghĩa là "ports per VM" không bằng "concurrent connections" — thực tế có thể phục vụ nhiều connections hơn.
Nhưng: nếu bạn có nhiều connections đến cùng một destination 3-tuple, mỗi connection cần một port khác nhau:
VM gọi 1.000 connections đến cùng 203.0.113.1:443/TCP
→ Cần 1.000 distinct NAT source ports
→ Đây là pattern gây exhaustion nhanh nhấtRàng Buộc Tái Sử Dụng Port: TIME_WAIT
Sau khi một TCP connection đóng, Cloud NAT không cho phép tái sử dụng cùng 5-tuple ngay lập tức. Một khoảng delay (TIME_WAIT timeout) phải trôi qua trước khi:
(NAT_IP:NAT_port → Dest_IP:Dest_port/TCP)
có thể được reuse cho connection mới đến cùng destination.Giá trị timeout mặc định:
- TCP TIME_WAIT (closed connections): 120 giây
- TCP established: 1.200 giây (20 phút)
- TCP transitory (half-open): 30 giây
- UDP: 30 giây (luồng active), 60 giây (sau khi hết lưu lượng)
- ICMP: 30 giây
Lưu ý quan trọng từ tài liệu GCP (2025): "The default TCP TIME_WAIT timeout for Cloud NAT is scheduled to decrease from 120 seconds to 30 seconds starting June 30, 2026, with all new gateways in all regions using the 30-second default on or after September 30, 2026."
Điều này có nghĩa là nếu application của bạn mở và đóng nhiều connections ngắn đến cùng một destination (ví dụ: spam HTTP requests đến cùng API endpoint mà không dùng keep-alive), các port bị "chiếm" bởi TIME_WAIT có thể nhanh chóng cạn kiệt.
TIME_WAIT Phantom Shortage
"Phantom shortage" là tình huống port exhaustion xảy ra không phải vì connections đang active mà vì connections đã đóng nhưng port vẫn bị giữ:
Scenario:
- VM có 1.024 ports allocated
- Application mở và đóng 10 connections/giây đến 203.0.113.1:443/TCP
- Sau 120 giây: 1.200 connections đã từng mở, tất cả đều closed
- Nhưng: 1.200 × 120s = 144.000 port-seconds bị chiếm bởi TIME_WAIT
- 1.024 ports × 120s = chỉ 122.880 port-seconds available
→ Port pool bị cạn kiệt hoàn toàn mặc dù KHÔNG có connection activeĐây là lý do giảm TCP TIME_WAIT timeout từ 120s xuống 30s (hoặc thậm chí 5s) có thể giải quyết exhaustion mà không cần thêm NAT IP.
Port Exhaustion: Triệu Chứng và Chẩn Đoán
Triệu Chứng
Port exhaustion thường im lặng ở tầng application. Dấu hiệu cần tìm:
Tầng network:
- Connections bị drop mà không có error rõ ràng
connect()timeout thay vìConnection refused- Latency spike bất thường trước khi timeout
Tầng Cloud NAT (metrics):
compute.googleapis.com/nat/dropped_sent_packets_counttăng đột biến vớireason = OUT_OF_RESOURCEScompute.googleapis.com/nat/nat_allocation_failed> 0
Tầng VM:
# Xem trạng thái connections:
netstat -an | egrep 'ESTABLISHED|TIME_WAIT|CLOSE_WAIT' | wc -l
# Chi tiết theo state:
netstat -an | awk '{print $6}' | sort | uniq -c | sort -rnTầng Cloud Logging:
- Log entries với
allocation_status: DROPPED
NAT_ALLOCATION_FAILED
nat_allocation_failed là metric chỉ ra rằng Cloud NAT không thể phân bổ NAT IP cho VM. Đây xảy ra khi:
- Tất cả NAT IP đã được phân bổ hết và không còn port nào available trong pool
- Đối với auto-allocated IPs: Google Cloud đã đạt hard limit số IPs có thể auto-allocate
Khác với OUT_OF_RESOURCES:
nat_allocation_failed: NAT IP-level failure — không thể phân bổ IP cho VM mớidropped_sent_packets_countvớiOUT_OF_RESOURCES: port-level failure — VM có NAT IP nhưng hết ports
Trong thực tế, OUT_OF_RESOURCES phổ biến hơn và là dấu hiệu port exhaustion điển hình.
Chẩn Đoán Step-by-Step
Bước 1: Xác nhận port exhaustion
# Cloud Monitoring query (MQL):
fetch gce_instance
| metric 'compute.googleapis.com/nat/dropped_sent_packets_count'
| filter metric.reason == 'OUT_OF_RESOURCES'
| align rate(1m)
| every 1m
| group_by [resource.instance_id], [value: sum(val())]Tìm VM instance_id nào có drop rate cao nhất.
Bước 2: Kiểm tra port usage theo VM
fetch gce_instance
| metric 'compute.googleapis.com/nat/port_usage'
| group_by 1m, [value_port_usage_max: max(value.port_usage)]
| every 1m
| top 25, max(val())Bước 3: Kiểm tra allocation failure
fetch nat_gateway
| metric 'compute.googleapis.com/nat/nat_allocation_failed'
| align rate(1m)
| every 1mBước 4: Tìm destination gây exhaustion nhất Nếu đã bật NAT logging:
# Trong Cloud Logging:
resource.type="nat_gateway"
jsonPayload.allocation_status="DROPPED"
| group by jsonPayload.connection.dest_ip, jsonPayload.connection.dest_port
| countPattern này cho thấy VMs đang hammering cùng một destination — điển hình của exhaustion do TIME_WAIT.
Chiến Lược Phòng Tránh và Xử Lý Exhaustion
1. Bật Dynamic Port Allocation
Đây thường là giải pháp đầu tiên và hiệu quả nhất. DPA cho phép VM burst mà không cần phân bổ trước số lớn cho tất cả VMs.
gcloud compute routers nats update NAT_GATEWAY_NAME \
--router=ROUTER_NAME \
--region=REGION \
--enable-dynamic-port-allocation \
--min-ports-per-vm=MIN_VALUE \
--max-ports-per-vm=MAX_VALUEChọn max-ports-per-vm dựa trên peak connections của workload nặng nhất. Giá trị hợp lý: 4.096 hoặc 8.192 cho GKE nodes.
2. Giảm TCP TIME_WAIT Timeout
Nếu application mở và đóng nhiều short-lived connections đến cùng destination:
gcloud compute routers nats update NAT_GATEWAY_NAME \
--router=ROUTER_NAME \
--region=REGION \
--tcp-time-wait-timeout=30s # hoặc thấp hơn, minimum là 5sGiảm từ 120s xuống 30s có thể giải phóng port 4× nhanh hơn. Giảm xuống 5s có thể có tác dụng cho ứng dụng có thể handle TCP RST.
Cảnh báo: Giảm TIME_WAIT quá thấp (< 15s) có thể gây vấn đề với TCP retransmission — packet "ghost" từ connection cũ có thể nhầm vào connection mới. Giảm xuống 30s là safe, xuống 5s cần test kỹ.
3. Thêm NAT IP Address
Tăng pool port tuyến tính. Thêm 1 IP = thêm 64.512 TCP ports và 64.512 UDP ports.
# Auto-allocate thêm IPs:
gcloud compute routers nats update NAT_GATEWAY_NAME \
--auto-allocate-nat-external-ips
# Hoặc manually add IP:
gcloud compute addresses create nat-ip-2 --region=REGION
gcloud compute routers nats update NAT_GATEWAY_NAME \
--nat-external-ip-pool=nat-ip-1,nat-ip-2Khi nào dùng: khi đã bật DPA nhưng vẫn exhaustion, hoặc khi muốn predictable IP addresses (allowlisting ở downstream).
4. Connection Pooling Ở Tầng Application
Đây là giải pháp lâu dài và hiệu quả nhất về chi phí. Thay vì mỗi request mở một TCP connection mới, dùng connection pool:
Không dùng connection pool:
100 requests/giây × 120s TIME_WAIT = 12.000 ports cần thiết tại một thời điểm
Dùng connection pool (50 connections):
50 connections × (120s TIME_WAIT × 0) = ~50 ports cần thiết
(Connections được reuse, không có TIME_WAIT accumulation)Với HTTP/HTTPS: bật Connection: keep-alive hoặc HTTP/2 multiplexing. Với database: dùng connection pooler (PgBouncer, RDS Proxy). Với gRPC: dùng gRPC channel pooling.
5. NAT Rules Để Phân Tách Traffic
Nếu exhaustion xảy ra với một destination cụ thể, dùng NAT rules để route traffic đó qua IP pool riêng:
# NAT rule: traffic đến payment processor dùng dedicated IPs
natRules:
- ruleNumber: 1
match: "destination.ip == '203.0.113.1'"
action:
sourceNatActiveIps:
- natIpAddress: "projects/PROJECT/regions/REGION/addresses/payment-nat-ip"Điều này ngăn traffic thông thường "cạnh tranh" port với traffic quan trọng.
6. Tăng Ephemeral Port Range Trên VM
OS của VM có thể được cấu hình dùng nhiều ephemeral ports hơn:
# Linux: xem range hiện tại
cat /proc/sys/net/ipv4/ip_local_port_range
# Mặc định: 32768 60999 (28.231 ports)
# Tăng range:
echo "1024 65535" | sudo tee /proc/sys/net/ipv4/ip_local_port_range
# Permanent:
echo "net.ipv4.ip_local_port_range = 1024 65535" | sudo tee -a /etc/sysctl.confLưu ý: Tăng ephemeral port range của OS giúp VM có nhiều source ports để chọn khi tạo connections, giảm probability "reuse too soon" conflict ở tầng OS — nhưng không thay đổi giới hạn NAT port pool.
Failure Mode: Asymmetric Exhaustion
Một failure mode tinh tế: không phải tất cả VMs đều exhausted cùng lúc. Với static allocation, mỗi VM nhận cùng số port — nhưng không phải VM nào cũng dùng nhiều như nhau. VM A có thể dùng 98% allocation trong khi VM B chỉ dùng 2%. Không thể "borrow" port từ VM B sang VM A.
Dynamic Port Allocation giải quyết chính xác vấn đề này: pool được chia sẻ và phân bổ theo nhu cầu thực tế.