k8s (四) 服務:讓客戶端發現 pod 並與之通信

服務是一種爲一組功能相同的 pod 提供單一不變的接入點的資源。當服務存在時,它的 IP 地址和端口不會改變。客戶端通過 IP 地址和端口號建立連接,這些連接會被路由到提供該服務的任意一個 pod 上。通過這種方式,客戶端不需要知道每個單獨的提供服務的 pod 的地址,這樣這些 pod就可以在集羣中隨時被創建或移除。

一、創建服務

# kubia-svc.yaml

apiVersion: v1
kind: Service
metadata:
  name: kubia
spec:
  ports:
  - port: 80 # 該服務的可用端口
    targetPort: 8080 # 服務將連接轉發到的容器端口
  selector:
    app: kubia # 具有 app=kubia 標籤的 pod 都屬於該服務

創建:

kubectl create -f kubia-svc.yaml

查看:

kubectl get svc # svc 是 service 的簡寫

(上篇文章已經通過 ReplicaSet 啓動了 3 個 app=kubia 標籤的 pod)通過 curl 命令即可看到效果:

curl 10.96.9.157 # 10.96.9.157 爲分配給服務的 IP

在運行中的容器中遠程執行命令:

kubectl exec kubia-ntxd8 -- curl -s 10.96.9.157

雙橫槓(—) 代表着 kubectl 命令項的結束。雙橫槓之後的內容指在 pod 內部需要執行的命令。

1.1. 配置服務上的會話親和性

如果多次執行同樣的命令,每次調用執行應該在不同的 pod 上。因爲服務代理通常將每個連接隨機指向選中的後端 pod 中的一個,即使連接來自於同一個客戶端。如果希望特定客戶端產生的所有請求每次都指向同一個 pod, 可以
設置服務的 sessionAffinity 屬性爲 ClientIP。Kubernetes 僅僅支持兩種形式的會話親和性服務:None(隨機) 和 ClientIP,不支持 cookie,因爲 Kubernetes 服務處理的是 TCP 和 UDP 而不是 HTTP。

1.2. 同一服務暴露多個端口

apiVersion: v1
kind: Service
metadata:
  name: kubia
spec:
  ports:
  - name: http # 必須給每個端口指定名字
    port: 80
    targetPort: 8080
  - name: https # 必須給每個端口指定名字
    port: 443
    targetPort: 8443
  selector:
    app: kubia

1.3. 使用命名的端口

好處是即使更換端口號也無需更改服務 spec

在 pod 的定義中指定 port 名稱:

kind: Pod
spec:
  containers:
  - image: luksa/kubia
    name: kubia
    ports:
    - name: http    
      containerPort: 8080
    - name: https    
      containerPort: 8443

在服務中引用端口:

kind: Service
metadata:
  name: kubia
spec:
  ports:
  - name: http
    port: 80
    targetPort: http # 映射到容器中被稱爲 http 的端口
  - name: https
    port: 443
    targetPort: https # 映射到容器中被稱爲 https 的端口 
  selector:
    app: kubia

二、服務發現

2.1. 通過環境變量發現服務

在 pod 開始運行的時候,Kubernetes 會初始化一系列的環境變量指向現在存在的服務。如果創建的服務早於客戶端 pod 的創建,pod 上的進程可以根據環境變量獲得服務的 IP 地址和端口號。

因爲現有的 pod 創建於服務之前,所以需要先刪除掉所有的 pod 這樣 ReplicaSet 會創建全新的 pod:

刪除 pod:

kubectl delete pods --all

查看環境變量:

kubectl exec kubia-jkj9s -- env

結果:

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
... ...
KUBIA_SERVICE_HOST=10.96.9.157
KUBIA_SERVICE_PORT=80

... ...

服務名稱中的橫槓被轉換爲下劃線,並且當服務名稱用作環境變量名稱中的前綴時,所有的字母都是大寫的。

2.2. 通過 DNS 發現服務

通過 FQDN (全限定域名)連接服務:


kubia 是服務名稱,default 代表所在的命名空間;svc.cluster.local 是在所有集羣本地服務名稱中使用的可配置集羣域後綴。(Kubernetes 通過修改每個容器的 /etc/resolv.conf 文件實現)

注意:客戶端仍然必須要知道服務的端口號,如果不是標準端口(80 或 5432),客戶端可以從環境變量中獲取端口號。

在 pod 容器中運行 shell:

