隱藏的調(diào)度邏輯,ImageLocalityPriority 導(dǎo)致 Spark On Kubernetes 執(zhí)行 Job Pod 調(diào)度不均勻 (包含源代碼分析)

我是 LEE择葡,老李紧武,一個在 IT 行業(yè)摸爬滾打 17 年的技術(shù)老兵。

事件背景

昨天公司一套大數(shù)據(jù) Spark Job 運行的 Kubernetes 集群突然出現(xiàn)大量 Job Pod 被 Pending敏储,導(dǎo)致很多計算任務(wù)被卡住阻星,然后大量超時報警發(fā)到了大數(shù)據(jù)業(yè)務(wù)部門。沒一會就被小伙伴們叫到會議室準備一起解決問題已添,不到會議室不知道妥箕,那個熱鬧得跟趕集一樣。我剛進門就被其他的小伙伴抓住更舞,一起開始了確認大量 Job Pod 被 Pending 的問題畦幢,所有涉及上下游的小伙伴都在自查系統(tǒng),我也投入到容器相關(guān)的系統(tǒng)的檢查中缆蝉。

通過一段時間的排查宇葱,發(fā)現(xiàn) Pod 所運行的節(jié)點都很正常,而且整個 Kubernetes 所有節(jié)點資源都很充裕刊头,沒有理由 Pod 都集中運行到一個 Node 節(jié)點上黍瞧。不知不覺就進入了茫茫的日志海洋的排查,直到最大的領(lǐng)導(dǎo) GM 出現(xiàn)原杂,任然沒有找到出現(xiàn)這個問題位置印颤。沒有辦法,只能求教我們 Kubernetes 源代碼的大佬 - 吳老師穿肄,請他一起來“邊看源代碼年局,邊解決問題”。這個時候 Spark Job 的任務(wù)還在不停的創(chuàng)建被碗,集群上的 Job 任務(wù)還在瘋狂的堆積某宪,已經(jīng)嚴重影響到真實的生產(chǎn)流,GM 臉色越來越嚴肅锐朴。

現(xiàn)象獲取

我們做了很多假設(shè),都逐一被否定蔼囊。實在沒有辦法焚志,既然大量 Job Pod 被 Pending贩据,是因為被調(diào)度到一個固定的節(jié)點上導(dǎo)致的留特,大概率的是 Kubernetes 調(diào)度器的問題,我們把注意力集中到了 kube-scheduler 運行的 3 臺服務(wù)器上。通過長時間的觀測日志黔姜,總是發(fā)現(xiàn)相同的內(nèi)容,所有的 Job Pod 都被調(diào)度到了一個 Node 節(jié)點上分苇,不管怎么重啟 Job 或者 kube-scheduler 結(jié)果都一樣彤蔽,日志如下圖:

異常調(diào)度結(jié)果

在我們一籌莫展的時候,吳老師提議我們把 kube-scheduler 的日志用最詳細的方式輸出挑社,再觀察下日志陨界。尤其要觀察下 kube-scheduler 的 Filter 和 Scope 兩個環(huán)節(jié)的數(shù)據(jù)變化。

一不做二不休痛阻,說干就干菌瘪,直接讓所有 kube-scheduler 的日志輸出都在最高模式的下,我們繼續(xù)觀察日志阱当,然后查看 kube-scheduler 中的 plugin 中數(shù)值的變化俏扩。果不其然,進過大概 10 分鐘觀察和數(shù)據(jù)統(tǒng)計弊添,我們在結(jié)果中看到了一些內(nèi)容录淡,kube-scheduler 中的一個 plugin 讓我們高度重視,而且對比了整個 kube-scheduler 調(diào)度結(jié)果油坝,基本確認就是這個 plugin 導(dǎo)致的嫉戚,它就是:ImageLocalityPriority。他影響了 kube-scheduler 調(diào)度結(jié)果免钻。

具體 ImageLocalityPriority 產(chǎn)生的分數(shù)如下:

異常調(diào)度結(jié)果

從上面兩張圖對比就可以看出邏輯彼水,10.10.33.57 獲得 Scope 為 100,然后 Job Pod 都被調(diào)度到了 10.10.33.57 上极舔。是不是感覺非常有意思呢凤覆?

如果你覺得非常有意思,那么你跟我一樣拆魏,吳老師也是覺得非常有意思盯桦。 是不是很有想法跟我們一起往下看看具體原理呢?

原理分析

在定位到了問題以后渤刃,我們使用了一些“方法”解決了這個問題拥峦,讓卡住的大量 Job Pod 從新在整個集群上快速執(zhí)行起來。具體解決方案到下一部分我們再說卖子,我們先看看是什么原因?qū)е逻@個問題出現(xiàn)的略号,這樣我們才能真正的理解解決方案中的內(nèi)容。

