istio源碼解析系列(二)-服務(wù)治理配置生效流程解析

前言

本系列文章主要從源碼(35e2b904)出發(fā)豺旬,對(duì)istio做深入剖析哄芜,讓大家對(duì)istio有更深的認(rèn)知诗轻,從而方便平時(shí)排查問題嗅义。不了解Service Mesh和Istio的同學(xué)請(qǐng)先閱讀敖小劍老師如下文章進(jìn)行概念上的理解:

服務(wù)治理配置生效流程解析

如果大家安裝bookinfo并執(zhí)行過文檔中的task屏歹,可以了解到,所有服務(wù)治理流程都是通過istioctl工具之碗,執(zhí)行指定yaml配置文件來實(shí)現(xiàn)蝙眶。那么從執(zhí)行istioctl指令到配置文件生效,整個(gè)流程到底是什么樣的呢褪那?下面給大家做一個(gè)簡單的介紹幽纷。

整個(gè)配置生效的流程圖如下所示:


image.png

配置文件解析

以task request-routing為例,我們的需求是把名為jason的用戶訪問reviews服務(wù)的版本切換為v2博敬。route-rule-reviews-test-v2.yaml內(nèi)容如下所示:

apiVersion: config.istio.io/v1alpha2
kind: RouteRule
metadata:
  name: reviews-test-v2
spec:
  destination:
    name: reviews
  precedence: 2
  match:
    request:
      headers:
        cookie:
          regex: "^(.*?;)?(user=jason)(;.*)?$"
  route:
  - labels:
      version: v2

解析并執(zhí)行istioctl create指令

通過istioctl create -f samples/bookinfo/kube/route-rule-reviews-test-v2.yaml指令來使規(guī)則生效友浸,執(zhí)行istioctl create指令運(yùn)行的相關(guān)代碼入口如下:

istio/cmd/istioctl/main.go#postCmd#113行。

postCmd = &cobra.Command{
        Use:     "create",
        Short:   "Create policies and rules",
        Example: "istioctl create -f example-routing.yaml",
        RunE: func(c *cobra.Command, args []string) error {
                    if len(args) != 0 {
                      c.Println(c.UsageString())
                      return fmt.Errorf("create takes no arguments")
                    }
                    // varr為轉(zhuǎn)換成功的istio內(nèi)部model.Config切片冶忱,包括routeRule尾菇、gateway、ingressRule囚枪、egressRule派诬、policy等
                    // others是不能轉(zhuǎn)換成model.Config的k8s object wrapper切片,后面會(huì)當(dāng)成mixer配置來處理
                    varr, others, err := readInputs()
                    if err != nil {
                        return err
                    }
                    if len(varr) == 0 && len(others) == 0 {
                        return errors.New("nothing to create")
                    }
            ...
        }
}

解析出model.Config切片链沼、crd.istioKind切片流程

  • model.Config 為istio配置單元
  • crd.IstioKind 對(duì)k8s API對(duì)象做了一層封裝

readInput函數(shù)解析create命令的相關(guān)參數(shù)(比如-f)默赂,如果是-f指定的文件是有效文件,則會(huì)調(diào)用pilot/pkg/config/kube/crd包的ParseInputs函數(shù)解析該文件括勺。

func readInputs() ([]model.Config, []crd.IstioKind, error) {
    var reader io.Reader
        ...
            // 讀取指定yaml文件
        if in, err = os.Open(file); err != nil {
            return nil, nil, err
        }
        defer func() {
            if err = in.Close(); err != nil {
                log.Errorf("Error: close file from %s, %s", file, err)
            }
        }()
        reader = in
    ... 
    input, err := ioutil.ReadAll(reader)
    ...
    return crd.ParseInputs(string(input))
}

ParseInputs函數(shù)內(nèi)部邏輯:

