Run Airflow and Jenkins as a Kubernetes way to saving cost

Run Airflow and Jenkins as a Kubernetes way

前言

今天要一次來談兩個服務,分別是 Jenkins 跟 Airflow,大家印象中可能覺得,這兩個服務常被使用的場景,有些許的不一樣,Jenkins 是歷史悠久的老頭,大部分人用它來完成一些 CI/CD 的工作,而 Airflow 則是近年來大家在資料工程領域常用的 ETL 工具。

漸強實驗室使用這兩個服務已經有蠻長一段時間,不意外的 Jenkins 用來負責各種服務的 CI/CD pipeline,每週在環境中自動化的迭代與發佈數十次的版本,而 Airflow 則是負責定時在各個資料庫與資料倉儲中提取、搬運以及計算資料。

今天要來分享(推坑)的,並不是叫大家使用這兩個好棒棒的服務(因為我相信這兩個服務,在各自的領域都是非常多人使用的解決方案),而是推薦大家如何使用🤔!? 會根據我在漸強實戰的服務架構與系統維運經驗,談談漸強在系統架構層面,是用如何用 Kubernetes 的姿勢(方式)來部署和使用 Jenkins & Airflow,也會談到這樣的選擇帶來了哪些好處?跟為什麼如果你的團隊也熟悉 Kubernetes ,非常值得推薦你們也嘗試看看。

補充說明一下,Jenkins & Airflow 的配置與運作模式是蠻不一樣的,所以此文章並不分享配置細節(不然寫不完…),而是要來用比較大局觀的方式聊聊一些架構跟維運上的概念,以及我的一些想法。

為什麼把風流老爺放在一起談?

如果你熟悉這兩個服務(風流+老爺),說到底!這兩個服務是有一些本質上的相同,那就是:

就是他們都是根據某些規則,來觸發調度一系列的任務

以規則來說, Jenkins 可能是你的 PR Merge 後,從 Github 打來的 webhook,而 Airflow 就是 Scheduler 的時間排程;再來是則是觸發之後要調度的任務,任務簡單的說就是跑一個或是一組程式,Jenkins 可能就是跑你所撰寫的 pipeline,而 Airflow 則是執行你定義的 Dag。

所以每當條件觸發了某項任務,相對就會需要有一個環境與跟運算資源來完成這個任務。
要環境!?要資源!?
不就正剛好是 Kubernetes 的拿手好戲嗎?

換個姿勢看 Kubernetes

一直以來 Kubernetes 被定義成 Container Orchestration,可以用來快速方便的調度部署許多容器化的服務,換個角度看,利用容器化技術可以達成輕量快速的環境切分,加上動態的容器生命週期控制,換句話說 Kubernetes 就是一個環境與資源的調度管理器,可以動態且快速地提供不同的環境與運算資源。

你可能會說,我操作一些 Cloud 的 API 也可以完成這件事,但是本質上 Kubernetes 提供的是更完整且統一的介面和更即時且動態的操作,這應該會是這幾年不管走到哪裡都通用,且主流的使用方式。

我認為 Jenkins 或是 Airflow 的元件,是不是部署在 Kubernetes 裡面管理,其實不是主要的重點,老實說也不會得到太多特別的好處,可能就是在同一個叢集內,可以專注在內部的權限,比外部更安全且方便一點。最重要的點是讓這些任務再觸發的時候,能夠透過 Kubernetes 來提供任務執行的環境以及資源,這才是精髓以及價值之所在。

這一切的基礎以及實作方法,就從 Kubernetes 的 API 開始,當你需要環境與資源的時候,環境可以自己準備好(用 Dockerfile 輕鬆“寫”個環境, build 好 image),透過 Kubernetes API 去跟 kube-api-server 說,我需要一個不管是 Pod 也好 Job 也好,用什麼 image 跑什麼指令,需要哪些資源,Call 個 API 之後 Kubernetes 就幫你搞定。

一旦有了這個概念後,你可能可以開始想像,不只是 Airflow 可以這樣做,Jenkins 可以這樣做,甚至你自己的 APP 也可以這樣做,不過話說回來,還是先簡單的來聊聊 Jenkins 跟 Airflow 運作的方式。

Jenkins

傳統上部署 Jenkins 之外,都可能會在部署一些不同環境的 Agent Node,來讓 Jenkins Master 派發工作到這些機器上,而這些機器在閒置的時間,就造成一些資源的浪費,而且不只是資源的浪費,多了幾個 Node 對維運人員來說,就又多了幾分需要維護或監控的成本。