(★)導(dǎo)致這次問題的真兇:ImageLocalityPriority

ImageLocalityPriority 前世今生

ImageLocalityPriority 插件的設(shè)計目的是通過優(yōu)先將 Pod 分配到已經(jīng)緩存了所需鏡像的節(jié)點上來提高 Kubernetes 調(diào)度器的性能和效率。

在 Kubernetes 集群中玄柠,每個節(jié)點都需要下載所有需要運行的容器鏡像突梦。如果集群中的所有節(jié)點都沒有所需鏡像,則 Kubernetes 將會選擇其中之一羽利,并將鏡像下載到該節(jié)點上宫患。這可能會導(dǎo)致不必要的網(wǎng)絡(luò)負載和較長的 Pod 啟動時間。

為了避免這種情況这弧,ImageLocalityPriority 插件引入了鏡像本地性的概念娃闲,即首選在已經(jīng)擁有所需鏡像的節(jié)點上啟動 Pod。這樣可以減少鏡像下載時間和網(wǎng)絡(luò)負載匾浪,并且可以提高調(diào)度效率和性能皇帮。

需要注意的是,使用 ImageLocalityPriority 插件會使節(jié)點之間的鏡像緩存不一致户矢,因此需要根據(jù)實際情況進行權(quán)衡和調(diào)整玲献。例如,在使用容器鏡像倉庫時梯浪,可以配置自己的鏡像緩存策略來確保節(jié)點之間的鏡像緩存一致性捌年。

ImageLocalityPriority 存在的目的

Kubernetes 調(diào)度器中的 ImageLocalityPriority 插件是通過優(yōu)先將 Pod 分配到已經(jīng)緩存了所需鏡像的節(jié)點上來提高調(diào)度性能的。

當需要將一個 Pod 分配給某個節(jié)點時挂洛,ImageLocalityPriority 插件會考慮該節(jié)點上是否已經(jīng)緩存了該 Pod 所需的鏡像礼预。如果該節(jié)點已經(jīng)擁有了所需鏡像,則該節(jié)點的得分會更高虏劲;否則托酸,該節(jié)點的得分會相應(yīng)降低。

為了確定一個節(jié)點是否已經(jīng)緩存了所需鏡像柒巫,ImageLocalityPriority 插件會查找該節(jié)點上的 Docker 版本和鏡像列表励堡,并與 Pod 的鏡像列表進行比較。如果發(fā)現(xiàn)鏡像列表匹配堡掏,則該節(jié)點的得分會更高应结。

需要注意的是,ImageLocalityPriority 插件只考慮節(jié)點上已經(jīng)緩存了的鏡像泉唁,而不考慮鏡像從其他節(jié)點下載的時間和網(wǎng)絡(luò)負載等因素鹅龄。因此,在使用 ImageLocalityPriority 插件時亭畜,需要根據(jù)實際情況進行權(quán)衡和調(diào)整扮休,并確保集群中的所有節(jié)點都能夠快速可靠地獲取所需鏡像

ImageLocalityPriority 算法解析

結(jié)合上面的提到的內(nèi)容,ImageLocalityPriority 算法實現(xiàn)非常簡單和粗暴拴鸵。

總共非常了兩部分:

  1. sumImageScores: 計算節(jié)點上應(yīng)用 Pod 中所有 Container 的容量打分玷坠,并最后匯總這個分數(shù)蜗搔。
  2. calculatePriority: 根據(jù) sumImageScores 和 Container 的數(shù)量計算這個 Pod 的分數(shù)。

有上面的兩部分計算的結(jié)果侨糟,最后通過 Scope 方法碍扔,將這個 plugin 計算的分數(shù)返回給 kube-scheduler瘩燥。

sumImageScores

pkg/scheduler/framework/plugins/imagelocality/image_locality.go

// sumImageScores returns the sum of image scores of all the containers that are already on the node.
// Each image receives a raw score of its size, scaled by scaledImageScore. The raw scores are later used to calculate
// the final score. Note that the init containers are not considered for it's rare for users to deploy huge init containers.
func sumImageScores(nodeInfo *framework.NodeInfo, containers []v1.Container, totalNumNodes int) int64 {
    var sum int64
    for _, container := range containers {
        if state, ok := nodeInfo.ImageStates[normalizedImageName(container.Image)]; ok {
            sum += scaledImageScore(state, totalNumNodes)
        }
    }
    return sum
}

