Fix Kubernetes LoadBalancer Service Bị Stuck ở <pending> — External IP Không Bao Giờ Được Gán

intermediate☸️ Kubernetes2026-05-13| Kubernetes 1.20+, mọi cloud provider (GKE, EKS, AKS) hoặc bare-metal cluster (kubeadm, k3s, kind, minikube)

Error Message

Service is in Pending state and External IP is <pending>
#kubernetes#service#loadbalancer#cloud-provider

Chuyện gì đã xảy ra

Triển khai một service với type: LoadBalancer, chạy kubectl get svc, và cột EXTERNAL-IP chỉ hiển thị <pending>. Đợi năm phút. Vẫn pending. Mười phút. Vẫn pending.

NAME         TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
my-app-svc   LoadBalancer   10.96.123.45    <pending>      80:31234/TCP   12m

Chín trong mười trường hợp, nguyên nhân đều rơi vào một trong ba tình huống: không có cloud-provider controller đang chạy, cấu hình cloud sai, hoặc bạn đang dùng cluster bare-metal/local nơi việc cấp phát LoadBalancer đơn giản là không tồn tại theo mặc định.

Quy trình debug

Bước 1 — Kiểm tra events của service trước

kubectl describe svc my-app-svc

Kéo xuống phần Events ở cuối. Không có gì — hoặc chỉ là <none>? Điều đó có nghĩa là cloud-provider controller thậm chí không cố gắng cấp phát IP. Gần như chắc chắn là controller đó không chạy gì cả.

Bước 2 — Kiểm tra xem có cloud-provider controller nào tồn tại không

# Với cluster cloud, kiểm tra cloud-controller-manager
kubectl get pods -n kube-system | grep cloud-controller

# Với bare-metal dùng MetalLB
kubectl get pods -n metallb-system

# Với k3s (dùng ServiceLB/Klipper theo mặc định)
kubectl get pods -n kube-system | grep svclb

Nếu không có gì trong số này tồn tại, bạn đã có câu trả lời. Không có ai chịu trách nhiệm phân bổ IP bên ngoài.

Bước 3 — Kiểm tra annotation của node trên cluster cloud

Cloud controller manager đọc annotation của node để xác định region, zone và VPC cần cấp phát.

kubectl describe nodes | grep -A5 'Labels\|Annotations' | grep -i 'region\|zone\|provider'

Tìm kiếm topology.kubernetes.io/region (hoặc label cũ hơn failure-domain.beta.kubernetes.io/region trên các cluster trước phiên bản 1.17). Thiếu provider label thường có nghĩa là các node chưa được khởi tạo với đúng cờ cloud metadata.

Bước 4 — Với cluster on-prem/bare-metal: xác nhận không có IP pool nào được cấu hình

# Nếu MetalLB đã được cài, kiểm tra xem có IPAddressPool không
kubectl get ipaddresspool -n metallb-system

# MetalLB phiên bản cũ hơn dùng ConfigMap
kubectl get configmap config -n metallb-system -o yaml

Giải pháp

Trường hợp A — Cluster bare-metal hoặc on-prem: cài MetalLB

Kubernetes trên bare-metal không đi kèm với bất kỳ triển khai LoadBalancer nào. MetalLB lấp đầy khoảng trống đó.

# Cài MetalLB
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.5/config/manifests/metallb-native.yaml

# Đợi cho đến khi sẵn sàng
kubectl wait --namespace metallb-system \
  --for=condition=ready pod \
  --selector=app=metallb \
  --timeout=90s

Bây giờ định nghĩa một IP pool từ dải mạng LAN của bạn. Chọn các địa chỉ mà router chưa phân phát qua DHCP:

apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: local-pool
  namespace: metallb-system
spec:
  addresses:
  - 192.168.1.200-192.168.1.250
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: local-advert
  namespace: metallb-system
kubectl apply -f metallb-pool.yaml

Trường hợp B — Cluster dev local (minikube, kind, Docker Desktop)

Cluster local không cấp phát IP thật. Trên minikube, lệnh tunnel xử lý việc này:

# Trong một terminal riêng — giữ nó chạy
minikube tunnel

Chạy lệnh đó, sau đó kiểm tra lại kubectl get svc. IP bên ngoài sẽ chuyển thành 127.0.0.1 trong vài giây.

