隨着公司業務的發展,底層容器環境也須要在各個區域部署,實現多雲架構, 使用各個雲廠商提供的CNI插件是k8s多雲環境下網絡架構的一種高效的解法。咱們在阿里雲的方案中,便用到了阿里雲提供的CNI插件terway。terway所提供的VPC互通的網絡方案,方便對接已有的基礎設施,同時沒有overlay網絡封包解包的性能損耗,簡單易用,出現網絡問題方便診斷。本文對該插件作簡單的代碼分析,理解其原理,以便後期診斷問題和維護。html
阿里雲開源的terway代碼有三部分組成:node
CNI plugin: 即CNI插件,實現ADD、DEL、VERSION
三個接口來供kubelet調用, 該插件將kubelet傳遞的參數進行簡單處理後,會經過gRPC調用terwayBackendServer來實現具體的邏輯,例如申請網絡設備等。同步調用terwayBackendServer將網絡設備分配完畢以後,會經過ipvlanDriver.Driver
進行pod sandbox network namespace的Setup
操做,同時還會經過TC進行流控。該插件會經過daemonSet中initContainer安裝到全部node上。面試
在terway的main函數中會啓動gRPC server監聽請求,同時會建立一個TerwayBackendServer
, TerwayBackendServer封裝所有操做邏輯,在newNetworkService
函數中會依次初始化各個子模塊實例,具體包括:api
ENIMultiIP
這種模式,對應的就是newENIIPResourceManager
ENIMultiIP模式會申請阿里雲彈性網卡並配置多個輔助VPC的IP地址,將這些輔助IP地址映射和分配到Pod中,這些Pod的網段和宿主機網段是一致的,可以實現VPC網絡互通。緩存
整個架構以下圖所示:安全
首先咱們理解一下kubernetes pod管理模塊,該模塊用於獲取kubernetes pod狀態。terway爲了支持一些高級的特性,例如流控等,有一些信息沒法經過CNI調用傳遞過來, 仍是得去kubernetes中去查詢這些信息。此外CNI調用在一些異常狀況下可能沒法準確回調CNI插件, 例如用戶直接kubectl delete pod --force --graceperiod=0
,此時就須要kubernetes做爲惟一的single source of truth
, 保證最後網絡設備在pod刪除時確定可以被釋放掉。 它內部主要的方法就是GetPod
與GetLocalPod
。GetPod
方法會請求apiserver返回pod信息,若是該pod已經在apiserver中刪除,就會從本地的storage中獲取。該storage是用boltDB作爲底層存儲的一個本地文件,每一個被處理過的pod都會在該storage中保存一份信息,且該pod副本並不會隨着apiserver中pod的刪除而刪除,這樣後面程序若是須要該pod信息能夠從該storage中獲取。同時該pod副本會經過異步清理goroutine在pod刪除一小時後刪除。GetLocalPod
是從apiserver獲取該node上全部的pod信息,該過程是調用kubernetes最多的地方,目前兩個清理goroutine會每5min調用一次,調用量相對較小,對apiserver的負載影響不大。該模塊也會在本地DB裏緩存一份數據,便於在kubernetes pod刪除後還能夠拿到用戶信息。服務器
其次是resourceDB模塊,該模塊是用來持久化狀態信息,該DB中記錄了當前已分配的pod及其網絡設備(networkResource)信息。每次請求/釋放設備都會更新該DB。程序從新啓動初始化完成以後,也會從resouceDB中恢復上次運行的數據。
除了基本的分配刪除操做會更新該DB, terway還啓動異步goroutine按期清理,保證異常狀況下的最終一致性,該goroutine會從apiserve中獲取全部pod信息和當前DB中的信息進行對比,若是對應的pod已經刪除會先釋放對應的網絡設備,而後從DB中刪除該記錄。同時延遲清理能夠實現Statefulset的Pod在更新過程當中IP地址保持不變,網絡
最重要的是resouceManager
模塊,該iterface封裝了具體網絡設備的操做,以下所示:數據結構
// ResourceManager Allocate/Release/Pool/Stick/GC pod resource // managed pod and resource relationship type ResourceManager interface { Allocate(context *networkContext, prefer string) (types.NetworkResource, error) Release(context *networkContext, resID string) error GarbageCollection(inUseResList map[string]interface{}, expireResList map[string]interface{}) error }
從其中三個method能夠很明顯的看出能夠執行的的動做,每次CNI插件調用backendServer時, 就會調用ResoueceManager進行具體的分配釋放操做。對於ENIMultiIP
這種模式來講,具體的實現類是eniIPResourceManager
:閉包
type eniIPResourceManager struct { pool pool.ObjectPool }
其中只有pool一個成員函數,具體的實現類型是simpleObjectPool
, 該pool維護了當前全部的ENI信息。當resouceManager進行分配釋放網絡設備的時候實際上是從該pool中進行存取便可:
func (m *eniIPResourceManager) Allocate(ctx *networkContext, prefer string) (types.NetworkResource, error) { return m.pool.Acquire(ctx, prefer) } func (m *eniIPResourceManager) Release(context *networkContext, resID string) error { if context != nil && context.pod != nil { return m.pool.ReleaseWithReverse(resID, context.pod.IPStickTime) } return m.pool.Release(resID) } func (m *eniIPResourceManager) GarbageCollection(inUseSet map[string]interface{}, expireResSet map[string]interface{}) error { for expireRes := range expireResSet { if err := m.pool.Stat(expireRes); err == nil { err = m.Release(nil, expireRes) if err != nil { return err } } } return nil }
由上述代碼可見,resouceManager實際操做的都是simpleObjectPool這個對象。 咱們看看這個pool到底作了那些操做。首先初始化該pool:
// NewSimpleObjectPool return an object pool implement func NewSimpleObjectPool(cfg Config) (ObjectPool, error) { if cfg.MinIdle > cfg.MaxIdle { return nil, ErrInvalidArguments } if cfg.MaxIdle > cfg.Capacity { return nil, ErrInvalidArguments } pool := &simpleObjectPool{ factory: cfg.Factory, inuse: make(map[string]types.NetworkResource), idle: newPriorityQueue(), maxIdle: cfg.MaxIdle, minIdle: cfg.MinIdle, capacity: cfg.Capacity, notifyCh: make(chan interface{}), tokenCh: make(chan struct{}, cfg.Capacity), } if cfg.Initializer != nil { if err := cfg.Initializer(pool); err != nil { return nil, err } } if err := pool.preload(); err != nil { return nil, err } log.Infof("pool initial state, capacity %d, maxIdle: %d, minIdle %d, idle: %s, inuse: %s", pool.capacity, pool.maxIdle, pool.minIdle, queueKeys(pool.idle), mapKeys(pool.inuse)) go pool.startCheckIdleTicker() return pool, nil }
能夠看到在建立的時候會根據傳入的config依次初始化各成員變量, 其中
eniIPFactory
。priorityQeueu
類型,即全部空閒的networkResouce經過優先級隊列排列,優先級隊列的比較函數會比較reverse
字段,reverse
默認是入隊時間,也就是該networkResouce的釋放的時間,這樣作可以儘可能使一個IP釋放以後不會被立馬被複用。reverse
字段對於一些statueSet的resouce也會進行一些特殊處理,由於statufulSet是有狀態workload, 對於IP的釋放也會特殊處理,保證其儘量複用。成員變量初始化完成以後會調用Initializer
, 該函數會回調一個閉包函數,定義在newENIIPResourceManager
中: 當程序啓動時,resouceManager經過讀取存儲在本地磁盤也就是resouceDB中的信息獲取當前正在使用的networkResouce,而後經過ecs獲取當前全部eni設備及其ip, 依次遍歷全部ip判斷當前是否在使用,分別來初始化inuse和idle。這樣能夠保證程序重啓以後能夠重構內存中的pool數據信息。
而後會調用preload
,該函數確保pool(idle)中有minIdle個空閒元素, 防止啓動時大量調用factory。
最後會進行go pool.startCheckIdleTicker()
異步來goroutine中調用checkIdle
按期查詢pool(idle)中的元素是否超過maxIdle個元素, 若是超過則會調用factory進行釋放。同時每次調用factory也會經過notifyCh
來通知該goroutine執行檢查操做。
pool結構初始化完成以後,resouceManager中全部對於networkResource的操做都會經過該pool進行,該pool在必要條件下再調用factory進行分配釋放。
factory的具體實現是eniIPFactory
, 用來調用ecs SDK進行申請釋放eniIP, 並維護對應的數據結構。不一樣於直接使用eni設備,ENIMultiIP
模式會爲每一個eni設備會有多個eniIP。eni設備是經過ENI
結構體標識, eniIP經過ENIIP
結構體標識。terway會爲每一個ENI
建立一個goroutine, 該ENI上全部eniIP的分配釋放都會在goroutine內進行,factory經過channel與該groutine通訊, 每一個goroutine對應一個接受channel ipBacklog
,用於傳遞分配請求到該goroutine。 每次factory 須要建立(eniIPFactory.Create)一個eniIP時, 會一次遍歷當前已經存在的ENI
設備,若是該設備還有空閒的eniIP,就會經過該ipBacklog
channel發送一個元素到該ENI設備的goroutine進行請求分配, 當goroutine將eniIP分配完畢以後經過factory 的resultChan
通知factory, 這樣factory就成功完成一次分配。 若是全部的ENI的eniIP都分配完畢,會首先建立ENI設備及其對應goroutine。由於每一個ENI設備會有個主IP, 因此首次分配ENI不須要發送請求到ipBacklog
, 直接將該主ip返回便可。對應的釋放(Dispose)就是先釋放eniIP, 等到只剩最後一個eniIP(主eniIP)時會釋放整個ENI設備。對於全部ecs調用都會經過buffer channel進行流控,防止瞬間調用過大。
總之,terway的整個實現,邏輯比較清晰,而且擴展性也較高。後期,能夠比較方便地在此基礎上作一些定製和運維支持,從而很好地融入公司的基礎架構設施。
(轉載)原文地址:http://www.javashuo.com/article/p-qmvkqoux-co.html
超值推薦:
阿里雲雙12已開啓,雲產品冰點價,新用戶專享1折起,1核2G雲服務器僅需89元/年,229元/3年。買了對於提高技術或者在服務器上搭建自由站點,都是很不錯的,若是本身有實際操做,面試+工做中確定是加分項。(老用戶能夠用家人或朋友的帳號購買,真心便宜&划算)
可「掃碼」或者「點擊購買 "
##END