Jenkins master with agent node

那如果是要讓 Jenkins 派發工作到 Kubernetes 要怎麼做呢?這邊主要就是透過 kubernetes-plugin 來完成這件事情,設定其實也蠻簡單的,就是安裝 plugin 之後,去設定 Kubernetes API server 的一些資訊(包含地址跟一些憑證等等…),然後就可以在你的 Jenkinsfile 非常間單的撰寫一些你需要的 Pod spec,跟每個 Stage 需要做的事情,最後 Jenkins 就可以根據你給的觸發條件,在派發工作時透過 kubernetes-plugin 的幫忙,利用 Kubernetes API 來動態操作 Kubernetes 的資源,Pod 會在派發工作時被放到 K8s 叢集中工作,完成後就消失,如此的乾淨不沾手。

Jenkins 使用 kubernetes-plugin 發派任務到 K8s 示意圖

在撰寫文章時,我有稍微 Google 了一下,發現現在還蠻多教學的,不管你是不是把 Jenkins Master 部署在同一個 Kubernetes 叢集中,甚至是獨立 VM 部署,應該都有需多資料可以參考,所以有興趣的人可以去查一查如何集成你現在的 Jenkins,如果就想要重新部署一個到 Kubernetes 中,懶人包救星 Helm 官方有一包 Jenkins 的 Chart 非常方便,直接拿來爽用(Google 到的可能有超過一半都是用這個教學),不過我必須要提醒一件事,千萬不要貪圖方便,直接用別人寫好的 Chart 部署了就用,卻不去理解裡面的配置是怎麼運作,這是非常危險的,到時出事就會花非常多的時間查找問題。

Airflow

Airflow 一般來說需要幾個主要的原件組成:

  1. Webserver (一個 Flask APP,可以讓你方便操作以及視覺化 Dag 流程等等…)
  2. Scheduler (排程服務)
  3. Metadata Database (用來存 Airflow 的狀態,通常是 PostgreSQL 或是 MySQL)
  4. Executor (執行排程觸發的任務有很多不同的種類)

這邊要來談的主要就是 Executor ,一般來說有 LocalExecutor、SequentialExecutor、CeleryExecutor 和 KubernetesExecutor 這四種,如果你的 Airflow 有同時要跑多個 Dags 或是 Dag 有平行運算的工作,SequentialExecutor 或是 LocalExecutor 會是比較不好的選擇,通常這兩種用在 local 開發或 debug 的時候,而在 Airflow 1.10.0 之前是沒有 KubernetesExecutor 的,所以我想大部分系統就會使用 CeleryExecutor 來運作。

所以首先就站在架構的角度來看 CeleryExecutor 的配置,有三個服務你躲不掉 Webserver、Scheduler、Metadata Database,這三個服務架好架滿是必不可少的,再來使用了 CeleryExecutor,這時候你需要搭配一個 Broker ,通常是一台 Redis 或是一台 RabbitMQ,然後可能是一個或是多個 Celery Worker。

Airflow with CeleryExecutor

如果是使用 KubernetesExecutor 呢?同 Jenkins 那邊的概念,需要運行任務時,再去跟 Kubernetes 說你需要一個 Pod 來跑就好,這時你的 Broker 跟 Celery worker 一個都不需要存在,瞬間少了兩組服務(Redis or RabbitMQ + Celery)需要部署跟維運監控,除了節省了一些常駐的計算資源成本,也節省了 SRE 的時間。

Airflow with KubernetesExecutor

兩種配置對於 Airflow 來說,運算資源的影響比較多,至於在環境切分的部分,我反而覺得兩種 Executor 感覺上就差不了多少,因為如果用了 CeleryExecutor 也可以搭配 Kubernetes Operator 來對 Kubernetes 創建你需要的 Pod 來運行任務,所以就利用容器打包環境的彈性來說,都是做得到的。

漸強的作法與經驗

在此也介紹漸強的作法,漸強在 Jenkins 的部分是利用 Helm Chart 直接部署在 Kubernetes 中,每個專案的 CI/CD 則是透過撰寫 Jenkinsfile 搭配 Jenkins 的 Multibranch Pipeline 來完成 CI/CD 的工作。我的經驗是部署的時候,小問題蠻多的,尤其是想升級 Jenkins 版本的時候千萬不要衝動,有空閒的時候再升級,因為通常升級後,都會遇到不同的問題,導致不能用…,這一年多的時間前前後後升級過幾次,每次升級都會又多花一點時間,在解不會動的 bug,這是比較令人頭痛的問題😂,當然老爺子歷史悠久,用的人多,站在巨人的肩膀上,問題通常都是查得到解得掉的。

