Spark on K8s 在茄子科技的實踐

大資料技術前線發表於2023-04-11

來源:DataFunSummit

導讀 Spark 大家都很熟悉,但如何在雲原生場景下應用好它是一個難點。本次將分享茄子科技在 Spark on K8s 方面的實踐。

主要包括以下三大部分:

1. Spark 與雲原生

2. Spark on K8s 原理介紹

3. Spark on K8s 在茄子科技的應用

分享嘉賓|梁有擇 茄子科技 大資料技術專家

編輯整理|唐洪超 敏捷雲

出品社群|DataFun



01
Spark 與雲原生
Spark 作為開源社群優秀的大資料計算引擎,極大地提高了傳統 Hadoop 生態下大資料計算的效率。隨著雲端計算時代的發展,給大資料開發者帶來了便利的同時也帶來了新的挑戰。

1. 傳統大資料計算叢集的缺陷

首先來看一下傳統 Hadoop 生態下的大資料叢集有哪些缺陷。

Spark on K8s 在茄子科技的實踐

(1)第一個缺陷是成本高,我們需要維護多個叢集,還需要非常專業的運維人員才能去維護非常多的元件,如 Hadoop、Hive、ZooKeeper 等等,叢集運維成本非常高。另外,伺服器本身裸機價格和物理硬體的維護成本也是非常高的。還有 Hadoop 生態下的 HDFS 為了高可用,通常得維護多個副本,這就導致了大量的資料冗餘,相對應的成本也會非常高。
(2)第二個缺陷就是靈活性比較低,一是沒有辦法做到非常高效的節點伸縮,需要提前預估業務需要多少資源,然後再去搭建環境。二是版本升級會比較困難,每個節點都有一套環境,且有可能同時執行不同版本的環境,在統一做升級的時候是很困難的。
(3)第三個缺陷就是存算耦合,Hadoop 叢集既是儲存節點,又是計算節點。當資料量非常大,而計算量並沒有那麼大的情況下,儲存資源擴容時,計算資源如 CPU 記憶體這些也必須要跟著擴容。會導致存算不匹配的情況,帶來大量的資源浪費。
2. 公有云帶來的優勢

Spark on K8s 在茄子科技的實踐

公有云具有兩大方面的優勢:
(1)首先,公有云上擁有非常廉價的物件儲存,且不需要預留空間,也不需要專業運維人員維護,直接用就可以了。
(2)另一個優勢就是彈性計算,在雲上不需要自己去維護物理伺服器,它有非常好的彈性虛擬機器,根據使用時長來收費,不需要去提前準備物理伺服器,也不需要專業的運維人員來運維。同時公有云上還有一種更加廉價的機型,叫做 spot 例項,或者叫可搶佔式的虛擬機器。我們可以利用這種非常廉價的彈性計算節點來執行我們的計算任務。
3. 如何充分利用公有云帶來的優勢?

Spark on K8s 在茄子科技的實踐

如何充分利用公有云帶來的優勢呢?
首先,大多數的雲服務商都提供了雲上的 Hadoop 叢集產品,如 AWS EMR。EMR 相當於一套搭建好的 Hadoop 叢集。在 EMR 裡面,你可以使用雲上廉價的物件儲存來替代 HDFS 儲存資料,同時,EMR 類產品往往會提供一定的伸縮能力,不同的雲商的伸縮能力可能有所不同,但或多或少會有一些彈性伸縮的能力。
4. EMR 類產品的缺陷

Spark on K8s 在茄子科技的實踐

EMR 產品是否是最佳的雲上使用方式?既然雲上有了這樣的產品,我們是不是用它就好了?答案是否定的,因為它也存在一些缺陷:首先,雖然 EMR 這類產品已經簡化了很多的部署操作,但是 EMR 部署好了之後,上面那些元件仍然需要一些相對專業的人員來維護,可能要改一些配置,或者給使用者提供一些環境等等,依舊是一個相對複雜的環境。另外,Yarn 作為一個資源排程器,它本身要消耗一定的資源,也受限於框架,Yarn 是 Java 寫的,它需要執行在 JVM 之上,JVM 是一個需要記憶體非常多的環境,所以叢集通常是需要預留 25% 左右的記憶體資源,當然也可以相對調低一些,但是也不能做到很低,否則它的排程器可能就會有問題。這樣會導致記憶體效率比較低。
5. 傳統 Hadoop 生態,三大元件的前世今生

