Migrate sang Cilium kube-proxy-less

K
Kai··6 min read

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ặncho 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.

Related Posts