譯者 | 李睿
審校 | 重樓
雖然使用傳統的docker build命令構建Docker鏡像的方法簡單直接,但在處理由多個組件組成的復雜應用程序時,這個過程可能會變得繁瑣且容易出錯。這就是Docker Bake發揮重要作用的地方——它是一個強大而靈活的工具,用于組織多階段和并行的鏡像構建。
本文將介紹Docker Bake的功能、其相對于傳統方法的優勢,以及在各種開發場景中使用Docker Bake的實際示例。
什么是Docker Bake?
Docker Bake是BuildKit的一個功能,允許用戶使用配置文件組織和自動化 Docker鏡像構建過程。
Docker Bake的主要優勢包括:
- 聲明性語法。可以使用HCL(HashiCorp配置語言)、JSON或YAML(Docker Compose文件)來描述所需的結果,而不是在腳本中使用多個命令。
- 并行構建。BuildKit在可能的情況下自動并行執行鏡像構建。
- 緩存重用。在不同構建之間高效利用緩存。
- 分組和有針對性的構建。能夠定義圖像組并僅構建當前所需的目標。
- 變量和繼承。在構建目標之間具有強大的變量系統和屬性繼承功能。
- CI/CD 集成。輕松集成到持續集成和交付管道中。
Bake文件剖析
以下了解Bake文件的主要組成部分:
1.變量
變量允許定義可以在配置的不同部分使用的值,并且可以在運行時輕松地重新定義:
Shell
variable "TAG" {
default = "latest"
}
variable "DEBUG" {
default = "false"
}
變量可以通過字符串插值在配置的其他部分使用:${TAG}。
2.組
群組允許將多個目標組合在一起,然后同時構建:
Shell
group "default" {
targets = ["app", "api"]
}
group "backend" {
targets = ["api", "database"]
}
3.目標
目標是構建的主要單元,每個目標定義一個Docker鏡像:
Shell
target "app" {
dockerfile = "Dockerfile.app"
context = "./app"
tags = ["myorg/app:${TAG}"]
args = {
DEBUG = "${DEBUG}"
}
platforms = ["linux/amd64", "linux/arm64"]
}
主要目標參數:
- dockerfile——dockerfile的路徑
- context——構建上下文
- tags——圖像的標簽
- args ——傳遞給Dockerfile的參數
- platforms——用于多平臺構建的平臺
- target——在Dockerfile中進行多階段構建的目標
- output——在哪里輸出構建結果
- cache-from和cache-to——cache設置
4.繼承
Bake最強大的功能之一是繼承參數的能力:
Shell
target "base" {
context = "."
args = {
BASE_IMAGE = "node:16-alpine"
}
}
target "app" {
inherits = ["base"]
dockerfile = "app/Dockerfile"
tags = ["myapp/app:latest"]
}
應用程序目標將繼承基中的所有參數,并用自己的參數覆蓋或補充它們。
5.函數
在HashiCorp配置語言(HCL)中,可以定義函數以實現更靈活的配置:
Shell
function "tag" {
params = [name, version]
result = ["${name}:${version}"]
}
target "app" {
tags = tag("myapp/app", "v1.0.0")
}
安裝和設置
Docker Bake是BuildKit的一部分,BuildKit是一個用于構建Docker鏡像的現代引擎。從Docker 23.0開始,BuildKit默認是啟用的,所以大多數用戶不需要額外的配置。但是,如果用戶使用的是舊版本的Docker或想要確保BuildKit已經激活,請按照以下的說明進行操作。
檢查Docker版本
確保有最新版本的Docker(23.0或更高版本)。可以使用命令檢查版本:
Plain Text
docker --version
如果Docker版本過時了,請按照官方文檔進行更新。
激活BuildKit(適用于舊Docker版本)
對于低于Docker23.0的版本,需要手動激活BuildKit。這可以通過以下方式之一完成:
通過環境變量:
Shell
export DOCKER_BUILDKIT=1
Plain Text
In the Docker configuration file: Edit the ~/.docker/config.json file and add the following parameters:
JSON
{
"features": {
"buildkit": true
}
}
- 通過命令行:當使用docker build或docker buildx bake命令時,可以明確指定使用BuildKit:
Shell
DOCKER_BUILDKIT=1 docker buildx bake
安裝Docker Buildx
Docker Buildx是Docker CLI的擴展,它為構建鏡像提供了額外的功能,包括對多平臺構建的支持。從Docker 20.10開始,Buildx包含在Docker中,但為了獲得完整的功能,建議確保已經安裝并激活它。
(1)檢查Buildx安裝
Shell
docker buildx version
如果沒有安裝Buildx,可以按照下面的說明操作。
(2)安裝Buildx
- For Linux:
Shell
mkdir -p ~/.docker/cli-plugins
curl -sSL https://github.com/docker/buildx/releases/latest/download/buildx-linux-amd64 -o ~/.docker/cli-plugins/docker-buildx
chmod +x ~/.docker/cli-plugins/docker-buildx
- For macOS (using Homebrew):
Shell
brew install docker-buildx
(3)創建和使用Buildx生成器
在默認情況下,Docker使用內置的構建器,但為了獲得完整的功能,建議創建一個新的構建器:
Shell
1 docker buildx create --use --name my-builder
檢查構建器是否處于激活狀態:
Shell
1 docker buildx ls
Docker Bake基礎知識
配置文件
Docker Bake使用的配置文件可以用HCL(默認)、JSON或YAML格式編寫。以下是這些文件的標準名稱:
- docker-bake.hcl
- docker-bake.json
也可以使用帶有一些擴展的docker-compose.yml。
HCL文件結構
典型的Docker Bake配置文件的結構如下:
Shell
// Defining variables
variable "TAG" {
default = "latest"
}
// Defining groups
group "default" {
targets = ["app", "api"]
}
// Defining common settings
target "docker-metadata-action" {
tags = ["user/app:${TAG}"]
}
// Defining build targets
target "app" {
inherits = ["docker-metadata-action"]
dockerfile = "Dockerfile.app"
context = "./app"
}
target "api" {
inherits = ["docker-metadata-action"]
dockerfile = "Dockerfile.api"
context = "./api"
}
執行構建
從默認組構建所有目標:
Plain Text
docker buildx bake
建立一個特定的目標或組:
Plain Text
1 docker buildx bake app
傳遞變量:
Plain Text
TAG=v1.0.0 docker buildx bake
實例
示例1:簡單的多組件應用程序
假設有一個由Web前端、API和數據庫服務組成的應用程序。以下是一個docker-bake.hcl文件可能的樣子:
Shell
variable "TAG" {
default = "latest"
}
group "default" {
targets = ["frontend", "api", "db"]
}
group "services" {
targets = ["api", "db"]
}
target "base" {
context = "."
args = {
BASE_IMAGE = "node:16-alpine"
}
}
target "frontend" {
inherits = ["base"]
dockerfile = "frontend/Dockerfile"
tags = ["myapp/frontend:${TAG}"]
args = {
API_URL = "http://api:3000"
}
}
target "api" {
inherits = ["base"]
dockerfile = "api/Dockerfile"
tags = ["myapp/api:${TAG}"]
args = {
DB_HOST = "db"
DB_PORT = "5432"
}
}
target "db" {
context = "./db"
dockerfile = "Dockerfile"
tags = ["myapp/db:${TAG}"]
}
示例2:多平臺構建
Docker Bake的一個強大的方面是易于建立多平臺構建:
Shell
variable "TAG" {
default = "latest"
}
group "default" {
targets = ["app-all"]
}
target "app" {
dockerfile = "Dockerfile"
tags = ["myapp/app:${TAG}"]
}
target "app-linux-amd64" {
inherits = ["app"]
platforms = ["linux/amd64"]
}
target "app-linux-arm64" {
inherits = ["app"]
platforms = ["linux/arm64"]
}
target "app-all" {
inherits = ["app"]
platforms = ["linux/amd64", "linux/arm64"]
}
示例3:不同的開發環境
Docker Bake可以很容易地管理不同環境(例如,開發、測試和生產)的構建。為此,可以使用通過命令行覆蓋的變量:
Shell
variable "ENV" {
default = "dev"
}
group "default" {
targets = ["app-${ENV}"]
}
target "app-base" {
dockerfile = "Dockerfile"
args = {
BASE_IMAGE = "node:16-alpine"
}
}
target "app-dev" {
inherits = ["app-base"]
tags = ["myapp/app:dev"]
args = {
NODE_ENV = "development"
DEBUG = "true"
}
}
target "app-stage" {
inherits = ["app-base"]
tags = ["myapp/app:stage"]
args = {
NODE_ENV = "production"
API_URL = "https://api.stage.example.com"
}
}
target "app-prod" {
inherits = ["app-base"]
tags = ["myapp/app:prod", "myapp/app:latest"]
args = {
NODE_ENV = "production"
API_URL = "https://api.example.com"
}
}
要為特定環境構建鐿像,使用以下命令:
Plain Text
ENV=prod docker buildx bake
高級Docker Bake功能
矩陣構建
Docker Bake允許根據參數組合定義創建多個構建變體的矩陣:
Shell
variable "REGISTRY" {
default = "docker.io/myorg"
}
target "matrix" {
name = "app-${platform}-${version}"
matrix = {
platform = ["linux/amd64", "linux/arm64"]
version = ["1.0", "2.0"]
}
dockerfile = "Dockerfile"
tags = ["${REGISTRY}/app:${version}-${platform}"]
platforms = ["${platform}"]
args = {
VERSION = "${version}"
}
}
這段代碼將為平臺和版本的每種組合創建四個鏡像變體。可以使用一個命令來構建它們。
使用外部文件和函數
Docker Bake允許使用外部文件和函數進行更靈活的配置:
Shell
// Import variables from a JSON file
variable "settings" {
default = {}
}
function "tag" {
params = [name, tag]
result = ["${name}:${tag}"]
}
target "app" {
dockerfile = "Dockerfile"
tags = tag("myapp/app", "v1.0.0")
args = {
CONFIG = "${settings.app_config}"
}
}
然后可以傳遞一個settings文件:
Plain Text
docker buildx bake --file settings.json
與Docker Compose集成
Docker Bake可以與Docker Compose集成,這對現有項目來說特別方便:
YAML
# docker-compose.yml
services:
app:
build:
context: ./app
dockerfile: Dockerfile
args:
VERSION: "1.0"
image: myapp/app:latest
api:
build:
context: ./api
dockerfile: Dockerfile
image: myapp/api:latest
Shell
# docker-bake.hcl
target "default" {
context = "."
dockerfile-inline = <<EOT
FROM docker/compose:1.29.2
WORKDIR /app
COPY docker-compose.yml .
RUN docker-compose build
EOT
}
條件邏輯
對于更復雜的場景,可以使用條件邏輯:
Shell
variable "DEBUG" {
default = "false"
}
target "app" {
dockerfile = "Dockerfile"
tags = ["myapp/app:latest"]
args = {
DEBUG = "${DEBUG}"
EXTRA_PACKAGES = DEBUG == "true" ? "vim curl htop" : ""
}
}
在CI/CD中使用Docker Bake
Docker Bake非常適合在CI/CD管道中使用。以下是一個與GitHub Actions集成的例子,在Docker Hub中使用秘密(secrets)進行安全認證:
Shell
# .github/workflows/build.yml
name: Build and Publish
on:
push:
branches: [main]
tags: ['v*']
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Docker Metadata
id: meta
uses: docker/metadata-action@v4
with:
images: myapp/app
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
- name: Build and push
uses: docker/bake-action@v2
with:
files: |
./docker-bake.hcl
targets: app
push: true
set: |
*.tags=${{ steps.meta.outputs.tags }}
調試和監視構建
Docker Bake為調試構建過程提供了幾個有用的選項:
查看配置而不構建:
Plain Text
docker buildx bake –print
詳細的日志:
Plain Text
docker buildx bake --progress=plain
導出為JSON進行分析:
Plain Text
docker buildx bake --print | jq
與其他工具的比較
Docker Bake vs. Docker Compose
特性 | Docker Bake | Docker Compose |
主要目的 | 構建鏡像 | 容器管理 |
并行構建 | 自動 | 有限 |
矩陣構建 | 是 | 否 |
繼承 | 強大的系統 | 有限 (擴展) |
多平臺 | 集成 | 否 |
配置格式 | HCL, JSON | YAML |
Docker Bake vs. Build Scripts
特性 | Docker Bake | Bash/scripts |
聲明性 | 高 | 低 |
維護的復雜性 | 低 | 高 |
可重用性 | 簡單 | 復雜 |
并行性 | 自動 | 人工 |
CI/CD集成 | 簡單的 | 需要努力 |
最佳實踐
將目標組織成邏輯組:
Shell
group "all" {
targets = ["app", "api", "worker"]
}
group "backend" {
targets = ["api", "worker"]
}
將繼承用于常見設置:
Shell
target "common" {
context = "."
args = {
BASE_IMAGE = "node:16-alpine"
}
}
target "app" {
inherits = ["common"]
dockerfile = "app/Dockerfile"
}
將復雜的配置組織到多個文件中:
Shell
docker buildx bake \
-f ./common.hcl \
-f ./development.hcl \
app-dev
使用變量實現靈活性:
Shell
variable "REGISTRY" {
default = "docker.io/myorg"
}
target "app" {
tags = ["${REGISTRY}/app:latest"]
}
為復雜的構建場景應用矩陣:
Shell
target "matrix" {
matrix = {
env = ["dev", "prod"]
platform = ["linux/amd64", "linux/arm64"]
}
name = "app-${env}-${platform}"
tags = ["myapp/app:${env}-${platform}"]
}
常見問題及解決方法
問題1:緩存沒有被有效使用
解決方案
正確地組織Dockerfile,把那些變化不太頻繁的層放在文件的開頭:
Dockerfile
FROM node:16-alpine
# First copy only dependency files
COPY package.json package-lock.json ./
RUN npm install
# Then copy the source code
COPY . .
問題2:環境變量沖突
解決方案
在Docker Bake中使用顯式值:
Shell
target "app" {
args = {
NODE_ENV = "production"
}
}
問題3:難以調試構建
解決方案
使用詳細的日志和檢查:
Plain Text
docker buildx bake --progress=plain --print app
結論
Docker Bake提供了一種強大、靈活和聲明性的方法來組織Docker鏡像構建。它解決了團隊在使用傳統構建方法時面臨的許多問題,特別是在復雜的多組件項目中。
Docker Bake的主要優點:
- 聲明式方法
- 高效的緩存使用
- 并行和多平臺構建
- 強大的變量和繼承系統
- 與CI/CD管道的出色集成
在工作流程中實施Docker Bake可以顯著簡化和加快映像構建過程,特別是對于使用微服務架構或復雜多組件應用程序的團隊而言。
有用的資源
- Official Docker Bake documentation
- BuildKit GitHub repository
- HCL documentation
- Docker Buildx GitHub repository
原文標題:Docker Bake: A Modern Approach to Container Building,作者:Suleiman Dibirov