Kubernetes Service 使用指南

什麼是 Kubernetes Service?

Kubernetes Service 是一個穩定的網路抽象層,用來將流量導向一組符合條件的 Pod。由於 Pod 會隨著部署、擴展、重啟或節點異動而重新建立,Pod IP 並不適合作為固定存取入口;Service 則提供固定的服務名稱、虛擬 IP 與連線方式,讓應用程式可以穩定地彼此溝通。

Service 通常會透過 selector 找到帶有指定 label 的 Pod,並把流量轉送到 Pod 的 targetPort。對使用者或其他服務來說,只需要存取 Service,就不需要關心後方 Pod 的實際位置。

Service 的主要功能:

  • 穩定入口:即使 Pod 被重新建立,Service 名稱與 IP 仍能維持穩定
  • 服務發現:透過 Kubernetes DNS 使用 Service 名稱存取服務
  • 負載分散:將請求分配到多個符合 selector 的 Pod
  • 對外暴露:依照不同 Service Type,提供叢集內或叢集外的存取方式

為什麼需要 Service?

在 Kubernetes 中,Deployment 負責管理 Pod 的生命週期,但 Deployment 本身不提供固定的網路入口。當應用程式需要被其他 Pod、節點外部使用者,或雲端負載平衡器存取時,就需要透過 Service 建立對應的流量入口。


Service 的基本運作方式

以下是一個常見的 Service 設定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: Service
metadata:
name: dockertest-service
namespace: dockertest
spec:
type: ClusterIP
selector:
app: dockertest
ports:
- name: http
port: 8080
targetPort: 8080
protocol: TCP

重要欄位說明

欄位 說明
type Service 的類型,決定服務如何被存取
selector 用來選取後端 Pod 的 label 條件
port Service 對外提供的連接埠
targetPort Pod 容器實際接收流量的連接埠
nodePort NodePort 類型使用的節點連接埠

在這個範例中,Service 會尋找 label 為 app: dockertest 的 Pod,並將送到 Service 8080 port 的流量轉送到 Pod 的 8080 port。

Service Type 類型介紹

Kubernetes Service 主要可以分成 ClusterIPNodePortLoadBalancerExternalName 四種類型。此外,Headless Service 雖然不是獨立的 type,但也是很常見的服務發現模式。

ClusterIP

ClusterIP 是預設的 Service Type。它會在叢集內建立一個虛擬 IP,只能被叢集內部的 Pod 或節點存取,適合用於服務之間的內部通訊。

適用情境:

  • 後端 API 只需要被叢集內其他服務呼叫
  • 資料庫、快取或內部微服務不需要直接暴露到叢集外
  • 搭配 Ingress 作為 HTTP/HTTPS 的後端服務

NodePort

NodePort 會在每一個 Kubernetes Node 上開啟一個固定連接埠,外部使用者可以透過 NodeIP:NodePort 存取服務。Kubernetes 預設的 NodePort 範圍通常是 30000-32767

適用情境:

  • 本機測試或開發環境需要快速對外存取服務
  • 沒有雲端 Load Balancer,但仍希望透過節點 IP 暴露服務
  • 作為 LoadBalancer 或 Ingress Controller 的底層入口

LoadBalancer

LoadBalancer 會向雲端供應商申請外部負載平衡器,並將外部流量導向 Service。在本機環境中,通常需要搭配 MetalLB 或 Minikube tunnel 才能取得外部 IP。

適用情境:

  • 需要將服務直接暴露給叢集外部使用者
  • 使用雲端 Kubernetes 服務,例如 GKE、EKS、AKS
  • 需要由雲端平台管理外部 IP 與負載平衡器

ExternalName

ExternalName 不會建立 ClusterIP,也不會使用 selector 選取 Pod。它會透過 DNS CNAME 的方式,將 Service 名稱對應到另一個外部 DNS 名稱。

適用情境:

  • 讓叢集內服務用 Kubernetes Service 名稱存取外部系統
  • 將外部資料庫、第三方 API 或既有服務包裝成叢集內 DNS 名稱
  • 在不建立代理或端點的情況下統一服務名稱

Headless Service

Headless Service 是將 clusterIP 設定為 None 的 Service。它不會建立虛擬 IP,而是讓 DNS 直接回傳後端 Pod IP,常用於需要直接發現 Pod 的應用程式。

適用情境:

  • StatefulSet 需要穩定的 Pod DNS 名稱
  • 應用程式需要自行處理連線與負載分散
  • 需要直接取得後端 Pod IP 清單

