k8s Service

CarloPan發表於2020-12-06

1.4.9 Service

1.概述
Service服務也是Kubernetes裡的核心資源物件之一,Kubernetes裡的每個Service其實就是我們經常提起的微服務架構中的一個微服務,之前講解Pod、RC等資源物件其實都是為講解KubernetesService做鋪墊的。圖1.12顯示了Pod、RC與Service的邏輯關係。
在這裡插入圖片描述
從圖1.12中可以看到,Kubernetes的Service定義了一個服務的訪問入口地址,前端的應用(Pod)通過這個入口地址訪問其背後的一組由Pod副本組成的叢集例項,Service與其後端Pod副本叢集之間則是通過Label Selector來實現無縫對接的。RC的作用實際上是保證Service的服務能力和服務質量始終符合預期標準。

通過分析、識別並建模系統中的所有服務為微服務——Kubernetes Service,我們的系統最終由多個提供不同業務能力而又彼此獨立的微服務單元組成的,服務之間通過TCP/IP進行通訊,從而形成了強大而又靈活的彈性網格,擁有強大的分散式能力、彈性擴充套件能力、容錯能力,程式架構也變得簡單和直觀許多,如圖1.13所示。
在這裡插入圖片描述
既然每個Pod都會被分配一個單獨的IP地址,而且每個Pod都提供了一個獨立的Endpoint(PodIP+ContainerPort)以被客戶端訪問,現在多個Pod副本組成了一個叢集來提供服務,那麼客戶端如何來訪問它們呢?一般的做法是部署一個負載均衡器(軟體或硬體),為這組Pod開啟一個對外的服務埠如8000埠,並且將這些Pod的Endpoint列表加入8000埠的轉發列表,客戶端就可以通過負載均衡器的對外IP地址+服務埠來訪問此服務。客戶端的請求最後會被轉發到哪個Pod,由負載均衡器的演算法所決定。

Kubernetes也遵循上述常規做法,執行在每個Node上的kube-proxy程式其實就是一個智慧的軟體負載均衡器,負責把對Service的請求轉發到後端的某個Pod例項上,並在內部實現服務的負載均衡與會話保持機制。但Kubernetes發明了一種很巧妙又影響深遠的設計**:Service沒有共用一個負載均衡器的IP地址,每個Service都被分配了一個全域性唯一的虛擬IP地址,這個虛擬IP被稱為Cluster IP**。這樣一來,每個服務就變成了具備唯一IP地址的通訊節點,服務呼叫就變成了最基礎的TCP網路通訊問題。

我們知道,Pod的Endpoint地址會隨著Pod的銷燬和重新建立而發生改變,因為新Pod的IP地址與之前舊Pod的不同。而**Service一旦被建立,Kubernetes就會自動為它分配一個可用的Cluster IP,而且在Service的整個生命週期內,它的Cluster IP不會發生改變。**於是,服務發現這個棘手的問題在Kubernetes的架構裡也得以輕鬆解決:只要用Service的Name與Service的Cluster IP地址做一個DNS域名對映即可完美解決問題。現在想想,這真是一個很棒的設計。

說了這麼久,下面動手建立一個Service來加深對它的理解。建立一個名為tomcat-service.yaml的定義檔案,內容如下:
在這裡插入圖片描述
上述內容定義了一個名為tomcat-service的Service,它的服務埠為8080,擁有“tier=frontend”這個Label的所有Pod例項都屬於它,執行下面的命令進行建立:
在這裡插入圖片描述

我們之前在tomcat-deployment.yaml裡定義的Tomcat的Pod剛好擁有這個標籤,所以剛才建立的tomcat-service已經對應一個Pod例項,執行下面的命令可以檢視tomcat-service的Endpoint列表,其中172.17.1.3是Pod的IP地址,埠8080是Container暴露的埠:
在這裡插入圖片描述
你可能有疑問:“說好的Service的Cluster IP呢?怎麼沒有看到?”執行下面的命令即可看到tomct-service被分配的Cluster IP及更多的資訊:

在這裡插入圖片描述
在這裡插入圖片描述
在spec.ports的定義中,targetPort屬性用來確定提供該服務的容器所暴露(EXPOSE)的埠號,即具體業務程式在容器內的targetPort上提供TCP/IP接入;port屬性則定義了Service的虛埠。前面定義Tomcat服務時沒有指定targetPort,則預設targetPort與port相同。

