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
LoadBalancerkhô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: LoadBalancerthànhtype: NodePortvà 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 svcthườ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.

