容器化 | ClickHouse Operator 原理解析

作者:蘇厚鎮(zhèn) 青云科技數(shù)據(jù)庫(kù)研究工程師

從事 RadonDB ClickHouse 相關(guān)工作,熱衷于研究數(shù)據(jù)庫(kù)內(nèi)核。

通過(guò)《ClickHouse on K8s 部署篇》告材,對(duì)比了 RadonDB ClickHouse 集群在 Kubernetes 中部署的幾種方案坤次,表明使用 Operator 進(jìn)行部署和管理是最方便快捷的。

那么到底什么才是 Operator斥赋,Operator 又是如何與 Kubernetes 進(jìn)行協(xié)同工作的缰猴,Operator 的代碼邏輯又是怎樣的?本篇將基于 Operator 基本概念和源代碼解析疤剑,深度解析 ClickHouse Operator 運(yùn)行原理滑绒。

什么是 Operator?

在 Kubernetes 官方文檔[1]中隘膘,對(duì) Operator 的定義如下:

Operators are software extensions to Kubernetes that make use of custom resources to manage applications and their components. Operators follow Kubernetes principles, notably the control loop.

簡(jiǎn)單來(lái)說(shuō):Operator = 定制資源 + 控制器疑故。

那么定制資源和控制器又是什么呢?

定制資源

在 Kubernetes 官方文檔[1]中弯菊,對(duì)定制資源的定義如下:

Custom resources are extensions of the Kubernetes API.
It represents a customization of a particular Kubernetes installation.

Kubernetes 提供了一系列的資源纵势,包括 Statefulset、Service误续、Configmap 等吨悍。但是這些資源并不能完全滿足使用需求,例如在 K8s 中部署 ClickHouse 應(yīng)用時(shí)蹋嵌,需定制一個(gè) ClickHouse 應(yīng)用資源育瓜。

Kubernetes 提供了兩種方式向集群中添加定制資源:

  • CRD:無(wú)需編程。K8s 從 1.7 版本增加了 CRD 來(lái)擴(kuò)展 API栽烂,通過(guò) CRD 可以向 API 中增加新資源類型躏仇,無(wú)需修改 K8s 源碼或創(chuàng)建自定義的 API server,該功能大大提高了 Kubernetes 的擴(kuò)展能力腺办。
  • API 聚合:需要編程焰手。但支持對(duì) API 行為進(jìn)行更多的控制,例如數(shù)據(jù)如何存儲(chǔ)以及在不同 API 版本間如何轉(zhuǎn)換等怀喉。

ClickHouse Operator 在定制資源方面书妻,選用了 CRD 方式添加定制資源。

但是使用 CRD 定制資源后躬拢,僅僅是讓 Kubernetes 能夠識(shí)別定制資源的身份躲履。創(chuàng)建定制資源實(shí)例后,Kubernetes 只會(huì)將創(chuàng)建的實(shí)例存儲(chǔ)到數(shù)據(jù)庫(kù)中聊闯,并不會(huì)觸發(fā)任何業(yè)務(wù)邏輯工猜。在 ClickHouse 數(shù)據(jù)庫(kù)保存定制資源實(shí)例是沒(méi)有意義的,如果需要進(jìn)行業(yè)務(wù)邏輯控制菱蔬,就需要?jiǎng)?chuàng)建控制器篷帅。

控制器

Controller 的作用就是監(jiān)聽(tīng)指定對(duì)象的新增史侣、刪除、修改等變化魏身,并針對(duì)這些變化做出相應(yīng)的響應(yīng)惊橱,關(guān)于 Controller 的詳細(xì)設(shè)計(jì),可以參考 Harry (Lei) Zhang 老師在 twitter 上的分享叠骑,基本架構(gòu)圖如下:

file

從圖中可看出李皇,定制資源實(shí)例的變化會(huì)通過(guò) Informer 存入 WorkQueue,之后 Controller 會(huì)消費(fèi) WorkQueue宙枷,并對(duì)其中的數(shù)據(jù)做出業(yè)務(wù)響應(yīng)掉房。

Operator 其實(shí)就是圖中除了 API Server 和 etcd 的剩余部分。由于 Client慰丛、Informer 和 WorkQueue 是高度相似的卓囚,所以有很多項(xiàng)目可以自動(dòng)化生成 Controller 之外的業(yè)務(wù)邏輯(如 Client、Informer诅病、Lister)哪亿,因此用戶只需要專注于 Controller 中的業(yè)務(wù)邏輯即可。

ClickHouse Operator 代碼解析

代碼結(jié)構(gòu)

