步入Service Mesh微服務(wù)架構(gòu)時代

今天要和大家分享的是關(guān)于新一代微服務(wù)架構(gòu)——Service Mesh!在微服務(wù)架構(gòu)盛行的今天,作為一名互聯(lián)網(wǎng)技術(shù)從業(yè)人員,對于微服務(wù)的概念相信大家都已經(jīng)耳熟能詳了!而至于像Spring Cloud這樣的微服務(wù)框架,因為大部分互聯(lián)網(wǎng)公司都在此基礎(chǔ)上構(gòu)建過第一代微服務(wù)體系,所以對于做Java 的同學(xué)來說,Spring Cloud微服務(wù)體系應(yīng)該是非常熟悉了!

這里并不是說其他語言棧就沒有構(gòu)建微服務(wù)體系的框架,例如:Go語言也有像Go-Micro這樣的微服務(wù)框架,只不過目前除了像頭條這樣重度使用Go語言的公司外,其他絕大多數(shù)互聯(lián)網(wǎng)公司的服務(wù)端語言依然還是Java的天下!所以對于目前大部分已經(jīng)或打算采用微服務(wù)架構(gòu)的公司來說,Spring Cloud框架依然是它們的首選!但如果我說,這套體系發(fā)展到今天已經(jīng)快過時了,你會不會覺得胡扯呢?因為畢竟咱們現(xiàn)在微服務(wù)Spring Cloud這一套還沒用全??!

難道像Spring Cloud GateWay、Zuul、Eureka、Consul、Nacos、Feign/Ribbon、Hystrix、Sentinel、Spring Cloud Config、Apollo...,這些涵蓋了微服務(wù)體系——服務(wù)注冊與發(fā)現(xiàn)、限流、熔斷降級、負載均衡、服務(wù)配置等服務(wù)治理各個方面的牛逼開發(fā)框架或服務(wù)組件們都快要過時了嗎?

雖然這很難讓人接受,畢竟這些技術(shù)才剛剛捂熱!但在下一代微服務(wù)架構(gòu)Service Mesh 面前,它們中的絕大部分組件確實是快要過時了,倒不是說這些開源組件在技術(shù)上不牛逼或者沒有深入學(xué)習研究的價值了,而是它們所面向的微服務(wù)體系結(jié)構(gòu)在設(shè)計理念上與Service Mesh已經(jīng)存在代差,這種差距夸張點說就像殲-20與殲-10的差別。這聽起來可能有點聳人聽聞,但從目前微服務(wù)技術(shù)發(fā)展趨勢和實踐上看,這就是歷史潮流!接下來我將從理論和實踐層面對此進行分析和演示!

為什么要進入Service Mesh時代

前面我略微夸張的說到,以Spring Cloud為代表的微服務(wù)體系相比Service Mesh而言已經(jīng)存在技術(shù)代差,那憑什么這么說呢?接下來,我們回顧下使用Spring Cloud構(gòu)建微服務(wù)體系的大致技術(shù)流程!

要構(gòu)建微服務(wù)體系,首先我們需要獨立部署一款實現(xiàn)服務(wù)注冊/發(fā)現(xiàn)功能的組件服務(wù),目前可供選擇的主流方案一般有Eureka、Consul、Nacos等,搞定服務(wù)注冊/發(fā)現(xiàn)后,我們編寫一個Java微服務(wù),此時為了將該服務(wù)注冊到服務(wù)注冊中心,一般會引入Spring Cloud提供的支持對應(yīng)注冊中心接入的SDK,并在應(yīng)用入口類中通過@EnableDiscoveryClient注解的方式標注,之后SDK中的邏輯就會在應(yīng)用啟動時執(zhí)行服務(wù)注冊動作,并提供給注冊中心相應(yīng)地探測接口,以此實現(xiàn)微服務(wù)與服務(wù)注冊中心之間的連接。以此類推,我們可以通過這種方式將一組微服務(wù)都注冊到服務(wù)注冊中心!

而如果服務(wù)之間要互相調(diào)用怎么辦呢?一般我們會通過編寫FeignClient接口來實現(xiàn)微服務(wù)之間的調(diào)用,而其底層的邏輯則是通過Feign所集成的Ribbon組件去注冊中心中獲取目標服務(wù)的服務(wù)地址列表,之后Ribbon根據(jù)服務(wù)地址列表進行負載均衡調(diào)用。至于服務(wù)與注冊中心之間如何保證連接有效性,則依賴于服務(wù)注冊中心與其SDK之間的協(xié)作機制。

而高級一點,服務(wù)之間的調(diào)用除了實現(xiàn)負載均衡,還要實現(xiàn)熔斷限流、那么此時可以通過部署服務(wù)網(wǎng)關(guān)組件(例如Zuul/Spring Cloud GateWay)來實現(xiàn)微服務(wù)入口的熔斷限流、內(nèi)部服務(wù)之間的限流熔斷則通過集成Hystrix或Sentinel組件,以客戶端本地配置或遠程配置中心的方式來實現(xiàn)。

上述過程基本就是我們使用Spring Cloud構(gòu)建微服務(wù)體系的大致過程了!如果仔細思考下這個過程,我們會發(fā)現(xiàn)在該微服務(wù)體系的構(gòu)造過程中,與服務(wù)治理相關(guān)的大部分邏輯都是以SDK的方式耦合在具體的微服務(wù)應(yīng)用之中!服務(wù)注冊需要引入SDK、服務(wù)調(diào)用需要引入SDK、服務(wù)熔斷限流也需要SDK;除此之外,為了保證這套體系的正常運行,我們還需要額外維護服務(wù)注冊中心、服務(wù)網(wǎng)關(guān)這樣的基礎(chǔ)服務(wù)。這樣的結(jié)構(gòu)會導(dǎo)致什么弊端呢?具體有以下幾點:

1、框架/SDK太多,后續(xù)升級維護困難