Đang dùng kind? Cài MetalLB theo cách tương tự như bare-metal ở trên, hoặc chỉ cần chuyển service sang NodePort để test local — ít cài đặt hơn cho những môi trường dùng tạm.

Trường hợp C — Cluster cloud (EKS, GKE, AKS): cloud-controller-manager không chạy

# Kiểm tra log của controller manager
kubectl logs -n kube-system -l component=cloud-controller-manager --tail=50

# Hoặc với cluster được quản lý, kiểm tra controller dành riêng cho provider
# EKS: aws-load-balancer-controller
kubectl get pods -n kube-system | grep aws-load-balancer
kubectl logs -n kube-system -l app.kubernetes.io/name=aws-load-balancer-controller --tail=50

EKS đã bỏ cloud provider tích hợp sẵn từ nhiều năm trước. Bạn cần cài AWS Load Balancer Controller riêng biệt:

# Cài qua Helm
helm repo add eks https://aws.github.io/eks-charts
helm repo update
helm install aws-load-balancer-controller eks/aws-load-balancer-controller \
  -n kube-system \
  --set clusterName=my-cluster \
  --set serviceAccount.create=false \
  --set serviceAccount.name=aws-load-balancer-controller

GKE quản lý controller cho bạn — nếu nó hoạt động không bình thường, hãy kiểm tra tình trạng node pool và xác nhận service account của GKE có quyền compute.networks.use trên VPC của bạn.

Trường hợp D — Cấu hình sai subnet hoặc annotation trên cloud provider

Một LoadBalancer cần biết subnet nào cần gắn vào. Bỏ qua annotation và nó sẽ chờ mãi mà không thông báo gì.

# EKS: gắn annotation cho service với subnet ID
kubectl annotate svc my-app-svc \
  service.beta.kubernetes.io/aws-load-balancer-subnets=subnet-abc123,subnet-def456

# GKE: annotation LB nội bộ nếu dùng mạng nội bộ
kubectl annotate svc my-app-svc \
  cloud.google.com/load-balancer-type=Internal

Trường hợp E — Firewall hoặc Security Group chặn health check

Load balancer cloud gửi traffic health check ngược về các node của bạn. Nếu security group chặn traffic đó, LB sẽ bị treo lặng lẽ trong quá trình cấp phát.

# EKS: đảm bảo security group của node cho phép traffic inbound từ SG của load balancer
# Kiểm tra các port mà health check sử dụng
kubectl describe svc my-app-svc | grep -i 'node.*port\|health'

Xác minh

# Theo dõi cho đến khi EXTERNAL-IP được điền
kubectl get svc my-app-svc -w

# Khi IP xuất hiện, kiểm tra kết nối
curl http://<EXTERNAL-IP>:80

# Xác nhận các endpoint hoạt động bình thường
kubectl get endpoints my-app-svc

Cờ -w stream các thay đổi theo thời gian thực. Sau khi áp dụng bản sửa lỗi, <pending> sẽ chuyển thành IP thật trong vòng 30–60 giây.

Bài học rút ra

  • Câu hỏi đầu tiên mỗi lần: cluster này có gì chịu trách nhiệm cấp phát LoadBalancer không? Với cluster bare-metal và local, câu trả lời là không — trừ khi bạn cài MetalLB hoặc controller tương tự một cách rõ ràng.
  • Cluster cloud bị lỗi sau khi nâng cấp thường có sự không khớp phiên bản: cloud-controller-manager không được nâng cấp đồng bộ với control plane. Hậu quả là các lỗi thầm lặng.
  • Chỉ cần gỡ chặn nhanh? Chuyển type: LoadBalancer thành type: NodePort và truy cập trực tiếp qua <node-IP>:<node-port>. Không tao nhã, nhưng chạy được trong chưa đầy một phút.
  • MetalLB ở chế độ L2 không cần cài đặt BGP — một cấu hình dải IP đơn giản là đủ cho hầu hết home lab và môi trường on-prem. Chế độ BGP phù hợp production hơn nhưng yêu cầu router hỗ trợ.
  • Events của kubectl describe svc thường bị đánh giá thấp. Chúng thường cho bạn biết chính xác controller đã thử làm gì và tại sao thất bại, tiết kiệm 20 phút debug mò mẫm.

Related Error Notes