Gần đây mình có gặp một issue cũng khá khoai, bên PO báo là người dùng đang sử dụng web thì timeout, xong F5 thì lại vào được. Mình (và anh em) đã dành kha khá thời gian soi DNS, soi latency graph, soi code,… Và phát hiện vấn đề nằm ngay trên node: hết SNAT port và conntrack bị đầy.

Bài này mình có note lại các bước khoanh vùng thực tế mình đã dùng khi gặp timeout ngẫu nhiên, anh em có thể tham khảo thử xem hữu ích hoặc bookmark sau gặp thì cũng là một kinh nghiệm nhé.
Cụ thể mọi người hình dung như sau, khi service phải phụ thuộc vào các dịch vụ bên ngoài như payment, search, S3, Redis managed, API partner…. Không phải down hẳn, cũng không phải chậm đều, mà kiểu:
- 100 request thì 97 request OK
- 2-3 request timeout
- F5 thì… lại OK
- Scale pod lên thì tỉ lệ timeout tăng (đặc biệt khi outbound bị dồn qua cùng một điểm egress/NAT)
Lúc đó ai cũng nghi DNS, nghi provider, nghi code. Nhưng trong nhiều trường hợp, nguyên nhân lại đến từ các vấn đề hạ tầng như SNAT port exhaustion hoặc conntrack table exhaustion tại điểm NAT/egress (có thể nằm trên node hoặc gateway tuỳ kiến trúc).
Bức tranh thực tế là lưu lượng từ pod ra Internet phải đi qua nhiều lớp trung gian
Trong hạ tầng thực tế công ty, mọi người cũng không lạ các lớp bảo mật như vậy. Tại nhiều cluster, khi pod egress ra ngoài, traffic thường đi theo đường (sample vậy nhé, tuỳ CNI/kiến trúc mạng có thể khác):
Pod IP => (Node hoặc Egress/NAT gateway) => (SNAT/MASQUERADE tại điểm egress) => IP egress (node IP/NAT IP) => Internet
- Pod có IP private
- Tuỳ CNI/infra, SNAT có thể diễn ra trên node hoặc trên egress/NAT gateway, với mục tiêu chuyển source IP của pod thành IP egress.
- Và để phân biệt từng kết nối đi qua NAT, điểm NAT (node/gateway) sẽ dùng ephemeral source port để tạo ra mapping cho từng flow
Mà dải ephemeral ports dùng cho outbound/NAT mapping là hữu hạn.
SNAT port exhaustion: thiếu port khả dụng có thể gây timeout “ngẫu nhiên”
Vì sao lại ngẫu nhiên?
Vì khi port gần cạn, một phần kết nối mới có thể không lấy được NAT mapping/port phù hợp. Request đó có thể timeout (hoặc retry/fail tuỳ client). Một request khác thì may mắn xin được, vẫn OK.
Tại sao scale pod lên lại tệ hơn?
Khi scale số lượng pod lên, nếu nhiều pod cùng egress qua một điểm SNAT/NAT IP chung (node IP hoặc NAT gateway IP), dải ephemeral ports hữu hạn ở điểm đó sẽ chịu áp lực lớn hơn. Khi outbound concurrency tăng nhanh, port pressure tăng theo và đến ngưỡng thì tỷ lệ timeout có thể tăng rõ rệt, hệ thống trông “tệ hơn”.
Dấu hiệu hay gặp
- Đặc điểm của workload là số lượng lớn short-lived connection, xuất phát từ việc thiếu keep-alive, gRPC không reuse channel và HTTP client hoạt động theo kiểu mở/đóng liên tục
- Số lượng lớn socket ở trạng thái
TIME_WAIT, tức là kết nối đã đóng nhưng vẫn tồn tại thêm một khoảng thời gian (dấu hiệu connection churn cao) - Nhiều sidecar/proxy làm tăng số connection ra ngoài
- Nếu traffic tập trung vào một vài destination (ví dụ payment/search/partner API), áp lực NAT mapping/ports thường tăng nhanh hơn
Cách kiểm chứng nhanh trên node/pod
Các lệnh này nên chạy trên node bị nghi ngờ (debug node bằng daemonset/tooling của bạn). Tuy nhiên trong Kubernetes, phần lớn socket/connection nằm trong network namespace của pod, nên để phản ánh đúng workload thì nên đo thêm trong pod (hoặc nsenter vào netns của pod) để đối chiếu.
1) Xem port range
cat /proc/sys/net/ipv4/ip_local_port_range
Thường là một khoảng kiểu 32768 60999 tuỳ distro. Khoảng này càng hẹp, càng dễ chạm ngưỡng khi outbound connection churn cao.
2) Nhìn tổng quan TCP
ss -s
3) Đếm TIME_WAIT
ss -ant state time-wait | wc -l
Nếu con số này rất lớn và tăng theo load, đó là dấu hiệu connection churn cao (dễ kéo theo áp lực ephemeral port/NAT mapping). Nên đối chiếu thêm với conntrack/NAT stats để kết luận chắc hơn.
4) Kiểm tra phân bố outbound connections theo destination
ss -antp | head
# hoặc lọc theo dst
ss -ant | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -nr | head
Ưu tiên giảm connection churn từ ứng dụng
- Bật keep-alive / connection pooling (HTTP client, JDBC connection pool, Redis client, gRPC channel reuse)
- Giảm connection churn: không tạo client/connection theo từng request, tái sử dụng instance/pool
- Với Java/Go/Node: xác minh HTTP client có reuse connection thật (proxy/env/config có thể vô hiệu hoá keep-alive hoặc làm pool không hoạt động như kỳ vọng)
Nếu cần xử lý ở tầng hạ tầng:
- Mở rộng dải ephemeral ports (
ip_local_port_range) để tăng số port khả dụng cho outbound - Giảm áp lực TIME_WAIT (thận trọng vì đây là OS tuning, chỉ làm khi hiểu rõ trade-off và đã thử giảm churn từ ứng dụng trước)
- Phân tán egress/SNAT: tăng số SNAT/NAT IP (NAT gateway nhiều IP/EIP, egress gateway), hoặc tách node pool cho workload egress-heavy để tránh dồn quá nhiều outbound qua một điểm
Conntrack quá tải: có thể drop packet/không tạo state cho flow mới, gây timeout
Conntrack là bảng kernel theo dõi trạng thái connection, đặc biệt khi bạn dùng NAT/iptables. Nếu bảng này full, kernel có thể không tạo được state cho flow mới và sẽ drop/không xử lý một phần packet liên quan, dẫn tới timeout.
Triệu chứng
-
Timeout ngẫu nhiên tăng theo lưu lượng
-
Trên node có thể xuất hiện log kiểu:
-
nf_conntrack: table full, dropping packet -
Một số flow sẽ fail dù không thấy CPU/RAM cao
Cách kiểm chứng
1) Xem mức sử dụng conntrack
cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_max
Nếu count bám sát max (đặc biệt kèm log/counter drop), thì đây là dấu hiệu rất mạnh cho thấy conntrack đang là bottleneck.
2) Thống kê conntrack
conntrack -S
Nhìn các counters drop/insert_failed.
3) Nếu bạn dùng Prometheus/node-exporter Hay có metric kiểu:
node_nf_conntrack_entriesnode_nf_conntrack_entries_limit
Alert đơn giản:
node_nf_conntrack_entries / node_nf_conntrack_entries_limit > 0.8
Cách sửa
- Tối ưu app để giảm số connection/flow
- Tăng nf_conntrack_max
- Tune conntrack timeouts
- Xem lại CNI/kube-proxy mode (conntrack có thể bị ăn bởi cả egress và một phần east-west/service NAT tuỳ stack):
- Trong nhiều stack, iptables NAT-heavy sẽ tạo áp lực đáng kể lên conntrack khi số flow lớn
- Một số dataplane eBPF có thể giảm áp lực lên conntrack ở một số đường đi nhất định (tuỳ stack), nhưng vẫn cần đo thực tế
Runbook khi gặp timeout egress
Khi bạn thấy timeout ngẫu nhiên ra ngoài, hãy thử làm theo thứ tự này:
- Xác định có phải chỉ outbound không
- inbound nội bộ cluster ổn, nhưng gọi ra ngoài fail => ưu tiên nghi egress path
- Xác định có dính theo node không
- Lấy vài request-id timeout, map về pod, map về node
- Nếu timeout tập trung theo 1-2 node => ưu tiên cao là vấn đề tại node/egress path của node (conntrack/port pressure/dataplane), cần kiểm chứng bằng số liệu trên node
- Trên node đó (và/hoặc trong pod nghi ngờ), check:
nf_conntrack_count/maxTIME_WAITcountip_local_port_rangess -sxem orphan/retrans/estab bất thường
- Nếu conntrack chạm ngưỡng kèm drop/insert_failed thì xử lý conntrack trước. Nếu TIME_WAIT lớn + kết nối ngắn (churn cao) thì ưu tiên xử lý connection churn/SNAT pressure trước.
- Sau khi ổn định hệ thống, quay lại xử lý dài hạn:
- fix ứng dụng (pool/keep-alive)
- thiết kế egress (phân tán IP NAT / giảm hotspot theo node)
Vì sao F5 lại thành công?
Vì mỗi lần F5 thường tạo một lượt request mới (và trong nhiều trường hợp sẽ kéo theo connection mới hoặc stream mới tuỳ HTTP/1.1, HTTP/2, keep-alive). Lần đầu fail không hẳn do provider chập chờn, mà có thể do tại thời điểm đó điểm egress/NAT không tạo được tài nguyên cho flow mới (ví dụ: SNAT port hoặc conntrack entry).
Khi F5, kết quả có thể khác vì:
- một phần conntrack entry vừa được giải phóng sau timeout
- một phần tài nguyên NAT/ephemeral port vừa được giải phóng (ví dụ TIME_WAIT giảm, conntrack entry hết hạn)
- thời điểm F5 không còn trùng lúc node bị spike port/conntrack pressure
Vì vậy, F5 thành công dễ tạo cảm giác lỗi nằm ở bên ngoài, nhưng trong nhiều trường hợp bottleneck nằm ở egress path (node/gateway) của hệ thống.
Checklist
-
Outbound client thường nên bật pooling/keep-alive (HTTP/gRPC/DB/Redis) và cần xác minh là connection thật sự được reuse trong môi trường thực tế
-
Theo dõi và đặt cảnh báo:
-
Conntrack utilization (alert từ 70-80%)
-
Số lượng socket ở trạng thái
TIME_WAIT(đặc biệt khi peak, và nên đối chiếu với churn/conntrack/NAT pressure) -
Số outbound connections trên mỗi node (để phát hiện hotspot)
-
Thiết kế egress:
-
Nếu có thể, tránh để traffic egress lớn tập trung qua một SNAT/NAT IP duy nhất
-
Tách node pool cho workload egress-heavy (hoặc phân bổ lại workload để giảm hotspot)
Bài này mình cũng không khẳng định DNS hay provider không bao giờ sai, mà chỉ thêm một góc nhìn: đôi khi vấn đề nằm ngay trong egress path (node/gateway). Nếu team bạn từng tranh luận kiểu do code hay do hạ tầng, mình thấy cách tốt nhất là thống nhất một checklist khoanh vùng như trên để giảm cảm tính thì đây là thêm 1 gạch đầu dòng bạn có thể tham khảo. Và bạn có thể disagree, nhưng miễn là dữ liệu (TIME_WAIT/conntrack/port range/log/counters) nói chuyện trước.