總而言之不管在環境的維護,或是資源的調整,甚至是維護或運營的成本,這樣的配置都是讓人非常滿意的,十分推薦大家使用!

而 Airflow 則是自己 Build image 來 deploy,然後把 Webserver 跟 Scheduler 也部署在 Kubernetes 中,Metadata Database 則是託管 GCP 的 Cloud SQL(用 PostgreSQL,主因是我不想管 DB😜 ),然後因為配置 KubernetesExecutor 所以不需要 Broker 跟 Celery,Dag 運行的 Log 是往 Cloud Storage 去寫,最後有趣的是存放 Dags 的方式,我是直接 code 一整包 build 成 image 一起 deploy,所以每個運行的 container 都會有最新相同的 Dags,這樣根據版本可以確保 Dags 長相一樣,也撇除了需要掛載或同步 Dags 的問題,到目前為止我是覺得非常方便,也沒出什麼差錯過。

再來談的是運行任務的動態資源部分,因為這兩個服務都運行在同一個較大的生產集群,所以我配置了幾組動態擴展的 stateless node pool,讓不同的服務可以在這些 node pool 上,共享動態的資源,來運行無狀態的任務,而 Jenkins 跟 Airflow 的任務,就會根據 spec 設定的 affinity,調度到這些可自動擴展的 node pool 上使用共享的資源,來達到節省資源的結果,這邊比較要注意的是,自動擴展 node 是比較花費時間的,所以對於 Jenkins Jobs 或是 Airflow ETL 這些相對比較不需要即時性的任務來說是夠用的,但是對於一些會遇到流量峰值的服務,勸你還是把 Node 開好開滿,容器擴展速度快,但也可能追不上瞬間流量,更別說 VM 擴展的速度了🐢。

如果你想更省,還可以使用 Preemptible/Spot instance 來加入這些動態擴展的 pool 中,配合 Taint 和 Toleration 的配置來使用這些便宜的運算資源,不過考量可用性的角度,不建議全部替代成 spot instance,可能要測試一下 spot instance 的占比,來達到不影響可用性為原則。

綜合我剛剛所提,漸強使用這兩個服務的方法,主要就是兩點好處:

  • 少部署一些服務:相對少花一點錢,也少花一點工程師的維護時間
  • 動態的運算資源:一樣是省錢,而且在容器化的幫助下,相信也是可以省下工程師許多設定環境的時間。

老實說我其實給不出量化的數據,來說到底省了多少錢,或是省了多少時間,一方面這跟開發速度與功能的迭代有關,另一方面運算的資源不只是 Jenkins 跟 Airflow 在分,但秉持著動態資源分享的方式,是一定可以省到錢的,然後就是以 SRE 的角度來說,能夠少照顧一些機器跟服務,省下的絕對不只有時間,更多的是~你不知道的事🙃~

結論

我覺得能夠動態的去向 Kubernetes 取得這種”用完就收”的運算資源真的很方便,尤其你 Kubernetes 如果是用託管的服務(GKE、AKS、EKS 等等…)更是舒適到不行,如果因此又可以少部署一些常駐的服務,省錢又省時間去維護,不管對工程師或是公司本身,這個爽度的提升是相當的高阿,當然有人可能會覺得我是 Kubernetes 傳教士,什麼都要 Kubernetes,這時就要談一下我的立場了…

我實踐架構的原則是:

需要自己搭的,能少一種是一種,

預算如果夠!? 最好通通託管或 serverless,

工程師專心寫 code 最幸福 。

遵從以上法則,幸福指數才會越來越高,雖然現實是殘酷的,哈哈

雖然本文沒有著墨在詳細的部署流程跟配置上,根據我部署過許多不同服務的經驗,Jenkins + kubernetes-plugin 跟 Airflow + KubernetesExecutor 應該都屬於中等難度(我的不負責任難度推估XD),Jenkins 主要是小問題多,但都查得到答案,Airflow 則是元件多了點,但配置相對容易,如果大家已經有在使用 Kubernetes 不妨考慮一下這兩個組合,以上就是漸強使用風流老爺爽度加成的誠心推薦!

最後一樣有任何問題都歡迎找我討論交流,謝謝各位的耐心。

--

--