0088 — Portabilité : architectures (arm64/x86_64), OS et libc
Contexte
Section intitulée « Contexte »Le dépôt est développé sur macOS/arm64 (Apple Silicon) et déployé sur
Linux/x86_64 ; le banc de validation cluster (Lima) tourne en arm64. Trois
plans de portabilité coexistent donc sans qu’aucun document ne pose la posture
d’ensemble — chaque Dockerfile, chaque dépendance native et chaque hook git
décidait dans son coin. Un audit transverse (arm64 ↔ x86_64, OS, libc) a relevé
des écarts concrets, dont certains silencieux (verts en CI parce que la CI ne
teste qu’une seule cible).
Trois notions reviennent et méritent d’être fixées à leur première occurrence (charte 0052, R2) :
- libc : la bibliothèque C standard liée par les binaires natifs. Deux
implémentations courantes sous Linux : glibc (Debian, Ubuntu —
python:slim) et musl (Alpine —node:alpine). Un binaire précompilé pour glibc ne se charge pas tel quel sur musl, et inversement. - manifest-list (ou image index OCI) : un manifeste Docker qui, sous un même
tag/digest, agrège plusieurs images une par couple architecture/OS. Au
pull, le démon résout l’entrée correspondant à la plateforme hôte. Les images de base officielles (node,python) en sont — c’est ce qui rend leurFROMportable arm64/x86_64 sans effort. - manifest-list au build : à l’inverse, produire une image multi-architecture
exige
docker buildxavec--platform linux/amd64,linux/arm64; un build par défaut ne produit que l’architecture du runner.
Décision
Section intitulée « Décision »La portabilité visée est arm64 + x86_64 sous Linux pour les artefacts déployés, et macOS arm64/x86_64 + Linux pour l’outillage de développement. Windows et les architectures exotiques (s390x, ppc64le…) ne sont pas des cibles ; le code ne doit simplement pas leur être hostile sans raison.
Quatre invariants en découlent :
-
Images de base par manifest-list, jamais par image arch-spécifique. On épingle par digest (ADR 0084) le digest de la manifest-list, pas celui d’une variante
amd64. Acquis pournodeetpython. -
dataops/(Python) est verrouillé sur Python 3.10, en parité 1:1 avec l’image Dagster du cluster (verrou chart Helm, ADR cluster 0006) et avecrequires-python >=3.10,<3.11. Le.python-version,uv.locket leFROM … python:3.10-slimdoivent rester alignés. Un bump Dependabot qui change la minor (3.10→3.x) est un faux positif à refuser : seul le digest d’unpython:3.10-slimse met à jour. (C’est précisément la dérive corrigée par cet ADR : un bump avait fait passer les images à3.14-slimalors que tout le reste de la chaîne — dev, tests, cluster — reste sur 3.10.) -
Les dépendances natives non couvertes en musl restent confinées aux CLI hôtes, hors des images Alpine déployées.
@duckdb/node-api(DuckDB) n’a aucun binding musl : il vit danspackages/citation, consommé uniquement parcli/citation(exécuté sur l’hôte de dev, glibc/macOS — jamais dockerisé en Alpine). De même@napi-rs/canvasettesseract.js(rendu/OCR) restent danscli/researcher-profiles. Corollaire opposable : dockeriser l’un de ces paquets ennode:alpineest interdit sans d’abord traiter le support musl (ou basculer la base sur glibc). -
Le code applicatif n’assume ni OS ni binaire système. Pas d’appel à un binaire externe non garanti (
unzip,sed -ià syntaxe BSD…) ; manipulation de chemins viapath/pathlibetos.tmpdir(); séparation de lignes tolérante auCRLF(/\r?\n/).
Alternatives écartées
Section intitulée « Alternatives écartées »-
Builds Docker multi-arch en CI (
buildx --platform linux/amd64,linux/arm64). Souhaitable à terme, mais écarté pour l’instant : le jobimagescharge l’image localement (load: true) pour son smoke healthcheck, etloadest mono-plateforme par construction ; le multi-arch imposerait soit de scinder build-multi-arch (push) et build-smoke (load mono-arch), soit d’émuler arm64 via QEMU — ce qui multiplie le temps CI d’un check requis pour un déploiement aujourd’hui exclusivement x86_64. Conséquence assumée : les images publiées sur GHCR sont amd64. À rouvrir le jour d’un déploiement arm64 (voir Conséquences). -
Aligner
dataops/sur Python 3.14 (au lieu de revenir à 3.10). Écarté : casse le verrou de parité 1:1 avec le cluster (ADR cluster 0006) et violerequires-python <3.11; le gain (surface CVE de l’image de base plus fraîche) ne justifie pas une divergence dev↔prod sur la version du langage. -
Matrice CI multi-OS (macOS/Windows runners). Écarté : coût (minutes runner payantes) disproportionné pour un déploiement Linux-only ; macOS est déjà couvert de facto comme environnement de développement quotidien.
Accepted.
Conséquences
Section intitulée « Conséquences »- Garde-fou Python à deux niveaux. Mécanique :
dependabot.ymlignore les bumps major et minor de version pour les imagespythonetphp(écosystèmedocker) — seul le digest (patch sécurité du même tag) est proposé ; un3.10→3.14ne peut plus être ouvert automatiquement. Documentaire : le commentaire au-dessus duFROMrappelle la consigne au relecteur. (noden’a pas besoin de règle : son tag est interpolé parARG, non bumpé par Dependabot.) - Surface CVE 3.10 assumée.
python:3.10-slimtraîne des vulnérabilités sans correctif amont ; elles relèvent du.trivyignoretracé (ADR 0069), pas d’un changement de minor. Les imagesdataops/ne passent pas le gate Trivy deimages.yml(hors matrice) — la revue se fait au build cluster. - Déploiement arm64 = travail explicite. Tant que la décision « images amd64 »
tient, un nœud arm64 tirerait une image émulée (lente) ou échouerait. Le passage
au multi-arch (buildx +
platforms) et le déblocage des binaires CI épinglés…-amd64/linux_x64(kubeconform, gitleaks) sont les deux chantiers à mener ensemble ce jour-là. - Nouvelle dépendance native = question de libc d’abord. Avant d’ajouter une dépendance à binaire précompilé, vérifier sa couverture musl si elle doit entrer dans une image Alpine ; sinon la cantonner à un CLI hôte (invariant 3).
Voir aussi ADR 0084
(épinglage par digest), ADR 0069
(scan d’image, .trivyignore) et ADR 0055
(frontière Python de dataops/).