在這套體系中,與服務(wù)治理相關(guān)的邏輯都是以SDK代碼依賴的方式嵌入在微服務(wù)之中,如果某天我們想升級下服務(wù)注冊中心的SDK版本,或者熔斷限流組件Hystrix或Sentinel的版本,那么需要升級改造的微服務(wù)可能會是成百上千,且由于這些組件都與業(yè)務(wù)應(yīng)用綁定在一起,在升級的過程中會不會影響業(yè)務(wù)穩(wěn)定,這都是需要謹慎對待的事情,所以對SDK的升級難度可想而知的!

2、多語言微服務(wù)SDK維護成本高

試想下如果構(gòu)建的微服務(wù)體系,也要支持像Go、Python或者其他語言編寫的微服務(wù)的話,那么上述這些微服務(wù)治理相關(guān)的SDK是不是得單獨再維護幾套呢?所以在這種體系結(jié)構(gòu)中,對多語言微服務(wù)的支持就成了一個問題!

3、服務(wù)治理策略難以統(tǒng)一控制

基于該套體系構(gòu)建的微服務(wù)體系,在對像熔斷、限流、負載均衡等服務(wù)治理相關(guān)的策略管理上,都是比較分散的,可能有人會寫到自己的本地配置文件,有人會硬編碼到代碼邏輯中,也可能有人會將其配置到遠程配置中心,總之對于服務(wù)治理策略邏輯都是由對應(yīng)的開發(fā)人員自己控制,這樣就很難形成統(tǒng)一的控制體系!

4、服務(wù)治理邏輯嵌入業(yè)務(wù)應(yīng)用,占有業(yè)務(wù)服務(wù)資源

在這套微服務(wù)體系中,服務(wù)治理相關(guān)的邏輯都是在微服務(wù)應(yīng)用進程中寄生運行的,這多少會占有寶貴的業(yè)務(wù)服務(wù)器資源,影響應(yīng)用性能的發(fā)揮!

5、額外的服務(wù)治理組件的維護成本

無論是服務(wù)注冊中心、還是服務(wù)網(wǎng)關(guān),這些除了微服務(wù)應(yīng)用本身之外服務(wù)治理組件,都需要我們以中間件基礎(chǔ)服務(wù)的方式進行維護,需要額外的人力、額外的服務(wù)器成本!

以上就是以Spring Cloud為代表的傳統(tǒng)微服務(wù)體系的弊端,如果我說在Service Mesh體系下,以上幾點都不再是問題,甚至都不需要研發(fā)人員再進行任何關(guān)注!我們只需要寫一個普通的Spring Boot服務(wù),也不需要引入服務(wù)注冊SDK、熔斷限流SDK組件,總之你寫一個普普通通的服務(wù),就能實現(xiàn)像之前Spring Cloud微服務(wù)體系所能支持的大部分服務(wù)治理功能,你會相信嗎?你會不會覺得這跟之前寫單體應(yīng)用沒啥差別了呢?

不管你怎么想,這些就是Service Mesh要干的事情!Service Mesh的目標就是要將微服務(wù)治理體系下沉為一套與業(yè)務(wù)無關(guān)的基礎(chǔ)設(shè)施。從這個角度看,如果咱們不認真學(xué)習下Service Mesh,那么以后將變得越來越低智,因為Spring Cloud好歹還能讓我們感知下微服務(wù)的存在,而在Service Mesh中,微服務(wù)治理體系作為基礎(chǔ)設(shè)施的一部分,對普通研發(fā)人員將越來越透明!

Service Mesh的解決方案是什么

在前面我們說到,Service Mesh的目標是要將微服務(wù)治理體系下沉為與業(yè)務(wù)無關(guān)的基礎(chǔ)設(shè)施。這句話怎么理解呢?實際上Service Mesh微服務(wù)治理技術(shù)的誕生也不是憑空產(chǎn)生的,而是在以Kubernetes為代表的容器編排技術(shù)逐步成為軟件運行主流基礎(chǔ)環(huán)境的背景下,以及以Spring Cloud框架為代表的傳統(tǒng)微服務(wù)技術(shù)體系弊端逐步顯現(xiàn)的情況下技術(shù)自然迭代發(fā)展的結(jié)果。總之,就是有點萬事具備,只欠東風的感覺!

所以,我們看到目前落地的Service Mesh方案中大多都是與Kubernetes深度結(jié)合的方案,例如最受矚目的Istio!接下來我們具體看看在Service Mesh中微服務(wù)治理的核心邏輯是怎么實現(xiàn)的(以Istio+Envoy為例)!

要理解在Service Mesh中的微服務(wù)治理邏輯的具體實現(xiàn),就不得不上一張關(guān)于服務(wù)網(wǎng)格概念很經(jīng)典但剛開始看著又很難理解的圖,如下:

image

如果你之前大致了解過Service Mesh的概念,那么這張圖相信你一定見過。其中綠色的正方形表示正常部署的微服務(wù),而藍色的正方形表示一個網(wǎng)絡(luò)代理,也就是大家通常所說的SideCar。在Service Mesh架構(gòu)下,每部署一個微服務(wù),都需要部署一個與之相對應(yīng)的代理服務(wù),所有與微服務(wù)本身的交互都通過SideCar代理,而SideCar之間會形成一張形似網(wǎng)格的交互鏈路,這就是服務(wù)網(wǎng)格名稱的來由!

在Service Mesh中,當我們將一個服務(wù)部署在Kubernetes之后,安裝在Kubernetes中的Service Mesh組件(例如Istio)就會自動在該微服務(wù)的同一個Pod之中啟動一個與之對應(yīng)的代理進程(例如istio-proxy),這個保姆式的代理進程會代替微服務(wù)本身去實現(xiàn)原先在Spring Cloud體系中需要微服務(wù)自身完成的服務(wù)注冊、負載均衡、熔斷限流等微服務(wù)治理功能。并且,這些代理進程并不是孤軍奮戰(zhàn),而是會通過像xDS協(xié)議(Service Mesh中數(shù)據(jù)面與控制面通信的通用協(xié)議)與Service Mesh控制組件保持連接。