Spark on K8s 在茄子科技的實踐

針對這些缺陷,我們有沒有什麼好的方式來解決?回到 Hadoop,傳統的 Hadoop 生態主要的三元件 HDFS、MapReduce、Yarn。其中 HDFS,我們有云上更廉價的物件儲存來替代它,且物件儲存在各方面顯然是優於 HDFS 的。計算引擎方面,MapReduce  可以用 Spark 來替換,Spark 的效率和效能優於 MapReduce。
6. Spark on K8s 的優勢

Spark on K8s 在茄子科技的實踐

我們選擇用 K8s 來代替 Yarn 作為 Spark 作業的資源排程器。其優勢之一是,它的部署環境非常簡單,我們現在使用的是雲上託管的 K8s 服務,我們不需要去維護它的控制節點,當然每個雲服務的 EMR 都有自己的產品,如 AWS 的 EKS,華為雲的 CCE,谷歌的 GKE。這種類似的產品,我們不需要維護它的控制節點,也不需要在上面常駐任何 Spark 的服務就可以執行 Spark 作業。另外它也沒有環境依賴,因為執行時所有的大資料作業都是容器化的,不需要節點上有一些提前預置好的環境,也就決定了執行的時候多版本可以共存。
第二點優勢是其彈性優勢。無論我們使用涉及開源的 K8s 的 cluster-auto scaler 外掛,還是某些雲商自己實現的基於 K8s 的更高效的擴縮容機制,都可以保證叢集能夠極快地自動擴縮容。這個時候,因為可以快速的把不用的節點關閉,也就相應地節約了計算的成本。
第三點優勢,它沒有按節點來收取服務費用,只需要收取一個控制面的服務費用,這個服務費用是非常低的,在公司級的資源使用下,這部分的費用幾乎是可以忽略不計的。
第四點優勢,它有更高的資源使用率。它是使用 go 語言編寫的 kubelet 服務,它所需要預留的資源會遠遠低於 JVM 上所需要的,其節點利用率可以達到 90% 甚至更高。
Spark on K8s 就是我們在雲原生場景下做出的一個選擇。
02
Spark on K8s 原理介紹
接下來介紹 Spark on K8s 的原理。
1. Spark 的叢集部署模式

Spark on K8s 在茄子科技的實踐

Spark 官方提供了四種叢集部署的模式:Standalone、YARN、Mesos、 Kubernetes。Standalone 需要常駐 Master 服務和 Worker 服務。它作為資源排程,只能去排程 Spark 做作業。同時它需要每個節點預先準備好 Spark 執行時環境,所以不太適合生產環境使用。YARN 在傳統的大資料體系下是一個比較好的排程器。它不需要常駐 Spark 相關的服務,YARN 的容器內其實也是可以進行任何作業的,但是需要每個節點去事先準備好執行時環境,YARN 其實是更貼近於我們的傳統 Hadoop 生態,它也有一些排程上的最佳化,比如計算時會盡可能地去找資料所在的 HDFS 節點,不過在我們雲原生的場景下就不太適用了。Mesos 在 Spark 3.2 版本後已經被標記為棄用了,所以我們就不過多談它。Kubernetes 也是無需常駐 Spark 相關服務,支援容器化執行任何作業,也不需要依賴節點執行時環境,它是更貼近於雲原生生態的。
2. Spark on k8s 如何執行

Spark on K8s 在茄子科技的實踐

首先 Spark 有一個客戶端,客戶端會構建好 driver pod 物件,向 K8s 的 apiserver 傳送請求,去建立 driver pod,Spark 的 driver 程式執行在 driver pod 當中。Spark driver 啟動之後,會在 driver 內構建 executor pod 的物件,建立 executor pod,並持續 watch and list 去監聽每一個 executor pod 的狀態。當任務執行結束的時候,executor pod 會被清理,driver pod 會繼續以 completed 的狀態存在。這就是 Spark on K8s 的執行過程。
3. Spark 的 dynamicAllocation 功能

Spark on K8s 在茄子科技的實踐

