使用 Docker Buildx Bake 精通多平臺構建、測試等功能

本指南演示瞭如何使用 Docker Buildx Bake 簡化和自動化映象構建、測試以及生成構建產物的過程。透過在宣告式的 docker-bake.hcl 檔案中定義構建配置,您可以消除手動編寫的指令碼,併為複雜的構建、測試和產物生成啟用高效的工作流程。

假設

本指南假設您熟悉以下內容

先決條件

  • 您的機器上安裝了最新版本的 Docker。
  • 您已安裝 Git 用於克隆倉庫。
  • 您正在使用 containerd 映象儲存。

簡介

本指南使用一個示例專案來演示 Docker Buildx Bake 如何簡化您的構建和測試工作流程。該倉庫包含一個 Dockerfile 和一個 docker-bake.hcl 檔案,為您提供了一個即用型的設定來嘗試 Bake 命令。

首先克隆示例倉庫

git clone https://github.com/dvdksn/bakeme.git
cd bakeme

Bake 檔案 docker-bake.hcl 使用目標(targets)和組(groups),以宣告式語法定義構建目標,使您能夠高效地管理複雜的構建。

以下是 Bake 檔案開箱即用的樣子

target "default" {
  target = "image"
  tags = [
    "bakeme:latest",
  ]
  attest = [
    "type=provenance,mode=max",
    "type=sbom",
  ]
  platforms = [
    "linux/amd64",
    "linux/arm64",
    "linux/riscv64",
  ]
}

target 關鍵字為 Bake 定義了一個構建目標。default 目標定義了在命令列未指定特定目標時要構建的目標。以下是 default 目標的選項快速摘要

  • target:Dockerfile 中的目標構建階段。

  • tags:分配給映象的標籤。

  • attest:附加到映象的證明

    提示

    證明提供了元資料,如構建來源(provenance),用於追蹤映象構建的來源,以及 SBOM(軟體物料清單),可用於安全審計和合規性檢查。

  • platforms:要構建的平臺變體。

要執行此構建,只需在倉庫的根目錄執行以下命令

$ docker buildx bake

使用 Bake,您可以避免冗長且難以記住的命令列呼叫,透過用結構化的配置檔案替換手動的、易出錯的指令碼,來簡化構建配置管理。

作為對比,以下是未使用 Bake 的構建命令的樣子

$ docker buildx build \
  --target=image \
  --tag=bakeme:latest \
  --provenance=true \
  --sbom=true \
  --platform=linux/amd64,linux/arm64,linux/riscv64 \
  .

測試和程式碼檢查

Bake 不僅用於定義構建配置和執行構建。您還可以使用 Bake 來執行測試,有效地將 BuildKit 用作任務執行器。在容器中執行測試非常適合確保可復現的結果。本節將展示如何新增兩種型別的測試

  • 使用 go test 進行單元測試。
  • 使用 golangci-lint 進行程式碼風格違規檢查。

以測試驅動開發(TDD)的方式,首先向 Bake 檔案新增一個新的 test 目標

target "test" {
  target = "test"
  output = ["type=cacheonly"]
}
提示

使用 type=cacheonly 可確保構建輸出被有效地丟棄;層被儲存到 BuildKit 的快取中,但 Buildx 不會嘗試將結果載入到 Docker Engine 的映象儲存中。

對於測試執行,您不需要匯出構建輸出——只有測試執行才重要。

要執行此 Bake 目標,請執行 docker buildx bake test。此時,您將收到一個錯誤,指示 Dockerfile 中不存在 test 階段。

$ docker buildx bake test
[+] Building 1.2s (6/6) FINISHED
 => [internal] load local bake definitions
...
ERROR: failed to solve: target stage "test" could not be found

要滿足此目標,請新增相應的 Dockerfile 目標。這裡的 test 階段基於與構建階段相同的基礎階段。

FROM base AS test
RUN --mount=target=. \
    --mount=type=cache,target=/go/pkg/mod \
    go test .
提示

--mount=type=cache 指令可以在構建之間快取 Go 模組,透過避免重新下載依賴項來提高構建效能。這個共享快取確保了相同的依賴集在構建、測試和其他階段都可用。

現在,使用 Bake 執行 test 目標將評估此專案的單元測試。如果您想驗證它是否有效,可以對 main_test.go 進行任意更改以導致測試失敗。

接下來,要啟用程式碼檢查,請向 Bake 檔案新增另一個名為 lint 的目標

target "lint" {
  target = "lint"
  output = ["type=cacheonly"]
}

然後在 Dockerfile 中,新增構建階段。此階段將使用 Docker Hub 上的官方 golangci-lint 映象。

提示

因為此階段依賴於執行外部依賴項,通常最好將要使用的版本定義為構建引數。這使您將來可以透過將依賴項版本集中放置在 Dockerfile 的開頭來更輕鬆地管理版本升級。

ARG GO_VERSION="1.23"
ARG GOLANGCI_LINT_VERSION="1.61"

