⚠️ Traduction non officielle - Cette documentation est une traduction communautaire non officielle de Docker.

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.

    Tip

    Les 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"]
}
Tip

L'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 .
Tip

La 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.

Tip

Parce 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 :

docker-bake.hcl
 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.

docker-bake.hcl
   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.

docker-bake.hcl
   tags = [
-    "bakeme:latest",
+    mode == "release" ? "bakeme:latest" : "bakeme:dev"
   ]
  • Si mode est release, le nom du tag est bakeme:latest
  • Si mode est debug, le nom du tag est bakeme: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.

Dockerfile
 # 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.

Tip

Les 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 :