Skip to content

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 port

Cơ 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ơn

Cơ 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 IPsPorts/VMMax VMs
164⌊64.512 / 64⌋ = 1.008
1128⌊64.512 / 128⌋ = 504
11.024⌊64.512 / 1.024⌋ = 63
14.096⌊64.512 / 4.096⌋ = 15
264⌊129.024 / 64⌋ = 2.016
1064⌊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ừ pool

Phầ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ất

Rà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_count tăng đột biến với reason = OUT_OF_RESOURCES
  • compute.googleapis.com/nat/nat_allocation_failed > 0

Tầng VM:

bash
# 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 -rn

Tầ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:

  1. Tất cả NAT IP đã được phân bổ hết và không còn port nào available trong pool
  2. Đố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ới
  • dropped_sent_packets_count với OUT_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

bash
# 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

bash
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

bash
fetch nat_gateway
| metric 'compute.googleapis.com/nat/nat_allocation_failed'
| align rate(1m)
| every 1m

Bước 4: Tìm destination gây exhaustion nhất Nếu đã bật NAT logging:

bash
# Trong Cloud Logging:
resource.type="nat_gateway"
jsonPayload.allocation_status="DROPPED"
| group by jsonPayload.connection.dest_ip, jsonPayload.connection.dest_port
| count

Pattern 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.

bash
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_VALUE

Chọ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:

bash
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à 5s

Giả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.

bash
# 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-2

Khi 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:

yaml
# 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:

bash
# 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.conf

Lư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ế.


References