```kubectl exec -it kubia-jkj9s bash

訪問服務:

curl http://kubia.default.svc.cluster.local

三、連接集羣外部的服務

3.1. 服務的 endpoint

服務和 pod 並不是直接相連的,而是通過 endpoint 資源。

查看服務描述,可以看到包含 endpoint 的信息:

kubectl describe svc kubia

endpoint 資源就是暴露一個服務的 IP 地址和端口的列表,也可以使用命令查看:

kubectl get endpoints kubia

儘管在 spec 服務中定義了 pod 選擇器,但在重定向傳入連接時不會直接使用它。相反,選擇器用於構建 IP 和端口列表,然後存儲在 endpoint 資源中。當客戶端連接到服務時,服務代理選擇這些 IP 和端口對中的一個,並將傳入連接重定向到在該位置監聽的服務器。

3.2. 手動配置服務的 endpoint

如果創建了不包含 pod 選擇器的服務, Kubernetes 將不會創建 endpoint 資源(畢
竟缺少選擇器,將不會知道服務中包含哪些 pod)。這樣就需要創建 endpoint 資源來指定該服務的 endpoint 列表。

3.2.1. 創建沒有選擇器的服務

# external-service.yaml

apiVersion: v1
kind: Service
metadata:
  name: external-service # 服務的名字必須和 endpoint 對象的名字相匹配
spec:
  ports:
  - port: 80

kubectl create -f external-service.yaml

3.2.2. 創建 endpoint

# external-service-endpoints.yaml

apiVersion: v1
kind: Endpoints
metadata:
  name: external-service # endpoint 的名字必須和服務的名字相匹配
subsets:
  - addresses: # 服務將連接重定向到 endpoint 的 IP 地址
    - ip: 11.11.11.11
    - ip: 22.22.22.22
    ports:
    - port: 80 # endpoint 的目標端口

kubectl create -f external-service-endpoints.yaml

3.2.3. 爲外部服務創建別名

除了手動配置服務的 endpoint 來代替公開外部服務方法,有一種更簡單的方法,就是通過其完全限定域名(FQDN)訪問外部服務

apiVersion: v1
kind: Service
metadata:
  name: external-service
spec:
  type: ExternalName # type 設置成 ExternalName
  externalName: api.somecompany.com # 實際服務的完全限定域名
  ports:
  - port: 80

服務創建完成後 pod 可以通過 external-service.svc.cluster.local 域名連接到外部服務,而不是使用服務的實際 FQDN。

四、將服務暴露給外部客戶端

有幾種方式可以在外部訪問服務:

  • 將服務的類型設置成 NodePort:每個集羣節點都會在節點上打開一個端口,對於 NodePort 服務,每個集羣節點在節點本身(因此得名叫 NodePort)上打開一個端口,並將在該端口上接收到的流量重定向到基礎服務。該服務僅在內部集羣 IP 和端口上纔可訪間,但也可通過所有節點上的專用端口訪問。
  • 將服務的類型設置成 LoadBalance:NodePort類型的一種擴展,這使得服務可以通過一個專用的負載均衡器來訪問,這是由 Kubernetes 中正在運行的雲基礎設施提供的。負載均衡器將流量重定向到跨所有節點的節點端口。客戶端通過負載均衡器的 IP 連接到服務。
  • 創建一個 Ingress 資源,這是一個完全不同的機制,通過一個 IP 地址公開多個服務。它運行在 HTTP 層(網絡協議第 7 層)上,因此可以提供比工作在第 4 層的服務更多的功能。

4.1. 使用 NodePort 類型的服務

創建 NodePort 類型的服務:

# kubia-svc-nodeport.yaml

apiVersion: v1
kind: Service
metadata:
  name: kubia-nodeport
spec:
  type: NodePort # NodePort 類型
  ports:
  - port: 80 # 服務集羣 IP 的端口號
    targetPort: 8080 # 背後 pod 的目標端口號
    nodePort: 30123 # 通過集羣節點的 30123 端口可以訪問該服務,如果沒有指定則隨機分配
  selector:
    app: kubia

kubectl create -f kubia-svc-nodeport.yaml

查看服務:

kubectl get svc kubia-nodeport

因爲我們使用的 Minikube,可以運行下面命令訪問 NodePort 服務:讓客戶端發現

minikube service kubia-nodeport

4.2. 通過負載均衡器將服務暴露出來

在雲提供商上運行的 Kubernetes 集羣通常支持從雲基礎架構自動提供負載平衡器。所有需要做的就是設置服務的類型爲 Load Badancer 而不是 NodePort。

創建 LoadBalance 服務:

# kubia-svc-loadbalancer.yaml

apiVersion: v1
kind: Service
metadata:
  name: kubia-loadbalancer
spec:
  type: LoadBalancer
  ports:
  - port: 80
    targetPort: 8080
  selector:
    app: kubia

沒有指定端口則隨機分配一個端口

kubectl create -f kubia-svc-loadbalancer.yaml

創建服務後,雲基礎架構需要一段時間才能創建負載均衡器並將其 IP 地址寫入服務對象。完成之後 IP 地址將被列爲服務的外部 IP 地址:

kubectl get svc kubia-loadbalancer

4.3. 通過 Ingress 暴露服務

爲什麼需要 Ingress:一個重要的原因是每個 LoadBalancer 服務都需要自己的負載均衡器,以及獨有的公有 IP 地址,而 Ingress 只需要一個公網 IP 就能爲許多服務提供訪問。當客戶端向 Ingress 發送 HTTP 請求時,Ingress 會根據請求的主機名和路徑決定請求轉發到的服務。

Ingress 在網絡棧 (HTTP) 的應用層操作,並且可以提供一些服務不能實現的功能,諸如基於 cookie 的會話親和性 (session affinity) 等功能。

啓用 Ingress 組件:

minikube addons enable ingress

因爲 minikube 啓動參數添加了 —driver=none 參數,不支持 Ingress 後續暫略。。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章