[轉] Service Mesh深度學習系列(三)| istio源碼分析之pilot-discovery模塊分析(中)

pilot總體架構

首先我們回顧一下 pilot 總體架構,上面是官方關於 pilot 的架構圖,因爲是 old_pilot_repo 目錄下,可能與最新架構有出入,僅供參考。所謂的 pilot 包含兩個組件:pilot-agent 和 pilot-discovery。圖裏的 agent 對應 pilot-agent 二進制,proxy 對應 Envoy 二進制,它們兩個在同一個容器中,discovery service 對應 pilot-discovery 二進制,在另外一個跟應用分開部署的單獨的 deployment 中。

  1. discovery service:從Kubernetes apiserver list/watch
    service、endpoint、pod、node等資源信息,監聽istio控制平面配置信息(如 VirtualService、DestinationRule等), 翻譯爲Envoy可以直接理解的配置格式。
  2. proxy:也就是Envoy,直接連接 discovery service,間接地從Kubernetes 等服務註冊中心獲取集羣中微服務的註冊情況。
  3. agent:生成Envoy配置文件,管理Envoy生命週期。
  4. service A/B:使用了istio的應用,如Service A/B,的進出網絡流量會被proxy接管。

對於模塊的命名方法,本文采用模塊對應源碼main.go所在包名稱命名法。其他istio分析文章有其他命名方法。比如pilot-agent也被稱爲istio pilot,因爲它在Kubernetes上的部署形式爲一個叫istio-pilot的deployment。

pilot-discovery的統一存儲模型(Abstract Model)

根據上面官方的 pilot-discovery 架構圖,pilot-discovery 有兩個輸入信息(黃色部分)

  1. 來自 istioctl 的控制面信息,也就是圖中的 Rules API,如route rule、virtual service 等,這些信息以 Kubernetes CRD 資源形式保存。
  2. 來自服務註冊中心的服務註冊信息,也就是圖上的 Kubernetes,Mesos、Cloud Foundry 等。在 Kubernetes 環境下包括 pod 、service、node、endpoint。

爲了實現 istio 對不同服務註冊中心的支持,如 Kubernetes,consul、Cloud Foundry等,pilot-discovery 需要對以上兩個輸入來源的數據有一個統一的存儲格式,也就是圖中的 Abstract Model,這種格式就定義在 pilot/pkg/model 包下。

舉例,下面列表羅列了 istio Abstract Model 中 service 的一些成員如何根據 Kubernetes 服務註冊信息中的 service 對象轉化得到:

  1. HostName:<name>.<namespace>.svc.cluster.local其中 name和 namespace 分別爲 Kubernetes service 對象的 name 和所屬的 namespace。cluster.local 爲默認 domain suffix,可以通過 proxy-discovery discovery 命令的 domain flag 提供自定義值。
  2. Ports: 對應 Kubernetes service 的 spec.ports。
  3. Address: 對應 Kubernetes service 的spec.ClusterIP。
  4. ExternalName: 對應 Kubernetes service的spec.ExternalName。
  5. ServiceAccounts: 對應 Kubernetes service 的 annotation 中 key 值爲 alpha.istio.io/kubernetes-serviceaccounts 和 alpha.istio.io/canonical-serviceaccounts的 annotation 信息。
  6. Resolution: 根據情況可以設置爲 client side LB、DNS Lb 和 Passthrough。比如對於 ClusterIP 類型的 Kubernetes service,Resolution 設置爲 client side LB,表示應用發出的請求由 sidecar(也就是 Envoy )負責做負載均衡,而對於 Kubernetes 中的 headless service 則設置爲 Passthrough。

上面 pilot-discovery 架構圖中的 Platform Adaptor 負責實現服務註冊中心數據到 Abstract Model 之間的數據轉換,在代碼裏,Platform Adaptor 包含兩部分:

  1. pilot/pkg/serviceregistry/kube/conversion.go 裏包括一系列將 Kubernetes 服務註冊中心中的 label、pod、service、service port等 Kubernetes 資源對象轉換爲 Abstract Model 中的對應資源對象的函數。
  2. pilot/pkg/config/kube/crd/conversion.go 裏包括將 DestinationRule 等 CRD 轉換爲Abstract Model 中的 Config 對象的函數。

