Introduction
Depuis la version 23.0 de Docker, BuildKit
est devenu le moteur de création
d'images de conteneurs par défaut en remplacement de l'ancien qui devenait
obsolète face à ses concurrents que sont Buildah
, Kaniko
par exemple.
Aujourd'hui, je vais parler de certaines nouvelles fonctionnalités qui
permettent d'utiliser différents contextes de construction d'images.
Petit tour des fonctionnalités de Buildkit
Buildkit
est pour moi une petite révolution dans le domaine des outils de
constructions d'image que j'ai manquée. Je m'étais amusé à l'utiliser pour
générer des images en parallèle une image à destination de plusieurs
plateformes, mais sans en saisir tout le potentiel des nouvelles fonctionnalités
qu'il intégrait.
En fait BuildKit
est un produit à part entière. Il possède une architecture
permettant de supporter d'autres langages de définition d'images, autre que les
Dockerfile
, mais aussi plusieurs formats de sortie des images. Il peut être
utilisé via Docker Desktop ou Docker Engine, déployé sur des machines distantes,
mais aussi des clusters Kubernetes
.
Pour améliorer les temps de constructions, il fait appel à un DAG qui permet :
- De paralléliser la construction des étapes indépendantes.
- De détecter et de bypasser des étapes inutiles
- D'améliorer la copie des fichiers depuis le contexte local
- D'utiliser des caches dans des volumes
L'intégration de Buildkit dans Docker Engine
Le sujet est complexe à expliquer, car la documentation est difficile à comprendre. Elle est éclatée un peu partout et pas forcément à jour.
Il faut bien comprendre que BuildKit
est un moteur de construction d'images
qui est intégré dans Docker Engine
et Docker Desktop
. L'intégration dans
Docker Engine
se fait via le plugin buildx
qui normalement est installé
avec. Vérifions :
dpkg --get-selections|grep docker
docker-buildx-plugin install
docker-ce install
docker-ce-cli install
docker-ce-rootless-extras install
docker-compose-plugin install
Le plugin Buildx
est défini par défaut, mais pourrait être remplacé par un
autre.
Buildx
est très souple, car il permet d'utiliser différents drivers. Chaque
pilote définit comment et où la construction de l'image se fait et possède ses
propres fonctionnalités.
Parmi les drivers actuellement supportés, on trouve les suivants :
- Le driver
docker
le driver par défaut qui utilise Docker installé localement. - Le driver
docker-container
qui permet la création d'un environnementBuildKit
personnalisable dans un conteneur Docker. - Le driver
kubernetes
qui permet de lancer des constructions sur des clusters kubernetes. - Le driver
remote
qui permet de lancer des constructions sur des machines distantes oubuildkit
est installé en tant que démon.
On voit que tout est pensé pour le rendre très souple et pour pouvoir déporter
les constructions d'images de containers un peu partout. Dans ce billet, je vais
me limiter à l'utiliser aux drivers docker
et docker-container
, mais je
prévois d'écrire des billets sur les deux autres drivers.
Le driver docker
Le driver Docker
ne possède pas de paramètres pour le moment. C'est celui qui
est invoqué avec la commande docker build
. Il ne permet par exemple de
construire d'images pour d'autres plateformes.
Le driver docker-container
Le driver docker-container
possède les paramètres suivants :
image=<IMAGE>
qui permet de définir l'image du conteneur à utiliser pour exécuterbuildkit
.network=<NETMODE>
qui permet de définir le mode réseau pour exécuter le conteneurbuildkit
.cgroup-parent=<CGROUP>
qui permet de définir lecgroup
parent du conteneurbuildkit
si docker utilise le pilotecgroupfs
. La valeur par défaut est/docker/buildx
.
Dans ce billet, nous n'utiliserons aucune de ces options.
Gestion des instances
Les instances de constructeur sont des environnements isolés dans lesquels des builds peuvent être lancés.
Lister les instances
Pour obtenir la liste des instances disponibles, on utilise la commande ls
:
docker buildx ls
NAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMS
default * docker default default running v0.11.6+616c3f613b54 linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/386
On ne retrouve que celui du driver docker
par défaut.
Inspection d'une instance
Pour obtenir la liste complète des paramètres actuels, on fait appel à la
commande inspect
.
docker buildx inspect default
Name: default
Driver: docker
Last Activity: 2023-10-25 09:03:27 +0000 UTC
Nodes:
Name: default
Endpoint: default
Status: running
Buildkit: v0.11.6+616c3f613b54
Platforms: linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/386
Labels:
org.mobyproject.buildkit.worker.moby.host-gateway-ip: 172.17.0.1
Création d'une instance
La commande create
permet de créer de nouvelles instances de constructeur.
Voyons comment créer une nouvelle instance utilisant le driver
docker-container
.
docker buildx create --driver docker-container --bootstrap --name mybuilder
[+] Building 5.8s (1/1) FINISHED
=> [internal] booting buildkit 5.8s
=> => pulling image moby/buildkit:buildx-stable-1 5.2s
=> => creating container buildx_buildkit_mybuilder0 0.5s
mybuilder
L'option --bootstrap
permet de démarrer l'instance après sa création.
Faisons quelques vérifications :
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d831723dd9e0 moby/buildkit:buildx-stable-1 "buildkitd" 11 minutes ago Up 11 minutes buildx_buildkit_mybuilder0
On retrouve une image de buildkit
fournie par le projet moby sur lequel tourne
le démon buildkitd
.
docker build ls
NAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMS
mybuilder * docker-container
mybuilder0 unix:///var/run/docker.sock running v0.12.3 linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/386
default docker
default default running v0.11.6+616c3f613b54 linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/386
Construction d'une image utilisant le driver docker-container
On prend un simple Dockerfile
:
FROM alpine:3.18
CMD ["/bin/echo", "Build with BuildKit!"]
Lors du build, il faut indiquer la sortie, car lors de la création de l'instance, elle n'a pas été indiqué. On a à notre disposition deux options :
--load
pour la charger dans Docker--push
pour la pousser directement dans une registry
On utilisera ici l'option --load
:
docker buildx build . --tag my-image:0.1 --load
[+] Building 0.9s (6/6) FINISHED docker-container:mybuilder
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 95B 0.0s
=> [internal] load metadata for docker.io/library/alpine:3.18 0.9s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> CACHED [1/1] FROM docker.io/library/alpine:3.18@sha256:eece025e432126ce23f223450a0326fbebde39cdf496a85d8c016293fc851978 0.0s
=> => resolve docker.io/library/alpine:3.18@sha256:eece025e432126ce23f223450a0326fbebde39cdf496a85d8c016293fc851978 0.0s
=> exporting to docker image format 0.0s
=> => exporting layers 0.0s
=> => exporting manifest sha256:ffee8b73b5c65b7371e6a5cd2e8af178253eff5ce9a35ae7afe6785b934817ac 0.0s
=> => exporting config sha256:cd081914e1d135e58bc9ad0aa40a874050ac7f338b94599a7a8c367ba88c685d 0.0s
=> => sending tarball 0.0s
=> importing to docker 0.0s
> docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
my-image 0.1 cd081914e1d1 3 weeks ago 7.34MB
Notre image a bien été créé !
Le multi-plateforme
Nous allons créer une seconde instance prenant en charge des plateformes autres celles de l'hôte.
docker buildx create --use --bootstrap \
--name mybuilder2 \
--driver docker-container \
> --platform linux/arm64,linux/arm/v8
[+] Building 0.0s (0/1)
[+] Building 3.1s (1/1) FINISHED
=> [internal] booting buildkit 3.1s
=> => pulling image moby/buildkit:buildx-stable-1 2.6s
=> => creating container buildx_buildkit_mybuilder20 0.5s
mybuilder2
Vérifions que nous avons bien notre nouvelle instance :
docker buildx ls
NAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMS
mybuilder docker-container
mybuilder0 unix:///var/run/docker.sock running v0.12.3 linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/386
mybuilder2 * docker-container
mybuilder20 unix:///var/run/docker.sock running v0.12.3 linux/arm64*, linux/arm/v8*, linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/386
default docker
default default running v0.11.6+616c3f613b54 linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/386 ```
On voit bien que nos plateformes s'ajoutent à celle de notre hôte.
On tente le build en indiquant la plateforme arm/v8
:
docker buildx build . --tag my-image:0.1 --load --platform linux/arm/v8
[+] Building 7.1s (7/7) FINISHED docker-container:mybuilder2
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 95B 0.0s
=> [internal] load metadata for docker.io/library/alpine:3.18 4.2s
=> [auth] library/alpine:pull token for registry-1.docker.io 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [1/1] FROM docker.io/library/alpine:3.18@sha256:eece025e432126ce23f223450a0326fbebde39cdf496a85d8c016293fc851978 2.8s
=> => resolve docker.io/library/alpine:3.18@sha256:eece025e432126ce23f223450a0326fbebde39cdf496a85d8c016293fc851978 0.0s
=> => sha256:622a0779436eb93ceea635e910268f867c2eba47d4f62f0bd45f0bd165af3572 2.90MB / 2.90MB 2.8s
=> exporting to docker image format 2.8s
=> => exporting layers 0.0s
=> => exporting manifest sha256:56312a9ccaaea5c7f13803bee97f4eea883282ba10cb6497abb475574a2ca4b3 0.0s
=> => exporting config sha256:2544f3c45a5f12c7b6078d0896b52fcbb61404e971f6e1c25aef659b900e4073 0.0s
=> => sending tarball 0.0s
=> importing to docker
On inspecte l'image :
docker image inspect my-image:0.1
[
{
"Id": "sha256:2544f3c45a5f12c7b6078d0896b52fcbb61404e971f6e1c25aef659b900e4073",
"RepoTags": [
"my-image:0.1"
],
"RepoDigests": [],
"Parent": "",
"Comment": "buildkit.dockerfile.v0",
"Created": "2023-09-28T20:59:24.681335468Z",
"Container": "",
"ContainerConfig": {
"Hostname": "",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": null,
"Cmd": null,
"Image": "",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": null
},
"DockerVersion": "",
"Author": "",
"Config": {
"Hostname": "",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"/bin/echo",
"Build with BuildKit!"
],
"ArgsEscaped": true,
"Image": "",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": null
},
"Architecture": "arm",
"Variant": "v8",
"Os": "linux",
"Size": 4694566,
"VirtualSize": 4694566,
"GraphDriver": {
"Data": {
"MergedDir": "/var/lib/docker/overlay2/d2d07f0bf6c88f3dc68bc8f23d58ead2b3c873b1af449fe97ffcbec2e418e806/merged",
"UpperDir": "/var/lib/docker/overlay2/d2d07f0bf6c88f3dc68bc8f23d58ead2b3c873b1af449fe97ffcbec2e418e806/diff",
"WorkDir": "/var/lib/docker/overlay2/d2d07f0bf6c88f3dc68bc8f23d58ead2b3c873b1af449fe97ffcbec2e418e806/work"
},
"Name": "overlay2"
},
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:315bd5088587f904ce90b0801a0c8db45648746faf106758d6946f4ef96ae5d0"
]
},
"Metadata": {
"LastTagTime": "0001-01-01T00:00:00Z"
}
}
]
On a bien une image pour les processeurs arm/v8
. Cela simplifie énormément le
process à mon précédent test.
Définition de l'instance par défaut
La commande use
permet de définir l'instance à utiliser par défaut.
docker buildx use default
Lors de la création d'une instance, il est possible d'utiliser le paramètre
--use
pour basculer directement sur celui-ci.
Arrêt d'une instance
L'arrêt d'une instance se fait avec la commande stop
:
docker buildx stop mybuilder2
Vérifions le resultat :
docker buildx ls
NAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMS
mybuilder docker-container
mybuilder0 unix:///var/run/docker.sock running v0.12.3 linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/386
mybuilder2 * docker-container
mybuilder20 unix:///var/run/docker.sock stopped linux/arm64*, linux/arm/v8*
default docker
default default running v0.11.6+616c3f613b54 linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/386
Il n'y a pas de commande start car il suffit de relancer un build pour que l'instance démarre.
Destruction des instances
La commande rm
permet de détruire une instance de construction.
Faire du ménage
Buuildkit
utilise des ressources et il faut pouvoir faire de temps en temps un
peu le ménage. Pour cela, nous avons deux commandes : du
pour obtenir des
informations sur l'espace disque occupé et prune
pour le nettoyage.
docker buildx du
ID RECLAIMABLE SIZE LAST ACCESSED
g1m8rrh7yz00ud5xon5zq2a7b true 2.9MB About a minute ago
k8pfdeh1dna0kwrkvil9brtou* true 8.192kB About a minute ago
qvz1azsg8rxfofd0qzcha6r50* true 4.096kB About a minute ago
Reclaimable: 2.912MB
Total: 2.912MB
docker buildx prune
WARNING! This will remove all dangling build cache. Are you sure you want to continue? [y/N] y
ID RECLAIMABLE SIZE LAST ACCESSED
g1m8rrh7yz00ud5xon5zq2a7b true 2.9MB 4 minutes ago
k8pfdeh1dna0kwrkvil9brtou* true 8.192kB 4 minutes ago
qvz1azsg8rxfofd0qzcha6r50* true 4.096kB 4 minutes ago
Total: 2.912MB
Dans mon test vu que mon image est des plus simples cela ne change rien. Mais si on construit des images plus complexes alors là cela change tout.
Plus loin
Dans les prochains billets, nous verrons comment utiliser les deux autres drivers
qui sont remote
et kubernetes
.