KubeCube (https://kubecube.io) 是由網易數帆近期開源的一個輕量化的企業級容器平台,為企業提供 kubernetes 資源可視化管理以及統一的多集群多租戶管理等功能。KubeCube 社區將通過系列技術文章解讀 KubeCube 的設計特點和技術實現,幫助開發者和用戶更快地理解和上手 KubeCube。本文是第三篇,深度解讀 KubeCube 的多集群能力。
為什麼需要多集群?
生產級落地,需要經過多個環境驗證,單個集群往往不能滿足隔離需求
當業務規模超過了單集群的承載能力時
當企業考慮使用多雲或者混合雲架構時
當架構設計上考慮雲容災,異地多活等場景時
K8s 多集群管理現狀
多集群管理並不是 K8s 社區的一個主要目標,雖然社區提出了 mcs 的多集群 service 標準,但是這依然無法滿足企業想要管理多集群的需求。如果僅僅使用 K8s 原生的能力進行多集群建設,K8s 管理者往往需要管理大量的不同 K8s 集群的配置文件,需要管理不同用戶的認證以及權限信息,並且對於應用的多集群發布、運維需要大量的人工的介入,很容易出現配置管理混亂、因操作不當導致故障等問題。
KubeCube 多集群能力
KubeCube 可以接管任意標準 Kubernetes 集群,對接管的所有 Kubernetes 集群提供統一的用戶管理和基於 Kubernetes 原生 RBAC 擴展的訪問控制。為提升用戶管理多個Kubernetes集群的效率,KubeCube提供了在線運維工具,可以通過KubeCube這一統一入口,快速管理多集群資源:CloudShell 可以在線對各集群使用kubectl,WebConsole 可以在線訪問各集群中的Pod。
另外,考慮到混合雲場景下 KubeCube 管控集群與業務集群間的網絡抖動、異常等問題。我們提供了業務集群自治能力,當業務集群與KubeCube管控集群失聯時,業務集群的訪問控制等可正常生效,不會受到影響。
KubeCube 的多集群模型
KubeCube 基於多集群模型,實現了多集群管理能力,多集群統一認證和鑒權的能力,多集群多租戶管理能力以及多集群的容錯能力,了解多集群模型能夠幫助你更加深入地了解 KubeCube。下文會對 KubeCube 和 Warden 中的各個多集群模塊的設計進行探索。
KubeCube 中的多集群模塊
KubeCube 是管控集群上的核心組件,它實現了多集群的生命周期管理,同時也作為 UI/openAPI 的操作入口。了解 KubeCube 中的多集群模塊,我們需要關注以下幾個 topic:
cluster cr 與 InternalCluster 的關係
multi-cluster-manager 如何管理 InternalCluster
cluster reconcile 的流程
scout 如何偵查計算集群的心跳
Multi-Cluster-Manager -- 多集群管理器
多集群管理器本質上就是對InternalCluster的管理,接口中包含了操作InternalCluster的所需方法。
// MultiClustersManager access to internal clustertype MultiClustersManager interface { // Add runtime cache in memory Add(cluster string, internalCluster *InternalCluster) error Get(cluster string) (*InternalCluster, error) Del(cluster string) error // FuzzyCopy return fuzzy cluster of raw FuzzyCopy() map[string]*FuzzyCluster // ScoutFor scout heartbeat for warden ScoutFor(ctx context.Context, cluster string) error // GetClient get client for cluster GetClient(cluster string) (kubernetes.Client, error)}
InternalCluster是真實Clustercr 的運行時映射,Clustercr 代表了被KubeCube納管的集群。
InternalCluster包含四個字段:
Client包含了溝通指定集群所需的所有 k8s client,包括sig/client-go中的clientset,sig/controller-manager中的client.Client和cache.Cache,以及k8s.io/metric中的clientset,通過Client,我們可以以各種姿勢溝通不同 k8s 集群的 k8s-apiserver,這也是實現多集群的基礎。
type Client interface { Cache() cache.Cache Direct() client.Client Metrics() versioned.Interface ClientSet() kubernetes.Interface}
Scout為探測指定集群健康狀況的偵察員,在下文會詳細闡述
Config為溝通指定集群所需的rest.Config,由Clustercr 中的KubeConfig轉化而來
StopCh是用來關閉Scout對指定集群的偵查行為,以及停止與該集群相關的所有informer行為
Scout -- 計算集群偵查員
scout 的職責是偵查指定集群的健康狀況,它對外提供 http 接口來接收來自不同集群的心跳,隨後將心跳包發送到對應的 scout 的 Receiver channel 中,對內則是起了一個 goroutine 循環接收來自 Receiver channel 的心跳包,並且根據健康和超時兩種情況做不同的 callback 處理。
// Collect will scout a specified warden of clusterfunc (s *Scout) Collect(ctx context.Context) { for { select { case info := <-s.Receiver: s.healthWarden(ctx, info) case <-time.Tick(time.Duration(s.WaitTimeoutSeconds) * time.Second): s.illWarden(ctx) case <-ctx.Done(): clog.Warn("scout of %v warden stopped: %v", s.Cluster, ctx.Err()) return } }}Cluster-Controller -- 集群 cr 控制器
Cluster 的 controller 主要負責感知 KubeCube 納管的集群變更,並且初始化計算集群的相關設置:
watch 到新的 cluster cr 的 create 事件
嘗試使用 cluster cr 中的元信息去溝通對應 k8s 集群的 apiserver
如果與對應的 k8s 集群建聯失敗,則會使該事件進入 retry 隊列(詳見下文的多集群容錯)
與 k8s 集群建聯成功後會創建對應的InternalCluster,並將其添加到MultiClustersManager管理的緩存中
向對應的集群下方 warden 的 deployment 以及一些必要的 crd 資源
創建該集群對應的 scout,並開啟對該集群的偵查
Warden 中的多集群模塊
Warden 是作為一種 cluster agent 的身份存在於每個計算集群中,它提供了計算集群與管控集群之間的資源同步能力、熱插拔能力、統一鑒權的能力、資源配置管理能力、心跳上報能力等等。了解 warden 中的多集群模塊,我們需要關注以下幾個 topic:
warden 如何向管控集群上報心跳信息
warden 如何從管控集群同步資源
Reporter -- 集群信息上報者
Warden 的 reporter 上報者是與 KubeCube 的 scout 偵察員一一對應,遙相呼應的。
Warden 需要會在啟動時根據已註冊的健康檢查方法去檢查各個模塊的健康狀態,等到它所依賴的各個模塊都達到健康狀態後,才會開始向管控集群上報心跳,如果健康檢查超時,warden 將會啟動失敗。
// waitForReady wait all components of warden readyfunc (r *Reporter) waitForReady() error { counts := len(checkFuncs) if counts < 1 { return fmt.Errorf("less 1 components to check ready") } // wait all components ready in specified time ctx, cancel := context.WithTimeout(context.Background(), time.Duration(r.WaitSecond)*time.Second) defer cancel() readyzCh := make(chan struct{}) for _, fn := range checkFuncs { go readyzCheck(ctx, readyzCh, fn) } for { select { case <-ctx.Done(): return ctx.Err() case <-readyzCh: counts-- if counts < 1 { return nil } } }}
Warden 為它自身所有的模塊提供了健康檢查入口,所有的會影響 warden 正常運行的模塊都需要通過RegisterCheckFunc方法註冊健康檢查函數以確保 warden 的正常啟動。
var checkFuncs []checkFunc// RegisterCheckFunc should be used to register readyz check funcfunc RegisterCheckFunc(fn checkFunc) { checkFuncs = append(checkFuncs, fn)}func readyzCheck(ctx context.Context, ch chan struct{}, checkFn checkFunc) { for { select { case <-time.Tick(waitPeriod): if checkFn() { ch <- struct{}{} return } case <-ctx.Done(): return } }}
完成各個模塊的健康檢查後,warden 啟動就緒,開始向管控集群上報包含集群信息的心跳,並且根據與管控集群的通信情況來觸發對應的函數回調。KubeCube 中的對應的 scout 會根據收到的上報信息做相應的處理。
// report do real report loopfunc (r *Reporter) report(stop <-chan struct{}) { for { select { case <-time.Tick(time.Duration(r.PeriodSecond) * time.Second): if !r.do() { r.illPivotCluster() } else { r.healPivotCluster() } case <-stop: return } }}Sync-Manager -- 集群資源同步器
Sync-Manager 實現了從管控集群同步資源到計算集群的能力,這也是實現多集群統一認證和鑒權能力的基礎。
多集群容錯
不可否認的是,多 K8s 集群在現實情況中可能會出現跨集群通信故障,有時候是因為集群與集群之間的網絡故障,有時候是某一集群的 K8s 故障,總之會造成跨集群訪問不可用。就 KubeCube 而言,目前主要關心兩種情況:
KubeCube 運行時,member cluster 失聯
KubeCube 啟動時,member cluster 失聯
針對上述的兩種情況,KubeCube 分別在 cluster-controller、scout 以及 kubecube-apiserver 的 middleware 中做出了相應的處理。
Cluster-Controller
上文我們提到了 cluster-controller 的 reconcile 邏輯,已知當我們在初始化納管一個集群時,我們會將通信失敗的 cluster 放進 retry 隊列中,並且將 cluster cr 的狀態 update 為 initFailed。
// generate internal cluster for current cluster and add // it to the cache of multi cluster manager skip, err := multiclustermgr.AddInternalCluster(currentCluster) if err != nil { log.Error(err.Error()) } if err != nil && !skip { updateFn := func(cluster *clusterv1.Cluster) { initFailedState := clusterv1.ClusterInitFailed reason := fmt.Sprintf("cluster %s init failed", cluster.Name) cluster.Status.State = &initFailedState cluster.Status.Reason = reason } err := utils.UpdateStatus(ctx, r.Client, ¤tCluster, updateFn) if err != nil { log.Error("update cluster %v status failed", currentCluster.Name) return ctrl.Result{}, err } r.enqueue(currentCluster) return ctrl.Result{}, nil }
初始化失敗的集群會在一個單獨的 goroutine 中進行定時重試重聯操作,並將重連成功的 cluster 通過 K8s 的 GenericEvent 重新做 reconcile,同時會在重試隊列中刪除該重試任務。重試超時默認為 12h,重試間隔為 7s。
// try to reconnect with cluster api server, requeue if every is ok go func() { log.Info("cluster %v init failed, keep retry background", cluster.Name) // pop from retry queue when reconnected or context exceed or context canceled defer r.retryQueue.Delete(cluster.Name) for { select { case <-time.Tick(retryInterval): _, err := client.New(config, client.Options{Scheme: r.Scheme}) if err == nil { log.Info("enqueuing cluster %v for reconciliation", cluster.Name) r.Affected <- event.GenericEvent{Object: &cluster} return } case <-ctx.Done(): log.Info("cluster %v retry task stopped: %v", cluster.Name, ctx.Err()) // retrying timeout need update status // todo(weilaaa): to allow user reconnect cluster manually if ctx.Err().Error() == "context deadline exceeded" { updateFn := func(cluster *clusterv1.Cluster) { state := clusterv1.ClusterReconnectedFailed reason := fmt.Sprintf("cluster %s reconnect timeout: %v", cluster.Name, retryTimeout) cluster.Status.State = &state cluster.Status.Reason = reason } err := utils.UpdateStatus(ctx, r.Client, &cluster, updateFn) if err != nil { log.Warn("update cluster %v status failed: %v", cluster.Name, err) } } return } } }()
當然,當用戶主動刪除該 cluster cr 時,controller 會調用 context 的 cancel 方法停止該 cluster 的重試任務,並將其從重試隊列中刪除。未來會支持用戶手動觸發重試重連的能力。
// stop retry task if cluster in retry queue cancel, ok := r.retryQueue.Load(cluster.Name) if ok { cancel.(context.CancelFunc)() clog.Debug("stop retry task of cluster %v success", cluster.Name) return nil }
Scout
Scout 作為計算集群的偵察員,當它感知到 member cluster 失聯時,它會更新對應的 cluster cr 的 status 為 clusterAbnormal,並且告知 multiClusterManger 該集群的異常狀態。
if !isDisconnected(cluster, s.WaitTimeoutSeconds) { // going here means cluster heartbeat is normal if s.ClusterState != v1.ClusterNormal { clog.Info("cluster %v connected", cluster.Name) } s.LastHeartbeat = cluster.Status.LastHeartbeat.Time s.ClusterState = v1.ClusterNormal return } if s.ClusterState == v1.ClusterNormal { reason := fmt.Sprintf("cluster %s disconnected", s.Cluster) updateFn := func(obj *v1.Cluster) { state := v1.ClusterAbnormal obj.Status.State = &state obj.Status.Reason = reason obj.Status.LastHeartbeat = &metav1.Time{Time: s.LastHeartbeat} } clog.Warn("%v, last heartbeat: %v", reason, s.LastHeartbeat) err := utils.UpdateStatus(ctx, s.Client, cluster, updateFn) if err != nil { clog.Error(err.Error()) } } s.ClusterState = v1.ClusterAbnormal
當 Scout 感知到 member cluster 重新上報心跳,恢復連接時,它會更新對應 cluster cr 的 status 為 normal,並告知 multiClusterManger 該集群恢復正常。
if s.ClusterState != v1.ClusterNormal { clog.Info("cluster %v connected", cluster.Name) } s.LastHeartbeat = time.Now() updateFn := func(obj *v1.Cluster) { state := v1.ClusterNormal obj.Status.State = &state obj.Status.Reason = fmt.Sprintf("receive heartbeat from cluster %s", s.Cluster) obj.Status.LastHeartbeat = &metav1.Time{Time: s.LastHeartbeat} } err = utils.UpdateStatus(ctx, s.Client, cluster, updateFn) if err != nil { clog.Error(err.Error()) return } s.ClusterState = v1.ClusterNormal
KubeCube-Apiserver-Middlewares
作為一個 http server 的預處理函數,它提供了集群狀態預檢的能力。當你想要通過 KubeCube 訪問某一集群的資源時,它會向 multiClusterManager 詢問該集群的狀態,如果對應集群不健康,它會做快速失敗。
// PreCheck do cluster health check, early return// if cluster if unhealthyfunc PreCheck() gin.HandlerFunc { return func(c *gin.Context) { cluster := fetchCluster(c) clog.Debug("request path: %v, request cluster: %v", c.FullPath(), cluster) if len(cluster) > 0 { _, err := multicluster.Interface().Get(cluster) if err != nil { clog.Warn("cluster %v unhealthy, err: %v", cluster, err.Error()) response.FailReturn(c, errcode.CustomReturn(http.StatusInternalServerError, "cluster %v unhealthy", cluster)) return } } }}多集群統一的認證和鑒權能力
KubeCube 的認證和鑒權能力是基於 K8s 原生的 RBAC 之上的,不同的是 KubeCube 在此之上做了多集群統一認證和鑒權能力的拓展。
權限規則同步
要做到同一個用戶在不同集群中有同樣的認證和鑒權結果,即需要保證不同集群間的權限規則相同:
新建用戶時,KubeCube 會為其創建對應的 user cr,也就是 roleBinding 中的 subject 主體
集群管理員為新用戶分配角色時,KubeCube 會為該 user 創建對應的 RBAC 規則
Warden 的資源同步管理器會將 user cr 以及 RBAC 規則從管控集群同步到計算集群
用戶訪問
KubeCube 支持靈活的用戶訪問方式,包括通過 KubeCube 的前端訪問 K8s,通過 kubectl 訪問 K8s 以及直接使用 rest-ful 的方式訪問 K8s,本質都是使用 user 對應的 token 去訪問 K8s。
KubeCube 會為每一個 user 生成複合 jwt 標準的 token,用戶可以獲取由此生成的 KubeConfig,前端憑藉此 token 與 KubeCube 交互
攜帶 token 去請求 k8s-apiserver 時,k8s-apiserver 會根據我們事先在它的啟動參數中配置的authentication-token-webhook-config-file: "/etc/cube/warden/webhook.config"參數去訪問 warden 的 authR webhook api,並獲取認證結果,比如持有 vela 的 token:xxxx,去訪問 warden 的認證服務後,得到 vela 這個 user
然後 k8s-apiserver 通過 vela user,以及與之匹配的 rbac 規則作為鑑權
從架構上看,即使 KubeCube 遭遇故障,只有 warden 正常運行,用戶依然可以通過 kubectl 和 rest-ful 的方式,通過統一的 K8s 認證和鑒權去訪問對應的 K8s 資源。
總結
KubeCube 的多集群模型實現依靠的是 KubeCube 和 Warden 的相輔相成,在使用上提供了多集群統一的認證、鑒權以及多租戶管理能力,在故障處理上提供了多集群容錯以及單集群自治的能力。
寫在最後
未來我們會持續提供更多功能,幫助企業簡化容器化落地。也歡迎大家的寶貴建議,添加以下微信進入KubeCube交流群。
作者簡介:蔡鑫濤,網易數帆輕舟容器平台開發,KubeCube Committer
相關文章:
KubeCube 開源:魔方六面,降階 Kubernetes 落地應用
開源 | KubeCube 多級租戶模型
KubeCube主頁:https://www.kubecube.io
GitHub:https://github.com/kubecube-io/kubecube