在 pilot/pkg/bootstrap 包下的 Server  結構體代表 pilot-discovery,其中包含3個重要成員負責這兩類信息的獲取、格式轉換以及數據變更事件的處理框架:

  1. ConfigStoreCache ConfigStoreCache 對象中 embed 了 ConfigStore 對象。ConfigStore 對象利用 client-go 庫從 Kubernetes 獲取 route rule、virtual service等 CRD 形式存在控制面信息,轉換爲 model 包下的 Config 對象,對外提供 Get、List、Create、Update、Delete等 CRUD 服務。而 ConfigStoreCache 在此基礎之上還允許註冊控制面信息變更處理函數。
  2. IstioConfigStoreIstioConfigStore 封裝了 embed 在 ConfigStoreCache 中的同一個 ConfigStore 對象。其主要目的是爲訪問 route rule、virtual service 等數據提供更加方便的接口。相對於ConfigStore 提供的 Get、List、Create、Update、Delete 接口,IstioConfigStore 直接提供更爲方便的 RouteRules、VirtualServices 接口。
  3. ServiceController利用 client-go 庫從 Kubernetes 獲取 pod 、service、node、endpoint,並將這些 CRD 轉換爲 model 包下的 Service、ServiceInstance 對象。

在 istio 中,使用 istioctl 配置的 VirtualService、DestinationRule 等被稱爲 configuration,而從Kubernetes等服務註冊中心獲取的信息被稱爲service信息。所以從名稱看ConfigStoreCache、IstioConfigStore負責處理第一類信息,ServiceController負責第二類。

pilot-discovery爲Envoy提供的xds服務

所謂xds

基於上面介紹的統一數據存儲格式 Abstract Model,pilot-discovery 爲數據面(運行在 sidecar中的 Envoy 等 proxy 組件)提供控制信息服務,也就是所謂的 discovery service 或者 xds 服務。這裏的 x 是一個代詞,類似雲計算裏的 XaaS 可以指代 IaaS、PaaS、SaaS 等。在 istio 中,xds 包括 cds(cluster discovery service)、lds(listener discovery service)、rds(route discovery service)、eds
(endpoint discovery service),而 ads(aggregated discovery service)是對這些服務的一個統一封裝。

以上 cluster、endpoint、route 等概念的詳細介紹和實現細節可以參考 Envoy 在社區推廣的data plane api(github.com/envoyproxy/data-plane-api),這裏只做簡單介紹:

  1. endpoint:一個具體的“應用實例”,對應 ip 和端口號,類似 Kubernetes 中的一個 pod。
  2. cluster:一個 cluster 是一個“應用集羣”,它對應多個提供相同服務的一個或多個endpoint。cluster 類似 Kubernetes 中 service 的概念,即一個 Kubernetes service 對應一個或多個用同一鏡像啓動,提供相同服務的 pod。
  3. route:當我們做灰度發佈、金絲雀發佈時,同一個服務會同時運行多個版本,每個版本對應一個 cluster。這時需要通過 route 規則規定請求如何路由到其中的某個版本的 cluster 上。

以上這些內容實際上都是對 Envoy 等 proxy 的配置信息,而所謂的 cluster discovery service、route discovery service 等 xxx discovery service 就是 Envoy 等從 pilot-discovery 動態獲取 endpoint、cluster 等配置信息的協議和實現。爲什麼要做動態配置加載,自然是爲了使用istioctl等工具統一、靈活地配置 service mesh。

而爲什麼要用ads來“聚合”一系列xds,並非僅爲了在同一個 gRPC 連接上實現多種xds來省下幾個網絡連接,ads還有一個非常重要的作用是解決cds、rds信息更新順序依賴的問題,從而保證以一定的順序同步各類配置信息,這方面的討論可以詳見 Envoy 官網。

Envoy 有篇博客叫 The universal data plane API。按照 Envoy 的設想,社區中無論是實現控制面的團隊(比如 istio 自己),還是實現數據面的團隊(比如Envoy、nginx等),大家都能參與並採用這個 github.com/envoyproxy/data-plane-api 上規定的這套控制面與數據面之間的 data plane api 接口。所以雖然 repo 叫 data plane api,但博客的名字加上了 universal 這個形容詞。

