Distroless, les images sans distributions

Less is more

Le journalisme a un suffixe magique, “gate”, qui permet de créer des buzzwords qui claquent, depuis le watergate. En informatique, le suffixe magique est “less”, comme dans

  • diskless, un ordinateur qui démarre via le réseau (sans utiliser son disque).
  • headless, un système de gestion de contenu, qui permettra de générer un site statique et/ou une API distante depuis le javascript de la page web.
  • cpu-less, blagounette d’ordinateur rudimentaire à base de portes logiques.
  • db-less, architecture sans base de données relationnelles.
  • serverless, du cloud sans le cloud, juste votre code poussé dans le nuage.
  • code-less, le terme no-code est plus courant. Une interface permet de décrire une logique métier, sans écrire de code.
  • browserless, des test fonctionnels (ou du scraping de fourbe), depuis un navigateur web sur un ordinateur sans écran.
  • less, comme more mais mieux (humour UNIX).
  • distroless, une image conteneur construite sans distribution Linux

Lac d'Annecy

Conteneur

Nouvelles abstractions

Quand la virtualisation est apparue, la promesse était “ne touchez à rien, c’est comme avant”, on prend le contenu d’un serveur physique, on le met dans un serveur virtuel, et hop. Sauf que la virtualisation (fort pratique pour simuler une autre architecture) est rapidement devenue de la para-virtualisation, bien plus performante, l’OS invité collabore avec l’OS hôte. Cette collaboration est allé plus loin avec la création de matériel virtuel VirtIO, pour finalement aboutir aux machines virtuelles légères qui n’utilisent quasi que du Virtio ( DragonBall, FireCracker …).

Les conteneurs ont été conçus pour isoler une poignée de process (idéalement un). À l’arrivée des conteneurs, la même promesse a été faite : LXC, c’est presque comme une machine virtuelle, avec un init qui lance une grappe de services. Docker a fait le ménage pédagogique en expliquant bien qu’un conteneur va héberger un seul service. Mais, pour pallier aux cas de process zombie, Docker a intégré tini, avec l’option (--init).

Cette option est très peu utilisée, les applications métiers, même celles qui forkent des workers, savent gérer leur sous-process. De plus, le dogme demande de laisser à Kubernetes le soin de gérer la mise à l’échelle en multipliant les instances.

Image de base

Docker a fait le choix d’utiliser comme dossier racine un assemblage de pelures de systèmes de fichiers. Seule la pelure du haut est modifiable. Pour créer une image, on part d’une image, et on ajoute des pelures. Par convention, la première couche, vide, s’appelle SCRATCH.

Le meilleur moyen d’appréhender une nouvelle abstraction est de commencer par faire comme avant, et donc d’utiliser une distribution Linux, en partant d’un bootstrap (par ce que l’installation complète depuis un CD, à la Packer, ça va 5 minutes), puis d’installer des paquets.

Une image Docker est allégée, elle n’est pas bootable et n’a ni kernel, ni la ribambelle de services systèmes.

Docker a porté son choix d’image de référence sur Debian (libre à vous d’en faire d’autres à partir de votre distribution favorite). Ubuntu a deux fois plus de fan, propose de nouvelles versions à date fixe, mais fait régulièrement des choix polémiques. Ubuntu étant basé sur Debian, et ses ajouts ne s’aventurant que rarement dans les couches basses et les runtimes, ça ne change pas grand-chose. Donc, Debian.

L’image Debian officielle (maintenue par Debian) est construite à partir de son système de paquets, via l’outil debootstrap, utilisé aussi pour créer des images disques pour des machines virtuelles. Pour avoir des images reproductibles, deboostrap est emballé par debuerreotype, qui en plus de la gestion de snapshot va aussi appliquer différents paramétrages pour minimiser la taille de l’image.

Avec une optimisation agressive (en virant la doc essentiellement), on gagne 30% de place, et on a une image dite slim.

Les images de langages (python, ruby, golang, node, rust…) sont basées sur les images Debian officielles. Comme les images partagent leurs ancêtres, avec tout basé sur Debian, on mutualise pas mal de place en disque et réseau (lors du déploiement).

Les utilisateurs Docker se sont rendu compte qu’en partant sur une distribution Alpine, distribution minimaliste prévue pour l’embarqué, on obtient une image 10 fois plus petite, mais avec un système de paquet moins cohérent que Debian, une libc rikiki (musl) et busybox plutôt que les outils gnu. Les images de langages proposent souvent une variante basée sur Alpine, en plus de Debian.

Pour construire l’image de son application, on utilise souvent deux images : une image de construction, éphémère, avec les outils de développement, et une image d’exécution qui finira en production.

Images minimalistes

Pour limiter la surface d’attaque, les faux positifs dans les analyses de versions de paquets, et la taille des images, la nouvelle tendance est d’utiliser des images minimalistes. Minimaliste pour ne pas dire “à l’os”, ces images n’ont pas de shell, et aucun utilitaire.

Debugs et tests ne se feront pas depuis l’image, mais autour de l’image.

L’application aura des tests unitaires, l’image des tests fonctionnels et des tests de charges. Les erreurs remonteront via les journaux et Sentry. Les soucis de performances seront analysé par les traces, les sondes déployées en production.

Dans le pire du pire, un debug en preprod sera fait avec un conteneur sidecar, par ce que le conteneur de l’application sans shell, ne permet pas de s’y connecter avec un docker exec. Kubernetes permet de se connecter à une instance (un pod) mais je ne suis pas sûr que ce soit une bonne idée de le faire en production.

Distroless

Google a exploré une piste minimaliste pour construire des images dites “runtime” avec différents critères :

  • basé sur une distribution connue, pour la confiance et la correction des CVEs.
  • les applications apprécient d’utiliser les dernières mises à jour (stables) de leur langage, ce que ne peut fournir une distribution qui a besoin de la valider avec une brouette de paquets.
  • en se basant sur Debian, on peut construire l’application avec l’image de langage officielle, elle aussi basée sur Debian, pour ensuite déposer le tout dans une image “runtime”.
  • Les images doivent pouvoir être construites rapidement, avec du cache efficace, sans réclamer un utilisateur privilégié.
  • Différentes architectures de processeurs doivent pouvoir être gérées, sans émulation ni CI sur différentes architectures de serveurs.

