字節(jié)跳動 YARN 云原生化演進(jìn)實(shí)踐
一. 演進(jìn)背景
字節(jié)跳動(以下簡稱字節(jié))內(nèi)部離線業(yè)務(wù)具有龐大的規(guī)模,線上每天有數(shù)十萬節(jié)點(diǎn)運(yùn)行,每天的任務(wù)數(shù)達(dá)到百萬量級,每天使用的資源量達(dá)到千萬核量級。在如此龐大的計(jì)算規(guī)模下,為了能夠高效地處理任務(wù),提高資源流轉(zhuǎn)效率,調(diào)度系統(tǒng)發(fā)揮了非常重要的作用。
如上圖所示,我們可以清楚地看到,字節(jié)內(nèi)部調(diào)度架構(gòu)分為兩大塊 —— 離線調(diào)度系統(tǒng)和在線調(diào)度系統(tǒng),離線調(diào)度系統(tǒng)主要負(fù)責(zé)離線資源管理和離線任務(wù)調(diào)度,在線調(diào)度系統(tǒng)主要負(fù)責(zé)在線資源管理和在線任務(wù)調(diào)度。
- 離線調(diào)度系統(tǒng)基于 YARN 實(shí)現(xiàn),主要包括 Resource Manager(RM) 和 Node Manager(NM) 兩個(gè)組件,負(fù)責(zé)資源調(diào)度和容器運(yùn)行時(shí)管理。字節(jié)內(nèi)部在 YARN 的基礎(chǔ)上進(jìn)行了很多功能豐富和優(yōu)化工作,針對不同場景實(shí)現(xiàn)了不同的調(diào)度器,例如:Batch Scheduler,Gang Scheduler 等。
- 在線調(diào)度系統(tǒng)基于 Kubernetes 生態(tài),進(jìn)行了很多優(yōu)化,支持字節(jié)內(nèi)部多樣化的在線服務(wù)。
為了提高字節(jié)內(nèi)部整體的資源利用率,我們也進(jìn)行了混部技術(shù)的探索 。 主要思路是在在線的節(jié)點(diǎn)上同時(shí)部署 Kubelet 和 NM 服務(wù),當(dāng)在線節(jié)點(diǎn)比較空閑時(shí)可以及時(shí)將空閑資源出讓給離線業(yè)務(wù)使用,以此使得整個(gè)數(shù)據(jù)中心的資源利用率能夠得到比較大的提升。
但隨著公司內(nèi)業(yè)務(wù)規(guī)模的持續(xù)發(fā)展 ,這一套系統(tǒng)也暴露出了一些短板 :
- 首先,在離線屬于兩套系統(tǒng),一些重大活動場景需要通過運(yùn)維方式進(jìn)行在離線資源轉(zhuǎn)換,運(yùn)維負(fù)擔(dān)繁重,轉(zhuǎn)換周期長;
- 其次,現(xiàn)在的混部架構(gòu)只是在部分節(jié)點(diǎn)上同時(shí)部署了 NM 和 Kubelet 兩個(gè) Agent,資源利用率仍有很大的提高空間;
- 最后,在離線是兩套割裂的系統(tǒng),Quota 平臺、機(jī)器運(yùn)維等都不能復(fù)用,大數(shù)據(jù)作業(yè)無法享受到云原生的各種好處,例如:資源池化、更好的單機(jī)隔離特性等。
綜上所述, 字節(jié)內(nèi)部有三個(gè)核心訴求:
- 重大活動場景(春節(jié) / 雙 11 等),在離線資源需要能夠高效、靈活地相互轉(zhuǎn)換;
- 整個(gè)數(shù)據(jù)中心的利用率需要得到更全面、充分的提升,進(jìn)一步降本增效;
- 在離線資源共池,Quota 管控、調(diào)度、運(yùn)行、機(jī)器運(yùn)維統(tǒng)一。
為了實(shí)現(xiàn)上述訴求,我們進(jìn)行了一些思考和探索。其中一種解決方案是:能不能讓離線作業(yè)直接遷移到 Kubernetes? 即:大數(shù)據(jù)生態(tài)下的各個(gè)計(jì)算引擎(包括:Spark、Flink 等)進(jìn)行深度改造去適配 Kubernetes。在探索過程中發(fā)現(xiàn)這種方式有比較大的缺陷,主要有以下三點(diǎn):
- 對計(jì)算引擎侵入較深,計(jì)算引擎?zhèn)刃枰龃罅扛脑觳拍苤С衷仍?YARN 的各種特性;
- 生產(chǎn)環(huán)境的作業(yè)(百萬級)非常多,如何從 YARN 平滑遷移到 Kubernetes 也是個(gè)比較大的問題;
- 特別地,部分比較古老的計(jì)算引擎,比如 MapReduce,目前處于 Maintain 狀態(tài),已經(jīng)無法進(jìn)行大的改造來遷移。
基于以上思考,我們提出了一種全新的解決方案——Yodel。Yodel 的全稱是 YARN on G?del(G?del 是公司內(nèi)部增強(qiáng)版 Kubernetes,它對 API Server、G?del 調(diào)度器以及底層運(yùn)行時(shí)都進(jìn)行了增強(qiáng)),是字節(jié)跳動提出的 Hadoop YARN 云原生化演進(jìn)實(shí)踐方案。通過 Yodel 我們將公司內(nèi)的大數(shù)據(jù)業(yè)務(wù)(Spark、Flink 等)、訓(xùn)練業(yè)務(wù)(Primus)平滑遷移到了 Kubernetes ,實(shí)現(xiàn)了在離線資源池統(tǒng)一,提升了整體資源利用率。
二. 解決方案
下面將從 Yodel 整體架構(gòu)、Remote Godel Scheduler(RGS) 服務(wù)、Remote Kubelete Service(RKS) 服務(wù)、持久化服務(wù)、平滑遷移及重要優(yōu)化六個(gè)方面來詳細(xì)介紹我們的解決方案。
2.1 Yodel 整體架構(gòu)
Yodel 基于 YARN 實(shí)現(xiàn),新增 ZK / ETCD / KV State Store、Remote Godel Scheduler 、Remote Kubelet Service 服務(wù)。ZK / ETCD / KV State Store 主要用于持久化存儲、Remote Godel Scheduler 維護(hù)資源請求并與 API Server 交互,將調(diào)度能力統(tǒng)一到 Godel Scheduler;Remote Kubelet Service 實(shí)現(xiàn)了 YARN NM 所有接口,對用戶和作業(yè)透明的前提下,把 NM 的 Container 管理能力平滑下沉到 Kubelet。?
Yodel 整體架構(gòu)圖
從上面的架構(gòu)圖可以清楚看到 Yodel 的整體架構(gòu), 圖中藍(lán)色組件是進(jìn)行了適配改造的組件,藍(lán)色中標(biāo)紅的組件是新增組件,黃色組件是 G?del 生態(tài)下的組件,關(guān)于新增組件:
- ZK / ETCD / KV State Store:支持將集群元數(shù)據(jù)信息持久化到 ZK、 ETCD 和 KV 等持久化存儲,可以通過 API Server 方便地進(jìn)行相關(guān)數(shù)據(jù)查詢和更新;
- Remote Godel Scheduler:維護(hù)集群所有任務(wù)的資源請求,通過該服務(wù)將任務(wù)的資源請求轉(zhuǎn)化為 Pod 寫入 API Server,同時(shí)與 API Server 交互獲取已調(diào)度的 Pod,最終將調(diào)度能力下沉到底層的 Godel Scheduler;
- Remote Kubelet Service:實(shí)現(xiàn)了原來 YARN 中 NM 的所有接口,例如:啟動容器、停止容器、獲取容器狀態(tài)的接口。通過這個(gè)服務(wù)容器啟動從 NM 切換到 Kubelet,最終將容器運(yùn)行時(shí)的管理下沉到底層的 Kubelet。
下面介紹在 Yodel 架構(gòu)下一個(gè)離線任務(wù)的提交和運(yùn)行流程 :
- 用戶從開發(fā)機(jī)或任務(wù)托管平臺向集群提交一個(gè)任務(wù);
- 當(dāng)任務(wù)經(jīng)過校驗(yàn)后,Yodel RM 會新建一個(gè) App 對象并持久化至 API Server;
- Yodel RM 創(chuàng)建 AM Pod 并寫入 API Server,等待底層調(diào)度器調(diào)度;
- Yodel RM 收到已經(jīng)調(diào)度完成的 AM Pod 并進(jìn)行相關(guān)轉(zhuǎn)化操作;
- Yodel RM 將相關(guān)啟動信息豐富至 AM Pod 中并 Patch 至 API Server 由 Kubelet 拉起相關(guān)進(jìn)程;
- AM 啟動成功后,隨心跳主動向 Yodel RM 申請資源;
- Yodel RM 收到任務(wù)的資源請求后,通過 RGS 服務(wù)將資源請求轉(zhuǎn)化為 Pod 對象或 PodGroup 對象并寫入到 API Server;
- 底層調(diào)度器 Watch 到相關(guān)對象后,按照一定策略進(jìn)行調(diào)度,同時(shí) Yodel RM 也會及時(shí)地 Watch 到已經(jīng)調(diào)度的 Pod;
- Yodel RM 會將已經(jīng)調(diào)度的 Pod 轉(zhuǎn)化為 Container,隨心跳返回給對應(yīng)的 AM;
- AM 收到已經(jīng)調(diào)度的 Container 后,會再跟 Yodel RM 進(jìn)行交互,來啟動對應(yīng)的容器;
- Yodel RM 收到容器啟動請求后,通過 RKS 服務(wù)將容器啟動所需要的信息豐富到 Pod 對象里并 Patch 到 API Server。Kubelet Watch 到待啟動的 Pod 后,會進(jìn)行這個(gè) Pod 的啟動。
Yodel 架構(gòu) Pod 生命周期
上面講了 Yodel 架構(gòu)下任務(wù)的啟動流程,下面我們來看一下對于一個(gè) Pod 來說,它的生命周期是怎么樣的 ,核心流程如上圖所示:
- 首先,AM 啟動起來后會隨心跳申請資源;
- Yodel RM 收到資源請求后,會基于該資源請求的資源量、優(yōu)先級等創(chuàng)建一個(gè) Pod 對象寫入 API Server。創(chuàng)建完成后,該 Pod 對象處于 Pending-Unscheduled 狀態(tài),等待底層調(diào)度器進(jìn)行調(diào)度;
- 底層調(diào)度器 Watch 到新創(chuàng)建的 Pod 后,根據(jù)一定策略進(jìn)行調(diào)度,調(diào)度完成后會將調(diào)度結(jié)果寫入 API Server。寫入完成后,該 Pod 對象的狀態(tài)會變?yōu)?Pending-Scheduled 狀態(tài);
- Yodel RM Watch 到已經(jīng)調(diào)度完成的 Pod 后會轉(zhuǎn)化為 Container,該 Pod 對象的狀態(tài)會變?yōu)?Allocated 狀態(tài);
- 新分配的 Container 會隨心跳返回給 AM,Container 被對應(yīng) AM 拿走后,該 Pod 對象的狀態(tài)會變?yōu)?Acquired 狀態(tài);
- AM 獲取到容器后會與 Yodel RM 交互進(jìn)行啟動操作;
- Yodel RM 收到容器拉起請求后,會把容器啟動所需的信息填充到 Pod 對象中并 Patch 到 API Server ;
- Kubelet Watch 到需要啟動的 Pod 后,會啟動相關(guān)進(jìn)程,容器運(yùn)行時(shí)由 Kubelet 維護(hù)。
2.2 Remote Godel Scheduler
下面來介紹調(diào)度模塊比較重要的一個(gè)服務(wù) —— RGS 服務(wù)。由上圖可以看到,RGS 服務(wù)主要分為三大部分:
- 最上層是 Quota Manager 負(fù)責(zé)進(jìn)行 Quota 管理: Quota Manager 部分在 YARN 基礎(chǔ)上進(jìn)行了增強(qiáng)。在 YARN 中有隊(duì)列的概念,但隊(duì)列只支持一種資源類型。在 Yodel 中對此進(jìn)行了擴(kuò)展,一個(gè)隊(duì)列可以同時(shí)支持兩種類型的資源 —— Guaranteed Resource 和 Best-effort Resource。單隊(duì)列支持兩種資源類型后可以顯著簡化用戶的隊(duì)列管理成本,對用戶使用更友好。
- Guaranteed Resource :穩(wěn)定資源,使用 Guaranteed Resource 的容器一般情況下不會被搶占也不會被驅(qū)逐;
- Best-effort Resource:混部資源, 是在線節(jié)點(diǎn)出讓的暫時(shí)空閑不用的資源,資源會隨著節(jié)點(diǎn)負(fù)載情況動態(tài)波動,使用 Best-effort Resource 的容器可能會被搶占或驅(qū)逐;
- 中間層是 Allocate Service 負(fù)責(zé)進(jìn)行請求轉(zhuǎn)換和狀態(tài)維護(hù): 主要包括四個(gè)子服務(wù),Convert Reqeust To Pod Service 負(fù)責(zé)將任務(wù)的資源請求轉(zhuǎn)化為 Pod 對象并寫入 API Server;Convert Pod To Container Service 負(fù)責(zé)將已經(jīng)調(diào)度的 Pod 轉(zhuǎn)化為 Container 并返回給 AM;Update Pod Status Service 負(fù)責(zé)及時(shí)更新 Pod 狀態(tài)并持久化至 API Server ;Delete Pod Service 負(fù)責(zé)在容器或任務(wù)結(jié)束時(shí),及時(shí)刪除 API Server 中的相關(guān)對象。
- 最下層是 Remote Scheduler 負(fù)責(zé)進(jìn)行調(diào)度和關(guān)鍵信息持久化。
通過上述各個(gè)服務(wù)的協(xié)調(diào)配合, Yodel 能夠?qū)崿F(xiàn):
- 100% 兼容 Hadoop 協(xié)議,用戶無需要做任何改動,可以像原來使用 YARN 一樣來使用 Yodel;
- 支持 GT 和 BE 兩種資源類型,方便上層用戶對平臺的使用。
2.3 Remote Kubelet Service
接下來介紹 RKS 服務(wù)。RKS 部署在 Yodel RM 內(nèi)部,實(shí)現(xiàn)了 YARN NM 的所有接口,把 NM 的 Container 管理能力平滑下沉到 Kubelet。它主要由兩部分組成:
- Patch Pod Service : 主要負(fù)責(zé)收到 AM 拉起請求后,將容器啟動所需的信息豐富到 Pod 對象中,這些信息包括:容器的 ENV 、 HDFS 自研列表、啟動命令等;
- Pod Status Update Service : 該服務(wù)會及時(shí)從 API Server Watch Pod 的最新狀態(tài),并將狀態(tài)返回給對應(yīng) AM。
此外,為了補(bǔ)齊 NM 上的運(yùn)行體驗(yàn),底層以 daemonset 方式部署了一些其他服務(wù)。這些 daemonset 補(bǔ)齊了 NM 的能力,使得離線作業(yè)只需要升級 hadoop 依賴,不用做太多改動,就能讓容器運(yùn)行在 Kubelet 上。這些服務(wù)包括:
- LocalizationService : 用于下載 Pod 所需的 HDFS 資源;
- Log Serving : 用于方便用戶查看 Pod 日志;
- Shuffle Service : 主要有 Spark Shuffle Service 及 MR Shuffle Service,這些 Shuffle Service 是從 NM 的進(jìn)程解耦出來的,單獨(dú)部署用于提供計(jì)算框架的 Shuffle 服務(wù);
- Metrics Collector : 用于收集離線 Pod 運(yùn)行時(shí)的各維度監(jiān)控信息;
- Webshell : 方便用戶通過 Web 端進(jìn)入到容器的 Shell,方便排查問題。
下面看一個(gè)容器是怎么運(yùn)行在 Kubelet 上的:
- 改造了 NM Client SDK,使 AM 調(diào)用 startContainer 時(shí)能直連 RKS;
- RKS 收到啟動請求后,會把 containerLaunch 上下文等信息寫入到 Pod 并 Patch 到 API Server;
- Kubelet Watch 到離線 Pod 后,會通過本機(jī)的 LocalizationService 下載 Pod 對應(yīng)的 HDFS 資源;
- 下載完成后, Kubelet 通過 Containerd 把對應(yīng)的 HDFS 資源掛載到容器的 Pod 里,之后通過 Containerd 啟動 Pod;
- 啟動完成后,Kubelet 會把 Pod 的狀態(tài)更新回 API Server;
- RKS watch 到 Pod 狀態(tài)變化后,同步更新內(nèi)存中的 Container 狀態(tài),之后等待 AM 心跳時(shí)同步 Container 最新狀態(tài)。
2.4 持久化服務(wù)
YARN 架構(gòu)是通過 ZKRMStateStore 將元數(shù)據(jù)信息持久化到 ZooKeeper,而 Yodel 架構(gòu),我們自己實(shí)現(xiàn)了一個(gè) KVStateStore 存儲元數(shù)據(jù)到 API Server,存儲的元數(shù)據(jù)包括 MetaData,Queue,Application,Appattempt 和 Pod。現(xiàn)在線上的一個(gè) API Server 可以支持存儲 300 queue,2w 個(gè) application,10w 個(gè) app attempt,以及支持 30W 離線 Pod 同時(shí)運(yùn)行。
- MetaData:集群元數(shù)據(jù)信息、集群默認(rèn)配置等;
- Queue (~300 / cluster) :隊(duì)列 Quota、ACL 信息等;
- Application(~2W / cluster) :Name、User、State 等;
- AppAttempt(~10W / cluster) :Name、User、State 等;
- Pod (~30W / cluster) :State、Annotation、ENV、HDFS 自研列表、啟動命令等。
2.5 平滑遷移
字節(jié)內(nèi)每天運(yùn)行著百萬量級的任務(wù),如何平滑地把作業(yè)從 YARN 架構(gòu)遷移到 Yodel 架構(gòu)是一個(gè)很大的挑戰(zhàn),整體上我們是通過 ResLake 來完成的,首先介紹幾個(gè)關(guān)鍵組件:
- WorkFlow Hosting:作業(yè)托管平臺,負(fù)責(zé)進(jìn)行作業(yè)提交;
- ResLake:是 RM Proxy,可以根據(jù)一定策略把作業(yè)路由到不同集群;
- Quota Platform:用于同步隊(duì)列的 Quota 信息;
- AutoMigration:負(fù)責(zé)從 YARN 集群下線節(jié)點(diǎn),搬遷到 Yodel 集群上。
ResLake 在 YARN 集群和 Yodel 集群上有同名的隊(duì)列,如上圖中的 root.queueA 和 root.queueB,這兩個(gè)集群上的隊(duì)列有著相同的元數(shù)據(jù)信息。AutoMigration 服務(wù)會不斷地從 YARN 搬遷節(jié)點(diǎn)到 Yodel 集群,搬遷信息會同步給 Quota Platform,Quota Platform 會進(jìn)一步將隊(duì)列 Quota 信息同步給 Reslake。作業(yè)托管平臺提交作業(yè)到 ResLake 時(shí),ResLake 會根據(jù) YARN/Yodel 上隊(duì)列的 Quota 信息,決定作業(yè)是提交到 YARN 集群還是 Yodel 集群。隨著機(jī)器不斷地往 Yodel 搬遷,最終作業(yè)也平滑遷移到了 Yodel 集群上。
2.6 重要優(yōu)化
在 Yodel 架構(gòu)升級上線過程中,也遇到了很多問題,我們也做了非常多的優(yōu)化,主要包括性能優(yōu)化和運(yùn)行優(yōu)化。
2.6.1 性能優(yōu)化
- Recover 階段異步恢復(fù) Pod 狀態(tài)降低切主時(shí)間(秒級):起初為了確保切主后集群、隊(duì)列和任務(wù)的各維度統(tǒng)計(jì)信息準(zhǔn)確,采用同步方式恢復(fù) Pod,但上線時(shí)發(fā)現(xiàn)恢復(fù)過程非常耗時(shí)。為此通過優(yōu)化,在確保各維度信息統(tǒng)計(jì)準(zhǔn)確的前提下異步恢復(fù) Pod 狀態(tài),將切主時(shí)間縮短到秒級;
- 異步多線程操作 Pod 以提高調(diào)度吞吐(~2K / s):通過異步多線程方式將已經(jīng)調(diào)度 Pod 轉(zhuǎn)化為 Container 后,調(diào)度吞吐得到顯著提升,目前調(diào)度吞吐可以達(dá)到每秒 2000 個(gè) Pod;
- PodName 散列優(yōu)化助力底層存儲寫延遲降低為原來的 1 / 100(百分之一) : 因 API Server 底層采用基于 range 的 KV 存儲,若 PodName 有序會頻繁產(chǎn)生分區(qū)裂變,導(dǎo)致 API Server 的相關(guān)處理延遲顯著增加。通過將 PodName 進(jìn)行散列優(yōu)化,將 Pod 打散存儲在不同的分區(qū)中,底層存儲寫延遲下降 100 倍;
- 與 API Server 交互增強(qiáng),Java Fabric8 Kubernetes Client 優(yōu)化:
- 支持指數(shù)退讓重試,增強(qiáng) API Server 故障容錯(cuò);
- List 操作默認(rèn)添加 ResourceVersion 參數(shù),避免擊穿到底層存儲;
- 將 Informer Resync 設(shè)置為 0,避免頻繁內(nèi)存拷貝造成 OOM。
2.6.2 運(yùn)行優(yōu)化
- AM 容器運(yùn)行在單獨(dú)資源池,獨(dú)立優(yōu)先級不可搶占:對于使用 BE 資源的容器有被搶占或驅(qū)逐的風(fēng)險(xiǎn),而 AM 作為任務(wù)的 Master 一旦失敗就會導(dǎo)致整個(gè)任務(wù)失敗。為了避免此問題,將 AM 容器運(yùn)行在單獨(dú)的資源池,確保 AM 可以穩(wěn)定運(yùn)行避免任務(wù)失敗;
- 支持鏡像本地化約束,平均拉起速度提升約 1000 倍:一些離線任務(wù)的鏡像比較大,在容器啟動時(shí)拉取鏡像會花費(fèi)較多時(shí)間,進(jìn)而導(dǎo)致啟動時(shí)間變長。為了解決該問題,支持了鏡像本地化約束,讓容器可以盡量調(diào)度到有鏡像的節(jié)點(diǎn),該功能上線后容器平均拉起速度提升 1000 倍;
- 支持雙棧節(jié)點(diǎn)和 v6 only 節(jié)點(diǎn)在單集群混跑:通過將雙棧節(jié)點(diǎn)和 v6 only 節(jié)點(diǎn)混跑在同一個(gè)集群中顯著降低了運(yùn)維成本,同時(shí)也有利于資源利用率提升;
- Shuffle 數(shù)據(jù)寫遠(yuǎn)程,避免打爆本地磁盤:shuffle 數(shù)據(jù)通常較大很容易將本地磁盤打滿,將 shuffle 數(shù)據(jù)寫遠(yuǎn)程后,可以避免因本地磁盤打滿而導(dǎo)致任務(wù)運(yùn)行異常;
- 大量引入 SSD 和 Nvme 磁盤,加速作業(yè)運(yùn)行。
三. 上線收益
Yodel 架構(gòu)已經(jīng)在字節(jié)內(nèi)部上線,上線后帶來了如下收益:
- 高效資源切換:實(shí)現(xiàn)了 2022 元旦/春節(jié) 約 50 萬核離線資源分鐘級出讓,顯著提高了在離線資源轉(zhuǎn)化效率,為重大活動場景下的資源切換提供了堅(jiān)實(shí)的技術(shù)支撐;
- 利用率提升:NM 和周邊單機(jī)組件下線,降低 Overhead,帶來單機(jī) 2% 利用率提升;
- 在離線統(tǒng)一:在離線資源全量共池,Quota 管控、調(diào)度、運(yùn)行、機(jī)器運(yùn)維統(tǒng)一。
四. 未來規(guī)劃
- RGS & RKS 部署云原生化
- 接入服務(wù)發(fā)現(xiàn)
- 支持容器化部署
- 可彈性擴(kuò)展
- 開源 Yodel 回饋社區(qū)