xds 在 pilot-discovery 中的實現框架

pilot-discovery 在初始化 discovery service(xds服務)的過程中(initDiscoveryService 方法),創建了 discovery server 對象,由它負責啓動了兩個 gRPC 服務:eds(endpoint discovery service)和 ads(aggregated discovery service)。其中單獨存在的 eds gRPC 服務僅僅是爲了向後兼容老版本 istio 而存在,0.8版本的 istio 主要對外的 discovery service 就是指 ads,而其中已經整合了 eds。本文主要的分析的 xds 就是指 ads。

本系列文章的上一篇中說明,pilot-discovery 在初始化 discovery service 的過程中創建了兩個服務對象,其中第一個 discovery server 對象負責爲 Envoy 提供 gRPC 協議的 discovery service,而第二個 discovery service 對象則負責爲 Envoy 提供 REST 協議的 discovery service。

根據Envoy的data plane api定義,ads需要對外提供的gRPC接口AggregatedDiscoveryServiceServer只有StreamAggregatedResources一個方法。在discovery service 初始化過程中創建的 pilot/pkg/proxy/envoy/v2 包下的 DiscoveryServer 對象實現了 gRPC server 端接口。

envoy 爲方便第三放開發者開發控制面,提供了 go-control-plane 庫。基於 go-control-plane 庫,開發者可以方便地實現基於 gRPC 協議的 discovery service。istio 0.8 版使用的go-control-plane 版本 commit 號爲 bc01fbf,在這個版本中AggregatedDiscoveryServiceServer 接口就只有 StreamAggregatedResources 一個方法。但是在go-control-plane 2018年7月的一次commit中又爲AggregatedDiscoveryServiceServer 接口增加了 IncrementalAggregatedResources 方法,支持更爲靈活的 discovery service 和 Envoy 之間的交互。

discovery server 的主要邏輯,就是在與每一個 Envoy 建立一個雙向 streaming 的 gRPC 連接(Bidirectional streaming RPC)之後:

  1. 啓動一個協程從 gRPC 連接中讀取來自 Envoy 的請求。
  2. 在原來的協程中處理來自各 gRPC 連接的請求。

discovery server 從 Envoy 收到的請求類型爲 go-control-plane 庫下的 DiscoveryRequest。DiscoveryRequest 幾個相對重要的成員如下:

  1. VersionInfo
    Envoy在收到一個DiscoveryResponse之後會馬上再發送一個DiscoveryRequest作爲ACK/NACK,從而告訴 discovery service 消息是否成功處理。VersionInfo用來表示 Envoy 端到目前爲止成功處理的最新的消息版本。
  2. Node.Id
    連上 discovery service 的 Envoy 的唯一標識。標識符當 istio 部署在不同的服務註冊中心(service registry)時會有不同的形式。在 Kubernetes 作爲服務註冊中心時,一個可能的Id值爲sidecar~172.00.00.000~sleep-55b5877479-rwcct.default~default.svc.cluster.local。以“~”爲分割符,可以將Node.Id 解析爲4部分:

     

    • Type:表示 Envoy sidecar 扮演的角色,如 Sidecar,Ingress,Router 等。
    • IPAddress:Envoy sidecar 所在 pod 的 IP 地址。
    • ID:Envoy sidecar 所在 pod 的 name 和 namespace,中間用”.”連接,也就是上面例 子中的 sleep-55b5877479-rwcct.default。
    • Domain:Envoy sidecar所在pod的namespace加svc.cluster.local,中間用“.”連接,也就是上面例子中的 default.svc.cluster.local 關於這四個域的說明的更多信息,詳見本系列文章第一篇中關於 pilot-agent 中 role 的說明。
  3. ResourceName
    Envoy sidecar 關注的資源列表,對於 cds、lds 來說,ResourceName
    通常是空的,因爲Envoy 總是需要知道所有的相關數據。而對於eds,rds來講,Envoy 則可以選擇性的指明需要監控的資源對象列表。
  4. TypeUrlads 服務將原來分開的單獨xds服務,如 cds、lds 等,合併在同一個雙向 streaming 的gRPC 連接上。所以當 Envoy 向 discovery server 發送DiscoveryRequest時,需要使用 TypeUrl 來指明當前請求的服務類型。TypeUrl值可以是 cds、lds 等。
  5. ReponseNonce
    discovery service的 StreamAggregatedResources 方法提供的雙向 streaming ads 服務中,discovery service 可以連續向 Envoy 發送多個 DiscoveryResponse。當 Envoy 收到 DiscoveryResponse 後,會發送 DiscoveryRequest 來ACK之前的 DiscoveryResponse。爲了減少歧義,Envoy 使用 ReponseNonce 指定當前DiscoveryRequestACK的是之前的哪個DiscoveryResponse。具體設置方式就是把ReponseNonce 指定爲需要 ACK 的 DiscoveryResponse 中的 Nonce 值,關於 discovery server 如何在DiscoveryResponse中設置Nonce,詳見下文的分析。
  6. ErrorDetail
    當 Envoy 處理來自 discovery server 的 DiscoveryResponse 的過程中發生錯誤時,會在ACK/NACK 的 DiscoveryRequest 中帶上具體錯誤信息 ErrorDetail 。

