Istio Pilot架构解析
本文節選自 ServiceMesher 社區聯合編寫的《Istio Handbook——Istio 服務網格進階實戰》。
本書地址:https://github.com/servicemesher/istio-handbook/
在應用從單體架構向微服務架構演進的過程中,微服務之間的服務發現、負載均衡、熔斷、限流等服務治理需求是無法回避的問題。
在 Service Mesh 出現之前,通常的做法是將這些基礎功能以 SDK 的形式嵌入業務代碼中,但是這種強耦合的方案會增加開發的難度,增加維護成本,增加質量風險。比如 SDK 需要新增新特性,業務側也很難配合 SDK 開發人員進行升級,所以很容易造成 SDK 的版本碎片化問題。如果再存在跨語言應用間的交互,對于多語言 SDK 的支持也非常的低效。一方面是相當于相同的代碼以不同語言重復實現,實現這類代碼既很難給開發人員帶來成就感,團隊穩定性難以保障;另一方面是如果實現這類基礎框架時涉及到了語言特性,其他語言的開發者也很難直接翻譯。
而 Service Mesh 的本質則是將此類通用的功能沉淀至 sidecar 中,由 sidecar 接管服務的流量并對其進行治理。在這個思路下,可以通過流量劫持的手段,做到代碼零侵入性。這樣可以讓業務開發人員更關心業務功能。而底層功能由于對業務零侵入,也使得基礎功能的升級和快速的更新迭代成為可能。
Istio 是近年來 Service Mesh 的代表作,而 Istio 流量管理的核心組件就是是 Pilot。Pilot 主要功能就是管理和配置部署在特定 Istio 服務網格中的所有 sidecar 代理實例。它管理 sidecar 代理之間的路由流量規則,并配置故障恢復功能,如超時、重試和熔斷。
Pilot 架構
Pilot 架構(圖片來自Istio官方網站)根據上圖, Pilot 幾個關鍵的模塊如下。
抽象模型 (Abstract Model)
為了實現對不同服務注冊中心 (Kubernetes、consul) 的支持,Pilot 需要對不同的輸入來源的數據有一個統一的存儲格式,也就是抽象模型。
抽象模型中定義的關鍵成員包括 HostName(service 名稱)、Ports(service 端口)、Address(service ClusterIP)、Resolution (負載均衡策略) 等。
平臺適配器 (Platform adapters)
Pilot 的實現是基于平臺適配器(Platform adapters) 的,借助平臺適配器 Pilot 可以實現服務注冊中心數據到抽象模型之間的數據轉換。
例如 Pilot 中的 Kubernetes 適配器通過 Kubernetes API 服務器得到 Kubernetes 中 service 和 pod 的相關信息,然后翻譯為抽象模型提供給 Pilot 使用。
通過平臺適配器模式, Pilot 還可以從 Consul 等平臺中獲取服務信息,還可以開發適配器將其他提供服務發現的組件集成到 Pilot 中。
xDS API
Pilot 使用了一套起源于 Envoy 項目的標準數據面 API 來將服務信息和流量規則下發到數據面的 sidecar 中。這套標準數據面 API,也叫 xDS。
Sidecar 通過 xDS API 可以動態獲取 Listener (監聽器)、Route (路由)、Cluster (集群)及 Endpoint (集群成員)配置:
LDS,Listener 發現服務:Listener 監聽器控制 sidecar 啟動端口監聽(目前只支持 TCP 協議),并配置 L3/L4 層過濾器,當網絡連接達到后,配置好的網絡過濾器堆棧開始處理后續事件。
RDS,Router 發現服務:用于 HTTP 連接管理過濾器動態獲取路由配置,路由配置包含 HTTP 頭部修改(增加、刪除 HTTP 頭部鍵值),virtual hosts (虛擬主機),以及 virtual hosts 定義的各個路由條目。
CDS,Cluster 發現服務:用于動態獲取 Cluster 信息。
EDS,Endpoint 發現服務:用與動態維護端點信息,端點信息中還包括負載均衡權重、金絲雀狀態等,基于這些信息,sidecar 可以做出智能的負載均衡決策。
通過采用該標準 API, Istio 將控制面和數據面進行了解耦,為多種數據平面 sidecar 實現提供了可能性。例如螞蟻金服開源的 Golang 版本的 Sidecar MOSN (Modular Observable Smart Network)。
用戶 API (User API)
Pilot 還定義了一套用戶 API, 用戶 API 提供了面向業務的高層抽象,可以被運維人員理解和使用。
運維人員使用該 API 定義流量規則并下發到 Pilot ,這些規則被 Pilot 翻譯成數據面的配置,再通過標準數據面 API 分發到 sidecar 實例,可以在運行期對微服務的流量進行控制和調整。
通過運用不同的流量規則,可以對網格中微服務進行精細化的流量控制,如按版本分流、斷路器、故障注入、灰度發布等。
Pilot 實現
Pilot 實現(根據原圖片重繪,原圖來自Istio官方網站)圖中實線連線表示控制流,虛線連線表示數據流。帶 [pilot] 的組件表示為 Pilot 組件,圖中關鍵的組件如下:
Discovery service:即 pilot-discovery,主要功能是從 Service provider(如 kubernetes 或者 consul )中獲取服務信息,從 Kubernetes API Server 中獲取流量規則(Kubernetes CRD Resource),并將服務信息和流量規則轉化為數據面可以理解的格式,通過標準的數據面 API 下發到網格中的各個 sidecar 中。
agent:即 pilot-agent 組件,該進程根據 Kubernetes API Server 中的配置信息生成 Envoy 的配置文件,負責啟動、監控 sidecar 進程。
proxy:既 sidecar proxy,是所有服務的流量代理,直接連接 pilot-discovery ,間接地從 Kubernetes 等服務注冊中心獲取集群中微服務的注冊情況。
service A/B:使用了 Istio 的應用,如 Service A/B,的進出網絡流量會被 proxy 接管。
下面介紹下 Pilot 相關的組件 pilot-agent、pilot-discovery 的關鍵實現。
pilot-agent
pilot-agent 負責的主要工作如下:
生成 sidecar 的配置
Sidecar 的啟動與監控
生成 sidecar 配置
Sidecar 的配置主要在 pilot-agent 的 init 方法與 proxy 命令處理流程的前半部分生成。其中 init 方法為 pilot-agent 二進制的命令行配置大量的 flag 與默認值,而 proxy 命令處理流程則負責將這些 flag 組裝成為 ProxyConfig 對象以啟動 Envoy。下面分析幾個相對重要的配置。
//go?語言,源碼摘自?pilot-agent,role?角色定義 role?=?&model.Proxy{} ...type?Proxy?struct?{//?ClusterID?用于指代?proxy?所在集群名稱ClusterID?string//?Type?用于標記?proxy?運行模式Type?NodeTypeIPAddresses?[]stringID?stringDNSDomain?string... }role 默認的對象為 proxy,關鍵參數如下:
Type:pilot-agent 的 role 有兩種運行模式。根據 role.Type 變量定義,最新版本有2個類型, sidecar、router 。默認是 sidecar。
IPAddress, ID:可以接受參數,依據注冊中心的類型,給予默認值。默認處理方式是 Kubernetes。在 Kubernetes 默認值下,IPAddress 默認為 INSTANCE_IP,ID 默認為 POD_NAME,DNSDomain 默認為 default.svc.cluster.local。
Istio 可以對接的第三方注冊中心有 Kubernetes、Consul、MCP、Mock。
Envoy 配置文件及命令行參數主要有2個:
Envoy 的啟動目錄默認為/usr/local/bin/envoy
Envoy 的啟動參數相關代碼在func (e *envoy) args中。
Envoy 啟動參數關鍵釋義:
–restart-epoch:epoch 決定了Envoy 熱重啟的順序,第一個 Envoy 進程對應的 epoch 為0,后面新建的 Envoy 進程對應 epoch 順序遞增1
–drain-time-s:在 pilot-agent init 函數中指定默認值為2秒,可通過 pilot-agent proxy 命令的 drainDuration flag 指定
–parent-shutdown-time-s:在 pilot-agent init 函數中指定默認值為3秒,可通過 pilot-agent proxy 命令的 parentShutdownDuration flag 指定
–service-cluster:在 pilot-agent init 函數中指定默認值為 istio-proxy ,可通 pilot-agent proxy 命令的 serviceCluster flag 指定
–service-node:將 role 的字符串拼接成 node.Type~ip~ID~DNSDomain 格式
Sidecar 的啟動與監控
創建 envoy 對象,結構體包含 proxyConfig、role.serviceNode、loglevel 和 pilotSAN(service account name)等。
創建 agent 對象,包含前面創建的 envoy 結構體,一個 epochs 的 map,1個 channel:statusCh。
創建 watcher ,包含證書和 agent.Restart 方法并啟動協程執行 watcher.Run。
watcher.Run 首先執行 agent.Restart,啟動 Envoy 。然后啟動協程調用 watchCerts ,用于監控各種證書,如果證書文件發生變化,則重新生成證書簽名并重啟 Envoy。
創建 context,啟動協程調用 cmd.WaitSignalFunc 以等待進程接收到 SIGINT, SIGTERM 信號,接受到信號之后通過 context 通知 agent,agent 接到通知后調用 terminate 來 kill 所有 Envoy 進程,并退出 agent 進程
agent.Run 主進程堵塞,監聽 statusCh,這里的 status 其實就是 exitStatus,在監聽到 exitStatus 后,會刪除當前 epochs 中的 channel 資源。
pilot-discovery
pilot-discovery 扮演服務注冊中心、Istio 控制平面到 sidecar 之間的橋梁作用。pilot-discovery 的主要功能如下:
監控服務注冊中心(如 Kubernetes)的服務注冊情況。在 Kubernetes 環境下,會監控 service、endpoint、pod、node 等資源信息。
監控 Istio 控制面信息變化,在 Kubernetes 環境下,會監控包括 RouteRule、 VirtualService、Gateway、EgressRule、ServiceEntry 等以 Kubernetes CRD 形式存在的 Istio 控制面配置信息。
將上述兩類信息合并組合為 sidecar 可以理解的(遵循 Envoy data plane api 的)配置信息,并將這些信息以 gRPC 協議提供給 sidecar。
pilot-discovery 關鍵實現邏輯如下:
初始化及啟動
//go?語言,源碼摘自?pilot-discovery,pilot-discovery?初始化及啟動的關鍵部分,省去異常處理//?創建?discoveryServer?對象并啟動 discoveryServer,?err?:=?bootstrap.NewServer(serverArgs) discoveryServer.Start(stop)//?discoveryServer?對象的具體創建方法 func?NewServer(args?*PilotArgs)?(\*Server,?error)?{//環境變量e?:=?&model.Environment{...}s?:=?&Server{clusterID:??????getClusterID(args),????????????????????????????????//集群idenvironment:????e,????????????????????????????????????????????????//環境變量EnvoyXdsServer:?envoyv2.NewDiscoveryServer(e,?args.Plugins),?????//Pilot?針對?Envoy?v2?xds?APIs?的?gRPC?實現,用于通知?envoy?配置更新...}s.initKubeClient(args)s.initMeshConfiguration(args,?fileWatcher)????????s.initConfigController(args)????????????????????s.initServiceControllers(args)s.initDiscoveryService(args)... } ...gRPC服務啟動 func?(s?*Server)?Start(stop?<-chan?struct{})?error?{go?func()?{s.grpcServer.Serve(s.GRPCListener)}() }pilot-discovery 的初始化主要在 pilot-discovery 的 init 方法和在 discovery 命令處理流程中調用的 bootstrap.NewServer 完成,關鍵步驟如下:
創建 Kubernetes apiserver client(initKubeClient),可以在 pilot-discovery 的 discovery 命令的 kubeconfig flag 中提供文件路徑,默認為空。
讀取 mesh 配置(initMeshConfiguration),包含 MixerCheckServer、MixerReportServer、ProxyListenPort、RdsRefreshDelay、MixerAddress 等一些列配置,默認 mesh 配置文件"/etc/istio/config/mesh"。
初始化與配置存儲中心的連接(initConfigController 方法)對 Istio 做出的各種配置,比如 route rule、virtualservice 等,需要保存在配置存儲中心(config store)內。
配置與服務注冊中心(service registry)的連接(initServiceControllers 方法)
初始化 discovery 服務(initDiscoveryService),將 discovery 服務注冊為 Config Controller 和 Service Controller 的 Event Handler,監聽配置和服務變化消息。
啟動 gRPC Server 并接收來自 Envoy 端的連接請求。
接收 sidecar 端的 xDS 請求,從 Config Controller、Service Controller 中獲取配置和服務信息,生成響應消息發送給 sidecar。
監聽來自 Config Controller 、Service Controller 的變化消息,并將配置、服務變化內容通過 xDS 接口推送到 sidecar。
配置信息監控與處理
ConfigController 是 Pilot 實現配置信息監控與處理的核心,它關聯的幾個關鍵的結構體如下:
//go?語言,源碼摘自?pilot-discovery,pilot-discovery?實現配置監聽的關鍵部分//?用于存儲?route?rule、virtualservice?等流量配置信息 type?ConfigStore?interface?{Schemas()?collection.SchemasGet(typ?resource.GroupVersionKind,?name,?namespace?string)?*ConfigList(typ?resource.GroupVersionKind,?namespace?string)?([]Config,?error)Create(config?Config)?(revision?string,?err?error)Update(config?Config)?(newRevision?string,?err?error)Delete(typ?resource.GroupVersionKind,?name,?namespace?string)?errorVersion()?stringGetResourceAtVersion(version?string,?key?string)?(resourceVersion?string,?err?error)GetLedger()?ledger.LedgerSetLedger(ledger.Ledger)?error }//?擴展了?ConfigStore?存儲,并提供資源處理的注冊函數,使用此函數注冊后,資源變更會回調?handler?處理 type?ConfigStoreCache?interface?{RegisterEventHandler(kind?resource.GroupVersionKind,?handler?func(Config,?Config,?Event))Run(stop?<-chan?struct{})HasSynced()?bool }//controller?實現了?ConfigStore?接口和?ConfigStoreCache?接口 type?controller?struct?{client?*Clientqueue??queue.Instancekinds??map[resource.GroupVersionKind]*cacheHandler }type?Task?func()?error//?controller?的?queue?的類型,包裝了?Task?任務 type?Instance?interface?{Push(task?Task)Run(<-chan?struct{}) }//initServiceControllers?下的?kubernets?下的?Controller?,由?initKubeRegistry?創建 func?NewController(client?kubernetes.Interface,?options?Options)?*Controller?{c?:=?&Controller{client:?????????????????????client,queue:??????????????????????queue.NewQueue(1?*?time.Second),...}...registerHandlers(c.services,?c.queue,?"Services",?c.onServiceEvent)ConfigController 用于處理 Istio 流控 CRD, 如 VirtualService、DestinationRule 等。
ConfigStore 對象利用 client-go 庫從 Kubernetes 獲取 RouteRule、VirtualService 等 CRD 形式存在控制面信息,轉換為 model 包下的 Config 對象,對外提供 Get、List、Create、Update、Delete 等 CRUD 服務。
ConfigStoreCache 則主要擴展了:注冊 Config 變更事件處理函數 RegisterEventHandler 、開始處理流程的 Run 方法。
Pilot 中,目前實現了 ConfigStoreCache 的 controller 主要有以下五種:
crd/controller/controller.go
serviceregistry/mcp/controller.go
kube/gateway/controller.go
kube/ingress/controller.go
memory/controller.go
其中比較關鍵的是 crd controller。CRD 是 CustomResourceDefinition 的縮寫 ,CRD Contriller 利用 SharedIndexInformer 實現對 CRD 資源的 list/watch。將 Add、Update、Delete 事件涉及到的 CRD 資源對象封裝為一個 Task ,并 push 到 ConfigController 的 queue 里,queue 隊列始終處于監聽狀態,只要隊列中有內容,就會回調 task 函數執行。關鍵代碼的實現如下:
//go?語言,源碼摘自?pilot-discovery,pilot-discovery?實現配置監聽的關鍵部分,接上一段代碼中的?registerHandlersfunc?registerHandlers(informer?cache.SharedIndexInformer,?q?queue.Instance,?otype?string,handler?func(interface{},?model.Event)?error)?{informer.AddEventHandler(cache.ResourceEventHandlerFuncs{AddFunc:?func(obj?interface{})?{...q.Push(...)...},UpdateFunc:?func(old,?cur?interface{})?{...q.Push(...)...},DeleteFunc:?func(obj?interface{})?{...q.Push(...)...},}) }//queue?的實現,始終等待執行?task func?(q?*queueImpl)?Run(stop?<-chan?struct{})?{...for?{if?len(q.tasks)?==?0?{return}task,?q.tasks?=?q.tasks[0],?q.tasks[1:]task()} }小結
本節為大家介紹了 Pilot 的架構和基本實現,后面我們將為大家介紹 istiod 中其他兩個組件 Citadel 和 Galley。
本書由阿里云高級技術專家王夕寧撰寫,詳細介紹 Istio 的基本原理與開發實戰,包含大量精選案例和參考代碼可以下載,可快速入門Istio開發。基于 Istio 1.4 版本,圖書為彩印。Gartner認為,2020年服務網格將成為所有領先的容器管理系統的標配技術。本書適合所有對微服務和云原生感興趣的讀者,推薦大家對本書進行深入的閱讀。
也歡迎大家關注 ServiceMesher 社區聯合編寫的 Istio Handbook:https://github.com/servicemesher/istio-handbook/
總結
以上是生活随笔為你收集整理的Istio Pilot架构解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C#黔驴技巧之实现统计结果排名
- 下一篇: 当模板方法遇到了委托函数,你的代码又可以