Explorer une image sans shell

Voici une petite recette, qui sent le vestiaire, pour aller visiter une image sans shell. Commencez par voler un busybox, un bon petit binaire statique qui sert à bricoler dans des Linux trop petits.

Comme distroless est basé sur Debian, le vol de busybox se fera à partir d’une Debian de la même version.

# Préparez un volume pour accueillir le larcin
mkdir /tmp/tools
# Et branchez le dans votre bonne vieille Debian
docker run -ti --rm -v /tmp/tools:/tools debian:12-slim
# Vous êtes dans le conteneur.
# Un conteneur n'a pas d'index apt, ça prend de la place et c'est tout le temps périmé
apt update
# On va éventrer le paquet, autant faire ça dans un endroit tranquille
cd /tmp
# En suivant la voie de Debian, téléchargez le paquet dans sa version courante
apt download busybox-static
# dpkg va ouvrir le paquet, pas moyen de le faire à la main, une debian-slim n'a pas l'application ar
dpkg-deb -R busybox-static_*.deb .
# Recopiez le binaire dans le volume monté
mv bin/busybox /tools/
# Au revoir
exit

Vous pouvez maintenant lancer une image ascétique en montant busybox dans le PATH, comme le dossier /usr/local/bin prévu pour accueillir du bazar, n’existe pas dans l’image, ce sera donc usr/bin.

$ docker run -ti --rm -v /tmp/tools/busybox:/usr/bin/busybox gcr.io/distroless/static-debian12 busybox sh


BusyBox v1.35.0 (Debian 1:1.35.0-4+b3) built-in shell (ash)
Enter 'help' for a list of built-in commands.

Bazel

Distroless utilise Bazel, un outil de build très agressif sur la parallélisation et le cache (en utilisant un graphe de dépendance). Bazel est la version open source de Blaze, outil interne de Google, libéré en 2015.

Bazel utilise un dialecte spécifique, Starlack (basé sur la syntaxe de Python), mais n’hésite pas à utiliser des outils maison (évidemment compilé par Bazel).

Gestion des paquets Debian

Distroless fait le choix de faire les images sans utiliser de conteneur, ni les outils de gestion de paquets Debian. En travaillant directement avec les paquets (on pourrait ricaner en parlant de containerless), plus de soucis de multi architecture, ni même d’OS hôte, le build sera le même depuis un Mac ou un Windows (oui, le build sera fait depuis une CI, dans un conteneur, comme tout le monde).

Distroless utilise son propre module golang pour gérer des paquets Debian.

Il y a un sous-module (peu palpitant), build qui va permettre à gazelle d’écrire des fichiers bzl avec les détails sur les paquets à utiliser.

L’autre sous-module, deb, est le plus intéressant, il gère les debianeries pour récupérer, entre autres, la liste de paquets.

Instantanés Debian

Debian fournit un système d’instantanés pour retrouver les versions des paquets à une date donnée. Le site snapshot.debian.org explique son fonctionnement (on notera l’architecture moderniste : 2 sponsors fournissent un serveur apache+varnish avec une application Flask en python 3 derrière un load balancing DNS, et du certificat LE. Par contre, c’est 135 To de stockage, pour un historique depuis 2005).

Chercher un instantané pas trop vieux avec la liste des paquets

deb/snapshot.go

Il suffit d’aller sur https://snapshot.debian.org/archive/debian/?year=%d&month=%d puis de fouiller dans le HTML l’expression régulière [0-9]+T[0-9]+Z. On notera qu’il peut y avoir des jours sans snapshots, et plusieurs snapshots sur une journée, avec des horaires aléatoires.

Le commentaire dans le code précise que parfois, le dernier instantané est incomplet, et qu’il suffit de prendre le précédent. De toutes façon, le code cherche un snapshot qui a plus de deux jours (ce délai est codé en dur).

On peut ensuite aller sur https://snapshot.debian.org/archive/debian/%s/dists/%s/main/binary-%s/Packages.xz avec le snapshot, la version (bookworm pour la version courante) puis l’architecture, pour connaitre l’URL de la liste des paquets.

Ça, c’est pour main, Debian utilise plusieurs canaux, il faut recommencer pour updates et security.

On n’est pas tout à fait à l’état de l’art comme API REST, mais bon, avoir accès à tous les instantanés des différentes Debian garanti une reproductibilité et un retour en arrière possible.

Lister les paquets d’un instantané

deb/parser.go

Le format de liste de paquets Debian est plutôt carré, sauf que le parser prévoit un buffer de 1Mo pour lire des lignes trop longues.

Nomenclature des versions de paquet Debian

deb/versions.go

Debian utilise une nomenclature personnelle pour les versions de paquets, prenant en compte la version du paquet source et un versionnage des correctifs appliqués par le mainteneur. Comme on travaille avec 3 listes de paquets, il faut être capable de trier les versions pour trouver la plus récente.

Format de paquet

Les paquets Debian utilisent un format antédiluvien, ar. La commande ar fait parti de GNU binutils, mais la Linux Standard Base l’a déprécié et il sera bientôt retiré.

Chose intéressante, le tar de BSD (celui disponible sur MacOS) sait lire des archives ar, et c’est avec cet outil que Bazel dépiaute les paquets Debian dans la règle étrangement nommée locale.

Pour installer (comme une brute) un paquet Debian dans une image de conteneur, il suffit d’ouvrir le paquet (au format ar), de récupérer les données (l’archive tar, data.tar.xz), de créer le fichier status qui se retrouvera dans /var/lib/dpkg/status.d et d’en faire un tar, qui sera ajouté à votre image de base. On notera qu’à la différence des images debian-slim, la documentation que l’on trouve dans usr/share/doc est conservé.

En maintenant la base dpkg/status, les outils listant les versions, comme Syft fonctionnent sans surprises.

