Migrate sang Cilium kube-proxy-less
Bài 45 giải thích vì sao. Bài này làm thật: chuyển cụm đang chạy từ kube-proxy iptables + bridge (Part I) sang Cilium 1.19 dựa eBPF, gỡ hẳn kube-proxy, bật Hubble. Đây là migration trên cụm sống — CoreDNS, metrics-server, EBS CSI đều đang chạy — nên rủi ro thật, và ta sẽ vấp đúng những cái bẫy mà một cụm tự dựng (không cloud-controller-manager, không IMDS-cho-pod) gặp phải. Tôi giữ nguyên cả phần gỡ rối vì đó mới là giá trị thật.
Bước 1 — cài Cilium (kube-proxy vẫn chạy song song)
Cài trước, gỡ kube-proxy sau — để có đường lui. Dùng Helm, ghim 1.19.4. Cấu hình bám đúng kiến trúc Part I: native routing (như Bài 13), pod CIDR 10.200.0.0/16 (như Bài 14), và k8sServiceHost trỏ LB nội bộ (vì không còn kube-proxy, agent Cilium phải biết API server ở đâu):
helm install cilium cilium/cilium --version 1.19.4 --namespace kube-system \
--set kubeProxyReplacement=true \
--set k8sServiceHost=10.0.1.10 --set k8sServicePort=6443 \
--set ipam.mode=cluster-pool \
--set ipam.operator.clusterPoolIPv4PodCIDRList='{10.200.0.0/16}' \
--set ipam.operator.clusterPoolIPv4MaskSize=24 \
--set routingMode=native --set ipv4NativeRoutingCIDR=10.200.0.0/16 \
--set autoDirectNodeRoutes=true --set enableIPv4Masquerade=true \
--set hubble.relay.enabled=true --set hubble.ui.enabled=true
autoDirectNodeRoutes=true cho Cilium tự cài route node→pod-CIDR-của-node-kia (thay vì dựa route VPC tay của Bài 14) — native routing thuần Cilium. Chờ agent (DaemonSet) + operator lên rồi xem trạng thái:
kubectl exec ds/cilium -n kube-system -c cilium-agent -- cilium-dbg status | grep -E "KubeProxyReplacement|Routing"
KubeProxyReplacement: True [ens5 10.0.1.20 (Direct Routing)]
Routing: Network: Native Host: Legacy
KubeProxyReplacement: True — eBPF đã sẵn sàng làm việc của kube-proxy. Nhưng kube-proxy vẫn đang chạy (systemd, Bài 12) — giờ mới gỡ.
Bước 2 — gỡ kube-proxy
Cụm này cài kube-proxy bằng systemd trên worker (không phải DaemonSet như kubeadm), nên gỡ = tắt service + xóa rule iptables nó để lại:
for w in worker-0 worker-1; do
ssh $w 'sudo systemctl stop kube-proxy; sudo systemctl disable kube-proxy
sudo iptables-save | grep -v KUBE | sudo iptables-restore' # xóa rule KUBE-*
done
worker-0: rule KUBE còn lại: 0 | kube-proxy active: inactive
worker-1: rule KUBE còn lại: 0 | kube-proxy active: inactive
74 rule iptables của Bài 45 biến mất. Từ giờ Service ClusterIP chỉ do eBPF xử lý. Cilium cũng tự dọn CNI config cũ — nó viết 05-cilium.conflist (ưu tiên cao) và đổi tên 10-bridge.conf (Bài 14) thành 10-bridge.conf.cilium_bak:
ssh worker-0 'sudo ls /etc/cni/net.d/'
# 05-cilium.conflist 10-bridge.conf.cilium_bak 99-loopback.conf.cilium_bak
Bước 3 — restart pod để nhận datapath Cilium
Pod đang chạy vẫn mang IP từ bridge cũ. Restart chúng để Cilium cấp IP và datapath mới:
for d in coredns metrics-server ebs-csi-controller snapshot-controller; do
kubectl rollout restart deployment/$d -n kube-system
done
kubectl rollout restart daemonset/ebs-csi-node -n kube-system
CoreDNS lên lại với IP mới (10.200.0.148, 10.200.1.204 — do Cilium IPAM cấp). Kiểm chứng điều cốt lõi — Service + DNS chạy mà không kube-proxy:
kubectl exec cli -- nslookup kubernetes.default.svc.cluster.local # qua DNS 10.32.0.10
kubectl exec cli -- wget -qO- https://10.32.0.1:443/healthz # qua ClusterIP
Address: 10.32.0.1 # DNS phân giải OK (CoreDNS, qua Cilium eBPF)
TLS error alert 47 # kết nối ĐẾN apiserver OK (lỗi chỉ vì thiếu client cert)
Cả ClusterIP (10.32.0.1) lẫn DNS (10.32.0.10) đều thông — eBPF DNAT thay kube-proxy thành công.
Bốn cái bẫy của cụm tự dựng (và cách gỡ)
Sau migration, ebs-csi (Bài 43) crash hàng loạt. Đây là phần thật nhất của bài, bốn vấn đề chồng nhau mà cụm KTHW + Cilium hay gặp:
(1) Node thiếu providerID. EBS CSI cần biết pod chạy trên EC2 instance nào. Cụm ta không chạy cloud-controller-manager nên node.spec.providerID rỗng. Set tay:
kubectl patch node worker-0 -p '{"spec":{"providerID":"aws:///ap-southeast-1a/i-0f1ab..."}}'
(2) Node thiếu topology label. Driver còn cần node.kubernetes.io/instance-type + topology.kubernetes.io/zone (cũng do cloud-controller-manager đặt, mà ta không có). Thêm tay:
kubectl label node worker-0 node.kubernetes.io/instance-type=t3.medium \
topology.kubernetes.io/region=ap-southeast-1 topology.kubernetes.io/zone=ap-southeast-1a
Sau (1)+(2), ebs-csi-node (chỉ cần metadata) lên 3/3. Nhưng ebs-csi-controller vẫn chết — vì nó cần credentials, không chỉ metadata.
(3) IMDS hop limit chặn pod. Controller lấy IAM credentials từ IMDS (169.254.169.254). Lỗi no EC2 IMDS role found ... context deadline exceeded — pod không tới được IMDS. Thủ phạm: EC2 IMDSv2 mặc định HttpPutResponseHopLimit=1, mà pod cách IMDS một hop (qua masquerade của Cilium) → bị chặn. Nâng hop limit:
aws ec2 modify-instance-metadata-options --instance-id i-0f1ab... --http-put-response-hop-limit 2
(4) Controller cần hostNetwork để chắc chắn tới IMDS. Ngay cả với hop limit 2, đường pod→IMDS qua eBPF/masquerade vẫn chập chờn. Giải pháp chắc nhất: cho controller dùng mạng host (tới IMDS trực tiếp, không qua Cilium):
kubectl patch deployment ebs-csi-controller -n kube-system --type=strategic \
-p '{"spec":{"template":{"spec":{"hostNetwork":true,"dnsPolicy":"ClusterFirstWithHostNet"}}}}'
Sau đó controller 2/2, driver khởi động, dry-run AWS API thành công. (Trên cụm managed/EKS, IRSA hoặc Pod Identity giải việc credentials gọn hơn — nhưng ở cụm tay, đây là cách trực tiếp.) Hubble-relay/ui cũng từng crash nhưng chỉ transient — chúng start trước khi DNS sẵn sàng trong lúc migration, tự hồi khi CoreDNS ổn.
Xác nhận cuối: kube-proxy-less thật
kubectl get pods -n kube-system # 17 pod Running
kubectl exec ds/cilium -n kube-system -c cilium-agent -- cilium-dbg status | grep -E "KubeProxy|Hubble|health"
ssh worker-0 'sudo iptables-save | grep -cE "KUBE-SERVICES|KUBE-SVC-"'
KubeProxyReplacement: True
Hubble: Ok Current/Max Flows: 4095/4095, Flows/s: 17
Cluster health: 2/2 reachable
0 # <- KHÔNG còn rule kube-proxy (KUBE-SERVICES/KUBE-SVC)
0 rule kube-proxy — chỉ còn vài rule KUBE-FIREWALL/KUBE-KUBELET-CANARY của kubelet (vô hại, không liên quan routing Service). Service hoàn toàn do eBPF lo. Và Hubble Ok — giờ thấy được mọi flow (Bài này không sa đà; Hubble UI/observability để khai thác ở các bài policy).
🧹 Dọn dẹp
kubectl delete deployment net-test ; kubectl delete svc net-test # workload kiểm thử
Chỉ xóa workload kiểm thử. Giữ toàn bộ: Cilium + Hubble (là CNI mới của cụm), và các sửa đổi vĩnh viễn (kube-proxy đã disable, providerID + label trên node, IMDS hop limit, ebs-csi-controller hostNetwork). Cụm giờ chạy kube-proxy-less với 17 pod kube-system Running. Manifest/giá trị Helm ở github.com/nghiadaulau/kubernetes-from-scratch, thư mục 46-cilium-migrate.
Tổng kết
Migration thành công: cụm chuyển từ kube-proxy iptables + bridge sang Cilium 1.19.4 eBPF kube-proxy-less + Hubble. Quy trình: cài Cilium (helm, kubeProxyReplacement=true, native routing, autoDirectNodeRoutes) song song → tắt+disable kube-proxy systemd + flush iptables (74 rule → 0 KUBE-SERVICES/SVC) → Cilium tự thay CNI config (.cilium_bak) → restart pod nhận IP Cilium. Kết quả kiểm chứng: KubeProxyReplacement: True, Service ClusterIP + DNS chạy qua eBPF, Cluster health 2/2, Hubble Ok. Bốn cái bẫy của cụm tự dựng đều có lời giải thật: providerID + topology label (thay cloud-controller-manager), IMDS hop limit 1→2 (cho pod qua Cilium tới IMDS), và hostNetwork cho ebs-csi-controller (lấy IAM credentials). Đây là chân dung trung thực của một migration sản xuất — phần khó không phải lệnh cài, mà là các phụ thuộc ngầm lộ ra khi đổi datapath.
Bài 47 khai thác sức mạnh mới: NetworkPolicy. Trước đây không có CNI hỗ trợ policy nên mọi pod nói chuyện tự do; giờ có Cilium, ta sẽ chặn và cho phép lưu lượng theo nhãn (và xem Hubble hiển thị verdict drop/allow) — bảo mật mạng theo identity như Bài 45 hứa.