TiDB Operator 原始碼閱讀 (三) 編排元件控制迴圈

choubou發表於2021-09-09

[上篇文章]中,我們介紹了 TiDB Operator 的 Controller Manager 的設計和實現,瞭解了各個 Controller 如何接受和處理變更。在這篇文章中,我們將討論元件的 Controller 的實現。TiDBCluster Controller 負責了 TiDB 主要元件的生命週期管理,我們將以此為例, 介紹元件控制迴圈的編排設計。我們將會了解到完成 TiDB 叢集的生命週期管理過程中,各種控制迴圈事件經過了怎樣的編排,這些事件中又完成了哪些資源管理操作。在閱讀時,大家瞭解這些工作的大致過程和定義即可,我們將在下一篇文章中具體介紹各個元件如何套用下面的正規化。

元件控制迴圈的呼叫

在上一篇文章的程式碼介紹中,我們提到了 TiDBCluster controllerupdateTidbCluster 函式,位於 pkg/controller/tidbcluster/tidb_cluster_control.go,它是 TiDB 元件生命週期管理的入口,呼叫了一系列生命週期管理函式。略去註釋,我們可以發現 updateTidbCluster 函式依次呼叫了以下函式:

  1. c.reclaimPolicyManager.Sync(tc)

  2. c.orphanPodsCleaner.Clean(tc)

  3. c.discoveryManager.Reconcile(tc)

  4. c.ticdcMemberManager.Sync(tc)

  5. c.tiflashMemberManager.Sync(tc)

  6. c.pdMemberManager.Sync(tc)

  7. c.tikvMemberManager.Sync(tc)

  8. c.pumpMemberManager.Sync(tc)

  9. c.tidbMemberManager.Sync(tc)

  10. c.metaManager.Sync(tc)

  11. c.pvcCleaner.Clean(tc)

  12. c.pvcResizer.Resize(tc)

  13. c.tidbClusterStatusManager.Sync(tc)

這些函式可以分為兩類,一是 TiDB 元件的視角組織的控制迴圈實現,例如 PD,TiDB,TiKV,TiFlash,TiCDC,Pump,Discovery,另外一類是負責管理 TiDB 元件所使用的 Kubernetes 資源的管理以及其他元件外圍的生命週期管理操作,例如 PV 的 ReclaimPolicy 的維護,OrphanPod 的清理,Kubernetes 資源的 Meta 資訊維護,PVC 的清理和擴容,TiDBCluster 物件的狀態管理等。

TiDB 元件的生命週期管理過程

TiDB 的主要元件控制迴圈的程式碼分佈在 pkg/manager/member 目錄下以 _member_manager.go 結尾的檔案下,比如 pd_member_manager.go,這些檔案又引用了諸如 _scaler.go_upgrader.go 的檔案,這些檔案包含了擴縮容和升級相關功能的實現。從各個元件的 _member_manager.go 相關檔案,我們可以提煉出以下通用實現:

// Sync Service
if err := m.syncServiceForTidbCluster(tc); err != nil {
    return err
}
 
// Sync Headless Service
if err := m.syncHeadlessServiceForTidbCluster(tc); err != nil {
    return err
}
 
// Sync StatefulSet
return syncStatefulSetForTidbCluster(tc)
 
func syncStatefulSetForTidbCluster(tc *v1alpha1.TidbCluster) error {
    if err := m.syncTidbClusterStatus(tc, oldSet); err != nil {
        klog.Errorf("failed to sync TidbCluster: [%s/%s]'s status, error: %v", ns, tcName, err)
    }
 
    if tc.Spec.Paused {
        klog.V(4).Infof("tidb cluster %s/%s is paused, skip syncing for statefulset", tc.GetNamespace(), tc.GetName())
        return nil
    }
 
    cm, err := m.syncConfigMap(tc, oldSet)
 
    newSet, err := getnewSetForTidbCluster(tc, cm)
 
    if err := m.scaler.Scale(tc, oldSet, newSet); err != nil {
        return err
    }
 
    if err := m.failover.Failover(tc); err != nil {
        return err
    }
 
    if err := m.upgrader.Upgrade(tc, oldSet, newSet); err != nil {
        return err
    }
 
    return UpdateStatefulSet(m.deps.StatefulSetControl, tc, newSet, oldSet)
}

這段程式碼主要完成了同步 Service 和 同步 Statefulset 的工作,同步 Service 主要是為元件建立或同步 Service 資源,同步 Statefulset 具體包含了一下工作:

  1. 同步元件的 Status;

  2. 檢查 TiDBCluster 是否停止暫停了同步;

  3. 同步 ConfigMap;

  4. 根據 TidbCluster 配置生成新的 Statefulset,並且對新 Statefulset 進行滾動更新,擴縮容,Failover 相關邏輯的處理;

  5. 建立或者更新 Statefulset;

元件的控制迴圈是對上面幾項工作迴圈執行,使得元件保持最新狀態。下面將介紹 TiDB Operator 中這幾項工作具體需要完成哪些方面的工作。

