Istio Pilot代碼深度解析

本文作者趙化冰,將在明天下午 1 點(diǎn)半在成都螞蟻 C 空間為大家分享《服務(wù)網(wǎng)格技術(shù)在5G網(wǎng)絡(luò)管理平臺(tái)中的落地實(shí)踐》歡迎大家逆趋,查看活動(dòng)詳情蠢沿。

Istio Pilot 組件介紹

在Istio架構(gòu)中,Pilot組件屬于最核心的組件匾乓,負(fù)責(zé)了服務(wù)網(wǎng)格中的流量管理以及控制面和數(shù)據(jù)面之間的配置下發(fā)录煤。Pilot內(nèi)部的代碼結(jié)構(gòu)比較復(fù)雜,本文中我們將通過(guò)對(duì)Pilot的代碼的深入分析來(lái)了解Pilot實(shí)現(xiàn)原理荞胡。

首先我們來(lái)看一下Pilot在Istio中的功能定位妈踊,Pilot將服務(wù)信息和配置數(shù)據(jù)轉(zhuǎn)換為xDS接口的標(biāo)準(zhǔn)數(shù)據(jù)結(jié)構(gòu),通過(guò)gRPC下發(fā)到數(shù)據(jù)面的Envoy泪漂。如果把Pilot看成一個(gè)處理數(shù)據(jù)的黑盒廊营,則其有兩個(gè)輸入,一個(gè)輸出:

目前Pilot的輸入包括兩部分?jǐn)?shù)據(jù)來(lái)源:

  • 服務(wù)數(shù)據(jù): 來(lái)源于各個(gè)服務(wù)注冊(cè)表(Service Registry)萝勤,例如Kubernetes中注冊(cè)的Service露筒,Consul Catalog中的服務(wù)等。
  • 配置規(guī)則: 各種配置規(guī)則敌卓,包括路由規(guī)則及流量管理規(guī)則等慎式,通過(guò)Kubernetes CRD(Custom Resources Definition)形式定義并存儲(chǔ)在Kubernetes中。

Pilot的輸出為符合x(chóng)DS接口的數(shù)據(jù)面配置數(shù)據(jù)趟径,并通過(guò)gRPC Streaming接口將配置數(shù)據(jù)推送到數(shù)據(jù)面的Envoy中瘪吏。

備注:Istio代碼庫(kù)在不停變化更新中,本文分析所基于的代碼commit為: d539abe00c2599d80c6d64296f78d3bb8ab4b033

Pilot-Discovery 代碼結(jié)構(gòu)

Istio Pilot的代碼分為Pilot-Discovery和Pilot-Agent蜗巧,其中Pilot-Agent用于在數(shù)據(jù)面負(fù)責(zé)Envoy的生命周期管理掌眠,Pilot-Discovery才是控制面進(jìn)行流量管理的組件,本文將重點(diǎn)分析控制面部分幕屹,即Pilot-Discovery的代碼蓝丙。

下圖是Pilot-Discovery組件代碼的主要結(jié)構(gòu):

Pilot-Discovery的入口函數(shù)為:pilot/cmd/pilot-discovery/main.go中的main方法级遭。main方法中創(chuàng)建了Discovery Server,Discovery Server中主要包含三部分邏輯:

Config Controller

Config Controller用于管理各種配置數(shù)據(jù)渺尘,包括用戶創(chuàng)建的流量管理規(guī)則和策略挫鸽。Istio目前支持三種類(lèi)型的Config Controller:

  • Kubernetes:使用Kubernetes來(lái)作為配置數(shù)據(jù)的存儲(chǔ),該方式直接依附于Kubernetes強(qiáng)大的CRD機(jī)制來(lái)存儲(chǔ)配置數(shù)據(jù)沧烈,簡(jiǎn)單方便掠兄,是Istio最開(kāi)始使用的配置存儲(chǔ)方案。
  • MCP (Mesh Configuration Protocol):使用Kubernetes來(lái)存儲(chǔ)配置數(shù)據(jù)導(dǎo)致了Istio和Kubernetes的耦合锌雀,限制了Istio在非Kubernetes環(huán)境下的運(yùn)用蚂夕。為了解決該耦合,Istio社區(qū)提出了MCP腋逆,MCP定義了一個(gè)向Istio控制面下發(fā)配置數(shù)據(jù)的標(biāo)準(zhǔn)協(xié)議婿牍,Istio Pilot作為MCP Client,任何實(shí)現(xiàn)了MCP協(xié)議的Server都可以通過(guò)MCP協(xié)議向Pilot下發(fā)配置惩歉,從而解除了Istio和Kubernetes的耦合等脂。如果想要了解更多關(guān)于MCP的內(nèi)容,請(qǐng)參考文后的鏈接撑蚌。
  • Memory:一個(gè)在內(nèi)存中的Config Controller實(shí)現(xiàn)上遥,主要用于測(cè)試。