#...

FROM golangci/golangci-lint:v${GOLANGCI_LINT_VERSION}-alpine AS lint
RUN --mount=target=.,rw \
    golangci-lint run

最後,要啟用同時執行兩個測試,您可以在 Bake 檔案中使用 groups 結構。一個組可以指定透過單次呼叫執行多個目標。

group "validate" {
  targets = ["test", "lint"]
}

現在,執行兩個測試就像這樣簡單

$ docker buildx bake validate

構建變體

有時您需要構建一個程式的多個版本。以下示例使用 Bake 來構建程式的單獨的“釋出”和“除錯”變體,使用矩陣。使用矩陣可以讓您並行執行具有不同配置的構建,從而節省時間並確保一致性。

矩陣將單個構建擴充套件為多個構建,每個構建代表矩陣引數的唯一組合。這意味著您可以透過最少的配置更改,來協調 Bake 並行構建程式的生產和開發版本。

本指南的示例專案已設定為使用構建時選項來有條件地啟用除錯日誌和跟蹤功能。

  • 如果您使用 go build -tags="debug" 編譯程式,則會啟用額外的日誌和跟蹤功能(開發模式)。
  • 如果您在沒有 debug 標籤的情況下構建,程式將使用預設記錄器進行編譯(生產模式)。

透過新增一個定義要構建的變數組合的矩陣屬性來更新 Bake 檔案