這也就引出了Service Mesh架構(gòu)中關(guān)鍵的兩個概念:控制面與數(shù)據(jù)面。前面我們所示的Sidecar(例如istio-proxy,實際上是envoy)就是數(shù)據(jù)面,與微服務(wù)治理邏輯相關(guān)的信息都存儲在數(shù)據(jù)面中,而控制面則是Service Mesh的中心控制組件(例如Istio中的Pilot組件),控制面可以通過xDS協(xié)議(具體又分為LDS、CDS...)向數(shù)據(jù)面下發(fā)各種服務(wù)治理相關(guān)的規(guī)則,例如限流規(guī)則、路由規(guī)則、服務(wù)節(jié)點更新信息等等。

這種設(shè)計方式就是Service Mesh最核心的設(shè)計邏輯——通過Sidecar的方式代理微服務(wù)進行服務(wù)治理邏輯(數(shù)據(jù)面),通過控制面感知外界環(huán)境的變化并通過xDS協(xié)議支持各種微服務(wù)治理策略規(guī)則的集中管理和下發(fā),而這里的控制面和數(shù)據(jù)面都會被融合進像Kubernetes這樣的基礎(chǔ)架構(gòu)環(huán)境中,對于普通微服務(wù)的開發(fā),研發(fā)人員要做的只是將一個應(yīng)用以編排的方式部署進k8s集群即可!而所有與微服務(wù)治理相關(guān)的邏輯都由代理數(shù)據(jù)面與控制面協(xié)作完成。

這里我們以Service Mesh最著名的開源方案Istio的架構(gòu)圖來解釋上面所說的邏輯,具體如下:

image

其中服務(wù)注冊發(fā)現(xiàn)可以直接利用Kubernetes的內(nèi)部發(fā)現(xiàn)機制,通過監(jiān)聽Kubernetes Pod的變化來實現(xiàn),具體示意圖如下:

image

而微服務(wù)治理相關(guān)的邏輯,以Istio為例,流程大致是這樣的:

image

管理員通過Pilot配置治理規(guī)則,并通過xDS協(xié)議向Envoy下發(fā)治理規(guī)則,而Envoy從Pilot獲取微服務(wù)治理規(guī)則后,就可以在流量訪問的時候按照規(guī)則執(zhí)行相應(yīng)的限流、路由等微服務(wù)治理邏輯了!

Istio+Envoy的Service Mesh架構(gòu)玩法

前面我們從原理層面大致介紹了Service Mesh微服務(wù)架構(gòu)的核心概念及流程邏輯,如果你玩過Service Mesh架構(gòu),那么理解起來是很容易的!但是如果沒有具體實踐過,特別是如果對Kubernetes沒有基本的了解,那么上面的概念可能也并不好理解,例如你可能會竭力地想象"我到底應(yīng)該怎么部署那個所謂的Sidecar代理?","在Service Mesh架構(gòu)下怎么去開發(fā)服務(wù)?"等這樣的問題!

而如果我寫到這里不寫了,那么這篇文章也只是與大部分介紹過Service Mesh的文章一樣,要么就是各種高大上不接地氣的原理介紹,要么就是翻過來覆過去的概念介紹,或者好不容易找到一篇帶有示例的文章,但大多數(shù)也是基于Istio官方Demo的演示!

而對于開發(fā)過Spring Cloud微服務(wù)應(yīng)用的同學(xué)來說,其實并不是很好理解!所以接下來的玩法實踐,我將以最接近實際開發(fā)場景的方式、站在一個曾經(jīng)使用Spring Cloud框架開發(fā)過微服務(wù)的研發(fā)人員角度,來整體介紹如何用平常工作中所使用的Java流行框架(如Spring Boot)來開發(fā)基于Service Mesh體系的微服務(wù)應(yīng)用!

具體過程及步驟如下:

01 k8s環(huán)境準備及Istio安裝

要玩轉(zhuǎn)Service Mesh微服務(wù)架構(gòu),基本的前提是需要一個功能完整的Kubernetes環(huán)境,這里我所使用的k8s環(huán)境是在開發(fā)本上安裝一個Linux虛擬機并在其之上部署一個只有Master節(jié)點的Kubernetes單節(jié)點集群,另外由于Istio對Kubernetes的版本是有要求的,這里所使用的k8s版本是v1.18.6。

這里我先假設(shè)你已經(jīng)搞定了Kubernetes環(huán)境,接下來開始安裝Istio,選擇的版本是istio-1.8.4,具體步驟如下:

1)、下載Istio發(fā)布包

由于官方提供的下載腳本執(zhí)行速度比較慢,可以直接在github找到相應(yīng)的istio發(fā)布版本后通過wget命令將其下載至主機指定目錄(能正常連接k8s集群):

wget https://github.com/istio/istio/releases/download/1.8.4/istio-1.8.4-linux-amd64.tar.gz

下載成功后,解壓安裝包:

tar -zxvf istio-1.8.4-linux-amd64.tar.gz

進入解壓安裝包目錄:

cd istio-1.8.4/

2)、將istioctl客戶端添加到系統(tǒng)可執(zhí)行路徑

在具體安裝istio時需要使用istioctl命令,因此需要先將該命令加入系統(tǒng)可執(zhí)行路徑,命令如下:

export PATH=$PWD/bin:$PATH

3)、執(zhí)行安裝istio命令

這里使用istioctl命令執(zhí)行安裝命令,具體如下:

istioctl install --set profile=demo

這里"--set profile=demo"表示安裝一個istio測試環(huán)境!成功安裝后的信息輸出如下:

