Spécification Technique : Réutilisation des Images par Source
Statut : Proposé (rédigé avant implémentation, conformément à la démarche déjà suivie pour les specs 08 à 13) Principe cadre : Conforme à
00-architecture-principles-substitutability.md. Prolonge13-image-cache-offload.md, qui a rendu le cache d'images durable et borné mais pas réutilisable. Date : 2026-10-05 Auteur : Équipe Atelier
1. Constat, vérifié empiriquement
Mesuré le 2026-10-05 sur kind-atelier-dev (controller et api-server lancés depuis les sources, commit be66aa8), avec https://github.com/microsoft/vscode-remote-try-python, 1 CPU et 768 Mi. Une seule exécution par mesure : ce sont des ordres de grandeur.
| Mesure | Résultat |
|---|---|
| Premier Workshop : création → microVM démarrée | environ 110 s, dont 105 s de build (le controller, hors cluster, n'a confirmé Running qu'une fois la route vers le réseau des pods ajoutée) |
Second Workshop, même dépôt, même révision : création → Running |
106 s, dont 90 s en BuildingImage |
| Pod parent créé → microVM démarrée, image prête | environ 4,5 s |
| Reprise d'un Workshop suspendu | environ 16 s |
Le second Workshop reconstruit entièrement une image qui existe déjà. Quatre causes indépendantes, chacune suffisante à elle seule :
- Le controller ne regarde que le Workshop courant. Dans la branche
Runningde la boucle de réconciliation (crates/controller/src/reconcile.rs), tant questatus.imageDigestde CE Workshop est absent,ensure_image_build_jobcrée un Job pour lui. Rien ne demande si un autre Workshop a déjà construit la même source. - L'image est nommée d'après le Workshop.
crates/image-builder/src/main.rspousse vers<registre>/atelier-workshops/<nom du workshop>:latest: la référence décrit qui construit, pas ce qui est construit. - Le cache de couches suit ce même nom.
crates/builder-vm-init/src/main.rsdériveENVBUILDER_CACHE_REPOde cette référence : il repart vide pour chaque nouveau Workshop. Le second build a pris 90 s contre 105 s pour le premier : aucun gain qui ressemble à une réutilisation de couches. - La clé du cache est l'empreinte du fichier produit.
publish_to_cacheindexe par le SHA-256 durootfs.ext4fini. Deux builds de la même source ne produisent pas les mêmes octets (identifiants du système de fichiers, horodatages) : la clé diffère, et le cache « content-addressed » ne peut pas reconnaître un rebuild. La spec 13 l'avait déjà constaté (§3, « Correction ») sans pouvoir le résoudre : le digest n'est connu qu'après le build.
Ce qui ne fait pas obstacle : rien de secret ni de propre à un Workshop n'est gravé dans l'image. La clé SSH autorisée et le mot de passe de session sont récupérés au démarrage auprès du serveur de métadonnées du pod (crates/net-proxy/src/metadata.rs). Une image est donc partageable telle quelle.
Coût supplémentaire, hors build : crates/firecracker/src/vm.rs déclare le rootfs comme ressource copiée (MovedResourceType::Copied) et l'attache en écriture. L'image de l'essai pesait 2,5 Gio ; sa copie est une part plausible, mais non mesurée séparément, des 16 s entre Provisioning et Running.
2. Objectifs / non-objectifs
Dans le périmètre :
1. Une source déjà construite n'est jamais reconstruite : le second Workshop d'une même source passe directement à Provisioning.
2. Plusieurs Workshops de la même source créés en même temps attendent un seul build.
3. Deux révisions proches d'un même dépôt partagent leurs couches inchangées.
Hors périmètre (reporté, voir §5) :
- Rootfs en lecture seule avec une couche inscriptible par VM, pour ne plus copier l'image à chaque démarrage : changement plus profond (séquence de boot du guest, format des snapshots), qui mérite sa propre spécification.
- Démarrage de plusieurs Workshops depuis un même snapshot modèle.
- rootfs.ext4 reproductible à l'octet près : utile, mais superflu dès que le build est indexé par sa source.
3. Décision d'architecture
3.1. Une clé de source
La clé de source d'une image est l'empreinte de tout ce qui détermine son contenu :
- l'URL du dépôt, normalisée ;
- le commit vers lequel la révision se résout.
HEAD, valeur par défaut deDevcontainerSource.revision, n'est pas une clé : elle doit être résolue (git ls-remote) avant toute recherche ; - le chemin du
devcontainer.json; - une version des injections propres à
image-builder(sshd, ttyd, code-server, init, configuration du proxy), incrémentée quand elles changent, pour invalider les anciennes images ; - ce qui entre dans le rootfs depuis le cluster : le bundle de CA d'entreprise (spec 15), l'architecture du guest.
3.2. L'image devient une ressource à part entière
Nouvelle ressource personnalisée WorkshopImage, nommée par la clé de source, avec une phase (Building, Ready, Failed) et le digest produit. La boucle de réconciliation passe de « pas de digest → je construis » à :
- résoudre la révision et calculer la clé de source ;
- lire ou créer la
WorkshopImagede cette clé ; Ready→ recopier son digest dansstatus.imageDigestet passer àProvisioning;Building→ attendre (requeue) ;- absente → la créer, et avec elle l'unique Job de build, propriété de l'image et non d'un Workshop.
La concurrence est réglée par construction : la création de la ressource est l'unique point de décision, et le serveur d'API Kubernetes garantit qu'un seul des Workshops concurrents la crée.
Le PVC et S3 continuent de stocker les fichiers par digest de sortie (spec 13). La passe d'éviction (crates/controller/src/eviction.rs) gagne une règle simple : une entrée est évinçable quand aucune WorkshopImage ne la référence, ou quand celle qui la référence n'a servi à aucun Workshop depuis un délai configurable.
3.3. Des artefacts de registre nommés par la clé
Pousser vers <registre>/atelier-images/<clé de source> et utiliser un ENVBUILDER_CACHE_REPO commun. Indépendamment de §3.2, ce seul changement permet à une révision différente du même dépôt de réutiliser les couches inchangées.
4. Risques et questions ouvertes
- Révisions mobiles. Un Workshop créé sur
maindoit-il garder son image jusqu'à sa recréation, ou être reconstruit quand la branche avance ? Proposition : la clé est résolue une fois, à la création du Workshop, et consignée dans son statut ; un rafraîchissement est une action explicite. - Egress au moment du build. Le build passe aujourd'hui par
spec.egressAllowlistdu Workshop (constaté pendant l'essai : avec une liste vide, la microVM builder ne joint pasgithub.comet le build échoue). Avec une image partagée, la liste de quel Workshop s'applique ? Une politique d'egress de build portée par l'image répondrait aussi au cas d'un environnement qui a besoin du réseau pour se construire et d'aucun pour s'exécuter. - Dépôts privés. Les identifiants de build sont résolus par Workshop (
resolve_git_credentials). Une image construite avec les accès d'un groupe ne doit pas être servie à un autre : le groupe propriétaire entre dans la clé de source, ou la lecture d'uneWorkshopImageest soumise à la même vérification d'accès que le dépôt. - Clone de l'espace de travail.
ensure_workspace_clonegrave le dépôt cible dans l'image et installe un rafraîchissement au démarrage. Cela reste correct avec une image indexée par commit, mais doit être revérifié. - Build en échec. Une
WorkshopImageenFailedne doit pas bloquer définitivement tous les Workshops de cette source : prévoir une nouvelle tentative bornée, ou une expiration de l'état d'échec.
5. Suite envisagée, hors de cette spec
- Rootfs en lecture seule et couche inscriptible par VM : le démarrage cesse de dépendre de la taille de l'image, et la taille de la couche devient le quota disque du Workshop.
- Snapshot modèle par image, restauré dans chaque nouveau Workshop.
- Recalage de l'horloge du guest à la reprise : constaté pendant le même essai, après une suspension de 40 s l'horloge du guest retardait d'environ 45 s sur celle de l'hôte.