Maîtriser les constructions multi-plateformes, les tests, et plus encore avec Docker Buildx Bake
Ce guide montre comment simplifier et automatiser le processus de construction
d'images, de test et de génération d'artefacts de construction en utilisant Docker Buildx Bake. En
définissant les configurations de construction dans un fichier déclaratif docker-bake.hcl
, vous pouvez
éliminer les scripts manuels et permettre des flux de travail efficaces pour les constructions complexes,
les tests et la génération d'artefacts.
Hypothèses
Ce guide suppose que vous êtes familier avec :
Prérequis
- Vous avez une version récente de Docker installée sur votre machine.
- Vous avez Git installé pour cloner des dépôts.
- Vous utilisez le magasin d'images containerd.
Introduction
Ce guide utilise un projet d'exemple pour démontrer comment Docker Buildx Bake peut
rationaliser vos flux de travail de construction et de test. Le dépôt inclut à la fois un
Dockerfile et un fichier docker-bake.hcl
, vous donnant une configuration prête à l'emploi pour essayer
les commandes Bake.
Commencez par cloner le dépôt d'exemple :
git clone https://github.com/dvdksn/bakeme.git
cd bakeme
Le fichier Bake, docker-bake.hcl
, définit les cibles de construction dans une syntaxe
déclarative, en utilisant des cibles et des groupes, vous permettant de gérer efficacement les constructions
complexes.
Voici à quoi ressemble le fichier Bake prêt à l'emploi :
target "default" {
target = "image"
tags = [
"bakeme:latest",
]
attest = [
"type=provenance,mode=max",
"type=sbom",
]
platforms = [
"linux/amd64",
"linux/arm64",
"linux/riscv64",
]
}
Le mot-clé target
définit une cible de construction pour Bake. La cible default
définit la cible à construire lorsqu'aucune cible spécifique n'est spécifiée sur la ligne de
commande. Voici un résumé rapide des options pour la cible default
:
-
target
: L'étape de construction cible dans le Dockerfile. -
tags
: Les tags à assigner à l'image. -
attest
: Les Attestations à attacher à l'image.TipLes attestations fournissent des métadonnées telles que la provenance de la construction, qui suit la source de la construction de l'image, et un SBOM (Software Bill of Materials), utile pour les audits de sécurité et la conformité.
-
platforms
: Les variantes de plateforme à construire.
Pour exécuter cette construction, exécutez simplement la commande suivante à la racine du dépôt :
$ docker buildx bake
Avec Bake, vous évitez les longues incantations de ligne de commande difficiles à retenir, simplifiant la gestion de la configuration de construction en remplaçant les scripts manuels, sujets aux erreurs, par un fichier de configuration structuré.
Pour comparaison, voici à quoi ressemblerait cette commande de construction sans Bake :
$ docker buildx build \
--target=image \
--tag=bakeme:latest \
--provenance=true \
--sbom=true \
--platform=linux/amd64,linux/arm64,linux/riscv64 \
.
Tests et linting
Bake ne sert pas seulement à définir des configurations de construction et à exécuter des constructions. Vous pouvez également utiliser Bake pour exécuter vos tests, en utilisant efficacement BuildKit comme un exécuteur de tâches. Exécuter vos tests dans des conteneurs est excellent pour garantir des résultats reproductibles. Cette section montre comment ajouter deux types de tests :
- Tests unitaires avec
go test
. - Linting pour les violations de style avec
golangci-lint
.
Dans un esprit de Développement Dirigé par les Tests (TDD), commencez par ajouter une nouvelle cible test
au fichier Bake :
target "test" {
target = "test"
output = ["type=cacheonly"]
}
TipL'utilisation de
type=cacheonly
garantit que la sortie de la construction est effectivement rejetée ; les couches sont enregistrées dans le cache de BuildKit, mais Buildx ne tentera pas de charger le résultat dans le magasin d'images du moteur Docker.Pour les exécutions de tests, vous n'avez pas besoin d'exporter la sortie de la construction — seule l'exécution des tests compte.
Pour exécuter cette cible Bake, exécutez docker buildx bake test
. À ce moment,
vous recevrez une erreur indiquant que l'étape test
n'existe pas dans le
Dockerfile.
$ 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
Pour satisfaire cette cible, ajoutez la cible Dockerfile correspondante. L'étape test
ici est basée sur la même étape de base que l'étape de construction.
FROM base AS test
RUN --mount=target=. \
--mount=type=cache,target=/go/pkg/mod \
go test .
TipLa directive
--mount=type=cache
met en cache les modules Go entre les constructions, améliorant les performances de construction en évitant le besoin de re-télécharger les dépendances. Ce cache partagé garantit que le même ensemble de dépendances est disponible pour la construction, les tests et les autres étapes.
Maintenant, l'exécution de la cible test
avec Bake évaluera les tests unitaires pour ce
projet. Si vous voulez vérifier que cela fonctionne, vous pouvez faire une modification arbitraire
à main_test.go
pour faire échouer le test.
Ensuite, pour activer le linting, ajoutez une autre cible au fichier Bake, nommée lint
:
target "lint" {
target = "lint"
output = ["type=cacheonly"]
}
Et dans le Dockerfile, ajoutez l'étape de construction. Cette étape utilisera l'image officielle
golangci-lint
sur Docker Hub.
TipParce que cette étape repose sur l'exécution d'une dépendance externe, il est généralement une bonne idée de définir la version que vous voulez utiliser comme un argument de construction. Cela vous permet de gérer plus facilement les mises à jour de version à l'avenir en regroupant les versions des dépendances au début du 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
Enfin, pour permettre l'exécution des deux tests simultanément, vous pouvez utiliser la construction groups
dans le fichier Bake. Un groupe peut spécifier plusieurs cibles à exécuter avec une
seule invocation.
group "validate" {
targets = ["test", "lint"]
}
Maintenant, exécuter les deux tests est aussi simple que :
$ docker buildx bake validate
Construire des variantes
Parfois, vous devez construire plus d'une version d'un programme. L'exemple suivant utilise Bake pour construire des variantes distinctes "release" et "debug" du programme, en utilisant des matrices. L'utilisation de matrices vous permet d'exécuter des constructions parallèles avec différentes configurations, ce qui économise du temps et garantit la cohérence.
Une matrice étend une seule construction en plusieurs constructions, chacune représentant une combinaison unique de paramètres de matrice. Cela signifie que vous pouvez orchestrer Bake pour construire à la fois la version de production et de développement de votre programme en parallèle, avec des changements de configuration minimes.
Le projet d'exemple pour ce guide est configuré pour utiliser une option au moment de la construction pour activer conditionnellement la journalisation de débogage et les capacités de traçage.
- Si vous compilez le programme avec
go build -tags="debug"
, les capacités supplémentaires de journalisation et de traçage sont activées (mode développement). - Si vous construisez sans le tag
debug
, le programme est compilé avec un enregistreur par défaut (mode production).
Mettez à jour le fichier Bake en ajoutant un attribut de matrice qui définit les combinaisons de variables à construire :
target "default" {
+ matrix = {
+ mode = ["release", "debug"]
+ }
+ name = "image-${mode}"
target = "image"
L'attribut matrix
définit les variantes à construire ("release" et "debug").
L'attribut name
définit comment la matrice est étendue en plusieurs
cibles de construction distinctes. Dans ce cas, l'attribut de matrice étend la construction
en deux flux de travail : image-release
et image-debug
, chacun utilisant différents
paramètres de configuration.
Ensuite, définissez un argument de construction nommé BUILD_TAGS
qui prend la valeur de la
variable de matrice.
target = "image"
+ args = {
+ BUILD_TAGS = mode
+ }
tags = [
Vous voudrez également changer la façon dont les tags d'image sont assignés à ces constructions.
Actuellement, les deux chemins de la matrice généreraient les mêmes noms de tags d'image, et
s'écraseraient mutuellement. Mettez à jour l'attribut tags
pour utiliser un opérateur conditionnel afin de
définir le tag en fonction de la valeur de la variable de matrice.
tags = [
- "bakeme:latest",
+ mode == "release" ? "bakeme:latest" : "bakeme:dev"
]
- Si
mode
estrelease
, le nom du tag estbakeme:latest
- Si
mode
estdebug
, le nom du tag estbakeme:dev
Enfin, mettez à jour le Dockerfile pour consommer l'argument BUILD_TAGS
pendant l'étape
de compilation. Lorsque l'option -tags="${BUILD_TAGS}"
s'évalue à
-tags="debug"
, le compilateur utilise la fonction configureLogging
dans le fichier
debug.go
.
# build compile le programme
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" .
C'est tout. Avec ces changements, votre commande docker buildx bake
construit maintenant
deux variantes d'images multi-plateformes. Vous pouvez inspecter la configuration de construction
canonique que Bake génère en utilisant la commande docker buildx bake --print
.
L'exécution de cette commande montre que Bake exécutera un groupe default
avec
deux cibles avec différents arguments de construction et tags d'image.
{
"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"]
}
}
}
En tenant compte également de toutes les variantes de plateformes, cela signifie que la configuration de construction génère 6 images différentes.
$ 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
Exporter des artefacts de construction
Exporter des artefacts de construction comme des binaires peut être utile pour le déploiement dans des environnements sans Docker ou Kubernetes. Par exemple, si vos programmes sont destinés à être exécutés sur la machine locale d'un utilisateur.
TipLes techniques discutées dans cette section peuvent être appliquées non seulement à la sortie de construction comme les binaires, mais à tout type d'artefacts, tels que les rapports de test.
Avec des langages de programmation comme Go et Rust où les binaires compilés sont généralement portables, créer des cibles de construction alternatives pour exporter uniquement le binaire est trivial. Tout ce que vous avez à faire est d'ajouter une étape vide dans le Dockerfile contenant rien d'autre que le binaire que vous voulez exporter.
Tout d'abord, ajoutons un moyen rapide de construire un binaire pour votre plateforme locale et
de l'exporter vers ./build/local
sur le système de fichiers local.
Dans le fichier docker-bake.hcl
, créez une nouvelle cible bin
. Dans cette étape, définissez
l'attribut output
sur un chemin de système de fichiers local. Buildx détecte automatiquement
que la sortie ressemble à un chemin de fichier, et exporte les résultats vers le chemin spécifié
en utilisant l'
exportateur local.
target "bin" {
target = "bin"
output = ["build/bin"]
platforms = ["local"]
}
Notez que cette étape spécifie une plateforme local
. Par défaut, si platforms
n'est pas spécifié, les constructions ciblent le système d'exploitation et l'architecture de l'hôte BuildKit. Si
vous utilisez Docker Desktop, cela signifie souvent que les constructions ciblent linux/amd64
ou
linux/arm64
, même si votre machine locale est macOS ou Windows, car Docker
s'exécute dans une VM Linux. L'utilisation de la plateforme local
force la plateforme cible à
correspondre à votre environnement local.
Ensuite, ajoutez l'étape bin
au Dockerfile qui copie le binaire compilé
de l'étape de construction.
FROM scratch AS bin
COPY --from=build "/usr/bin/bakeme" /
Maintenant, vous pouvez exporter la version de votre plateforme locale du binaire avec docker buildx bake bin
. Par exemple, sur macOS, cette cible de construction génère un
exécutable au format Mach-O — le
format exécutable standard pour macOS.
$ docker buildx bake bin
$ file ./build/bin/bakeme
./build/bin/bakeme: Mach-O 64-bit executable arm64
Ensuite, ajoutons une cible pour construire toutes les variantes de plateforme du programme.
Pour ce faire, vous pouvez
hériter de la cible bin
que vous venez de créer, et l'étendre en ajoutant les plateformes souhaitées.
target "bin-cross" {
inherits = ["bin"]
platforms = [
"linux/amd64",
"linux/arm64",
"linux/riscv64",
]
}
Maintenant, la construction de la cible bin-cross
crée des binaires pour toutes les plateformes.
Des sous-répertoires sont automatiquement créés pour chaque variante.
$ docker buildx bake bin-cross
$ tree build/
build/
└── bin
├── bakeme
├── linux_amd64
│ └── bakeme
├── linux_arm64
│ └── bakeme
└── linux_riscv64
└── bakeme
5 directories, 4 files
Pour générer également des variantes "release" et "debug", vous pouvez utiliser une matrice comme vous l'avez fait avec la cible par défaut. Lorsque vous utilisez une matrice, vous devez également différencier le répertoire de sortie en fonction de la valeur de la matrice, sinon le binaire est écrit au même endroit pour chaque exécution de la matrice.
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
Conclusion
Docker Buildx Bake rationalise les flux de travail de construction complexes, permettant des constructions multi-plateformes, des tests et des exportations d'artefacts efficaces. En intégrant Buildx Bake dans vos projets, vous pouvez simplifier vos constructions Docker, rendre votre configuration de construction portable, et gérer plus facilement des configurations complexes.
Expérimentez avec différentes configurations et étendez vos fichiers Bake pour répondre aux besoins de votre projet. Vous pourriez envisager d'intégrer Bake dans vos pipelines CI/CD pour automatiser les constructions, les tests et le déploiement d'artefacts. La flexibilité et la puissance de Buildx Bake peuvent améliorer considérablement vos processus de développement et de déploiement.
Lectures complémentaires
Pour plus d'informations sur l'utilisation de Bake, consultez ces ressources :