Giải quyết lỗi ‘Pending’ LoadBalancer trên Bare-metal Kubernetes

DevOps tutorial - IT technology blog
DevOps tutorial - IT technology blog

Rào cản Bare-metal

Chuyển từ một dịch vụ được quản lý (managed service) như AWS EKS sang môi trường bare-metal thường mang lại cảm giác rất tuyệt—cho đến khi bạn cố gắng expose một service. Bạn tạo một Service với type: LoadBalancer, chạy lệnh kubectl get svc và chờ đợi. Năm phút trôi qua, rồi mười phút. Cột EXTERNAL-IP vẫn bị kẹt ở trạng thái <pending> vô thời hạn.

Tiếp theo là vấn đề đau đầu về Control Plane. Trong một thiết lập production, một master node duy nhất giống như một quả bom hẹn giờ. Nhưng nếu bạn triển khai ba master node, các worker node và client kubectl của bạn cần một điểm truy cập (entry point) duy nhất và ổn định. Trên cloud, bạn chỉ cần khởi tạo một NLB. Còn với on-prem, bạn thường băn khoăn không biết làm thế nào để lấp đầy khoảng trống đó mà không phải bỏ ra 10.000 USD để mua bộ cân bằng tải phần cứng.

Tôi đã từng vật lộn với chính thiết lập này khi xây dựng các private cloud cho các môi trường lưu lượng truy cập cao. Phương pháp này đã chứng minh được sự ổn định trong các cluster từ quy mô home lab ba node cho đến các tủ rack production 20 node. Nó cho phép bạn xử lý phần cứng vật lý của mình với sự linh hoạt tương tự như một nhà cung cấp cloud hàng đầu.

Tại sao IP của bạn bị kẹt ở trạng thái “Pending”

Kubernetes sử dụng thiết kế dạng mô-đun. Bản thân nó không thực sự biết cách “tạo ra” một bộ cân bằng tải. Thay vào đó, nó mong đợi một Cloud Controller Manager (CCM) giao tiếp với một API bên ngoài—như AWS hoặc OpenStack—để cấp phát tài nguyên mạng và báo cáo lại IP.

Khi bạn chạy một cluster nguyên bản (vanilla) trên các máy chủ vật lý, CCM đó bị thiếu. Cluster không có cách nào để thông báo với mạng nội bộ của bạn rằng: “Vui lòng định tuyến lưu lượng truy cập for 192.168.1.50 đến các node cụ thể này.” Nếu không có cơ chế xử lý các thông báo Layer 2 (ARP) hoặc Layer 3 (BGP), các service của bạn sẽ không bao giờ nhận được IP external. Control Plane của bạn cũng sẽ thiếu một địa chỉ hợp nhất, có độ khả dụng cao (high availability).

So sánh các lựa chọn

Tôi đã thử nghiệm nhiều giải giải pháp trước khi quyết định chọn Kube-vip. Dưới đây là cái nhìn tổng quan:

  • Keepalived + HAProxy: Đây là cách tiếp cận Linux truyền thống. Mặc dù đáng tin cậy, nhưng nó yêu cầu quản lý các file cấu hình bên ngoài Kubernetes. Điều này phá vỡ quy trình “hạ tầng dưới dạng mã” (infrastructure as code) và tăng thêm gánh nặng thủ công khi mở rộng node.
  • MetalLB: Đây là một công cụ mạnh mẽ để cung cấp IP cho các Service. Tuy nhiên, MetalLB không được thiết kế để cung cấp Virtual IP (VIP) cho Control Plane trước khi cluster tồn tại. Bạn thường phải cần một công cụ khác chỉ để khởi tạo các master node.
  • Kube-vip: Đây là kẻ chiến thắng “tất cả trong một”. Nó cung cấp VIP cho Control Plane thông qua các static pod và quản lý Service LoadBalancer thông qua một DaemonSet. Nó nhẹ, nhanh và nằm hoàn toàn trong hệ sinh thái Kubernetes.

Giải pháp: Sử dụng Kube-vip cho mọi thứ

Kube-vip đóng vai trò như một công cụ đa năng cho mạng. Nó xử lý tính khả dụng cao của API Server và logic của Service LoadBalancer trong một file thực thi duy nhất. Hãy cùng thiết lập nó trên một cluster mới.

1. Khởi tạo VIP cho Control Plane

Phần khó nhất của một cluster HA là giai đoạn khởi tạo ban đầu. Bạn cần VIP tồn tại trước khi chạy lệnh kubeadm init. Kube-vip giải quyết vấn đề này bằng cách chạy dưới dạng một Static Pod. Kubelet sẽ tự động khởi động nó trước khi các thành phần còn lại của cluster trực tuyến.

Chọn một IP chưa sử dụng trong subnet của bạn. Trong ví dụ này, tôi sẽ dùng 192.168.1.50. Trước tiên, hãy thiết lập các biến môi trường:

