Dockerfile 的工作原理說明

什麼是 Dockerfile?

Dockerfile 是一個文本文件,其中包含有關如何構建 Docker 鏡像的說明。每個指令都由一個命令組成,後跟一個或多個參數。按照慣例,命令以大寫形式編寫,以區分它們與參數,並使 Dockerfile 更具可讀性。

以下是 Node.js 應用程序的 Dockerfile 示例:

FROM node:20.11.1
WORKDIR /app
COPY package.json /app
RUN npm install
COPY . /app
CMD ["node", "server.js"]

以下是從此 Docker 文件構建 Docker 鏡像時執行的順序任務:

  1. Docker 首先在本地緩存中查找 FROM 指令(指定的基本鏡像 node:20.11.1。如果在本地找不到它,Docker 會從 Docker Hub 獲取它。

  2. 接下來,Docker 在容器的文件系統內創建一個工作目錄,由 WORKDIR 指令(/app)指定。

  3. COPY 指令將 package.json 複製到容器中的 / app 目錄中。這對管理項目依賴性至關重要。

  4. 然後,Docker 執行 RUN npm install 命令來安裝 package.json 中定義的依賴項。

  5. 安裝依賴項後,Docker 使用另一個 COPY 指令將剩餘的項目文件複製到 / app 目錄中。

  6. 最後,CMD 指令設置了在容器(節點 server.js)內運行的默認命令,從而啓動應用程序。

常見的 Dockerfile 說明

下面,我們討論了 Dockerfile 中常用的一些最重要的命令:

有關所有可用的 Dockerfile 說明的全面指南,請參閱 Dockerfile 參考中的官方 Docker 文檔。

Dockerfile 指令和 Docker 鏡像層之間的關係

Dockerfile 中的每個指令都在 Docker 映像中創建一個新層。這些層相互堆疊,每個層代表從其下方的層所做的更改。這裏需要注意的最重要的一點是,Docker 緩存這些層以加快後續構建的速度。

作爲一般規則,任何修改文件系統(如 FROM、RUN 和 COPY)的 Dockerfile 命令都會創建一個新層。指導如何構建圖像和運行容器(如 WORKDIR、ENV 和 ENTRYPOINT)的命令將零字節大小的元數據層添加到創建的圖像中。

要查看創建圖像層的命令以及它們爲 Docker 圖像貢獻的大小,您可以運行以下命令:

docker history <IMAGE_NAME>

您還可以運行以下命令來查找圖像層的數量:

docker inspect --format '{{json .RootFS.Layers}}' <IMAGE_NAME>

在此命令中,我們使用 Go 模板來提取圖層的信息。

Dockerfile 和構建緩存

當您使用 Dockerfile 構建 Docker 映像時,Docker 會根據其構建緩存檢查每個指令(層)。如果圖層沒有更改(意味着指令及其上下文與之前的構建相同),Docker 使用緩存層,而不是再次執行指令。

讓我們看看這個在行動。以下是我們在上一節中使用 Dockerfile 構建示例節點應用程序時獲得的輸出:

從上面的截圖來看,構建過程花了 1244.2 秒。

構建另一個 Docker 映像(無需對應用程序代碼或 Dockerfile 進行任何更改),構建時間大幅減少到僅 6.9 秒,如下所示:

第二次構建的構建時間顯著減少表明了 Docker 對構建緩存的有效使用。由於 Dockerfile 指令或應用程序代碼沒有更改,Docker 使用了第一個構建的緩存層。

需要注意的還有一點是,緩存具有級聯效應。一旦指令被修改,所有後續指令,即使未改變,也將重新執行,因爲 Docker 無法再保證其結果與以前相同。

Docker 緩存機制的這一特徵對 Dockerfile 中指令的組織有重大影響。在即將到來的 Dockerfile 最佳實踐部分中,我們將學習如何戰略性地訂購 Dockerfile 說明以優化構建時間。

編寫 Dockerfiles 的最佳實踐

下面,我們討論在編寫 Dockerfiles 時應遵循的三個建議的最佳做法:

1 使用 .dockerignore 文件

編寫 Dockerfiles 時,請確保僅將應用程序所需的文件和文件夾複製到容器的文件系統。爲了幫助解決這個問題,請在與 Dockerfile 相同的目錄中創建一個. dockerignore 文件。在此文件中,列出構建和運行應用程序不需要的所有文件和目錄——類似於如何使用 a.gitignore 文件從 git 存儲庫中排除文件。

在 Docker 構建上下文中不包含無關的文件有助於保持圖像大小小。較小的圖像帶來了顯著的優勢:它們需要更少的時間和帶寬來下載,佔用更少的磁盤存儲空間,並且在加載到 Docker 容器時消耗更少的內存。

2 保持圖像層的數量相對較小

編寫 Dockerfiles 時要遵循的另一個最佳做法是儘可能減少圖像層的數量,因爲這直接影響容器的啓動時間。但是,我們如何有效地減少圖像層的數量?

一個簡單的方法是將多個 RUN 命令合併到一個命令中。

假設我們有一個 Dockerfile,其中包含以下三個單獨的命令:

RUN apt-get update
RUN apt-get install -y nginx
RUN apt-get clean

這將導致三個單獨的層。然而,通過將這些命令合併爲一個,如下所示,我們可以將層數從三個減少到一個。

RUN apt-get update && \
    apt-get install -y nginx && \
    apt-get clean

在這個版本中,我們使用 && 運算符和 \ 用於行延續。&& 運算符按順序執行命令,確保每個命令僅在前一個命令成功時運行。這種方法對於維護構建的完整性至關重要,因爲在任何命令失敗時停止構建,從而防止創建有缺陷的映像。這些 \ 有助於將長命令分解爲更易讀的段。

3 訂購 Dockerfile 說明,以儘可能多地利用緩存

我們知道,Docker 使用構建緩存來避免重建它已經構建且不包含任何明顯更改的任何圖像層。由於這種緩存策略,您在 Dockerfile 中組織指令的順序對於確定構建過程的平均持續時間非常重要。

最佳做法是放置最不可能在開始時更改的指令,以及在 Dockerfile 結束時更頻繁更改的指令。 

此策略基於 Docker 如何重建圖像:Docker 根據其緩存順序檢查每個指令。如果它遇到指令的更改,它不能將緩存用於此指令和所有後續指令。相反,Docker 從更改點開始重建每個層。

考慮下面的 Docker 文件:

FROM node:20.11.1
WORKDIR /app
COPY . /app
RUN npm install
CMD ["node", "server.js"]

它工作正常,但有一個問題。在第 3 行,我們將整個目錄(包括應用程序代碼)複製到容器中。接下來,在第 4 行,我們安裝依賴項。此設置有一個顯著的缺點:對應用程序代碼的任何修改都會導致從這一點開始緩存失效。因此,每個構建都會重新安裝依賴項。考慮到依賴性更新比應用程序代碼的更改更頻繁,這個過程不僅耗時,而且不必要。

爲了更好地利用 Docker 的緩存,我們可以調整我們的方法,最初只複製 thepackagepackage.json 文件來安裝依賴項,然後複製應用程序代碼的其餘部分:

FROM node:20.11.1
WORKDIR /app
COPY package.json /app
RUN npm install
COPY . /app
CMD ["node", "server.js"]

此修改意味着應用程序代碼的更改現在隻影響第 5 行以後的緩存。在此之前發生的依賴項安裝受益於緩存保留(除非對 package.json 進行更改),從而優化構建時間。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/vi4jmlASY-Di3MKVLdgOcg