Spark 有一個 dynamic allocation 的功能,可以基於 task 數去動態調整 Spark 作業所需要的 executor 個數。
因為 Spark 的 shuffle 是依賴本地儲存的,在 on YARN 模式下,它必須要基於另外一個External Shuffle Service 服務才能啟用動態擴縮的功能,但是 External Shuffle Service 無法在 K8s 環境下部署。這種場景下,Spark on K8s 引入了 shuffleTracking 的功能,它能夠追蹤每一份 shuffle 資料是否被之後的 stage 所引用。當一個 executor 上的 shuffle 資料沒有後續的 stage 引用時, 這個 executor 可以被縮容。如上圖,第一個 stage 可能產生了一些 shuffle 資料,第二個 stage 的前兩個 executor 需要讀取 stage1 的資料。第三個 executor 不需要讀取 stage1 的資料,stage1 可能落了一些 shuffle 資料被 stage2 所依賴,到 stage3 的時候,stage1 的 shuffle 資料可能就不需要了,它只需要 stage2 產生的 shuffle 資料。這個時候 excutor-3 上就沒有後續 stage4 所需要的資料,所以這個時候就可以把  exec-3 給縮掉,等到了 stage4 的時候,它可能又需要更多的節點來計算了,這時候就可以再去擴容出新的 exec-4。
4. Spark 如何獲取雲服務的訪問許可權

Spark on K8s 在茄子科技的實踐

Spark 如何獲取雲服務的訪問許可權?我們之前說過 Spark 要讀取雲上廉價的物件儲存,要讀取物件儲存必然涉及到訪問許可權。因為在不同的雲廠商,不同的租戶,肯定需要不同的訪問許可權。Spark 讀取資料主要是 executor,因為 executor 是真正負責去執行 task 的角色,所以它需要一個證明身份的東西,去雲廠商那裡去拿我儲存的資料。Spark官方提供了 AWS S3 的訪問方式。透過一個 Hadoop 的引數來指定訪問物件儲存的 aksk,即 access key 和 secret key,類似於我們通常所說的賬號密碼。如果 aksk 有訪問對應物件儲存的許可權,你就可以訪問該資料了。
透過 aksk 訪問物件儲存資源其實是一種極不安全的方式。因為 Spark 引數都是以明文的方式去設定的,就像圖中一樣,aksk 一旦洩露,只要是在有網路的環境下,別人都可以去利用你的 aksk 做許可權內的任何事情。
因此大多數運營商會提供一種將 K8s 的 serviceaccount 和雲身份管理的系統結合的方式,把 K8s 的 RBAC 認證和雲服務商的認證相結合實現 pod 級別的許可權隔離。在使用的時候,我們只需要去透過引數給 Spark 指定 serviceaccount,就可以拿到域名上的許可權。如下圖是一個 AWS 的 IAM Role for Service Account 的原理圖,它的鏈路非常複雜,有興趣的同學可以瞭解一下。其實我們作為使用者來說,直接按文件配置好之後就可以用了。

Spark on K8s 在茄子科技的實踐

有一點需要注意,在 Spark 2.x 的版本是沒辦法使用的,因為 Spark 2.x 版本的 executor pod 裡面是沒有賦予 service account 的,同時這個引數也是沒有暴露出來的。因為我們訪問資料的往往是 executor,但是 executor 又不需要去請求建立任何 K8s 的資源,所以 Spark2.x 版本是沒有給 executor 賦予 serviceaccount resource 的。如果在 Spark 2.x 的版本想要使用 IAM Role for Service Account ,只能去改 Spark 的程式碼。 
03

Spark on K8s 在茄子科技的應用

1. 社群發展與茄子科技應用的時間線

Spark on K8s 在茄子科技的實踐