Ce genre d’installation ne prendra pas en compte les déclencheurs du paquet (preinst et postinst), mais bon, l’image Debian Docker officielle brime aussi le paquet en les empêchant de lancer/relancer des services.

Distroless fournit des images de base spécialisées (node, java, python…) pour faire tourner du code métier, il ne se charge même pas de la compilation du code, et donc à part quelques bibliothèques, vous n’allez pas installer grand-chose (car tout est dans votre dossier de vendoring).

Les couches d’une image conteneur

Bazel a décidé de bouder Docker en archivant rules_docker, pour mettre en avant le format d’image OCI avec rules_oci, et en disant quelques méchancetés sur son défunt concurrent. Docker utilise les images OCI de manière transparente, ce n’est donc pas très grave, et Distroless fournit des exemples de Dockerfile utilisant ses images.

Distroless assemble ses images OCI à partir d’une liste de tar, certains crée à partir de paquets Debian, d’autre sont générés (et tous sont cachés).

Des tests sont effectués avec container-structure-test, un simple outil en ligne de commande, qui va savoir travailler avec une liste de tar pour vérifier la présence/absence d’un fichier, ou avec docker, pour lancer des commandes. Les paranoïaques ont la possibilité d’utiliser gVisor pour lancer des commandes douteuses dans des conteneurs.

Les images sont créées pour chacune des architectures, puis rassemblées par rules_oci pour en faire une image multiarchitectures.

Inventaire

L’administration américaine demande d’utiliser des SBOM (Software Bill Of Material), et il existe différents formats dont SPDX (Software Package Data Exchange).

Debian s’intéresse à SPDX, surtout pour gérer les histoires de licence (tout comme le projet reuse).

Distroless a un outil pour créer une entrée SPDX à partir d’un paquet Debian : debian_spdx. À partir de la liste de paquets demandés pour créer une image, il est facile de créer un SBOM spdx : oci_image_spdx.

Dans une première itération, il était possible de pousser les SBOM specifications dans un registre OCI, mais rien ne garantit la cohérence entre cette spécification et l’image. Il est maintenant recommandé d’utiliser une Attestation spec qui utilise le format d’attestation spécifié par in-toto, et une signature.

Par convention, Docker puis OCI, stockent la description de l’image à un endroit, le manifest, qui va désigner des blobs. Les blobs sont immuables et nommés à partir de leur hachage, ce qui permet d’avoir un cache efficace, et de ne télécharger que les blobs manquants. Un blob est souvent la couche d’une image, mais le système reste ouvert sur le contenu d’un blob.

Demo time

Prenons comme exemple gcr.io/distroless/static-debian12:latest, la fameuse image de moins de 2Mo.

La convention de nommage permet de construire l’url de son manifest.

Si dans le nom d’une image il y a un ou deux éléments séparés par des slash, elle sera sur le hub Docker, si il y a 3 éléments, le premier désigne le hub, ici gcr.io (pour Google Container Registry je présume).

v2 est la version de l’API.

distroless est le groupe.

static-debian12 est l’image.

manifests par ce que je souhaite connaître la description de l’image.

latest est le tag par défaut.

$ curl -L --silent gcr.io/v2/distroless/static-debian12/manifests/latest
{
  "schemaVersion": 2,
  "mediaType": "application/vnd.oci.image.index.v1+json",
  "manifests": [
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "size": 1951,
      "digest": "sha256:285af5683714393b2702463eb92bf1290bb12401a265c2a8942ebd79ac1ac673",
      "platform": {
        "os": "linux",
        "architecture": "amd64"
      }
    },
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "size": 1951,
      "digest": "sha256:7866c847208413c5f8c6c9fa1c78dca9421f6deb1c553c8384d6cf877b592b1c",
      "platform": {
        "os": "linux",
        "architecture": "arm64",
        "variant": "v8"
      }
    },
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "size": 1951,
      "digest": "sha256:60f84cc8a1e4afa08c901b307b2aba8ae34ee088b92659114038660840eeac4a",
      "platform": {
        "os": "linux",
        "architecture": "arm",
        "variant": "v7"
      }
    },
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "size": 1951,
      "digest": "sha256:3791cb6581d68b72be439062377c67e16a21110cf68d9114e5384a510cacc159",
      "platform": {
        "os": "linux",
        "architecture": "s390x"
      }
    },
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "size": 1951,
      "digest": "sha256:8edfd8cf3c82045c05763cb2d25c0f421ccce218b69440190870a5d821fb8f95",
      "platform": {
        "os": "linux",
        "architecture": "ppc64le"
      }
    }
  ]
}

application/vnd.oci.image.index.v1+json précise que le format OCI pour décrire les index d’image est utilisé (et non le format Docker).

Il y a plusieurs manifests car l’image est multi architectures.

Prenons l’image arm64, par ce que le arm64, c’est chic, et allons chercher son manifest. Pour ne pas risquer une confusion avec un tag, on préfixe avec le type de hachage. On notera que cette fois ci, le json n’est pas indenté, tache dont s’acquitte très bien jq.