func ParseInputs(inputs string) ([]model.Config, []IstioKind, error) {
    var varr []model.Config
    var others []IstioKind
    reader := bytes.NewReader([]byte(inputs))
    var empty = IstioKind{}

    // We store configs as a YaML stream; there may be more than one decoder.
    yamlDecoder := kubeyaml.NewYAMLOrJSONDecoder(reader, 512*1024)
    for {
        obj := IstioKind{}
        // 從reader中反序列化出IstioKind實(shí)例obj
        err := yamlDecoder.Decode(&obj)
        ...
        schema, exists := model.IstioConfigTypes.GetByType(CamelCaseToKabobCase(obj.Kind))
        ...
        config, err := ConvertObject(schema, &obj, "")
        ...
        if err := schema.Validate(config.Spec); err != nil {
            return nil, nil, fmt.Errorf("configuration is invalid: %v", err)
        }
        varr = append(varr, *config)
    }

    return varr, others, nil
}

ParseInputs返回三種類型的值[]Config缆八、[]IstioKind、error疾捍。

  • istio/pilot/pkg/model#[]Config
    其中Config為Istio內(nèi)部的配置單元奈辰,包含匿名ConfigMeta以及ConfigMeta序列化的protobuf message;用戶指定的yaml配置會(huì)被解析成相應(yīng)的實(shí)例乱豆。
  • pilot/pkg/config/kube/crd#[]IstioKind
    IstioKind為k8s API object的一層封裝奖恰,內(nèi)部包含兩個(gè)匿名結(jié)構(gòu)體和一個(gè)map:
    type IstioKind struct {
        meta_v1.TypeMeta   `json:",inline"`
        meta_v1.ObjectMeta `json:"metadata"`
        Spec               map[string]interface{} `json:"spec"`
    }
    
    • IstioKindk8s.io/apimachinery/pkg/apis/meta/v1#TypeMeta
      TypeMeta包含了k8s REST資源類型(如RouteRule)、k8s API版本號(hào)(如config.istio.io/v1alpha2)宛裕。
    • k8s.io/apimachinery/pkg/apis/meta/v1#ObjectMeta
      ObjectMeta包含了k8s 資源對(duì)象包含的各必要字段瑟啃,包括Name、Namespace揩尸、UID等蛹屿。
    • Spec
      一個(gè)存儲(chǔ)Spec數(shù)據(jù)的map。

上述代碼將string類型的配置反序列化成IstioKind實(shí)例后岩榆,通過model.IstioConfigTypes.GetByType()方法獲取istio的[]ProtoSchema實(shí)例错负。

// ConfigDescriptor 是一個(gè)由ProtoSchema組成的切片
type ConfigDescriptor []ProtoSchema
// ProtoSchema結(jié)構(gòu)體定義了配置類型名稱和protobuf消息的雙向映射
type ProtoSchema struct {
    Type        string // 配置的proto類型坟瓢,如route-rule
    Plural      string // type復(fù)數(shù)形式,如route-rules
    Group       string // 配置的proto組名湿颅,如config
    Version     string // 配置API的版本號(hào)载绿,如一lpha2
    MessageName string // 配置的proto message名,如istio.routing.v1alpha1.RouteRule
    Gogo        bool   // 是否為gogo protobuf編碼
    Validate    func(config proto.Message) error // protobuf校驗(yàn)函數(shù)
}

拿到schema后油航,通過ConvertObject方法崭庸,將k8s風(fēng)格的object實(shí)例轉(zhuǎn)換成istio內(nèi)部的Config模型實(shí)例,并根據(jù)schema類型調(diào)用相應(yīng)的校驗(yàn)函數(shù)對(duì)protobuf message進(jìn)行校驗(yàn)谊囚。

將配置變更提交到k8s

istio/cmd/istioctl/main.go#postCmd#140行怕享。

for _, config := range varr {
    // 初始化namespace數(shù)據(jù)
    if config.Namespace, err = handleNamespaces(config.Namespace); err != nil {
        return err
    }

    // 構(gòu)造k8s crd.Client實(shí)例,crd.Client包含初始化的apiVerison到restClient映射的map镰踏。
    // 對(duì)每一種apiVerison(由schema.Group函筋、"istio.io"、schema.Version組成的string奠伪,如"config.istio.io/v1alpha2"跌帐、"networking.istio.io/v1alpha3"等)
    // 都對(duì)應(yīng)一個(gè)crd.restClient實(shí)例。
    var configClient *crd.Client
    if configClient, err = newClient(); err != nil {
        return err
    }
    var rev string
    // 通過k8s REST接口執(zhí)行配置
    if rev, err = configClient.Create(config); err != nil {
        return err
    }
    fmt.Printf("Created config %v at revision %v\n", config.Key(), rev)
}

