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