淺談 Dockerfile 使用方法及最佳實踐
伺服器Docker 是一個容器化平台,能夠協助開發人員在不同環境中輕鬆部署和執行應用程式。 其中,Dockerfile 是一個關鍵的工具,它是一個文本檔案,包含一系列指令,用於定義 Docker 映像檔的建立流程。 Dockerfile 包括了從基本映像檔開始,逐步添加應用程式、相依套件、環境變數及相關設定,使您能夠建立一個高度客製化的容器環境,同時確保應用程式在不同的環境中能保持一致性。
Dockerfile 的基本結構
一個簡單的 Dockerfile 通常包含了以下結構:
# 使用一個映像檔作為基礎
FROM base_image
# 作者資訊
LABEL maintainer="email"
# 在容器中安裝相依套件
RUN apt-get update \
&& apt-get install -y torch torchvision torchaudio \
&& apt-get clean
# 複製程式碼到容器中
COPY app /app
# 定義容器運行時的命令
CMD python server.py
請注意,如果您再 dockerfile 安裝套件時,發現找不到要安裝的套件,可以先執行 apt-get update 更新一下套件資訊再安裝。
Dockerfile 常用指令
本節介紹常用的 Dockerfile 指令,並且過濾不建議使用或不常使用的指令,如需了解完整的指令說明,請參考下方官方鏈結。
FROM
指定基礎映像檔。
格式:
FROM [--platform=<platform>] <image> [AS <name>]
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]
LABEL
加入 meta 資料,通常是一些映像檔資訊,與建構映像檔沒有什麼太大關係。
格式:
LABEL <key>=<value> <key>=<value> <key>=<value> ...
範例:
# 請務必使用雙引號,不要使用單引號
LABEL version="1.0" description="test"
RUN
在 docker build 時執行的指令。
格式:
# Shell Form
RUN <command>
# Exec Form (官方推薦)
RUN ["executable", "param1", "param2"]
範例:
# 以上兩個範例執行同樣的任務
RUN ["wget", "https://domain.com/file/"]
RUN wget https://domain.com/file/
CMD
在 docker run 時執行的指令,在 Dockerfile 檔案中只能有一個 CMD 指令,如果有多個 CMD 指令,僅最後一個會被執行。
格式:
# Shell Form
CMD command param1 param2 (shell form)
# Exec Form (官方推薦)
CMD ["executable","param1","param2"]
# 作為 ENTRYPOINT 的預設參數
CMD ["param1","param2"]
範例:
# 以上兩個範例執行同樣的任務
CMD ["python", "server.py"]
CMD python server.py
ENTRYPOINT
在 docker run 時執行的指令,與 CMD 類似,通常兩者會同時使用讓容器傳遞參數。
格式:
# Shell Form
ENTRYPOINT command param1 param2
# Exec Form (官方推薦)
ENTRYPOINT ["executable", "param1", "param2"]
範例:
# Dockerfile
ENTRYPOINT ["ls"]
CMD ["-a"]
# [正常執行]
# 執行指令: docker run image
# 組合後的指令:ls -a
# [取代 CMD]
# 執行指令: docker run image -al
# 組合後的指令:ls -al
# 將完全取代 CMD 的內容
# [取代 ENTRYPOINT]
# 執行指令: docker run --entrypoint ps image
# 組合後的指令:ps
# 將完全取代 ENTRYPOINT 的內容
# [取代 ENTRYPOINT 及 CMD]
# 執行指令: docker run --entrypoint ps image aux
# 組合後的指令:ps aux
# 將完全取代 ENTRYPOINT 及 CMD 的內容
請注意使用 --entrypoint 設定,CMD 的參數就會消失。 此外,--entrypoint 後面只能接單一指令,不能有參數,但 CMD 後面可以接多個參數,請參考下方說明:
# 正確
docker run --entrypoint ps image
docker run --entrypoint="ps" image
# 錯誤
docker run --entrypoint ps aux image
docker run --entrypoint "ps aux" image
docker run --entrypoint="ps aux" image
# 正確
docker run image -a -l
# 錯誤
docker run image "-a -l"
EXPOSE
指定在容器內監聽的 Port。
格式:
EXPOSE <port> [<port>/<protocol>...]
範例:
EXPOSE 80/tcp 443/tcp
在 docker run 時,可以使用 -p 綁定對應的 Port,如下所示:
# 表示電腦的 8080 Port 對應到容器的 80 Port
docker run -p 8080:80 image
# 自動使用與容器相同的 Port 對應
docker run -P image
VOLUME
指定在容器中要掛載的目錄。
格式:
VOLUME ["folder1","folder2"]
範例:
VOLUME ["/data","/tmp"]
在 docker run 時,可以使用 -v 對應,如下所示:
# 表示電腦的 /root/data 目錄對應到容器 /data 目錄
# rw 表示權限可讀可寫
docker run -v /root/data:/data:rw image
COPY
將文件或目錄複製到映像檔中,與 ADD 指令類似,但 ADD 指令除了可以複製檔案,還可以下載遠端資料至映像檔中。 不過官方不建議使用 ADD 指令,如果您要複製檔案請用 COPY 指令,如果你要下載遠端資料請用 RUN wget URL。
格式:
COPY [--chown=<user>:<group>] [--chmod=<perms>] <src>... <dest>
COPY [--chown=<user>:<group>] [--chmod=<perms>] ["<src>",... "<dest>"]
範例:
# 將電腦 /src 目錄中檔案,複製到容器根目錄
COPY /src /
ENV
設定環境變數,該變數在後續 Dockerfile 及容器中都有效。
格式:
ENV <key>=<value> ...
範例:
ENV CUDA_VERSION="12.2" NVIDIA_DRIVER="nvidia-driver-535"
# 使用環境變數
RUN apt-get update && apt-get install $NVIDIA_DRIVER -y
# 持久性的環境變數有時會產生意想不到的問題
# 如果僅在 RUN 過程所需的環境變數,可以參考以下指令
# DEBIAN_FRONTEND 環境變數僅在這行指令有效
RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y ...
使用 Shell Form 指令會自動置換環境變數的值,但 Exec Form 指令則不會,請參考下方說明。
ENV HOME="/home"
# Shell Form 將置換 $HOME 變數
# 以下指令將輸出 /home
CMD echo $HOME
# Exec Form 將不會置換環境變數
# 以下指令將輸出 $HOME
CMD ["echo", "$HOME"]
# 讓 Exec Form 置換環境變數需執行 shell
# 這種模式與 Shell Form 同形式
# 以下指令將輸出 /home
CMD ["/bin/sh", "-c", "echo $HOME"]
ARG
定義在建構映像檔時所使用的變數,與 docker build --build-arg 搭配使用。
格式:
ARG <name>[=<default value>]
範例:
ARG HTTP_PROXY
ARG FTP_PROXY
在建構映像檔時,可以使用 --build-arg 傳遞參數。
docker build \
--build-arg HTTP_PROXY=http://127.0.0.1:1234 \
--build-arg FTP_PROXY=http://127.0.0.1:4567 \
image
WORKDIR
指定工作路徑,類似在 command line 的 cd 指令,如果在 Dockerfile 沒有指定 WORKDIR,則預設為 / 根目錄。
格式:
WORKDIR folder1
範例:
WORKDIR /
# WORKDIR 可以接續使用
# 以下指令最終輸出 /data/tmp
WORKDIR /
WORKDIR data
WORKDIR tmp
RUN pwd
Dockerfile 快取
Dockerfile 中的每個指令都會建立一個分層,且每層執行過後都會產生快取,當重新建構映像檔時,只要資料未經修改就能重複使用這些快取層。
舉了例子:
FROM pytorch/pytorch:latest
COPY . /
RUN pip install --no-cache-dir -r requirements.txt
CMD python api.py
使用以下指令建構映像檔。
docker build . -t test_image
第一次建構
不會有任何問題,每一個指令都會順利執行並產生快取。
第二次建構
當我們修改了一些程式碼並重新建構時,由於第 2 行複製的檔案已經改變了,因此從第 2 行開始,包含後面的指令都需重新執行,無法使用快取。
這會有什麼問題?
由於安裝套件通常需要花費較久的時間,且我們只是修改了程式碼,並沒有異動相依套件,但缺需要重新安裝套件。
該如何改善這個問題呢?
您可以將 requirements.txt 與 python 程式碼分開複製,請參考下方範例,這樣再重新建構時,第 1 ~ 3 行就可以使用快取,也就不用重新安裝套件了。
FROM pytorch/pytorch:latest
COPY requirements.txt /
RUN pip install --no-cache-dir -r requirements.txt
COPY . /
CMD python api.py
建構映像檔與執行容器
當 Dockerfile 編寫完成之後,可以使用以下指令建構映像檔。
# 建構映像檔
docker build .
# 建構映像檔並指定映像檔名稱
docker build . -t [指定映像檔名稱]
# 建構映像檔並指定映像檔名稱,不使用快取
docker build . --no-cache -t [指定映像檔名稱]
建構好映像檔之後,建議實際運行容器並檢查應用程式是否正確運行。
docker run --name [自訂容器名稱] [映像檔名稱]
Dockerfile 注意事項
- 使用官方基本映像檔
- 盡量使用官方提供的映像檔作為開始,例如您的應用程式是用 Python 開發的,就使用官方提供的 Python 映像檔。 Docker Hub 針對官方映像檔,會標示 Docker Official Image 文字。
- 減少映像檔分層
-
Dockerfile 的每個指令都會建立一個分層,過多的分層會增加映像檔的大小和建構時間。
因此,盡量將多個指令組合成一個,並使用 && 鏈接多個指令,以減少層數。例如:
# 建議做法 RUN apt-get update && apt-get install -y torch torchvision torchaudio && apt-get clean # 不建議做法 RUN apt-get update RUN apt-get install -y torch torchvision torchaudio RUN apt-get clean
- 使用 .dockerignore 文件
-
有時候您可能不希望某些檔案或目錄複製到容器中,例如:node_modules。
此時,您可以在程式碼根目錄中建立 .dockerignore 檔案,在檔案中條列不需要複製到容器中的檔案和目錄。
.dockerignore 的用法與 .gitignore 類似,參考下方說明:
# 我是註解 # 排除下一層目錄中副檔名為 .tmp 的檔案 */*.tmp # 排除下兩層目錄中副檔名為 .tmp 的檔案 */*/*.tmp # 排除所有目錄中副檔名為 .tmp 的檔案 **/*.tmp # 排除副檔名為 .md 的檔案 # README.md 除外 (也就是說 README.md 還是會被複製到容器中) *.md !README.md
- 清理不需要的文件
- 在 Dockerfile 中,使用 RUN 安裝相依套件或編譯套件,可以在安裝完成後清理不需要的檔案,以減小映像檔的大小。
0 則留言