Service Type 比較

類型 是否有 ClusterIP 是否可從叢集外存取 常見用途
ClusterIP 叢集內部服務通訊
NodePort 是,透過 NodeIP:NodePort 本機測試、簡易外部入口
LoadBalancer 是,透過外部 IP 雲端或 MetalLB 對外服務
ExternalName 視外部 DNS 而定 將 Service 名稱對應到外部服務
Headless Service Pod DNS 發現、StatefulSet

測試用 Deployment

以下範例使用 Spring Boot 應用程式作為測試基礎。為了避免和目前叢集內其他環境混合,所有測試資源都會放在獨立的 dockertest namespace 中。應用程式會提供 /hello API,測試時預期回應內容為 Hello, World!

建立測試 Namespace 與 Registry Secret

先建立 dockertest namespace,讓 Deployment、Secret 與 Service 都集中在同一個範圍內管理。

1
2
3
4
5
6
7
8
# 建立專案使用的命名空間,讓相關資源集中在 dockertest 底下管理。
apiVersion: v1
kind: Namespace
metadata:
name: dockertest
labels:
# 標記此命名空間屬於開發環境,方便查詢或套用環境相關策略。
environment: dev
1
2
3
4
5
# 建立獨立測試 namespace
kubectl apply -f dockertest-namespace.yaml

# 確認 namespace 已建立
kubectl get namespace dockertest

由於測試映像放在 GitLab Container Registry 私有倉庫,還需要建立 image pull secret。實務上不建議把 registry token 直接寫進文章或提交到版本庫,請在本機或 CI 環境中用下列方式建立 Secret:

1
2
3
4
5
6
kubectl create secret docker-registry gitlab-registry-secret \
--namespace dockertest \
--docker-server=inner.gitlab.dev:5050 \
--docker-username=<GITLAB_USERNAME> \
--docker-password=<GITLAB_ACCESS_TOKEN> \
--docker-email=<EMAIL>

如果需要使用 YAML 管理 Secret,可保留 Secret 結構,但請將 .dockerconfigjson 替換成實際環境產生的 base64 內容,不要把可用憑證寫入公開或共用筆記。

1
2
3
4
5
6
7
8
9
10
11
# 儲存拉取 GitLab Container Registry 私有映像所需的認證資訊。
apiVersion: v1
kind: Secret
metadata:
name: gitlab-registry-secret
namespace: dockertest
# kubernetes.io/dockerconfigjson 是 Kubernetes 用來存放 Docker registry 認證的 Secret 類型。
type: kubernetes.io/dockerconfigjson
data:
# .dockerconfigjson 內容需為 base64 編碼後的 Docker config JSON。
.dockerconfigjson: <BASE64_DOCKER_CONFIG_JSON>

Spring Boot Deployment

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# 部署 Spring Boot 應用程式,並設定容器映像、環境變數與健康檢查。
apiVersion: apps/v1
kind: Deployment
metadata:
name: dockertest-deployment
namespace: dockertest
labels:
# metadata labels 可協助識別此 Deployment 的環境、應用與版本。
environment: dev
app: dockertest
version: latest
spec:
# 開發環境先維持單一副本;正式環境可依需求調整 replicas。
replicas: 1
selector:
matchLabels:
# selector 必須與 template.metadata.labels 相符,Deployment 才能管理這些 Pod。
app: dockertest
template:
metadata:
labels:
# Pod labels 供 Deployment selector 與 Service selector 使用。
app: dockertest
version: latest
spec:
containers:
- name: dockertest
# 應用程式映像來源,需搭配下方 imagePullSecrets 才能從私有 registry 拉取。
image: inner.gitlab.dev:5050/pcion123/docker-test:latest
# Always 會在每次建立 Pod 時嘗試拉取最新映像,適合 latest tag 的開發部署。
imagePullPolicy: Always
ports:
# Spring Boot 預設服務埠,Service 也會轉發到這個 containerPort。
- containerPort: 8080
env:
# JVM 記憶體與 Spring profile 設定。
- name: JAVA_OPTS
value: "-Xmx512m -Xms256m -Dspring.profiles.active=dev"
# 指定應用啟動模式;目前設定為 service-test,供程式依本機叢集環境調整行為。
- name: START_MODE
value: "service-test"
# readinessProbe 用來判斷 Pod 是否可接收流量;未就緒時 Service 不會導流到此 Pod。
readinessProbe:
httpGet:
path: /hello
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
imagePullSecrets:
# 指定上方建立的 registry Secret,供 Kubernetes 拉取私有映像使用。
- name: gitlab-registry-secret

