Kubernetes LoadBalancer 使用指南

什麼是 LoadBalancer Service?

Kubernetes LoadBalancer Service 是一種 Service 類型,用於將叢集內的應用程式暴露給外部網路。它會提供一個可從叢集外部存取的入口 IP,並透過 Service selector 將流量導向後端 Pod。

在雲端 Kubernetes 環境中,type: LoadBalancer 通常會自動建立雲端供應商的負載平衡器;在本機或裸機環境中,Kubernetes 本身不會自動分配外部 IP,因此常見做法是搭配 MetalLB,讓本地叢集也能取得 LoadBalancer IP。

LoadBalancer Service 的主要功能:

  • 外部存取:讓叢集外部可以透過固定 IP 與連接埠存取應用程式
  • 服務入口:透過 Service 提供穩定入口,避免直接依賴會變動的 Pod IP
  • 負載分配:將進入 Service 的流量分配到符合 selector 的後端 Pod
  • 流量策略:支援來源 IP 保留、會話親和性與內外部流量策略等設定

為什麼使用 LoadBalancer Service?

相較於 ClusterIP 或 NodePort,LoadBalancer Service 更適合需要直接對外提供 IP 入口的服務:

  1. 外部連線更直覺:使用者可以直接透過 LoadBalancer IP 與 Service port 存取服務
  2. 部署設定更清楚:Service 負責穩定入口,Deployment 負責維護後端 Pod
  3. 適合非 HTTP 場景:除了 HTTP 服務,也能暴露 TCP 或 UDP 類型的服務

實作範例

這篇沿用前面 Service 範例已經建立好的 Spring Boot 應用程式,重點放在 LoadBalancer Service 如何提供外部 IP 入口。開始前先確認叢集裡已有下列條件:

  • dockertest namespace 已存在
  • dockertest-deployment 已建立,Pod 具有 app: dockertest label
  • 應用程式容器實際監聽 8080 port
  • Pod 已經 Ready,可以回應 /hello

Service 會透過下列 selector 找到這組 Pod:

1
2
selector:
app: dockertest

只要 Deployment 的 template.metadata.labels 也包含 app: dockertest,LoadBalancer Service 就能將外部流量導到這些 Pod。這裡不再重複 Deployment、Registry Secret 與 Namespace 的完整 YAML,只保留必要檢查指令:

1
2
kubectl rollout status deployment/dockertest-deployment -n dockertest
kubectl get pods -n dockertest -l app=dockertest --show-labels

私有映像提醒

如果映像放在 GitLab Container Registry 等私有 registry,請在部署工作負載時先建立 imagePullSecrets。文章或 Git repository 中不建議放入實際的 .dockerconfigjson base64 內容。

LoadBalancer Service 做了什麼?

LoadBalancer Service 可以理解成「叢集外部入口」加上「Service 負載分配」的組合。

當我們建立 type: LoadBalancer 的 Service 後,Kubernetes 會建立一個 Service 物件,並等待底層環境提供外部 IP。外部請求進入這個 IP 與 Service port 後,Kubernetes 會依照 selector 找到符合條件的 Pod,再把流量轉送到 targetPort

以下方設定為例:

1
2
3
4
5
ports:
- name: http
port: 8080
targetPort: 8080
protocol: TCP

它代表:

  • 外部使用者連到 LoadBalancer IP 的 8080 port
  • Service 將流量轉送到後端 Pod 的 8080 port
  • 後端 Pod 由 selector.app: dockertest 決定

因此整體流量會像這樣:

1
Client -> LoadBalancer IP:8080 -> Service -> Pod:8080

Case 1:建立基本 LoadBalancer Service

第一個案例只建立 dockertest-loadbalancer,讓外部使用者可以直接透過 LoadBalancer IP 與 8080 port 存取 Spring Boot 服務。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: v1
kind: Service
metadata:
name: dockertest-loadbalancer
namespace: dockertest
spec:
type: LoadBalancer
externalTrafficPolicy: Cluster
sessionAffinity: None
selector:
app: dockertest
ports:
- name: http
port: 8080
targetPort: 8080
protocol: TCP