根據 discovery server 收到的 DiscoveryRequest 中指定的請求服務類型(TypeUrl),istio 的ads 服務統一封裝了 cds、lds、rds 和 eds 4種服務,即在同一個雙向 streaming 的 gRPC 連接上提供這4種服務。

接下來本文按照 ads 在配置發生變更時對外的 push xds 信息的順序,分別描述 cds、eds。由於篇幅限制,rds 和 lds 且聽下回分解。

cds服務

本文前面介紹,cds,即 cluster discovery service,是 pilot-discovery 爲 Envoy 動態提供cluster相關信息的協議。Envoy可以向pilot-discovery的gRPC server發送一個DiscoveryRequest,並將需要獲取的配置信息類型(TypeUrl
)設置爲 cds。discovery server,即 ads 服務的實現類,在收到 DiscoveryRequest 後,將 Abstract Model 中保存的相關信息組裝成 cluster,然後封裝在 DiscoveryResponse 返回給Envoy。

discovery server 爲了組裝出 cluster 信息,需要從 Abstract Model 中提取以下兩類信息類型;

  1. 服務註冊信息:如從 Kubernetes 中的服務註冊信息轉化而來的 service。
  2. 通過 istioctl 提供的配置信息,如 DestinationRule。

discovery server 將這兩類信息組裝成 cluster 信息的流程大致如下:

  1. 獲取 abstract model 中保存的 service 信息,爲每個 service 創建一個“空白”的 cluster對象。以 Kubernetes 作爲服務註冊中心的情況爲例,abstract model 中的 service 信息主要有兩個來源:
    • 在 Kubernetes 中定義的 service 資源對象。
    • 通過 istioctl 配置的 ServiceEntry 資源對象,用來代表那些沒有註冊在服務註冊中心的服務,比如運行在 Kubernetes 之外的一個數據庫。這些資源對象也保存在Kubernetes 中,以 CRD 的形式存在。
  2. 設置 cluster 名稱,形式爲:outbound|service端口號||Hostname
    其中的 service 端口號對應 Kubernetes 中 service 對象的端口號,而 Hostname 就是service mesh中客戶端方用來訪問服務方的地址,形式爲<name>.<namespace>.svc.cluster.local。其中 name 和 namespace 分別爲 Kubernetes service 對象的 name 和所屬的 namespace,cluster.local 爲默認 domain suffix 。其中第三項對 cluster 來說是空白信息,只對 subcluster 有效,詳見下面的分析。
  3. 設置 cluster 的默認流量控制策略,如:默認的負載均衡策略爲 round robin,默認的timeout 時間等。
  4. 配置與該 cluster 相關的 eds 更新方式。istio 中每個 cluster 都可以單獨配置相關 eds 的更新方式,即告訴 Envoy 下次請求eds信息時,應該採用何種方式。從 istio 2018年4月的一個 commit(67be0412)開始統一使用 ads 作爲 eds 更新方法,而不是單獨與 discovery server 建立 gRPC 連接來更新 eds 信息。
  5. 根據 service 的 HostName 屬性查找對應的 DestinationRule。根據 DestinationRule 中定義的 subset 創建 subcluster。使用 istioctl 創建的 DestinationRule 資源可以用來表達同一個服務的多個版本。比如下面的 DestinationRule 定義了 reviews 服務的3個subset ,每個 subset 對應 reviews 服務的一個版本:v1、v2和v3。在 Kubernetes 環境下翻譯過來就是具有 label version=v1 的service 是v1版本的 reviews 服務,具有 label version=v2 的 service 是v2版本的 reviews服務,以此類推。
    針對這裏的每個版本(subset),需要創建一個單獨的 subcluster(其實就是一個獨立的cluster),subcluster 具有跟前面創建的 cluster 有類似的名稱,形式爲 outbound|service端口號|subset名稱|Hostname,注意這裏的第三項不再是空白。