部署 Spring Boot 應用程式

1
2
3
4
5
6
7
8
# 建立 Deployment YAML
kubectl apply -f dockertest-deployment.yaml

# 查看 Deployment 狀態
kubectl get deployment dockertest-deployment -n dockertest

# 查看 Pod 狀態
kubectl get pods -l app=dockertest -n dockertest

確認 Pod 進入 RunningREADY 顯示為 1/1 後,就可以開始建立不同類型的 Service。

本機 API 端點測試

在建立 Service 前,可以先透過 kubectl port-forward 驗證應用程式本身是否正常回應。這個測試會將本機 8080 port 轉發到 Deployment 後方 Pod 的 8080 port。

1
2
# 將本機 8080 port 轉發到 Spring Boot 應用程式
kubectl port-forward deployment/dockertest-deployment -n dockertest 8080:8080

開啟另一個終端機執行測試:

1
curl http://localhost:8080/hello

預期會取得下列回應:

1
Hello, World!

ClusterIP 測試案例

ClusterIP 適合測試叢集內部服務通訊。建立後,可以從另一個臨時 Pod 使用 Service 名稱存取 Spring Boot 應用程式。

ClusterIP Service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: Service
metadata:
name: dockertest-clusterip
namespace: dockertest
spec:
type: ClusterIP
selector:
app: dockertest
ports:
- name: http
port: 8080
targetPort: 8080
protocol: TCP

部署與測試

1
2
3
4
5
6
7
8
9
10
11
# 建立 ClusterIP Service
kubectl apply -f dockertest-clusterip-service.yaml

# 查看 Service
kubectl get service dockertest-clusterip -n dockertest

# 查看 Service 對應的 Endpoints
kubectl get endpoints dockertest-clusterip -n dockertest

# 從叢集內部建立臨時 Pod 測試連線
kubectl run curl-test -n dockertest --rm -it --image=curlimages/curl --restart=Never -- curl http://dockertest-clusterip:8080/hello

如果連線成功,會看到 Hello, World! 回應內容。

NodePort 測試案例

NodePort 可以讓叢集外部透過節點 IP 和指定連接埠存取應用程式。這個方式很適合在 Minikube、Kind 或測試環境中快速驗證服務。

NodePort Service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: Service
metadata:
name: dockertest-nodeport
namespace: dockertest
spec:
type: NodePort
selector:
app: dockertest
ports:
- name: http
port: 8080
targetPort: 8080
nodePort: 30080
protocol: TCP

部署與測試

1
2
3
4
5
6
7
8
# 建立 NodePort Service
kubectl apply -f dockertest-nodeport-service.yaml

# 查看 Service 與 NodePort
kubectl get service dockertest-nodeport -n dockertest

# 查看節點 IP
kubectl get nodes -o wide

如果是在一般 Kubernetes 節點,可以使用節點 IP 測試:

1
curl http://<NODE_IP>:30080/hello

如果是在 Minikube,可以使用下列指令取得服務 URL:

1
minikube service dockertest-nodeport -n dockertest --url

接著使用輸出的 URL 測試 /hello API:

1
curl <MINIKUBE_SERVICE_URL>/hello

LoadBalancer 測試案例

LoadBalancer 適合將服務暴露給叢集外部。若使用雲端 Kubernetes,建立 Service 後通常會自動分配外部 IP;若使用 Minikube 或裸機環境,可能需要先啟用 minikube tunnel 或安裝 MetalLB。

LoadBalancer Service

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

部署與測試

1
2
3
4
5
# 建立 LoadBalancer Service
kubectl apply -f dockertest-loadbalancer-service.yaml

# 查看外部 IP
kubectl get service dockertest-loadbalancer -n dockertest

如果 EXTERNAL-IP 已經取得 IP,就可以直接測試:

1
curl http://<EXTERNAL_IP>:8080/hello

在 Minikube 環境中,可以使用下列方式測試:

1
2
3
4
5
# 方法一:直接開啟 Service URL
minikube service dockertest-loadbalancer -n dockertest --url

# 方法二:啟用 tunnel,讓 LoadBalancer 取得外部 IP
minikube tunnel

注意事項