套用後查看 Service 狀態:

1
2
kubectl apply -f dockertest-loadbalancer-service.yaml
kubectl get service dockertest-loadbalancer -n dockertest

dockertest-loadbalancer 會向底層環境申請外部 IP。取得 IP 後,可以直接從叢集外部呼叫:

1
curl http://<EXTERNAL-IP>:8080/hello

這種方式適合需要直接暴露 TCP/HTTP 服務的場景,例如本機測試、內網系統、或不需要透過網域路由的服務。測試時重點是確認 EXTERNAL-IP 已取得位址,並且能透過 http://<EXTERNAL-IP>:8080/hello 取得 Hello, World! 回應。

Case 2:外部 port 與容器 port 不同

第二個案例只建立一個 LoadBalancer Service,並把外部 7777 port 轉送到容器的 8080 port。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: v1
kind: Service
metadata:
name: dockertest-loadbalancer
namespace: dockertest
spec:
type: LoadBalancer
externalTrafficPolicy: Cluster
sessionAffinity: None
selector:
app: dockertest
ports:
- name: tcp-game
port: 7777
targetPort: 8080
protocol: TCP

這段設定的重點在於 porttargetPort 不同:

  • port: 7777:Service 對外提供的連接埠
  • targetPort: 8080:Pod 容器實際接收流量的連接埠

因此流量會變成:

1
Client -> LoadBalancer IP:7777 -> Service -> Pod:8080

如果應用程式實際仍然監聽 8080,但你希望外部使用者用 7777 連線,就可以使用這種寫法。這在遊戲伺服器、內部測試服務、或需要符合既有連線規則的系統中很常見。

測試方式如下:

1
2
3
kubectl get service dockertest-loadbalancer -n dockertest

curl http://<EXTERNAL-IP>:7777/hello

下圖可以看到 LoadBalancer 對外提供 7777 port,但仍然能將流量轉送到後端 Pod 的 8080 port;多次請求也會被分配到不同 Pod。

常用欄位說明

type

type 決定 Service 的暴露方式:

  • ClusterIP:只提供叢集內部 IP,適合內部服務互連
  • NodePort:在每個 Node 開一個固定範圍的 port,讓外部可透過 Node IP 存取
  • LoadBalancer:向底層環境申請外部 IP,適合直接暴露服務

selector

selector 決定 Service 要把流量導向哪些 Pod。本文範例使用:

1
2
selector:
app: dockertest

只要 Pod label 符合 app: dockertest,就會被加入這個 Service 的 endpoints。

可以用下列指令確認 Service 是否有找到 Pod:

1
kubectl get endpoints dockertest-loadbalancer -n dockertest

如果 endpoints 是空的,通常代表 Service selector 與 Pod label 對不起來,或 Pod 尚未 Ready。

port 與 targetPort

port 是 Service 暴露出來的連接埠,targetPort 是 Pod 實際接收流量的連接埠。

1
2
3
4
ports:
- name: http
port: 8080
targetPort: 8080

代表外部連到 8080,Service 也轉送到 Pod 的 8080

1
2
3
4
ports:
- name: tcp-game
port: 7777
targetPort: 8080

代表外部連到 7777,Service 轉送到 Pod 的 8080

externalTrafficPolicy

externalTrafficPolicy 會影響外部流量進入叢集後的轉發方式。

  • Cluster:預設值,流量可以被轉發到任一節點上的 Pod,負載分散較平均
  • Local:只轉發到接收該流量節點上的 Pod,可保留來源 IP,但需要注意 Pod 分布

開發與一般測試場景通常使用 Cluster 即可。如果應用程式需要取得真實 Client IP,才會優先考慮 Local

sessionAffinity

sessionAffinity 決定是否要讓同一個 client 固定連到同一個 Pod。

  • None:預設值,每次請求可被分配到不同 Pod
  • ClientIP:同一個 Client IP 會盡量被導向同一個 Pod