接下來看看Service的多埠問題。
很多服務都存在多個埠的問題,通常一個埠提供業務服務,另外一個埠提供管理服務,比如Mycat、Codis等常見中介軟體。Kubernetes Service支援多個Endpoint,在存在多個Endpoint的情況下,要求每個Endpoint都定義一個名稱來區分。下面是Tomcat多埠的Service定義樣例:
在這裡插入圖片描述
多埠為什麼需要給每個埠都命名呢?這就涉及Kubernetes的服務發現機制了,接下來進行講解。

2.Kubernetes的服務發現機制

任何分散式系統都會涉及“服務發現”這個基礎問題,大部分分散式系統都通過提供特定的API介面來實現服務發現功能,但這樣做會導致平臺的侵入性比較強,也增加了開發、測試的難度。Kubernetes則採用了直觀樸素的思路去解決這個棘手的問題。
首先,每個Kubernetes中的Service都有唯一的Cluster IP及唯一的名稱,而名稱是由開發者自己定義的,部署時也沒必要改變,所以完全可以被固定在配置中。接下來的問題就是如何通過Service的名稱找到對應的Cluster IP

最早時Kubernetes採用了Linux環境變數解決這個問題,即每個Service都生成一些對應的Linux環境變數(ENV),並在每個Pod的容器啟動時自動注入這些環境變數。以下是tomcat-service產生的環境變數條目:

在這裡插入圖片描述
在上述環境變數中,比較重要的是前3個環境變數。可以看到,每個Service的IP地址及埠都有標準的命名規範,遵循這個命名規範,就可以通過程式碼訪問系統環境變數來得到所需的資訊,實現服務呼叫。

考慮到通過環境變數獲取Service地址的方式仍然不太方便、不夠直觀,後來Kubernetes通過Add-On增值包引入了DNS系統,把服務名作為DNS域名,這樣程式就可以直接使用服務名來建立通訊連線了。目前,Kubernetes上的大部分應用都已經採用了DNS這種新興的服務發現機制,後面會講解如何部署DNS系統。

3.外部系統訪問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所在的物理網路卡流出的。

最後說說Service的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端的服務模組,比如上面的tomcat-service,那麼使用者怎麼訪問它?

採用NodePort是解決上述問題的最直接、有效的常見做法。以tomcat-service為例,在Service的定義裡做如下擴充套件即可(見程式碼中的粗體部分):

在這裡插入圖片描述
其中,nodePort:31002這個屬性表明手動指定tomcat-service的NodePort為31002,否則Kubernetes會自動分配一個可用的埠。接下來在瀏覽器裡訪問http://:31002/,就可以看到Tomcat的歡迎介面了,如圖1.14所示。
在這裡插入圖片描述
圖1.14 通過NodePort訪問Service

NodePort的實現方式是在Kubernetes叢集裡的每個Node上都為需要外部訪問的Service開啟一個對應的TCP監聽埠,外部系統只要用任意一個Node的IP地址+具體的NodePort埠號即可訪問此服務,在任意Node上執行netstat命令,就可以看到有NodePort埠被監聽:
在這裡插入圖片描述
但NodePort還沒有完全解決外部訪問Service的所有問題,比如負載均衡問題。假如在我們的叢集中有10個Node,則此時最好有一個負載均衡器,外部的請求只需訪問此負載均衡器的IP地址,由負載均衡器負責轉發流量到後面某個Node的NodePort上,如圖1.15所示。

在這裡插入圖片描述
圖1.15中的Load balancer元件獨立於Kubernetes叢集之外,通常是一個硬體的負載均衡器,或者是以軟體方式實現的,例如HAProxy或者Nginx。對於每個Service,我們通常需要配置一個對應的Load balancer例項來轉發流量到後端的Node上,這的確增加了工作量及出錯的概率。於是Kubernetes提供了自動化的解決方案,如果我們的叢集執行在谷歌的公有云GCE上,那麼只要把Service的type=NodePort改為type=LoadBalancer,Kubernetes就會自動建立一個對應的Loadbalancer例項並返回它的IP地址供外部客戶端使用。其他公有云提供商只要實現了支援此特性的驅動,則也可以達到上述目的。此外,裸機上的類似機制(Bare Metal Service Load Balancers)也在被開發。

相關文章