Service Connectivity Debugging: DNS, Routing & Network Policy
Tại sao Network Debugging Khó
Networking failures trong GKE đặc biệt khó debug vì:
Thứ nhất, "network down" thường biểu hiện giống nhau — connection refused, timeout, DNS resolution fail — nhưng nguyên nhân có thể ở nhiều layer khác nhau: application config, Kubernetes Service config, DNS resolution, iptables rules, CNI plugin, VPC firewall rules. Mỗi layer có tooling khác nhau.
Thứ hai, network issues thường intermittent. Conntrack table overflow chỉ xảy ra dưới load cao. DNS timeout chỉ xảy ra khi CoreDNS bị overloaded. Nếu bạn debug sau khi traffic giảm, bạn có thể không reproduce được.
Thứ ba, packet path trong GKE có nhiều hop. Một request từ Pod A đến Service B đi qua: application DNS resolution → CoreDNS → Service ClusterIP → iptables/eBPF DNAT → target Pod endpoint → network policy enforcement → container. Bất kỳ hop nào cũng có thể fail.
Để debug hiệu quả, bạn phải biết internal model của từng layer.
DNS Internal Model trong GKE
Hai DNS Provider: kube-dns và Cloud DNS
GKE hỗ trợ hai provider:
- kube-dns (mặc định cho cluster cũ): CoreDNS + dnsmasq
- Cloud DNS (option mới hơn): Managed DNS service của Google, không cần deploy DNS pods trong cluster
Trong cả hai trường hợp, mọi Pod trong cluster được cấu hình /etc/resolv.conf với:
nameserver 10.96.0.10 # ClusterIP của DNS service
search NAMESPACE.svc.cluster.local svc.cluster.local cluster.local
options ndots:5CoreDNS Resolution Pipeline (kube-dns default)
Khi Pod resolve my-service:
- Pod gửi DNS query UDP đến nameserver IP (ClusterIP của kube-dns service)
- iptables/eBPF DNAT ClusterIP → một trong các CoreDNS Pod IPs (load balancing)
- CoreDNS nhận query
my-service - CoreDNS áp dụng search domains: lần lượt thử:
my-service.NAMESPACE.svc.cluster.local→ lookup trong Kubernetes pluginmy-service.svc.cluster.localmy-service.cluster.local- Nếu tất cả fail và ndots < 5: thử resolve
my-servicetrực tiếp (external)
- Kubernetes plugin của CoreDNS query Kubernetes API để lấy Service/Endpoint thông tin
- Trả về ClusterIP của Service
ndots Problem — Nguyên Nhân Phổ Biến Của DNS Timeout
options ndots:5 có nghĩa là: nếu domain name có ít hơn 5 dấu chấm, thêm search domains trước khi resolve.
Ví dụ query google.com (1 dấu chấm < 5):
- Thử
google.com.NAMESPACE.svc.cluster.local→ NXDOMAIN - Thử
google.com.svc.cluster.local→ NXDOMAIN - Thử
google.com.cluster.local→ NXDOMAIN - Cuối cùng thử
google.com→ SUCCESS
Mỗi lần thử là một UDP roundtrip đến CoreDNS. Với 3 search domains, một query external DNS thực ra là 4 queries. Dưới load cao, CoreDNS có thể respond chậm → application thấy latency cao trên mọi external call.
NodeLocal DNSCache giải quyết vấn đề này bằng cách cache DNS responses tại node level, giảm CoreDNS load và latency. Khi bật, Pod gửi DNS query đến local agent (169.254.20.10) thay vì ClusterIP của DNS service.
Debugging DNS Issues — Methodology
Bước 1: Xác định DNS query có đến CoreDNS không
# Test từ trong Pod có vấn đề
kubectl exec -it POD_NAME -n NAMESPACE -- /bin/sh
nslookup service-name # Có resolve không?
nslookup service-name.NAMESPACE # Fully qualified — bỏ qua search domain
dig @10.96.0.10 service-name.NAMESPACE.svc.cluster.local # Direct query tới CoreDNSNếu nslookup fail nhưng dig @DNS_IP thành công → vấn đề ở /etc/resolv.conf hoặc search domain config.
Bước 2: Kiểm tra CoreDNS Pod health
kubectl get pods -n kube-system -l k8s-app=kube-dns
# Tất cả pods phải ở Running state
kubectl logs -n kube-system -l k8s-app=kube-dns --tail=100
# Tìm: SERVFAIL, timeout, refusedBước 3: Packet capture (advanced)
Nếu cần xác nhận DNS packets có đến CoreDNS không:
# Toolbox trên GKE node
kubectl debug node/NODE_NAME -it --image=ubuntu
# Hoặc SSH vào node (GKE standard cluster)
# Sau đó:
tcpdump -i any port 53 -nn -v 2>/dev/null | head -50Bước 4: Kiểm tra NetworkPolicy trên kube-system namespace
Nếu cluster có NetworkPolicy, cần đảm bảo Pods có thể reach DNS:
kubectl get networkpolicy -n kube-system
# Nếu có policy restrictive → đảm bảo port 53 TCP/UDP không bị blockLỗi Cloud DNS phổ biến
Khi dùng Cloud DNS thay vì kube-dns:
# Check Cloud DNS policy được gán cho cluster network
gcloud dns policies list
# Test DNS resolution
kubectl exec -it POD_NAME -- dig @169.254.169.254 service.namespace.svc.cluster.local
# 169.254.169.254 là metadata server, cũng có thể serve DNS cho GKE clusterService Routing: iptables và eBPF (GKE Dataplane V2)
Cơ Chế ClusterIP và DNAT
Kubernetes Service với type ClusterIP có một virtual IP (VIP) không gán vào bất kỳ interface nào. Khi Pod gửi packet đến ClusterIP, iptables (hoặc eBPF) bắt packet và DNAT nó đến một Pod endpoint thực.
GKE Dataplane V1 (iptables):
- kube-proxy chạy trên mỗi node, watch Kubernetes API
- Khi Service/Endpoint thay đổi, kube-proxy cập nhật iptables rules
- Mỗi Service → một chain iptables với probabilistic NAT rules (round-robin)
- Vấn đề scale: với 10,000 Services, có thể có hàng trăm nghìn iptables rules → latency cao khi traverse chain
GKE Dataplane V2 (eBPF, mặc định cho cluster mới):
- Cilium CNI replace kube-proxy
- Rules được compile thành BPF programs và load vào kernel
- Performance tốt hơn ở scale lớn
- Native NetworkPolicy enforcement không cần iptables chains riêng
- Xem thêm: Chương 07 — GKE Networking Internals
Conntrack Table và Exhaustion
Mỗi NAT connection được track trong kernel conntrack table. Với default settings, GKE nodes có conntrack table giới hạn. Khi table đầy, kernel drop new connections mà không có error rõ ràng — connection cố gắng kết nối nhưng silently fail.
Triệu chứng: Intermittent connection failures chỉ xảy ra dưới high load, không có error log từ application, không có packet loss detect được.
Diagnosis:
# SSH vào node (GKE standard)
# Xem conntrack table usage
cat /proc/sys/net/netfilter/nf_conntrack_count # Current entries
cat /proc/sys/net/netfilter/nf_conntrack_max # Max
# Nếu count gần max → conntrack exhaustionGKE node logs cũng ghi event nf_conntrack: table full, dropping packet.
Packet Path từ Pod đến Pod: Overlay Network
VPC-Native GKE (Alias IP)
Trong GKE VPC-native mode (mặc định), mỗi Pod có IP từ VPC subnet secondary range. Packet từ Pod A đến Pod B:
- Packet rời Pod A với src=PodA_IP, dst=PodB_IP
- Veth pair → node network namespace
- Kernel routing: lookup route table → route ra interface tương ứng
- Nếu PodB cùng node: veth pair của PodB, trực tiếp
- Nếu PodB khác node: packet đi qua VPC routing (không cần tunnel/overlay)
- VPC router deliver packet đến node của PodB
- Node của PodB: kernel route đến veth pair của PodB
Không có overlay network (VXLAN/GENEVE) trong VPC-native mode. Đây là điểm khác biệt với nhiều Kubernetes deployment khác. MTU không bị reduce vì không có encapsulation header.
Debugging Pod-to-Pod Connectivity
# Test từ Pod nguồn
kubectl exec -it SOURCE_POD -n NAMESPACE -- ping DST_POD_IP
# Test port cụ thể
kubectl exec -it SOURCE_POD -n NAMESPACE -- nc -zv DST_POD_IP 8080
# hoặc
kubectl exec -it SOURCE_POD -n NAMESPACE -- curl -v http://DST_POD_IP:8080/health
# Nếu ping thành công nhưng curl fail → vấn đề ở application layer, không phải network
# Nếu ping fail → network layer issue (NetworkPolicy, VPC firewall, routing)Network Policy: Cơ Chế Enforcement
Cách NetworkPolicy Được Enforce
NetworkPolicy là Kubernetes object định nghĩa ingress/egress rules cho Pods. Nhưng NetworkPolicy chỉ có tác dụng khi CNI plugin hỗ trợ enforcement.
Trong GKE:
- GKE Dataplane V1: NetworkPolicy được enforce qua iptables rules được cài bởi network policy controller
- GKE Dataplane V2 (Cilium): NetworkPolicy được enforce qua eBPF programs. Cilium compile policy thành BPF maps (hash maps lookup O(1)), không phải linear iptables chain traversal
Policy Compilation và Fan-out
Khi NetworkPolicy thay đổi, Cilium phải:
- Parse policy object
- Compute set of Pods affected (by selector)
- Compile BPF program cho mỗi affected Pod endpoint
- Load BPF programs vào kernel
Với large clusters có nhiều Pods và nhiều NetworkPolicies, compilation có thể mất vài giây. Trong khoảng thời gian đó, traffic behavior có thể không consistent.
Debugging Network Policy
Xác định policy có block traffic không:
# List all policies trong namespace
kubectl get networkpolicy -n NAMESPACE -o yaml
# Kiểm tra policy nào apply cho pod cụ thể (dựa trên label selector)
kubectl describe networkpolicy POLICY_NAME -n NAMESPACETest bypass policy để confirm:
# Tạm thời thêm label vào Pod để match một allow policy
# Hoặc tạo policy tạm thời allow all để test
kubectl apply -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: debug-allow-all
namespace: NAMESPACE
spec:
podSelector:
matchLabels:
app: problem-pod # label của pod cần test
ingress:
- {} # Allow all ingress
egress:
- {} # Allow all egress
EOFNếu sau khi apply debug policy, connectivity restore → problem là NetworkPolicy restriction.
GKE Dataplane V2: Network Policy Logging
Cilium có thể log policy verdicts (allow/deny). Enable:
# Check nếu network policy logging đã được enable
kubectl get configmap -n kube-system cilium-config -o yaml | grep policy-audit-modeVới logging enabled, bạn có thể thấy chính xác packet nào bị drop bởi policy nào trong Cloud Logging.
Six-Step Systematic Connectivity Debugging
Khi gặp "Pod A không connect được Pod B / Service B", theo 6 bước:
Bước 1: Confirm DNS Resolution
kubectl exec -it POD_A -- nslookup SERVICE_B
# Nếu fail → DNS issue (xem section DNS bên trên)
# Nếu OK → continueBước 2: Confirm Service có Endpoints
kubectl get endpoints SERVICE_B -n NAMESPACE
# Nếu Endpoints rỗng → không có Pod nào pass readiness probe + match service selector
# Check pod labels và service selector có khớp không
kubectl describe service SERVICE_B -n NAMESPACE | grep Selector
kubectl get pods -n NAMESPACE --show-labels | grep LABELBước 3: Test Connection với Service ClusterIP
kubectl exec -it POD_A -- curl http://CLUSTER_IP:PORT
# Nếu success → vấn đề ở DNS, không phải routing
# Nếu fail → vấn đề ở iptables/eBPF DNAT hoặc target podBước 4: Test Connection trực tiếp với Pod IP
# Lấy Pod IP của một endpoint
kubectl get endpoints SERVICE_B -n NAMESPACE -o json | jq '.subsets[].addresses[].ip'
kubectl exec -it POD_A -- curl http://POD_B_IP:PORT
# Nếu success nhưng bước 3 fail → iptables/eBPF DNAT issue
# Nếu fail → NetworkPolicy hoặc Pod application issueBước 5: Test từ Node Level
# Kiểm tra nếu node có thể reach Pod IP (loại trừ network policy)
# Cần SSH vào node (GKE standard) hoặc dùng DaemonSet debug pod
curl http://POD_B_IP:PORT # Từ node host network
# Nếu success từ node nhưng fail từ Pod → NetworkPolicy issue
# Nếu fail từ cả node → VPC routing hoặc Pod application issueBước 6: VPC-Level Connectivity Test
# Dùng Google Cloud Connectivity Tests
gcloud beta network-management connectivity-tests create test-name \
--source-ip=SOURCE_POD_IP \
--destination-ip=DESTINATION_POD_IP \
--destination-port=PORT \
--protocol=TCPGoogle Cloud Connectivity Tests phân tích VPC firewall rules và routing tables, cho biết packet có thể đến destination không ở level infrastructure.
Anti-Pattern: Skip DNS và Hardcode IP
Tại sao hardcode Pod IP là sai về cơ chế:
Pod IP trong Kubernetes là ephemeral. Khi Pod restart (do crash, rolling update, node drain), nó nhận IP mới. Service ClusterIP là stable — nó tồn tại cho đến khi Service bị xóa và không thay đổi.
Application hardcode Pod IP sẽ:
- Kết nối thành công ban đầu
- Pod restart → IP thay đổi
- Application có cached IP cũ → connection fail
- Application cần restart để pickup IP mới (nếu không có retry logic)
Pattern đúng: Luôn resolve qua Service DNS name. service-name.namespace.svc.cluster.local luôn resolve ra ClusterIP stable.
Load Balancing và Session Affinity
Service trong Kubernetes mặc định round-robin load balance. Tuy nhiên:
- iptables probabilistic NAT không phải true round-robin, có thể có imbalance dưới load thấp
- Với Cilium (Dataplane V2), BPF-based load balancing có distribution tốt hơn
Session Affinity (sessionAffinity: ClientIP) pin connection từ một client IP đến một Pod. Điều này giúp với stateful applications nhưng làm mất khả năng scale khi có hotspot client IP.