目前Istio的配置包括:

  • Virtual Service: 定義流量路由規(guī)則争涌。
  • Destination Rule: 定義和一個(gè)服務(wù)或者subset相關(guān)的流量處理規(guī)則粉楚,包括負(fù)載均衡策略疹吃,連接池大小徘跪,斷路器設(shè)置,subset定義等等厨钻。
  • Gateway: 定義入口網(wǎng)關(guān)上對(duì)外暴露的服務(wù)饮潦。
  • Service Entry: 通過(guò)定義一個(gè)Service Entry可以將一個(gè)外部服務(wù)手動(dòng)添加到服務(wù)網(wǎng)格中燃异。
  • Envoy Filter: 通過(guò)Pilot在Envoy的配置中添加一個(gè)自定義的Filter。

Service Controller

Service Controller用于管理各種Service Registry继蜡,提出服務(wù)發(fā)現(xiàn)數(shù)據(jù)回俐,目前Istio支持的Service Registry包括:

  • Kubernetes:對(duì)接Kubernetes Registry,可以將Kubernetes中定義的Service和Instance采集到Istio中稀并。
  • Consul: 對(duì)接Consul Catalog鲫剿,將Consul中定義的Service采集到Istio中。
  • MCP: 和MCP config controller類(lèi)似稻轨,從MCP Server中獲取Service和Service Instance灵莲。
  • Memory: 一個(gè)內(nèi)存中的Service Controller實(shí)現(xiàn),主要用于測(cè)試殴俱。

Discovery Service

Discovery Service中主要包含下述邏輯:

  • 啟動(dòng)gRPC Server并接收來(lái)自Envoy端的連接請(qǐng)求政冻。
  • 接收Envoy端的xDS請(qǐng)求枚抵,從Config Controller和Service Controller中獲取配置和服務(wù)信息,生成響應(yīng)消息發(fā)送給Envoy明场。
  • 監(jiān)聽(tīng)來(lái)自Config Controller的配置變化消息和來(lái)自Service Controller的服務(wù)變化消息汽摹,并將配置和服務(wù)變化內(nèi)容通過(guò)xDS接口推送到Envoy。(備注:目前Pilot未實(shí)現(xiàn)增量變化推送苦锨,每次變化推送的是全量配置逼泣,在網(wǎng)格中服務(wù)較多的情況下可能會(huì)有性能問(wèn)題)。

Pilot-Discovery 業(yè)務(wù)流程

Pilot-Disocvery包括以下主要的幾個(gè)業(yè)務(wù)流程:

初始化Pilot-Discovery的各個(gè)主要組件

Pilot-Discovery命令的入口為pilot/cmd/pilot-discovery/main.go中的main方法舟舒,在該方法中創(chuàng)建Pilot Server,Server代碼位于文件pilot/pkg/bootstrap/server.go中拉庶。Server主要做了下面一些初始化工作:

  • 創(chuàng)建并初始化Config Controller。
  • 創(chuàng)建并初始化Service Controller秃励。
  • 創(chuàng)建并初始化Discovery Server氏仗,Pilot中創(chuàng)建了基于Envoy V1 API的HTTP Discovery Server和基于Envoy V2 API的GPRC Discovery Server。由于V1已經(jīng)被廢棄夺鲜,本文將主要分析V2 API的gRPC Discovery Server皆尔。
  • 將Discovery Server注冊(cè)為Config Controller和Service Controller的Event Handler,監(jiān)聽(tīng)配置和服務(wù)變化消息币励。

