Vấn đề thực tế: Khi iptables chạm ngưỡng giới hạn mở rộng
Mùa thu năm ngoái, cụm Kubernetes chính của chúng tôi đã mở rộng lên hơn 500 microservices chạy trên 40 bare-metal worker node. Mức sử dụng tổng thể về CPU và memory vẫn duy trì ở mức an toàn khoảng 45%. Thế nhưng, các service thanh toán (checkout & billing) liên tục bị gián đoạn bởi những đợt tăng vọt độ trễ bất thường. Trong những đợt tăng tải định kỳ đạt 15.000 req/sec, thời gian phản hồi p99 đã nhảy vọt từ mức chuẩn 8ms lên tới hơn 240ms.
Hệ thống giám sát cảnh báo ba vấn đề nghiêm trọng:
- CPU của các worker node ghi nhận thời gian chạy trong kernel-space (
system %) tăng cao, ngay cả khi throughput của ứng dụng chỉ ở mức vừa phải. - Các cập nhật định tuyến service mất từ 20 đến 45 giây để đồng bộ trên toàn bộ các node mỗi khi Horizontal Pod Autoscaler (HPA) scale thêm replica mới.
- Việc truy vết các packet bị drop đòi hỏi phải gắn
tcpdumptrực tiếp vào các pod tạm thời trong lúc xảy ra sự cố, làm tiêu tốn rất nhiều thời gian debug quý giá.
Hệ thống mạng gần như rơi vào tình trạng tắc nghẽn nghiêm trọng trước tần suất tạo/xóa pod (pod churn) liên tục.
Nguyên nhân gốc rễ: Kube-Proxy bộc lộ hạn chế khi mở rộng quy mô
Để khắc phục, chúng tôi đã đi sâu vào cơ chế hoạt động mạng mặc định của Kubernetes. Theo mặc định, kube-proxy cấu hình Linux iptables (hoặc IPVS) để điều hướng traffic của Service đến các pod endpoint.
1. Duyệt packet tuần tự O(N)
Với iptables, Linux kernel kiểm tra các quy tắc tường lửa theo thứ tự tuần tự. Nếu cluster có 5.000 backend endpoint, một packet gửi đến có thể phải trải qua hàng nghìn lần so khớp chuỗi trước khi tìm thấy đích đến chính xác. Việc duyệt tuyến tính này làm tiêu tốn chu kỳ tính toán trực tiếp trên đường truyền nhanh (network fast path) của kernel.
2. Khóa đồng bộ toàn bộ bảng (Full Table Synchronization Locks)
Bất cứ khi nào một pod được khởi tạo, tắt đi hoặc fail readiness probe, kube-proxy đều phải tạo lại và ghi đè toàn bộ danh sách quy tắc iptables. Trong các đợt autoscaling dồn dập, kernel phải dành phần lớn chu kỳ xử lý quan trọng cho việc khóa và sao chép bộ nhớ đệm thay vì định tuyến traffic của ứng dụng.
3. Quá tải Conntrack và drop packet âm thầm
Kube-proxy truyền thống phụ thuộc rất lớn vào cơ chế theo dõi kết nối của Linux (conntrack) để xử lý Service NAT. Khi số lượng phiên TCP đồng thời vượt quá 200.000, bảng conntrack sẽ chạm giới hạn phân bổ. Lúc này, kernel bắt đầu báo lỗi nf_conntrack: table full, dropping packet và tự động ngắt các kết nối client đang hoạt động.
So sánh các giải pháp: Calico vs. IPVS vs. Cilium eBPF
Trước khi áp dụng lên môi trường production, chúng tôi đã chạy benchmark thử nghiệm trên ba kiến trúc mạng với 10.000 Service endpoint và 20.000 RPS đồng thời:
| Chỉ số kiến trúc | Calico (iptables tiêu chuẩn) | kube-proxy (Chế độ IPVS) | Cilium CNI (eBPF Native) |
|---|---|---|---|
| Độ phức tạp tra cứu Service | Tìm kiếm tuần tự O(N) | Bảng băm IPVS ipset O(1) | Tra cứu trực tiếp bộ nhớ BPF Map O(1) |
| Điểm gắn Hook của Packet | Chuỗi hook Netfilter | Hook Netfilter + IPVS | Gắn trực tiếp tại Socket layer & TC / XDP |
| Độ trễ cập nhật Rule (5k endpoints) | ~12–30 giây | ~2–5 giây | < 50 mili-giây (hoán đổi map nguyên tử) |
| Thu thập Telemetry tầng Layer 7 | Yêu cầu inject sidecar Envoy | Không tích hợp sẵn | Hỗ trợ Native thông qua Hubble eBPF instrumentation |
| Overhead của Kernel ở quy mô lớn | Tốn nhiều CPU | Trung bình | Thấp nhất (giảm ~35% system CPU trên node) |
IPVS giải quyết bài toán tra cứu tuyến tính bằng hash set, nhưng vẫn bị ràng buộc bởi overhead của Netfilter conntrack. Cilium tiếp cận theo hướng hoàn toàn khác: sử dụng extended Berkeley Packet Filter (eBPF) để chạy code an toàn trong sandbox trực tiếp tại Linux socket và Traffic Control (tc) hook. Nhờ đó, các packet hoàn toàn bỏ qua stack Netfilter truyền thống.
Từng bước triển khai: Cài đặt Cilium thay thế hoàn toàn Kube-Proxy
Chúng tôi đã triển khai Cilium trên cả cụm staging và production với chế độ thay thế toàn diện kube-proxy. Dưới đây là quy trình cài đặt chi tiết.
Bước 1: Kiểm tra Kernel và Mount BPF Filesystem
Cilium yêu cầu các eBPF helper hiện đại. Hãy sử dụng kernel từ bản 5.4 trở lên (khuyến nghị dùng 5.15+ LTS hoặc 6.x). Đảm bảo BPF filesystem đã được mount trên toàn bộ các node trong cụm:
# Kiểm tra phiên bản kernel đang chạy
uname -r
# Mount bpffs nếu chưa tồn tại
mount | grep /sys/fs/bpf || sudo mount -t bpf bpffs /sys/fs/bpf
Bước 2: Xóa DaemonSet Kube-Proxy hiện có
Trên các cụm mới khởi tạo bằng kubeadm, thêm cờ --skip-phases=addon/kube-proxy trong quá trình init. Với cụm đang hoạt động, xóa DaemonSet và dọn dẹp các chain iptables cũ:
# Xóa kube-proxy DaemonSet và ConfigMap
kubectl -n kube-system delete daemonset kube-proxy
kubectl -n kube-system delete configmap kube-proxy
# Dọn sạch các rule iptables KUBE-* còn sót lại trên các worker node (qua SSH hoặc Ansible)
sudo iptables-save | grep -v KUBE | sudo iptables-restore
Bước 3: Cài đặt Cilium bằng Helm
Khai báo rõ ràng endpoint của API Server control plane để Cilium có thể thiết lập kết nối cụm trước khi các pod mạng được khởi tạo hoàn tất:
# Thêm và cập nhật Cilium Helm repository
helm repo add cilium https://helm.cilium.io/
helm repo update
# Triển khai Cilium với tính năng socket load balancing toàn diện và khả năng quan sát qua Hubble
helm install cilium cilium/cilium --version 1.15.4 \
--namespace kube-system \
--set kubeProxyReplacement=true \
--set k8sServiceHost="10.0.0.100" \
--set k8sServicePort="6443" \
--set bpf.masquerade=true \
--set ipam.mode="kubernetes" \
--set hubble.enabled=true \
--set hubble.relay.enabled=true \
--set hubble.ui.enabled=true \
--set autoDirectNodeRoutes=true
Thay thế 10.0.0.100 bằng IP Virtual (VIP) của load balancer API Server hoặc IP của control-plane chính.
Bước 4: Xác minh Host Routing và Service Map của eBPF
Kiểm tra trạng thái của Cilium daemon để đảm bảo cơ chế cân bằng tải cấp độ socket đã tiếp quản toàn bộ nhiệm vụ định tuyến:
# Kiểm tra trạng thái sức khỏe tổng thể của cụm Cilium
cilium status --wait
# Xác nhận việc thay thế kube-proxy đang hoạt động hoàn toàn ở chế độ strict
kubectl -n kube-system exec -ti ds/cilium -c cilium-agent -- cilium-dbg status | grep -A 10 "KubeProxyReplacement"
Kết quả đầu ra sẽ xác nhận KubeProxyReplacement: True (Strict). Giờ đây, Cilium trực tiếp quản lý NodePort, ClusterIP, HostPort và external traffic thông qua các BPF map.
Bước 5: Giám sát Traffic và Packet Drop theo thời gian thực với Hubble
Hubble đọc trực tiếp các sự kiện eBPF từ ring buffer của kernel, cho phép bạn theo dõi các flow mà không cần sidecar hay tốn overhead từ tcpdump:
# Stream các packet bị drop theo thời gian thực trên toàn cụm
cilium hubble observe --type drop
# Truy vết traffic đi vào checkout namespace
cilium hubble observe --namespace checkout --follow
Kết quả sau 6 tháng vận hành Production
Việc chuyển đổi khỏi kube-proxy đã nâng tầm độ ổn định mạng của cụm cluster dưới mức tải cao:
- Độ trễ ổn định và dễ dự đoán: Độ trễ p99 của microservice thanh toán giảm mạnh từ 240ms xuống mức ổn định 6ms ngay cả trong những đợt lưu lượng truy cập đạt đỉnh.
- Tiết kiệm tài nguyên tính toán: Các worker node thu hồi được khoảng 14% dung lượng CPU – phần tài nguyên trước đây bị lãng phí cho việc quét chuỗi iptables và duy trì bảng conntrack.
- Autoscaling tức thì: Việc scale-out hơn 100 replica pod cập nhật tuyến đường ngay lập tức thông qua việc chèn BPF map nguyên tử mà không gây ra bất kỳ hiện tượng giật/trễ mạng nào.
Nếu hạ tầng Kubernetes của bạn đang vận hành hàng trăm service hoặc xử lý hàng chục nghìn kết nối đồng thời, việc chuyển sang Cilium eBPF sẽ mang lại những cải thiện vượt trội tức thì về cả hiệu năng lẫn hiệu quả vận hành.