curl -L --silent gcr.io/v2/distroless/static-debian12/manifests/sha256:7866c847208413c5f8c6c9fa1c78dca9421f6deb1c553c8384d6cf877b592b1c | jq .
{
  "config": {
    "mediaType": "application/vnd.oci.image.config.v1+json",
    "size": 1870,
    "digest": "sha256:1dcec56bafe5a886201f3011e859f9e95086aee5e19faf08fcbbd3ae46de18f4"
  },
  "layers": [
    {
      "digest": "sha256:8398841c83114be91a2fb43e384042528a6d82580f0ff857997dfa7090bda899",
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "size": 104183
    },
    {
      "digest": "sha256:2b776ada03417eaa87102a617f964324df1de8967698fc4209dc1a1fbdfae8cd",
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "size": 13384
    },
    {
      "digest": "sha256:2a977872b36c1a2309fac8bd22b0fa0b3ee6efd1a25af5016ee9409beca1b3cf",
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "size": 537713
    },
    {
      "digest": "sha256:b6824ed73363f94b3b2b44084c51c31bc32af77a96861d49e16f91e3ab6bed71",
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "size": 67
    },
    {
      "digest": "sha256:7c12895b777bcaa8ccae0605b4de635b68fc32d60fa08f421dc3818bf55ee212",
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "size": 188
    },
    {
      "digest": "sha256:33e068de264953dfdc9f9ada207e76b61159721fd64a4820b320d05133a55fb8",
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "size": 122
    },
    {
      "digest": "sha256:5664b15f108bf9436ce3312090a767300800edbbfd4511aa1a6d64357024d5dd",
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "size": 168
    },
    {
      "digest": "sha256:27be814a09ebd97fac6fb7b82d19f117185e90601009df3fbab6f442f85cd6b3",
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "size": 93
    },
    {
      "digest": "sha256:4aa0ea1413d37a58615488592a0b827ea4b2e48fa5a77cf707d0e35f025e613f",
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "size": 385
    },
    {
      "digest": "sha256:da7816fa955ea24533c388143c78804c28682eef99b4ee3723b548c70148bba6",
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "size": 321
    },
    {
      "digest": "sha256:9aee425378d2c16cd44177dc54a274b312897f5860a8e78fdfda555a0d79dd71",
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "size": 130495
    }
  ],
  "mediaType": "application/vnd.oci.image.manifest.v1+json",
  "schemaVersion": 2
}

Cette fois ci, on a des couches d’image OCI sous forme de tar compressé avec gzip : application/vnd.oci.image.layer.v1.tar+gzip.

La description des couches se trouve dans base/base.bzl

tars = [
   deb.package(arch, distro, "base-files"),
   deb.package(arch, distro, "netbase"),
   deb.package(arch, distro, "tzdata"),
   "//common:rootfs",
   "//common:passwd",
   "//common:home",
   "//common:group",
   # Create /tmp, too many things assume it exists.
   # tmp.tar has a /tmp with the correct permissions 01777
   "//common:tmp",
   ":nsswitch.tar",
   "//common:os_release_" + distro,
   "//common:cacerts_" + distro + "_" + arch,
]

On notera que les couches toutes petites contiennent de la conf, et sont les mêmes, indépendamment de l’architecture. Bricolez la commande curl pour le vérifier, c’est instructif.

Une convention de nommage, un peu crado, permet de créer l’url de l’attestation d’une image : il faut ajouter .att à la fin, et surtout remplacer le : qui sépare le type de hachage de sa valeur, par un -. Une convention similaire permet de trouver la signature, avec le suffixe .sig.