創(chuàng)建gRPC Server并接收Envoy的連接請(qǐng)求

Pilot Server創(chuàng)建了一個(gè)gRPC Server慷蠕,用于監(jiān)聽(tīng)和接收來(lái)自Envoy的xDS請(qǐng)求。pilot/pkg/proxy/envoy/v2/ads.go 中的 DiscoveryServer.StreamAggregatedResources方法被注冊(cè)為gRPC Server的服務(wù)處理方法食呻。

當(dāng)gRPC Server收到來(lái)自Envoy的連接時(shí)流炕,會(huì)調(diào)用DiscoveryServer.StreamAggregatedResources方法,在該方法中創(chuàng)建一個(gè)XdsConnection對(duì)象搁进,并開(kāi)啟一個(gè)goroutine從該connection中接收客戶端的xDS請(qǐng)求并進(jìn)行處理浪感;如果控制面的配置發(fā)生變化昔头,Pilot也會(huì)通過(guò)該connection把配置變化主動(dòng)推送到Envoy端饼问。

配置變化后向Envoy推送更新

這是Pilot中最復(fù)雜的一個(gè)業(yè)務(wù)流程,主要是因?yàn)榇a中采用了多個(gè)channel和queue對(duì)變化消息進(jìn)行合并和轉(zhuǎn)發(fā)揭斧。該業(yè)務(wù)流程如下:

  1. Config Controller或者Service Controller在配置或服務(wù)發(fā)生變化時(shí)通過(guò)回調(diào)方法通知Discovery Server莱革,Discovery Server將變化消息放入到Push Channel中。
  2. Discovery Server通過(guò)一個(gè)goroutine從Push Channel中接收變化消息讹开,將一段時(shí)間內(nèi)連續(xù)發(fā)生的變化消息進(jìn)行合并盅视。如果超過(guò)指定時(shí)間沒(méi)有新的變化消息,則將合并后的消息加入到一個(gè)隊(duì)列Push Queue中旦万。
  3. 另一個(gè)goroutine從Push Queue中取出變化消息闹击,生成XdsEvent,發(fā)送到每個(gè)客戶端連接的Push Channel中成艘。
  4. 在DiscoveryServer.StreamAggregatedResources方法中從Push Channel中取出XdsEvent赏半,然后根據(jù)上下文生成符合x(chóng)DS接口規(guī)范的DiscoveryResponse贺归,通過(guò)gRPC推送給Envoy端。(gRPC會(huì)為每個(gè)client連接單獨(dú)分配一個(gè)goroutine來(lái)進(jìn)行處理断箫,因此不同客戶端連接的StreamAggregatedResources處理方法是在不同goroutine中處理的)

響應(yīng)Envoy主動(dòng)發(fā)起的xDS請(qǐng)求

Pilot和Envoy之間建立的是一個(gè)雙向的Streaming gRPC服務(wù)調(diào)用拂酣,因此Pilot可以在配置變化時(shí)向Envoy推送,Envoy也可以主動(dòng)發(fā)起xDS調(diào)用請(qǐng)求獲取配置仲义。Envoy主動(dòng)發(fā)起xDS請(qǐng)求的流程如下:

  1. Envoy通過(guò)創(chuàng)建好的gRPC連接發(fā)送一個(gè)DiscoveryRequest
  2. Discovery Server通過(guò)一個(gè)goroutine從XdsConnection中接收來(lái)自Envoy的DiscoveryRequest婶熬,并將請(qǐng)求發(fā)送到ReqChannel中
  3. Discovery Server的另一個(gè)goroutine從ReqChannel中接收DiscoveryRequest,根據(jù)上下文生成符合x(chóng)DS接口規(guī)范的DiscoveryResponse埃撵,然后返回給Envoy赵颅。

Discovery Server業(yè)務(wù)處理關(guān)鍵代碼片段