// scaledImageScore returns an adaptively scaled score for the given state of an image.
// The size of the image is used as the base score, scaled by a factor which considers how much nodes the image has "spread" to.
// This heuristic aims to mitigate the undesirable "node heating problem", i.e., pods get assigned to the same or
// a few nodes due to image locality.
func scaledImageScore(imageState *framework.ImageStateSummary, totalNumNodes int) int64 {
    spread := float64(imageState.NumNodes) / float64(totalNumNodes)
    return int64(float64(imageState.Size) * spread)
}

  • scaledImageScore 負責(zé)計算已經(jīng)下載鏡像(待調(diào)度 Job Pod 的鏡像)節(jié)點數(shù)量占 Kubernetes 總節(jié)點數(shù)量的比重秕重,比重值與指定的 Container 鏡像大小值相乘,返回 int64 值厉膀。
  • sumImageScores 負責(zé)將所有的 Pod 中所有的 Container 執(zhí)行 scaledImageScore 計算溶耘,將所有值進行求和,返回 int64 值服鹅。

數(shù)學(xué)公式:

scaledImageScore 分數(shù)計算

calculatePriority

pkg/scheduler/framework/plugins/imagelocality/image_locality.go

// The two thresholds are used as bounds for the image score range. They correspond to a reasonable size range for
// container images compressed and stored in registries; 90%ile of images on dockerhub drops into this range.
const (
    mb                    int64 = 1024 * 1024
    minThreshold          int64 = 23 * mb
    maxContainerThreshold int64 = 1000 * mb
)

// calculatePriority returns the priority of a node. Given the sumScores of requested images on the node, the node's
// priority is obtained by scaling the maximum priority value with a ratio proportional to the sumScores.
func calculatePriority(sumScores int64, numContainers int) int64 {
    // 1G 容量 * Pod 中 Container 的數(shù)量凳兵,獲得最大的上限
    maxThreshold := maxContainerThreshold * int64(numContainers)
    if sumScores < minThreshold {
        sumScores = minThreshold
    } else if sumScores > maxThreshold {
        sumScores = maxThreshold
    }

    // 返回值在 0 - 100 之間
    return int64(framework.MaxNodeScore) * (sumScores - minThreshold) / (maxThreshold - minThreshold)
}

calculatePrioritysumImageScores 計算的結(jié)果在函數(shù)內(nèi)做對比:

  1. 如果 sumScores < 23 * 1024 * 1024,則 calculatePriority 返回 0
  2. 如果 sumScores >= numContainers * 1000 * 1024 * 1024, 則 calculatePriority 返回 100

不管你的 Pod 中間有多少的 Container企软,最后 Pod 計算結(jié)果只會落在 0 - 100 之間庐扫。

TIPS:我們這邊出問題的應(yīng)用是單 Container 的 Pod,這個 Container 的 Image 鏡像已經(jīng)超過了 1G仗哨,所以我們看到的打分是 100形庭。

數(shù)學(xué)公式:

calculatePriority 分數(shù)計算

Scope

pkg/scheduler/framework/plugins/imagelocality/image_locality.go

