Kubernetes & Service
Kubernetes & Service
概述
Service
服務也是Kubernetes
裡的核心資源物件之一,Kubernetes
裡的每個Service
其實就是我們經常提起的微服務架構中的一個微服務,之前講解Pod
、RC
等資源物件其實都是為講解Kubernetes Service
做鋪墊的。圖顯示了Pod、RC與Service的邏輯關係。
從圖中可以看到,Kubernetes的Service定義了一個服務的訪問入口地址,前端的應用(Pod)通過這個入口地址訪問其背後的一組由Pod副本組成的叢集例項,Service與其後端Pod副本叢集之間則是通過Label Selector
來實現無縫對接的。RC的作用實際上是保證Service
通過分析、識別並建模系統中的所有服務為微服務—Kubernetes Service
,我們的系統最終由多個提供不同業務能力而又彼此獨立的微服務單元組成的,服務之間通過TCP/IP進行通訊,從而形成了強大而又靈活的彈性網格,擁有強大的分散式能力、彈性擴充套件能力、容錯能力,程式架構也變得簡單和直觀許多。如圖所示:
既然每個Pod都會被分配一個單獨的IP地址,而且每個Pod都提供了一個獨立的Endpoint(Pod IP+ContainerPort)
以被客戶端訪問,現在多個Pod副本
組成了一個叢集
來提供服務,那麼客戶端如何來訪問它們呢?
一般的做法是部署一個負載均衡器(軟體或硬體)
Endpoint
列表加入8000埠的轉發列表,客戶端就可以通過負載均衡器的對外IP地址+服務埠來訪問此服務。客戶端的請求最後會被轉發到哪個Pod,由負載均衡器的演算法所決定。
Kubernetes也遵循上述常規做法,執行在每個Node上的kube-proxy
程序其實就是一個智慧的軟體負載均衡器
,負責把對Service
的請求轉發到後端的某個Pod例項
上,並在內部實現服務的負載均衡與會話保持機制。
但Kubernetes發明了一種很巧妙又影響深遠的設計:Service
沒有共用一個負載均衡器的IP地址,每個Service
都被分配了一個全域性唯一的虛擬IP地址,這個虛擬IP被稱為Cluster IP
我們知道,Pod的Endpoint
地址會隨著Pod的銷燬和重新建立而發生改變,因為新Pod的IP地址與之前舊Pod的不同。而Service一旦被建立,Kubernetes就會自動為它分配一個可用的Cluster IP
,而且在Service的整個生命週期內,它的Cluster IP
不會發生改變。於是,服務發現這個棘手的問題在Kubernetes
的架構裡也得以輕鬆解決:只要用Service
的Name與Service的Cluster IP地址做一個DNS域名對映即可完美解決問題。現在想想,這真是一個很棒的設計。
動手建立一個Service來加深對它的理解。建立一個名為tomcat-service.yaml的定義檔案,內容如下:
apiVersion: v1
kind: Service
metadata:
name: tomcat-service
spec:
ports:
- port: 8080
selector:
tier: frontend
建立
roverliang@roverliangdeMac-mini study % kubectl create -f tomcat-service.yaml
service/tomcat-service created
Kubernetes 的服務發現機制
任何分散式系統都會涉及服務發現
這個基礎問題,大部分分散式系統都通過提供特定的API介面來實現服務發現功能,但這樣做會導致平臺的侵入性比較強,也增加了開發、測試的難度。Kubernetes則採用了直觀樸素的思路去解決這個棘手的問題。
首先,每個Kubernetes
中的Service
都有唯一的Cluster IP
及唯一的名稱
,而名稱是由開發者自己定義的,部署時也沒必要改變,所以完全可以被固定在配置中。接下來的問題就是如何通過Service的名稱
找到對應的Cluster IP
。
最早時Kubernetes採用了Linux環境變數解決這個問題,即每個Service都生成一些對應的Linux環境變數(ENV),並在每個Pod的容器啟動時自動注入這些環境變數。
考慮到通過環境變數獲取Service地址的方式仍然不太方便、不夠直觀,後來Kubernetes通過Add-On
增值包引入了DNS系統,把服務名作為DNS域名,這樣程式就可以直接使用服務名來建立通訊連線了。目前,Kubernetes上的大部分應用都已經採用了DNS這種新興的服務發現機制,後面會講解如何部署DNS系統。
外部系統訪問Service 的問題
為了更深入地理解和掌握Kubernetes,我們需要弄明白Kubernetes裡的3種IP,這3種IP分別如下。
- Node Ip: Node 的IP地址
- Pod IP: Pod 的IP地址
- Cluster IP: Service 的IP地址
首先,Node IP
是Kubernetes叢集中每個節點的物理網絡卡的IP地址,是一個真實存在的物理網路,所有屬於這個網路的伺服器都能通過這個網路直接通訊,不管其中是否有部分節點不屬於這個Kubernetes叢集。這也表明在Kubernetes叢集之外的節點訪問Kubernetes叢集之內的某個節點或者TCP/IP服務時,都必須通過Node IP
通訊。
Pod IP
是每個Pod的IP地址,它是Docker Engine
根據docker0網橋的IP地址段進行分配的,通常是一個虛擬的二層網路,前面說過,Kubernetes要求位於不同Node上的Pod都能夠彼此直接通訊,所以Kubernetes裡一個Pod裡的容器訪問另外一個Pod裡的容器時,就是通過Pod IP
所在的虛擬二層網路進行通訊的,而真實的TCP/IP流量是通過Node IP所在的物理網絡卡流出的。
Cluster IP
,它也是一種虛擬的IP
,但更像一個“偽造”的IP網路,原因有以下幾點
- ◎ Cluster IP僅僅作用於
Kubernetes Service
這個物件,並由Kubernetes管理和分配IP地址(來源於Cluster IP地址池)。 - ◎ Cluster IP無法被Ping,因為沒有一個“實體網路物件”來響應。
- ◎ Cluster IP只能結合
Service Port
組成一個具體的通訊埠,單獨的Cluster IP不具備TCP/IP通訊的基礎,並且它們屬於Kubernetes叢集這樣一個封閉的空間,叢集外的節點如果要訪問這個通訊埠,則需要做一些額外的工作。 - ◎ 在Kubernetes叢集內,
Node IP網
、Pod IP網
與Cluster IP網
之間的通訊,採用的是Kubernetes自己設計的一種程式設計方式的特殊路由規則,與我們熟知的IP路由有很大的不同。
根據上面的分析和總結,我們基本明白了:Service的Cluster IP屬於Kubernetes叢集內部的地址,無法在叢集外部直接使用這個地址。那麼矛盾來了:實際上在我們開發的業務系統中肯定多少有一部分服務是要提供給Kubernetes叢集外部的應用或者使用者來使用的,典型的就是web端的服務模組。
NodePort的實現方式是在Kubernetes叢集裡的每個Node上都為需要外部訪問的Service開啟一個對應的TCP監聽埠,外部系統只要用任意一個Node的IP地址+具體的NodePort埠號即可訪問此服務,在任意Node上執行netstat命令,就可以看到有NodePort埠被監聽:
但NodePort還沒有完全解決外部訪問Service的所有問題,比如負載均衡問題。假如在我們的叢集中有10個Node,則此時最好有一個負載均衡器,外部的請求只需訪問此負載均衡器的IP地址,由負載均衡器負責轉發流量到後面某個Node的NodePort上。
圖中的Load balancer
元件獨立於Kubernetes叢集
之外,通常是一個硬體的負載均衡器,或者是以軟體方式實現的,例如HAProxy
或者Nginx
。對於每個Service,我們通常需要配置一個對應的Load balancer例項來轉發流量到後端的Node上,這的確增加了工作量及出錯的概率。於是Kubernetes提供了自動化的解決方案,如果我們的叢集執行在谷歌的公有云GCE上,那麼只要把Service的type=NodePort
改為type=LoadBalancer
,Kubernetes就會自動建立一個對應的Load balancer例項並返回它的IP地址供外部客戶端使用。其他公有云提供商只要實現了支援此特性的驅動,則也可以達到上述目的。此外,裸機上的類似機制(Bare Metal Service Load Balancers)也在被開發。