curl -L --silent gcr.io/v2/distroless/static-debian12/manifests/sha256-7866c847208413c5f8c6c9fa1c78dca9421f6deb1c553c8384d6cf877b592b1c.att | jq .
{
  "schemaVersion": 2,
  "mediaType": "application/vnd.oci.image.manifest.v1+json",
  "config": {
    "mediaType": "application/vnd.oci.image.config.v1+json",
    "size": 243,
    "digest": "sha256:1f3ca242c4197b2abe2db3f9d162b4719fccd6261e2b45819c9d51361f340b8a"
  },
  "layers": [
    {
      "mediaType": "application/vnd.dsse.envelope.v1+json",
      "size": 12944,
      "digest": "sha256:c42ef2b6fa9e3d15aad198c23a147bc2fbc474af3c607ffe05ef5e934cb7d977",
      "annotations": {
        "dev.cosignproject.cosign/signature": "",
        "dev.sigstore.cosign/bundle": "{\"SignedEntryTimestamp\":\"MEUCIAz/DSGCZlu0Zm/sN4BJf4MmEzLnZFANmlHy50k8I/bhAiEAj8keEKbxX/VYZAfjZIsgbMkGDRlo+yO+j/3Skmgh/qY=\",\"Payload\":{\"body\":\"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaW50b3RvIiwic3BlYyI6eyJjb250ZW50Ijp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiJjNDJlZjJiNmZhOWUzZDE1YWFkMTk4YzIzYTE0N2JjMmZiYzQ3NGFmM2M2MDdmZmUwNWVmNWU5MzRjYjdkOTc3In0sInBheWxvYWRIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiNDU1OTc4MDgyNDU0OGRlNDM5NzdlZDJhN2Q2NGE2YWQ1MDA4OTgwZTcxY2Q5ZDBmN2Y2NzBmMTU2YWM3MjIyYyJ9fSwicHVibGljS2V5IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVTTBla05EUVcxcFowRjNTVUpCWjBsVlUyNWxaVFYyWTBSUVIzSnpTa2h4SzIxcmFVUTBjbTVRWkdjd2QwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcFJkMDE2UlhoTlZHTXhUMVJGTUZkb1kwNU5hbEYzVFhwRmVFMVVaM2RQVkVVd1YycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVU0ZFM5SmVrMHZkVlp5YjB4cGFGTmpiVzQzV1dKaGJuRkZVVWxyVWpKeU9HNDRXbFVLY1ZsbVJrTmFaaTlaV1ZoT1RtdDBMMDh6VEVvcmJURmpTRlpJWjBoR2IwVlNZamxVZVdrd1pUaGpXRGRTZGxGblZqWlBRMEZaWTNkblowZEVUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlZPU3k4d0NrcE9SR1V4TUc5SU5VUjZhMGRPVW5sT1oyMU1abUpOZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDA5QldVUldVakJTUVZGSUwwSkROSGRNU1VWeFlUSldOV0pIVm5wak1FSnJZVmhPTUdOdE9YTmFXRTU2VEcxc2FHSlROVzVqTWxaNVpHMXNhZ3BhVjBacVdUSTVNV0p1VVhWWk1qbDBUVU5yUjBOcGMwZEJVVkZDWnpjNGQwRlJSVVZITW1nd1pFaENlazlwT0haWlYwNXFZak5XZFdSSVRYVmFNamwyQ2xveWVHeE1iVTUyWWxSQmNrSm5iM0pDWjBWRlFWbFBMMDFCUlVsQ1FqQk5SekpvTUdSSVFucFBhVGgyV1ZkT2FtSXpWblZrU0UxMVdqSTVkbG95ZUd3S1RHMU9kbUpVUTBKcFVWbExTM2RaUWtKQlNGZGxVVWxGUVdkU04wSklhMEZrZDBJeFFVNHdPVTFIY2tkNGVFVjVXWGhyWlVoS2JHNU9kMHRwVTJ3Mk5Bb3phbmwwTHpSbFMyTnZRWFpMWlRaUFFVRkJRbXBwTm5KTlExRkJRVUZSUkVGRldYZFNRVWxuU1ZSWFkwOU5VbWhJVUdScmJtcEdZV2wwWVZaUlJ5OTVDbFZuVFVSR05XRTNlVWxWT1hSaEsya3pRbEZEU1VoMUwxcHRZemRLY0hSSmJ6bDFNRGxKWjBKd2VEUXdhMFYyT1ZjMk9XOU1NRU5sU2xKcmNucEhkSGdLVFVGdlIwTkRjVWRUVFRRNVFrRk5SRUV5YTBGTlIxbERUVkZEVDJob2NUZGhMM1F2TXpKS05EWnZMMmx0WmtjcmVIWkhlbEp0UWxWQllubFFjbVJFYXdwT1dVRnVVa3gwYVZoWWRHaFRhRkJHTTAxc1NtdGtaRmR4YjJ0RFRWRkRUR05uUVRNdlIycFRTakJvUkVSeE9GQkNNMnBvWkcwM1pFbHBhMlF6TUdGMUNrSTVNMVpFUzJGSWJXNWpjSEIwTW05a2NucElWRkU1U21sNE4zUnBUMmM5Q2kwdExTMHRSVTVFSUVORlVsUkpSa2xEUVZSRkxTMHRMUzBLIn19\",\"integratedTime\":1710179954,\"logIndex\":77294834,\"logID\":\"c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d\"}}",
        "dev.sigstore.cosign/certificate": "-----BEGIN CERTIFICATE-----\nMIIC4zCCAmigAwIBAgIUSnee5vcDPGrsJHq+mkiD4rnPdg0wCgYIKoZIzj0EAwMw\nNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRl\ncm1lZGlhdGUwHhcNMjQwMzExMTc1OTE0WhcNMjQwMzExMTgwOTE0WjAAMFkwEwYH\nKoZIzj0CAQYIKoZIzj0DAQcDQgAE8u/IzM/uVroLihScmn7YbanqEQIkR2r8n8ZU\nqYfFCZf/YYXNNkt/O3LJ+m1cHVHgHFoERb9Tyi0e8cX7RvQgV6OCAYcwggGDMA4G\nA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUNK/0\nJNDe10oH5DzkGNRyNgmLfbMwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4Y\nZD8wOAYDVR0RAQH/BC4wLIEqa2V5bGVzc0BkaXN0cm9sZXNzLmlhbS5nc2Vydmlj\nZWFjY291bnQuY29tMCkGCisGAQQBg78wAQEEG2h0dHBzOi8vYWNjb3VudHMuZ29v\nZ2xlLmNvbTArBgorBgEEAYO/MAEIBB0MG2h0dHBzOi8vYWNjb3VudHMuZ29vZ2xl\nLmNvbTCBiQYKKwYBBAHWeQIEAgR7BHkAdwB1AN09MGrGxxEyYxkeHJlnNwKiSl64\n3jyt/4eKcoAvKe6OAAABji6rMCQAAAQDAEYwRAIgITWcOMRhHPdknjFaitaVQG/y\nUgMDF5a7yIU9ta+i3BQCIHu/Zmc7JptIo9u09IgBpx40kEv9W69oL0CeJRkrzGtx\nMAoGCCqGSM49BAMDA2kAMGYCMQCOhhq7a/t/32J46o/imfG+xvGzRmBUAbyPrdDk\nNYAnRLtiXXthShPF3MlJkddWqokCMQCLcgA3/GjSJ0hDDq8PB3jhdm7dIikd30au\nB93VDKaHmncppt2odrzHTQ9Jix7tiOg=\n-----END CERTIFICATE-----\n",
        "dev.sigstore.cosign/chain": "-----BEGIN CERTIFICATE-----\nMIICGjCCAaGgAwIBAgIUALnViVfnU0brJasmRkHrn/UnfaQwCgYIKoZIzj0EAwMw\nKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y\nMjA0MTMyMDA2MTVaFw0zMTEwMDUxMzU2NThaMDcxFTATBgNVBAoTDHNpZ3N0b3Jl\nLmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0C\nAQYFK4EEACIDYgAE8RVS/ysH+NOvuDZyPIZtilgUF9NlarYpAd9HP1vBBH1U5CV7\n7LSS7s0ZiH4nE7Hv7ptS6LvvR/STk798LVgMzLlJ4HeIfF3tHSaexLcYpSASr1kS\n0N/RgBJz/9jWCiXno3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYB\nBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU39Ppz1YkEZb5qNjp\nKFWixi4YZD8wHwYDVR0jBBgwFoAUWMAeX5FFpWapesyQoZMi0CrFxfowCgYIKoZI\nzj0EAwMDZwAwZAIwPCsQK4DYiZYDPIaDi5HFKnfxXx6ASSVmERfsynYBiX2X6SJR\nnZU84/9DZdnFvvxmAjBOt6QpBlc4J/0DxvkTCqpclvziL6BCCPnjdlIB3Pu3BxsP\nmygUY7Ii2zbdCdliiow=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMw\nKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y\nMTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3Jl\nLmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7\nXeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxex\nX69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92j\nYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRY\nwB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQ\nKsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCM\nWP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9\nTNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ\n-----END CERTIFICATE-----",
        "predicateType": "https://spdx.dev/Document"
      }
    }
  ]
}

L’attestation contient des layers, comme une image, il ne faut pas se laisser distraire par la quantité d’annotations utilisée par la signature de cosign. Si il y a des layers, c’est qu’il y a des blobs (un layer et un blob, en l’occurrence).