Detected that your cluster does not support third party JWT authentication. Falling back to less secure first party JWT. See https://istio.io/v1.8/docs/ops/best-practices/security/#configure-third-party-service-account-tokens for details.
This will install the Istio 1.8.4 demo profile with ["Istio core" "Istiod" "Ingress gateways" "Egress gateways"] components into the cluster. Proceed? (y/N) Y
? Istio core installed                                                                                                                                                                                      
? Istiod installed                                                                                                                                                                                          
? Ingress gateways installed                                                                                                                                                                                
? Egress gateways installed                                                                                                                                                                                 
? Installation complete  

如果安裝成功,則可以通過kubectl命令查看istio相關(guān)組件是否已經(jīng)安裝在Kubernetes環(huán)境之中,命令如下:

kubectl get svc -n istio-system
NAME                   TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)                                                                      AGE
istio-egressgateway    ClusterIP      10.101.134.226   <none>        80/TCP,443/TCP,15443/TCP                                                     8m12s
istio-ingressgateway   LoadBalancer   10.96.167.106    <pending>     15021:31076/TCP,80:31032/TCP,443:31438/TCP,31400:32751/TCP,15443:31411/TCP   8m11s
istiod                 ClusterIP      10.102.112.111   <none>        15010/TCP,15012/TCP,443/TCP,15014/TCP  

此時可以看到istio的核心組件istiod,以及入口網(wǎng)關(guān)ingressgateway、出口網(wǎng)關(guān)egressgateway已經(jīng)成功以Service資源的方式運行在了Kuberntes集群之中!

4)、k8s默認命名空間開啟自動注入Envoy Sidecar

這是一個關(guān)鍵的步驟,如果我們的微服務(wù)應(yīng)用未來是默認部署在k8s的default命名空間,那么在安裝istio是需要開啟該空間的Sidecar自動注入功能。這是我們前面提到每啟動一個微服務(wù)應(yīng)用,k8s就會默認在相同的Pod中自動啟動一個代理進程的關(guān)鍵設(shè)置!

具體命令如下:

$ kubectl label namespace default istio-injection=enabled
namespace/default labeled

5)、Istio可觀測性部署

Kiali是一個基于服務(wù)網(wǎng)格的Istio管理控制臺,它提供了一些數(shù)據(jù)儀表盤和可觀測能力,同時也可以讓我們?nèi)ゲ僮骶W(wǎng)格的配置。使用如下方式快速部署一個用于演示的Kiali,命令如下:

$ kubectl apply -f samples/addons
serviceaccount/grafana created
configmap/grafana created
service/grafana created
deployment.apps/grafana created
configmap/istio-grafana-dashboards created
configmap/istio-services-grafana-dashboards created
deployment.apps/jaeger created
service/tracing created
service/zipkin created
service/jaeger-collector created
Warning: apiextensions.k8s.io/v1beta1 CustomResourceDefinition is deprecated in v1.16+, unavailable in v1.22+; use apiextensions.k8s.io/v1 CustomResourceDefinition
customresourcedefinition.apiextensions.k8s.io/monitoringdashboards.monitoring.kiali.io created
serviceaccount/kiali created
configmap/kiali created
clusterrole.rbac.authorization.k8s.io/kiali-viewer created
clusterrole.rbac.authorization.k8s.io/kiali created
clusterrolebinding.rbac.authorization.k8s.io/kiali created
role.rbac.authorization.k8s.io/kiali-controlplane created
rolebinding.rbac.authorization.k8s.io/kiali-controlplane created
service/kiali created
deployment.apps/kiali created
serviceaccount/prometheus created
configmap/prometheus created
clusterrole.rbac.authorization.k8s.io/prometheus created
clusterrolebinding.rbac.authorization.k8s.io/prometheus created
service/prometheus created
deployment.apps/prometheus created
....

其中具體會安裝部署Promethues、Grafana、Zipkin等指標及鏈路采集服務(wù)!因為安裝的組件比較多,也比較耗費資源,如果集群資源不是很充足,可能會出現(xiàn)啟動比較慢的情況。如果正常部署成功,可以查看Pod狀態(tài),命令如下:

# kubectl get pod -n istio-system -o wide
NAME                                    READY   STATUS    RESTARTS   AGE   IP           NODE         NOMINATED NODE   READINESS GATES
grafana-94f5bf75b-4mkcn                 1/1     Running   1          30h   10.32.0.11   kubernetes   <none>           <none>
istio-egressgateway-7f79bc776-w6rqn     1/1     Running   3          30h   10.32.0.3    kubernetes   <none>           <none>
istio-ingressgateway-74ccb8977c-gnhbb   1/1     Running   2          30h   10.32.0.8    kubernetes   <none>           <none>
istiod-5d4dbbb8fc-lhgsj                 1/1     Running   2          30h   10.32.0.5    kubernetes   <none>           <none>
jaeger-5c7675974-4ch8v                  1/1     Running   3          30h   10.32.0.13   kubernetes   <none>           <none>
kiali-667b888c56-8xm6r                  1/1     Running   3          30h   10.32.0.6    kubernetes   <none>           <none>
prometheus-7d76687994-bhsmj             2/2     Running   7          30h   10.32.0.14   kubernetes   <none>           <none>

由于前面安裝istio時,我們并沒有在istio-system空間開啟自動注入Sidecar(其label istio-injection=disabled),這里為了在k8s集群之外正常訪問Kiali、Prometheus、Granfana、Tracing的控制面板(它們共同組成了Service Mesh的可觀測體系),可以通過nodePort的方式對外暴露端口。

Kiali的NodePort訪問操作方式:

將部署的Kiali的Service文件導(dǎo)出到主機的某個目錄,例如:

kubectl get svc -n istio-system kiali -o yaml > kiali-nodeport.yaml

之后編輯導(dǎo)出的文件,刪除metadata下的annotation、resourceVersion、selfFlink、uid等信息;并修改下spec下的type類型值,將ClusterIP修改為NodePort,并指定nodePort端口信息;同時刪除status狀態(tài)字段即可。具體如下:

spec:
  ...
  ports:
  - name: http
    nodePort: 31001
   ...
  type: NodePort

編輯完成后執(zhí)行執(zhí)行命令:

kubectl apply -f kiali-nodeport.yaml

之后查看服務(wù)端口,命令如下:

kubectl get svc -n istio-system kiali
NAME    TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)                          AGE
kiali   NodePort   10.100.214.196   <none>        20001:31001/TCP,9090:30995/TCP   41h

此時通過k8s的集群外部IP+31001端口就能訪問Kiali控制面板了,效果如下圖所示:

image

與Kiali的操作方式類似,我們也可以通過獲取修改部署的Promethues、Granfana、Tracing、Zipkin等服務(wù)的發(fā)布文件,通過設(shè)置NodePort端口,從而在k8s集群外部進行可觀測界面的訪問!例如:

#Prometheus
kubectl get svc -n istio-system prometheus -o  yaml > prometheus-nodeport.yaml
kubectl apply -f prometheus-nodeport.yaml 

#Granfana
kubectl get svc -n istio-system grafana -o yaml > grafana-nodeport.yaml
kubectl apply -f grafana-nodeport.yaml

#Jaeger(分布式鏈路)
kubectl get svc -n istio-system tracing -o yaml > tracing-nodeport.yaml
kubectl apply -f tracing-nodeport.yaml
...

其中Granfana的訪問效果示意圖如下:

image

02 Spring Boot微服務(wù)開發(fā)

經(jīng)過前面的步驟,我們已經(jīng)從基礎(chǔ)架構(gòu)環(huán)境的角度完成了基于Istio的Service Mesh微服務(wù)體系的構(gòu)建!如果類比之前基于Spring Cloud框架的微服務(wù)開發(fā)體驗,那么在Istio體系下應(yīng)該如何進行微服務(wù)應(yīng)用的開發(fā)呢?

接下來我們通過一個實際的應(yīng)用示例來演示,如何開發(fā)基于Istio的Service Mesh微服務(wù)應(yīng)用,服務(wù)鏈路如下:

image

如上所示鏈路,具體說明如下:

1)、為了完整演示在Service Mesh架構(gòu)下微服務(wù)的研發(fā)過程,這里我們定義3個微服務(wù),其中micro-api服務(wù)是面向外部客戶端接入的Api服務(wù)提供Http協(xié)議訪問;

2)、而micro-api與micro-order之間則基于微服務(wù)的注冊發(fā)現(xiàn)機制進行內(nèi)部服務(wù)調(diào)用,具體采用Http協(xié)議;

3)、而micro-order與micro-pay之間也基于微服務(wù)注冊發(fā)現(xiàn)機制進行內(nèi)部微服務(wù)調(diào)用,為了演示更多的研發(fā)場景,這兩個微服務(wù)之間的通信我們采用Grpc協(xié)議;

規(guī)劃好了微服務(wù)應(yīng)用架構(gòu),接下來就可以具體開發(fā)了!具體的服務(wù)代碼層面的構(gòu)建,這里并不需要做任何微服務(wù)框架的引入,你只需要通過Spring Boot構(gòu)建幾個基本的Spring Boot應(yīng)用即可,不需要引入任何服務(wù)治理相關(guān)的組件,只是一個簡單且單純的Spring Boot應(yīng)用,不需要連接注冊中心,也不需要引入什么OpenFeign、Hystrix、Sentinel之類的組件。

具體的代碼結(jié)構(gòu)如下圖所示:

image

可以看到應(yīng)用的入口類中已經(jīng)沒有服務(wù)發(fā)現(xiàn)之類的注解!接下來我們講重點

首先,在之前基于Spring Cloud的微服務(wù)調(diào)用中,如果通過Http協(xié)議進行服務(wù)調(diào)用,一般我們是通過引入OpenFeign來實行,服務(wù)方提供一個FeignClient接口定義,調(diào)用方代碼直接引入即可,而具體的運行邏輯,則是OpenFeign中集成的Ribbon組件會從注冊中心獲取目標服務(wù)地址列表,然后進行負載均衡調(diào)用。

但在Service Mesh架構(gòu)下負載均衡及服務(wù)發(fā)現(xiàn)的邏輯已經(jīng)由Istio中的Sidecar幫我們干了,所以在這里就不能還像以前一樣引入OpenFeign了!那么怎么辦呢?為了延續(xù)之前的編程風格及服務(wù)通信代碼的簡易性,這里我們需要自己定制一個類似于OpenFeign的框架,可以基于OpenFeign的源碼進行改造,但是要去掉其中關(guān)于服務(wù)負載均衡、熔斷限流等服務(wù)治理相關(guān)的邏輯,讓它變成一個只是簡單進行Http服務(wù)調(diào)用的框架。

目前市面上并沒有這樣一個官方的適配框架,所以一些落地Service Mesh架構(gòu)的公司為了兼容Spring Cloud微服務(wù)體系的遷移,也是自己單獨改造和封裝的,這里我從github上找了一個個人改造的代碼并進行了適配修改,測試是可以的!其具備的能力說明如下:

1、支持在istio服務(wù)網(wǎng)格體系下,完成服務(wù)間的快速調(diào)用(體驗和原先Spring Cloud Feign類似);

2、支持多環(huán)境配置,例如本地環(huán)境微服務(wù)的調(diào)用地址可配置為本地,其他環(huán)境默認為Kubernetes集群中的服務(wù);

3、支持鏈路追蹤,默認透傳如下Header,可以自動支持jaeger、zipkin鏈路追蹤等,如下:

`"x-request-id", "x-b3-traceid", "x-b3-spanid", "x-b3-sampled", "x-b3-flags", "x-b3-parentspanid","x-ot-span-context", "x-datadog-trace-id", "x-datadog-parent-id", "x-datadog-sampled", "end-user", "user-agent"`

最后的實際編程風格是這樣的:

@FakeClient(name = "micro-order")
@RequestMapping("/order")
public interface OrderServiceClient {
    /**
     * 訂單創(chuàng)建
     */
    @PostMapping("/create")
    ResponseResult<CreateOrderBO> create(@RequestBody CreateOrderDTO createOrderDTO);
}