docker-bake.hcl
 target "default" {
+  matrix = {
+    mode = ["release", "debug"]
+  }
+  name = "image-${mode}"
   target = "image"

matrix 屬性定義了要構建的變體(“release” 和 “debug”)。name 屬性定義了矩陣如何擴充套件為多個不同的構建目標。在這種情況下,矩陣屬性將構建擴充套件為兩個工作流:image-releaseimage-debug,每個工作流使用不同的配置引數。

接下來,定義一個名為 BUILD_TAGS 的構建引數,它接受矩陣變數的值。

docker-bake.hcl
   target = "image"
+  args = {
+    BUILD_TAGS = mode
+  }
   tags = [

您還需要更改為這些構建分配映象標籤的方式。目前,兩個矩陣路徑都會生成相同的映象標籤名稱,並會相互覆蓋。更新 tags 屬性,使用條件運算子根據矩陣變數值設定標籤。

docker-bake.hcl
   tags = [
-    "bakeme:latest",
+    mode == "release" ? "bakeme:latest" : "bakeme:dev"
   ]
  • 如果 moderelease,標籤名稱是 bakeme:latest
  • 如果 modedebug,標籤名稱是 bakeme:dev

最後,更新 Dockerfile 以在編譯階段使用 BUILD_TAGS 引數。當 -tags="${BUILD_TAGS}" 選項評估為 -tags="debug" 時,編譯器將使用 debug.go 檔案中的 configureLogging 函式。

Dockerfile
 # build compiles the program
 FROM base AS build
-ARG TARGETOS TARGETARCH
+ARG TARGETOS TARGETARCH BUILD_TAGS
 ENV GOOS=$TARGETOS
 ENV GOARCH=$TARGETARCH
 RUN --mount=target=. \
        --mount=type=cache,target=/go/pkg/mod \
-       go build -o "/usr/bin/bakeme" .
+       go build -tags="${BUILD_TAGS}" -o "/usr/bin/bakeme" .

就是這些了。有了這些更改,您的 docker buildx bake 命令現在可以構建兩個多平臺映象變體。您可以使用 docker buildx bake --print 命令來檢查 Bake 生成的規範構建配置。執行此命令顯示 Bake 將執行一個包含兩個目標的 default 組,這兩個目標具有不同的構建引數和映象標籤。

{
  "group": {
    "default": {
      "targets": ["image-release", "image-debug"]
    }
  },
  "target": {
    "image-debug": {
      "attest": ["type=provenance,mode=max", "type=sbom"],
      "context": ".",
      "dockerfile": "Dockerfile",
      "args": {
        "BUILD_TAGS": "debug"
      },
      "tags": ["bakeme:dev"],
      "target": "image",
      "platforms": ["linux/amd64", "linux/arm64", "linux/riscv64"]
    },
    "image-release": {
      "attest": ["type=provenance,mode=max", "type=sbom"],
      "context": ".",
      "dockerfile": "Dockerfile",
      "args": {
        "BUILD_TAGS": "release"
      },
      "tags": ["bakeme:latest"],
      "target": "image",
      "platforms": ["linux/amd64", "linux/arm64", "linux/riscv64"]
    }
  }
}

考慮到所有的平臺變體,這意味著構建配置會生成 6 個不同的映象。

$ docker buildx bake
$ docker image ls --tree

IMAGE                   ID             DISK USAGE   CONTENT SIZE   USED
bakeme:dev              f7cb5c08beac       49.3MB         28.9MB
├─ linux/riscv64        0eae8ba0367a       9.18MB         9.18MB
├─ linux/arm64          56561051c49a         30MB         9.89MB
└─ linux/amd64          e8ca65079c1f        9.8MB          9.8MB

bakeme:latest           20065d2c4d22       44.4MB         25.9MB
├─ linux/riscv64        7cc82872695f       8.21MB         8.21MB
├─ linux/arm64          e42220c2b7a3       27.1MB         8.93MB
└─ linux/amd64          af5b2dd64fde       8.78MB         8.78MB

匯出構建產物

匯出像二進位制檔案這樣的構建產物,對於部署到沒有 Docker 或 Kubernetes 的環境非常有用。例如,如果您的程式要在使用者的本地機器上執行。

提示

本節討論的技術不僅可以應用於像二進位制檔案這樣的構建輸出,還可以應用於任何型別的產物,例如測試報告。

對於像 Go 和 Rust 這樣編譯後的二進位制檔案通常是可移植的程式語言,為僅匯出二進位制檔案建立備用構建目標非常簡單。您所需要做的就是在 Dockerfile 中新增一個空階段,其中只包含您想要匯出的二進位制檔案。

首先,讓我們新增一種快速構建本地平臺二進位制檔案並將其匯出到本地檔案系統的 ./build/local 的方法。

docker-bake.hcl 檔案中,建立一個新的 bin 目標。在此階段中,將 output 屬性設定為本地檔案系統路徑。Buildx 會自動檢測到輸出看起來像檔案路徑,並使用本地匯出器將結果匯出到指定路徑。

target "bin" {
  target = "bin"
  output = ["build/bin"]
  platforms = ["local"]
}

請注意,此階段指定了一個 local 平臺。預設情況下,如果未指定 platforms,構建將以 BuildKit 主機的作業系統和架構為目標。如果您使用 Docker Desktop,這通常意味著構建目標是 linux/amd64linux/arm64,即使您的本地機器是 macOS 或 Windows,因為 Docker 在 Linux VM 中執行。使用 local 平臺會強制目標平臺與您的本地環境相匹配。

接下來,將 bin 階段新增到 Dockerfile,它會從構建階段複製已編譯的二進位制檔案。

FROM scratch AS bin
COPY --from=build "/usr/bin/bakeme" /

現在,您可以使用 docker buildx bake bin 匯出您的本地平臺版本的二進位制檔案。例如,在 macOS 上,此構建目標會生成一個Mach-O 格式的可執行檔案——這是 macOS 的標準可執行檔案格式。

$ docker buildx bake bin
$ file ./build/bin/bakeme
./build/bin/bakeme: Mach-O 64-bit executable arm64

接下來,讓我們新增一個目標來構建程式的所有平臺變體。為此,您可以繼承剛剛建立的 bin 目標,並透過新增所需的平臺來擴充套件它。

target "bin-cross" {
  inherits = ["bin"]
  platforms = [
    "linux/amd64",
    "linux/arm64",
    "linux/riscv64",
  ]
}

現在,構建 bin-cross 目標會為所有平臺建立二進位制檔案。每個變體都會自動建立子目錄。

$ docker buildx bake bin-cross
$ tree build/
build/
└── bin
    ├── bakeme
    ├── linux_amd64
    │   └── bakeme
    ├── linux_arm64
    │   └── bakeme
    └── linux_riscv64
        └── bakeme

5 directories, 4 files

為了同時生成“釋出”和“除錯”變體,您可以像處理預設目標時一樣使用矩陣。當使用矩陣時,您還需要根據矩陣值來區分輸出目錄,否則每次矩陣執行都會將二進位制檔案寫入相同的位置。

target "bin-all" {
  inherits = ["bin-cross"]
  matrix = {
    mode = ["release", "debug"]
  }
  name = "bin-${mode}"
  args = {
    BUILD_TAGS = mode
  }
  output = ["build/bin/${mode}"]
}
$ rm -r ./build/
$ docker buildx bake bin-all
$ tree build/
build/
└── bin
    ├── debug
    │   ├── linux_amd64
    │   │   └── bakeme
    │   ├── linux_arm64
    │   │   └── bakeme
    │   └── linux_riscv64
    │       └── bakeme
    └── release
        ├── linux_amd64
        │   └── bakeme
        ├── linux_arm64
        │   └── bakeme
        └── linux_riscv64
            └── bakeme

10 directories, 6 files

結論

Docker Buildx Bake 簡化了複雜的構建工作流程,實現了高效的多平臺構建、測試和產物匯出。透過將 Buildx Bake 整合到您的專案中,您可以簡化 Docker 構建,使您的構建配置具有可移植性,並更輕鬆地處理複雜的配置。

嘗試不同的配置,並擴充套件您的 Bake 檔案以適應您專案的需求。您可以考慮將 Bake 整合到您的 CI/CD 管道中,以自動化構建、測試和產物部署。Buildx Bake 的靈活性和強大功能可以顯著改善您的開發和部署流程。

進一步閱讀

有關如何使用 Bake 的更多資訊,請檢視以下資源