configClient.Create方法執(zhí)行流程如下:

func (cl *Client) Create(config model.Config) (string, error) {
    rc, ok := cl.clientset[apiVersionFromConfig(&config)]
    ...
    // 根據(jù)config.Type獲取schema
    schema, exists := rc.descriptor.GetByType(config.Type)
    ...
    // 調(diào)用schema指定的Validate函數(shù)绊率,對(duì)Spec這個(gè)protobuff進(jìn)行校驗(yàn)
    if err := schema.Validate(config.Spec); err != nil {
        return "", multierror.Prefix(err, "validation error:")
    }
    // ConvertConfig函數(shù)將model.Config實(shí)例轉(zhuǎn)換成IstioObject實(shí)例谨敛。
    // IstioObject是一個(gè)k8s API object的接口,crd包下有很多結(jié)構(gòu)體實(shí)現(xiàn)了該接口滤否,如MockConfig脸狸、RouteRule等
    out, err := ConvertConfig(schema, config)
    ...

    // 檢索clientset map,用指定的restClient實(shí)例發(fā)送POST請(qǐng)求藐俺,使配置生效炊甲。
    obj := knownTypes[schema.Type].object.DeepCopyObject().(IstioObject)
    err = rc.dynamic.Post().
        Namespace(out.GetObjectMeta().Namespace).
        Resource(ResourceName(schema.Plural)).
        Body(out).
        Do().Into(obj)
    if err != nil {
        return "", err
    }
    return obj.GetObjectMeta().ResourceVersion, nil
}

pilot-discovery初始化

pilot/cmd/pilot-discovery/main.go#57行,構(gòu)造discoveryServer實(shí)例欲芹。

...
discoveryServer, err := bootstrap.NewServer(serverArgs)
if err != nil {
    return fmt.Errorf("failed to create discovery service: %v", err)
}
...

監(jiān)聽k8s相關(guān)資源變更

NewServer函數(shù)內(nèi)部流程如下:

func NewServer(args PilotArgs) (*Server, error) {
    ...
    // 初始化pilot配置控制器卿啡,根據(jù)pilot-discovery啟動(dòng)指令,初始化配置控制器菱父。
    // 默認(rèn)只會(huì)初始化kube配置控制器(kubeConfigController颈娜,它實(shí)現(xiàn)了model.ConfigStoreCache接口)。
    // kubeConfigController會(huì)watch k8s pod registration 滞伟、ingress resources揭鳞、traffic rules等變化炕贵。
    if err := s.initConfigController(&args); err != nil {
        return nil, err
    }
    // 初始化服務(wù)發(fā)現(xiàn)控制器梆奈,控制器內(nèi)部會(huì)構(gòu)造K8sServiceControllers。
    if err := s.initServiceControllers(&args); err != nil {
        return nil, err
    }
    // 初始化DiscoveryService實(shí)例称开,實(shí)例內(nèi)部注冊了envoy xDS路由亩钟。
    // kubeConfigController中watch到變更后乓梨,envoy輪詢xDS接口,獲取變更清酥。
    if err := s.initDiscoveryService(&args); err != nil {
        return nil, err
    }
    ...
}

注冊envoy xDS路由

initDiscoveryServic方法內(nèi)部流程如下:

func (s *Server) initDiscoveryService(args *PilotArgs) error {
    // 構(gòu)造pilot runtime environment扶镀。environment中保存了kubeConfigController、serviceController等焰轻。
    environment := model.Environment{
        Mesh:             s.mesh,
        IstioConfigStore: model.MakeIstioStore(s.configController),
        ServiceDiscovery: s.ServiceController,
        ServiceAccounts:  s.ServiceController,
        MixerSAN:         s.mixerSAN,
    }
    // 構(gòu)造DiscoveryService實(shí)例臭觉。
    discovery, err := envoy.NewDiscoveryService(
        s.ServiceController,
        s.configController,
        environment,
        args.DiscoveryOptions,
    )
}