下面是Discovery Server的關(guān)鍵代碼片段和對(duì)應(yīng)的業(yè)務(wù)邏輯注解,為方便閱讀盯另,代碼中只保留了邏輯主干性含,去掉了一些不重要的細(xì)節(jié)。

處理xDS請(qǐng)求和推送的關(guān)鍵代碼

該部分關(guān)鍵代碼位于 istio.io/istio/pilot/pkg/proxy/envoy/v2/ads.go 文件的StreamAggregatedResources 方法中鸳惯。StreamAggregatedResources方法被注冊(cè)為gRPC Server的handler商蕴,對(duì)于每一個(gè)客戶端連接,gRPC Server會(huì)啟動(dòng)一個(gè)goroutine來(lái)進(jìn)行處理芝发。

代碼中主要包含以下業(yè)務(wù)邏輯:

  • 從gRPC連接中接收來(lái)自Envoy的xDS 請(qǐng)求绪商,并放到一個(gè)channel reqChannel中。
  • 從reqChannel中接收xDS請(qǐng)求辅鲸,根據(jù)xDS請(qǐng)求的類(lèi)型構(gòu)造響應(yīng)并發(fā)送給Envoy格郁。
  • 從connection的pushChannel中接收Service或者Config變化后的通知,構(gòu)造xDS響應(yīng)消息独悴,將變化內(nèi)容推送到Envoy端例书。
// StreamAggregatedResources implements the ADS interface.
func (s *DiscoveryServer) StreamAggregatedResources(stream ads.AggregatedDiscoveryService_StreamAggregatedResourcesServer) error {
        
    ......

    //創(chuàng)建一個(gè)goroutine來(lái)接收來(lái)自Envoy的xDS請(qǐng)求,并將請(qǐng)求放到reqChannel中
    con := newXdsConnection(peerAddr, stream)
    reqChannel := make(chan *xdsapi.DiscoveryRequest, 1)
    go receiveThread(con, reqChannel, &receiveError)

     ......
    
    for {
        select{
        //從reqChannel接收Envoy端主動(dòng)發(fā)起的xDS請(qǐng)求
        case discReq, ok := <-reqChannel:        
            //根據(jù)請(qǐng)求的類(lèi)型構(gòu)造相應(yīng)的xDS Response并發(fā)送到Envoy端
            switch discReq.TypeUrl {
            case ClusterType:
                err := s.pushCds(con, s.globalPushContext(), versionInfo())
            case ListenerType:
                err := s.pushLds(con, s.globalPushContext(), versionInfo())
            case RouteType:
                err := s.pushRoute(con, s.globalPushContext(), versionInfo())
            case EndpointType:
                err := s.pushEds(s.globalPushContext(), con, versionInfo(), nil)
            }

        //從PushChannel接收Service或者Config變化后的通知
        case pushEv := <-con.pushChannel:
            //將變化內(nèi)容推送到Envoy端
            err := s.pushConnection(con, pushEv)   
        }            
    }
}

處理服務(wù)和配置變化的關(guān)鍵代碼

該部分關(guān)鍵代碼位于 istio.io/istio/pilot/pkg/proxy/envoy/v2/discovery.go 文件中刻炒,用于監(jiān)聽(tīng)服務(wù)和配置變化消息决采,并將變化消息合并后通過(guò)Channel發(fā)送給前面提到的 StreamAggregatedResources 方法進(jìn)行處理。

ConfigUpdate是處理服務(wù)和配置變化的回調(diào)函數(shù)坟奥,service controller和config controller在發(fā)生變化時(shí)會(huì)調(diào)用該方法通知Discovery Server树瞭。

func (s *DiscoveryServer) ConfigUpdate(req *model.PushRequest) {
  inboundConfigUpdates.Increment()

  //服務(wù)或配置變化后,將一個(gè)PushRequest發(fā)送到pushChannel中
  s.pushChannel <- req
}

在debounce方法中將連續(xù)發(fā)生的PushRequest進(jìn)行合并爱谁,如果一段時(shí)間內(nèi)沒(méi)有收到新的PushRequest晒喷,再發(fā)起推送;以避免由于服務(wù)和配置頻繁變化給系統(tǒng)帶來(lái)較大壓力访敌。