.
├── cmd         # metrics_exporter 和 operator 的入口函數(shù)
│   ├── metrics_exporter
│   └── operator
├── config      # ClickHouse 和 ClickHouse Operator 的配置文件贤笆,會(huì)通過(guò) ConfigMap 掛載到 pod
├── deploy      # 各類組件的部署腳本和部署 yaml 文件
│   ├── dev         # clickhouse operator 開(kāi)發(fā)版本安裝部署
│   ├── grafana     # grafana 監(jiān)控面板安裝部署
│   ├── operator    # clickhouse operator 安裝部署
│   ├── operator-web-installer
│   ├── prometheus  # prometheus 監(jiān)控部署
│   └── zookeeper   # zookeeper 安裝部署
├── dev         # 各類腳本蝇棉,如鏡像生成、應(yīng)用構(gòu)建芥永、應(yīng)用啟動(dòng)等
├── dockerfile  # 鏡像 dockerfile
├── docs        # 文檔
├── grafana-dashboard
├── hack
├── pkg         # 代碼邏輯
│   ├── announcer   # 通知器
│   ├── apis        # api, 定制資源類型
│   │   ├── clickhouse.radondb.com
│   │   └── metrics
│   ├── chop        # clickhouse operator 類型
│   ├── client      # 自動(dòng)生成篡殷,作用參考上面的圖
│   │   ├── clientset
│   │   ├── informers
│   │   └── listers
│   ├── controller  # controller 邏輯,主要關(guān)心部分
│   ├── model       # controller 調(diào)用 model
│   ├── util        # util
│   └── version     # version 信息
└── tests       # 自動(dòng)測(cè)試代碼

代碼邏輯

以下代碼均為簡(jiǎn)化版埋涧,僅取核心邏輯部分板辽。

Controller 中主要的工作邏輯存在于 Worker 中。

Run

Run 是 Worker 中整個(gè)工作邏輯入口棘催。Run 是一個(gè)無(wú)休止的工作循環(huán)劲弦,期望在一個(gè)線程中運(yùn)行。

func (w *worker) run() {
    ...
     
    for {
        // 消費(fèi) workqueue醇坝,該方法會(huì)阻塞邑跪,直到它可以返回一個(gè)項(xiàng)目
        item, shutdown := w.queue.Get()

        // 處理任務(wù)
        if err := w.processItem(item); err != nil {
            utilruntime.HandleError(err)
        }

        // 后置處理,從 workqueue 中刪除項(xiàng)目
        w.queue.Forget(item)
        w.queue.Done(item)
    }
     
    ...
}

processItem

processItem 處理 item呼猪,根據(jù) item 的類型決定需要調(diào)用的處理邏輯呀袱。

func (w *worker) processItem(item interface{}) error {
    ...
     
    switch item.(type) {
        ...
        case *ReconcileChi:
            reconcile, _ := item.(*ReconcileChi)
            switch reconcile.cmd {
            case reconcileAdd:      // 處理定制資源的新增
                return w.updateCHI(nil, reconcile.new)
            case reconcileUpdate:   // 處理定制資源的修改
                return w.updateCHI(reconcile.old, reconcile.new)
            case reconcileDelete:   // 處理定制資源的刪除
                return w.deleteCHI(reconcile.old)
            }

            utilruntime.HandleError(fmt.Errorf("unexpected reconcile - %#v", reconcile))
            return nil
        ...
    }
     
    ...
}

updateCHI

以最常用的 updateCHI 邏輯為例,看一下其處理邏輯郑叠。

// updateCHI 創(chuàng)建或者更新 CHI
func (w *worker) updateCHI(old, new *chop.ClickHouseInstallation) error {
    ...

    // 判斷是否需要執(zhí)行處理
    update := (old != nil) && (new != nil)
    if update && (old.ObjectMeta.ResourceVersion == new.ObjectMeta.ResourceVersion) {
        w.a.V(3).M(new).F().Info("ResourceVersion did not change: %s", new.ObjectMeta.ResourceVersion)
        return nil
    }

    // 判斷 new chi 是否正在被刪除
    if new.ObjectMeta.DeletionTimestamp.IsZero() {
        w.ensureFinalizer(new)      // 如果沒(méi)有,則添加 finalizer 防止 CHI 被刪除
    } else {
        return w.finalizeCHI(new)   // 如果刪除明棍,則無(wú)法繼續(xù)執(zhí)行操作乡革,返回
    }

    // 歸一化,方便后面使用
    old = w.normalize(old)
    new = w.normalize(new)
     
    actionPlan := NewActionPlan(old, new)   // 對(duì)比 old 和 new,生成 action plan

    // 進(jìn)行一系列的標(biāo)記沸版,方便 reconcile 進(jìn)行處理嘁傀,如 add、update 等视粮,代碼省略

    // 執(zhí)行 reconcile(需要深入理解)
    if err := w.reconcile(new); err != nil {
        w.a.WithEvent(new, eventActionReconcile, eventReasonReconcileFailed).
            WithStatusError(new).
            M(new).A().
            Error("FAILED update: %v", err)
        return nil
    }

    // 后置處理
    // 移除需要 delete 的項(xiàng)目
    actionPlan.WalkRemoved(
        func(cluster *chop.ChiCluster) {
            _ = w.deleteCluster(cluster)
        },
        func(shard *chop.ChiShard) {
            _ = w.deleteShard(shard)
        },
        func(host *chop.ChiHost) {
            _ = w.deleteHost(host)
        },
    )
    // 將新的 CHI 添加到監(jiān)控中
    if !new.IsStopped() {
        w.c.updateWatch(new.Namespace, new.Name, chopmodel.CreatePodFQDNsOfCHI(new))
    }
     
    ...
}

