介紹

Kubernetes 中的 Service 是一種資源,說他是資源是因為它是一種 Kubernetes 提供的 API 物件,通過描述和管理網路訪問來控制 Pods 的互動和連接方式。而Service用於將一組執行相同工作的容器 (Pods) 暴露為一個網路服務

其存在的主要目的是提供一個穩定的網路介面

讓應用程序或其他 Pods 能夠方便地相互通訊,即使 Pod 隨著時間可能會發生變化(例如重新啟動、擴展或縮減)。更具體一點,Service可以達到以下功能:

  1. 提供固定的訪問入口:Pods 是動態的,IP 可能會變動,但 Service 會提供一個固定的 IP 和 DNS 名稱,讓其他服務或應用可以持續連接,不受 Pods IP 變化的影響。
  2. 負載平衡:當多個 Pods 提供相同的功能時,Service 會自動分配流量給這些 Pods,確保請求被平均分配,避免單個 Pod 承受過多壓力。
  3. 解耦:Service 將應用程序的網路流量相關設定 與 Pod 的具體運行位置分離。簡單來說,你設定好Service之後,不管你擴展、縮減或是重新部署Pods,Service 都會自動根據你的設定去連接到正確的Pods,不需要再去修改Service的任何設定。

有不同類型的 Service,例如:

  • ClusterIP:只在 Kubernetes 集群內部可訪問,用於內部服務間的通訊,這是默認的 Service 類型。
    • 使用時間:當你的應用程序只需要在集群內部訪問時。
  • NodePort:在每個 Node(節點)上開放一個特定的端口,讓外部流量可以進入。
    • 使用時機:當你的應用程序需要從集群外部訪問時。
  • LoadBalancer:當你使用 LoadBalancer 類型的 Service 時,它會利用雲服務提供商(例如 AWS、GCP、Azure 等)的外部負載平衡器,將外部請求分配到 Kubernetes 集群中的多個節點(Nodes)上。每個節點上的 Kube-proxy 再將流量分發到與該 Service 關聯的 Pods,這樣可以實現外部請求的負載均衡,確保高可用性和分擔流量壓力。
    • 使用時機:客戶端發出多個請求,雲提供商的負載平衡器會根據流量狀況,將請求分配到集群中的不同 Nodes。
  • ExternalName:他的作用是將Kubernetes集群內部的請求根據Service的名稱轉發到外部的DNS名稱。當你創建一個 ExternalName 類型的 Service 時,Kubernetes 會在內部建立一個 DNS 記錄,將該 Service 名稱映射到你指定的外部 DNS 名稱。當集群中的 Pods 或其他服務嘗試訪問這個 Service 時,請求會被自動轉發到指定的外部域名。
    • 使用時機:假設你創建了一個名為 my-service 的 ExternalName Service,並將它指向 api.external-service.com,那麼在集群內的任何 Pod 或服務訪問 my-service 時,實際上會連接到 api.external-service.com

建立 Service

透過YAML

1
$ vim svc.yaml
1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector: # 指定這個 Service 要適用到哪些 Pod,依據label: app=MyApp來選取
app: MyApp
ports:
- protocol: TCP
port: 80 # 要作port-forward的設定,指定Pod的Port對應到的Node的Port
targetPort: 9376
1
$ kubectl apply -f svc.yaml

透過Expose

這種方法是將Pod expose給外部使用者,相當於創建一個Service。創建的Service會自動將該Pod的label填入selector欄位,name欄位則輸入Service的名稱,type是Service的類別。

1
$ kubectl expose po <pod-name> --name=<svc-name> --type=<type-of-svc>

Service 的差異

Service依照不同功能及使用場景,主要有三種Service:NodePort, ClusterIP及LoadBlancer

NodePort

NodePort的目標是將Pod expose給外部使用者


根據上圖可以看到

  1. 在Node上開放一個特定的Port,讓外部流量可以進入
  2. 再透過Service將流量分發到Pods上