從上圖可以看到社群發展和茄子科技應用的時間線,Spark 2.4 是 2018 年 11 月上線的,公司是在 2019 年的 6 月份正式將 Spark on K8s 應用在了生產環境。我們基於社群的 Spark 2.4.3 的版本,做了比較多的改造後應用於生產環境上線。
社群在 2020 年 6 月份上線了 Spark 3.0 版本。相應的,茄子科技在 2020 年 10 月基於 Spark 3.0.1 版本上線。之所以上線很快,是因為我們在中間這段過程中一直在關注 Spark 3.0,將它的 master 分支中的一些 feature 合併到了我們的 Spark 2.4.3 版本中,所以在 Spark 3.0 正式推出上線之後,我們也就比較快速的完成了我們內部 Spark 的版本迭代。並且在上線後,很長一段時間裡,我們也是將社群中可能會用到的一些 feature 不斷合併到我們內部的 Spark 中,以此來快速迭代更新我們內部的 Spark 版本。
Spark 3.2 推出之後,我們為什麼要上線 3.2 版本?其實很大一部分原因是 Spark 3.2 支援 reuse pvc,在後文中還將詳細介紹這一功能。
2. 我們做了什麼? 
下面介紹一下茄子科技為了 Spark on K8s 在生產環境的應用所做的工作。
(1)檢視 Spark web ui

Spark on K8s 在茄子科技的實踐

首先第一點就是解決檢視 Spark web UI 比較麻煩的問題,因為 K8s 叢集內的 pod 和 VPC 內的子網可能不在同一個網路平面,Spark 的每一個 pod 的網路對叢集外可能是不通的,我們透過 K8s 自己的服務發現機制,即 service 和 ingress 去解決。又因為每一個 Spark 任務都是一個獨立的 pod,我們沒有辦法提前去建立好 ingress 給到 Spark,只能透過一些方式將 Spark 任務啟動的時候啟動的 4040 埠的 Spark UI 服務暴露出去。我們選擇的也是社群的一個元件,叫做 CONTOUR,它是基於 envoy 的一個元件,可以定製化一些路由規則,可以配置一個泛域名,透過監聽 Spark Driver 的 service 去動態地建立出它的資源 ingressroute,然後把 Spark 的 web UI 暴露到叢集之外。當然中間我們需要實現一個 Spark UI controller,去監聽 Spark driver 的 service,從而呼叫 CONTOUR 的介面去動態建立 ingressroute 資源。有了 ingressroute 資源,CONTOUR 就可以解析 ingressroute,根據 ingressroute 的規則把對應的流量透過 envoy 轉發到對應的 pod。
同時我們也在 K8s 上部署了 Spark history server,它可以檢視歷史的 event log。
(2)排程最佳化
另外,如果用原生的 Spark on K8s,它的 pod 排程是比較隨機的,可能在啟動的時候會比較分散。

Spark on K8s 在茄子科技的實踐

上圖可以看到我們在雲上分為了兩個可用區 AZ1 和 AZ2。每個可用區可能又有多個節點組,如 node group 1 和 node group 2。如果直接去提交兩個 Spark 任務(紅色和藍色代表兩個不同的任務),它的 pod 的分佈可能就會比較分散。node1 上既有 driver,也有 executor,每一個節點組上也都有可能會被分配不同的 pod。這樣會導致一些問題。首先,每一個 Spark 任務,它的執行的過程中,每個 executor 之間需要不斷地做資料交換,要不斷地做 shuffle read 和 shuffle write,這就導致了 AZ1 和 AZ2 之間是有資料傳輸的,有的雲廠商會收取比較高的跨 AZ 資料傳輸的費用,這是我們不願意見到的,是一個不必要的成本。如果每一個任務都跑在同一個 AZ 下,就不需要跨 AZ 的流量費用了。
另外,紅色任務和藍色任務比較分散,而它的執行時間又不一樣長,比如紅色的任務 10 分鐘就結束了,而藍色的任務要執行一個小時,當這些紅色的 pod 都退出之後,會發現這個節點的資源可能已經空閒出來一半了,但是由於藍色的任務依舊存在,此節點也是不能被縮容的,這就會導致有一半的資源沒有被釋放掉而空閒了 50 分鐘,這就造成了極大的資源浪費。
再者,如果全用 Spot 例項,當 driver 節點被回收,整個任務就掛掉了,也就沒法利用  Spark 自身的容錯機制了。

Spark on K8s 在茄子科技的實踐