// Score invoked at the score extension point.
func (pl *ImageLocality) Score(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (int64, *framework.Status) {
    // 從 Snapshot 中獲取 NodeInfo。
    nodeInfo, err := pl.handle.SnapshotSharedLister().NodeInfos().Get(nodeName)
    // 如果出現(xiàn)錯誤厌漂,則返回 0 和帶有錯誤信息的 Status萨醒。
    if err != nil {
        return 0, framework.AsStatus(fmt.Errorf("getting node %q from Snapshot: %w", nodeName, err))
    }

    // 獲取所有 NodeInfo 列表,并得到節(jié)點數(shù)苇倡。
    nodeInfos, err := pl.handle.SnapshotSharedLister().NodeInfos().List()
    if err != nil {
        return 0, framework.AsStatus(err)
    }
    totalNumNodes := len(nodeInfos)

    // 計算 pod 的 priority富纸,并返回 score。
    score := calculatePriority(sumImageScores(nodeInfo, pod.Spec.Containers, totalNumNodes), len(pod.Spec.Containers))
    return score, nil
}

Score 方法就是返回 calculatePriority 計算的結(jié)果給 kube-scheduler旨椒,然后 kube-scheduler 繼續(xù)通過其他的 plugin 打分晓褪,最后返回所有 plugin 分數(shù)總和,決定 Pod 在哪個 Node 節(jié)點上運行综慎。

處理方法

清楚了 ImageLocalityPriority 整體結(jié)構(gòu)涣仿,以及相關(guān)代碼以及算法實現(xiàn),那么對應(yīng)的解決方案也就有了寥粹。

確實如此变过,而且解決方案也非常的簡單:

  1. 在 kube-scheduler 中關(guān)閉 ImageLocalityPriority 插件。
  2. 在 kube-scheduler 中降低 ImageLocalityPriority 的優(yōu)先級涝涤。
  3. 全部節(jié)點上部署 Image 鏡像同步器媚狰,讓 ImageLocalityPriority 打分返回 100 的 Pod 使用鏡像在 Kubernetes 集群中所有節(jié)點都被下載。

這里提供兩種實操阔拳,第 3 種有很多實現(xiàn)方式崭孤,這個大家可以自行 baidu类嗤,然后選擇復(fù)合自己的方案。

關(guān)閉 ImageLocalityPriority

關(guān)閉 ImageLocalityPriority

降低 ImageLocalityPriority 優(yōu)先級

降低 ImageLocalityPriority 優(yōu)先級

最終效果

最后我們這邊為了緊急恢復(fù) Spark Job 在 Kubernetes 上的運行辨宠,我們用了最簡單的方案 1遗锣。當然我們這邊還有更多的工作要做,才能真正讓這個問題在我們這邊測底消除嗤形。

同時也通過一個 test-job 來驗證我們的解決方案的效果精偿,與預(yù)期相符

恢復(fù)正常調(diào)度

多啰嗦一句:ImageLocalityPriority 導(dǎo)致 Spark Job 的 Pod 在 Kubernetes 調(diào)度不均的問題赋兵,確實沒有想過是因為鏡像的大小的導(dǎo)致的笔咽。我想這個問題也會導(dǎo)致很多小伙伴一頭霧水,最后在獲得吳老師同意后霹期,決定把我這次碰到問題成文叶组,提供給大家“避坑”。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末历造,一起剝皮案震驚了整個濱河市甩十,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌吭产,老刑警劉巖侣监,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異垮刹,居然都是意外死亡达吞,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進店門荒典,熙熙樓的掌柜王于貴愁眉苦臉地迎上來酪劫,“玉大人,你說我怎么就攤上這事寺董「苍悖” “怎么了?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵遮咖,是天一觀的道長滩字。 經(jīng)常有香客問我,道長御吞,這世上最難降的妖魔是什么麦箍? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮陶珠,結(jié)果婚禮上挟裂,老公的妹妹穿的比我還像新娘。我一直安慰自己揍诽,他們只是感情好诀蓉,可當我...
    茶點故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布栗竖。 她就那樣靜靜地躺著,像睡著了一般渠啤。 火紅的嫁衣襯著肌膚如雪狐肢。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天沥曹,我揣著相機與錄音份名,去河邊找鬼。 笑死架专,一個胖子當著我的面吹牛同窘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播部脚,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼裤纹!你這毒婦竟也來了委刘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤鹰椒,失蹤者是張志新(化名)和其女友劉穎锡移,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體漆际,經(jīng)...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡淆珊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了奸汇。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片施符。...
    茶點故事閱讀 38,747評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖擂找,靈堂內(nèi)的尸體忽然破棺而出戳吝,到底是詐尸還是另有隱情,我是刑警寧澤贯涎,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布听哭,位于F島的核電站,受9級特大地震影響塘雳,放射性物質(zhì)發(fā)生泄漏陆盘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一败明、第九天 我趴在偏房一處隱蔽的房頂上張望隘马。 院中可真熱鬧,春花似錦肩刃、人聲如沸祟霍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽沸呐。三九已至醇王,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間崭添,已是汗流浹背寓娩。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留呼渣,地道東北人棘伴。 一個月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像屁置,于是被迫代替她去往敵國和親焊夸。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,658評論 2 350

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

  • 前言 在近期的工作中蓝角,我們發(fā)現(xiàn) k8s 集群中有些節(jié)點資源使用率很高阱穗,有些節(jié)點資源使用率很低,我們嘗試重新部署應(yīng)用...
    劼哥stone閱讀 738評論 0 0
  • 前言 Kubernetes中的調(diào)度是將待處理的pod綁定到節(jié)點的過程使鹅,由Kubernetes的一個名為kube-s...
    YP小站閱讀 3,322評論 0 1
  • kube-scheduler是 kubernetes 系統(tǒng)的核心組件之一揪阶,主要負責(zé)整個集群資源的調(diào)度功能,根據(jù)特定...
    祁恩達閱讀 4,631評論 0 0
  • 簡述ETCD及其特點患朱? etcd 是 CoreOS 團隊發(fā)起的開源項目鲁僚,是一個管理配置信息和服務(wù)發(fā)現(xiàn)(servic...
    成淺閱讀 350評論 0 1
  • kubernetes 簡介 一個迅速過一遍kubernetes 非常不錯的資源:基于Kubernetes構(gòu)建Doc...
    bradyjoestar閱讀 15,277評論 2 7