- Si votre environnement utilise déjà les images Docker comme unité de déploiement, Nix constitue un point d’entrée pour expérimenter les builds déterministes et le verrouillage des dépendances sans bouleverser fortement le workflow
- Un
docker build classique dépend facilement de l’état de l’Internet public, par exemple des dépôts Ubuntu ou de téléchargements externes, ce qui rend difficile la reproduction de la même image longtemps après
- Nix connaît les dépendances nécessaires au niveau des paquets, ce qui permet avec
dockerTools.buildLayeredImage de ne retéléverser que les couches modifiées
- L’exemple
douglas-adams-quotes, basé sur Go, montre qu’on peut construire le binaire avec pkgs.buildGoModule, puis le charger et le déployer comme une image Docker classique avec nix build.#docker, docker load <./result
- Si plusieurs services d’un monorepo partagent leurs couches et le cache Nix, on peut réduire le temps et le coût de build lors des déploiements répétés et de la reproduction d’images issues d’anciens commits
Pourquoi ajouter Nix à la construction d’images Docker
- Nix réunit trois dimensions : gestionnaire de paquets, langage et système d’exploitation
- Les instructions de build des paquets sont écrites en langage Nix
- Le gestionnaire de paquets Nix peut, à partir de ces instructions, produire des logiciels, des outils, des images NixOS, des images de conteneurs, etc.
- Introduire Nix dans un pipeline CI/CD existant n’est pas simple
- Nix diffère de la manière de travailler à laquelle beaucoup de développeurs sont habitués
- Sauf dans un environnement où tout peut être reparti de zéro, comme une nouvelle startup ou un homelab, la barrière à l’adoption est élevée
- Les organisations qui utilisent déjà Docker peuvent commencer par appliquer Nix à la construction d’images de conteneurs afin d’expérimenter sans casser fortement leur flux de déploiement existant
Là où les builds Docker deviennent fragiles
- Docker et la conteneurisation sont largement adoptés, si bien que les images Docker servent de facto de format de paquet universel sur Internet
- Des plateformes comme Fly.io, Railway ou Render utilisent des images Docker plutôt que des images VM arbitraires ou des programmes Linux sous forme de tarball
- Les builds Docker ne sont pas toujours déterministes
- La plupart des Dockerfiles vus sur Internet buildent correctement, mais la petite part qui échoue peut se transformer en problème d’exploitation
- L’une des plus grandes faiblesses tient au fait que le build Docker accède à l’Internet public
- C’est nécessaire pour télécharger des paquets depuis les dépôts Ubuntu
- Lorsqu’il faut régénérer la même image plus tard, il est difficile de revenir à l’état exact du dépôt Ubuntu de l’époque
- Ubuntu 18.04 approche de sa fin de support cette année-là, et l’on peut ne découvrir qu’après incident ce qui dépend encore de cette version
- Ajouter des paquets à une image Docker de manière simple peut aussi générer du gaspillage d’espace
- Exécuter
apt-get upgrade au début du build peut remplacer des fichiers présents dans l’image du conteneur
- Les fichiers d’avant remplacement peuvent rester dans les couches et s’accumuler comme des copies fantômes
La manière dont Nix gère les couches Docker
- Nix connaît à l’avance les dépendances nécessaires et peut les répartir sur le moins possible de couches Docker
- Changer une seule ligne de code ne nécessite pas de réinstaller les dépendances via
apt ou npm
- Seul le minimum réellement modifié doit être répercuté dans la mise à jour de l’image
dockerTools est une boîte à outils qui place des paquets Nix et leurs dépendances dans des images Docker
- Les images Docker produites avec Nix se divisent en deux grandes approches
- Image non stratifiée : le programme, ses dépendances et des éléments supplémentaires comme les certificats racine TLS sont placés dans un seul dossier puis exposés comme une image à couche unique
- Image stratifiée : chaque dépendance est placée dans une couche distincte afin de ne téléverser que ce qui a réellement changé
- Docker dispose lui aussi d’un stockage adressé par contenu, mais il est difficile d’en tirer pleinement parti avec
docker build seul
- Les images stratifiées de Nix utilisent des couches par paquet, donc une dépendance comme
glibc n’a besoin d’être téléversée qu’une seule fois
- En revanche, si cette bibliothèque doit être modifiée, sa couche devra être renvoyée
Exemple de service Go : Douglas Adams Quotes
- Le service d’exemple est un programme Go qui fournit des citations de Douglas Adams
- Un projet en modules Go définit son paquet Nix avec
pkgs.buildGoModule
bin = pkgs.buildGoModule {
pname = "douglas-adams-quotes";
inherit version;
src = ./.;
vendorHash = null;
};
- Cette définition utilise le template de modules Go pour configurer le compilateur Go, le compilateur C pour CGo et le téléchargement des dépendances externes
pname est le nom du paquet
version est générée automatiquement à partir du commit Git du service
src = ./.; utilise le code source du répertoire de travail courant
- S’il n’y a pas de dépendances au-delà de la bibliothèque standard, on peut laisser
vendorHash = null
- S’il y a des dépendances externes, il faut spécifier le hash de toutes les dépendances ou utiliser
gomod2nix
- Le paquet se build avec la commande suivante
nix build .#bin
- Une image Docker en couches peut être créée avec
dockerTools.buildLayeredImage
docker = pkgs.dockerTools.buildLayeredImage {
name = "registry.fly.io/douglas-adams-quotes";
tag = "latest";
config.Cmd = "${bin}/bin/douglas-adams-quotes";
};
- En indiquant le binaire serveur compilé dans
config.Cmd, les éléments nécessaires sont copiés avec lui
glibc et les composants requis à l’exécution sont également inclus
- Des paquets supplémentaires comme les certificats racine CA peuvent être placés dans
contents
docker = pkgs.dockerTools.buildLayeredImage {
name = "registry.fly.io/douglas-adams-quotes";
tag = "latest";
contents = with pkgs; [ cacert ];
config.Cmd = "${bin}/bin/douglas-adams-quotes";
};
- L’image se construit puis se charge dans le démon Docker avec les commandes suivantes
nix build .#docker
docker load < ./result
- En l’ouvrant avec
dive, on voit que chaque couche ajoute des paquets différents de nixpkgs, puis qu’à la fin les éléments sont reliés symboliquement à la racine de l’image
L’effet du partage de couches dans un monorepo
- Si plusieurs services se trouvent dans le même dépôt, les images Docker produites avec Nix peuvent partager leurs couches
- Ce partage se fait sans configuration supplémentaire
- Avec Docker seul, obtenir le même résultat conduit souvent à créer plusieurs images de base communes, qui peuvent inclure des outils et du contenu inutiles pour certains services
- Par exemple, le dépôt
Xe/x est un monorepo qui rassemble dix ans de side projects, d’expérimentations et d’outils
- Plusieurs projets y sont déployés sur environ trois plateformes
- Un travail a été mené pour les empaqueter en images Docker afin de converger vers une base de déploiement commune
- Lorsqu’une mise à jour d’un service est poussée, une partie des mises à jour partagées avec la plupart des autres services est poussée en même temps
- Cette approche fait gagner du temps et de l’argent sur plusieurs projets
Ce que le cache Docker seul couvre difficilement
- En théorie, l’utilisation du cache Docker peut permettre de construire des images presque aussi efficacement que Nix
- Mais obtenir le même effet avec une approche Docker peut rendre les étapes de build difficiles à maintenir
- Il faut installer les bibliothèques partagées dans des couches séparées
- Il faut réactiver la pile réseau pendant le build, ce qui affaiblit la reproductibilité
- Il faut réajuster les chemins de recherche, les flags du compilateur et la configuration liée à CGo
dockerTools.buildLayeredImage de Nix prend en charge les détails de mise en conteneur des paquets, ce qui simplifie la configuration
Reproduire un état passé et tirer parti du cache Nix
- L’un des grands avantages de Nix est de pouvoir reconstruire exactement un logiciel à un instant passé
- Si l’on doit reproduire plus tard un bug dans l’environnement d’un client précis, on peut recréer la même image Docker
- Le build des paquets fige l’état complet du logiciel et de ses dépendances à un moment donné
XeDN est un projet développé depuis plusieurs années, et sa version d’il y a 14 mois peut être buildée avec la commande suivante
nix build github:Xe/x/567fdc2#xedn-docker
- Cette commande construit la cible
xedn-docker à partir d’un commit précis du dépôt GitHub repo Xe/x
- Une fois chargée dans le démon Docker, elle produit une image identique octet pour octet à celle de l’époque
- L’exemple inclut aussi Go 1.19
- Reproduire la même chose avec un build Docker classique peut nécessiter beaucoup de stockage
- Le cache Nix enregistre les sorties des commandes Nix pour éviter de les reconstruire plus tard
- Chaque laptop de développeur n’a pas besoin de rebuilder un paquet comme
nokogiri
- Il peut récupérer un résultat déjà construit dans le cloud
- Garnix est utilisé comme CI pour tous les projets flakes et remonte l’état des builds à chaque commit
- La configuration des machines de homelab est aussi buildée avec Garnix, ce qui permet de récupérer la configuration la plus récente depuis le cache Garnix lors des mises à jour du soir au lieu de tout rebuilder localement
Conclusion en tant qu’artefact de déploiement
- On peut considérer Nix comme un meilleur constructeur d’images Docker que le constructeur d’images de Docker
- Nix pousse à décrire le résultat attendu, plutôt qu’à lister manuellement toute la procédure pour y parvenir
- Dans un environnement qui utilise déjà Docker, commencer par la construction d’images Docker avec Nix constitue une voie d’adoption réaliste
- Les images Docker produites avec Nix peuvent partager leurs couches entre différentes parties d’un monorepo
- Grâce au cache binaire, il n’est pas nécessaire de reconstruire du code déjà buildé par le passé
- Le résultat final reste une image de conteneur standard, déployable sur des plateformes comme AWS, Google Cloud ou Fly.io
1 commentaires
Commentaires sur Hacker News
J’ai essayé plusieurs fois d’aimer Nix, mais j’ai l’impression qu’il faut que j’abandonne
J’ai deux systèmes qui tournent sous Nix et j’ai peur d’y toucher ; il m’est déjà arrivé de casser les deux et de devoir tout réinstaller depuis zéro. En théorie, Nix est idempotent et déterministe, mais en pratique il faut comprendre par rapport à quoi il est déterministe, et si on ne connaît pas en profondeur le fonctionnement de toutes les briques dont il dépend, on se retrouve avec des résultats étranges, des erreurs bizarres et inutiles, ou parfois aucune information en retour
La documentation, même quand elle est complète et techniquement correcte, est affreusement obscure, et les tutoriels ne vous emmènent qu’à 80 % du chemin. Au-delà, il faut construire le reste soi-même, ce qui demande des années d’expérience accumulée et de frustration, et je n’ai plus envie de supporter ça. L’avantage de Docker, au contraire, c’est presque son chaos : avec une connaissance approximative du shell et du gestionnaire de paquets de la distribution, on peut construire presque n’importe quoi, et quand il y a un problème, les outils qui existent depuis des décennies rendent le diagnostic et la correction bien plus simples
Nix, c’est un peu comme Emacs : avec de la patience et une connaissance profonde, presque visionnaire, on peut tout faire, mais on a l’impression qu’il faut soit s’y consacrer entièrement, soit rester à distance et regarder
Si on build deux fois de suite sur la même machine, on obtient le même résultat ; mais après un certain temps, quand le gestionnaire de paquets et les tags de l’image de base ont été mis à jour, une reconstruction un mois plus tard peut donner un résultat complètement différent. Quand une équipe gère une quarantaine de conteneurs, la maintenance de ces conteneurs finit par représenter une grosse partie du travail
Donc, en théorie, Nix devrait être parfait, mais c’est tellement différent que les outils des éditeurs ne tournent pas sous Nix, et quand une erreur survient il est difficile de trouver rapidement une solution sur le web. C’est cette frustration qui m’a poussé à créer https://www.stablebuild.com, qui fournit des builds déterministes basés sur Docker au-dessus de conteneurs Ubuntu, Debian et Alpine. Le système repose sur un cache pull-through immuable pour Docker Hub, des instantanés complets quotidiens des dépôts de paquets Ubuntu/Debian/Alpine, des instantanés quotidiens des PPA populaires et de l’index PyPI, ainsi qu’un cache immuable pour des fichiers et URL arbitraires
En pratique, c’est facile à utiliser, facile à déboguer et compatible avec un large éventail de logiciels ; dans les conteneurs migrés vers StableBuild, nous n’avons eu zéro incident lié au non-déterminisme
J’ai déjà passé du temps à essayer de comprendre pourquoi
nix developne se comportait pas comme indiqué dans la documentation, et comment faire pour qu’il se comporte ainsi. La doc explique qu’on entre, par défaut, dans l’environnement du moment du build, mais en réalité le comportement par défaut n’est pas hermétique, et même les noms des options en ligne de commande prêtent à confusion, au point qu’il a fallu aller extraire l’information dans le code sourceJ’aimerais aussi qu’on traite plus explicitement les cas limites qui cassent la reproductibilité. Le code en virgule flottante est sensible à l’ordre des opérations, et l’état peut fuiter à cause de la préemption du système d’exploitation ; même les choses évidentes devraient être précisées, sinon les gens finissent par se tirer une balle dans le pied
Avec une configuration « Erase your darlings », on peut éliminer la majeure partie de l’état non reproductible hors des comptes utilisateur. C’est un peu pénible, mais en même temps, qu’est-ce qui ne l’est pas sur NixOS ?
https://grahamc.com/blog/erase-your-darlings/
Je ne m’occupe pas de l’intérieur des comptes utilisateur, et je n’aime pas Home Manager
Docker est correct, mais les performances sur Mac ne sont pas terribles, et j’apprécie le fait qu’avec Nix il soit trivial d’installer plusieurs machines de la même manière et de les faire fonctionner de façon identique. La documentation de Nix est médiocre, mais ChatGPT-4 a été excellent pour résoudre les problèmes liés à Nix
J’ai l’impression que 90 % des problèmes avec Nix venaient du fait qu’on essayait de faire quelque chose qui n’était pas « la manière Nix »
J’ai utilisé presque tous les gestionnaires de paquets des distributions, et j’ai réussi à tous les casser d’une manière ou d’une autre, même sans rien faire de spécial. Fedora Kinoite, elle, tient bien le coup malgré l’ajout et la suppression de couches, les mises à jour quotidiennes, et même un rebase depuis Silverblue. Personnellement, je pense que rpm-ostree finira par remplacer Nix
Je considère que Nix et NixOS en sont à peu près là où git en était avant GitHub
L’idée de base repose sur une informatique plus sérieuse que les approches existantes comme SVN ou Docker, et même si la plomberie interne a encore des problèmes, ce n’est pas pire ; en revanche, les outils orientés utilisateur et la documentation ne sont pas encore au niveau d’une adoption grand public
Il est possible que la sortie de flox ait changé cela : https://flox.dev. C’est presque fluide, ce qui n’a rien de surprenant venant d’anciens de DE Shaw
Nix n’a pratiquement pas de sens sans les flakes et
nix-command, mais tous deux sont documentés comme expérimentaux et désactivés par défaut. La documentation s’améliore, mais reste insuffisante, et le nixlang est excellent une fois bien appris, sans pour autant être acceptable comme barrière d’entrée pour le grand public.nix-env -iA foone correspond en pratique presque jamais à ce qu’on veut réellement faire ; du coup, Nix tient plus de l’arme secrète pour les entreprises capables d’assumer une expertise interne que du gestionnaire de paquets tel qu’il est présentéflox abaisse le seuil pour obtenir une « meilleure expérience dès le premier essai ». Nix/NixOS, ou quelque chose du même genre, finira par reléguer Docker au second plan comme Subversion, mais cela n’arrivera pas avant un moment à la GitHub — puis tout se produira d’un coup
La plupart des reproches faits à Nix dans ce fil sont techniquement faux, mais tout à fait compréhensibles, et plus important encore, ce n’est pas la faute des utilisateurs. Je sais qu’il y a de moins en moins de gens qui ont connu le monde d’avant git/GitHub, mais ça vaudrait la peine d’entendre Linus expliquer à des personnes qui auraient passé les critères d’embauche du Google des débuts pourquoi elles devraient s’intéresser à un outil perçu comme complexe
https://youtu.be/MjIPv8a0hU8?si=QC0UnHXRdMpp2tI4
Ça fait un moment qu’on construit ça, et j’aimerais savoir s’il y a des points à mettre en priorité pour l’améliorer
git le permet, mais Nix pas encore, donc je ne sais pas si un moment à la GitHub aiderait vraiment
Ce billet de blog n’explique pas pourquoi les couches Docker partagées sont utiles
La raison, c’est le cache : plus d’images partagent les mêmes couches, plus on peut mettre en cache de choses, ce qui accélère le démarrage des conteneurs
Si Docker est faible sur ce point, c’est parce que pour bénéficier du cache, il faut que la construction d’une image produise des couches aussi proches que possible des couches existantes. Par exemple, si j’exécute
apt-get install python3aujourd’hui et qu’il n’y a pas de nouvelle mise à jour, il faudrait obtenir exactement la même couche qu’hier ; or les couches Docker sont mises en cache via le hash des fichiers, donc tous les fichiers doivent être strictement identiques, métadonnées comme l’heure de création comprisesNix stocke déjà les dépendances par hash, donc avec la même version et la même configuration, la couche est toujours identique
Or les dépendances forment généralement un graphe plutôt qu’un arbre, ce qui devient vite pénible. Des outils alternatifs comme Nix, ou probablement Bazel, n’ont pas cette contrainte et peuvent projeter le graphe de dépendances sur les couches Docker pour obtenir un cache plus fin. C’est impossible à exprimer avec un Dockerfile
apt-getn’est pas invalidée automatiquementElle ne change que si on utilise
--no-cacheou s’il y a un changement dans une couche parenteJ’ai passé les 2 ou 3 derniers jours à me battre pour construire une image Docker sur Darwin, et cet article donne l’impression que l’univers se moque de moi
Nix est clairement le meilleur outil pour l’objectif visé, mais il a aussi des recoins sombres et abandonnés qui vous aspirent l’âme. J’adore ça, mais parfois j’ai l’impression d’être Morty entraîné dans une aventure de Rick au pays des compilateurs
Docker est fondamentalement plus proche d’une prison pour binaires Linux. Sous Linux, il suffit de compiler le binaire et de le mettre dans le conteneur, et le code Nix reste simple. Si vous savez compiler le code, créer le conteneur n’est qu’une étape de plus
Mais Docker exige des binaires Linux, et sur Mac, Docker Desktop lance une VM Linux, fait tout le travail dedans, puis masque ce fait. Nix ne procède pas ainsi, donc il y a deux options
La première consiste à faire de la compilation croisée, mais il faut pouvoir croiser jusqu’à glibc, et même si la plupart des dépendances communautaires le permettent, certains paquets peuvent ne pas avoir été pensés par leur auteur pour la compilation croisée. En plus, Hydra, qui alimente le cache Nix standard, ne produit pas de builds en compilation croisée, donc on peut compiler longtemps pour finir en échec
La seconde consiste à attacher un builder Linux au Mac et à lui envoyer les builds
x86_64-linux. Cela peut être une machine physique, une VM, ou même un conteneur Docker NixOSLa solution n°1 semble être la bonne en théorie, mais la n°2 est plus pratique. Si vous rencontrez ce genre de problème, c’est probablement parce que vous essayez la n°1, ce qui demande non seulement Nix mais aussi une solide expérience de la compilation croisée. Ce serait bien si Hydra construisait aussi les compilations croisées de Darwin vers Linux pour les mettre en cache et éviter les régressions, mais cela augmenterait aussi les coûts. Mieux vaut sans doute essayer la solution n°2
Il me semble qu’il y avait une solution officielle : https://ryantm.github.io/nixpkgs/builders/special/darwin-bui...
Sur certaines stacks, la compilation croisée n’est pas totalement robuste, donc je fais de la compilation croisée
{aarch64,amd64} x {linux,darwin}avec Docker. Je fais tout compiler en lançant plusieurs Docker sur Darwin aarch64, et l’expérience a été bonnehttps://github.com/gytis-ivaskevicius/high-quality-nix-conte...
Il y a un ou deux bugs, mais ils semblent surtout liés aux volumes, et pour la construction d’images Docker, ça se passe correctement. Le côté le plus rude, c’est la vitesse, à cause du cumul entre le matériel et le fait que Docker doive émuler une VM Linux
L’expérience de création d’images Docker pour des applications Java avec Nix n’a pas été très bonne
Après l’abandon de
gradle2nix, il ne semble plus y avoir d’alternative évidente pour les applications Java basées sur Gradle. J’avais autrefois proposé à un ami d’essayer de produire une image Docker aussi petite que possible pour une simple application Spring Boot, mais le résultat avec Nix faisait deux fois la taille d’une image créée sans NixLe code est visible ici : https://github.com/jossephus/Docker_challenge/blob/main/flak...
zulu et le JDK inclus par gradle via l’argument
jdkse retrouvent tous les deux dans l’image. Un coup d’œil àgradleGendans nixpkgs permet de comprendre. Désolé pourgradle2nix, je travaille sur une amélioration moins bricoléeC’était grosso modo comme ça : https://gist.github.com/takeda/17b6b645ad4758d5aaf472b84447b...
Tout était lié avec musl, Python avait été compilé en désactivant tous les paquets inutilisés par l’application, et les parties non utilisées de boto3/botocore avaient été retirées. À eux seuls, boto3/botocore dépassent les 100 Mo
Les paquets Nix ciblent par défaut le système d’exploitation NixOS, donc dans une situation générale où l’espace disque n’est pas un problème, on veut que toutes les fonctionnalités soient activées. Cela entraîne beaucoup de dépendances inutiles. À l’inverse, les images Alpine sont conçues pour Docker, avec pour objectif de désactiver les fonctionnalités annexes dans les paquets, donc le résultat est plus petit
Pour produire une petite image, il faut désactiver ce qui n’est pas nécessaire via
override. Par exemple, zulu inclut alsa, fontconfig, freetype, xorg, cups, gtk, cairo, ffmpeg, etc. Mon ami a extrait avec précaution uniquement les fichiers nécessaires pour les mettre dans le conteneur, alors que côté Nix cela revenait à embarquer tout le paquet zulu et toutes ses dépendancesIl faut d’abord corriger cela pour n’inclure qu’un seul JDK, puis il est possible de réduire davantage la taille du JDK avec les méthodes ci-dessus. Utiliser
openjdk_headlesspourrait simplifier les choseshttps://github.com/NixOS/nixpkgs/blob/master/pkgs/developmen...
https://github.com/GoogleContainerTools/jib/tree/master/jib-...
openjdk_headlessévite les dépendances GTK et X inutiles pour SpringJ’ai remplacé Zulu par la build OpenJDK headless de Nixpkgs afin de supprimer les dépendances aux bibliothèques graphiques, puis j’ai créé un JRE minimal personnalisé avec
pkgs.jre_minimaletjlinkLa taille de l’image est de 161 Mo, donc légèrement plus grande que l’image
demo_jlink. C’est parce que le JRE fait environ 90 Mo, puisqu’il inclut tous les modules réellement nécessaires à l’exécution de l’application. L’appel àjdepsdansDockerfile_jlinkne détectait pas tous les modules et construisait un JRE avec seulementjava.base. Si mon JRE minimal ne contenait lui aussi quejava.base, il ferait environ 50 Mo, et l’image de conteneur cassée ferait 117 Mo avec PodmanJ’ai aussi supprimé un
copyToRootincorrect dans l’appel àdockerTools.buildImage. Le simple contexte de chaîne deconfig.Cmdsuffit déjà à faire entrer l’application dans l’image, et le code existant copiait donc l’application une seconde fois. Je suis également passé àdockerTools.buildLayeredImage, qui place chaque store path dans une couche d’image distincte ; c’est intéressant pour la mutualisation des dépendances entre plusieurs images de conteneur, mais cela ne change rien pour une expérience sur une image uniqueCe qui reste concerne surtout l’optimisation de la taille du JRE.
glibcest la dépendance suivante la plus volumineuse, à environ 30 Mo, apparemment parce que Nixpkgs compile glibc avec la prise en charge de nombreuses locales et encodages de caractères. Je ne sais pas s’il est possible, ni si ce serait pratique, de séparer cela dans une derivation ou une output distincte pour le rendre optionnel. Si l’on construit plusieurs images avecdockerTools.buildLayeredImage, alors glibc et toutes les autres dépendances sont partagées, à condition d’utiliser le même commit Nixpkgshttps://github.com/max-privatevoid/hackernews-docker-challen...
Si vous avez déjà adopté Nix, c’est excellent, et j’aimerais que la gestion de paquets déclarative comme Nix ou Guix se répande davantage.
Si vous utilisez déjà Docker mais souhaitez introduire Nix progressivement, cette présentation propose aussi cette approche : https://youtu.be/l17oRkhgqHE
Au lieu de migrer immédiatement toute la configuration et la construction des conteneurs vers Nix, on peut conserver le Dockerfile et lui faire construire la configuration Nix. Le principal inconvénient est qu’on ne profite absolument pas des layers, et l’avantage est qu’on peut réutiliser l’infrastructure ou l’automatisation Docker existante tout en faisant évoluer le Dockerfile progressivement.
[1] https://mitchellh.com/writing/nix-with-dockerfiles
Cela dit, j’ai l’impression qu’une grande partie de cette non-reproductibilité vient du fait qu’il n’y a aucune garantie qu’un des layers Docker reste disponible en permanence. Dans ce cas, je me demande aussi si Nix garantit que les versions de paquets restent indéfiniment dans le dépôt.
C’est un peu hors sujet, mais l’illustratrice Annie Ruygt, qui a réalisé les visuels de ces slides, a aussi créé les logos et le brand art de deux startups du YC, RethinkDB (rethinkdb.com) et Pachyderm (pachyderm.io).
J’ai travaillé chez Pachyderm, et Pachyderm a été fondée par d’anciens ingénieurs de RethinkDB. Son travail est vraiment excellent.
Il y a relativement peu de temps, j’ai passé environ une demi-journée à essayer de construire une image de base CI avec Nix sur recommandation de l’équipe infra, mais l’image était énorme et certaines parties ne fonctionnaient pas à cause de problèmes de liens.
Ce qui m’a particulièrement agacé, c’est qu’en créant une image multi-architecture, il essayait réellement d’exécuter quelque chose d’une autre architecture, tout en ne prenant en charge que la virtualisation matérielle via qemu. Ma machine de build et mon poste de travail sont des VM, donc je n’ai pas ça, même si j’ai
binfmt-misc. S’il avait fork/exec unmkdirarm64 pour créer/tmp, ça aurait fonctionné, mais un layer Docker n’est qu’un fichier tar, donc on peut créer un répertoire comme ci-dessousecho "tmp uid=0 gid=0 time=0 mode=0755 type=dir" | bsdtar -cf - @-Il est même très probable que ce layer exact existe déjà quelque part, auquel cas l’utilisateur n’aurait même pas besoin de le télécharger.
Chaque fois que j’essaie Nix, j’ai l’impression que dans quelques mois je pourrai l’utiliser régulièrement. nixpkgs contient presque tous les paquets que je veux, et l’installation sur poste de travail fonctionne bien. Mais un besoin du type « il me faut bash, python, build-essential et Bazel » ne semble pas être l’objectif d’un constructeur d’images Docker. Si c’est juste pour mettre un binaire Go dans une image Docker, il n’y a pas besoin de Nix. Il suffit de prendre une distroless et de mettre l’application dans un fichier tar, et cela devient un conteneur. Personnellement, j’utilise
rules_ocide Bazel, qui ne fait au fond pas beaucoup plus que ça, avec juste un peu plus d’intelligence pour construire des binaires pour plusieurs architectures, générer un YAML d’index d’images et les pousser vers un registre.Bien sûr, il faut que les fichiers de build du paquet le prennent en charge. Et l’idée que qemu ne prend en charge que la virtualisation matérielle ne me semble pas correcte non plus. On peut utiliser qemu aussi pour des architectures où seule l’émulation logicielle est possible.
Un exemple minimal est présenté ici : https://discourse.nixos.org/t/how-do-i-get-a-shell-nix-with-...
Je n’irais pas jusqu’à dire que c’est aussi simple que d’utiliser
pkgCross, mais je suis curieux de savoir quel problème concret a été rencontré dans le flux habituel.https://nix.dev/tutorials/cross-compilation.html
Je l’ai utilisée pendant des années sur plusieurs projets, mais si la taille est un problème, on peut créer des images très petites ne contenant que ce qui est spécifié, comme mentionné dans l’article.
[1] https://hub.docker.com/r/nixos/nix/tags
J’utilisais même musl, et cela produisait en CI les plus petites images, plus vite et plus régulièrement que tout ce que j’avais fait avec d’autres outils, avec un cache qui fonctionnait bien. Je ne comprends pas bien ce qui était censé avoir été exécuté.
buildFHSEnvpour prendre en charge un binaire tiers bizarre.On dirait que Nix a besoin de plus d’articles expliquant comment passer en douceur, morceau par morceau, depuis un système existant.
En tant qu’ingénieur plateforme, j’aimerais apprécier Nix, mais ce n’est pas simple pour tout le monde.
Je trouve aussi que l’expérience développeur reste assez mauvaise. Par exemple, je préfère l’expérience développeur de devbox, où l’on peut ajouter des paquets avec
devbox add python@3.11.Quand on regarde un
flake.nixde 120 lignes, il est difficile de dire que c’est précisément « plus simple ».https://github.com/Xe/douglas-adams-quotes/blob/main/flake.n...
Le flake lié définit la manière de compiler un binaire Go, définit une image Docker qui utilise ce binaire comme point d’entrée, et définit aussi un module NixOS qui crée un service systemd exécutant ce binaire Go. Il inclut également un test NixOS pour vérifier que ce module NixOS crée bien le service systemd attendu.
Le framework de test NixOS est assez impressionnant, et les tests s’exécutent dans une VM QEMU faisant tourner NixOS. Dans l’article lié, ce qui est pertinent, c’est la définition du binaire Go et de l’image Docker, ainsi qu’une partie du boilerplate autour.
En outre, de nombreuses lignes correspondent directement à la description du service systemd en ligne, donc elles ne disparaîtraient avec aucun autre système. Il y a aussi une manière élégante de déclarer plusieurs systèmes avec des overrides.
Cet exemple relève davantage de « faisons une chose banale comme s’il s’agissait d’un gros projet sérieux », et dans un cas ponctuel, cela pourrait probablement être réduit à environ 40 lignes.
Ce binaire et ce fichier de configuration sont ce qui se rapproche le plus d’un « flat pack » qu’on puisse obtenir sous Linux, et cet exemple montre bien ce que nous avons raté entre la forêt et les arbres.
Guix a aussi une option Docker simple et correcte avec
guix pack -f docker.Guix a aussi l’avantage d’utiliser Guile/Scheme, un langage déjà employé ailleurs, au lieu de son propre langage dédié.
[1] https://guix.gnu.org/manual/en/html_node/Invoking-guix-pack....