這里是micro-order微服務(wù)給micro-api所提供的接口調(diào)用代碼,micro-api服務(wù)引入調(diào)用即可,從編程風格上與之前Spring Cloud微服務(wù)的開發(fā)方式十分類似。只不過到這里為止,你并沒有能看到任何與服務(wù)注冊發(fā)現(xiàn)相關(guān)的邏輯!

其次,服務(wù)治理的核心邏輯都是由Istio及Sidecar代理完成的,完成應(yīng)用開發(fā)后,只需要編寫k8s部署文件將服務(wù)部署進安裝了Istio環(huán)境的Kubernetes集群即可,而將編寫的Java服務(wù)部署到k8s集群的流程,涉及"Docker鏡像打包->鏡像倉庫發(fā)布->k8s部署拉取鏡像"這一套CI/CD操作流程

接下來重點演示micro-api及micro-order的k8s發(fā)布文件,看看它們有什么特別之處:

micro-order服務(wù)k8s發(fā)布文件(micro-order.yaml):

apiVersion: v1
kind: Service
metadata:
  name: micro-order
  labels:
    app: micro-order
    service: micro-order
spec:
  type: ClusterIP
  ports:
    - name: http
      port: 80
      targetPort: 9091
  selector:
    app: micro-order

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: micro-order-v1
  labels:
    app: micro-order
    version: v1
spec:
  replicas: 2
  selector:
    matchLabels:
      app: micro-order
      version: v1
  template:
    metadata:
      labels:
        app: micro-order
        version: v1
    spec:
      containers:
        - name: micro-order
          image: 10.211.55.2:8080/micro-service/micro-order:1.0-SNAPSHOT
          imagePullPolicy: Always
          tty: true
          ports:
            - name: http
              protocol: TCP
              containerPort: 19091

如上所示,這是micro-order服務(wù)的k8s部署文件,就是正常定義了該應(yīng)用的Service資源及Deployment編排資源;為了后面演示服務(wù)的負載均衡調(diào)用,這里我特地將該應(yīng)用部署成了2個副本!

接下來繼續(xù)看看調(diào)用方micro-api服務(wù)的k8s發(fā)布文件(micro-api.yaml):

apiVersion: v1
kind: Service
metadata:
  name: micro-api
spec:
  type: ClusterIP
  ports:
    - name: http
      port: 19090
      targetPort: 9090
  selector:
    app: micro-api

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: micro-api
spec:
  replicas: 1
  selector:
    matchLabels:
      app: micro-api
  template:
    metadata:
      labels:
        app: micro-api
    spec:
      containers:
        - name: micro-api
          image: 10.211.55.2:8080/micro-service/micro-api:1.0-SNAPSHOT
          imagePullPolicy: Always
          tty: true
          ports:
            - name: http
              protocol: TCP
              containerPort: 19090

與micro-order一樣也只是定義了該應(yīng)用的k8s正常發(fā)布資源,到這里也并沒有體現(xiàn)出micro-api是怎么調(diào)用micro-order服務(wù)的!接下來我們通過這個文件將服務(wù)發(fā)布至k8s集群中(注意,是開啟了Sidecar自動注入的默認命名空間)!

部署成功后,查看Pods信息,具體如下:

# kubectl get pods 
NAME                                      READY   STATUS    RESTARTS   AGE
micro-api-6455654996-57t4z                2/2     Running   4          28h
micro-order-v1-84ddc57444-dng2k           2/2     Running   3          23h
micro-order-v1-84ddc57444-zpmjl           2/2     Running   4          28h

如上所示,可以看到一個micro-api Pod兩個micro-order Pod都已經(jīng)正常運行起來了!但不知你發(fā)現(xiàn)沒有,每個Pod中READY字段顯示的都是2/2,這意味著每個Pod中都啟動了兩個容器,一個是微服務(wù)應(yīng)用本身,另外一個就是自動注入啟動的Sidecar代理進程。為了更深理解這個邏輯,我們可以通過命令查看下Pod的描述信息:

# kubectl describe pod micro-api-6455654996-57t4z

Name:         micro-api-6455654996-57t4z
...

IP:           10.32.0.10
IPs:
  IP:           10.32.0.10
Controlled By:  ReplicaSet/micro-api-6455654996
Init Containers:
  istio-init:
    Container ID:  docker://eb0298bc8456f5f1336dfe2e8baab6035fccce898955469353da445aceab15cb
    Image:         docker.io/istio/proxyv2:1.8.4
    Image ID:      docker-pullable://istio/proxyv2@sha256:6a4ac67c1a74f95d3b307a77ad87e3abb4fcd64ddffe707f99a4458f39d9ce85
    ....

Containers:
  micro-api:
    Container ID:   docker://ebb45c5fa826f78c354877fc0a4c07d6b2fae4c6304e15729268b1cc6a69abca
    Image:          10.211.55.2:8080/micro-service/micro-api:1.0-SNAPSHOT
    Image ID:       docker-pullable://10.211.55.2:8080/micro-service/micro-api@sha256:f303016a604f30b99df738cbb61f89ffc166ba96d59785172c7b769c1c75a18d

    此處省略...

  istio-proxy:
    Container ID:  docker://bba9dc648b9e1a058e9c14b0635e0872079ed3fe7d55e34ac90ae03c5e5f3a66
    Image:         docker.io/istio/proxyv2:1.8.4
    Image ID:      docker-pullable://istio/proxyv2@sha256:6a4ac67c1a74f95d3b307a77ad87e3abb4fcd64ddffe707f99a4458f39d9ce85

    此處省略...

可以看到在開啟了Sidecar自動注入的命名空間中,每啟動一個Pod,Istio都會將Sidecar代理以初始化容器(Init Containers)的方式,自動啟動一個對應(yīng)地istio-proxy代理進程(Envoy),到這里你應(yīng)該真實感到到Sidecar到底是一個什么樣的存在了吧!

