作者:米哈游大數(shù)據(jù)開(kāi)發(fā)
近年來(lái),容器、微服務(wù)、Kubernetes 等各項(xiàng)云原生技術(shù)的日漸成熟,越來(lái)越多的公司開(kāi)始選擇擁抱云原生,并開(kāi)始將 AI、大數(shù)據(jù)等類型的企業(yè)應(yīng)用部署運(yùn)行在云原生之上。以 Spark 為例,在云上運(yùn)行 Spark 可以充分享有公共云的彈性資源、運(yùn)維管控和存儲(chǔ)服務(wù)等,并且業(yè)界也涌現(xiàn)了不少 Spark on Kubernetes 的優(yōu)秀實(shí)踐。
在剛剛結(jié)束的 2023 云棲大會(huì)上,米哈游數(shù)據(jù)平臺(tái)組大數(shù)據(jù)技術(shù)專家杜安明分享了米哈游大數(shù)據(jù)架構(gòu)向云原生化升級(jí)過(guò)程中的目標(biāo)、探索和實(shí)踐,以及如何通過(guò)以阿里云容器服務(wù) ACK 為底座的 Spark on K8s 架構(gòu),獲得在彈性計(jì)算、成本節(jié)約以及存算分離方面的價(jià)值。
背景簡(jiǎn)介
隨著米哈游業(yè)務(wù)的高速發(fā)展,大數(shù)據(jù)離線數(shù)據(jù)存儲(chǔ)量和計(jì)算任務(wù)量增長(zhǎng)迅速,早期的大數(shù)據(jù)離線架構(gòu)已不再滿足新場(chǎng)景和需求。
為了解決原有架構(gòu)缺乏彈性、運(yùn)維復(fù)雜、資源利用率低等問(wèn)題,2022 年下半年,我們著手調(diào)研將大數(shù)據(jù)基礎(chǔ)架構(gòu)云原生化,并最終在阿里云上落地了 Spark on K8s + OSS-HDFS 方案,目前在生產(chǎn)環(huán)境上已穩(wěn)定運(yùn)行了一年左右的時(shí)間,并獲得了彈性計(jì)算、成本節(jié)約以及存算分離這三大收益。
1. 彈性計(jì)算
由于游戲業(yè)務(wù)會(huì)進(jìn)行周期版本更新、開(kāi)啟活動(dòng)以及新游戲的上線等,對(duì)離線計(jì)算資源的需求與消耗波動(dòng)巨大,可能是平時(shí)水位的幾十上百倍。利用 K8s 集群天然的彈性能力,將 Spark 計(jì)算任務(wù)調(diào)度到 K8s 上運(yùn)行,可以比較輕松的解決這類場(chǎng)景下資源消耗洪峰問(wèn)題。
2. 成本節(jié)約
依托阿里云容器服務(wù) Kubernetes 版 ACK 集群自身強(qiáng)大的彈性能力,所有計(jì)算資源按量申請(qǐng)、用完釋放,再加上我們對(duì) Spark 組件的定制改造,以及充分利用 ECI Spot 實(shí)例,在承載同等計(jì)算任務(wù)和資源消耗下,成本節(jié)約達(dá) 50%。
3. 存算分離
Spark 運(yùn)行在 K8s 之上,完全使用 K8s 集群的計(jì)算資源,而訪問(wèn)的則數(shù)據(jù)也由 HDFS、OSS 逐步切換到 OSS-HDFS 上,中間 Shuffle 數(shù)據(jù)的讀寫(xiě)采用 Celeborn,整套架構(gòu)實(shí)現(xiàn)了計(jì)算和存儲(chǔ)的解耦,易于維護(hù)和擴(kuò)展。
Spark on K8s架構(gòu)演進(jìn)
眾所周知,Spark 引擎可以支持并運(yùn)行在多種資源管理器之上,比如 Yarn、K8s、Mesos 等。在大數(shù)據(jù)場(chǎng)景下,目前國(guó)內(nèi)大多公司的 Spark 任務(wù)還是運(yùn)行在 Yarn 集群之上的,Spark 在 2.3 版本首次支持 K8s,并于 2021 年 3 月發(fā)布的 Spark3.1 版本才正式 GA。
相較于 Yarn,Spark 在 K8s 上起步較晚,盡管在成熟度、穩(wěn)定性等方面還存在一定的欠缺,但是 Spark on K8s 能夠?qū)崿F(xiàn)彈性計(jì)算以及成本節(jié)約等非常突出的收益,所以各大公司也都在不斷進(jìn)行嘗試和探索,在此過(guò)程中,Spark on K8s 的運(yùn)行架構(gòu)也在不斷的向前迭代演進(jìn)。
1. 在離線混部
目前,將 Spark 任務(wù)運(yùn)行在 K8s 上,大多公司采用的方案依舊是在線與離線混合部署的方式。架構(gòu)設(shè)計(jì)依據(jù)的原理是,不同的業(yè)務(wù)系統(tǒng)會(huì)有不同的業(yè)務(wù)高峰時(shí)間。大數(shù)據(jù)離線業(yè)務(wù)系統(tǒng)典型任務(wù)高峰期間會(huì)是凌晨的0點(diǎn)到 9 點(diǎn)鐘,而像是各種應(yīng)用微服務(wù)、Web 提供的 BI 系統(tǒng)等,常見(jiàn)的業(yè)務(wù)高峰期是白天時(shí)間,在這個(gè)時(shí)間以外的其它時(shí)間中,可以將業(yè)務(wù)系統(tǒng)的機(jī)器 Node 加入到 Spark 所使用的 K8s NameSpace 中。如下圖所示,將 Spark 與其他在線應(yīng)用服務(wù)等都部署在一套 K8s 集群之上。
該架構(gòu)的優(yōu)點(diǎn)是可以通過(guò)在離線業(yè)務(wù)的混合部署和錯(cuò)峰運(yùn)行,來(lái)提升機(jī)器資源利用率并降低成本,但是缺點(diǎn)也比較明顯,即架構(gòu)實(shí)施起來(lái)復(fù)雜,維護(hù)成本比較高,而且難以做到嚴(yán)格的資源隔離,尤其是網(wǎng)絡(luò)層面的隔離,業(yè)務(wù)之間不可避免的會(huì)產(chǎn)生一定的相互影響,此外,我們認(rèn)為該方式也不符合云原生的理念和未來(lái)發(fā)展趨勢(shì)。
2. SparkonK8s+OSS-HDFS
考慮到在離線混合部署的弊端,我們?cè)O(shè)計(jì)采用了一種新的、也更加符合云原生的實(shí)現(xiàn)架構(gòu):底層存儲(chǔ)采用 OSS-HDFS (JindoFs),計(jì)算集群采用阿里云的容器服務(wù) ACK,Spark 選擇功能相對(duì)豐富且比較穩(wěn)定的 3.2.3 版本。
OSS-HDFS 完全兼容了 HDFS 協(xié)議,除了具備 OSS 無(wú)限容量、支持?jǐn)?shù)據(jù)冷熱存儲(chǔ)等優(yōu)點(diǎn)以外,還支持了目錄原子性、毫秒級(jí) rename 操作,非常適用于離線數(shù)倉(cāng),可以很好的平替現(xiàn)有 HDFS 和 OSS。
阿里云 ACK 集群提供了高性能、可伸縮的容器應(yīng)用管理服務(wù),可以支持企業(yè)級(jí) Kubernetes 容器化應(yīng)用的生命周期管理,ECS 是大家所熟知的阿里云服務(wù)器,而彈性容器實(shí)例 ECI 是一種 Serverless 容器運(yùn)行服務(wù),可以按量秒級(jí)申請(qǐng)與釋放。
該架構(gòu)簡(jiǎn)單易維護(hù),底層利用 ECI 的彈性能力,Spark 任務(wù)可以較為輕松的應(yīng)對(duì)高峰流量,將 Spark 的 Executor 調(diào)度在 ECI 節(jié)點(diǎn)上運(yùn)行,可最大程度的實(shí)現(xiàn)計(jì)算任務(wù)彈性與最佳的降本效果,整體架構(gòu)的示意圖如下所示。
云原生架構(gòu)設(shè)計(jì)與實(shí)現(xiàn)
1. 基本原理
在闡述具體實(shí)現(xiàn)之前,先簡(jiǎn)要介紹一下 Spark 在 K8s 上運(yùn)行的基本原理。Pod 在 K8s 中是最小的調(diào)度單元,Spark 任務(wù)的 Driver 和 Executor 都是一個(gè)單獨(dú) Pod,每個(gè) Pod 都分配了唯一的 IP 地址,Pod 可以包含一個(gè)或多個(gè) Container,無(wú)論是 Driver 還是 Executor 的 JVM 進(jìn)程,都是在 Container 中進(jìn)行啟動(dòng)、運(yùn)行與銷毀的。
一個(gè) Spark 任務(wù)被提交到 K8s 集群之后,首先啟動(dòng)的是 Driver Pod,而后 Driver 會(huì)向 Apiserver 按需申請(qǐng) Executor,并由 Executor 去執(zhí)行具體的 Task,作業(yè)完成之后由 Driver 負(fù)責(zé)清理所有的 Executor Pod,以下是這幾者關(guān)系的簡(jiǎn)要示意圖。
2. 執(zhí)行流程
下圖展示了完整的作業(yè)執(zhí)行流程,用戶在完成 Spark 作業(yè)開(kāi)發(fā)后,會(huì)將任務(wù)發(fā)布到調(diào)度系統(tǒng)上并進(jìn)行相關(guān)運(yùn)行參數(shù)的配置,調(diào)度系統(tǒng)定時(shí)將任務(wù)提交到自研的 Launcher 中間件,并由中間件來(lái)調(diào)用 spark-k8s-cli,最終由 Cli 將任務(wù)提交至 K8s 集群上。任務(wù)提交成功之后,Spark Driver Pod 最先啟動(dòng),并向集群申請(qǐng)分配 Executor Pod,Executor 在運(yùn)行具體的 Task 時(shí),會(huì)與外部 Hive、Iceberg、OLAP 數(shù)據(jù)庫(kù)、OSS-HDFS 等諸多大數(shù)據(jù)組件進(jìn)行數(shù)據(jù)的訪問(wèn)與交互,而 Spark Executor 之間的數(shù)據(jù) Shuffle 則由 CeleBorn 來(lái)實(shí)現(xiàn)。
3. 任務(wù)提交
關(guān)于如何將 Spark 任務(wù)提交到 K8s 集群上,各個(gè)公司的做法不盡相同,下面先簡(jiǎn)要描述下目前比較常規(guī)的做法,然后再介紹目前我們線上所使用的任務(wù)提交和管理方式。
3.1 使用原生 spark-submit
通過(guò) spark-submit 命令直接提交,Spark 原生就支持這種方式,集成起來(lái)比較簡(jiǎn)單,也符合用戶的習(xí)慣,但是不方便進(jìn)行作業(yè)狀態(tài)跟蹤和管理,無(wú)法自動(dòng)配置 Spark UI 的 Service 和 Ingress,任務(wù)結(jié)束后也無(wú)法自動(dòng)清理資源等,在生產(chǎn)環(huán)境中并不適合。
3.2 使用 spark-on-k8s-operator
這是目前較常用的一種提交作業(yè)方式,K8s 集群需要事先安裝 spark-operator,客戶端通過(guò) kubectl 提交 yaml 文件來(lái)運(yùn)行 Spark 作業(yè)。本質(zhì)上這是對(duì)原生方式的擴(kuò)展,最終提交作業(yè)依然是使用 spark-submit 方式,擴(kuò)展的功能包括:作業(yè)管理,Service/Ingress 創(chuàng)建與清理,任務(wù)監(jiān)控,Pod 增強(qiáng)等。此種方式可在生產(chǎn)環(huán)境中使用,但與大數(shù)據(jù)調(diào)度平臺(tái)集成性不太好,對(duì)于不熟悉 K8s 的用戶來(lái)說(shuō),使用起來(lái)復(fù)雜度和上手門檻相對(duì)較高。
3.3 使用 spark-k8s-cli
在生產(chǎn)環(huán)境上,我們采用 spark-k8s-cli 的方式進(jìn)行任務(wù)的提交。spark-k8s-cli 本質(zhì)上是一個(gè)可執(zhí)行的文件,基于阿里云 emr-spark-ack 提交工具我們進(jìn)行了重構(gòu)、功能增強(qiáng)和深度的定制。
spark-k8s-cli 融合 spark-submit 和 spark-operator 兩種作業(yè)提交方式的優(yōu)點(diǎn),使得所有作業(yè)都能通過(guò) spark-operator 管理,支持運(yùn)行交互式 spark-shell 和本地依賴的提交,并且在使用方式上與原生 spark-submit 語(yǔ)法完全一致。
在上線使用初期,我們所有任務(wù)的 Spark Submit JVM 進(jìn)程都啟動(dòng)在 Gateway Pod 中,在使用一段時(shí)間后,發(fā)現(xiàn)該方式穩(wěn)定性不足,一旦 Gateway Pod 異常,其上的所有正在 Spark 任務(wù)都將失敗,另外 Spark 任務(wù)的日志輸出也不好管理。鑒于此種情況,我們將 spark-k8s-cli 改成了每個(gè)任務(wù)使用單獨(dú)一個(gè) Submit Pod 的方式,由 Submit Pod 來(lái)申請(qǐng)啟動(dòng)任務(wù)的 Driver,Submit Pod 和 Driver Pod 一樣都運(yùn)行在固定的 ECS 節(jié)點(diǎn)之上,Submit Pod 之間完全獨(dú)立,任務(wù)結(jié)束后 Submit Pod 也會(huì)自動(dòng)釋放。spark-k8s-cli 的提交和運(yùn)行原理如下圖所示。
關(guān)于 spark-k8s-cli,除了上述基本的任務(wù)提交以外,我們還做了其他一些增強(qiáng)和定制化的功能。
支持提交任務(wù)到同地域多個(gè)不同的 K8s 集群上,實(shí)現(xiàn)集群之間的負(fù)載均衡和故障轉(zhuǎn)移切換
實(shí)現(xiàn)類似 Yarn 資源不足時(shí)的自動(dòng)排隊(duì)等待功能(K8s 如果設(shè)置了資源 Quota,當(dāng) Quota 達(dá)到上限后,任務(wù)會(huì)直接失敗)
增加與 K8s 網(wǎng)絡(luò)通信等異常處理、創(chuàng)建或啟動(dòng)失敗重試等,對(duì)偶發(fā)的集群抖動(dòng)、網(wǎng)絡(luò)異常進(jìn)行容錯(cuò)
支持按照不同部門或業(yè)務(wù)線,對(duì)大規(guī)模補(bǔ)數(shù)任務(wù)進(jìn)行限流和管控功能
內(nèi)嵌任務(wù)提交失敗、容器創(chuàng)建或啟動(dòng)失敗以及運(yùn)行超時(shí)等告警功能
4. 日志采集與展示
K8s 集群本身并沒(méi)有像 Yarn 那樣提供日志自動(dòng)聚合和展示的功能,Driver 和 Executor 的日志收集需要用戶自己來(lái)完成。目前比較常見(jiàn)的方案是在各個(gè) K8s Node 上部署 Agent,通過(guò) Agent 把日志采集并落在第三方存儲(chǔ)上,比如 ES、SLS 等,但這些方式對(duì)于習(xí)慣了在 Yarn 頁(yè)面上點(diǎn)擊查看日志的用戶和開(kāi)發(fā)者來(lái)說(shuō),使用起來(lái)很不方便,用戶不得不跳轉(zhuǎn)到第三方系統(tǒng)上撈取查看日志。
為實(shí)現(xiàn) K8s Spark 任務(wù)日志的便捷查看,我們對(duì) Spark 代碼進(jìn)行了改造,使 Driver 和 Executor 日志最終都輸出到 OSS 上,用戶可以在 Spark UI 和 Spark Jobhistory 上,直接點(diǎn)擊查看日志文件。
上圖所示為日志的收集和展示原理,Spark 任務(wù)在啟動(dòng)時(shí),Driver 和 Executor 都會(huì)首先注冊(cè)一個(gè) Shutdown Hook,當(dāng)任務(wù)結(jié)束 JVM 退出時(shí),調(diào)用 Hook 方法把完整的日志上傳到 OSS 上。此外,想要完整查看日志,還需要對(duì) Spark 的 Job History 相關(guān)代碼做下改造,需要在 History 頁(yè)面顯示 stdout 和 stderr,并在點(diǎn)擊日志時(shí),從 OSS 上拉取對(duì)應(yīng) Driver 或 Executor 的日志文件,最終由瀏覽器渲染查看。另外,對(duì)于正在運(yùn)行中的任務(wù),我們會(huì)提供一個(gè) Spark Running Web UI 給用戶,任務(wù)提交成功后,spark-operator 會(huì)自動(dòng)生成的 Service 和 Ingress 供用戶查看運(yùn)行詳情,此時(shí)日志的獲取通過(guò)訪問(wèn) K8s 的 api 拉取對(duì)應(yīng) Pod 的運(yùn)行日志即可。
5. 彈性與降本
基于 ACK 集群提供的彈性伸縮能力,再加上對(duì) ECI 的充分利用,同等規(guī)模量級(jí)下的 Spark 任務(wù),運(yùn)行在 K8s 的總成本要明顯低于在 Yarn 固定集群上,同時(shí)也大大提高了資源利用率。
彈性容器實(shí)例 ECI 是一種 Serverless 容器運(yùn)行服務(wù),ECI 和 ECS 最大的不同就在于 ECI 是按量秒級(jí)計(jì)費(fèi)的,申請(qǐng)與釋放速度也是秒級(jí)的,所以 ECI 很適合 Spark 這一類負(fù)載峰谷明顯的計(jì)算場(chǎng)景。
上圖示意了 Spark 任務(wù)在 ACK 集群上如何申請(qǐng)和使用 ECI,使用前提是在集群中安裝 ack-virtual-node 組件,并配置好 Vswitch 等信息,在任務(wù)運(yùn)行時(shí),Executor 被調(diào)度到虛擬節(jié)點(diǎn)上,并由虛擬節(jié)點(diǎn)申請(qǐng)創(chuàng)建和管理 ECI。
ECI 分為普通實(shí)例和搶占式實(shí)例,搶占式實(shí)例是一種低成本競(jìng)價(jià)型實(shí)例,默認(rèn)有 1 小時(shí)的保護(hù)期,適用于大部分 Spark 批處理場(chǎng)景,超出保護(hù)期后,搶占式實(shí)例可能被強(qiáng)制回收。為進(jìn)一步提升降本效果,充分利用搶占式實(shí)例的價(jià)格優(yōu)勢(shì),我們對(duì) Spark 進(jìn)行改造,實(shí)現(xiàn)了 ECI 實(shí)例類型自動(dòng)轉(zhuǎn)換的功能。Spark 任務(wù)的 Executor Pod 都優(yōu)先運(yùn)行在搶占式 ECI 實(shí)例上,當(dāng)發(fā)生庫(kù)存不足或其他原因無(wú)法申請(qǐng)創(chuàng)建搶占式實(shí)例,則自動(dòng)切換為使用普通 ECI 實(shí)例,保證任務(wù)的正常運(yùn)行。具體實(shí)現(xiàn)原理和轉(zhuǎn)換邏輯如下圖所示。
6. Celeborn
由于 K8s 節(jié)點(diǎn)的磁盤(pán)容量很小,而且節(jié)點(diǎn)都是用時(shí)申請(qǐng)、用完釋放的,無(wú)法保存大量的 Spark Shuffle 數(shù)據(jù)。如果對(duì) Executor Pod 掛載云盤(pán),掛載盤(pán)的大小難以確定,考慮到數(shù)據(jù)傾斜等因素,磁盤(pán)的使用率也會(huì)比較低,使用起來(lái)比較復(fù)雜。此外,雖然 Spark 社區(qū)在 3.2 提供了 Reuse PVC 等功能,但是調(diào)研下來(lái)覺(jué)得功能尚不完備且穩(wěn)定性不足。
為解決 Spark 在 K8s 上數(shù)據(jù) Shuffle 的問(wèn)題,在充分調(diào)研和對(duì)比多家開(kāi)源產(chǎn)品后,最終采用了阿里開(kāi)源的 Celeborn 方案。Celeborn 是一個(gè)獨(dú)立的服務(wù),專門用于保存 Spark 的中間 Shuffle 數(shù)據(jù),讓 Executor 不再依賴本地盤(pán),該服務(wù) K8s 和 Yarn 均可以使用。Celeborn 采用了 Push Shuffle 的模式,Shuffle 過(guò)程為追加寫(xiě)、順序讀,提升數(shù)據(jù)讀寫(xiě)性能和效率。
基于開(kāi)源的 Celeborn 項(xiàng)目,我們內(nèi)部也做了一些數(shù)據(jù)網(wǎng)絡(luò)傳輸方面的功能增強(qiáng)、Metrics 豐富、監(jiān)控告警完善、Bug 修復(fù)等工作,目前已形成了內(nèi)部穩(wěn)定版本。
7. KyuubionK8s
Kyuubi 是一個(gè)分布式和多租戶的網(wǎng)關(guān),可以為 Spark、Flink 或 Trino 等提供 SQL 等查詢服務(wù)。在早期,我們的 Spark Adhoc 查詢是發(fā)送到 Kyuubi 上執(zhí)行的。為了解決 Yarn 隊(duì)列資源不足,用戶的查詢 SQL 無(wú)法提交和運(yùn)行的問(wèn)題,在 K8s 上我們也支持了 Kyuubi Server 的部署運(yùn)行,當(dāng) Yarn 資源不足時(shí),Spark 查詢自動(dòng)切換到 K8s 上運(yùn)行。鑒于 Yarn 集群規(guī)模逐漸縮減,查詢資源無(wú)法保證,以及保障相同的用戶查詢體驗(yàn),目前我們已將所有的 SparkSQL Adhoc 查詢提交到 K8s 上執(zhí)行。
為了讓用戶的 Adhoc 查詢也能在 K8s 上暢快運(yùn)行,我們對(duì) Kyuubi 也做了一些源碼改造,包括對(duì) Kyuubi 項(xiàng)目中 docker-image-tool.sh、Deployment.yaml、Dockfile 文件的改寫(xiě),重定向 Log 到 OSS 上,Spark Operator 管理支持、權(quán)限控制、便捷查看任務(wù)運(yùn)行 UI 等。
8. K8sManager
在 Spark on K8s 場(chǎng)景下,盡管 K8s 有集群層面的監(jiān)控告警,但是還不能完全滿足我們的需求。在生產(chǎn)環(huán)境中,我們更加關(guān)注的是在集群上的 Spark 任務(wù)、Pod 狀態(tài)、資源消耗以及 ECI 等運(yùn)行情況。利用 K8s 的 Watch 機(jī)制,我們實(shí)現(xiàn)了自己的監(jiān)控告警服務(wù) K8s Manager,下圖所示為該服務(wù)的示意圖。
K8sManager 是內(nèi)部實(shí)現(xiàn)的一個(gè)比較輕量的 Spring Boot 服務(wù),實(shí)現(xiàn)的功能就是對(duì)各個(gè) K8s 集群上的 Pod、Quota、Service、ConfigMap、Ingress、Role 等各類資源信息監(jiān)聽(tīng)和匯總處理,從而生成自定義的 Metrics 指標(biāo),并對(duì)指標(biāo)進(jìn)行展示和異常告警,其中包括集群 CPU 與 Memory 總使用量、當(dāng)前運(yùn)行的 Spark 任務(wù)數(shù)、Spark 任務(wù)內(nèi)存資源消耗與運(yùn)行時(shí)長(zhǎng) Top 統(tǒng)計(jì)、單日 Spark 任務(wù)量匯總、集群 Pod 總數(shù)、Pod 狀態(tài)統(tǒng)計(jì)、ECI 機(jī)器型號(hào)與可用區(qū)分布統(tǒng)計(jì)、過(guò)期資源監(jiān)控等等,這里就不一一列舉了。
9. 其他工作
9.1 調(diào)度任務(wù)自動(dòng)切換
在我們的調(diào)度系統(tǒng)中,Spark 任務(wù)支持配置 Yarn、K8s、Auto 三種執(zhí)行策略。如果用戶任務(wù)指明了需要運(yùn)行使用的資源管理器,則任務(wù)只會(huì)在 Yarn 或 K8s 上運(yùn)行,若用戶選擇了 Auto,則任務(wù)具體在哪里執(zhí)行,取決于當(dāng)前 Yarn 隊(duì)列的資源使用率,如下圖所示。由于總?cè)蝿?wù)量較大,且 Hive 任務(wù)也在不斷遷移至 Spark,目前仍然有部分任務(wù)運(yùn)行在 Yarn 集群上,但最終的形態(tài)所有任務(wù)將由 K8s 來(lái)托管。
9.2 多可用區(qū)、多交換機(jī)支持
Spark 任務(wù)運(yùn)行過(guò)程中大量使用 ECI,ECI 創(chuàng)建成功有兩個(gè)前提條件: 1、能夠申請(qǐng)到 IP 地址;2、當(dāng)前可用區(qū)有庫(kù)存。實(shí)際上,單個(gè)交換機(jī)提供的可用 IP 數(shù)量有限,單個(gè)可用區(qū)擁有的搶占式實(shí)例的總個(gè)數(shù)也是有限的,因此在實(shí)際生產(chǎn)環(huán)境中,無(wú)論是使用普通 ECI 還是 Spot 類型的 ECI,比較好的實(shí)踐方式是配置支持多可用區(qū)、多交換機(jī)。
9.3 成本計(jì)算
由于在 Spark 任務(wù)提交時(shí),都已明確指定了每個(gè) Executor 的 Cpu、Memory 等型號(hào)信息,在任務(wù)結(jié)束 SparkContxt 關(guān)閉之前,我們可以從任務(wù)的中拿到每個(gè) Executor 的實(shí)際運(yùn)行時(shí)長(zhǎng),再結(jié)合單價(jià),即可計(jì)算出 Spark 任務(wù)的大致花費(fèi)。由于 ECI Spot 實(shí)例是隨著市場(chǎng)和庫(kù)存量隨時(shí)變動(dòng)的,該方式計(jì)算出來(lái)的單任務(wù)成本是一個(gè)上限值,主要用于反映趨勢(shì)。
9.4 優(yōu)化 SparkOperator
在上線初期任務(wù)量較少時(shí),Spark Operator 服務(wù)運(yùn)行良好,但隨著任務(wù)不斷增多,Operator 處理各類 Event 事件的速度越來(lái)越慢,甚至集群出現(xiàn)大量的 ConfigMap、Ingress、Service 等任務(wù)運(yùn)行過(guò)程中產(chǎn)生的資源無(wú)法及時(shí)清理導(dǎo)致堆積的情況,新提交 Spark 任務(wù)的 Web UI 也無(wú)法打開(kāi)訪問(wèn)。發(fā)現(xiàn)問(wèn)題后,我們調(diào)整了 Operator 的協(xié)程數(shù)量,并實(shí)現(xiàn)對(duì) Pod Event 的批量處理、無(wú)關(guān)事件的過(guò)濾、TTL 刪除等功能,解決了 Spark Operator 性能不足的問(wèn)題。
9.5 升級(jí) SparkK8sClient
Spark3.2.2 采用 fabric8 (Kubernetes Java Client) 來(lái)訪問(wèn)和操作 K8s 集群中的資源,默認(rèn)客戶端版本為 5.4.1,在此版本中,當(dāng)任務(wù)結(jié)束 Executor 集中釋放時(shí),Driver 會(huì)大量發(fā)送 Delete Pod 的 Api 請(qǐng)求到 K8s Apiserver 上,對(duì)集群 Apiserver 和 ETCD 造成較大的壓力,Apiserver 的 cpu 會(huì)瞬間飆高。
目前我們的內(nèi)部 Spark 版本,已將 kubernetes-client 升級(jí)到 6.2.0,支持 pod 的批量刪除,解決 Spark 任務(wù)集中釋放時(shí),由大量的刪除 Api 請(qǐng)求操作的集群抖動(dòng)。
問(wèn)題與解決方案
在整個(gè) Spark on K8s 的方案設(shè)計(jì)以及實(shí)施過(guò)程中,我們也遇到了各種各樣的問(wèn)題、瓶頸和挑戰(zhàn),這里做下簡(jiǎn)單的介紹,并給出我們的解決方案。
1.彈性網(wǎng)卡釋放慢
彈性網(wǎng)卡釋放速度慢的問(wèn)題,屬于 ECI 大規(guī)模應(yīng)用場(chǎng)景下的性能瓶頸,該問(wèn)題會(huì)導(dǎo)致交換機(jī)上 IP 的劇烈消耗,最終導(dǎo)致 Spark 任務(wù)卡住或提交失敗,具體觸發(fā)原因如下圖所示。目前阿里云團(tuán)隊(duì)已通過(guò)技術(shù)升級(jí)改造解決,并大幅提升了釋放速度和整體性能。
2.Watcher 失效
Spark 任務(wù)在啟動(dòng) Driver 時(shí),會(huì)創(chuàng)建對(duì) Executor 的事件監(jiān)聽(tīng)器,用于實(shí)時(shí)獲取所有 Executor 的運(yùn)行狀態(tài),對(duì)于一些長(zhǎng)時(shí)運(yùn)行的 Spark 任務(wù),這個(gè)監(jiān)聽(tīng)器往往會(huì)由于資源過(guò)期、網(wǎng)絡(luò)異常等情況而失效,因此在此情況下,需要對(duì) Watcher 進(jìn)行重置,否則任務(wù)可能會(huì)跑飛。該問(wèn)題屬于 Spark 的一個(gè) Bug,當(dāng)前我們內(nèi)部版本已修復(fù),并將 PR 提供到了 Spark 社區(qū)。
3.任務(wù)卡死
如上圖所示,Driver 通過(guò) List 和 Watch 兩種方式來(lái)獲取 Executor 的運(yùn)行狀況。Watch 采用被動(dòng)監(jiān)聽(tīng)機(jī)制,但是由于網(wǎng)絡(luò)等問(wèn)題可能會(huì)發(fā)生事件漏接收或漏處理,但這種概率比較低。List 采用主動(dòng)請(qǐng)求的方式,比如每隔 3 分鐘,Driver 可向 Apiserver 請(qǐng)求一次自己任務(wù)當(dāng)前全量 Executor 的信息。
由于 List 請(qǐng)求任務(wù)所有 Pod 信息,當(dāng)任務(wù)較多時(shí),頻繁 List 對(duì) K8s 的 Apiserver 和 ETCD 造成較大壓力,早期我們關(guān)閉了定時(shí) List,只使用 Watch。當(dāng) Spark 任務(wù)運(yùn)行異常,比如有很多 Executor OOM 了,有一定概率會(huì)導(dǎo)致 Driver Watch 的信息錯(cuò)誤,盡管 Task 還沒(méi)有運(yùn)行完,但是 Driver 卻不再申請(qǐng) Executor 去執(zhí)行任務(wù),發(fā)生任務(wù)卡死。對(duì)此我們的解決方案如下:
在開(kāi)啟 Watch 機(jī)制的同時(shí),也開(kāi)啟 List 機(jī)制,并將 List 時(shí)間間隔拉長(zhǎng),設(shè)置每 5 分鐘請(qǐng)求一次
修改 ExecutorPodsPollingSnapshotSource 相關(guān)代碼,允許 Apiserver 服務(wù)端緩存,從緩存中獲取全量 Pod 信息,降低 List 對(duì)集群的壓力
4. Celeborn 讀寫(xiě)超時(shí)、失敗
ApacheCeleborn 是阿里開(kāi)源的一款產(chǎn)品,前身為 RSS (Remote Shuffle Service)。在早期成熟度上還略有欠缺,在對(duì)網(wǎng)絡(luò)延遲、丟包異常處理等方面處理的不夠完善,導(dǎo)致線上出現(xiàn)一些有大量 Shuffle 數(shù)據(jù)的 Spark 任務(wù)運(yùn)行時(shí)間很長(zhǎng)、甚至任務(wù)失敗,以下三點(diǎn)是我們針對(duì)此問(wèn)題的解決辦法。
優(yōu)化 Celeborn,形成內(nèi)部版本,完善網(wǎng)絡(luò)包傳輸方面的代碼
調(diào)優(yōu) CelebornMaster 和 Worker 相關(guān)參數(shù),提升 Shuffle 數(shù)據(jù)的讀寫(xiě)性能
升級(jí) ECI 底層鏡像版本,修復(fù) ECILinux 內(nèi)核 Bug
5. 批量提交任務(wù)時(shí),Quota 鎖沖突
為了防止資源被無(wú)限使用,我們對(duì)每個(gè) K8s 集群都設(shè)置了 Quota 上限。在 K8s 中,Quota 也是一種資源,每一個(gè) Pod 的申請(qǐng)與釋放都會(huì)修改 Quota 的內(nèi)容 (Cpu/Memory 值),當(dāng)很多任務(wù)并發(fā)提交時(shí),可能會(huì)發(fā)生 Quota 鎖沖突,從而影響任務(wù) Driver 的創(chuàng)建,任務(wù)啟動(dòng)失敗。
應(yīng)對(duì)這種情況導(dǎo)致的任務(wù)啟動(dòng)失敗,我們修改 Spark Driver Pod 的創(chuàng)建邏輯,增加可配置的重試參數(shù),當(dāng)檢測(cè)到 Driver Pod 創(chuàng)建是由于 Quota 鎖沖突引起時(shí),進(jìn)行重試創(chuàng)建。Executor Pod 的創(chuàng)建也可能會(huì)由于 Quota 鎖沖突而失敗,這種情況可以不用處理,Executor 創(chuàng)建失敗 Driver 會(huì)自動(dòng)申請(qǐng)創(chuàng)建新的,相當(dāng)于是自動(dòng)重試了。
6.批量提交任務(wù)時(shí),UnknownHost 報(bào)錯(cuò)
當(dāng)瞬時(shí)批量提交大量任務(wù)到集群時(shí),多個(gè) Submit Pod 會(huì)同時(shí)啟動(dòng),并向 Terway 組件申請(qǐng) IP 同時(shí)綁定彈性網(wǎng)卡,存在一定概率出現(xiàn)以下情況,即 Pod 已經(jīng)啟動(dòng)了,彈性網(wǎng)卡也綁定成功但是實(shí)際并沒(méi)有完全就緒,此時(shí)該 Pod 的網(wǎng)絡(luò)通信功能實(shí)際還無(wú)法正常使用,任務(wù)訪問(wèn) Core DNS 時(shí),請(qǐng)求無(wú)法發(fā)出去,Spark 任務(wù)報(bào)錯(cuò) UnknownHost 并運(yùn)行失敗。該問(wèn)題我們通過(guò)下面這兩個(gè)措施進(jìn)行規(guī)避和解決:
為每臺(tái) ECS 節(jié)點(diǎn),都分配一個(gè) TerwayPod
開(kāi)啟 Terway 的緩存功能,提前分配好 IP 和彈性網(wǎng)卡,新 Pod 來(lái)的直接從緩存池中獲取,用完之后歸還到緩存池中
7. 可用區(qū)之間網(wǎng)絡(luò)丟包
為保障庫(kù)存的充足,各 K8s 集群都配置了多可用區(qū),但跨可用區(qū)的網(wǎng)絡(luò)通信要比同可用區(qū)之間通信的穩(wěn)定性略差,即可用區(qū)之間就存在一定概率的丟包,表現(xiàn)為任務(wù)運(yùn)行時(shí)長(zhǎng)不穩(wěn)定。
對(duì)于跨可用區(qū)存在網(wǎng)絡(luò)丟包的現(xiàn)象,可嘗試將 ECI 的調(diào)度策略設(shè)定為 VSwitchOrdered,這樣一個(gè)任務(wù)的所有 Executor 基本都在一個(gè)可用區(qū),避免了不同可以區(qū) Executor 之間的通信異常,導(dǎo)致的任務(wù)運(yùn)行時(shí)間不穩(wěn)定的問(wèn)題。
總結(jié)與展望
最后,非常感謝阿里云容器、ECI、EMR 等相關(guān)團(tuán)隊(duì)的同學(xué),在我們整個(gè)技術(shù)方案的落地與實(shí)際遷移過(guò)程中,給予了非常多的寶貴建議和專業(yè)的技術(shù)支持。
目前新的云原生架構(gòu)已在生產(chǎn)環(huán)境上穩(wěn)定運(yùn)行了近一年左右的時(shí)間,在未來(lái),我們將持續(xù)對(duì)整體架構(gòu)進(jìn)行優(yōu)化和提升,主要圍繞以下幾個(gè)方面:
持續(xù)優(yōu)化云原生的整體方案,進(jìn)一步提升系統(tǒng)承載與容災(zāi)能力
云原生架構(gòu)升級(jí),更多大數(shù)據(jù)組件容器化,讓整體架構(gòu)更加徹底的云原生化
更加細(xì)粒度的資源管理和精準(zhǔn)的成本控制
審核編輯:湯梓紅
-
容器
+關(guān)注
關(guān)注
0文章
492瀏覽量
22027 -
大數(shù)據(jù)
+關(guān)注
關(guān)注
64文章
8854瀏覽量
137210 -
云原生
+關(guān)注
關(guān)注
0文章
240瀏覽量
7932 -
kubernetes
+關(guān)注
關(guān)注
0文章
223瀏覽量
8683
原文標(biāo)題:米哈游大數(shù)據(jù)云原生實(shí)踐
文章出處:【微信號(hào):OSC開(kāi)源社區(qū),微信公眾號(hào):OSC開(kāi)源社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論