乾貨|如何步入Service Mesh微服務架構時代

今天要和大家分享的是關於新一代微服務架構——Service Mesh的具體玩法!在微服務架構盛行的今天,作爲一名互聯網技術從業人員,對於微服務的概念相信大家都已經耳熟能詳了!而至於像Spring Cloud這樣的微服務框架,因爲大部分互聯網公司都在此基礎上構建過第一代微服務體系,所以對於做Java 的同學來說,Spring Cloud微服務體系應該是非常熟悉了!

這裏並不是說其他語言棧就沒有構建微服務體系的框架,例如Go語言也有像Go-Micro這樣的微服務框架,只不過目前除了像頭條這樣重度使用Go語言的公司外,其他絕大多數互聯網公司的服務端語言依然還是Java的天下!所以對於目前大部分已經或打算採用微服務架構的公司來說,Spring Cloud框架依然是它們的首選!但如果我說,這套體系發展到今天已經快過時了,你會不會覺得我是在瞎掰呢?因爲畢竟咱們現在天天玩的微服務還都是Spring Cloud這一套啊!

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

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

爲什麼要進入Service Mesh時代

前面我略微誇張的說到,以Spring Cloud爲代表的微服務體系相比Service Mesh而言已經存在技術代差,那憑什麼這麼說呢?接下來,我們回顧下使用Spring Cloud構建微服務體系的大致技術流程!

要構建微服務體系,首先我們需要獨立部署一款實現服務註冊/發現功能的組件服務,目前可供選擇的主流方案一般有Eureka、Consul、Nacos等,搞定服務註冊/發現後,我們編寫一個Java微服務,此時爲了將該服務註冊到服務註冊中心,一般會引入Spring Cloud提供的支持對應註冊中心接入的SDK,並在應用入口類中通過@EnableDiscoveryClient註解的方式標註,之後SDK中的邏輯就會在應用啓動時執行服務註冊動作,並提供給註冊中心相應地探測接口,以此實現微服務與服務註冊中心之間的連接。以此類推,我們可以通過這種方式將一組微服務都註冊到服務註冊中心!

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

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

上述過程基本就是我們使用Spring Cloud構建微服務體系的大致過程了!如果仔細思考下這個過程,我們會發現在該微服務體系的構造過程中,與服務治理相關的大部分邏輯都是以SDK的方式耦合在具體的微服務應用之中!服務註冊需要引入SDK、服務調用需要引入SDK、服務熔斷限流也需要SDK;除此之外,爲了保證這套體系的正常運行,我們還需要額外維護服務註冊中心、服務網關這樣的基礎服務。這樣的結構會導致什麼弊端呢?具體有以下幾點:

1、框架/SDK太多,後續升級維護困難

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

2、多語言微服務SDK維護成本高

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

3、服務治理策略難以統一控制

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

4、服務治理邏輯嵌入業務應用,佔有業務服務資源

在這套微服務體系中,服務治理相關的邏輯都是在微服務應用進程中寄生運行的,這多少會佔有寶貴的業務服務器資源,影響應用性能的發揮!

5、額外的服務治理組件的維護成本

無論是服務註冊中心、還是服務網關,這些除了微服務應用本身之外服務治理組件,都需要我們以中間件基礎服務的方式進行維護,需要額外的人力、額外的服務器成本!

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

不管你怎麼想,這些就是Service Mesh要乾的事情!Service Mesh的目標就是要將微服務治理體系下沉爲一套與業務無關的基礎設施。從這個角度看,如果咱們不認真學習下Service Mesh,那麼以後將變得越來越低智,因爲Spring Cloud好歹還能讓我們感知下微服務的存在,而在Service Mesh中,微服務治理體系作爲基礎設施的一部分,對普通研發人員將越來越透明!

Service Mesh的解決方案是什麼

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

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

要理解在Service Mesh中的微服務治理邏輯的具體實現,就不得不上一張關於服務網格概念很經典但剛開始看着又很難理解的圖,如下:

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

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

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

這種設計方式就是Service Mesh最核心的設計邏輯——通過Sidecar的方式代理微服務進行服務治理邏輯(數據面),通過控制面感知外界環境的變化並通過xDS協議支持各種微服務治理策略規則的集中管理和下發,而這裏的控制面和數據面都會被融合進像Kubernetes這樣的基礎架構環境中,對於普通微服務的開發,研發人員要做的只是將一個應用以編排的方式部署進k8s集羣即可!而所有與微服務治理相關的邏輯都由代理數據面與控制面協作完成。

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

其中服務註冊發現可以直接利用Kubernetes的內部發現機制,通過監聽Kubernetes Pod的變化來實現,具體示意圖如下:

而微服務治理相關的邏輯,以Istio爲例,流程大致是這樣的:

管理員通過Pilot配置治理規則,並通過xDS協議向Envoy下發治理規則,而Envoy從Pilot獲取微服務治理規則後,就可以在流量訪問的時候按照規則執行相應的限流、路由等微服務治理邏輯了!

Istio+Envoy的Service Mesh架構玩法

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

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

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

具體過程及步驟如下:

01 k8s環境準備及Istio安裝

要玩轉Service Mesh微服務架構,基本的前提是需要一個功能完整的Kubernetes環境,這裏我所使用的k8s環境是在開發本上安裝一個Linux虛擬機並在其之上部署一個只有Master節點的Kubernetes單節點集羣,另外由於Istio對Kubernetes的版本是有要求的,這裏所使用的k8s版本是v1.18.6。

這裏我先假設你已經搞定了Kubernetes環境,接下來開始安裝Istio,選擇的版本是istio-1.8.4,具體步驟如下:

1)、下載Istio發佈包

由於官方提供的下載腳本執行速度比較慢,可以直接在github找到相應的istio發佈版本後通過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客戶端添加到系統可執行路徑

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

export PATH=$PWD/bin:$PATH

3)、執行安裝istio命令

這裏使用istioctl命令執行安裝命令,具體如下:

istioctl install --set profile=demo

這裏"--set profile=demo"表示安裝一個istio測試環境!成功安裝後的信息輸出如下:

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相關組件是否已經安裝在Kubernetes環境之中,命令如下:

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,以及入口網關ingressgateway、出口網關egressgateway已經成功以Service資源的方式運行在了Kuberntes集羣之中!

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

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

具體命令如下:

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

5)、Istio可觀測性部署

Kiali是一個基於服務網格的Istio管理控制檯,它提供了一些數據儀表盤和可觀測能力,同時也可以讓我們去操作網格的配置。使用如下方式快速部署一個用於演示的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等指標及鏈路採集服務!因爲安裝的組件比較多,也比較耗費資源,如果集羣資源不是很充足,可能會出現啓動比較慢的情況。如果正常部署成功,可以查看Pod狀態,命令如下:

# 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文件導出到主機的某個目錄,例如:

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

之後編輯導出的文件,刪除metadata下的annotation、resourceVersion、selfFlink、uid等信息;並修改下spec下的type類型值,將ClusterIP修改爲NodePort,並指定nodePort端口信息;同時刪除status狀態字段即可。具體如下:

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

編輯完成後執行執行命令:

kubectl apply -f kiali-nodeport.yaml

之後查看服務端口,命令如下:

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控制面板了,效果如下圖所示:

與Kiali的操作方式類似,我們也可以通過獲取修改部署的Promethues、Granfana、Tracing、Zipkin等服務的發佈文件,通過設置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的訪問效果示意圖如下:

02 Spring Boot微服務開發

經過前面的步驟,我們已經從基礎架構環境的角度完成了基於Istio的Service Mesh微服務體系的構建!如果類比之前基於Spring Cloud框架的微服務開發體驗,那麼在Istio體系下應該如何進行微服務應用的開發呢?

接下來我們通過一個實際的應用示例來演示,如何開發基於Istio的Service Mesh微服務應用,服務鏈路如下:

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

1)、爲了完整演示在Service Mesh架構下微服務的研發過程,這裏我們定義3個微服務,其中micro-api服務是面向外部客戶端接入的Api服務提供Http協議訪問;

2)、而micro-api與micro-order之間則基於微服務的註冊發現機制進行內部服務調用,具體採用Http協議;

3)、而micro-order與micro-pay之間也基於微服務註冊發現機制進行內部微服務調用,爲了演示更多的研發場景,這兩個微服務之間的通信我們採用Grpc協議;

規劃好了微服務應用架構,接下來就可以具體開發了!具體的服務代碼層面的構建,這裏並不需要做任何微服務框架的引入,你只需要通過Spring Boot構建幾個基本的Spring Boot應用即可,不需要引入任何服務治理相關的組件,只是一個簡單且單純的Spring Boot應用,不需要連接註冊中心,也不需要引入什麼OpenFeign、Hystrix、Sentinel之類的組件。

具體的代碼結構如下圖所示:

可以看到應用的入口類中已經沒有服務發現之類的註解!接下來我們講重點

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

但在Service Mesh架構下負載均衡及服務發現的邏輯已經由Istio中的Sidecar幫我們幹了,所以在這裏就不能還像以前一樣引入OpenFeign了!那麼怎麼辦呢?爲了延續之前的編程風格及服務通信代碼的簡易性,這裏我們需要自己定製一個類似於OpenFeign的框架,可以基於OpenFeign的源碼進行改造,但是要去掉其中關於服務負載均衡、熔斷限流等服務治理相關的邏輯,讓它變成一個只是簡單進行Http服務調用的框架。

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

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