NodePort 的 YAML 設定如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# $ vim NP-Svc.yaml

apiVersion: v1
kind: Service
metadata:
name: nodeport-service
spec:
type: NodePort
ports:
- targetPort: 80 # Pod的port
port: 80 # cluster IP 的 port
nodePort: 30008 # 指定node對外的port
selector:
app: MyApp
  • targetPort: 指定Pod上允許外部資源存取的Port Number,Service 會將請求導向到Pod的這個Port,如果在YAML裡面沒有指定targetPort,則預設會使用port的值。
  • port: 這個端口定義了集群內部如何訪問該服務,當 Pod 或其他服務嘗試通過該服務的名字來訪問時,這就是它們使用的端口號。如果設置 port: 80,那麼集群內的其他應用可以通過服務名和 port 80 來訪問該服務。
  • nodePort: 指定節點(Node)要開放哪一個Port,NodePort範圍是30000 ~ 32767,若NodePort欄位沒有給定,K8s會自動assign一個範圍內的Port為NodePort。當你訪問 Kubernetes 節點的這個端口時,Kubernetes 會自動將流量轉發到該服務的後端 Pod 上。nodePort 允許你從集群外部通過節點的 IP 和該端口來訪問服務。

ClusterIP

ClusterIP的目標是將Pod expose給集群內部使用者


ClusterIP是 default的Service Type,它只提供集群內部的服務,集群內的Pod都可以透過它互相訪問,集群外部則無法訪問它。ClusterIP的應用場景通常是保護某些資料不被外部存取,例如一個Web Application有back-end和front-end,我只想將front-end Pod expose出去,但又想back-end和front-end可以溝通,這時就可以用到ClusterIP Service。

但是如果你使用的是前後端分離的話,前端應用(通常是一個獨立的 Web 應用)和後端 API 是獨立部署和運行的。因此,瀏覽器在這種情況下直接與後端 API 進行通訊,而不經過 Kubernetes 集群內的前端 Pods。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# $ vim CIP-Svc.yaml

apiVersion: v1
kind: Service
metadata:
name: clusterip-service
spec:
type: ClusterIP
ports:
- targetPort: 80
port: 80
# nodePort: 30008 # 就沒有node port
selector:
app: MyApp

LoadBalancer

LoadBalancer的目標是讓流量分配到多個節點上多個Pods上

在 Kubernetes 中,LoadBalancer 用於自動創建一個雲提供商的外部負載均衡器(如 AWS ELB、GCP LB)來將外部流量導向服務的後端 Pod。它可以幫助你將外部請求分配到 Kubernetes 集群中的多個節點和 Pod 上,實現高可用性和負載均衡。儘管 LoadBalancer 可以幫助實現自動的外部流量負載均衡,但在很多情況下,團隊會選擇不使用它,原因包括以下幾點:

成本高
公有雲負載均衡器(如 AWS ELB 或 GCP Load Balancer)通常會按小時或流量計費。對於每個應用使用一個 LoadBalancer 類型的服務,隨著服務數量增多,成本會非常高,特別是在生產環境中部署大量微服務時。

受限於雲供應商的配置
且通常只支持 L4 層(TCP/UDP)(應用程式端口級別)的負載均衡。它不能像自定義的反向代理(如 NGINX 或 Traefik)一樣靈活配置 HTTP 層的負載均衡、路由規則或其他更細緻的流量控制策略。

  • L4 層負載均衡(傳輸層): L4 負載均衡基於 TCP(傳輸控制協議)或 UDP(用戶數據報協議)協議來進行流量的分配,它只在網絡層的 IP 地址和端口號的基礎上進行決策,並不理解應用層的數據。
  • 與 L4 不同,L7 層負載均衡可以處理應用層的數據,並基於這些數據進行更細緻的路由和控制。例如,L7 負載均衡器能夠解析 HTTP/HTTPS 流量,並根據具體的應用層信息(如 URL 路徑、HTTP headers、Cookies)進行決策。