reconcile

updateCHI 中最重要的方法即 reconcile细办,該方法根據(jù)添加的標(biāo)記做實(shí)際的處理。

func (w *worker) reconcile(chi *chop.ClickHouseInstallation) error {
    w.a.V(2).M(chi).S().P()
    defer w.a.V(2).M(chi).E().P()

    w.creator = chopmodel.NewCreator(w.c.chop, chi) // cretea creator
    return chi.WalkTillError(
        // 前置處理
        // 1. 處理 CHI svc蕾殴,即 svc/clickhouse-{CHIName}
        // 2. 處理 CHI configmap笑撞,即 configmap/chi-{CHIName}-common-{configd/usersd}
        w.reconcileCHIAuxObjectsPreliminary,
        // 處理集群
        // 1. 處理 Cluster svc,即 svc/cluster-{CHIName}-{ClusterName}钓觉,不過(guò)貌似沒(méi)有茴肥?
        w.reconcileCluster,
        // 處理分片
        // 1. 處理 Shard svc,即 svc/shard-{CHIName}-{ClusterName}-{ShardName}荡灾,不過(guò)貌似沒(méi)有瓤狐?
        w.reconcileShard,
        // 處理副本
        // 0. 將副本從集群中解除
        // 1. 處理 Host Configmap,即 chi-{CHIName}-deploy-confd-{ClusterName}-{ShardName}-{HostName}
        // 2. 處理 Host StatefulSet批幌,即 chi-{CHIName}-{ClusterName}-{ShardName}-{HostName}
        // 3. 處理 Host PV础锐,即 chi-{CHIName}-{ClusterName}-{ShardName}-{HostName}
        // 4. 處理 Host svc,即 chi-{CHIName}-{ClusterName}-{ShardName}-{HostName}
        // 5. 解除 Host 的 add 狀態(tài)
        // 6. 判斷 Host 是否正常運(yùn)行
        // 7. 將副本添加到集群中荧缘,如果 Host 出錯(cuò)皆警,則回滾
        w.reconcileHost,
        // 后置處理
        // 1. 更新 CHI configmap,即 configmap/chi-{CHIName}-common-configd
        w.reconcileCHIAuxObjectsFinal,
    )
}

總結(jié)

至此胜宇,便揭開(kāi)了 Operator 的神秘面紗耀怜。如果對(duì) Operator 有更多興趣,歡迎到 Github 代碼庫(kù)查看更多細(xì)節(jié)桐愉。

[1]. Kubernetes 官方文檔 : https://kubernetes.io/docs/concepts/extend-kubernetes/operator/

[2]. RadonDB ClickHouse Kubernetes : https://github.com/radondb/radondb-clickhouse-operator/tree/chronus

推薦閱讀

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末财破,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子从诲,更是在濱河造成了極大的恐慌左痢,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件系洛,死亡現(xiàn)場(chǎng)離奇詭異俊性,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)描扯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)定页,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人绽诚,你說(shuō)我怎么就攤上這事典徊『技澹” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵卒落,是天一觀的道長(zhǎng)羡铲。 經(jīng)常有香客問(wèn)我,道長(zhǎng)儡毕,這世上最難降的妖魔是什么也切? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮腰湾,結(jié)果婚禮上雷恃,老公的妹妹穿的比我還像新娘。我一直安慰自己檐盟,他們只是感情好褂萧,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著葵萎,像睡著了一般导犹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上羡忘,一...
    開(kāi)封第一講書(shū)人閱讀 51,125評(píng)論 1 297
  • 那天谎痢,我揣著相機(jī)與錄音,去河邊找鬼卷雕。 笑死节猿,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的漫雕。 我是一名探鬼主播滨嘱,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼浸间!你這毒婦竟也來(lái)了太雨?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤魁蒜,失蹤者是張志新(化名)和其女友劉穎囊扳,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體兜看,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡锥咸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了细移。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片搏予。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖弧轧,靈堂內(nèi)的尸體忽然破棺而出缔刹,到底是詐尸還是另有隱情球涛,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布校镐,位于F島的核電站,受9級(jí)特大地震影響捺典,放射性物質(zhì)發(fā)生泄漏鸟廓。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一襟己、第九天 我趴在偏房一處隱蔽的房頂上張望引谜。 院中可真熱鬧,春花似錦擎浴、人聲如沸员咽。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)贝室。三九已至,卻和暖如春仿吞,著一層夾襖步出監(jiān)牢的瞬間滑频,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工唤冈, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留峡迷,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓你虹,卻偏偏與公主長(zhǎng)得像绘搞,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子傅物,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

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