最近在寫 E2E 測試遇到一個問題,因 E2E 專案中,除了專案本身的 Docker Image 需要 Build 之外,還有多個測試環境的 Image 也要 Build,這造成了我在這個專案上需要創建多個 Dockerfile
發生了什麼問題? 我一開始的錯誤處理方式
菜鳥時期的我,以為 Dockerfile 就是一定得命名為"Dockerfile",這導致了我沒辦法在專案根目錄下創建三個 Dockerfile,因為會導致命名衝突
那我想出了什麼處理方式?相當簡單,很菜的我,一開始便自然地根據不同環境在專案下創建了不同的目錄,然後在目錄底下存放各自的 Dockerfile 就很類似這種感覺:
E2Eproject/
├── testenvironment1/
│ └── Dockerfile
├── testenvironment2/
│ └── Dockerfile
├── requirements.txt
└── Dockerfile
接著便接著發生下一個問題 — 錯誤的建構上下文
以其中一個測試環境內的 Dockerfile 為範例,當時我的寫法如下,請特別注意 COPY ../ /usr/src/app/
這行
# Use the official Python base image from the DockerHub.
FROM python:3.12
# Set the working directory within the container.
WORKDIR /usr/src/app
# Set the PYTHONPATH environment variable. This is where Python looks for modules.
# It's set to the work directory to allow local modules to be found.
ENV PYTHONPATH /usr/src/app
# Copy the contents of the current host directory into the container's work directory.
COPY ../ /usr/src/app/
# Install the project dependencies specified in the requirements.txt file.
# The --no-cache-dir option is used to disable the cache and reduce the layer size.
RUN pip install --no-cache-dir -r ../requirements.txt
# 略 ...
RUN chmod +x /usr/src/app/start-tests.sh
# The command to run the application.
CMD ["sh", "/usr/src/app/start-headless-tests.sh"]
注意:本文的 Dockerfile 例子主要用於展示建構上下文的概念。在實際開發中,建議採用層級快取(layer caching)的最佳實踐,也就是先複製並安裝 requirements.txt,再複製其他檔案。有關 Build cache 的資訊請查閱 Docker 官方文檔。
會注意到,我使用了 ../
,會這麼寫是因為專案根目錄(或是說打包所需的檔案)都在此 Dockerfile 所處目錄的上一層
接著就是很自然的輸入下方指令,然後就出現 ERROR 了:
$ cd testenvironment1
$ docker build -t e2e-project-testenv:v1 .
Dockerfile:16
--------------------
14 | # Install the project dependencies specified in the requirements.txt file.
15 | # The --no-cache-dir option is used to disable the cache and reduce the layer size.
16 | >>> RUN pip install --no-cache-dir -r ../requirements.txt
17 |
18 | # Install additional dependencies for HTML report generation
--------------------
ERROR: failed to solve: process "/bin/sh -c pip install --no-cache-dir -r ../requirements.txt" did not complete successfully: exit code: 1
Service 'E2E-project' failed to build : Build failed
TL;DR
-
Docker 建構上下文就是告訴 Docker 從哪個目錄開始打包檔案,例如:你在執行指令
docker built .
這個 “.” 就是建構上下文,Docker 會將這個目錄及其子目錄下的所有檔案作為建構上下文打包成 tar 檔案 -
Dockerfile 與 build image 的上下文目錄不必強關聯在一起
-
我們在指令中指定一個目錄作為上下文,然後也透過
-f
參數指定使用哪個建構檔案,並且名稱可以自己任意命名docker build -t e2e-project-testenv:v1 -f testenvironment1/Dockerfile /myapp
先來了解什麼是 Docker 建構上下文
什麼是 Docker 建構上下文(build context)
首先,讓我們簡單回顧一下 Docker 的基本概念。Docker 允許您打包應用程式和所需環境到一個稱為 “鏡像”(image) 的容器中。這個鏡像可以在任何安裝了 Docker 的系統上運行。
-
Docker 建構上下文的概念:
- 當您使用
docker build
命令建立 Docker 鏡像時,Docker 會將指定路徑下的檔案和目錄打包成一個 tar 檔案。 - 這個 tar 檔案被稱為 “建構上下文”(build context)。
- 當您使用
-
為什麼需要建構上下文:
- Docker 的鏡像是在 Docker 伺服器(通常是遠端伺服器)上建構的。
- 為了建構鏡像,Docker 伺服器需要訪問到所有必要的檔案,比如原始碼、配置檔案等。
- 因此,客戶端(您的電腦)會把這些檔案打包成 tar 檔案,然後上傳給伺服器。
-
Dockerfile 和建構上下文:
- Dockerfile 是一個包含了建構鏡像所需步驟的文本檔案。
- 在 Dockerfile 中,您可以引用建構上下文中的檔案。比如,您可以複製建構上下文中的檔案到鏡像裡,或者執行建構上下文中的腳本。
總結來說,Docker 建構上下文是 Docker 客戶端將建構鏡像所需的檔案打包並傳輸給 Docker 伺服器的過程。這確保了 Docker 伺服器有所有必要的檔案來建構鏡像。
為何我一開始的處理方式會出現 ERROR?
關鍵就出在:
- 當我們執行
docker build -t e2e-project-testenv:v1 .
,Docker 客戶端會先將後面的指定路徑(.) 打包成一個 tar 檔案,傳送給 Docker 伺服器端,接著才會根據 Dockerfile 中所定義的腳本進行構建
什麼意思呢?
- 我當時執行指令
cd testenvironment1
接著執行docker build -t e2e-project-testenv:v1 .
- 事實上就是把 testenvironment1 這個目錄以及其子目錄下的所有檔案打包好,傳送至 Docker Daemon Docker 在 Build 的時候只能取用上下文的檔案,requirements.txt 位於這個目錄的上層(即 E2Eproject 目錄中),因此它不會被包含在建構上下文中,也就無法在 Dockerfile 中被訪問。
更優雅的處理方式
如果建構鏡像時沒有明確指定 Dockerfile,那麼 Docker Client 默認在建構鏡像時指定的上下文路徑下找名字為 Dockerfile 的建構檔案
但事實上,Dockerfile 與 build image 的上下文目錄不必強關聯在一起
我們在指令中指定一個目錄作為上下文
然後也透過 -f
參數指定使用哪個建構檔案
並且名稱可以自己任意命名!
並且名稱可以自己任意命名!!
並且名稱可以自己任意命名!!!
這完完全全解惑了我當初菜鳥所以為的「 Dockerfile 就是一定得命名為"Dockerfile"」
例如:
$ cd E2Eproject
$ docker build -t e2e-project-testenv:v1 -f testenvironment1/Dockerfile .