2、支持多環境配置,例如本地環境微服務的調用地址可配置爲本地,其他環境默認爲Kubernetes集羣中的服務;

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 {
    /**
     * 訂單創建
     */
    @PostMapping("/create")
    ResponseResult<CreateOrderBO> create(@RequestBody CreateOrderDTO createOrderDTO);
}

這裏是micro-order微服務給micro-api所提供的接口調用代碼,micro-api服務引入調用即可,從編程風格上與之前Spring Cloud微服務的開發方式十分類似。只不過到這裏爲止,你並沒有能看到任何與服務註冊發現相關的邏輯!

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

接下來重點演示micro-api及micro-order的k8s發佈文件,看看它們有什麼特別之處:

micro-order服務k8s發佈文件(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服務的k8s部署文件,就是正常定義了該應用的Service資源及Deployment編排資源;爲了後面演示服務的負載均衡調用,這裏我特地將該應用部署成了2個副本!

接下來繼續看看調用方micro-api服務的k8s發佈文件(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一樣也只是定義了該應用的k8s正常發佈資源,到這裏也並沒有體現出micro-api是怎麼調用micro-order服務的!接下來我們通過這個文件將服務發佈至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都已經正常運行起來了!但不知你發現沒有,每個Pod中READY字段顯示的都是2/2,這意味着每個Pod中都啓動了兩個容器,一個是微服務應用本身,另外一個就是自動注入啓動的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)的方式,自動啓動一個對應地istio-proxy代理進程(Envoy),到這裏你應該真實感到到Sidecar到底是一個什麼樣的存在了吧!

03 部署Istio微服務網關

前面的步驟中我們已經完成了微服務應用的開發,並且也已經將其部署到了k8s集羣,Sidecar代理也正常啓動了,那麼怎麼訪問呢?

一般來說如果要訪問Kubernetes集羣中的Service,可以通過NodePort端口映射及Ingress的方式向k8s集羣外暴露訪問端口。但在Istio中採用了一種新的模型——Istio Gateway來代替Kubernetes中的Ingress資源類型。在Istio微服務體系中,所有外部流量的訪問都應該通過Gateway進來,並由Gateway轉發到對應的內部微服務!

而基於統一的控制面配置,Istio也可以集中管理Gateway網關的流量訪問規則,實現對外部流量訪問的整體管控!在前面部署Istio時,"istio-ingressgateway"入口流量網關已經作爲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

接下來我們需要設置通過該網關訪問micro-api微服務的邏輯,編寫網關部署文件(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

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

配置完上述網關路由轉發規則後,我們嘗試通過訪問istio-ingressgateway來到達訪問微服務接口的效果,具體鏈路是:"外部調用->istio-ingressgateway->micro-api->micro-order"。

但是對於istio-ingressgateway的訪問,由於也是k8s內部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

以上分別設置了istio-ingressgateway的http/https的NodePort訪問端口,設置完成後具體查看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。具體訪問效果如下:

從調用效果上可以看到,基於Istio的Service Mesh微服務體系已經運行成功!而從編程體驗上看,你似乎已經快感覺不出微服務的存在了!反正稀裏糊塗的服務就調通了,服務發現怎麼做到的?負載均衡怎麼做到的?這些問題在不需要你關心的同時,可能也引起了你的疑惑!接下來我們通過調用日誌簡單感知下調用鏈路所經過的邏輯!

04 鏈路調用日誌原理分析

通過Postman調用返回結果後,我們分別看下鏈路所經過的服務日誌!先看看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的網關日誌中,可以看到/api/order/create接口的訪問情況,確實是被轉發到了micro-api所在的pod ip,符合前面配置的網關路由規則。

接下來我們查看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代理以負載均衡的方式,分別調用了micro-order服務的兩個不同實例(打下劃線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代理被轉到了具體的micro-order實例!

通過上面日誌的分析,雖然很細節的原理可能還是有疑問,但至少可以得到一個結論,那就是在Istio的Service Mesh微服務架構中,服務的轉發、路由邏輯的確都是由Sidecar代理來乾的,而且從日誌中可以看到Envoy代理時刻都在保持着同控制面服務istiod的連接,並隨時通過xDS協議更新着服務治理規則!

後記

本文從Service Mesh的大致原理出發,以實際的開發案例演示瞭如何開發一套基於Service Mesh架構的微服務體系!應該算是能夠讓大家入門Service Mesh了!也多少彌補了一點目前網絡上Service Mesh具體實踐文章幾近空白的情況!

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

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

寫在最後

歡迎大家關注我的公衆號【風平浪靜如碼】,海量Java相關文章,學習資料都會在裏面更新,整理的資料也會放在裏面。

覺得寫的還不錯的就點個贊,加個關注唄!點關注,不迷路,持續更新!!!

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