curl -L --silent gcr.io/v2/distroless/static-debian12/blobs/sha256:c42ef2b6fa9e3d15aad198c23a147bc2fbc474af3c607ffe05ef5e934cb7d977 | jq .
{
  "payloadType": "application/vnd.in-toto+json",
  "payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5c
  […]
  uc2hpcFR5cGVcIjpcIkRFUEVORFNfT05cIn1dfSJ9",
  "signatures": [
    {
      "keyid": "",
      "sig": "MEUCIEdF83LnL01ToM4pkDvgnMkqvzRHwT3viDb/bOhqYA1JAiEAlgZR0Lei6lng9itoHr/Iw1xMllI67DngGtyg0o+hP4A="
    }
  ]
}

On a un document in-toto avec un gros paté en base64 (que je n’affiche que partiellement), que jq va traiter pour nous.

$ curl -L --silent gcr.io/v2/distroless/static-debian12/blobs/sha256:c42ef2b6fa9e3d15aad198c23a147bc2fbc474af3c607ffe05ef5e934cb7d977 | jq '.payload|@base64d|fromjson'

On obtient un prédicat de type https://spdx.dev/Document, le sujet qui désigne l’image, avec un hash pour préciser la version. Le corps du prédicat, qui contient le SPDX attendu est sous forme de json dans du json (sans base64 cette fois ci).

