您還記得那些日子,當您編寫出色的軟體,但無法將其安裝在其他人的計算機上,或剛安裝就崩潰? 雖然這從來都不是很好的體驗,但我們總是可以說
現在,由於容器化,這已不再是藉口。
簡而言之,通過容器化,您可以將應用程式和所有必要的依賴項打包到映象中。 執行時,您將該映象作為容器執行。 這樣一來,您就不必為弄亂他人的系統而執行您的軟體。 如果容器在您的計算機上執行,則您的軟體應隨即執行。 這對於資料科學家在部署依賴於不同軟體包和版本的模型時也很有用。 對我來說,資料科學家必須知道如何建立映象和容器。
眾所周知,Docker是該領域的主要參與者,並且Docker映象無處不在。 這很棒,因為您可以輕鬆地並排啟動不同版本的資料庫。 將您的應用程式映象合併在一起也非常簡單。 這是由於大量的基本映象和簡單的定義語言所致。 但是,當您在不知道自己在做什麼的情況下將映象混合在一起時,就會遇到兩個問題。
· 由於映象不必要地發胖,您浪費了磁碟空間。· 您等待太長時間的構建會浪費時間。在本文中,我想向您展示如何緩解這兩個問題。 幸運的是,這僅需要您了解Docker提供的一些技巧和技術。 為了使本教程有趣且有用,我向您展示了如何將Python App打包到Docker映像中。
你準備好了嗎? 讓我們繼續。
教程假設我們所有的程式碼都位於一個Python檔案main.py中。 因為我們很酷,所以我們使用最新最好的Python版本,在撰寫本文時為3.8。 我們的應用程式只是一個簡單的Web伺服器,它依賴於Pandas,fastapi和uvicorn。 我們將依賴項儲存在requirements.txt檔案中。 在本地,我們在虛擬環境中開發應用程式。 該環境位於與程式碼位於同一資料夾中的名為.venv的資料夾中(這很快變得很重要)。 現在,我們決定將所有內容打包到Docker映像中。 為此,我們要做的就是
· 使用可用於Python 3.8的基本映像。· 複製程式碼和需求檔案。· 在映像中安裝需求和依賴項。· 公開執行我們的應用程式的命令我們的Docker映像的第一個版本看起來像
FROM python:3.8.0-slimCOPY . /appRUN apt-get update \\&& apt-get install gcc -y \\&& apt-get cleanWORKDIR appRUN pip install --user -r requirements.txtENTRYPOINT uvicorn main:app --reload --host 0.0.0.0 --port 8080
除了我們的程式碼和要求之外,我們還需要安裝GCC,因為FastApi在安裝時需要這樣做。 我們通過以下方式建立我們的形象
docker build -t my-app:v1 .
該映像的大小約為683 MB,需要大約一分鐘的時間來構建(不下載基礎映像)。 讓我們看看這種情況如何減少時間。
基本映象關於基本映象,我已經使用Python slim做出了明智的選擇。 我為什麼要選擇那個呢?
例如,我可以使用完整的Ubuntu或CentOS映像,這將導致映像大小> 1GB。 但是,由於我只需要Python,因此無需安裝所有元件。
在影象大小的低端,我們可以使用python:3.8.0-alpine。 但是,我的程式碼依賴於Pandas,這在高山上安裝很麻煩。 高山也有關於穩定性和安全性的問題。 此外,slim僅比alpine大80MB,這還是可以的。 有關如何選擇最佳Python影象的更多資訊,請向有興趣的讀者介紹該文章。
構建上下文構建映象時,列印到控制檯的第一行顯示:將構建上下文傳送到Docker守護程式。 在我的計算機上,這花費了大約5秒鐘,並且傳送了154 MB。 這是怎麼回事 Docker將構建上下文中的所有檔案和資料夾複製到守護程式。 在這裡,構建上下文是儲存Dockerfile的目錄。由於我們只需要兩個文字檔案,所以154 MB聽起來很多,不是嗎? 這樣做的原因是Docker複製了所有內容,例如包含虛擬環境的.venv資料夾或.git資料夾。
要解決此問題,您只需在Dockerfile旁邊新增一個名為.dockerignore的檔案。 在此檔案中,逐行列出了Docker不應該複製的內容。 就像git對.gitignore檔案所做的一樣。 舉一個小例子,假設我們的資料夾中有幾個Excel檔案和PNG,我們不想複製它們。 .dockerignore檔案看起來像
*.xlsx*.pngvenv.venv.git
在我們的示例中,新增此檔案後,"將構建上下文傳送到docker"僅花費幾毫秒,並且僅傳送了7.2 kb。 我將映像大小從683 Mb減小到了529 Mb,這大約是以前構建上下文的大小。 真好! 新增.dockerignore檔案有助於加快構建速度並減小影象大小。
層快取如前所述,在我的計算機上構建此映像大約需要60秒鐘。 我估計大多數時候,大約有99.98%的時間用於安裝需求和依賴項。 您可能會認為現在這裡沒有太多的改進空間。 但是有些時候您必須經常構建映像! 為什麼? Docker可以利用層快取。
Docker檔案中的每一行都代表一個層。 通過新增/刪除行中的內容,或更改其引用的檔案或資料夾,可以更改圖層。 發生這種情況時,將重建此層以及其下的所有層。 否則,Docker將使用該層的快取版本。 要利用該漏洞,您應該對Dockerfile進行結構化,以便
經常不變的層應該出現在Dockerfile的開頭附近。 在這裡安裝編譯器是一個很好的例子。經常變化的層應該出現在Dockerfile的結尾附近。 複製原始碼是這裡的完美示例。酷。 理論足夠,讓我們回到我們的例子。
假設您沒有更改要求,而只更新了程式碼。 開發軟體時,這很常見。 現在,每次構建映像時,都會重新安裝這些討厭的依賴項。 建立映象始終需要相同的時間。 煩人! 我們還沒有使用快取。
神奇的新Dockerfile來解決您的問題
FROM python:3.8.0-slimRUN apt-get update \\&& apt-get install gcc -y \\&& apt-get cleanCOPY requirements.txt /app/requirements.txtWORKDIR appRUN pip install --user -r requirements.txtCOPY . /appENTRYPOINT uvicorn main:app --reload --host 0.0.0.0 --port 1234
看起來並沒有什麼魔力和不同,對吧? 我們要做的唯一一件事就是首先安裝GCC,然後分別複製需求和複製原始碼。
GCC和依賴項更改很少。 這就是為什麼此層現在顯示得很早的原因。 需求變更也比GCC緩慢但頻繁。 這就是為什麼該層位於GCC之後。 我們的原始碼經常更改。 因此,將其複製會發生得較晚。 現在,當我們更改原始碼並重建映像時,由於Docker使用快取的層,因此不會重新安裝依賴項。 現在,重建幾乎不需要時間。 太好了,因為我們可以花更多的時間來測試和執行我們的應用程式!
多階段構建在示例影象中,我們必須安裝GCC才能安裝FastApi和uvicorn。 但是,對於執行該應用程式,我們不需要編譯器。 現在想象您不僅需要GCC,還需要其他程式,例如Git,CMake,NPM或…。 您的生產形象越來越胖。
多階段構建,助我們一臂之力!
使用多階段構建,您可以在同一Dockerfile中定義各種映象。 每個映象執行不同的步驟。 您可以將從一個映象生成的檔案和工件複製到另一個映象。 最常見的情況是,您有一個用於構建應用程式的映象,另一個是用於執行應用程式的映象。 您需要做的就是將構建工件和依賴項從構建映象複製到應用程式映像。
對於我們的示例,這看起來像
# Here is the build imageFROM python:3.8.0-slim as builderRUN apt-get update \\&& apt-get install gcc -y \\&& apt-get cleanCOPY requirements.txt /app/requirements.txtWORKDIR appRUN pip install --user -r requirements.txtCOPY . /app# Here is the production imageFROM python:3.8.0-slim as appCOPY --from=builder /root/.local /root/.localCOPY --from=builder /app/main.py /app/main.pyWORKDIR appENV PATH=/root/.local/bin:$PATHENTRYPOINT uvicorn main:app --reload --host 0.0.0.0 --port 1234
構建該檔案時,最終的生產映像大小為353 MB。 這大約是我們第一個版本的大小的一半。 恭喜,還不錯。 請記住,您的生產映象越小,效果越好!
附帶說明,多階段構建還可以提高安全性。 為什麼呢? 假設您需要一個金鑰(例如SSH金鑰)來在構建時訪問某些資源。 即使您在以後的層中刪除了該機密,它仍在先前的層中存在。 這意味著有權訪問您的影象的人可以獲取該金鑰。 在多階段構建中,您僅複製必要的執行時工件。 因此,生產映象永遠都看不到金鑰,而您已經解決了該問題。
有關多階段構建的更多詳細資訊,我請讀者參考本文和本文。
總結在本文中,我向您展示了一些簡單的技巧和竅門,說明如何建立更快構建的較小的Docker映像。 記得
始終新增.dockerignore檔案。考慮一下圖層的順序,並將其從緩慢變化的動作轉變為快速變化的動作。嘗試使用和利用多階段構建。我希望這將為您節省一些磁碟空間和時間。
感謝您關注這篇文章。 與往常一樣,如有任何問題,意見或建議,請隨時與我聯絡。
(本文翻譯自Simon Hawe的文章《How to Build Slim Docker Images Fast》,參考:https://towardsdatascience.com/how-to-build-slim-docker-images-fast-ecc246d7f4a7)