這些都是排程的問題,我們所做的最佳化如上圖所示,利用 K8s 的 nodeSelector 和它的親和/反親和策略,把 driver 和 executor 所屬的 node group 分開,driver 執行在單獨的 node group,為了防止被回收,該 node group 中的節點使用的都是按需計費的例項,並且 driver 消耗的資源本身是比較少的。executor 所屬的 node group 中的節點則可以使用 sport 例項,同時每一個任務做了節點級別的親和/反親和策略,即我的每一個 executor 之間做軟親和的策略和不同任務的 executor 之間做強制的反親和策略,這時它的 executor pod 絕對不會分配到同一個節點上去。這樣,相同節點上只會有相同的任務,當紅色任務結束時,就可以直接把 node 1、node 3、node 4 全都釋放掉,只留 node 2、node 5、node 6 讓它繼續去執行。這就是我們在排程層面做的一個最佳化。
(3)Reuse PVC 功能

Spark on K8s 在茄子科技的實踐

另外一個重要的功能是 Reuse PVC,我們升級 Spark 3.2 版本的一個最大的動力就是因為其推出了這個透過複用 K8s 的 PVC 來恢復 shuffle 資料的功能。這個功能可以做到在 executor 被退出的時候,executor 所寫到本地的 shuffle 資料仍然能被複用。這可以避免任務的重算帶來的資源浪費。
Spark 本身具有良好的容錯性,它的 executor 節點就可以執行在雲上的可搶佔式節點上,這種節點型別的特點就是價格非常低,同時又隨時有可能被回收。茄子科技內部現在是使用了大量的可搶佔式節點來執行 Spark 任務。每天都會面臨大量的節點回收造成的 task 重算,既浪費資源又影響效率。

Spark on K8s 在茄子科技的實踐

如上圖,有三個 executor 在 stage0 的時候做運算,它執行了 6 個 stage,但是這個時候 exec-3 被回收掉了,到了 stage1 的時候 task0.5 和 task0.6 都要重算一遍,因為上面的 shuffle 資料已經丟了。社群為了解決這個問題就推出了 Reuse PVC 功能。在 executor 被回收掉之後啟動一個新的 executor,掛上之前儲存 shuffle 資料的 PVC,就可以實現節點的複用。這個功能看起來很美好,但是我們在實際的測試中發現它的侷限性非常大,所以效果是比較差的。
(4)社群版 Reuse PVC 功能的侷限性

Spark on K8s 在茄子科技的實踐

首先第一點就是恢復的準確性比較低。在開啟 dynamicAllocation 的時候,是沒有辦法識別 pod 是怎樣的退出狀態的,有可能是節點回收掉了,也有可能就是 dynamic 把它正常退出掉了。這個時候它會像上圖一樣,exec-3 是一個正常退出,exec-4 是一個節點回收,當 exec-3 和 exec-4 它們兩個掛掉之後,再啟動一個 exec-5,這個 exec-5 應該掛載哪一個 PVC?在這種情況下開源版本是隨機選一個,這個時候它的準確性就非常低了。很明顯 PVC-3 的資料已經沒有用了。
另一點就是上報 shuffle 元資訊的時機會比較滯後。我們知道 Spark 的 task 執行時,寫出一份 shuffle 資料,會上報給 driver 的 MapOutputTracker 這麼一個物件裡面去,會把它的 shuffle 的元資訊如 shuffle 資料所在的 executor、executor IP、block ID 等資訊上報給 driver,但是在開源版本里面的 shuffle 實現裡,只有在做 shuffle writer 初始化的時候才會去觸發上報的動作。所以如果在 shuffle read 階段是沒有辦法複用 shuffle 資料的。
第三點是這個功能是在 KuberntesLocalDiskShuffleExecutorComponents 實現類中,這個實現類當中尋找的路徑跟實際的 Cluster 模式執行環境下的路徑是不太相符的。
另外 Spark 本身排程的機制就是 Spark 在處理 executor lost 異常的時候,會立即重新計算丟失的 executor 上的 task,此時去 recover 舊的資料可能新的資料已經正在被計算了,所以此時複用它已經沒有意義了。
基於上述問題,我們對它做了比較大的改造。
(5)Reuse PVC 功能的改造

Spark on K8s 在茄子科技的實踐

