DBT、Airflow 和 Kubernetes的架構演進 - yan

banq發表於2022-08-02

如果您在 Kubernetes 叢集上部署 Airflow,並且正在尋找將 DBT 整合到 Airflow 中的方法,那麼本文可能會給您一些啟發。

需要對 Airflow、DBT(資料構建工具)和 Kubernetes 有一些基本的瞭解。

第 1 部分 — RPC 伺服器設定
Astronomer 的人給了我們一些很好的想法,告訴我們如何建立 DBT 和 Airflow 之間的整合。我們最初的設計深受他們這篇文章的啟發。
就像文章中推薦的一樣,我們設定了一個 CI/CD 流程,該流程manifest.json會在主分支上的每次新提交時生成一個新檔案。
然後,Airflow 例項隨後讀取該manifest.json檔案,為每個模型建立一個 DAG,該 DAG 還負責執行上游模型。
但是,我們認為有幾點可以改進:

  • DBT 模型可能必須與 Airflow 例項放在一起,以便 Airflow 訪問它們並dbt run在它們上執行。假設 Airflow 儲存庫已經存在,DBT 相關檔案可能必須位於同一個儲存庫中。這可能會使儲存庫變得臃腫,分析工程師可能會發現很難維護 DBT 相關元件。
  • 從技術上講,可以建立一個不同的儲存庫來儲存 DBT 模型,並讓 Airflow 例項簡單地從儲存 DBT 模型的目錄中讀取。在 Kubernetes 設定中,這可能意味著設定共享卷形式的額外複雜性,併為每次新更新複製所有 DBT 模型。
  • 此外,隨著我​​們在 Airflow 上部署的各種工作負載,在同一個 Airflow 例項中安裝了越來越多的庫。我們希望保持庫的精簡以避免衝突和混亂的依賴問題。

在尋求解決這些問題的解決方案時,我們發現 DBT 為我們提供了一個開箱即用的外掛來執行 RPC 伺服器,這可能會有所幫助。
根據 DBT,“該伺服器在 dbt 專案的上下文中編譯和執行查詢。此外,RPC 伺服器提供的方法使您能夠列出和終止正在執行的程式。”。
本質上,這意味著一旦我們設定了伺服器,而不是將 DBT 模型放在 Airflow 儲存庫中,我們可以:
  • 向 RPC 伺服器(已經擁有專案的上下文)提交請求以執行 DBT 命令
  • 輪詢提交作業的狀態
  • 工作完成後,解析響應並根據最終狀態決定做什麼

因此,對於從 parsing 生成的每個任務節點,我們將使用 a來執行滿足這些基本步驟的函式manifest.json,而不是使用 aBashOperator來執行。dbt runPythonOperator
隨著 DBT 儲存庫與 Airflow 儲存庫的分離,現在整體部署如下所示:

DBT、Airflow 和 Kubernetes的架構演進 - yan
對於每個 DBT 任務,PythonOperator 執行以下函式:

使用 PythonOperator 建立 DBT 任務
下圖進一步細分了 Airflow 為特定模型執行 DBT 任務時發生的情況:

DBT、Airflow 和 Kubernetes的架構演進 - yan

  1. 氣流排程器生成一個 pod 來執行 DBT 任務
  2. pod 向 RPC 伺服器發出請求,指定它要執行的命令
  3. RPC 伺服器接收到請求,然後啟動一個作業來執行命令
  4. pod 收到帶有作業 ID 的響應
  5. Pod 使用作業 ID 不斷向 RPC Server 發出請求以檢查作業的狀態
  6. 作業仍在執行
  7. RPC 伺服器響應作業仍在執行
  8. pod 發出另一個請求以檢查作業的狀態
  9. 最終,工作完成或失敗
  10. pod 將收到一個響應,說明工作已完成或失敗。Airflow 任務將被標記為成功或失敗。

透過這種設計,我們設法將 DBT 儲存庫與 Airflow 儲存庫分離。分析工程師可以使用 DBT 儲存庫,而無需擔心如何在生產環境中執行這些模型。資料工程師可以使用更精簡的儲存庫,並避免任何潛在的庫衝突問題。

第 2 部分 — KubernetesPodOperator 設定
RPC 伺服器能夠在一段時間內發揮其作用。但是,當我們新增更多模型並更頻繁地執行它們時,這種設定的弱點開始顯現。由於 RPC 伺服器是使用固定資源部署的,當有更多請求進入時,它無法自行擴充套件。
更糟糕的是,RPC 伺服器沒有內建佇列系統來處理傳入的請求負載。任務需要更長的時間才能完成。響應時間過長的任務被標記為“失敗”,並且 Airflow 觸發了重試,這會堆積到現有的作業上。該轉換系統的整體效能顯著下降。

DBT、Airflow 和 Kubernetes的架構演進 - yan
正如我們所見,RPC 伺服器設定實際上是Kubernetes 叢集中構建的系統其餘部分的反模式。
當 Airflow 執行越來越多的任務時,會產生更多的 pod,並且叢集能夠擴充套件資源以滿足這些 pod 的需求。相反,RPC 伺服器必須使用其固定資源並嘗試執行所有被請求的模型。

我們試圖透過為 RPC 伺服器提供更多資源來解決此問題,但它不可擴充套件。增加的資源往往跟不上加班增加的 DBT 模型數量的增加。在停機期間,RPC 伺服器只是囤積資源而不執行任何作業,這會轉化為更高的伺服器成本。

在高需求期間嘗試擴大 RPC 伺服器的例項數量在技術上是可行的。但是,由於觸發 RPC 伺服器中的作業的 pod 必須從具有給定 'request_token' 的同一 RPC 伺服器例項輪詢,因此必須實現將請求路由回同一 RPC 伺服器的機制。

由於這種增加的複雜性,我們的團隊認為不值得進一步追求這種設計。

此時,該團隊一直在嘗試使用 KubernetesPodOperator 來處理無法使用預設運算子輕鬆構建的管道。我們發現使用 KubernetesPodOperator 的一個優勢是特定於工作負載的庫僅包含在映像中,而 Airflow 只是觸發作業的編排器,進而生成 Pod 以在叢集上執行工作負載。

我們可以為該 Pod 指定我們想要的資源,以及任何變數和配置。這是一個優雅的抽象,允許我們執行我們想要的任何工作。
因此,我們認為我們可以使用 KubernetesPodOperator 來執行 DBT 任務。對於解析manifest.json檔案的每個模型節點,而不是向 RPC 伺服器發出請求並等待響應的 PythonOperator,它可能是拉取 DBT 儲存庫映像的 KubernetesPodOperator,並指定自定義命令來執行該特定模型。

下面的程式碼片段說明了透過解析manifest.json檔案在每個節點上建立的任務:
使用 KuberenetesPodOperator 建立 DBT 任務

DBT、Airflow 和 Kubernetes的架構演進 - yan
使用 KubernetesPodOperator 執行 DBT 任務

  1. 排程程式生成一個 pod 來執行 DBT 任務(簡化)
  2. 任務拉取最新的 DBT 倉庫映象
  3. 然後該任務執行特定於模型的自定義命令

在這種情況下,如果為叢集分配了足夠的節點,當有大量活躍的 DBT 任務時,叢集只會增加節點的數量來處理傳入的工作負載。與之前的迭代不同,資源現在是動態配置的。

結論
當前的設定滿足了我們為轉換層設計的所有要求。它具有高度可擴充套件性和穩定性。團隊可以在較小的程式碼庫上工作,並且更容易維護。從那以後,我們的工作量增加了幾倍,系統可以毫不費力地處理它們。

相關文章