[istio源碼分析][galley] galley之上游(source)

1. 前言

轉載請說明原文出處, 尊重他人勞動成果!

源碼位置: https://github.com/nicktming/istio
分支: tming-v1.3.6 (基于1.3.6版本)

1. [istio源碼分析][galley] galley之上游(source)
2. [istio源碼分析][galley] galley之runtime
3. [istio源碼分析][galley] galley之下游(mcp)

[istio源碼分析][galley] galley之runtime 中分析了galley整個機制中一個承上啟下的組件, 在 [istio源碼分析][galley] galley之下游(mcp) 中分析了galley中負責下游處理的mcp, 本文將分析galley的上游, 也就是信息來源source.

2. source

cd $GOPATH/src/istio.io/istio/galley/pkg/source
> tree -L 1
├── fs
├── kube

可以看到當前source支持兩個來源fs(文件) 和 kube(k8s集群).

3. fs

galley的來源是文件, 也就是增/刪/改文件來進行對象的添加刪除修改操作. 另外galley可以感知道其操作.

3.1 例子

如果沒有k8s集群, 則可以使用文件來進行測試, 這里寫了個簡單的例子https://github.com/nicktming/istio/tree/tming-1.3.6/galley/pkg/source/test來幫助理解.

// main.go
...
func main()  {
    dir := "./fs"
    shutdown := make(chan os.Signal, 1)
    // 監控文件變化
    appsignals.FileTrigger(dir, syscall.SIGUSR1, shutdown)
    // 創建source
    s := newOrFail(dir)
    // 啟動source
    ch := startOrFail(s)
    // 下游接收到的事件
    receive(ch)
}

例子中的文件夾fs里面保存了一些yaml文件, 文件里面有一些對象包括VirtualServiceService等. 運行:

receive event:[Event](Added: [VKey](istio/networking/v1alpha3/virtualservices:route-for-myapp @v0)), entry:{{0001-01-01 00:00:00 +0000 UTC map[] map[]} [VKey](istio/networking/v1alpha3/virtualservices:route-for-myapp @v0) hosts:"some.example.com" gateways:"some-ingress" http:<route:<destination:<host:"some.example.internal" > > > }
receive event:[Event](Added: [VKey](k8s/core/v1/services:kube-system/kube-dns @v0)), entry:{{2018-02-12 23:48:44 +0800 CST map[lk1:lv1] map[ak1:av1]} [VKey](k8s/core/v1/services:kube-system/kube-dns @v0) &ServiceSpec{Ports:[{dns-tcp TCP 53 {0 53 } 0}],Selector:map[string]string{},ClusterIP:10.43.240.10,Type:ClusterIP,ExternalIPs:[],SessionAffinity:,LoadBalancerIP:,LoadBalancerSourceRanges:[],ExternalName:,ExternalTrafficPolicy:,HealthCheckNodePort:0,PublishNotReadyAddresses:false,SessionAffinityConfig:nil,}}
receive event:[Event](FullSync), entry:{{0001-01-01 00:00:00 +0000 UTC map[] map[]} [VKey](: @) <nil>}

可以看到有兩個Added事件和一個FullSync事件. 接著手動修改文件中的某個值, 比如在名字為kube-dnsService中添加了一個labelenv: test,此時再次查看日志:

receive event:[Event](Updated: [VKey](k8s/core/v1/services:kube-system/kube-dns @v1)), entry:{{2018-02-12 23:48:44 +0800 CST map[env:test lk1:lv1] map[ak1:av1]} [VKey](k8s/core/v1/services:kube-system/kube-dns @v1) &ServiceSpec{Ports:[{dns-tcp TCP 53 {0 53 } 0}],Selector:map[string]string{},ClusterIP:10.43.240.10,Type:ClusterIP,ExternalIPs:[],SessionAffinity:,LoadBalancerIP:,LoadBalancerSourceRanges:[],ExternalName:,ExternalTrafficPolicy:,HealthCheckNodePort:0,PublishNotReadyAddresses:false,SessionAffinityConfig:nil,}}

添加了一個關于kube-system/kube-dns這個Service的更新事件(Updated).

3.2 分析

初始化一個文件類型的source.

func New(root string, schema *schema.Instance, config *converter.Config) (runtime.Source, error) {
    fs := &source{
        config:  config,
        root:    root,
        kinds:   map[string]bool{},
        shas:    map[fileResourceKey][sha1.Size]byte{},
        worker:  util.NewWorker("fs source", log.Scope),
        version: 0,
    }
    // 支持的schema 比如VirtualService, Service, Pod等
    for _, spec := range schema.All() {
        fs.kinds[spec.Kind] = true
    }
    return fs, nil
}

1. root為根文件夾path.
2. schema為該source支持的類型, 比如VirtualService, ServicePod等.
3. config在將yaml文件轉成對象時會用到.
4. shas在內存中保存著所有yaml文件的內容.

