最佳化構建中的快取使用
使用 Docker 構建時,如果指令及其依賴檔案自上次構建以來沒有更改,則會從構建快取中重用一個層。從快取中重用層可以加快構建過程,因為 Docker 無需再次構建該層。
以下是一些可用於最佳化構建快取和加快構建過程的技術
- 調整層順序:將 Dockerfile 中的命令按邏輯順序放置有助於避免不必要的快取失效。
- 保持上下文精簡:上下文是傳送給構建器以處理構建指令的檔案和目錄集。保持上下文儘可能小可以減少需要傳送給構建器的資料量,並降低快取失效的可能性。
- 使用繫結掛載:繫結掛載允許您將主機上的檔案或目錄掛載到構建容器中。使用繫結掛載可以幫助您避免影像中不必要的層,這可能會減慢構建過程。
- 使用快取掛載:快取掛載允許您指定在構建期間使用的持久包快取。持久快取有助於加快構建步驟,尤其是涉及使用包管理器安裝包的步驟。擁有包的持久快取意味著即使您重新構建一個層,您也只下載新的或更改的包。
- 使用外部快取:外部快取允許您將構建快取儲存在遠端位置。外部快取影像可以在多個構建之間以及不同環境之間共享。
調整層順序
將 Dockerfile 中的命令按邏輯順序放置是一個很好的開始。由於更改會導致後續步驟的重新構建,因此請嘗試將開銷較大的步驟放在 Dockerfile 的開頭附近。經常更改的步驟應放在 Dockerfile 的末尾附近,以避免觸發未更改層的重新構建。
考慮以下示例。一個 Dockerfile 片段,它從當前目錄中的原始檔執行 JavaScript 構建
# syntax=docker/dockerfile:1
FROM node
WORKDIR /app
COPY . . # Copy over all files in the current directory
RUN npm install # Install dependencies
RUN npm build # Run build
這個 Dockerfile 效率相當低下。每次構建 Docker 映象時,更新任何檔案都會導致所有依賴項重新安裝,即使依賴項自上次以來沒有更改。
相反,`COPY` 命令可以分為兩部分。首先,複製包管理檔案(在本例中為 `package.json` 和 `yarn.lock`)。然後,安裝依賴項。最後,複製專案原始碼,專案原始碼經常更改。
# syntax=docker/dockerfile:1
FROM node
WORKDIR /app
COPY package.json yarn.lock . # Copy package management files
RUN npm install # Install dependencies
COPY . . # Copy over project files
RUN npm build # Run build
透過在 Dockerfile 的較早層中安裝依賴項,當專案檔案更改時,無需重新構建這些層。
保持上下文精簡
確保您的上下文不包含不必要檔案的最簡單方法是在構建上下文的根目錄中建立 `.dockerignore` 檔案。`.dockerignore` 檔案類似於 `.gitignore` 檔案,它允許您從構建上下文中排除檔案和目錄。
這是一個 `.dockerignore` 檔案的示例,它排除了 `node_modules` 目錄以及所有以 `tmp` 開頭的檔案和目錄
node_modules
tmp*
`\.dockerignore` 檔案中指定的忽略規則適用於整個構建上下文,包括子目錄。這意味著它是一種相當粗粒度的機制,但它是排除您知道在構建上下文中不需要的檔案和目錄的好方法,例如臨時檔案、日誌檔案和構建工件。
使用繫結掛載
您可能熟悉在使用 `docker run` 或 Docker Compose 執行容器時使用的繫結掛載。繫結掛載允許您將主機上的檔案或目錄掛載到容器中。
# bind mount using the -v flag
docker run -v $(pwd):/path/in/container image-name
# bind mount using the --mount flag
docker run --mount=type=bind,src=.,dst=/path/in/container image-name
要在構建中使用繫結掛載,您可以在 Dockerfile 中使用 `RUN` 指令的 `--mount` 標誌
FROM golang:latest
WORKDIR /app
RUN --mount=type=bind,target=. go build -o /app/hello
在此示例中,當前目錄在 `go build` 命令執行之前被掛載到構建容器中。原始碼在 `RUN` 指令執行期間在構建容器中可用。當指令執行完畢後,掛載的檔案不會持久化到最終映象或構建快取中。只保留 `go build` 命令的輸出。
Dockerfile 中的 `COPY` 和 `ADD` 指令允許您將檔案從構建上下文複製到構建容器中。使用繫結掛載有利於構建快取最佳化,因為您不會向快取新增不必要的層。如果您的構建上下文較大,並且僅用於生成工件,那麼最好使用繫結掛載臨時掛載生成工件所需的原始碼到構建中。如果您使用 `COPY` 將檔案新增到構建容器中,BuildKit 將把所有這些檔案都包含在快取中,即使這些檔案未在最終映象中使用。
在構建中使用繫結掛載時需要注意以下幾點
繫結掛載預設是隻讀的。如果需要寫入掛載的目錄,則需要指定 `rw` 選項。但是,即使使用 `rw` 選項,更改也不會持久儲存在最終映象或構建快取中。檔案寫入在 `RUN` 指令執行期間持續,並在指令完成後被丟棄。
掛載的檔案不會持久儲存在最終映象中。只有 `RUN` 指令的輸出會持久儲存在最終映象中。如果需要將構建上下文中的檔案包含在最終映象中,則需要使用 `COPY` 或 `ADD` 指令。
如果目標目錄不為空,則目標目錄的內容會被掛載的檔案隱藏。原始內容在 `RUN` 指令完成後恢復。
例如,給定一個只包含 `Dockerfile` 的構建上下文
. └── Dockerfile
以及一個將當前目錄掛載到構建容器中的 Dockerfile
FROM alpine:latest WORKDIR /work RUN touch foo.txt RUN --mount=type=bind,target=. ls RUN ls
第一個帶繫結掛載的 `ls` 命令顯示掛載目錄的內容。第二個 `ls` 列出原始構建上下文的內容。
構建日誌#8 [stage-0 3/5] RUN touch foo.txt #8 DONE 0.1s #9 [stage-0 4/5] RUN --mount=target=. ls -1 #9 0.040 Dockerfile #9 DONE 0.0s #10 [stage-0 5/5] RUN ls -1 #10 0.046 foo.txt #10 DONE 0.1s
使用快取掛載
Docker 中的常規快取層對應於指令及其依賴檔案的精確匹配。如果指令及其依賴檔案自該層構建以來已更改,則該層將失效,構建過程必須重新構建該層。
快取掛載是一種指定在構建期間使用的持久快取位置的方法。快取是跨構建累積的,因此您可以多次讀取和寫入快取。這種持久快取意味著即使您需要重新構建一個層,您也只下載新的或更改的包。任何未更改的包都會從快取掛載中重用。
要在構建中使用快取掛載,您可以在 Dockerfile 中使用 `RUN` 指令的 `--mount` 標誌
FROM node:latest
WORKDIR /app
RUN --mount=type=cache,target=/root/.npm npm install
在此示例中,`npm install` 命令為 `/root/.npm` 目錄(npm 快取的預設位置)使用快取掛載。快取掛載在跨構建中持續存在,因此即使您最終重新構建該層,您也只下載新的或更改的包。對快取的任何更改都會在跨構建中持續存在,並且快取會在多個構建之間共享。
您如何指定快取掛載取決於您使用的構建工具。如果您不確定如何指定快取掛載,請參閱您使用的構建工具的文件。以下是一些示例
RUN --mount=type=cache,target=/go/pkg/mod \
go build -o /app/hello
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt update && apt-get --no-install-recommends install -y gcc
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r requirements.txt
RUN --mount=type=cache,target=/root/.gem \
bundle install
RUN --mount=type=cache,target=/app/target/ \
--mount=type=cache,target=/usr/local/cargo/git/db \
--mount=type=cache,target=/usr/local/cargo/registry/ \
cargo build
RUN --mount=type=cache,target=/root/.nuget/packages \
dotnet restore
RUN --mount=type=cache,target=/tmp/cache \
composer install
請務必閱讀您正在使用的構建工具的文件,以確保您使用正確的快取掛載選項。包管理器對如何使用快取有不同的要求,使用錯誤的選項可能會導致意外行為。例如,Apt 需要獨佔訪問其資料,因此快取使用 `sharing=locked` 選項,以確保使用相同快取掛載的並行構建相互等待,並且不會同時訪問相同的快取檔案。
使用外部快取
構建的預設快取儲存是您正在使用的構建器(BuildKit 例項)的內部儲存。每個構建器都使用自己的快取儲存。當您在不同的構建器之間切換時,快取不會在它們之間共享。使用外部快取允許您為推送和拉取快取資料定義一個遠端位置。
外部快取對於 CI/CD 管道特別有用,因為在 CI/CD 管道中,構建器通常是短暫的,並且構建時間寶貴。在構建之間重用快取可以顯著加快構建過程並降低成本。您甚至可以在本地開發環境中利用相同的快取。
要使用外部快取,請在 `docker buildx build` 命令中指定 `--cache-to` 和 `--cache-from` 選項。
- `--cache-to` 將構建快取匯出到指定位置。
- `--cache-from` 指定用於構建的遠端快取。
以下示例展示瞭如何使用 `docker/build-push-action` 設定 GitHub Actions 工作流,並將構建快取層推送到 OCI 登錄檔映象
name: ci
on:
push:
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push
uses: docker/build-push-action@v6
with:
push: true
tags: user/app:latest
cache-from: type=registry,ref=user/app:buildcache
cache-to: type=registry,ref=user/app:buildcache,mode=max
此設定告訴 BuildKit 在 `user/app:buildcache` 映象中查詢快取。當構建完成後,新的構建快取將被推送到同一個映象,覆蓋舊快取。
此快取也可以在本地使用。要在本地構建中拉取快取,您可以使用 `docker buildx build` 命令的 `--cache-from` 選項
$ docker buildx build --cache-from type=registry,ref=user/app:buildcache .
摘要
最佳化構建中的快取使用可以顯著加快構建過程。保持構建上下文精簡、使用繫結掛載、快取掛載和外部快取都是您可以用來充分利用構建快取和加快構建過程的技術。
有關本指南中討論的概念的更多資訊,請參閱