【CSDN 編者按】使用 pex 使我們能夠在 Docker 之上建立一個可重複的、一緻的環境,我們很高興使用這個 pex-on-docker 組合來探索其他的可能性。
原文連結:https://dagster.io/blog/fast-deploys-with-pex-and-docker
未經允許,禁止轉載
作者 | Shalabh Chaturvedi譯者 | 鄧曉娟 責編 | 王子彧出品 | CSDN(ID:CSDNnews)
無伺服器開發和回報循環
Dagster 是一個資料編排器。在無伺服器 Dagster 雲上,不需要建立本地開發環境或雲基礎設施,就可以開發和部署 Dagster 代碼。當你向 GitHub 送出修改時,GitHub Action 會直接建構和部署你的代碼到 Dagster 雲。你可以在使用者界面中檢視和互動你的 Dagster 對象。借助 Dagster 雲,遠端環境通常用于讓使用自動建立的暫存環境與合作者共享部署。個人本地開發和共享遠端環境相結合,形成了一個強大的開發周期。
最初,我們在這上面使用了基于 Docker 的标準建構流程。然而我們很快發現,這讓編輯-部署-運作的周期變得非常繁瑣緩慢。為了加快速度,我們建構了一個系統,實作在 Docker 鏡像之外運送代碼。這篇文章描述了我們分析的問題、确定的解決方案,以及在這個過程中做出的各種權衡。
Shalabh Chaturvedi 分享了 Dagster Cloud 新的快速部署能力的高水準概述,詳情請觀看視訊:https://youtu.be/mPT3FBFSw6g
Docker鏡像的問題
當我們在 GitHub 上建構 Docker 鏡像并将其部署到 Dagster 雲時,每次送出都需要3到5分鐘才能在 Dagster 使用者界面上顯示出來。無伺服器開發人員通常會在每次疊代中對代碼進行小的改動,但卻每次都要等待3分鐘以上才能看到改動的效果,這種無意義的等待很容易讓人厭煩。我們分析了一個問題:“當你修改一行代碼并送出後,會發生什麼?”發現了以下的情況。
我們分析了 "當你改變一行代碼并送出時會發生什麼",發現了以下情況。
- 20s > 提供 GitHub 運作器并下載下傳動作
- 10s > 下載下傳基于 Docker 的行動
- 60s > 建立并上傳使用者的 Docker 鏡像*。
- 90s > 在 AWS 中運作使用者的 Docker 鏡像
- 180s的運作時間
* 在啟用緩存的情況下需要60秒(如果沒有改變依賴關系的話);如果依賴關系有變化,則需要90秒以上。
如你所見,花費時間最長的兩件事是:
- 建構一個 Docker 鏡像(60-90多秒)
- 部署 Docker 容器(90秒)
那就讓我們來看看這兩件事都做了些什麼。
建構 Docker 鏡像
關于建構 Docker 鏡像需要注意的一些事情。
- Docker 鏡像是由堆棧中的多個層堆疊而成的,其中每一層都是由 Docker 檔案中的一個指令子集建構的;
- 每一層都由一個哈希值來識别;
- 當上傳鏡像到系統資料庫時,隻有不存在于系統資料庫中的層(由哈希值識别)被上傳;
- 使用 GitHub Actions 緩存在 GitHub 建構機上重建鏡像時,會将所有未受影響的層從緩存中拉到建構機上。請注意,如果你的項目中有大量的依賴關系沒有改變,它們會在建構過程中從緩存中一起被複制到建構機器上;
- Docker 的建構不是确定性的。如果你用完全相同的内容建構一個鏡像兩次,每次都可能産生不同的哈希值。(雖然不直接相關,但我們想記錄一下這個意外的觀察結果。作為一個極端案例,考慮到一個新建構的大層與已經在系統資料庫中的層相同,仍然可能作為一個新的層被上傳)。
啟動 Docker 容器
關于啟動 Docker 容器需要注意的是,我們使用 AWS Fargate,它需要45到90秒的時間來配置和啟動一個鏡像。且不提供任何圖像緩存。啟動一個新的容器會從系統資料庫中下載下傳所有的層到配置的容器上。
其他限制
在 Docker 鏡像建立和啟動後,我們運作使用者的代碼來提取中繼資料,顯示在使用者界面上。這一步無法避免,可能需要幾秒鐘到30秒,甚至更久,這取決于中繼資料的計算方式(比如它可以連接配接到資料庫來讀取模式)。這個代碼伺服器保持活動狀态,為中繼資料請求提供服務,直到推送新版本的代碼,然後啟動一個新的容器。
我們的一個關鍵要求是可重複性:我們需要能夠多次重新部署完全相同的代碼和環境。使用 Docker 鏡像的哈希值作為代碼和環境的辨別符,可以很好地滿足這一要求。
備選方案綜述
除了上述的方案以外,我們還探索和讨論了一些替代方案。
- 從 Fargate 切換到 EC2,以加快容器的啟動。這将增加我們的營運負擔,要求我們預先提供、監控和擴充我們的叢集。我們仍然會遇到 Docker 建構緩慢的問題;
- 換成不同的 Docker 建構系統,如 AWS CodeBuild。這将需要更多的部署工作,并與 GitHub 進行更深入的整合。目前還不清楚這樣做的回報是否值得;
- 切換到 AWS Lambda,啟動時間快得多。Lambda 環境有自己的基礎鏡像,對于自定義需求來說不太友好。而且它的執行時間還有15分鐘的限制,這對運作時間較長的伺服器來說,需要複雜的變通方法;
- 通過建構并隻上傳修改後的代碼到同一伺服器,重新使用長期運作的代碼伺服器。這裡的挑戰是實作打包和運作機制,以確定一個可靠和可重複的執行環境。我們研究了各種打包和分發 Python 環境的方法,包括 rsync、poetry、nix、shiv 和 pex。還考慮了使用 EFS卷來挂載Python環境,與這些工具相結合。
我們作出最終決定背後的有一個關鍵因素,是意識到雖然 Docker 鏡像是行業标準,但如果我們隻需要同步一個小的變化時,就去移動100多兆的鏡像,是很不必要的繁重操作。考慮到 Git 隻提供差異,但卻能産生完整而一緻的存儲庫。是以我們傾向于方案4,隻需要能找到一個合适的工具來做大部分的工作。經過一些實驗,我們發現 pex 的許多功能對我們的用例非常有效。
什麼是 PEX?
pex 是 Python Executable 的縮寫,它是一種将 Python 包捆綁到稱為 pex 檔案的工具。這些是可執行檔案,其中包含 Python 包和一些引導代碼。例如,我們可以把 dagster 包和它的依賴項捆綁成一個檔案,然後運作它。
% pex dagster --python=python3.8 -o dagster.pex % ./dagster.pex Python 3.8.16 (default, Dec 7 2022, 01:24:57) [Clang 14.0.0 (clang-1400.0.29.202)] on darwin Type "help", "copyright", "credits" or "license" for more information. (InteractiveConsole) >>> import dagster >>>
将整個環境放在一個檔案中,便于運輸和存儲在 S3 中。pex 提供的不僅僅是一個 "檔案中的虛拟環境",以下是我們使用的其他功能。
- 隔離
在運作時,pex 環境與其他網站範圍内的包完全隔離。環境中唯一存在的包是那些捆綁在 pex 檔案中的包。我們将多個 pex 檔案運送到同一台機器上,而不必擔心環境隔離問題。
- 确定性
使用相同的輸入包會産生位對位的相同的 pex 檔案。
$ pex dagster pandas -o out.pex | sha256sum e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 - $ pex dagster pandas -o out.pex | sha256sum e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -
這讓我們有信心用内容尋址來識别這些 pex 檔案。為了實作可重複性,除了Docker 鏡像的哈希值,還使用 pex 檔案哈希值。
- 組成
多個 pex 檔案可以在運作時合并,有效地将環境合并成一個。
% pex pandas -o pandas.pex % pex dagster -o dagster.pex % PEX_PATH=pandas.pex ./dagster.pex Python 3.8.16 (default, Dec 7 2022, 01:24:57) [Clang 14.0.0 (clang-1400.0.29.202)] on darwin Type "help", "copyright", "credits" or "license" for more information. (InteractiveConsole) >>> import pandas >>> import dagster >>>
我們用它把代碼分成兩部分,在運作時合并:一個包含所有依賴關系的 deps.pex 檔案和一個隻包含使用者代碼的 source.pex 檔案。
- 跨平台的建構
我們在無伺服器雲中使用 Linux python :*-slim 衍生的基礎鏡像。隻要軟體包的輪子可用, pex 工具可以在任何平台上為 Linux 建構 pex 檔案。
快速部署
我們使用 pex 與 S3 相結合來存儲 pex 檔案,建立了一個系統,其中快速路徑避免了建構和啟動 Docker 鏡像的開銷。
我們的系統是這樣工作的:當你向 GitHub 送出代碼時,GitHub Action 要麼進行完全建構,要麼進行快速建構,這取決于你的依賴關系自上次部署後是否有變化。我們跟蹤 setup.py 和 requirements.txt 中指定的依賴項。
對于一個完整的建構,将項目依賴性建構到 deps.pex 檔案,将代碼建構到 source.pex 檔案。兩者都被上傳到 Dagster 雲端。對于快速建構,隻建構和上傳 source.pex 檔案。
在 Dagster 雲中,可以重新使用一個現有的容器或提供一個新的容器作為代碼伺服器。将 deps.pex 和 source.pex 檔案下載下傳到這個代碼伺服器上,并使用它們在一個隔離的環境中運作代碼。我們從不在使用者之間共享一個容器,一個容器上的所有環境都屬于同一個使用者。快速部署的最佳情況和最壞情況的時間線如下。
其結果是,在快速建構(Fast Build)的路徑中,當我們進行快速建構并重用現有容器時,整個過程隻需40秒,而不像以前一樣需要3分鐘以上。
我們将這一功能稱為【快速部署】,現在所有新注冊的無伺服器使用者都預設開啟這一功能。
權衡與問題
快速部署極大地提高了部署速度(4-5倍),但它伴随着一些需要權衡的問題和其他因素,我們已經進行了調整:
- 雖然我們現在可以在一個代碼伺服器上運作多個環境,并且它們在代碼上是隔離的,但它們仍然共享相同的記憶體和 CPU。如果我們在一個容器上放了太多的環境,而且一個環境占用了太多的記憶體,就會對同一容器中的其他運作環境産生不利的影響;
- Docker 可以在任何作業系統上為 Linux 建構 Python 包,因為目标 Linux 作業系統和 Python 解釋器在建構過程中是可用的。pex 隻能為 Linux 建構提供輪子的包的 pex 檔案。作為退路,我們在建構過程中使用 Docker 容器來處理源碼分發。這個步驟可以在未來被移到一個單獨的共享服務中;
- 在建構 Docker 鏡像時,可以進行深度定制,例如,你可以指定一個自定義的基礎鏡像,而不是預設的 python :*-slim 鏡像之一。為了實作功能上的平等,我們必須實施一種方法,讓使用者指定他們自己的基礎 Docker 鏡像,我們在快速部署時使用這種鏡像。
GitHub 工作流程和 pex
很多人可能已經注意到,原圖中,過去基于 Docker 的下載下傳操作需要10秒左右。那麼我們是如何完全消除這個步驟的呢?
以前我們把 GitHub Action 代碼打包成 Docker 鏡像,然後使用 Docker 容器操作。而現在,我們把動作代碼打包成一個 pex 檔案,将其檢入動作倉庫,直接在 GitHub 運作器上運作。這就省去了下載下傳和啟動 Docker 動作鏡像的時間,同時仍然允許我們打包所有的依賴項。
我們做的另一個小優化是,隻使用一個 GitHub 工作流作業。在 GitHub 中的每一個工作啟動都需要10秒鐘來配置一個新的運作器。
結論
将部署時間從 3 分鐘以上減少到 40 秒,是一個顯著的加速,我們對這個結果非常滿意,特别是當測試自己的服務時。使用 pex 使我們能夠在 Docker 之上建立一個可重複的、一緻的環境,我們很高興使用這個 pex-on-docker 組合來探索其他的可能性。