NewDiscoveryService方法內(nèi)部流程如下:

func NewDiscoveryService(ctl model.Controller, configCache model.ConfigStoreCache,
    environment model.Environment, o DiscoveryServiceOptions) (*DiscoveryService, error) {
    out := &DiscoveryService{
        Environment: environment, // 將environment賦值給Environment成員。
        sdsCache:    newDiscoveryCache("sds", o.EnableCaching),
        cdsCache:    newDiscoveryCache("cds", o.EnableCaching),
        rdsCache:    newDiscoveryCache("rds", o.EnableCaching),
        ldsCache:    newDiscoveryCache("lds", o.EnableCaching),
    }
    container := restful.NewContainer()
    ...
    // 注冊web service容器辱志。
    out.Register(container)
}

out.Register方法內(nèi)部流程如下:

func (ds *DiscoveryService) Register(container *restful.Container) {
    ws := &restful.WebService{}
    ws.Produces(restful.MIME_JSON)
    ...

    // 注冊Envoy xDS(SDS蝠筑、CDS、RDS揩懒、LDS)路由
    // 注冊 Envoy RDS(Route discovery service)路由什乙。https://www.envoyproxy.io/docs/envoy/latest/api-v1/route_config/rds
    // RDS可以與SDS、EDS協(xié)同工作已球,來構(gòu)建用戶指定的路由拓?fù)洌ㄈ缌髁壳袚Q臣镣、藍(lán)綠部署等)。
    ws.Route(ws.
        GET(fmt.Sprintf("/v1/routes/{%s}/{%s}/{%s}", RouteConfigName, ServiceCluster, ServiceNode)).
        To(ds.ListRoutes).
        Doc("RDS registration").
        Param(ws.PathParameter(RouteConfigName, "route configuration name").DataType("string")).
        Param(ws.PathParameter(ServiceCluster, "client proxy service cluster").DataType("string")).
        Param(ws.PathParameter(ServiceNode, "client proxy service node").DataType("string")))

    // 注冊 Envoy LDS(Listener discovery service)路由智亮。https://www.envoyproxy.io/docs/envoy/latest/configuration/listeners/lds
    // Envoy可以從通過這個(gè)接口動(dòng)態(tài)獲取需要新的Listener信息忆某,從而在運(yùn)行時(shí)動(dòng)態(tài)實(shí)例化Listener。
    // Listener可以用來處理不同的代理任務(wù)(如速率限制鸽素、HTTP連接管理褒繁、原始TCP代理等)。
    ws.Route(ws.
        GET(fmt.Sprintf("/v1/listeners/{%s}/{%s}", ServiceCluster, ServiceNode)).
        To(ds.ListListeners).
        Doc("LDS registration").
        Param(ws.PathParameter(ServiceCluster, "client proxy service cluster").DataType("string")).
        Param(ws.PathParameter(ServiceNode, "client proxy service node").DataType("string")))
    ...
}
  • RDS路由綁定的ds.ListRoutes方法讀取environment中相關(guān)配置馍忽,返回給Envoy實(shí)例需要配置的路由信息棒坏。
  • LDS路由綁定的ds.ListListeners方法讀取environment中相關(guān)配置,返回給Envoy實(shí)例需要的Listener信息遭笋。
    Envoy實(shí)例輪詢xDS接口坝冕,獲取變更的配置信息,最終執(zhí)行具體的服務(wù)治理策略瓦呼。

總結(jié)

結(jié)合上文中貼出的流程圖


image.png

總結(jié)如下:

  • istio的piolit-discovery啟動(dòng)
    1. 初始化kube配置控制器喂窟,控制器中watch k8s pod、ingress以及流量管理規(guī)則等變更央串。
    2. 初始化envoy各發(fā)現(xiàn)服務(wù)磨澡,注冊envoy xDS路由,綁定相應(yīng)的配置變更handler质和。
    3. pilot-discovery等待envoy實(shí)例輪詢xDS接口稳摄,將變更返給envoy實(shí)例。
  • 用戶通過istioctl應(yīng)用配置
    1. istioctl解析指令(create饲宿、delete等)厦酬,通過k8s REST接口胆描,將變更推送的k8s。
    2. k8s產(chǎn)生變更仗阅,變更同步到kubeConfigController中昌讲。
    3. envoy實(shí)例輪詢xDS接口,應(yīng)用變更减噪。