APIVersionnetworking.istio.io/v1alpha3

kindDestinationRule

metadata

  namereviews

spec

  hostreviews

  trafficPolicy

    loadBalancer

      simpleRANDOM

    subsets

    - namev1

      labels

        versionv1

    - namev2

      labels

        versionv2

      trafficPolicy

        loadBalancer

          simpleROUND_ROBIN

     - namev3

       labels

         versionv3

6. 根據 DestinationRule 裏定義的 traffic policy,爲 cluster、subcluster 配置流量控制策略,包括 connection pool、outlier detection、負載均衡, upstream tls 設置等。
仔細看上面定義的叫 reviews 的 DestinationRule,我們可以看到裏面定義了2個 traffic policy,第一個 traffic policy 定義了整體負載均衡策略爲 RANDOM,第二個 traffic policy專門針對 subset v2,設置負載均衡爲 ROUND_ROBIN。定義在頂層的 RANDOM 整體負載均衡策略會影響到每個 subcluster。

discovery server 在組裝完成上述 cluster對象之後,將得到的所有cluster封裝在一個DiscoveryResponse 中,將 DiscoveryResponse 的類型(即 TypeUrl )設置爲type.googleapis.com/envoy.api.v2.Cluster,Nonce 設置爲當前時間(nonce 的解釋見本文前面部分), 啓動單獨的協程通過與 Envoy 建立的雙向 stream gRPC 連接發送給 Envoy,發送超時爲5秒。

eds服務

Envoy 通過 cds 服務獲取 service mesh 中的 cluster(應用集羣)信息之後,還需要知道每個cluster 所代表的應用集羣中的成員信息,即 endpoint。因此,Envoy 可以在向 discovery server 調用 cds 服務之後,繼續向 discovery server 發送 TypeUrl 爲 eds 的DiscoveryRequest,從而請求 endpoint 信息。

Envoy 發給 discovery server 的 DiscoveryRequest 中會在 ResourceNames 成員中包含它所關注的 cluster 的名稱列表,當前 istio 支持兩種 cluster 命名方法:

 

—  未完待續  —

 

推薦閱讀

  1.  Service Mesh深度學習系列(二)| istio源碼分析之pilot-discovery模塊分析(上)
  2.  Service Mesh深度學習系列|istio源碼分析之pilot-agent組件分析
  3.  程序員能量站|PouchContainer CRI的設計與實現
  4.  程序猿能量站|深入理解Docker容器引擎runC執行框架
  5.  觀雲臺+Pouch,走進容器新時代

 
作者簡介:
丁軼羣,諧雲科技CTO
2004年作爲高級技術顧問加入美國道富銀行(浙江)技術中心,負責分佈式大型金融系統的設計與研發。2011年開始領導浙江大學開源雲計算平臺的研發工作,是浙江大學SEL實驗室負責人,2013年獲得浙江省第一批青年科學家稱號,CNCF會員,多次受邀在Cloud Foundry, Docker大會上發表演講,《Docker:容器與容器雲》主要作者之一。
 
關注“諧雲科技”微信公衆號,閱讀全文哦~
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章