export VIP=192.168.1.50
export INTERFACE=eth0
KVVERSION=$(curl -sL https://api.github.com/repos/kube-vip/kube-vip/releases/latest | jq -r " .tag_name")

Tiếp theo, tạo manifest cho Static Pod. Chúng ta sử dụng chính image của Kube-vip để xây dựng cấu hình:

docker run --network host --rm ghcr.io/kube-vip/kube-vip:$KVVERSION \
    manifest pod \
    --interface $INTERFACE \
    --address $VIP \
    --controlplane \
    --services \
    --arp \
    --leaderElection | tee /etc/kubernetes/manifests/kube-vip.yaml

Khi bạn thực hiện lệnh kubeadm init --control-plane-endpoint "192.168.1.50:6443", Kubelet sẽ phát hiện file trong /etc/kubernetes/manifests/. Nó khởi động container và ngay lập tức bắt đầu thông báo VIP thông qua ARP. Giờ đây, bạn đã có một điểm truy cập ổn định cho cluster của mình.

2. Kích hoạt Service LoadBalancer

Khi cluster đã chạy, chúng ta cần khắc phục trạng thái <pending> cho các ứng dụng. Trong khi Static Pod xử lý các master node, một DaemonSet sẽ xử lý các service. Điều này đảm bảo Kube-vip chạy trên tất cả các node và theo dõi các yêu cầu LoadBalancer mới.

Bắt đầu bằng cách cài đặt các thiết lập RBAC. Điều này cấp cho Kube-vip các quyền cần thiết để theo dõi các service của bạn:

kubectl apply -f https://kube-vip.io/manifests/rbac.yaml

Bây giờ, hãy triển khai Kube-vip Cloud Provider. Bộ điều khiển nhỏ gọn này sẽ quản lý việc gán IP thực tế:

kubectl apply -f https://raw.githubusercontent.com/kube-vip/kube-vip-cloud-provider/main/manifest/kube-vip-cloud-controller.yaml

3. Định nghĩa dải IP (IP Pool)

Kube-vip cần biết những IP nào an toàn để sử dụng. Bạn định nghĩa dải này bằng một ConfigMap. Tạo một file tên là global-config.yaml với dải IP phù hợp với mạng của bạn:

apiVersion: v1
kind: ConfigMap
metadata:
  name: kubevip
  namespace: kube-system
data:
  # Dải IP cấp phát cho LoadBalancer
  range-global: 192.168.1.100-192.168.1.150

Áp dụng cấu hình: kubectl apply -f global-config.yaml. Giờ đây, bất cứ khi nào bạn triển khai một service, Kube-vip sẽ tự động lấy một IP từ dải 51 địa chỉ này.

Kiểm tra thiết lập

Để xác minh mọi thứ hoạt động bình thường, hãy triển khai một web server Nginx tiêu chuẩn. Chỉ mất vài giây để xác nhận:

kubectl create deployment nginx-test --image=nginx
kubectl expose deployment nginx-test --port=80 --type=LoadBalancer

Kiểm tra trạng thái: kubectl get svc nginx-test. Bạn sẽ thấy kết quả tương tự như thế này:

NAME         TYPE           CLUSTER-IP   EXTERNAL-IP     PORT(S)        AGE
nginx-test   LoadBalancer   10.96.0.45   192.168.1.100   80:31234/TCP   5s

Nếu bạn có thể curl 192.168.1.100 từ một máy khác trong mạng, thiết lập đã thành công. Bạn đã mô phỏng thành công môi trường cloud trên chính phần cứng của mình.

Những bài học xương máu

Chạy Kube-vip trong môi trường production đã dạy cho tôi một vài bài học quan trọng về mạng nội bộ:

  • Sự nhất quán của Interface: Đảm bảo các interface mạng của bạn (như eth0 hoặc enp0s3) được đặt tên giống hệt nhau trên tất cả các node. Nếu một node sử dụng tên khác, pod Kube-vip sẽ bị crash trên host đó.
  • Cấp phát IP tĩnh (DHCP Reservation): Luôn loại trừ dải VIP của bạn (192.168.1.100-150) khỏi pool của DHCP server. Không có gì làm cluster hỏng nhanh hơn việc một chiếc máy in văn phòng ngẫu nhiên chiếm mất IP trùng với database production của bạn.
  • Tốc độ Failover: Ở chế độ Layer 2, quá trình failover thường diễn ra dưới 10 giây. Nếu bạn cần failover dưới 1 giây cho quy mô lớn, hãy cân nhắc chuyển sang chế độ BGP, mặc dù nó yêu cầu switch hỗ trợ BGP.

Độ khả dụng cao trên bare-metal không đòi hỏi một núi cấu hình phức tạp. Bằng cách sử dụng Kube-vip, bạn giữ logic mạng của mình bên trong cluster. Điều này giúp hạ tầng của bạn dễ bảo trì, mở rộng và xử lý sự cố hơn. Cuối cùng, nó mang lại trải nghiệm “cloud-native” thực thụ cho các máy chủ vật lý.

Share: