淺入Kubernetes(8):外網訪問叢集 中已經介紹過部署一個 Deployment 和 Service,本篇是它的補充,將會廣泛地聊一下 Service。
文章地址 https://www.cnblogs.com/whuanle/p/14685430.html
Srevice
Service 是將執行在一組 Pods 上的應用程式公開為網路服務的抽象方法。如果我們使用 Deployment 部署 pod,則可以以 Deployment 為物件建立 Service。
在 K8S 中,每個 Pod 都有其自己唯一的 ip 地址,而 Service 可以為多個 Pod(一組)提供相同的 DNS 名,並且可以在他們直接進行負載均衡。
假如有一組 nginx pod,如果 nginx 動態伸縮改變或因為某些原因 ip/埠發生改變,那麼其 ip 和 埠都不是固定的,而且我們很難確定它新擴容的 pod 的地址是什麼,又萬一 pod 被刪除,ip 不再可用。
又假如一組 pod 稱為前端,如 web 服務,另一組 pod 稱為後端,例如 mysql。那麼 前端 如何查詢並跟蹤要連線的 ip 地址,以便前端可以使用工作負載的後端部分?
這真是 Service 要解決的問題。Kubernetes Service 定義了一種抽象:邏輯上的一組 Pod,一種可以訪問它們的策略 —— 通常稱為微服務。當使用 Service 為一組 pod (Deployment 的方式建立的)建立服務時,無論我們建立了多少個 pod 副本,這些 pod 怎麼變化,前端不需要關心它們呼叫了哪個後端副本,而且不需要知道後端 pod 的狀態也不需要跟蹤 pod。Service 把前後端的這種關聯抽象化,把它們解耦了。
Service 的建立及現象
現在按照下面的命令快速建立 pod,pod 將會在各個節點中部署執行。
kubectl create deployment nginx --image=nginx:latest --replicas=3
kubectl expose deployment nginx --type=LoadBalancer --port=80
然後執行命令檢視 Service:
kubectl get services
也就是說外部訪問埠是 30424。
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 3d6h
nginx LoadBalancer 10.101.132.236 <pending> 80:30424/TCP 39s
這時,我們可以通過公網和埠訪問這個 Service。
我們可以檢視此 Service 的 yaml 檔案:
kubectl get service nginx -o yaml
apiVersion: v1
kind: Service
metadata:
creationTimestamp: "2021-04-23T07:40:35Z"
labels:
app: nginx
name: nginx
namespace: default
resourceVersion: "369738"
uid: 8dc49805-2fc8-4407-adc0-8e31dc24fa79
spec:
clusterIP: 10.101.132.236
clusterIPs:
- 10.101.132.236
externalTrafficPolicy: Cluster
ipFamilies:
- IPv4
ipFamilyPolicy: SingleStack
ports:
- nodePort: 30424
port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx
sessionAffinity: None
type: LoadBalancer
status:
loadBalancer: {}
有了標準的 yaml 檔案模板,我們可以很方便地修改並定製一個 Service。
我們檢視通過 Deployment 建立的 pod:
kubectl get pods -o wide
NAME IP NODE NOMINATED NODE READINESS GATES
nginx-55649fd747-9fzlr 192.168.56.56 instance-2 <none> <none>
nginx-55649fd747-ckhrw 192.168.56.57 instance-2 <none> <none>
nginx-55649fd747-ldzkf 192.168.23.138 instance-1 <none> <none>
注:pod 在哪個節點中執行,我們是不一樣的。
當我們通過外部網路訪問時,Service 會自動提供其中一個 pod 給我們,但是這個過程較為複雜,我們這裡先將表面現象。
------------
| |
--- 公網ip --> | pod1 |
| pod2 |
| pod3 |
------------
然後我們通過命令檢視 iptables 配置:
iptables-save
然後查詢 random 關鍵字:
你可以看到有三個 default/nginx
, 第一個 pod 被訪問的機會都是 0.33333...
,然後 2/3 的概率中,2/3 的 0.5 的概率選擇第二個 pod,剩下的 1/3 概率選擇第三個 pod。
如果要訪問 pod,可以以任意部署了 nginx pod 的節點的 ip 進行訪問。由於 master 不能部署 pod,所以不能通過 master 的 ip 進行訪問。
當然,它並不是直接都是 0.33333..
這樣的,iptables 的規則有點複雜,這裡難以講清楚,我們只需要知道 外網能夠訪問 Service,而 Service 通過 iptable 為我們轉發流量。即使 Deployment 部署的 pod 不在同一個節點上, k8s 的 dns 服務等會正確處理的,我們不需要手動配置這些網路。
【圖來源:k8s 官網】
Service 定義
在上一小節中,介紹了 Service 的建立方法(kubectl expose ...
),也介紹了其依賴的 iptables,這裡將繼續學習 Service 的定義方法。
因為之前我們是通過 Deployment 進行操作,直接為一個 deployment 中的 pod (副本)統一對映。當然我們也可以為不同的 pod 進行網路對映。
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 6666
targetPort: 80
這裡我們使用了 selector
選擇器,一般 pod 的 Label 都會有 app
,表示此 pod 的名稱(一般是映象名稱)。port、targetPort 分別是 pod 埠、提供給外界訪問的埠。
當我們不通過 Deployment 或者 job 等物件處理 pod 時,可以通過 selector
來選擇合適的 pod。
Service 能夠將一個接收 容器或者 pod 的埠 targetPort
對映到任意的 port 埠,port 是外部可以訪問的埠。 如果使用 kubectl expose
去對映埠,會預設隨機提供一個 30xxx
埠。而使用 yaml ,預設情況下,targetPort
將被設定為與 port
欄位相同的值。
Endpoint slices
”端點切片(Endpoint Slices) 提供了一種簡單的方法來跟蹤 Kubernetes 叢集中的網路端點 (network endpoints)。它們為 Endpoints 提供了一種可伸縮和可擴充的替代方案。“
在 Kubernetes 中,EndpointSlice
包含對一組網路端點的引用。 指定選擇器後控制面會自動為設定了 選擇算符 的 Kubernetes 服務建立 Endpoint。
也就是說建立 Service(帶選擇運算子) 會自動建立 Endpiont。
我們檢視預設名稱空間的 endpoint:
kubectl get endpoints
NAME ENDPOINTS AGE
kubernetes 10.170.0.2:6443 3d7h
nginx 192.168.56.24:80,192.168.56.25:80,192.168.56.26:80 59m
這些都是 pod 的 ip 和埠,也就是說,通過 Endpoint 我們跟蹤 Kubernetes 叢集中的網路端點 (network endpoints)變得更加任意。不過這樣解釋是很難明白的,筆者翻了很多次資料,一點點試錯才搞懂。接下來我們一步步來上手操作,然後一點點理解這些操作的含義。
建立 Endpoint、Service
接下來我們手動建立 Service 和 Endpoint 和 ,需要先建立 Service ,再建立 Endpoint (這兩者建立順序可以隨意)。
Service
我們先刪除之前建立的 service。
kubectl delete service nginx
編寫 service.yaml 檔案內容如下如下:
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
ports:
- protocol: TCP
port: 6666
targetPort: 80
應用這個 Service:
kubectl apply -f service.yaml
檢視 service :
kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 3d12h
nginx ClusterIP 10.98.113.242 <none> 6666/TCP 6s
由於此 Service 沒有對映任何 pod 等,因此沒有任何用處,但是此時已經可以給開發人員一個交待了,或者說確定下 nginx 的 Service 埠和地址。至於真正的 nginx 服務,後面再確定。建立 Service 和 Endpoint 的順序是任意的,只是這裡我們提出抽象,先約定 埠,再提供服務,所以先建立 Service。
建立應用
我們隨便找臺 worker 或者 master 節點,建立一個 nginx 容器:
docker run -itd -p 80:80 nginx:latest
為什麼不用 pod,直接建立容器?因為我們處於開發階段,如果把 nginx 改成 mysql,我們要 Debug 呢?測試自己的資料庫呢?要模擬資料呢?我們在生產時再通過 Deployment 建立應用,但是此時我們可以使用自己的資料庫或者本地應用。
官方文件說:
- 希望在生產環境中使用外部的資料庫叢集,但測試環境使用自己的資料庫。
- 希望服務指向另一個 名字空間(Namespace) 中或其它叢集中的服務。
- 你正在將工作負載遷移到 Kubernetes。 在評估該方法時,你僅在 Kubernetes 中執行一部分後端。
總之,我們建立了 Service,可以提供了抽象,至於怎麼提供這個服務,我們可以使用 pod ,也可以直接使用命令執行機器上的二進位制程式,也可以通過 docker 提供。而且 mysql 可能是在外部服務提供的,或者 mysql 直接部署在宿主機上,而不使用容器和 pod,我們可以通過 Endpoint 來跟蹤 mysql 服務的埠。
然後查詢這個容器的 ip,:
docker inspect {容器id} | grep IPAddress
筆者得到的是:"IPAddress": "172.17.0.2"
,可以試試 curl 172.17.0.2
,看看是否能夠訪問 nginx,如果沒問題我們來進行下一步。
建立 Endpoint
建立一個 endpoint.yaml 檔案,內容如下(注意替換ip為你容器訪問ip):
apiVersion: v1
kind: Endpoints
metadata:
name: nginx
subsets:
- addresses:
- ip: 172.17.0.2
ports:
- port: 80
然後應用 yaml:
kubectl apply -f endpoint.yaml
檢視 endpoint:
kubectl get endpoints
# 不能填寫成 endpoint
然後訪問 Service 的 ip:
curl 10.99.142.24:6666
也可以通過公網訪問此 IP。
如果 Endpoint 需要跟蹤多個 ip (多個 pod 或者容器或者應用),可以使用:
- addresses:
- ip: 172.17.0.2
- ip: 172.17.0.3
- ip: 172.17.0.4
... ...