03 部署Istio微服務(wù)網(wǎng)關(guān)

前面的步驟中我們已經(jīng)完成了微服務(wù)應(yīng)用的開發(fā),并且也已經(jīng)將其部署到了k8s集群,Sidecar代理也正常啟動了,那么怎么訪問呢?

一般來說如果要訪問Kubernetes集群中的Service,可以通過NodePort端口映射及Ingress的方式向k8s集群外暴露訪問端口。但在Istio中采用了一種新的模型——Istio Gateway來代替Kubernetes中的Ingress資源類型。在Istio微服務(wù)體系中,所有外部流量的訪問都應(yīng)該通過Gateway進來,并由Gateway轉(zhuǎn)發(fā)到對應(yīng)的內(nèi)部微服務(wù)!

而基于統(tǒng)一的控制面配置,Istio也可以集中管理Gateway網(wǎng)關(guān)的流量訪問規(guī)則,實現(xiàn)對外部流量訪問的整體管控!在前面部署Istio時,"istio-ingressgateway"入口流量網(wǎng)關(guān)已經(jīng)作為Istio體系的一部分運行在k8s集群中了,如下:

# kubectl get svc -n istio-system|grep istio-ingressgateway
istio-ingressgateway   LoadBalancer   10.100.69.24     <pending>     15021:31158/TCP,80:32277/TCP,443:30508/TCP,31400:30905/TCP,15443:30595/TCP   46h

接下來我們需要設(shè)置通過該網(wǎng)關(guān)訪問micro-api微服務(wù)的邏輯,編寫網(wǎng)關(guān)部署文件(micro-gateway.yaml):

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: micro-gateway
spec:
  selector:
    istio: ingressgateway
  servers:
    - port:
        number: 80
        name: http
        protocol: HTTP
      hosts:
        - "*"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: micro-gateway
spec:
  hosts:
    - "*"
  gateways:
    - micro-gateway
  http:
    - match:
        - uri:
            exact: /api/order/create
      route:
        - destination:
            host: micro-api
            port:
              number: 19090

如上所示,該部署文件中定義了路由匹配規(guī)則,凡事訪問/api/order/create地址的請求都會被轉(zhuǎn)發(fā)到micro-api服務(wù)的19090端口!

配置完上述網(wǎng)關(guān)路由轉(zhuǎn)發(fā)規(guī)則后,我們嘗試通過訪問istio-ingressgateway來到達訪問微服務(wù)接口的效果,具體鏈路是:"外部調(diào)用->istio-ingressgateway->micro-api->micro-order"。

但是對于istio-ingressgateway的訪問,由于也是k8s內(nèi)部pod,所以暫時先配置一個NodePort端口映射,具體可以通過以下命令進行操作:

export INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="http2")].nodePort}')
export SECURE_INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="https")].nodePort}')
export INGRESS_HOST=127.0.0.1
export GATEWAY_URL=$INGRESS_HOST:$INGRESS_PORT

以上分別設(shè)置了istio-ingressgateway的http/https的NodePort訪問端口,設(shè)置完成后具體查看nodePort端口映射情況:

# kubectl get svc -n istio-system|grep istio-ingressgateway
istio-ingressgateway   LoadBalancer   10.100.69.24     <pending>     15021:31158/TCP,80:32277/TCP,443:30508/TCP,31400:30905/TCP,15443:30595/TCP   46h

可以看到通過http的32277以及https的30508端口可以訪問istio-ingressgateway。具體訪問url是:http://{k8s集群IP}:32277/接口url。具體訪問效果如下:

image

從調(diào)用效果上可以看到,基于Istio的Service Mesh微服務(wù)體系已經(jīng)運行成功!而從編程體驗上看,你似乎已經(jīng)快感覺不出微服務(wù)的存在了!反正稀里糊涂的服務(wù)就調(diào)通了,服務(wù)發(fā)現(xiàn)怎么做到的?負載均衡怎么做到的?這些問題在不需要你關(guān)心的同時,可能也引起了你的疑惑!接下來我們通過調(diào)用日志簡單感知下調(diào)用鏈路所經(jīng)過的邏輯!

04 鏈路調(diào)用日志原理分析

通過Postman調(diào)用返回結(jié)果后,我們分別看下鏈路所經(jīng)過的服務(wù)日志!先看看istio-ingressgateway的容器日志,具體如下:

# kubectl logs istio-ingressgateway-74ccb8977c-gnhbb -n istio-system

...
2021-03-18T08:02:30.863243Z    info    xdsproxy    Envoy ADS stream established
2021-03-18T08:02:30.865335Z    info    xdsproxy    connecting to upstream XDS server: istiod.istio-system.svc:15012
[2021-03-18T08:14:00.224Z] "POST /api/order/create HTTP/1.1" 200 - "-" 66 75 7551 6144 "10.32.0.1" "PostmanRuntime/7.26.8" "8e8bad1d-5dd9-954b-b218-15f8c9595a24" "10.211.55.12:32277" "10.32.0.10:9090" outbound|19090||micro-api.default.svc.cluster.local 10.32.0.8:57460 10.32.0.8:8080 10.32.0.1:33229 - -
[2021-03-18T08:14:32.465Z] "POST /api/order/create HTTP/1.1" 200 - "-" 66 75 3608 3599 "10.32.0.1" "PostmanRuntime/7.26.8" "ccf56049-88e8-9170-a1f5-93affbf6e098" "10.211.55.12:32277" "10.32.0.10:9090" outbound|19090||micro-api.default.svc.cluster.local 10.32.0.8:57460 10.32.0.8:8080 10.32.0.1:33229 - -
[2021-03-18T08:16:37.242Z] "POST /api/order/create HTTP/1.1" 200 - "-" 66 75 68 67 "10.32.0.1" "PostmanRuntime/7.26.8" "98ecbd52-91a0-97c6-9ce6-d8f6094560e0" "10.211.55.12:32277" "10.32.0.10:9090" outbound|19090||micro-api.default.svc.cluster.local 10.32.0.8:57460 10.32.0.8:8080 10.32.0.1:33229 - -