同步 Service

一般 Service 的 Reconcile 在元件 Reconcile 開始部分,它負責建立和同步元件所使用的 Service,例如 cluster1-pdcluster1-pd-peer。在控制迴圈函式中,會呼叫 getNewServiceForTidbCluster 函式,透過 Tidbcluster CR 中記錄的資訊建立一個新的 Service 的模板,如果 Service 不存在,直接建立 Service,否則,透過比對新老 Service Spec 是否一致,來決定是否要更新 Service 物件。

TiDB 元件使用的 Service 中,包括了 Service 和 Headless Serivce,為元件提供了被訪問的能力。當元件不需要知道是哪個例項正在與它通訊,並且可以接受負載均衡方式的訪問,則可以使用 Service 服務,例如 TiKV,TiDB 等元件訪問 PD 時,就可以使用 Service 地址。當元件需要區分是那個 Pod 在提供服務時,則需要用 Pod DNS 進行通訊,例如 TiKV 在啟動時,會將自己的 [Pod DNS] 作為 Advertise Address 對外暴露,其他 Pod 可以透過這個 Pod DNS 訪問到自己。

同步 Status

完成 Service 的同步後,元件接入了叢集的網路,可以在叢集內訪問和被訪問。控制迴圈會進入 syncStatefulSetForTidbCluster,開始對 Statefulset 進行 Reconcile,首先是使用 syncTidbClusterStatus 對元件的 Status 資訊進行同步,後續的擴縮容、Failover、升級等操作會依賴 Status 中的資訊進行決策。

同步 Status 是 TiDB Operator 比較關鍵的操作,它需要同步 Kubernetes 各個元件的資訊和 TiDB 的叢集內部資訊,例如在 Kubernetes 方面,在這一操作中會同步叢集的副本數量,更新狀態,映象版本等資訊,檢查 Statefulset 是否處於更新狀態。在 TiDB 叢集資訊方面,TiDB Operator 還需要將 TiDB 叢集內部的資訊從 PD 中同步下來。例如 PD 的成員資訊,TiKV 的儲存資訊,TiDB 的成員資訊等,TiDB 叢集的健康檢查的操作便是在更新 Status 這一操作內完成。

檢查 TiDBCluster 是否暫停同步

更新完狀態後,會透過 tc.Spec.Paused 判斷叢集是否處於暫停同步狀態,如果暫停同步,則會跳過下面更新 Statefulset 的操作。

同步 ConfigMap

在同步完 Status 之後,syncConfigMap 函式會更新 ConfigMap,ConfigMap 一般包括了元件的配置檔案和啟動指令碼。配置檔案是透過 YAML 中 Spec 的 Config 項提取而來,目前支援 TOML 透傳和 YAML 轉換,並且推薦 TOML 格式。啟動指令碼則包含了獲取元件所需的啟動引數,然後用獲取好的啟動引數啟動元件程式。在 TiDB Operator 中,當元件啟動時需要向 TiDB Operator 獲取啟動引數時,TiDB Operator 側的資訊處理會放到 discovery 元件完成。如 PD 獲取引數用於決定初始化還是加入某個節點,就會使用 wget 訪問 discovery 拿到自己需要的引數。這種在啟動指令碼中獲取引數的方法,避免了更新 Statefulset 過程中引起的非預期滾動更新,對線上業務造成影響。

生成新 Statefulset

getNewPDSetForTidbCluster 函式會得到一個新的 Statefulset 的模板,它包含了對剛才生成的 Service,ConfigMap 的引用,並且根據最新的 Status 和 Spec 生成其他項,例如 env,container,volume 等,這個新的 Statefulset 還需要送到下面 3 個流程進行滾動更新,擴縮容,Failover 方面的加工,最後將這個新生成的 Statefulset 會被送到 UpdateStatefulSet 函式處理,判斷其是否需要實際更新到元件對應的 Statefulset。

新 Statefulset 的加工(一): 滾動更新

m.upgrader.Upgrade 函式負責滾動更新相關操作,主要更新 Statefulset 中 UpgradeStrategy.TypeUpgradeStrategy.Partition,滾動更新是藉助 Statefulset 的 RollingUpdate 策略實現的。元件 Reconcile 會設定 Statefulset 的升級策略為滾動更新,即元件 Statefulset 的 UpgradeStrategy.Type 設定為 RollingUpdate 。在 Kubernetes 的 Statefulset 使用中,可以透過配置 UpgradeStrategy.Partition 控制滾動更新的進度,即 Statefulset 只會更新序號大於或等於 partition 的值,並且未被更新的 Pod。透過這一機制就可以實現每個 Pod 在正常對外服務後才繼續滾動更新。在非升級狀態或者升級的啟動階段,元件的 Reconcile 會將 Statefulset 的 UpgradeStrategy.Partition 設定為 Statefulset 中最大的 Pod 序號,防止有 Pod 更新。在開始更新後,當一個 Pod 更新,並且重啟後對外提供服務,例如 TiKV 的 Store 狀態變為 Up,TiDB 的 Member 狀態為 healthy,滿足這樣的條件的 Pod 才被視為升級成功,然後調小 Partition 的值進行下一 Pod 的更新。

