kube-proxy & Service Dataplane — iptables vs eBPF
Vì sao chủ đề này quan trọng
Đa số kỹ sư xem Service như một abstraction “luôn hoạt động”: tạo Service, có ClusterIP, gọi là tới. Nhưng phía sau abstraction đó là một dataplane phải dịch hàng nghìn Service và hàng chục nghìn endpoint thành cấu trúc dữ liệu trong kernel, rồi giữ chúng đồng bộ với thực tế đang thay đổi liên tục. Khi cluster nhỏ, mọi thứ vô hình. Khi cluster lớn, chính dataplane này trở thành điểm gãy: latency điều khiển tăng, CPU node bị ăn bởi việc viết lại rule, traffic đến Pod đã chết vì endpoint cập nhật chậm.
Hiểu cơ chế kube-proxy iptables — và vì sao nó gãy ở scale — là điều kiện để (1) biết khi nào cluster của bạn sắp chạm trần, (2) giải thích những incident “latency tăng theo số Service” tưởng vô lý, và (3) ra quyết định chuyển sang eBPF/Dataplane V2 có cơ sở. File này mổ xẻ iptables mode tới mức chuỗi rule, rồi đối chiếu với mô hình eBPF.
Internal model: kube-proxy iptables mode
kube-proxy làm gì
kube-proxy là một DaemonSet chạy trên mỗi node. Nó watch Service và EndpointSlice từ API server, rồi viết các quy tắc vào dataplane của node để hiện thực hóa ClusterIP. Ở chế độ iptables (phổ biến nhất trong mô hình cổ điển), kube-proxy không nằm trên đường đi của gói — nó chỉ lập trình iptables, còn kernel netfilter mới là thứ xử lý gói. Đây là điểm quan trọng: kube-proxy chậm không trực tiếp làm gói chậm, nhưng làm rule lạc hậu so với thực tế (traffic tới Pod đã chết).
Cấu trúc chuỗi iptables của Service
Khi một gói tới ClusterIP, nó đi qua một cây chuỗi (chain) do kube-proxy dựng:
PREROUTING/OUTPUT(tablenat) nhảy vào chuỗi tổngKUBE-SERVICES.KUBE-SERVICESchứa một rule cho mỗi (ClusterIP, port). Rule khớp đích = ClusterIP sẽ nhảy tới chuỗi riêng của Service đó:KUBE-SVC-<hash>.KUBE-SVC-<hash>chứa các rule chọn endpoint. Việc chọn dùng modulestatisticvới xác suất: rule đầu có xác suất1/n, rule kế1/(n-1), ... để phân bổ đều. Mỗi nhánh nhảy tớiKUBE-SEP-<hash>của một endpoint.KUBE-SEP-<hash>(Service EndPoint) thực hiện DNAT: viết lại destination thành Pod-backend IP:port.- conntrack ghi ánh xạ để gói reply được un-DNAT đúng chiều.
Session affinity (sessionAffinity: ClientIP) được hiện thực bằng module recent/conntrack để gói cùng client đi về cùng endpoint trong một khoảng thời gian.
Vấn đề cốt lõi: đây là cấu trúc tuyến tính
Chuỗi KUBE-SERVICES được duyệt tuần tự. Với mỗi gói tới một ClusterIP, kernel có thể phải so khớp qua nhiều rule trước khi tìm đúng Service. Tổng số rule tỉ lệ với số Service, và bên trong mỗi Service, số nhánh tỉ lệ với số endpoint. Đây là gốc rễ của mọi vấn đề scale.
Chain explosion: độ phức tạp O(Services × Endpoints)
Hãy hình dung một cluster có S Service và trung bình E endpoint mỗi Service. Số rule iptables mà kube-proxy phải quản lý tăng theo bậc O(S × E). Một cluster 5.000 Service × 20 endpoint = 100.000 nhánh endpoint, cộng overhead chuỗi → hàng trăm nghìn rule.
Hệ quả phi tuyến:
- Latency tra cứu tăng. Dù netfilter có tối ưu, việc duyệt chuỗi dài vẫn tốn chu kỳ CPU cho mỗi kết nối mới (gói đầu của mỗi flow). Với cluster có connection churn cao, chi phí này cộng dồn.
- Thời gian cập nhật tăng mạnh. Đây mới là vấn đề lớn nhất (xem phần resync). Viết lại một bảng rule khổng lồ mỗi khi endpoint đổi là cực kỳ tốn kém.
- Bộ nhớ và độ phức tạp debug tăng.
iptables-savecủa cluster lớn dài hàng chục nghìn dòng, gần như không đọc nổi bằng mắt.
Kubernetes upstream đã giảm nhẹ vấn đề này theo thời gian (ví dụ chế độ ipvs dùng hash thay vì chuỗi tuyến tính cho việc chọn endpoint), nhưng ipvs vẫn dựa trên netfilter/conntrack và không xóa bỏ hoàn toàn các vấn đề về resync và lock. Lời giải triệt để là rời khỏi iptables — đó là điều Dataplane V2 làm.
iptables lock contention
netfilter dùng một lock toàn cục khi cập nhật bảng rule. Khi kube-proxy (và các thành phần khác như Calico, hoặc ip-masq-agent) cùng muốn sửa iptables, các thao tác bị serialize. Lệnh iptables-restore (kube-proxy dùng để nạp rule hàng loạt) phải giữ lock trong suốt quá trình áp dụng.
Ở cluster lớn, khi có nhiều thay đổi đồng thời (rollout lớn làm nhiều endpoint đổi cùng lúc), kube-proxy phải nạp lại bảng rule lớn trong khi giữ lock; các tiến trình khác muốn sửa iptables phải chờ. Điều này tạo hiệu ứng dây chuyền: cập nhật dataplane chậm lại đúng lúc cluster đang thay đổi nhiều nhất — tức đúng lúc cần dataplane phản ánh thực tế nhanh nhất.
Rule resyncing: full sync vs incremental
kube-proxy đồng bộ rule theo chu kỳ và theo sự kiện. Có hai chế độ cập nhật về mặt khái niệm:
- Full sync: dựng lại toàn bộ bảng rule. Tốn kém, tỉ lệ với
O(S × E). kube-proxy có chu kỳ full sync định kỳ (ví dụ mỗi vài phút) để đảm bảo trạng thái đúng. - Incremental/partial sync: chỉ cập nhật phần thay đổi. Các phiên bản kube-proxy mới cố gắng giảm chi phí bằng cách chỉ sửa phần khác biệt thay vì viết lại tất cả (kube-proxy & EndpointSlice).
EndpointSlice (thay cho Endpoints nguyên khối) là một cải tiến quan trọng: thay vì một object Endpoints khổng lồ thay đổi là phải gửi lại toàn bộ, EndpointSlice chia nhỏ endpoint thành nhiều slice, giảm lượng dữ liệu phải truyền và xử lý khi một endpoint đổi.
Ảnh hưởng latency điều khiển (control-plane latency): khoảng thời gian từ lúc một Pod backend Ready/chết đến lúc rule trên mọi node phản ánh đúng điều đó. Nếu resync chậm, có một cửa sổ thời gian mà:
- Traffic vẫn được DNAT tới Pod đã chết → lỗi connection refused/timeout.
- Pod mới
Readynhưng chưa nhận traffic → mất capacity tạm thời.
Ở cluster nhỏ, cửa sổ này là mili giây. Ở cluster lớn với bảng rule khổng lồ, nó có thể là vài giây — đủ để gây lỗi 5xx có thể quan sát trong rollout.
Dataplane V2 (eBPF) mode: kiến trúc thay thế
Dataplane V2 (file 2) giải quyết tận gốc bằng cách thay iptables bằng eBPF:
- Tra cứu Service = hash map O(1). Thay vì duyệt chuỗi, eBPF tra một map ánh xạ (ClusterIP, port) → danh sách backend rồi chọn backend. Chi phí không tăng theo số Service.
- Cập nhật là thao tác map cục bộ. Khi endpoint đổi,
anetdcập nhật entry trong eBPF map — không có “full sync” bảng rule khổng lồ, không cóiptables-restoregiữ lock. - Không kube-proxy. Loại bỏ luôn DaemonSet kube-proxy và toàn bộ lớp iptables tương ứng (GKE Dataplane V2).
- Giới hạn là dung lượng map, không phải độ dài chuỗi. Như file 2 nêu, endpoint map có trần ~260.000 entry — một giới hạn cứng cần capacity plan, nhưng trong ngân sách đó hiệu năng tra cứu gần như phẳng.
Đổi lại, eBPF có ràng buộc riêng (không nạp eBPF tùy biến, xung đột agent eBPF bên thứ ba, ICMP phân mảnh — file 2). Nhưng với cân bằng tải Service ở scale, mô hình hash map vượt trội rõ rệt so với chuỗi iptables tuyến tính.
So sánh trực diện
| Tiêu chí | iptables (kube-proxy) | eBPF (Dataplane V2) |
|---|---|---|
| Độ phức tạp tra cứu | ~O(n) theo số rule | ~O(1) hash map |
| Cập nhật endpoint | Resync chuỗi, có thể full sync | Cập nhật map cục bộ |
| Lock contention | Có (netfilter lock) | Giảm mạnh |
| Latency điều khiển ở scale | Tăng theo S×E | Gần phẳng trong ngân sách map |
| Trần quy mô | Mềm (chậm dần) | Cứng (dung lượng map ~260K) |
| kube-proxy | Cần | Không |
Production architecture patterns
Pattern 1: Cluster lớn mới → Dataplane V2 ngay từ đầu
Nếu dự kiến cluster có hàng nghìn Service/endpoint hoặc connection churn cao, chọn Dataplane V2 lúc tạo cluster để tránh trần iptables. Quyết định này bất biến (file 2), nên đừng để rơi vào mô hình iptables rồi mới phát hiện trần.
Pattern 2: Giảm áp lực dataplane bằng thiết kế Service
Dù ở dataplane nào, vẫn nên:
- Tránh Service “fan-out” quá lớn (giảm E).
- Gộp/loại bỏ Service không cần thiết (giảm S).
- Dùng EndpointSlice (mặc định ở phiên bản hiện đại) để giảm chi phí cập nhật.
Pattern 3: Theo dõi latency điều khiển như một SLO nội bộ
Đo thời gian từ “Pod Ready” đến “nhận traffic” và từ “Pod chết” đến “ngừng nhận traffic”. Đây là chỉ số sớm cho thấy dataplane đang đuối ở scale, trước khi nó biến thành 5xx trong rollout.
Real-world scenarios
Scenario A: Latency p99 tăng dần theo số Service
Một cluster tăng từ 500 lên 4.000 Service trong một năm. p99 của kết nối mới tăng đều dù mỗi Service không thay đổi. Gốc rễ: chuỗi KUBE-SERVICES dài thêm, chi phí khớp rule cho gói đầu mỗi flow tăng. Hướng xử lý: migrate sang Dataplane V2 (tra cứu O(1)).
Scenario B: 5xx bùng lên mỗi lần rollout lớn
Mỗi đợt deploy lớn gây spike 5xx vài giây. Gốc rễ: nhiều endpoint đổi cùng lúc → kube-proxy full sync + giữ iptables lock → rule lạc hậu trong cửa sổ ngắn → traffic tới Pod đã chết. Hướng xử lý: EndpointSlice + giảm kích thước rollout; triệt để hơn là eBPF.
Scenario C: CPU node bị ăn bởi kube-proxy/Calico
Trên cluster Calico lớn, CPU system của node cao bất thường lúc cluster biến động. Gốc rễ: viết lại iptables/ipset liên tục. Hướng xử lý: Dataplane V2 chuyển sang cập nhật map cục bộ, giảm tải CPU này.
Common mistakes / anti-patterns
- Cho rằng Service “miễn phí”. Mỗi Service và endpoint là chi phí dataplane thực, tích lũy theo S×E.
- Bỏ qua latency điều khiển khi debug 5xx trong rollout. Vấn đề thường ở dataplane resync, không phải app.
- Tạo hàng nghìn Service không dọn dẹp. Làm phình chuỗi iptables.
- Dùng Service fan-out cực lớn. Tăng E, nặng resync, và có thể chạm trần endpoint map trên eBPF.
- Ở lại iptables cho cluster lớn mới vì “quen”. Bỏ lỡ lợi ích O(1) của eBPF.
- Không dùng EndpointSlice. (Phiên bản hiện đại mặc định bật; nhưng cần xác nhận trên cluster cũ.)
GCP-native implementation guidance
Xác định dataplane đang dùng
gcloud container clusters describe <cluster> \
--region=<region> \
--format="value(networkConfig.datapathProvider)"
# IPTABLES (kube-proxy) hoặc ADVANCED_DATAPATH (Dataplane V2/eBPF)Kiểm tra chuỗi iptables của Service (cluster iptables mode)
# Trên node (qua nsenter, xem file 6): xem độ lớn bảng nat
sudo iptables -t nat -L KUBE-SERVICES -n | wc -l
sudo iptables-save -t nat | grep -c KUBE-SEPĐo EndpointSlice của một Service
kubectl get endpointslices -l kubernetes.io/service-name=<svc> \
-o custom-columns=NAME:.metadata.name,ENDPOINTS:.endpoints[*].addressesKết nối với Architecture Framework
- Performance optimization: lựa chọn dataplane quyết định độ phức tạp tra cứu Service ở scale — O(n) iptables vs O(1) eBPF (Architecture Framework).
- Reliability: latency điều khiển (thời gian dataplane phản ánh thực tế endpoint) ảnh hưởng trực tiếp tới tỉ lệ 5xx trong rollout (Reliability pillar).
References
- GKE Dataplane V2
- kube-proxy (Kubernetes)
- Virtual IPs and Service proxies
- EndpointSlices
- GKE network overview
- Google Cloud Architecture Framework
ipvs mode: bước trung gian, không phải lời giải triệt để
Kubernetes upstream cung cấp kube-proxy chế độ ipvs, dùng bảng băm trong kernel (IPVS) để chọn endpoint thay vì duyệt chuỗi iptables tuyến tính. So với iptables mode, ipvs cải thiện đáng kể độ phức tạp tra cứu endpoint (O(1) cho việc chọn backend) và hỗ trợ nhiều thuật toán cân bằng (round-robin, least-connection, v.v.).
Tuy vậy, ipvs không xóa bỏ các vấn đề nền tảng: nó vẫn dựa trên netfilter/conntrack, vẫn cần một số rule iptables phụ trợ (cho masquerade, filtering), và vẫn chịu lock contention ở các thao tác netfilter liên quan. Quan trọng hơn, trên GKE, hướng đi được Google đầu tư là Dataplane V2 (eBPF) chứ không phải ipvs. Vì vậy ipvs đáng biết về mặt khái niệm (nó chứng minh “chọn endpoint bằng hash nhanh hơn chuỗi”), nhưng với GKE production, lựa chọn thực tế là iptables (mô hình cũ) hoặc eBPF (Dataplane V2), không phải tinh chỉnh sang ipvs.
EndpointSlice: vì sao nó quan trọng với chi phí cập nhật
Trước EndpointSlice, mỗi Service có một object Endpoints duy nhất chứa toàn bộ danh sách endpoint. Vấn đề: chỉ cần một endpoint đổi (một Pod Ready/chết), toàn bộ object Endpoints phải được cập nhật và phát tán (watch) tới mọi kube-proxy trên mọi node. Với Service có hàng nghìn endpoint, mỗi thay đổi nhỏ tạo ra một lượng dữ liệu khổng lồ phải truyền và xử lý — đây là một nguồn tải lớn lên control plane và dataplane.
EndpointSlice chia danh sách endpoint thành nhiều slice nhỏ (mặc định tối đa 100 endpoint mỗi slice). Khi một endpoint đổi, chỉ slice chứa nó được cập nhật, giảm mạnh lượng dữ liệu phải phát tán (EndpointSlices). Đây là lý do EndpointSlice là mặc định ở các phiên bản Kubernetes/GKE hiện đại, và là điều kiện để cluster lớn vận hành được dù vẫn ở iptables mode. Khi debug latency điều khiển, xác nhận cluster đang dùng EndpointSlice (không phải Endpoints legacy) là một bước kiểm tra cần thiết.
Rule of thumb: khi nào iptables mode sắp “gãy”
Không có một con số tuyệt đối, nhưng vài chỉ dấu thực dụng cho thấy cluster iptables đang tiến gần điểm gãy:
- Tổng (Services × Endpoints trung bình) lên tới hàng chục nghìn và còn tăng → chuỗi rule dài, full sync tốn kém.
- Tần suất thay đổi endpoint cao (autoscaling mạnh, rollout liên tục) → resync thường xuyên, lock contention.
- Connection churn cao (nhiều flow ngắn) → chi phí khớp chuỗi cho gói đầu mỗi flow cộng dồn.
Khi cả ba yếu tố cùng cao, đó là tín hiệu mạnh để lên kế hoạch chuyển sang Dataplane V2 (qua dựng cluster mới). Quan trọng: đừng đợi đến lúc 5xx bùng phát mới hành động — latency điều khiển xấu đi dần dần và thường chỉ lộ rõ trong một rollout lớn không may.
Tương tác với conntrack ở tầng Service
Mỗi kết nối qua Service tạo một entry conntrack để un-DNAT gói reply đúng chiều. Điều này nghĩa là số kết nối đồng thời qua Service bị giới hạn gián tiếp bởi conntrack table của node (file 6). Với workload connection-churn cực cao đi qua nhiều Service, conntrack có thể trở thành điểm nghẽn thật sự — trước cả khi chuỗi iptables là vấn đề. Khi đó, lời giải không nằm ở dataplane mà ở tầng ứng dụng: connection pooling, keep-alive, giảm số kết nối ngắn. Đây là lý do nhiều “sự cố Service” thực ra là sự cố conntrack đội lốt.
Phụ lục: vì sao “iptables chậm” thường là chuyện cập nhật, không phải xử lý gói
Một hiểu lầm phổ biến là iptables làm “mỗi gói chậm”. Thực tế, với một flow đã thiết lập, conntrack giúp gói tiếp theo đi nhanh (không phải duyệt lại toàn chuỗi). Chi phí lớn nằm ở hai chỗ khác:
- Gói đầu của mỗi flow mới phải duyệt chuỗi để được DNAT — đây là nơi chuỗi dài gây latency, nặng nhất với workload tạo nhiều kết nối ngắn.
- Cập nhật bảng rule khi topology đổi — đây là nơi tốn CPU và gây lock contention, nặng nhất khi cluster biến động.
Vì vậy hai loại workload “ghét” iptables nhất là: (a) workload connection-churn cao (nhiều flow ngắn), và (b) cluster có endpoint biến động liên tục (rollout thường xuyên, autoscaling mạnh). Cả hai đều hưởng lợi rõ khi chuyển sang eBPF.
Liên hệ với conntrack
Dù iptables hay eBPF, conntrack vẫn là tài nguyên hữu hạn (file 6). DNAT có trạng thái nghĩa là mỗi flow tốn một entry conntrack. Cluster connection-churn cao có thể làm đầy conntrack table và gây drop ngẫu nhiên — vấn đề tồn tại độc lập với lựa chọn dataplane, dù eBPF quản lý hiệu quả hơn.
Tín hiệu cảnh báo cluster sắp chạm trần dataplane
- p99 kết nối mới tăng dần theo số Service dù app không đổi.
- 5xx spike đồng pha với rollout lớn.
- CPU system của node cao lúc cluster biến động.
- Độ dài
iptables-savetăng tới hàng chục nghìn dòng. - Cảnh báo endpoint map gần đầy (trên Dataplane V2).
Khi thấy các tín hiệu này, đó là lúc nghiêm túc đánh giá migrate dataplane hoặc tái thiết kế Service.
Ghi nhớ cốt lõi
- iptables mode = chuỗi tuyến tính, độ phức tạp O(S×E), gãy ở cập nhật và lock.
- eBPF mode = hash map O(1), cập nhật cục bộ, trần là dung lượng map.
- Vấn đề lớn nhất của iptables ở scale là latency điều khiển, biểu hiện thành 5xx trong rollout.
- Lựa chọn dataplane là bất biến — quyết đúng khi tạo cluster.