無狀態服務通常使用 None。如果應用程式把登入狀態或連線狀態放在 Pod 本機記憶體中,才需要考慮 ClientIP,但更建議把狀態移到 Redis、資料庫或其他共享儲存中。

在 Minikube 測試 LoadBalancer

Minikube 預設不會像雲端平台一樣自動建立外部負載平衡器,因此 type: LoadBalancer 需要搭配本機的 LoadBalancer 實作才會取得 EXTERNAL-IP。MetalLB 的安裝與位址池設定會放到下一篇文章介紹,這裡先聚焦在 Service 本身的使用方式。

如果環境已經能提供 LoadBalancer IP,就可以直接建立 Service 並查看狀態:

1
2
kubectl apply -f loadbalancer-service.yaml
kubectl get service dockertest-loadbalancer -n dockertest

如果只是要在 Minikube 快速測試,也可以使用 tunnel 讓 LoadBalancer Service 取得本機可存取的路由:

1
minikube tunnel

minikube tunnel 需要管理員或 root 權限,執行後會替 LoadBalancer Service 建立本機可存取的路由。

部署與檢查指令

如果前置 Deployment 已經存在,這篇只需要套用 LoadBalancer Service YAML:

1
kubectl apply -f dockertest-loadbalancer-service.yaml

檢查 Service

1
2
kubectl get service dockertest-loadbalancer -n dockertest
kubectl describe service dockertest-loadbalancer -n dockertest

檢查 Pod 與 endpoints

1
2
kubectl get pods -n dockertest -l app=dockertest
kubectl get endpoints dockertest-loadbalancer -n dockertest

測試連線

1
2
3
EXTERNAL_IP=$(kubectl get service dockertest-loadbalancer -n dockertest -o jsonpath='{.status.loadBalancer.ingress[0].ip}')

curl http://$EXTERNAL_IP:8080/hello

如果使用 case2 的 7777 port,測試指令改成:

1
curl http://$EXTERNAL_IP:7777/hello

疑難排解

LoadBalancer IP 一直是 pending

先確認 Service 狀態:

1
2
kubectl get service dockertest-loadbalancer -n dockertest
kubectl describe service dockertest-loadbalancer -n dockertest

常見原因包含:

  • 目前環境沒有提供 LoadBalancer IP 的實作
  • 本機叢集尚未啟用對應的 LoadBalancer 機制
  • 使用 Minikube 時尚未執行 minikube tunnel
  • 雲端環境的負載平衡器資源尚未建立完成

Service 有外部 IP,但連不到應用程式

先檢查 Pod 是否正常 Ready:

1
kubectl get pods -n dockertest -l app=dockertest

再檢查 Service endpoints:

1
kubectl get endpoints dockertest-loadbalancer -n dockertest

如果 endpoints 是空的,請確認 Deployment 的 Pod label 與 Service selector 是否一致。

如果 endpoints 正常,則檢查應用程式是否真的監聽 targetPort,以及 readinessProbe 是否通過:

1
2
kubectl describe pod -n dockertest -l app=dockertest
kubectl logs -n dockertest -l app=dockertest

使用建議

如果只是讓叢集內部服務互相呼叫,使用 ClusterIP 就好。如果需要從叢集外部直接用 IP 和 port 存取服務,可以使用 LoadBalancer。如果服務是 HTTP/HTTPS,並且需要用網域、路徑或 TLS 管理多個應用程式,則建議交給 Ingress 處理,這樣 LoadBalancer 只需要出現在 Ingress Controller 入口或真正需要 IP 直連的服務上。

簡單來說:

  • 內部服務互連:ClusterIP
  • 外部 IP 直連:LoadBalancer
  • 網域與 HTTP/HTTPS 路由:Ingress 搭配 ClusterIP
  • 同時需要 IP 直連與網域路由:只在真的需要直連時替應用程式保留 LoadBalancer

透過這樣的分工,可以讓 Kubernetes 服務入口更清楚,也比較容易在開發、測試與正式環境之間維持一致的部署方式。