作者:曹野 車漾
背景介紹和麵臨的挑戰
隨著 Kubernetes 在 AI/大資料領域的普及和業務場景變得越來越複雜,資料科學家在研發效率和執行效率上遇到了新的挑戰。當下的應用,往往需要使用端到端的流水線來實現,以下圖所示的一個風控作業資料操作流為例:首先,需要從資料庫中匯出訂單相關資料;隨後,圖計算引擎會處理這些原始資料,構建“使用者-商品”關係圖,並透過圖演算法,初篩出其中隱藏的潛在作弊團伙;接下來,機器學習演算法會對這些潛在團伙進行作弊歸因,篩選出更準確的結果;最後這些結果會經過人工篩查,並最終做出業務處理。
在這樣的場景下,我們常常會遇到如下問題:
1. 開發環境和生產環境的差異導致資料工作流的開發和除錯變得複雜且低效: 資料科學家在自己的計算機上開發時通常使用 Python,但是又需要在生產環境中將程式碼轉化為他們並不熟悉的 YAML 檔案從而利用 Argo、Tekton 等基於 Kubernetes 的工作流引擎,這大大降低了開發和部署效率。
2. 需要引入分散式儲存實現中間臨時資料交換,帶來額外的開發、費用、運維成本: 端到端任務的子任務之間的資料交換通常依賴分散式檔案系統或物件儲存系統(如 HDFS、S3 和 OSS),這使得整個工作流需要進行大量的資料格式轉換和適配工作,導致冗餘的 I/O 操作,並由於中間資料的短期性,使用分散式儲存系統會導致額外的成本。
3. 在大規模 Kubernetes 叢集環境中資料處理的效率問題: 在大規模的 Kubernetes 叢集中,使用現有的分散式檔案系統處理資料時,由於排程系統對資料的讀寫本地性缺乏足夠的理解,並未有效地考慮到資料的位置問題,沒有充分利用資料的區域性性,導致在處理節點間的資料交換時,無法避免大量的資料重複拉取操作。這種操作既增加了 I/O 消耗,也降低了整體的執行效率。
為了解決上述問題,我們提出了結合 Vineyard 資料共享能力和 Fluid 資料編排能力的解決方案:
- Fluid 的 Python SDK 能夠方便地對資料流進行編排,為熟悉 Python 的資料科學家提供了一種簡單的方式來構建和提交以資料集操作為中心的工作流。此外,Fluid 支援在開發環境和雲上生產環境透過一套程式碼進行資料流管理。
- Vineyard 使端到端工作流中任務之間的資料共享更加高效, 透過記憶體對映的方式實現零複製資料共享,從而避免了額外的 I/O 開銷。
- 透過利用 Fluid 的資料親和性排程能力,在 Pod 排程策略考慮資料寫入節點的資訊,從而減小資料遷移引入的網路開銷,提升端到端效能。
解決方案
什麼是 Fluid
Fluid 是一個開源的 Kubernetes 原生的分散式資料集編排和加速引擎,主要服務於雲原生場景下的資料密集型應用。透過 Kubernetes 服務提供的資料層抽象,可以讓資料像流體一樣在諸如 HDFS、OSS、Ceph 等儲存源和 Kubernetes 上層雲原生應用計算之間靈活高效地移動、複製、驅逐、轉換和管理。
而具體資料操作對使用者透明,使用者不必再擔心訪問遠端資料的效率、管理資料來源的便捷性,以及如何幫助 Kuberntes 做出運維排程決策等問題。使用者只需以最自然的 Kubernetes 原生資料卷方式直接訪問抽象出來的資料,剩餘任務和底層細節全部交給 Fluid 處理。
Fluid 專案架構如下圖所示,當前主要關注資料集編排和應用編排這兩個重要場景。資料集編排可以將指定資料集的資料快取到指定特性的 Kubernetes 節點,而應用編排將指定該應用排程到可以或已經儲存了指定資料集的節點上。這兩者還可以組合形成協同編排場景,即協同考慮資料集和應用需求進行節點資源排程。
接著介紹 Fluid 中資料集 Dataset 的概念,資料集是邏輯上相關的一組資料的集合,會被運算引擎使用,比如大資料的 Spark、AI 場景的 TensorFlow。Dataset 的管理實際上也有多個維度,比如安全性、版本管理和資料加速。
Fluid 希望從資料加速出發,對資料集的管理提供支援。在 Dataset 上面, 透過定義 Runtime 這樣一個執行引擎來實現資料集安全性、版本管理和資料加速等能力。Runtime 定義了一系列生命週期的介面,可以透過實現這些介面來支援資料集的管理和加速。Fluid 的目標是為 AI 與大資料雲原生應用提供一層高效便捷的資料抽象,將資料從儲存抽象出來從而達到如下功能:
- 透過資料親和性排程和分散式快取引擎加速,實現資料和計算之間的融合,從而加速計算對資料的訪問。
- 將資料獨立於儲存進行管理,並且透過 Kubernetes 的名稱空間進行資源隔離,實現資料的安全隔離。
- 將來自不同儲存的資料聯合起來進行運算,從而有機會打破不同儲存的差異性帶來的資料孤島效應。
什麼是Vineyard Runtime
首先我們來介紹 Vineyard,一個專為雲原生環境大資料分析工作流中不同任務之間高效共享中間結果而設計的資料管理引擎。透過共享記憶體實現不同計算引擎之間中間資料共享的零複製,避免資料存入外部儲存(本地磁碟、S3 及 OSS 等)的開銷,從而提高了資料處理的效率和速度。
Vineyard Runtime 是 Fluid 中 Vineyard 相關元件的抽象,整體架構如下圖所示。
從圖中可以看到,Vineyard Runtime 由兩個核心元件 Master 和 Worker 構成,它們共同管理 Vineyard 中構成每個物件的 metadata 和 payload:
- Master:使用 etcd 作為後設資料管理系統,承擔 metadata 的儲存功能。
- Worker:使用 vineyardd(Vineyard 的守護程序) 管理共享記憶體,負責儲存 payload 資料。
在效能方面,當應用任務與 vineyardd 位於同一節點上,它們透過內部程序通訊(IPC)進行高速資料傳輸;而在不同節點之間,應用任務與 vineyardd 透過遠端過程呼叫(RPC)傳輸資料,速率相對較慢。
Vineyard Runtime 的優勢
- 效能卓越: 避免不必要的記憶體複製、序列化/反序列化、檔案 I/O 和網路傳輸等。同節點內應用與資料互動實現瞬時讀取(大約 0.1 秒),寫入僅需秒級時間;跨節點操作時,資料傳輸速度可接近網路頻寬上限。
- 簡單易用: 透過對分散式物件進行資料抽象,使用者只需要使用簡單的 put 和 get 操作就能在 Vineyard 中進行資料存取。同時支援包括 C++、Python、Java、Rust 和 Golang 在內的多語言 SDK 。
- 整合 Python 生態: 整合了常用的 Python 物件,例如 numpy.ndarray 、pandas.DataFrame、pyarrow.{Array,RecordBatch,Table}、pytorch.Dataset 和 pytorch.Module 等。
Vineyard Runtime 效能怎麼樣
在資料量大小為 22GB 的情況下,使用 Vineyard Runtime 的同節點和跨節點效能分別如下圖所示。
當使用者任務被排程到 vineyardd 所在節點上時,此時達到 Vineyard 的最佳效能,即透過 IPC 實現資料傳輸。由於 Vineyard 會預先分配記憶體,資料寫入到 Vineyard 比物件儲存作為中間介質快 68 倍左右;由於和 Vineyard 共享同一塊記憶體,使用者任務在讀取 Vineyard 資料時,資料將透過零複製的形式被傳遞到使用者任務中,實現瞬時讀取。
當使用者任務被排程非 vineyardd 所在節點時,此時達到 Vineyard 的最差效能,即透過 RPC 進行資料傳輸。相比於 OSS,由於 Vineyard 進行 RPC 傳輸時基本能打滿頻寬,資料寫入到 Vineyard 比 OSS 快 2.3 倍左右,從 Vineyard 讀取資料比 OSS 快 2.2 倍左右。
Fluid+Vineyard 實戰
在本教程中,我們將演示如何使用 Vineyard Runtime 在 ACK(Alibaba Cloud Kubernetes)上訓練一個線性迴歸模型,請按照以下步驟進行操作。
步驟一:在 ACK 叢集中安裝 Fluid
選擇 1:安裝 ack-fluid,安裝步驟可參考雲原生 AI 套件文件 [ 1] 。
選擇 2:使用開源版,我們使用 Kubectl [ 2] 建立一個名字為 fluid-system 的 namespace,然後使用 Helm [ 3] 來安裝 Fluid,僅需透過以下幾個命令來完成這個過程。
# 建立 fluid-system namespace
$ kubectl create ns fluid-system
# 將 Fluid 儲存庫新增到 Helm 儲存庫
$ helm repo add fluid https://fluid-cloudnative.github.io/charts
# 獲取最新的 Fluid 儲存庫
$ helm repo update
# 找到 Fluid 儲存庫中的開發版本
$ helm search repo fluid --devel
# 在 ACK 上部署相應版本的 Fluid chart
$ helm install fluid fluid/fluid --devel
當我們在 ACK 上部署好 Fluid 平臺後,接下來需要執行以下命令來安裝 Fluid Python SDK。
$ pip install git+https://github.com/fluid-cloudnative/fluid-client-python.git
步驟二:開啟資料與任務的協同排程(可選)
在雲環境中,端到端資料操作流水線經常包含多個子任務。當這些任務被 Kubernetes 排程時,系統僅考慮了所需的資源約束,而無法保證連續執行的兩個任務能在同一個節點執行。這導致二者在使用 Vineyard 共享中間結果時,會因為資料遷移引入額外的網路開銷。如果希望將任務和 Vineyard 排程到同一節點達到最佳效能,可以按照修改如下 configmap 配置開啟 FUSE 親和性排程,這樣系統排程將優先讓相關聯的任務在同一個節點進行記憶體訪問,以減少資料遷移產生的網路開銷。
# 按照以下命令修改 webhook-plugins 的配置,並開啟 fuse 親和性排程,
$ kubectl edit configmap webhook-plugins -n fluid-system
data:
pluginsProfile: |
pluginConfig:
- args: |
preferred:
# 開啟 fuse 親和性排程
- name: fluid.io/fuse
weight: 100
...
# 重啟 fluid-webhook pod
$ kubectl delete pod -lcontrol-plane=fluid-webhook -n fluid-system
步驟三:構建和部署線性迴歸資料操作流水線
該步驟包括資料預處理、模型訓練和模型測試階段,完整程式碼可參考示例程式碼 [ 4] 。該步驟主要步驟如下:
- 建立 Fluid 客戶端:程式碼使用預設的 kubeconfig 檔案連線到 Fluid 控制平臺,並建立 Fluid 客戶端例項。
import fluid
# 使用預設 kubeconfig 檔案連線到 Fluid 控制平臺,並建立 Fluid 客戶端例項
fluid_client = fluid.FluidClient(fluid.ClientConfig())
- 建立和配置 Vineyard 資料集與執行時環境:指令碼建立名為 vineyard 的資料集並初始化相關配置,包括副本數和記憶體大小,使資料集繫結到執行時環境。
# 在 default namespace 下建立名為 vineyard 的資料集
fluid_client.create_dataset(dataset_name="vineyard")
# 獲取 vineyard 資料集例項
dataset = fluid_client.get_dataset(dataset_name="vineyard")
# 初始化 vineyard runtime 的配置,並將 vineyard 資料集例項繫結到該 runtime。
# 副本數為 2,記憶體分別為 30Gi
dataset.bind_runtime(
runtime_type=constants.VINEYARD_RUNTIME_KIND,
replicas=2,
cache_capacity_GiB=30,
cache_medium="MEM",
wait=True
)
- 定義資料預處理、模型訓練和模型評估函式:分別實現了用於執行資料清洗和劃分資料集的預處理函式,訓練線性迴歸模型的訓練函式,以及用於評估模型效能的測試函式。
# 定義資料預處理函式
def preprocess():
...
# 將訓練資料和測試資料存入 vineyard
import vineyard
vineyard.put(X_train, name="x_train", persist=True)
vineyard.put(X_test, name="x_test", persist=True)
vineyard.put(y_train, name="y_train", persist=True)
vineyard.put(y_test, name="y_test", persist=True)
...
# 定義模型訓練函式
def train():
...
# 從 vineyard 中讀取訓練資料
import vineyard
x_test_data = vineyard.get(name="x_test", fetch=True)
y_test_data = vineyard.get(name="y_test", fetch=True)
...
# 定義模型測試函式
def test():
...
# 從 vineyard 中讀取測試資料
import vineyard
x_train_data = vineyard.get(name="x_train", fetch=True)
y_train_data = vineyard.get(name="y_train", fetch=True)
...
- 建立任務模版並定義任務工作流:利用先前定義的函式,建立了資料預處理、模型訓練和模型測試的任務模板。這些任務按順序執行以形成完整的工作流。
preprocess_processor = create_processor(preprocess)
train_processor = create_processor(train)
test_processor = create_processor(test)
# 建立線性迴歸模型的任務工作流:資料預處理 -> 模型訓練 -> 模型測試
# 下列的掛載路徑 "/var/run/vineyard" 是 vineyard 配置檔案的預設路徑
flow = dataset.process(processor=preprocess_processor, dataset_mountpath="/var/run/vineyard") \
.process(processor=train_processor, dataset_mountpath="/var/run/vineyard") \
.process(processor=test_processor, dataset_mountpath="/var/run/vineyard")
- 提交併執行任務工作流:將工作流提交到 Fluid 平臺進行執行,並等待任務完成。
# 將線性迴歸模型的任務工作流提交到 fluid 平臺,並開始執行
run = flow.run(run_id="linear-regression-with-vineyard")
run.wait()
- 資源清理:執行完畢後,清理在 Fluid 平臺上建立的所有資源。
# 清理所有資源
dataset.clean_up(wait=True)
透過以上一套 Python 程式碼可以幫助資料科學家在本地開發透過 kind 環境 [ 5] 進行除錯,把阿里雲 ACK 作為生產環境進行使用,在提升了開發效率的同時得到非常好的執行效能。
總結與展望
透過結合 Fluid 的資料編排和 Vineyard 的高效資料共享機制,可以解決 Kubernetes 中資料工作流中的開發效率低下、中間資料儲存成本高昂和執行效率不足的問題。點選此處即可獲得實戰原始碼。
未來,我們計劃支援兩個主要場景,一個是 AIGC 的模型加速,透過避免 FUSE 開銷和儲存物件格式轉換將模型載入效能進一步提升;另一個是支援 Serverless 場景,為 Serverless 容器提供高效的原生資料管理。
參考資料:
[1] 雲原生 AI 套件文件
https://help.aliyun.com/zh/ack/cloud-native-ai-suite/user-guide/deploy-the-cloud-native-ai-suite?spm=a2c4g.11186623.0.i14#task-2038811
[2] Kubectl
https://github.com/kubernetes/kubectl
[3] Helm
https://github.com/helm/helm
[4] 示例程式碼
https://v6d.io/tutorials/kubernetes/vineyard-on-fluid.html
[5] kind 環境
https://kind.sigs.k8s.io/