新 Statefulset 的加工(二): 擴縮容

m.scaler.Scale 函式負責擴縮容相關操作,主要是更新 Statefulset 中元件的 Replicas。Scale 遵循逐個擴縮容的原則,每次擴縮容的跨度為 1。Scale 函式會將 TiDBCluster 中指定的元件 Replicas 數,如 tc.Spec.PD.Replicas,與現有比較,先判斷是擴容需求還是縮容需求,然後完成一個步長的擴縮容的操作,再進入下一次元件 Reconcile,透過多次 Reconcile 完成所有的擴縮容需求。在擴縮容的過程中,會涉及到 PD 轉移 Leader,TiKV 刪除 Store 等使用 PD API 的操作,元件 Reconcile 過程中會使用 PD API 完成上述操作,並且判斷操作是否成功,再逐步進行下一次擴縮容。

新 Statefulset 的加工(三): Failover

m.failover.Failover 函式負責容災相關的操作,包括發現和記錄災難狀態,恢復災難狀態等,在部署 TiDB Operator 時配置開啟 AutoFailover 後,當發現有 Failure 的元件時記錄相關資訊到 FailureStores 或者 FailureMembers 這樣的狀態儲存的鍵值,並啟動一個新的元件 Pod 用於承擔這個 Pod 的工作負載。當原 Pod 恢復工作後,透過修改 Statefulset 的 Replicas 數量,將用於容災時分擔工作負載的 Pod 進行縮容操作。但是在 TiKV/TiFlash 的容災邏輯中,自動縮容容災過程中的 Pod 不是預設操作,需要設定 spec.tikv.recoverFailover: true 才會對新啟動的 Pod 縮容。

使用新 Statefulset 進行更新

在同步 Statefulset 的最後階段,已經完成了新 Statefulset 的生成,這時候會進入 UpdateStatefulSet 函式,這一函式中主要比對新的 Statefulset 和現有 StatefulSet 差異,如果不一致,則進行 Statefulset 的實際更新。另外,這一函式還需要檢查是否有沒有被管理的 Statefulset,這部分主要是舊版本使用 Helm Chart 部署的 TiDB,需要將這些 Statefulset 納入 TiDB Operator 的管理,給他們新增依賴標記。

完成上述操作後,TiDBCluster CR 的 Status 更新到最新,相關 Service,ConfigMap 被建立,生成了新的 Statefulset,並且滿足了滾動更新,擴縮容,Failover 的工作。元件的 Reconcile 週而復始,監控著元件的生命週期狀態,響應生命週期狀態改變和使用者輸入改變,使得叢集在符合預期的狀態下正常執行。

其他生命週期維護工作

除了 TiDB 主要元件的視角之外,還有一些運維操作被編排到了下面的函式實現中,他們分別負責了以下工作:

  1. Discovery,用於 PD 啟動引數的配置和 TiDB Dashboard Proxy,Discovery 的存在,可以提供一些動態資訊供元件索取,避免了修改 ConfigMap 導致 Pod 滾動更新。

  2. Reclaim PolicyManager,用於同步 tc.Spec.PVReclaimPolicy 的配置,預設配置下會將PV 的 Reclaim Policy 設定為 Retain,降低資料丟失的風險。

  3. OrphanPodCleaner,用於在 PVC 建立失敗的時候清除 Pod,讓 Statefulset Controller 重試 Pod 和對應 PVC 的建立。

  4. PVC Cleaner 用於 PVC 相關資源清理,清理被標記可以刪除的 PVC。

  5. PVC Resizer 用於 PVC 的擴容,在雲上使用時可以透過修改 TidbCluster 中的 Storage 相關配置修改 PVC 的大小。

  6. Meta Manager 用於同步 StoreIDLabel,MemberIDLabel,NamespaceLabel 等資訊到 Pod,PVC,PV 的 label 上。

  7. TiDBCluster Status Manager 用於同步 TidbMonitor 和 TiDB Dashboard 相關資訊。

小結

這篇文章介紹了 TiDBCluster 元件的控制迴圈邏輯的設計,試圖讓大家瞭解,當 TiDBCluster Controller 迴圈觸發各個元件的 Reconcile 函式時,元件 Reconcile 函式是按照怎樣的流程巡檢元件的相關資源,使用者預期的狀態,是如何透過 Reconcile 函式,變成實際執行的元件。TiDB Operator 中的控制迴圈都大致符合以上的設計邏輯,在後面的文章中,我們會具體介紹每個元件是如何套用上面的設計邏輯,實現元件的生命週期管理。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4548/viewspace-2797800/,如需轉載,請註明出處,否則將追究法律責任。

相關文章