作者

鄭偉短绸,小米信息部技術(shù)架構(gòu)組

招聘

小米信息部武漢研發(fā)中心,信息部是小米公司整體系統(tǒng)規(guī)劃建設(shè)的核心部門筹裕,支撐公司國內(nèi)外的線上線下銷售服務(wù)體系鸠按、供應(yīng)鏈體系、ERP體系饶碘、內(nèi)網(wǎng)OA體系目尖、數(shù)據(jù)決策體系等精細(xì)化管控的執(zhí)行落地工作,服務(wù)小米內(nèi)部所有的業(yè)務(wù)部門以及 40 家生態(tài)鏈公司扎运。

同時(shí)部門承擔(dān)微服務(wù)體系建設(shè)落地及各類后端基礎(chǔ)平臺(tái)研發(fā)維護(hù)瑟曲,語言涉及 Go、PHP豪治、Java洞拨,長年虛位以待對(duì)微服務(wù)、基礎(chǔ)架構(gòu)有深入理解和實(shí)踐负拟、或有大型電商后端系統(tǒng)研發(fā)經(jīng)驗(yàn)的各路英雄烦衣。

歡迎投遞簡歷:jin.zhang(a)xiaomi.com

更多技術(shù)文章:小米信息部技術(shù)團(tuán)隊(duì)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市掩浙,隨后出現(xiàn)的幾起案子花吟,更是在濱河造成了極大的恐慌,老刑警劉巖厨姚,帶你破解...
    沈念sama閱讀 211,639評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件衅澈,死亡現(xiàn)場離奇詭異,居然都是意外死亡谬墙,警方通過查閱死者的電腦和手機(jī)今布,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拭抬,“玉大人部默,你說我怎么就攤上這事≡旎ⅲ” “怎么了傅蹂?”我有些...
    開封第一講書人閱讀 157,221評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長累奈。 經(jīng)常有香客問我贬派,道長,這世上最難降的妖魔是什么澎媒? 我笑而不...
    開封第一講書人閱讀 56,474評(píng)論 1 283
  • 正文 為了忘掉前任搞乏,我火速辦了婚禮,結(jié)果婚禮上戒努,老公的妹妹穿的比我還像新娘请敦。我一直安慰自己,他們只是感情好储玫,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評(píng)論 6 386
  • 文/花漫 我一把揭開白布侍筛。 她就那樣靜靜地躺著,像睡著了一般撒穷。 火紅的嫁衣襯著肌膚如雪匣椰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,816評(píng)論 1 290
  • 那天端礼,我揣著相機(jī)與錄音禽笑,去河邊找鬼。 笑死蛤奥,一個(gè)胖子當(dāng)著我的面吹牛佳镜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播凡桥,決...
    沈念sama閱讀 38,957評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼蟀伸,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了缅刽?” 一聲冷哼從身側(cè)響起啊掏,我...
    開封第一講書人閱讀 37,718評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎衰猛,沒想到半個(gè)月后脖律,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,176評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡腕侄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評(píng)論 2 327
  • 正文 我和宋清朗相戀三年小泉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片冕杠。...
    茶點(diǎn)故事閱讀 38,646評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡微姊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出分预,到底是詐尸還是另有隱情兢交,我是刑警寧澤,帶...
    沈念sama閱讀 34,322評(píng)論 4 330
  • 正文 年R本政府宣布笼痹,位于F島的核電站配喳,受9級(jí)特大地震影響酪穿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜晴裹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評(píng)論 3 313
  • 文/蒙蒙 一被济、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧涧团,春花似錦只磷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽坪稽。三九已至沈自,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間弯予,已是汗流浹背苗沧。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評(píng)論 1 266
  • 我被黑心中介騙來泰國打工惠毁, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人崎页。 一個(gè)月前我還...
    沈念sama閱讀 46,358評(píng)論 2 360
  • 正文 我出身青樓鞠绰,卻偏偏與公主長得像,于是被迫代替她去往敵國和親飒焦。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蜈膨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評(píng)論 2 348

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