本文結合 Pilot 中的關鍵代碼來講明下 Istio 的服務發現,並以 Eureka 爲例看下 Adapter 的實現機制。能夠了解到:web
- Istio 的服務模型
- Istio 發現的機制和原理
- Istio 服務發現的 adpater 機制
基於以上了解能夠根據需開發集成自有的服務註冊表。json
首先,Istio 做爲一個(微)服務治理的平臺,和其餘的微服務模型同樣也提供了 Service,ServiceInstance 這樣抽象服務模型。如 Service 的定義中所表達的,一個服務有一個全域名,能夠有一個或多個偵聽端口。後端
type Service struct {
// Hostname of the service, e.g. "catalog.mystore.com"
Hostname Hostname `json:"hostname"`
Address string `json:"address,omitempty"`
Addresses map[string]string `json:"addresses,omitempty"`
// Ports is the set of network ports where the service is listening for connections
Ports PortList `json:"ports,omitempty"`
ExternalName Hostname `json:"external"`
...
}
複製代碼
固然這裏的 Service 不僅是 mesh 裏定義的 service,還能夠是經過 serviceEntry 接入的外部服務。bash
每一個port的定義在這裏:數據結構
type Port struct {
Name string `json:"name,omitempty"`
Port int `json:"port"`
Protocol Protocol `json:"protocol,omitempty"`
}
複製代碼
除了port號外,還有 一個 name 和 protocol。能夠看到支持這麼幾個 Protocolapp
const (
ProtocolGRPC Protocol = "GRPC"
ProtocolHTTPS Protocol = "HTTPS"
ProtocolHTTP2 Protocol = "HTTP2"
ProtocolHTTP Protocol = "HTTP"
ProtocolTCP Protocol = "TCP"
ProtocolUDP Protocol = "UDP"
ProtocolMongo Protocol = "Mongo"
ProtocolRedis Protocol = "Redis"
ProtocolUnsupported Protocol = "UnsupportedProtocol"
)
複製代碼
而每一個服務實例 ServiceInstance 的定義以下:負載均衡
type ServiceInstance struct {
Endpoint NetworkEndpoint `json:"endpoint,omitempty"`
Service *Service `json:"service,omitempty"`
Labels Labels `json:"labels,omitempty"`
AvailabilityZone string `json:"az,omitempty"`
ServiceAccount string `json:"serviceaccount,omitempty"`
}
複製代碼
熟悉 SpringCloud 的朋友對比下 SpringCloud 中對應 interface,能夠看到主要字段基本徹底同樣。ide
public interface ServiceInstance {
String getServiceId();
String getHost();
int getPort();
boolean isSecure();
URI getUri();
Map<string< span="" style="word-wrap: break-word;box-sizing: border-box;outline: none;-webkit-appearance: none;word-break: break-word;-webkit-tap-highlight-color: transparent;">, String> getMetadata();
}</string<><string< span="" style="word-wrap: break-word;box-sizing: border-box;outline: none;-webkit-appearance: none;word-break: break-word;-webkit-tap-highlight-color: transparent;"></string<>
複製代碼
以上的服務定義的代碼分析,結合官方 spec 能夠很是清楚的定義了服務發現的數據模型。可是,Istio 自己沒有提供服務發現註冊和服務發現的能力,翻遍代碼目錄也找不到一個存儲服務註冊表的服務。Discovery 部分的文檔是這樣來描述的:微服務
對於服務註冊,Istio 認爲已經存在一個服務註冊表來維護應用程序的服務實例(Pod、VM),包括服務實例會自動註冊這個服務註冊表上;不健康的實例從目錄中刪除。而服務發現的功能是 Pilot 提供了通用的服務發現接口,供數據面調用動態更新實例。源碼分析
即:Istio 自己不提供服務發現能力,而是提供了一種 adapter 的機制來適配各類不一樣的平臺。
具體講,Istio 的服務發如今 Pilot 中完成,經過如下框圖能夠看到,Pilot提供了一種平臺 Adapter,能夠對接多種不一樣的平臺獲取服務註冊信息,並轉換成Istio通用的抽象模型。
從pilot的代碼目錄也能夠清楚看到,至少支持consul、k8s、eureka、cloudfoundry等平臺。服務發現的幾重要方法方法和前面看到的 Service 的抽象模型一塊兒定義在 service 中。能夠認爲是 Istio 服務發現的幾個主要行爲。
// ServiceDiscovery enumerates Istio service instances.
type ServiceDiscovery interface {
// 服務列表
Services() ([]*Service, error)
// 根據域名的獲得服務
GetService(hostname Hostname) (*Service, error)
// 被InstancesByPort代替
Instances(hostname Hostname, ports []string, labels LabelsCollection) ([]*ServiceInstance, error)
//根據端口和標籤檢索服務實例,最重要的以方法。
InstancesByPort(hostname Hostname, servicePort int, labels LabelsCollection) ([]*ServiceInstance, error)
//根據proxy查詢服務實例,若是是sidecar和pod裝在一塊兒,則返回該服務實例,若是隻是裝了sidecar,相似gateway,則返回空
GetProxyServiceInstances(*Proxy) ([]*ServiceInstance, error)
ManagementPorts(addr string) PortList
}
複製代碼
下面選擇其中最簡單也多是你們最熟悉的 Eureka 的實現來看下這個 adapter 機制的工做過程.
Pilot 有三個獨立的服務分別是 agent,discovery和sidecar-injector。分別提供 sidecar 的管理,服務發現和策略管理,sidecar自動注入的功能。Discovery的入口都是 pilot 的 pilot-discovery。
在 service 初始化時候,初始化 ServiceController 和 DiscoveryService。
if err := s.initServiceControllers(&args); err != nil {
returnnil, err
}
if err := s.initDiscoveryService(&args); err != nil {
returnnil, err
}
複製代碼
前者是構造一個 controller 來構造服務發現數據,後者是提供一個 DiscoveryService,發佈服務發現數據,後面的分析能夠看到這個 DiscoveryService 向 Envoy 提供的服務發現數據正是來自 Controller構造的數據。咱們分開來看。
首先看 Controller。在 initServiceControllers 根據不一樣的registry類型構造不一樣 的conteroller 實現。如對於 Eureka 的註冊類型,構造了一個 Eurkea 的 controller。
case serviceregistry.EurekaRegistry:
eurekaClient := eureka.NewClient(args.Service.Eureka.ServerURL)
serviceControllers.AddRegistry(
aggregate.Registry{
Name: serviceregistry.ServiceRegistry(r),
ClusterID: string(serviceregistry.EurekaRegistry),
Controller: eureka.NewController(eurekaClient, args.Service.Eureka.Interval),
ServiceDiscovery: eureka.NewServiceDiscovery(eurekaClient),
ServiceAccounts: eureka.NewServiceAccounts(),
})
複製代碼
能夠看到 controller 裏包裝了 Eureka 的 client 做爲句柄,不難猜到服務發現的邏輯正式這個 client 連 Eureka 的名字服務的 server 獲取到。
func NewController(client Client, interval time.Duration) model.Controller {
return &controller{
interval: interval,
serviceHandlers: make([]serviceHandler, 0),
instanceHandlers: make([]instanceHandler, 0),
client: client,
}
}
複製代碼
ServiceDiscovery 中定義的幾個重要方法,咱們拿最重要的 InstancesByPort 來看下在 Eureka 下是怎麼支持,其餘的幾個都相似。能夠看到就是使用 Eureka client 去連 Eureka server 去獲取服務發現數據,而後轉換成istio通用的 Service 和 ServiceInstance 的數據結構。分別要轉換 convertServices convertServiceInstances convertPorts convertProtocol 等。
// InstancesByPort implements a service catalog operation
func (sd *serviceDiscovery) InstancesByPort(hostname model.Hostname, port int,
tagsList model.LabelsCollection) ([]*model.ServiceInstance, error) {
apps, err := sd.client.Applications()
services := convertServices(apps, map[model.Hostname]bool{hostname: true})
out := make([]*model.ServiceInstance, 0)
for _, instance := range convertServiceInstances(services, apps) {
out = append(out, instance)
}
return out, nil
}
複製代碼
Eureka client 或服務發現數據看一眼,其實就是經過 Rest 方式訪問/eureka/v2/apps 連 Eureka集羣來獲取服務實例的列表。
func (c *client) Applications() ([]*application, error) {
req, err := http.NewRequest("GET", c.url+appsPath, nil)
req.Header.Set("Accept", "application/json")
resp, err := c.client.Do(req)
data, err := ioutil.ReadAll(resp.Body)
var apps getApplications
if err = json.Unmarshal(data, &apps); err != nil {
return nil, err
}
return apps.Applications.Applications, nil
}
複製代碼
Application 是本地對 Instinstance對象的包裝。
type application struct {
Name string `json:"name"`
Instances []*instance `json:"instance"`
}
複製代碼
又看到了 eureka 熟悉的 ServiceInstance 的定義。當年有個同志提到一個方案是往 metadata 這個 map 裏塞租戶信息,在 eureka 上作多租。
type instance struct { // nolint: maligned
Hostname string `json:"hostName"`
IPAddress string `json:"ipAddr"`
Status string `json:"status"`
Port port `json:"port"`
SecurePort port `json:"securePort"`
Metadata metadata `json:"metadata,omitempty"`
}
複製代碼
以上咱們就看完了服務發現數據生成的過程。對接名字服務的服務發現接口,獲取數據,轉換成Istio抽象模型中定義的標準格式。下面看下這些服務發現數據怎麼提供出去被Envoy使用的。
在 pilot server 初始化的時候,除了前面初始化了一個 controller 外,還有一個重要的 initDiscoveryService 初始化 Discoveryservice
environment := model.Environment{
Mesh: s.mesh,
IstioConfigStore: model.MakeIstioStore(s.configController),
ServiceDiscovery: s.ServiceController,
..
}
…
s.EnvoyXdsServer = envoyv2.NewDiscoveryServer(environment, v1alpha3.NewConfigGenerator(registry.NewPlugins()))
s.EnvoyXdsServer.Register(s.GRPCServer)
..
複製代碼
即構造 gRPC server 提供了對外的服務發現接口。DiscoveryServer 定義以下
//Pilot支持Evnoy V2的xds的API
type DiscoveryServer struct {
// env is the model environment.
env model.Environment
ConfigGenerator *v1alpha3.ConfigGeneratorImpl
modelMutex sync.RWMutex
services []*model.Service
virtualServices []*networking.VirtualService
virtualServiceConfigs []model.Config
}
複製代碼
即提供了這個 grpc 的服務發現 Server,sidecar 經過這個 server 獲取服務發現的數據,而 server 使用到的各個服務發現的功能經過 Environment中的 ServiceDiscovery 句柄來完成。從前面 environment 的構造能夠看到這個 ServiceDiscovery 正是上一個 init 構造的 controller。
// Environment provides an aggregate environmental API for Pilot
type Environment struct {
// Discovery inte**ce for listing services and instances.
ServiceDiscovery
複製代碼
DiscoveryServer 在以下文件中開發了對應的接口,即所謂的 XDS API,能夠看到這些API都定義在 envoyproxy/go-control-plane/envoy/service/discovery/v2 下面,即對應數據面服務發現的標準API。Pilot 和很 Envoy 這套 API 的通訊方式,包括接口定義咱們在後面詳細展開。
這樣幾個功能組件的交互會是這個樣子。 Controller 使用 EurekaClient 來獲取服務列表,提供轉換後的標準的服務發現接口和數據結構。Discoveryserver 基於 Controller 上維護的服務發現數據,發佈成 gRPC 協議的服務供 Envoy使用。
很是不幸的是,碼完這篇文字碼完的時候,收到社區裏 merge 了這個 PR :由於 Eureka v2.0 has been discontinued,Istio 服務發現裏 removed eureka adapter 。即1.0版本後再也看不到 Istio 對 Eureka 的支持了。這裏描述的例子真的就成爲一個例子了。
咱們以官方文檔上這張經典的圖來端到端的串下整個服務發現的邏輯:
Pilot 中定義了 Istio 通用的服務發現模型,即開始分析到的幾個數據結構;
Pilot 使用 adapter 方式對接不一樣的(雲平臺的)的服務目錄,提取服務註冊信息;
Pilot 使用將2中服務註冊信息轉換成1中定義的自定義的數據結構。
Pilot 提供標準的服務發現接口供數據面調用。
數據面獲取服務服務發現數據,並基於這些數據更新sidecar後端的LB實例列表,進而根據相應的負載均衡策略將請求轉發到對應的目標實例上。
文中着重描述以上的通用模板流程和通常機制,不少細節忽略掉了。後續根據須要對於以上點上的重要功能會展開。如以上2和3步驟在 Kubernetes 中如何支持將在後面一篇文章《Istio 技術與實踐02:Istio 源碼分析之 Istio+Kubernetes 的服務發現》中重點描述,將瞭解到在 Kubernetes 環境下,Istio 如何使用 Pilot 服務發現的 Adapter 方式集成 Kubernetes 的 Service 資源,從而解決長久以來在 Kubernetes 上運行微服務使用兩套名字服務的尷尬局面。
注:文中代碼基於commit: 505af9a54033c52137becca1149744b15aebd4ba