$ curl -L --silent gcr.io/v2/distroless/static-debian12/blobs/sha256:c42ef2b6fa9e3d15aad198c23a147bc2fbc474af3c607ffe05ef5e934cb7d977 | jq '.payload|@base64d|fromjson|.predicate|fromjson'
{
  "spdxVersion": "SPDX-2.3",
  "dataLicense": "CC0-1.0",
  "SPDXID": "SPDXRef-DOCUMENT",
  "name": "//base:static_root_arm64_debian12",
  "documentNamespace": "http://spdx.org/spdxdocs/distroless/-slash--slash-base-colon-static-underscore-root-underscore-arm64-underscore-debian12",
  "creationInfo": {
    "licenseListVersion": "NOASSERTION",
    "creators": ["Organization: distroless"],
    "created": "1970-01-01T00:00:00Z"
  },
  "packages": [
    {
      "name": "//base:static_root_arm64_debian12",
      "SPDXID": "SPDXRef--slash--slash-base-colon-static-underscore-root-underscore-arm64-underscore-debian12",
      "downloadLocation": "NOASSERTION",
      "copyrightText": "NOASSERTION"
    },
    {
      "name": "base-files",
      "SPDXID": "SPDXRef-arm64-underscore-debian12-underscore-base-files",
      "versionInfo": "12.4+deb12u5",
      "supplier": "Person: Santiago Vila \\u003csanvila@debian.org\\u003e",
      "downloadLocation": "https://snapshot-cloudflare.debian.org/archive/debian/20240210T223313Z/pool/main/b/base-files/base-files_12.4+deb12u5_arm64.deb",
      "checksums": [
        {
          "algorithm": "SHA256",
          "checksumValue": "ca5e69b38214de267d7d59bf4d0c1abd10987abacb5c9bfaf72b178bee883d1b"
        }
      ],
      "copyrightText": "This is the Debian prepackaged version of the Debian Base System\nMiscellaneous files. These files were written by Ian Murdock\n<imurdock@debian.org> and Bruce Perens <bruce@pixar.com>.\n\nThis package was first put together by Bruce Perens <Bruce@Pixar.com>,\nfrom his own sources.\n\nThe GNU Public Licenses in /usr/share/common-licenses were taken from\nftp.gnu.org and are copyrighted by the Free Software Foundation, Inc.\n\nThe Artistic License in /usr/share/common-licenses is the one coming\nfrom Perl and its SPDX name is \"Artistic License 1.0 (Perl)\".\n\n\nCopyright (C) 1995-2011 Software in the Public Interest.\n\nThis program is free software; you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation; either version 2 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nOn Debian systems, the complete text of the GNU General\nPublic License can be found in `/usr/share/common-licenses/GPL'.\n",
      "summary": "Debian base system miscellaneous files",
      "description": "Debian base system miscellaneous files\nThis package contains the basic filesystem hierarchy of a Debian system, and\nseveral important miscellaneous files, such as /etc/debian_version,\n/etc/host.conf, /etc/issue, /etc/motd, /etc/profile, and others,\nand the text of several common licenses in use on Debian systems.",
      "externalRefs": [
        {
          "referenceCategory": "PACKAGE-MANAGER",
          "referenceType": "purl",
          "referenceLocator": "pkg:deb/debian/base-files@12.4+deb12u5?arch=arm64"
        }
      ]
    },
    {
      "name": "@arm64_debian12_base-files//:spdx",
      "SPDXID": "SPDXRef--at-arm64-underscore-debian12-underscore-base-files-slash--slash--colon-spdx",
      "downloadLocation": "NOASSERTION",
      "copyrightText": "NOASSERTION",
      "description": "Generated from base-files@12.4+deb12u5"
    },
    {
      "name": "netbase",
      "SPDXID": "SPDXRef-arm64-underscore-debian12-underscore-netbase",
      "versionInfo": "6.4",
      "supplier": "Person: Marco d'Itri \\u003cmd@linux.it\\u003e",
      "downloadLocation": "https://snapshot-cloudflare.debian.org/archive/debian/20240210T223313Z/pool/main/n/netbase/netbase_6.4_all.deb",
      "checksums": [
        {
          "algorithm": "SHA256",
          "checksumValue": "29b23c48c0fe6f878e56c5ddc9f65d1c05d729360f3690a593a8c795031cd867"
        }
      ],
      "copyrightText": "Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\nComment:\n This package was created by Peter Tobias tobias@et-inf.fho-emden.de on\n Wed, 24 Aug 1994 21:33:28 +0200 and maintained by Anthony Towns\n <ajt@debian.org> until 2001.\n It is currently maintained by Marco d'Itri <md@linux.it>.\n\nFiles: *\nCopyright:\n Copyright (c) 1994-1998 Peter Tobias\n Copyright (c) 1998-2001 Anthony Towns\n Copyright (c) 2002-2022 Marco d'Itri\nLicense: GPL-2\n This program is free software; you can redistribute it and/or modify\n it under the terms of the GNU General Public License, version 2, as\n published by the Free Software Foundation.\n .\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n GNU General Public License for more details.\n .\n You should have received a copy of the GNU General Public License along\n with this program; if not, write to the Free Software Foundation,\n Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n .\n On Debian systems, the complete text of the GNU General Public License\n version 2 can be found in '/usr/share/common-licenses/GPL-2'.\n",
      "summary": "Basic TCP/IP networking system",
      "description": "Basic TCP/IP networking system\nThis package provides the necessary infrastructure for basic TCP/IP based\nnetworking.\n.\nIn particular, it supplies common name-to-number mappings in /etc/services,\n/etc/rpc, /etc/protocols and /etc/ethertypes.",
      "externalRefs": [
        {
          "referenceCategory": "PACKAGE-MANAGER",
          "referenceType": "purl",
          "referenceLocator": "pkg:deb/debian/netbase@6.4?arch=all"
        }
      ]
    },
    {
      "name": "@arm64_debian12_netbase//:spdx",
      "SPDXID": "SPDXRef--at-arm64-underscore-debian12-underscore-netbase-slash--slash--colon-spdx",
      "downloadLocation": "NOASSERTION",
      "copyrightText": "NOASSERTION",
      "description": "Generated from netbase@6.4"
    },
    {
      "name": "tzdata",
      "SPDXID": "SPDXRef-arm64-underscore-debian12-underscore-tzdata",
      "versionInfo": "2024a-0+deb12u1",
      "supplier": "Person: GNU Libc Maintainers \\u003cdebian-glibc@lists.debian.org\\u003e",
      "downloadLocation": "https://snapshot-cloudflare.debian.org/archive/debian/20240210T223313Z/pool/main/t/tzdata/tzdata_2024a-0+deb12u1_all.deb",
      "checksums": [
        {
          "algorithm": "SHA256",
          "checksumValue": "0ca0baec1fca55df56039047a631fc1541c5a44c1c4879d553aaa3a70844eb12"
        }
      ],
      "homepage": "https://www.iana.org/time-zones",
      "copyrightText": "Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\nSource: https://www.iana.org/time-zones\nUpstream-Contact: The Internet Assigned Numbers Authority (IANA)\n                  Commentary should be addressed to tz@iana.org\n\nFiles: *\nCopyright: The Internet Assigned Numbers Authority (IANA)\nLicense: public-domain\n This database is in the public domain.\n",
      "summary": "time zone and daylight-saving time data",
      "description": "time zone and daylight-saving time data\nThis package contains data required for the implementation of\nstandard local time for many representative locations around the\nglobe. It is updated periodically to reflect changes made by\npolitical bodies to time zone boundaries, UTC offsets, and\ndaylight-saving rules.",
      "externalRefs": [
        {
          "referenceCategory": "PACKAGE-MANAGER",
          "referenceType": "purl",
          "referenceLocator": "pkg:deb/debian/tzdata@2024a-0+deb12u1?arch=all"
        }
      ]
    },
    {
      "name": "@arm64_debian12_tzdata//:spdx",
      "SPDXID": "SPDXRef--at-arm64-underscore-debian12-underscore-tzdata-slash--slash--colon-spdx",
      "downloadLocation": "NOASSERTION",
      "copyrightText": "NOASSERTION",
      "description": "Generated from tzdata@2024a-0+deb12u1"
    }
  ],
  "relationships": [
    {
      "spdxElementId": "SPDXRef-DOCUMENT",
      "relatedSpdxElement": "SPDXRef--slash--slash-base-colon-static-underscore-root-underscore-arm64-underscore-debian12",
      "relationshipType": "DESCRIBES"
    },
    {
      "spdxElementId": "SPDXRef--at-arm64-underscore-debian12-underscore-base-files-slash--slash--colon-spdx",
      "relatedSpdxElement": "SPDXRef-arm64-underscore-debian12-underscore-base-files",
      "relationshipType": "GENERATED_FROM"
    },
    {
      "spdxElementId": "SPDXRef--slash--slash-base-colon-static-underscore-root-underscore-arm64-underscore-debian12",
      "relatedSpdxElement": "SPDXRef--at-arm64-underscore-debian12-underscore-base-files-slash--slash--colon-spdx",
      "relationshipType": "DEPENDS_ON"
    },
    {
      "spdxElementId": "SPDXRef--at-arm64-underscore-debian12-underscore-netbase-slash--slash--colon-spdx",
      "relatedSpdxElement": "SPDXRef-arm64-underscore-debian12-underscore-netbase",
      "relationshipType": "GENERATED_FROM"
    },
    {
      "spdxElementId": "SPDXRef--slash--slash-base-colon-static-underscore-root-underscore-arm64-underscore-debian12",
      "relatedSpdxElement": "SPDXRef--at-arm64-underscore-debian12-underscore-netbase-slash--slash--colon-spdx",
      "relationshipType": "DEPENDS_ON"
    },
    {
      "spdxElementId": "SPDXRef--at-arm64-underscore-debian12-underscore-tzdata-slash--slash--colon-spdx",
      "relatedSpdxElement": "SPDXRef-arm64-underscore-debian12-underscore-tzdata",
      "relationshipType": "GENERATED_FROM"
    },
    {
      "spdxElementId": "SPDXRef--slash--slash-base-colon-static-underscore-root-underscore-arm64-underscore-debian12",
      "relatedSpdxElement": "SPDXRef--at-arm64-underscore-debian12-underscore-tzdata-slash--slash--colon-spdx",
      "relationshipType": "DEPENDS_ON"
    }
  ]
}

On notera que SPDX reprend le principe des triplets de RDF qui, promis juré, devait servir de base au web 3.0. Ces relationships décrivent un graphe avec des liaisons nommées (et des noms de nodes rigolos, un genre d’échappement littéral).