首先第一點,我們要在記憶體中維護一個 PVC 的狀態集。這個狀態集隨著 ExecutorPodsAllocator 這個類中接收 snapshot 更新的事件來觸發 PVC 狀態集的更新,以此保證 pod 的狀態和 PVC 的狀態一致。當建立新的 executor 的時候,優先使用 failed 狀態的 PVC,再去使用 exited 的 PVC。如果這兩者都沒有,再去建立新的PVC。這樣的機制確保了既不會讓 PVC 的數量過多,又可以準確地去複用 shuffle 資料。

Spark on K8s 在茄子科技的實踐

第二點就是在 executor 初始化的階段上報 shuffle 資訊,而不是等到 shuffle writer 初始化的時候才去上報。如上圖,原版的呼叫棧是在 SortShuffleManager.getWriter 方法裡去調 executorComponents 類的 initializeExecutor 的方法去 recoverDiskStore。我們改造之後,在  BlockManager 類裡增加 initialize 方法直接去調 ShuffleManager.initShuffleManager 方法去呼叫上面原版的呼叫鏈。
第三點我們修復了 Spark 在 Cluster 模式下執行時尋找 shuffle 資料路徑錯誤的問題,在 Cluster 模式下 Spark 的寫出路徑並不是原版所指向的以
第四點就是 executorLost 的問題,在 TaskSetManager 裡面會有一個叫 executorLost 的事件,在處理這個事件的時候,Spark 本身會去直接重算 executor 上面丟失的 task,我們做的處理是,在開啟了 Reuse PVC 的功能下,Spark 不去重算丟失的 executor 上的 task,只重算當前失敗的 task,不過這個改變會相對降低 Spark 的容錯性,但是在實際生產環境下,基於經驗來看,這樣的改變導致的容錯性降低是在可接受範圍內的,當進入下一個 stage 的時候,如果資料還沒有被恢復,還會做 task 的重算。
(6)Reuse PVC 功能改造前後效能對比

Spark on K8s 在茄子科技的實踐

從上圖可以看出,在功能改造之後,對比 Saprk 3.0.1 版本,3.2.2 的效能提升效果是非常明顯的。
(7)其他工作

Spark on K8s 在茄子科技的實踐

我們還做了一些其它的工作。比如增加了自動刪除結束的 driver pod 的功能,因為 driver pod 存在的時間長了,etcd 的壓力會非常大。另外做了基於 ELK 的日誌採集功能,增加了可以支援修改 pod 內部 DNS 配置的功能。我們還可以透過客戶端退出的方式把服務端也退出。另外就是 Spark-sql 謂詞下推和 Spark-sql 多維分析場景的最佳化,以及任務級別的雲成本計算等工作。

3. Spark on K8s 在茄子科技的使用規模

目前我們採用的是多雲多區域的架構,在每個雲上有多個大規模叢集,單個叢集的峰值在幾千臺,總節點規模近萬臺,每天有數萬個任務執行。
4. 未來工作

Spark on K8s 在茄子科技的實踐

我們未來還有很多工作要做。
首先第一個工作是從根本上解決 shuffle reuse 的問題,包括效能的提升。Remote Shuffle Service 是比較火的,目前一些頭部公司也做了一些開源方案,測試的效能效果都比較不錯,但是最大的問題就是在極大規模叢集下的效能和穩定性還有待進一步驗證。如何在效能和成本之間做一個平衡,也是其中的一個挑戰。
第二項是 Spark job 互動式的提交,像 Spark 本身自帶的 Spark-shell,Spark-sql 指令碼需要依賴客戶端的環境 thrift server,thrift server 只能執行 sql,並且它本身的缺陷也比較多。Spark 目前在互動式場景下的功能還是比較單薄的,這也是我們所要做的事。
第三個就是日誌檢視系統,官方推薦的 ELK 這種方式依賴會比較重,部署繁瑣。雲商的一些日誌收集系統,它的入網流量費會比較貴,Spark 日誌其實大多數情況下是沒用的,只有在排查一些問題或者是做一些調優的時候才會去用到。所以多數日誌是可以扔掉的,但是我們又無法判斷哪些是要的,哪些是不要的,所以用比較貴的儲存是比較浪費的。而且此類日誌收集方式都是以 pod 粒度來檢視的,並不能在一個大資料開發或 Spark 使用者的角度上去看。
以上三點是我們未來要做的一些工作。



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

相關文章