Istio技術與實踐02:源碼解析之Istio on Kubernetes 統一服務發現
前言

前言
前面文章《Istio源碼解析 Pilot服務發現的Adapter機制》結合Pilot的代碼實現介紹了Istio的抽象服務模型和基于該模型的數據結構定義,了解到Istio上只是定義的服務發現的接口,并未實現服務發現的功能,而是通過Adapter機制以一種可擴展的方式來集成各種不同的服務發現。本文重點講解Adapter機制在Kubernetes平臺上的使用。即Istio+Kubernetes如何實現服務發現。
Istio的官方設計上特別強調其架構上的可擴展性,即通過框架定義與實現解耦的方式來集成各種不同的實現。如Pilot上的adapter機制集成不同的服務注冊表,Mixer通過提供一個統一的面板給數據面Sidecar,后端可以通過模板定義的方式對接不同的Backend來進行各種訪問管理。但就現階段實現,從代碼或者文檔的細節去細看其功能,還是和Kubernetes結合最緊密。
Kubernetes和Istio的結合
從場景和架構上看Istio和Kubernetes都是非常契合的一種搭配。
首先從場景上看Kuberntes為應用負載的部署、運維、擴縮容等提供了強大的支持。通過Service機制提供了負載間訪問機制,通過域名結合Kubeproxy提供的轉發機制可以方便的訪問到對端的服務實例。因此如上圖可以認為Kubernetes提供了一定的服務發現和負載均衡能力,但是較深入細致的流量治理能力,因為Kubnernetes所處的基礎位置并未提供,而Istio正是補齊了這部分能力,兩者的結合提供了一個端到端的容器服務運行和治理的解決方案。
從架構看Istio和Kubernetes更是深度的結合。?得益于Kuberntes Pod的設計,數據面的Sidecar作為一種高性能輕量的代理自動注入到Pod中和業務容器部署在一起,接管業務容器的inbound和outbound的流量,從而實現對業務容器中服務訪問的治理。在控制面上Istio基于其Adapter機制集成Kubernetes的域名,從而避免了兩套名字服務的尷尬場景。
在本文中將結合Pilot的代碼實現來重點描述圖中上半部分的實現,下半部分的內容Pilot提供的通用的API給Envoy使用可參照上一篇文章的DiscoverServer部分的描述。
基于Kubernetes的服務發現
理解了Pilot的ServiceDiscovery的Adapter的主流程后,了解這部分內容比較容易。Pilot-discovery在initServiceControllers時,根據服務注冊配置的方式,如果是Kubernetes,則會走到這個分支來構造K8sServiceController。
case?serviceregistry.KubernetesRegistry:
s.createK8sServiceControllers(serviceControllers,?args);?err != nil {
return?err
}
創建controller其實就是創建了一個Kubenernetes的controller,可以看到List/Watch了Service、Endpoints、Node、Pod幾個資源對象。
// NewControllercreates a new Kubernetes controller
func NewController(clientkubernetes.Interface, options ControllerOptions) *Controller {
out := &Controller{
domainSuffix: options.DomainSuffix,
client:?????? client,
queue:??????? NewQueue(1 * time.Second),
}
out.services =out.createInformer(&v1.Service{}, "Service", options.ResyncPeriod,
func(opts meta_v1.ListOptions) (runtime.Object, error) {
return client.CoreV1().Services(options.WatchedNamespace).List(opts)
},
func(opts meta_v1.ListOptions) (watch.Interface, error) {
return client.CoreV1().Services(options.WatchedNamespace).Watch(opts)
})
out.endpoints =out.createInformer(&v1.Endpoints{}, "Endpoints", options.ResyncPeriod,
func(opts meta_v1.ListOptions) (runtime.Object, error) {
return client.CoreV1().Endpoints(options.WatchedNamespace).List(opts)
},
func(opts meta_v1.ListOptions) (watch.Interface, error) {
return client.CoreV1().Endpoints(options.WatchedNamespace).Watch(opts)
})
out.nodes =out.createInformer(&v1.Node{}, "Node", options.ResyncPeriod,
func(opts meta_v1.ListOptions) (runtime.Object, error) {
return client.CoreV1().Nodes().List(opts)
},
func(opts meta_v1.ListOptions) (watch.Interface, error) {
return client.CoreV1().Nodes().Watch(opts)
})
out.pods =newPodCache(out.createInformer(&v1.Pod{}, "Pod", options.ResyncPeriod,
func(opts meta_v1.ListOptions) (runtime.Object, error) {
return client.CoreV1().Pods(options.WatchedNamespace).List(opts)
},
func(opts meta_v1.ListOptions) (watch.Interface, error) {
return client.CoreV1().Pods(options.WatchedNamespace).Watch(opts)
}))
return out
}
在?createInformer?中其實就是創建了SharedIndexInformer。這種方式在Kubernetes的各種Controller中廣泛使用。Informer調用?APIserver的 List 和 Watch 兩種類型的 API。在初始化的時,先調用 List API 獲得全部資源對象,緩存在內存中; 然后,調用 Watch API 去 Watch這種這種資源對象,維護緩存。
Service informer := cache.NewSharedIndexInformer(
&cache.ListWatch{ListFunc: lf, WatchFunc: wf}, o,
resyncPeriod, cache.Indexers{})
下面看下Kubernetes場景下對ServiceDiscovery接口的實現。我們看下Kubernetes下提供的服務發現的接口,包括獲取服務列表和服務實例列表。
func (c *Controller) GetService(hostname model.Hostname) (*model.Service, error) {
name, namespace, err := parseHostname(hostname)
item, exists := c.serviceByKey(name, namespace)
svc := convertService(*item, c.domainSuffix)
return svc, nil
}
最終是從infromer的緩存中獲取Service資源對象。
func (c *Controller) serviceByKey(name, namespace string) (*v1.Service, bool) {
item, exists, err := c.services.informer.GetStore().GetByKey(KeyFunc(name, namespace))
return item.(*v1.Service), true
}
獲取服務實例列表也是類似,也是從Informer的緩存中獲取對應資源,只是涉及的對象和處理過程比Service要復雜一些。
func (c *Controller) InstancesByPort(hostname model.Hostname, reqSvcPort int,
labelsList model.LabelsCollection) ([]*model.ServiceInstance, error) {
// Get actual service by name
name, namespace, err := parseHostname(hostname)
item, exists := c.serviceByKey(name, namespace)
svc := convertService(*item, c.domainSuffix)
svcPortEntry, exists := svc.Ports.GetByPort(reqSvcPort)
for _, item := range c.endpoints.informer.GetStore().List() {
ep := *item.(*v1.Endpoints)
…
}
...
}
}
return nil, nil
}
可以看到就是做了如下的轉換,將Kubernetes的對一個服務發現的數據結構轉換成Istio的抽象模型對應的數據結構。
其實在conversion.go中提供了多個convert的方法將Kubernetes的數據對象轉換成Istio的標準格式。除了上面的對Service、Instance的convert外,還包含對port,label、protocol的convert。如下面protocol的convert就值得一看。
func ConvertProtocol(name string, proto v1.Protocol) model.Protocol {
out := model.ProtocolTCP
switch proto {
case v1.ProtocolUDP:
out = model.ProtocolUDP
case v1.ProtocolTCP:
prefix := name
i := strings.Index(name, "-")
if i >= 0 {
prefix = name[:i]
}
protocol := model.ParseProtocol(prefix)
if protocol != model.ProtocolUDP && protocol != model.ProtocolUnsupported {
out = protocol
}
}
return out
}
看過Istio文檔的都知道在使用Istio和Kuberntes結合的場景下創建Pod時要求滿足4個約束。其中重要的一個是Port必須要有名,且Port的名字名字的格式有嚴格要求:Service 的端口必須命名,且端口的名字必須滿足格式
另外同時在Informer?中添加對add、delete和update事件的回調,分別對應?informer?監聽到創建、更新和刪除這三種事件類型。可以看到這里是將待執行的回調操作包裝成一個task,再壓到Queue中,然后在Queue的run()方法中拿出去挨個執行,這部分不細看了。
到這里Kuberntes特有的服務發現能力就介紹完了。即kube\controller也實現了ServiceDiscovery中規定的服務發現的接口中定義的全部發方法。除了初始化了一個kube controller來從Kubeapiserver中獲取和維護服務發現數據外,在pilot server初始化的時候,還有一個重要的initDiscoveryService初始化DiscoveryServer,這個discoveryserver使用contrller,其實是ServiceDiscovery上的服務發現供。發布成通用協議的接口,V1是rest,V2是gRPC,進而提供服務發現的能力給Envoy調用,這部分是Pilot服務發現的通用機制,在上篇文章的adapter機制中有詳細描述,這里不再贅述。
總結
以上介紹了istio基于Kubernetes的名字服務實現服務發現的機制和流程。整個調用關系如下,可以看到和其他的Adapter實現其實類似。
1.KubeController使用List/Watch獲取和維護服務列表和其他需求的資源對象,提供轉換后的標準的服務發現接口和數據結構;
2.Discoveryserver基于Controller上維護的服務發現數據,發布成gRPC協議的服務供Envoy使用。
前面只是提到了服務發現的數據維護,可以看到在Kubernetes場景下,Istio只是是使用了kubeAPIServer中service這種資源對象。在執行層面,說到Service就不得不說Kuberproxy,因為Service只是一個邏輯的描述,真正執行轉發動作的是Kubeproxy,他運行在集群的每個節點上,把對Service的訪問轉發到后端pod上。在引入Istio后,將不再使用Kubeproxy做轉發了,而是將這些映射關系轉換成為pilot的轉發模型,下發到envoy進行轉發,執行更復雜的控制。這些在后面分析Discoveryserver和Sidecar的交互時再詳細分析。
在和Kubnernetes結合的場景下
強調下幾個概念:
1.Istio的治理Service就是Kubernetes的Service。不管是服務描述的manifest還是存在于服務注冊表中服務的定義都一樣。
2.Istio治理的是服務間的通信。這里的服務并不一定是所謂的微服務,并不在乎是否做了微服務化。只要有服務間的訪問,需要治理就可以用。一個大的單體服務打成一個鏡像在Kuberntes里部署起來被其他負載訪問和分拆成微服務后被訪問,在治理看來沒有任何差別。
本文只是描述了在服務發現上兩者的結合,隨著分析的深入,會發現Istio和Kubernetes的更多契合。K8s編排容器服務已經成為一種事實上的標準;微服務與容器在輕量、快速部署運維等特征的匹配,微服務運行在容器中也正成為一種標準實踐;隨著istio的成熟和ServiceMesh技術的流行,使用Istio進行微服務治理的實踐也正越來越多;而istio和k8s的這種天然融合使得上面形成了一個完美的閉環。對于云原生應用,采用kubernetes構建微服務部署和集群管理能力,采用Istio構建服務治理能力,也將成為微服務真正落地的一個最可能的途徑。有幸參與其中讓我們一起去見證和經歷這個趨勢吧。
華為云APP
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。