On notera aussi que le moulinage des informations issus des paquets Debian ont des soucis bizzaries d’UTF8 avec des \\u003c et des \\u003e.

Le SPDX permet de désigner tous les paquets utilisés, avec leur version, leur URL figée dans le temps, et le nom du responsable. Tout ce qu’il faut pour lancer une moulinette de chasse au CVE, pour mettre à jour la couche impactée sans devoir refaire les couches supérieures.

Outils pour gérer les images de conteneur

C’est instructif de manipuler les images avec curl et jq, mais il existe aussi des outils civilisés pour manipuler des registres d’images de conteneur : crane ou skopeo.

Pour vérifier les signatures des images, l’outil de référence est cosign.

Autres projets d’images minimalistes

Wolfi

Chainguard, les gens qui participent au développement de sigstore, propose Wolfi, une distribution Linux basée sur apk (comme Alpine), mais avec glibc, ciblant uniquement les conteneurs, et donc sans kernel, sans init.

Les images de base Wolfi peuvent s’utiliser “comme avant”, avec un Dockerfile et apk, comme on le ferait avec une image Alpine.

Les avant-gardistes, eux, construiront leurs images Wolfi avec apko

Apko

Apko est un outil minimaliste écrit en go, utilisable depuis un conteneur ou depuis un OS (Linux ou non), qui va créer l’archive (multi architecture) d’une image de conteneur et plein de SBOMs, à partir d’un YAML et d’un dépôt apk. Apko peut même pousser l’image dans un registre.

Comme Apko est un rebelle, il peut créer des fat containers avec s6 comme init.

Apko se fiche de Wolfi, de Alpine, de Bazel, de Docker, de Podman, de Containerd, de Kubernetes et de plein d’autres choses. Mais comme il est poli, il sait aussi bosser avec eux.

Apko fait des builds déterministes, multi architecture, et donc ne lance pas de commandes pendant le build. En bon monomaniaque, Apko mise tout sur apk, et donc pousse a utiliser son projet melange pour créer les apk qui seront utilisés par Apko. Ou vous pouvez truander avec un Dockerfile, à l’ancienne.

Melange

Melange est un autre projet de Chainguard pour créer des paquets apk multi architectures à partir d’un incontournable YAML. Pour être utilisé hors des Linux basés sur apk, Chainguard a réimplementé apk en go.

Le projet est tout frais (en version 0.6), peu documenté, mais prometeur,actif depuis 2 ans, avec une belle liste de pull requests.

La création pour les architectures non-natives passe par binfmt_misc qui s’appuie sur qemu.

Les builds classiques sont gérés (cargo, rust, npm, make install …), et il est même possible de créer un build Melange à partir d’un projet ruby, python ou apk.

Le code parle de conteneurs Docker/k8s/dagger/bubblewrap mais pas la documentation. Pour les développeurs Mac, il y a une intégration à Lima.

Les builds simples tombent en marche, de manière assez magique, mais j’attends une bonne bagarre avec le débug d’un build qui ne passe pas pour avoir un avis plus affirmé.

Chainguard croit très fort à apk comme artéfact universel, brique de base pour composer un conteneur.

Ko

Golang est le “fruit bas” des langages à déployer dans un conteneur. Golang crée un gros binaire statique, et peut cross-compiler très simplement.

Google a crée ko, avant de lui dédier son propre groupe dans Github et de postuler comme projet CNCF (il est niveau “bac à sable” pour l’instant).

On retrouve les arguments du moment : build natif, SBOM, YAML, publication dans un registre.

Dans les pinaillages, Ko recommande de ne pas pas utiliser cgo, et demande à ce que les éventuelles dépendances soient fournies dans l’image de base. Par contre, Ko sait déployer des assets.

Jib

Jib, du même Google, permet de créer des images de conteneurs à partir de projets Java.

Jib a la tâche encore plus facile que Ko, Java est par principe multi architectures. L’image de base est OpenJDK, mais il est possible de paramétrer ce choix.

Jib est une extension pour Maven ou Gradle, ou s’utilise comme outil indépendant et il n’utilise pas de conteneur.

Tout le travail de Jib est de déposer les différents jar sur différentes couches d’images de conteneurs, et lors d’un nouveau build de l’application, seuls les jar modifiés seront redéployés sur leur couche respective.

La suite

L’OCI continue de taper sur Docker pour lui expliquer la différence entre implémentation et spécification.

Des fois, c’est de mauvaise foi (à chaque fois que Redhat vante son écosystème concurrent), souvent c’est justifié. L’absence de marché clair pour Docker inc est triste, et la constance de Docker Desktop a demandé des sous (l’achat d’une licence) sur presque toutes les nouvelles commandes n’améliore pas leur karma.

Le Dockerfile reste le format standard et universel pour créer des conteneurs, mais la norme est le format de l’image. Il est normal de pouvoir créer une image de conteneur comme on le souhaite (comme umoci le propose), et d’explorer d’autres pistes. Il est par exemple intéressant de pouvoir travailler couche par couche pour composer une image, en ne changeant que ce qu’il y a de nouveau. La gestion des architectures multiples est une vraie question auquel Docker ne répond que partiellement.

Pouvoir débuguer en local/staging est normal, mais l’outillage doit être différent en production. Vous n’avez pas forcément la main sur le nombre d’instances de votre conteneur, il y a peu de chance que vous y ayez un accès privilégié, et vous n’allez pas attendre que l’incident revienne.

La généralisation des traces et des journaux structurés sont là depuis longtemps et maintenant indispensable. La vague ebpf prolonge ce mouvement et va offrir plus de visibilité sur le comportement des applications en production.

Les offres d’hébergement de conteneur de haut niveau, comme Fly.io propose une interface simple et va masquer les détails d’implémentation, pour plutôt proposer une intégration fine avec un ensemble de services pour le client ou le développeur.

Le postulat d’Heroku est toujours valide : “tu git push, ça finit en prod”, sauf que maintenant c’est explicite, normalisé, et sécurisé.

blogroll

social