3.2.2 Start
func (s *source) Start(handler resource.EventHandler) error {
    return s.worker.Start(nil, func(ctx context.Context) {
        // 初始化s.handler處理event
        s.handler = handler
        // 初始加載所有文件
        s.initialCheck()
        // 注冊一個signal 可以通過FileTrigger來監控文件 這樣文件變化就發送signal到此channel c
        c := make(chan appsignals.Signal, 1)
        appsignals.Watch(c)

        for {
            select {
            case <-ctx.Done():
                return
            case trigger := <-c:
                if trigger.Signal == syscall.SIGUSR1 {
                    log.Scope.Infof("Triggering reload in response to: %v", trigger.Source)
                    s.reload()
                }
            }
        }
    })
}

1. 初始化s.handler
2. 初始加載所有文件, 并生成事件發送出去.
3. 注冊一個signal可以通過FileTrigger來監控文件 這樣文件變化就發送signal到此c(channel)
4. 如果c(channel)中接收一個syscall.SIGUSR1信號, 就是表明監控的文件夾中的文件有變化, 所以調用s.reload()發送新事件.

3.2.2 initialCheck

func (s *source) initialCheck() {
    // 得到該文件夾下所有文件轉化成的對象 以map形式存儲
    newData := s.readFiles(s.root)
    s.mu.Lock()
    defer s.mu.Unlock()
    for k, r := range newData {
        s.process(resource.Added, k, r)
        s.shas[k] = r.sha
    }
    s.handler(resource.FullSyncEvent)
}

readFiles方法可以將某個目錄下面的所有yaml文件轉化成當前source支持的schema, 以map形式保存在newData中, 關于readFiles的實現這里就不分析了.

3.2.3 process

func (s *source) process(eventKind resource.EventKind, key fileResourceKey, r *fileResource) {
    version := resource.Version(fmt.Sprintf("v%d", s.version))

    var event resource.Event
    switch eventKind {
    case resource.Added, resource.Updated:
        event = resource.Event{
            Kind: eventKind,
            Entry: resource.Entry{
                ID: resource.VersionedKey{
                    Key: resource.Key{
                        Collection: r.spec.Target.Collection,
                        FullName:   key.fullName,
                    },
                    // 當前版本
                    Version: version,
                },
                Item:     r.entry.Resource,
                Metadata: r.entry.Metadata,
            },
        }
    case resource.Deleted:
        spec := kubeMeta.Types.Get(key.kind)
        event = resource.Event{
            Kind: eventKind,
            Entry: resource.Entry{
                ID: resource.VersionedKey{
                    Key: resource.Key{
                        Collection: spec.Target.Collection,
                        FullName:   key.fullName,
                    },
                    Version: version,
                },
            },
        }
    }

    log.Scope.Debugf("Dispatching source event: %v", event)
    s.handler(event)
}

這里的處理方式也很簡單, 組裝成resource.Event交由s.handler發到runtime中.
這里需要注意一下Version, 這個版本號在什么時候會變化? 再次回到Start方法, 當文件發生變化時會觸發reload方法, 接下來看一下reload方法.

3.2.4 reload

func (s *source) reload() {
    // 再次讀取所有文件
    newData := s.readFiles(s.root)
    s.mu.Lock()
    defer s.mu.Unlock()
    newShas := map[fileResourceKey][sha1.Size]byte{}
    // Compute the deltas using sha comparisons
    nextVersion := s.version + 1
    // sha 為上一個版本的數據內容
    // newData為當前版本的數據內容
    // 用sha和newData對比就可以得到所有事件內容
    // 最后更新sha為當前版本的數據內容
    for k, r := range newData {
        newShas[k] = r.sha
        sha, exists := s.shas[k]
        if exists && sha != r.sha {
            if s.version != nextVersion {
                s.version = nextVersion
            }
            s.process(resource.Updated, k, r)
        } else if !exists {
            if s.version != nextVersion {
                s.version = nextVersion
            }
            s.process(resource.Added, k, r)
        }
    }
    for k := range s.shas {
        if _, exists := newShas[k]; !exists {
            s.process(resource.Deleted, k, nil)
        }
    }
    s.shas = newShas
}

主要內容如下:
1. sha 為上一個版本的數據內容
2. newData為當前版本的數據內容
3.shanewData對比就可以得到所有事件內容并通過process方法發送給runtime.
4. 最后更新sha為當前版本的數據內容.

3.2.5 總結

source.png

1. 初始的時候通過initalCheck -> readFile -> process -> handler -> runtime.
2. 初始完的時候會發送一個FullSync事件表明第一次初始化結束.
3. 通過FileTrigger監控文件變化, 如有變化通過reload方法重新加載文件并更新內容對象, 并且根據當前內容和上一個版本內容對比發送對應事件給runtime.

3. k8s

有了針對文件作為source的理解, 對于k8s的理解就會更簡單了, 從原理上講, fs監控文件的變化, k8s使用informer機制監控k8s中原生資源和crd資源的變化即可.

3.1 source