// The debounce helper function is implemented to enable mocking
func debounce(ch chan *model.PushRequest, stopCh <-chan struct{}, pushFn func(req *model.PushRequest)) {

    ......

    pushWorker := func() {
        eventDelay := time.Since(startDebounce)
        quietTime := time.Since(lastConfigUpdateTime)

        // it has been too long or quiet enough
        //一段時(shí)間內(nèi)沒(méi)有收到新的PushRequest凉敲,再發(fā)起推送
        if eventDelay >= DebounceMax || quietTime >= DebounceAfter {
            if req != nil {
                pushCounter++
                adsLog.Infof("Push debounce stable[%d] %d: %v since last change, %v since last push, full=%v",
                pushCounter, debouncedEvents,
                quietTime, eventDelay, req.Full)

                free = false
                go push(req)
                req = nil
                debouncedEvents = 0
            }
        } else {
           timeChan = time.After(DebounceAfter - quietTime)
        }
    }
    for {
        select {
        ......

        case r := <-ch:
            lastConfigUpdateTime = time.Now()
            if debouncedEvents == 0 {
                timeChan = time.After(DebounceAfter)
                startDebounce = lastConfigUpdateTime
            }
            debouncedEvents++
            //合并連續(xù)發(fā)生的多個(gè)PushRequest
            req = req.Merge(r)
        case <-timeChan:
           if free {
               pushWorker()
            }
        case <-stopCh:
            return
    }
  }
}

完整的業(yè)務(wù)流程

參考閱讀

關(guān)于 ServiceMeshe 社區(qū)

ServiceMesher 社區(qū)是由一群擁有相同價(jià)值觀和理念的志愿者們共同發(fā)起,于 2018 年 4 月正式成立。

社區(qū)關(guān)注領(lǐng)域有:容器爷抓、微服務(wù)雨效、Service Mesh、Serverless废赞,擁抱開(kāi)源和云原生徽龟,致力于推動(dòng) Service Mesh 在中國(guó)的蓬勃發(fā)展。

社區(qū)官網(wǎng):https://www.servicemesher.com

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末唉地,一起剝皮案震驚了整個(gè)濱河市据悔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌耘沼,老刑警劉巖极颓,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異群嗤,居然都是意外死亡菠隆,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)狂秘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)骇径,“玉大人,你說(shuō)我怎么就攤上這事者春∑葡危” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵钱烟,是天一觀的道長(zhǎng)晰筛。 經(jīng)常有香客問(wèn)我,道長(zhǎng)拴袭,這世上最難降的妖魔是什么读第? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮拥刻,結(jié)果婚禮上怜瞒,老公的妹妹穿的比我還像新娘。我一直安慰自己泰佳,他們只是感情好盼砍,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布尘吗。 她就那樣靜靜地躺著逝她,像睡著了一般。 火紅的嫁衣襯著肌膚如雪睬捶。 梳的紋絲不亂的頭發(fā)上黔宛,一...
    開(kāi)封第一講書(shū)人閱讀 49,036評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音擒贸,去河邊找鬼臀晃。 笑死觉渴,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的徽惋。 我是一名探鬼主播案淋,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼险绘!你這毒婦竟也來(lái)了踢京?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤宦棺,失蹤者是張志新(化名)和其女友劉穎瓣距,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體代咸,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蹈丸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了呐芥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片逻杖。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖思瘟,靈堂內(nèi)的尸體忽然破棺而出弧腥,到底是詐尸還是另有隱情,我是刑警寧澤潮太,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布管搪,位于F島的核電站,受9級(jí)特大地震影響铡买,放射性物質(zhì)發(fā)生泄漏更鲁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一奇钞、第九天 我趴在偏房一處隱蔽的房頂上張望澡为。 院中可真熱鬧,春花似錦景埃、人聲如沸媒至。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)拒啰。三九已至,卻和暖如春完慧,著一層夾襖步出監(jiān)牢的瞬間谋旦,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留册着,地道東北人拴孤。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像甲捏,于是被迫代替她去往敵國(guó)和親演熟。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容