如上所示,從istio-ingressgateway的網(wǎng)關(guān)日志中,可以看到/api/order/create接口的訪問情況,確實是被轉(zhuǎn)發(fā)到了micro-api所在的pod ip,符合前面配置的網(wǎng)關(guān)路由規(guī)則。

接下來我們查看micro-api的istio-proxy代理的日志:

# kubectl logs micro-api-6455654996-57t4z istio-proxy
...
[2021-03-18T08:41:10.750Z] "POST /order/create HTTP/1.1" 200 - "-" 49 75 19 18 "-" "PostmanRuntime/7.26.8" "886390ea-e881-9c45-b859-1e0fc4733680" "micro-order" "10.32.0.7:9091" outbound|80||micro-order.default.svc.cluster.local 10.32.0.10:54552 10.99.132.246:80 10.32.0.10:39452 - default
[2021-03-18T08:41:10.695Z] "POST /api/order/create HTTP/1.1" 200 - "-" 66 75 104 103 "10.32.0.1" "PostmanRuntime/7.26.8" "886390ea-e881-9c45-b859-1e0fc4733680" "10.211.55.12:32277" "127.0.0.1:9090" inbound|9090|| 127.0.0.1:52782 10.32.0.10:9090 10.32.0.1:0 outbound_.19090_._.micro-api.default.svc.cluster.local default
...
[2021-03-18T08:47:22.215Z] "POST /order/create HTTP/1.1" 200 - "-" 49 75 78 70 "-" "PostmanRuntime/7.26.8" "9bbd3a3c-86c4-943f-999a-bc9a1dc02c35" "micro-order" "10.32.0.9:9091" outbound|80||micro-order.default.svc.cluster.local 10.32.0.10:54326 10.99.132.246:80 10.32.0.10:44338 - default
[2021-03-18T08:47:22.173Z] "POST /api/order/create HTTP/1.1" 200 - "-" 66 75 134 129 "10.32.0.1" "PostmanRuntime/7.26.8" "9bbd3a3c-86c4-943f-999a-bc9a1dc02c35" "10.211.55.12:32277" "127.0.0.1:9090" inbound|9090|| 127.0.0.1:57672 10.32.0.10:9090 10.32.0.1:0 outbound_.19090_._.micro-api.default.svc.cluster.local default

這里我們訪問了兩次接口情況,可以看到micro-api的Sidecar代理以負載均衡的方式,分別調(diào)用了micro-order服務(wù)的兩個不同實例(打下劃線IP)!

而訪問micro-order的istio-proxy代理日志:

# kubectl logs micro-order-v1-84ddc57444-dng2k istio-proxy
...
2021-03-18T08:33:06.146178Z    info    xdsproxy    Envoy ADS stream established
2021-03-18T08:33:06.146458Z    info    xdsproxy    connecting to upstream XDS server: istiod.istio-system.svc:15012
[2021-03-18T08:34:59.055Z] "POST /order/create HTTP/1.1" 200 - "-" 49 75 8621 6923 "-" "PostmanRuntime/7.26.8" "b1685670-9e54-9970-a915-5c5dd18debc8" "micro-order" "127.0.0.1:9091" inbound|9091|| 127.0.0.1:36420 10.32.0.7:9091 10.32.0.10:54552 outbound_.80_._.micro-order.default.svc.cluster.local default
[2021-03-18T08:41:10.751Z] "POST /order/create HTTP/1.1" 200 - "-" 49 75 17 16 "-" "PostmanRuntime/7.26.8" "886390ea-e881-9c45-b859-1e0fc4733680" "micro-order" "127.0.0.1:9091" inbound|9091|| 127.0.0.1:41398 10.32.0.7:9091 10.32.0.10:54552 outbound_.80_._.micro-order.default.svc.cluster.local default
....

可以看到,請求通過micro-order的istio-proxy代理被轉(zhuǎn)到了具體的micro-order實例!

通過上面日志的分析,雖然很細節(jié)的原理可能還是有疑問,但至少可以得到一個結(jié)論,那就是在Istio的Service Mesh微服務(wù)架構(gòu)中,服務(wù)的轉(zhuǎn)發(fā)、路由邏輯的確都是由Sidecar代理來干的,而且從日志中可以看到Envoy代理時刻都在保持著同控制面服務(wù)istiod的連接,并隨時通過xDS協(xié)議更新著服務(wù)治理規(guī)則!

后記

本文從Service Mesh的大致原理出發(fā),以實際的開發(fā)案例演示了如何開發(fā)一套基于Service Mesh架構(gòu)的微服務(wù)體系!應(yīng)該算是能夠讓大家入門Service Mesh了!也多少彌補了一點目前網(wǎng)絡(luò)上Service Mesh具體實踐文章幾近空白的情況!

但不得不說,雖然Service Mesh微服務(wù)架構(gòu)體系,極大的簡化了研發(fā)人員開發(fā)微服務(wù)應(yīng)用的成本;但另一方面Service Mesh在將微服務(wù)治理體系下沉為基礎(chǔ)設(shè)施一部分的同時,也增加了對Devops工程師的要求!畢竟要玩好Service Mesh架構(gòu),不僅需要開發(fā)技能,還需要對Service Mesh的架構(gòu)體系及其框架源碼有深刻的理解。除此之外,還需要對Kubernetes基礎(chǔ)設(shè)施特別熟悉!

總之,Service Mesh雖然先進,但是在團隊技能知識儲備尚不具備的情況下,貿(mào)然將這套體系引入生產(chǎn)環(huán)境,也是有風險的!所以這只是一個開始,在后面的時間里,我還會繼續(xù)分享有關(guān)Service Mesh及Istio的實踐及原理,感興趣的朋友可以持續(xù)關(guān)注下!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容