// galley/pkg/source/kube/source.go
func New(interfaces client.Interfaces, resyncPeriod time.Duration, schema *schema.Instance,
    cfg *kubeConverter.Config) (runtime.Source, error) {

    var err error
    var cl kubernetes.Interface
    var dynClient kubeDynamic.Interface
    var sharedInformers informers.SharedInformerFactory

    log.Scope.Info("creating sources for kubernetes resources")
    sources := make([]runtime.Source, 0)
    for i, spec := range schema.All() {
        log.Scope.Infof("[%d]", i)
        log.Scope.Infof("  Source:      %s", spec.CanonicalResourceName())
        log.Scope.Infof("  Collection:  %s", spec.Target.Collection)

        // If it's a known type, use a custom (optimized) source.
        if builtin.IsBuiltIn(spec.Kind) {
            // Lazy create the kube client.
            if cl, err = getKubeClient(cl, interfaces); err != nil {
                return nil, err
            }
            sharedInformers = getSharedInformers(sharedInformers, cl, resyncPeriod)
            // 創建k8s原生資源的source 比如pod, service
            source, err := builtin.New(sharedInformers, spec)
            if err != nil {
                return nil, err
            }
            sources = append(sources, source)
        } else {
            // Lazy-create the dynamic client
            if dynClient, err = getDynamicClient(dynClient, interfaces); err != nil {
                return nil, err
            }
            // Unknown types use the dynamic source.
            // 創建crd資源的source 比如virtualService, gateway等等
            source, err := dynamic.New(dynClient, resyncPeriod, spec, cfg)
            if err != nil {
                return nil, err
            }
            sources = append(sources, source)
        }
    }
    return &aggregate{
        sources: sources,
    }, nil
}

可以看到k8ssource使用了aggregate結構體來收集到所有的source. 主要來自兩大類:
1. k8s原生資源比如pod, Service, 使用原生client-go api即可. 每個資源都是一個source.
2. crd資源比如VirtualService, gateway, 使用dynamic client. 每個資源都是一個source.

3.2 Start

func (s *aggregate) Start(handler resource.EventHandler) error {
    s.mu.Lock()
    defer s.mu.Unlock()
    syncGroup := sync.WaitGroup{}
    syncGroup.Add(len(s.sources))
    syncHandler := func(e resource.Event) {
        if e.Kind == resource.FullSync {
            // 如果某一個資源同步完了 就減少一個
            syncGroup.Done()
        } else {
            // Not a sync event, just pass on to the real handler.
            // 不是同步操作 調用傳入的handler
            handler(e)
        }
    }
    for _, source := range s.sources {
        // 為每個資源調用各自的Start方法
        if err := source.Start(syncHandler); err != nil {
            return err
        }
    }
    go func() {
        // 等待所有資源同步完
        syncGroup.Wait()
        // 發送一個FullSync給runtime 表明所有資源以及同步完成
        handler(resource.FullSyncEvent)
    }()
    return nil
}

1. 可以看到Start方法是將所有的source全部啟動, 也就是list and watch所有的資源.
2. 等到所有的資源都已經同步完了, 也就是通過handler(e)發送給runtime了, 該Start才會發送一個FullSync事件給runtime.
3. 關于某個資源的Start就不多說了, 了解informer機制原理就明白了.

4. server

這里結合一個galleyserver端啟動程序看看是如何調用的.

// galley/pkg/server/components/processing.go
func (p *Processing) Start() (err error) {
      var mesh meshconfig.Cache
    var src runtime.Source

    if mesh, err = newMeshConfigCache(p.args.MeshConfigFile); err != nil {
        return
    }
    if src, err = p.createSource(mesh); err != nil {
        return
    }
    ...
    p.processor = runtime.NewProcessor(src, p.distributor, &processorCfg)
    ...
}
func (p *Processing) createSource(mesh meshconfig.Cache) (src runtime.Source, err error) {
    ...
    sourceSchema := p.getSourceSchema()
    if p.args.ConfigPath != "" {
        if src, err = fsNew(p.args.ConfigPath, sourceSchema, converterCfg); err != nil {
            return
        }
    } else {
        var k client.Interfaces
        if k, err = newKubeFromConfigFile(p.args.KubeConfig); err != nil {
            return
        }
        var found []schema.ResourceSpec
        ...
        sourceSchema = schema.New(found...)
        if src, err = newSource(k, p.args.ResyncPeriod, sourceSchema, converterCfg); err != nil {
            return
        }
    }
    return
}

可以看到創建的source將為參數傳入到NewProcessor中, 這個在 [istio源碼分析][galley] galley之runtime 中已經分析過了, 所以現在已經和runtime對接上了.

full_source.png

5.參考

1. istio 1.3.6源碼
2. https://cloud.tencent.com/developer/article/1409159

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,071評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,409評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,569評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,360評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,895評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,123評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,643評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,559評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,742評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,981評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,363評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,622評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,354評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,707評論 2 370

推薦閱讀更多精彩內容