Detailed Packet Path Analysis — 5 Đường Đi Của Gói Tin
Vì sao chủ đề này là kỹ năng debug cốt lõi
Mọi sự cố mạng GKE, dù phức tạp đến đâu, đều quy về một câu hỏi: gói tin chết ở chặng nào? Một kỹ sư biết chính xác packet đi qua những chặng nào — veth, bridge/eBPF, alias IP, fabric VPC, masquerade, Cloud NAT, load balancer — có thể đặt tcpdump đúng điểm, đọc đúng counter, và khoanh vùng trong vài phút. Một kỹ sư không có mental model này sẽ đoán mò: restart pod, xóa và tạo lại Service, đổi firewall lung tung, và đôi khi vô tình “sửa” bằng cách che giấu triệu chứng.
File này dựng mental model cho năm đường đi quan trọng nhất. Với mỗi đường, ta đi theo gói tin từ đầu đến cuối, chỉ ra nơi địa chỉ bị dịch (NAT), nơi quyết định được đưa ra (kernel hook, eBPF/iptables), và nơi gói có thể bị drop. Đây là nền tảng để file 6 (troubleshooting toolkit) trở nên hữu dụng.
Nền tảng: các thành phần trên một node
Trước khi đi vào từng path, cần nắm các thành phần Linux networking trên một node GKE:
- Network namespace của Pod: mỗi Pod có netns riêng, với
eth0(tên trong pod) và bảng route/ARP riêng. - veth pair: một “sợi cáp ảo” hai đầu; một đầu nằm trong netns Pod (
eth0), đầu kia nằm ở host netns. - Host networking: đầu host của veth được nối vào dataplane — với mô hình bridge cổ điển là
cbr0, với Dataplane V2 là chương trình eBPF gắn ở tc/host. eth0của node (NIC thật): nơi gói rời node ra VPC, mang alias IP của Pod làm địa chỉ nguồn (trong nội bộ VPC).- conntrack: bảng theo dõi kết nối của netfilter, cốt lõi cho NAT/DNAT có trạng thái (sẽ quay lại ở file 6).
Path 1: Same-node pod-to-pod
Đây là đường đơn giản nhất và là baseline để hiểu các đường còn lại.
Diễn biến (mô hình bridge cổ điển):
- Pod A (netns A) gửi gói tới IP của Pod B. Bảng route trong netns A trỏ default về đầu host của veth.
- Gói đi qua veth A → tới host netns.
- Ở host, gói vào
cbr0(Linux bridge). Vì Pod B cùng node nối vào cùng bridge, bridge chuyển gói ở L2 (theo MAC) sang veth B. - Gói đi qua veth B → vào netns B → tới
eth0của Pod B.
Không có NAT, không rời node, không chạm VPC. Latency cực thấp.
Với Dataplane V2 (eBPF): thay vì bridge L2 thuần, chương trình eBPF gắn ở host xử lý việc chuyển tiếp giữa hai endpoint cục bộ, đồng thời áp NetworkPolicy theo identity ngay tại đây. Về mặt logic vẫn “trong node”, nhưng quyết định forward/allow do eBPF thực hiện.
Nơi gói có thể chết: NetworkPolicy chặn (allow/deny ở eBPF/iptables), hoặc Pod B chưa Ready/đã chết nhưng vẫn còn trong endpoint. Đây là path lý tưởng để học tcpdump trên veth vì không bị nhiễu bởi NAT.
Lưu ý về intranode visibility: mặc định traffic same-node giữa các Pod có thể không qua đầy đủ các hook quan sát; GKE có tùy chọn “intranode visibility” để traffic nội node cũng hiện trong VPC Flow Logs (Intranode visibility).
Path 2: Cross-node pod-to-pod
Đây là nơi VPC-native (file 1) phát huy tác dụng.
Diễn biến:
- Pod A (node 1) gửi gói tới IP Pod B (node 2). IP Pod B là một alias IP thuộc dải
/24của node 2. - Gói đi veth A → host netns node 1. Dataplane (eBPF/route) thấy đích không phải Pod cục bộ → chuyển ra
eth0của node 1. - Không SNAT (đây là điểm then chốt của VPC-native): gói rời node 1 với source = Pod A IP, destination = Pod B IP, cả hai là alias IP hợp lệ trong VPC.
- Andromeda (SDN của VPC) biết Pod B IP là alias của node 2 (vì GKE đã gán dải đó lên NIC node 2) → định tuyến gói thẳng tới node 2. Không cần route tĩnh.
- Tại node 2, gói vào host netns → dataplane chuyển tới veth B → netns B → Pod B.
Điểm mấu chốt: Pod IP được định tuyến tự nhiên trong VPC, nên cross-node pod-to-pod không cần NAT và không cần overlay/encapsulation như nhiều CNI khác. Đây chính là lợi ích của alias IP đã mô tả ở file 1.
Nơi gói có thể chết:
- Firewall VPC chặn (firewall áp lên dải Pod/Node — kiểm tra rule ingress/egress).
- NetworkPolicy chặn ở node nguồn hoặc đích.
- MTU mismatch gây drop với gói lớn (đặc biệt nếu có lớp encapsulation hybrid hoặc ICMP phân mảnh bị drop trên Dataplane V2 — xem file 2).
Path 3: Pod-to-Service (ClusterIP)
Service ClusterIP là một IP ảo (VIP) không gắn với bất kỳ NIC nào; nó chỉ tồn tại như một rule DNAT.
Diễn biến:
- Pod A gửi gói tới ClusterIP
S(lấy từ secondary range Service). Gói rời netns A vào host. - Tại host, dataplane thực hiện DNAT: chọn một backend Pod trong tập endpoint của Service và viết lại destination từ
Sthành Pod-backend IP.- iptables mode (kube-proxy/Calico): duyệt chuỗi
KUBE-SERVICES→KUBE-SVC-xxx→ chọn endpoint theo xác suất (statistic module) →KUBE-SEP-xxxthực hiện DNAT. (Chi tiết ở file 4.) - eBPF mode (Dataplane V2): tra hash map của Service → chọn backend → DNAT trong eBPF, O(1).
- iptables mode (kube-proxy/Calico): duyệt chuỗi
- conntrack ghi nhận ánh xạ để gói reply được un-DNAT đúng chiều về.
- Sau DNAT, gói tiếp tục theo Path 1 (nếu backend cùng node) hoặc Path 2 (nếu khác node).
Session affinity: nếu Service đặt sessionAffinity: ClientIP, dataplane ghi nhớ ánh xạ client→backend trong một khoảng thời gian để các gói cùng client đi về cùng backend.
externalTrafficPolicy và internalTrafficPolicy: quyết định gói có được chuyển sang node khác để tới backend hay chỉ chọn backend cục bộ. Local giữ gói ở node nhận (bảo toàn source IP, tránh hop thừa) nhưng có thể mất cân bằng nếu phân bố Pod không đều; Cluster cân bằng toàn cục nhưng thêm một hop và thường SNAT.
Nơi gói có thể chết:
- Service không có endpoint
Ready→ gói bị drop/reject (biểu hiện “connection refused” hoặc timeout). - DNAT chọn backend đã chết do endpoint chưa kịp cập nhật (đặc biệt với iptables resync chậm — file 4).
- conntrack đầy → kết nối mới bị drop ngẫu nhiên (file 6).
Path 4: Pod-to-external (egress ra internet/on-prem)
Đây là path hay gây sự cố “SNAT sai” và liên quan trực tiếp tới ip-masq-agent và Cloud NAT.
Diễn biến (egress ra internet, node có external IP hoặc qua Cloud NAT):
- Pod A gửi gói tới một IP ngoài cluster (ví dụ API công khai, hoặc service on-prem).
- Tại host,
ip-masq-agentquyết định có SNAT hay không dựa trênnonMasqueradeCIDRs:- Nếu đích nằm trong
nonMasqueradeCIDRs→ giữ nguyên source = Pod IP (không SNAT). - Nếu đích không nằm trong danh sách → SNAT, thay source = node IP (ip-masq-agent).
- Nếu đích nằm trong
- Nếu node không có external IP (private cluster) và đích là internet → gói đi tiếp tới Cloud NAT, nơi source IP được dịch sang IP NAT công khai (Cloud NAT).
- Reply đi ngược lại: Cloud NAT/conntrack un-NAT, rồi un-SNAT, rồi DNAT về đúng Pod.
Mặc định quan trọng: khi ConfigMap ip-masq-agent không tồn tại hoặc không có nonMasqueradeCIDRs, hành vi mặc định là SNAT mọi traffic ra ngoài bằng node IP (ip-masq-agent). Đây là gốc rễ của Scenario fintech ở file 1: firewall on-prem chỉ cho dải Pod, nhưng gói lại mang node IP → bị chặn.
nonMasqueradeCIDRs nên gồm: tối thiểu dải Node và Pod của cluster (đặc biệt khi bật intranode visibility), cộng các dải đích mà bạn muốn giữ source Pod IP (ví dụ dải on-prem, các dải đi qua Cloud NAT mà bạn muốn dùng Pod IP). Nếu muốn không SNAT bất kỳ traffic nào, đặt nonMasqueradeCIDRs: [0.0.0.0/0].
Lưu ý Dataplane V2: manifest ip-masq-agent cần --random-fully=false khi dùng Dataplane V2 (ip-masq-agent).
Nơi gói có thể chết:
- SNAT sai → firewall đích chặn vì IP nguồn không như mong đợi.
- Cloud NAT port exhaustion → kết nối mới bị drop,
NAT_ALLOCATION_FAILED(capacity planning Cloud NAT là chủ đề riêng). - Firewall egress của VPC chặn.
Path 5: External-to-pod (traffic vào qua LoadBalancer)
Đây là path phức tạp nhất vì có hai biến thể khác nhau căn bản: qua NodePort/kube-proxy (cũ) và container-native qua NEG (khuyến nghị).
Biến thể A: Instance group + NodePort (mô hình cũ, “double hop”)
- Client → Google Front End (GFE)/load balancer → chuyển gói tới một node trên NodePort.
- Node nhận có thể không chạy Pod backend → kube-proxy (iptables) lại DNAT và chuyển gói sang node khác có Pod → đây là hop thứ hai (“double hop”).
- Vì hop thứ hai thường kèm SNAT, source IP của client bị che (trừ khi
externalTrafficPolicy: Local).
Vấn đề: load balancer chỉ thấy node, không thấy Pod; thêm hop làm tăng latency và điểm lỗi (container-native load balancing).
Biến thể B: Container-native load balancing qua NEG (khuyến nghị)
- GKE tạo Network Endpoint Group kiểu
GCE_VM_IP_PORT, trong đó endpoint chính là Pod IP:port (nhờ VPC-native, Pod IP định tuyến được). - Client → GFE/load balancer → đi thẳng tới Pod IP, bỏ qua NodePort và kube-proxy (container-native load balancing).
- Health check đi trực tiếp tới Pod (kết hợp readiness), nên load balancer “thấy” vòng đời Pod (startup, mất Pod) và traffic ổn định hơn.
Lợi ích: bớt một hop (latency thấp hơn), visibility ở cấp Pod (NEG), và là điều kiện cho Cloud Service Mesh. Đây là lý do container-native LB là mặc định khuyến nghị cho Service/Ingress/Gateway hiện đại trên GKE.
Nơi gói có thể chết:
- Health check fail (sai port/path/readiness) → backend bị đánh dấu unhealthy, LB trả 502.
- Firewall không cho dải health check của Google tới Pod.
- (Biến thể A)
externalTrafficPolicycấu hình sai gây mất cân bằng hoặc mất source IP ngoài ý muốn.
Production architecture patterns
Pattern 1: Mặc định container-native LB cho mọi traffic vào
Trừ trường hợp legacy, dùng NEG-based (container-native) cho Service LoadBalancer/Ingress/Gateway để tránh double-hop và có health check cấp Pod. Đây là pattern chuẩn cho VPC-native cluster.
Pattern 2: Thiết kế ip-masq-agent theo ma trận đích
Lập bảng: với mỗi nhóm đích (internet, on-prem, VPC peer, Google APIs), quyết định có giữ source Pod IP hay không, rồi cấu hình nonMasqueradeCIDRs tương ứng. Đừng để mặc định “SNAT tất cả” nếu có phụ thuộc firewall theo Pod IP.
Pattern 3: Chọn externalTrafficPolicy theo nhu cầu source IP
- Cần source IP thật của client (audit, geo, rate-limit) và backend phân bố đều → cân nhắc
Local. - Ưu tiên cân bằng tải đều, chấp nhận mất source IP →
Cluster. Với NEG container-native, vấn đề source IP thường được giải quyết tốt hơn ngay từ kiến trúc.
Real-world scenarios
Scenario A: 502 ngắt quãng qua Ingress
Backend đôi khi trả 502. Nguyên nhân: health check NEG trỏ sai readiness path; khi Pod tạm thời busy, health check fail, LB rút backend, gây 502. Sửa: căn chỉnh readiness probe và health check; đảm bảo firewall cho dải health check.
Scenario B: Service on-prem chặn vì thấy node IP
Pod gọi DB on-prem nhưng bị từ chối. tcpdump ở on-prem cho thấy source là node IP, không phải Pod IP. Gốc rễ: ip-masq-agent SNAT mặc định. Sửa: thêm dải on-prem vào nonMasqueradeCIDRs để giữ Pod IP.
Scenario C: Drop ngẫu nhiên egress lúc cao điểm
Egress ra internet thỉnh thoảng timeout dưới tải cao. Nguyên nhân: Cloud NAT port exhaustion. Sửa: thêm NAT IP / tăng ports-per-VM / dùng connection pooling + keep-alive để giảm số 5-tuple.
Common mistakes / anti-patterns
- Dùng NodePort/instance-group LB cho cluster VPC-native mới. Tự nhận double-hop và mất visibility cấp Pod.
- Để
ip-masq-agentmặc định khi có phụ thuộc firewall theo Pod IP. Gói mang node IP bị chặn. - Quên đưa dải Node/Pod vào
nonMasqueradeCIDRskhi bật intranode visibility. Có thể phá vỡ giao tiếp Pod. - Bỏ qua health check khi debug 502. 502 qua LB thường là vấn đề health check/readiness, không phải app.
- Không tính Cloud NAT port budget. Drop im lặng dưới tải cao.
- Đặt
tcpdumpsai chặng. Bắt ở node nhưng vấn đề ở veth Pod (hoặc ngược lại) → mất thời gian.
GCP-native implementation guidance
Service container-native (NEG) cho LB nội bộ
apiVersion: v1
kind: Service
metadata:
name: web
annotations:
cloud.google.com/neg: '{"ingress": true}'
spec:
type: ClusterIP
selector:
app: web
ports:
- port: 80
targetPort: 8080Cấu hình ip-masq-agent giữ Pod IP cho on-prem và VPC
apiVersion: v1
kind: ConfigMap
metadata:
name: ip-masq-agent
namespace: kube-system
data:
config: |
nonMasqueradeCIDRs:
- 10.0.0.0/8 # VPC + on-prem nội bộ: giữ source Pod IP
- 172.16.0.0/12
- 192.168.0.0/16
masqLinkLocal: false
resyncInterval: 60sKiểm tra đường đi với tcpdump (xem chi tiết file 6)
# Bắt gói trên veth của Pod ở host netns
sudo nsenter -t <PID-pod> -n tcpdump -ni eth0 host <dest-ip>Kết nối với Architecture Framework
- Performance optimization: container-native LB loại bỏ hop thừa, giảm latency và tăng độ ổn định traffic vào (Architecture Framework).
- Reliability: hiểu nơi gói có thể chết ở từng path là điều kiện để thiết kế health check, firewall và NAT đủ headroom (Reliability pillar).
References
- GKE network overview
- Container-native load balancing
- ip-masq-agent
- Cloud NAT overview
- Intranode visibility
- Service ExternalTrafficPolicy (Kubernetes)
- Google Cloud Architecture Framework
MTU, phân mảnh và MSS clamping xuyên các path
Một lớp vấn đề tinh vi cắt ngang cả năm path là MTU (Maximum Transmission Unit). VPC của Google hỗ trợ MTU tới 8896 byte, nhưng mặc định nhiều cấu hình dùng 1460. Khi gói lớn hơn MTU của một chặng nào đó trên đường đi (đặc biệt qua VPN/Interconnect tới on-prem, hoặc qua lớp encapsulation), nó phải phân mảnh hoặc bị drop.
Điểm cần nhớ cho từng path:
- Cross-node pod-to-pod (Path 2): vì VPC-native không encapsulation, MTU thường nhất quán trong VPC; vấn đề chủ yếu phát sinh khi gói đi tới on-prem qua tunnel có MTU nhỏ hơn.
- Pod-to-external (Path 4): đường ra on-prem qua VPN/Interconnect là nơi MTU mismatch hay cắn. Triệu chứng kinh điển: kết nối TCP “handshake xong nhưng treo khi truyền dữ liệu lớn” — đây là dấu hiệu Path MTU Discovery bị chặn (ICMP “fragmentation needed” bị firewall nuốt). Giải pháp thường là MSS clamping tại Cloud Router/biên để giới hạn kích thước segment TCP.
- Dataplane V2 (file 2): lưu ý gói ICMP phân mảnh có thể bị drop, nên đừng dựa vào phân mảnh; thiết kế MTU/MSS hợp lý ngay từ đầu.
Quy tắc thực dụng: nếu kết nối thiết lập được nhưng treo khi truyền payload lớn, nghĩ ngay tới MTU/MSS trước khi nghi app.
Passthrough Network LB và Direct Server Return (DSR)
Với Service LoadBalancer kiểu L4 passthrough (external/internal Network LB), gói không bị proxy: load balancer chuyển gói tới node/endpoint giữ nguyên địa chỉ đích, và reply có thể đi thẳng về client theo cơ chế Direct Server Return thay vì quay lại qua LB. Điều này khác với Application LB (L7) vốn terminate kết nối tại GFE/Envoy.
Hệ quả thực chiến:
- Passthrough LB bảo toàn source IP của client tốt hơn (đặc biệt với
externalTrafficPolicy: Local), hữu ích cho audit/geo/rate-limit. - Vì không proxy, một số tính năng L7 (URL routing, header-based) không có; cần Application LB cho các nhu cầu đó.
- Khi debug, nhớ rằng đường đi reply có thể bất đối xứng so với đường request — đừng giả định reply đi ngược đúng chặng của request.
Firewall cho health check: chặng dễ bị bỏ sót
Ở Path 5 (external-to-pod), health check của Google đến từ các dải IP riêng (ví dụ 35.191.0.0/16, 130.211.0.0/22 cho health check của Google Cloud Load Balancing). Nếu firewall VPC không cho các dải này tới Pod/NEG, backend bị đánh dấu unhealthy và LB trả 502 dù app hoàn toàn khỏe. Đây là một trong những nguyên nhân “502 bí ẩn” phổ biến nhất; luôn kiểm tra firewall health check khi debug Path 5.
Phụ lục: bảng tra nhanh “gói chết ở đâu”
| Triệu chứng | Path nghi ngờ | Chặng kiểm tra đầu tiên |
|---|---|---|
| Hai Pod cùng node không nói chuyện được | 1 | NetworkPolicy (eBPF/iptables), readiness Pod đích |
| Pod khác node timeout | 2 | Firewall VPC trên dải Pod/Node, MTU |
| Gọi Service báo connection refused | 3 | Endpoint Ready, DNAT/conntrack |
| Egress on-prem bị chặn | 4 | ip-masq-agent / nonMasqueradeCIDRs |
| Egress internet drop lúc cao điểm | 4 | Cloud NAT port usage |
| 502 qua LoadBalancer/Ingress | 5 | Health check NEG, readiness, firewall health check |
| Mất source IP của client | 3,5 | externalTrafficPolicy, mô hình LB (NEG vs NodePort) |
Quy trình debug chuẩn theo path
- Xác định path (dùng bảng trên) từ triệu chứng.
- Đặt điểm quan sát ở chặng nghi ngờ gần nguồn nhất rồi dịch dần về đích.
- Với mỗi chặng, hỏi: địa chỉ nguồn/đích lúc này là gì (đã NAT chưa)? Quyết định allow/deny ở đâu?
- Đối chiếu với conntrack và counter drop để xác nhận giả thuyết.
- Chỉ thay đổi cấu hình sau khi đã định vị chặng gãy — tránh “sửa mù”.
Vì sao NAT là nguồn gây nhầm lẫn lớn nhất
Phần lớn nhầm lẫn khi debug đến từ việc không biết lúc này gói đang mang địa chỉ gì. Cùng một kết nối, source IP có thể là Pod IP (cross-node nội VPC), node IP (sau SNAT), hay NAT IP công khai (sau Cloud NAT) tùy chặng. Khi bắt gói, luôn ghi rõ bạn đang đứng ở chặng nào và kỳ vọng thấy địa chỉ gì; sai lệch giữa kỳ vọng và thực tế chính là manh mối.
Ghi nhớ cốt lõi
- Same-node và cross-node pod-to-pod không NAT nhờ VPC-native.
- Pod-to-Service luôn có DNAT (iptables hoặc eBPF) + conntrack.
- Pod-to-external có thể SNAT (ip-masq-agent) rồi Cloud NAT nếu private.
- External-to-pod nên đi container-native (NEG) để bỏ double-hop.
- Đặt
tcpdumpđúng chặng quan trọng hơn việc bắt thật nhiều gói.