如果 EXTERNAL-IP 長時間顯示 <pending>,通常代表目前環境沒有可用的 Load Balancer 實作。雲端環境需要確認雲端控制器是否正常;本機環境則需要使用 Minikube tunnel 或 MetalLB。

ExternalName 測試案例

ExternalName 不是用來直接暴露 Pod 的 Service Type,因為它不會透過 selector 連到 Pod。為了示範它的用途,可以先建立 dockertest-clusterip,再建立一個 ExternalName Service 作為 DNS 別名,讓叢集內部透過另一個服務名稱存取同一個應用程式。

ExternalName Service

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Service
metadata:
name: dockertest-externalname
namespace: dockertest
spec:
type: ExternalName
externalName: dockertest-clusterip.dockertest.svc.cluster.local
ports:
- name: http
port: 8080
targetPort: 8080
protocol: TCP

部署與測試

1
2
3
4
5
6
7
8
9
10
11
# 確認 ClusterIP Service 已存在
kubectl get service dockertest-clusterip -n dockertest

# 建立 ExternalName Service
kubectl apply -f dockertest-externalname-service.yaml

# 查看 ExternalName Service
kubectl get service dockertest-externalname -n dockertest

# 從叢集內部使用 ExternalName 測試連線
kubectl run curl-test -n dockertest --rm -it --image=curlimages/curl --restart=Never -- curl http://dockertest-externalname:8080/hello

這個測試會透過 DNS CNAME 將 dockertest-externalname 對應到 dockertest-clusterip.dockertest.svc.cluster.local,再由原本的 ClusterIP Service 導向應用程式 Pod。

Headless Service 測試案例

Headless Service 不會建立 ClusterIP,而是直接透過 DNS 回傳後端 Pod IP。雖然這個範例只有一個應用程式 Pod,但仍可以用來觀察 Headless Service 的 DNS 與 Endpoints 行為。

Headless Service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: Service
metadata:
name: dockertest-headless
namespace: dockertest
spec:
clusterIP: None
selector:
app: dockertest
ports:
- name: http
port: 8080
targetPort: 8080
protocol: TCP

部署與測試

1
2
3
4
5
6
7
8
9
10
11
# 建立 Headless Service
kubectl apply -f dockertest-headless-service.yaml

# 查看 Service,CLUSTER-IP 會顯示 None
kubectl get service dockertest-headless -n dockertest

# 查看 Endpoints,會看到應用程式 Pod IP
kubectl get endpoints dockertest-headless -n dockertest

# 從叢集內部測試 Headless Service DNS
kubectl run curl-test -n dockertest --rm -it --image=curlimages/curl --restart=Never -- curl http://dockertest-headless:8080/hello

若要更明確觀察 DNS 解析結果,可以使用 nslookup

1
kubectl run dns-test -n dockertest --rm -it --image=busybox:1.36 --restart=Never -- nslookup dockertest-headless

常用檢查指令

建立 Service 後,可以使用下列指令確認 Service 是否正確連到應用程式 Pod。

1
2
3
4
5
6
7
8
9
10
11
# 查看所有 Service
kubectl get services -n dockertest

# 查看指定 Service 詳細資訊
kubectl describe service dockertest-clusterip -n dockertest

# 查看 Service 後端端點
kubectl get endpoints -n dockertest

# 查看符合 selector 的 Pod
kubectl get pods -l app=dockertest -n dockertest -o wide

如果 Service 沒有正常轉送流量,優先檢查 selector 是否與 Pod label 一致:

1
2
3
4
5
# 查看 Pod label
kubectl get pods -n dockertest --show-labels

# 查看 Service selector
kubectl describe service dockertest-clusterip -n dockertest

清除測試資源

測試完成後,可以使用下列指令刪除文章中建立的資源。

1
kubectl delete namespace dockertest

總結

Kubernetes Service 是連接 Pod 與流量來源的重要抽象層。ClusterIP 適合叢集內部通訊,NodePort 適合快速從節點外部測試,LoadBalancer 適合正式對外提供服務,ExternalName 則適合將外部 DNS 包裝成 Kubernetes 內的服務名稱。至於 Headless Service,雖然不是獨立的 Service Type,但在需要直接發現 Pod IP 或搭配 StatefulSet 時非常實用。

在實務上,選擇哪一種 Service Type 取決於服務的存取範圍。如果只需要內部服務通訊,使用 ClusterIP 即可;如果需要對外開放,則可以依環境選擇 NodePortLoadBalancer 或搭配 Ingress 進行更完整的 HTTP/HTTPS 路由管理。