Warum Railway von Nix auf Railpack umstellt
(blog.railway.com)- Railway hat den Builder für die Erstellung von Container-Images aus Nutzer-Code neu entwickelt und bringt die Erfahrungen aus Nixpacks, mit dem über 14 Millionen Apps gebaut wurden, in Railpack ein
- Nixpacks war für 80 % der Nutzer ausreichend, doch die übrigen 200.000 Railway-Nutzer konnten an Grenzen bei Versionsverwaltung, Image-Größe und Caching stoßen
- Railpack verbessert die Reproduzierbarkeit von Builds durch
major.minor.patch-Versionen, Dependency-Locking und einen auf Mise basierenden Installationsablauf statt der commit-basierten Versionsverwaltung von Nix - Durch die direkte Erzeugung von BuildKit LLB und Frontend wurden die Standard-Node-Images um 38 % und die Standard-Python-Images um 77 % verkleinert; außerdem wird Cache ermöglicht, der zwischen Umgebungen geteilt werden kann
- Railpack ist derzeit als Beta verfügbar und kann in den Service-Einstellungen aktiviert werden; Railway priorisiert zunächst die Qualität häufig genutzter Sprachen statt einer möglichst breiten Sprachunterstützung
Warum Railway einen neuen Builder entwickelt hat
- Railway hat Railpack als nächste Entwicklungsstufe seines Railway-Builders vorgestellt
- Railpack wurde von Grund auf neu entwickelt, basierend auf den Erfahrungen aus Nixpacks, mit dem über 14 Millionen Apps gebaut wurden
- Nixpacks wurde vor etwa drei Jahren veröffentlicht und wurde bei Railway zur Standardmethode, um aus Nutzer-Code Images zu bauen
- Für 80 % aller Nutzer funktionierte das gut, doch die übrigen 200.000 Railway-Nutzer konnten auf Einschränkungen stoßen
- Railway kam zu dem Schluss, dass der Builder deutlich aufgerüstet werden muss, um die Nutzerbasis von 1 Million auf 100 Millionen zu vergrößern
Grenzen von Nixpacks mit Nix
- Das größte Problem war die commit-basierte Paketversionsverwaltung von Nix
- Jedes Paket stellte nur die neueste Major-Version bereit
- Versionen waren an einen bestimmten Commit des nixpkgs repo gebunden
- Der Versuch, alle Patch-Versionen zu unterstützen, führte zu einer Struktur, in der Versionsstrings direkt auf Commit-SHAs gemappt wurden, was für Mitwirkende ohne Erfahrung mit der Nix-Versionsverwaltung weder klar noch leicht wartbar war
- Sprachen wie Node und Python konnten dadurch letztlich nur die neueste Major-Version unterstützen
- Wenn Commit-SHAs aktualisiert wurden, um neuere Paketversionen zu unterstützen, konnten sich gleichzeitig auch andere Paketversionen ändern
- Wenn sich die Standardversion änderte, stieg die Wahrscheinlichkeit, dass bestehende Nutzer-Builds mit unerwarteten Fehlern scheiterten
- Railway hielt es für problematischer, wenn zuvor erfolgreiche Builds plötzlich kaputtgehen, als wenn Nutzer keinen Zugriff auf die neuesten Pakete haben
Probleme bei Image-Größe und Caching
- Die Art, wie Nixpacks mit Nix Abhängigkeiten bezog, führte oft zu großen Image-Größen
- Nix sowie die für Build und Runtime nötigen Pakete und Bibliotheken landeten in einer einzigen
/nix/store-Schicht - Da sich Nix-Abhängigkeiten nicht in separate Schichten aufteilen ließen, gab es Grenzen bei der Verringerung der finalen Image-Größe
- Railway sieht darin kein Problem von Nix selbst, sondern der Art, wie Nixpacks Nix genutzt hat
- Auch beim Caching war es schwierig zu steuern, wann Layer-Cache invalidiert wurde
- Railway injiziert in jeden Build eine Deploy-ID-Umgebungsvariable
- In Dockerfiles konnten Layer, die nach dem Hinzufügen dieser Variable ausgeführt wurden, immer invalidiert werden und waren nicht cachebar
- Auch der Ansatz, zentrale Elemente von Nix vor den Nutzern zu verbergen, passte nicht gut
- Nutzer sollten nicht verstehen müssen, was eine derivation ist oder warum Node 22.14.0 in einer bestimmten Archivversion des unstable-Channels enthalten ist
Strukturelle Änderungen in Railpack
- Railway hat Railpack entwickelt, um die in Nixpacks aufgetretenen Probleme zu lösen
- Mit der Abkehr von Nix wurde auch der Name von Nixpacks zu Railpack geändert
- Die Codebasis wurde wegen der BuildKit-Bibliotheken von Rust auf Go umgestellt
- Railpack steuert die Erzeugung des finalen Images deutlich direkter
- Es erzeugt BuildKit LLB und Frontend direkt
- Im Vergleich zu Nixpacks sind die Standard-Node-Images 38 % kleiner und die Standard-Python-Images 77 % kleiner
- Für Versionsauflösung und den Großteil der Paketinstallation wird Mise verwendet
- Künftig bleibt Raum für die Unterstützung weiterer Quellen für ausführbare Dateien
- Abhängigkeiten aus erfolgreichen Builds können festgeschrieben werden
- Selbst wenn sich die Standard-Node-Version von 22 auf 24 ändert, kann verhindert werden, dass Builds dadurch kaputtgehen
- Mit BuildKit secrets wird sichergestellt, dass geheime Umgebungsvariablen weder in Build-Logs noch im finalen Image erscheinen
So funktionieren Railpack-Builds
- Der Railpack-Prozess ist in drei Phasen unterteilt
- Analyze: Der Code wird analysiert, um zu bestimmen, welche Pakete installiert, welche Befehle ausgeführt und welcher Startbefehl verwendet werden sollen
- Plan: Es wird ein JSON-serialisierbarer Build-Plan mit mehreren Schritten erstellt; jeder Schritt bezieht Eingaben aus anderen Schritten oder aus dem Gesamt-Image
- Generate: Auf Basis der Ein- und Ausgaben des Plans wird ein BuildKit-Build-Graph aufgebaut
- Ein Dockerfile ist linear, ein BuildKit-Graph ist dagegen viel stärker parallelisiert
- Jeder Befehl wird in seiner eigenen Stage eines Multi-Stage-Builds ausgeführt, was eine feingranulare Kontrolle über Eingabe-Layer und die Zusammensetzung des finalen Dateisystems ermöglicht
- Railpack erzeugt einen Build-Plan, der alle benötigten Build-Schritte enthält
- Jeder Schritt definiert konkret, welche vorherigen Schritte oder Images benötigt werden
- Dieses Format ist Low-Level-näher als das bisher in Nixpacks verwendete Verfahren
- Der Plan wird in einen Graphen im LLB-Format umgewandelt und so interpretiert
- BuildKit arbeitet vom Ende aus rückwärts, zieht nach Möglichkeit aus dem Cache und führt Befehle nur aus, wenn sie für die angeforderten Layer wirklich nötig sind
- Um Layer bei Änderungen bestimmter Umgebungsvariablen zu invalidieren, hasht Railpack die verwendeten Variablenwerte und mountet eine Datei mit diesem Hash in das Eingabe-Dateisystem
- Wenn sich Code und verwendete Variablen nicht ändern, greift der Layer-Cache
- Railpack kann vollständig definieren, wie ein Image erzeugt wird
Was mit Railpack möglich wird
- Statische Sites mit Vite, Astro, CRA und Angular lassen sich ohne Konfiguration bauen und deployen
- Die Integration zwischen Build und Railway-UI wird enger
- Neueste Sprachversionen können auch ohne Railpack-Release unterstützt werden
- In mehreren Umgebungen eines Projekts kann optimiertes Layer-Caching genutzt werden
Aktuelle Nutzung und Unterstützungsumfang
- Railpack wird derzeit als Beta angeboten und kann in den Service-Einstellungen aktiviert werden
- Es wird bereits für Builds auf railway.com und central station verwendet
- Aktuell werden folgende Ziele unterstützt
- Node
- Python
- Go
- PHP
- Statisches HTML-Deployment
- Grundlegende Unterstützung für statische Sites mit Vite, Astro, CRA und Angular
- Railway verfolgt das Ziel, eine Umgebung zu bieten, in der sich Frontend und Backend gleichermaßen einfach deployen lassen
- Unterstützung für Frameworks und Sprachen wird laufend ergänzt
- Wünsche können über die Help Station eingereicht werden
- Bis die Kern-APIs und Abstraktionen feststehen, hat die inhaltliche Tiefe bei häufig genutzten Sprachen Vorrang vor möglichst breiter Unterstützung
- Railpack ist Open Source, und die Dokumentation ist unter railpack.com verfügbar
1 Kommentare
Hacker-News-Kommentare
Ich bin zwar Nix-Fan, will Railway aber nicht dafür kritisieren, dass sie sich von Nix abwenden. Einige der Beschwerden scheinen mir jedoch mehr Erklärung zu brauchen.
Nixpkgs ist großartig, aber nicht dasselbe wie Nix; und wenn man beliebige Versionen einer Toolchain beziehen will, ist Nixpkgs eben nicht ideal. Nix-Tools, die beliebige Rust-Versionen holen, sind bereits sehr gut, und andere Nix-basierte Development-Tools haben gezeigt, wie man das gut handhaben kann.
Auch die Aussage „es gibt keine Möglichkeit, Nix-Abhängigkeiten in eine separate Schicht auszulagern“ verstehe ich nicht. Man kann sie einfach so aufteilen, wie man möchte, und auch die in Nixpkgs eingebauten Docker-Tools bieten dafür teilweise Unterstützung.
Der Wechsel von Rust zu Go hat nicht direkt mit Nix zu tun, ist aber interessant; außerdem klingt es so, als seien Railpacks und Nixpacks von unterschiedlichen Leuten gebaut worden. Ich habe gesehen, dass ziemlich Schlimmes passieren kann, wenn Leute ohne Nix-Erfahrung in einer Organisation eine unfertige Nix-Lösung übernehmen müssen. Deshalb verwende ich Nix im Job normalerweise nicht, um solche Situationen gar nicht erst entstehen zu lassen.
Auch bei „man teilt die Schichten einfach so auf, wie man möchte“ ist entscheidend, ob das offensichtlich, einfach und das Standardverhalten ist.
Die Leute sind nicht deshalb unzufrieden mit Nix, weil es nicht Turing-vollständig wäre, sondern weil es keine einfache First-Class-API bietet, die direkt zu idiomatischen Projekten des jeweiligen Ökosystems passt, und dadurch mehr Probleme schafft, als es löst.
Wenn jedes Projekt, das Nix nutzen will, am Ende eigene Module schreibt, um Nix-Probleme zu beheben, gibt es wenig Grund, Nix statt gut dokumentierter Mainstream-Tools zu verwenden. Genau danach sieht es auch hier aus, und die meisten werden vermutlich einfach Docker wählen.
Es ist frustrierend, dass ein Produkt für Entwickler eher an ideologisch reinen Flakes festhält, statt praktische Developer-Experience-Probleme in weniger als geologischen Zeiträumen zu lösen. Mir ist klar, dass es um freiwillige Beiträge geht, aber es ist sehr schade, wie viel technische Arbeit in etwas fließt, das wegen schlechter User Experience praktisch schwer nutzbar wird.
So wie Nix zusammen mit der Nixpkgs-Struktur funktioniert, bedeutet das Pinnen einer bestimmten Paketversion letztlich, den Commit des gesamten nixpkgs-Baums festzulegen. Builds von node/python/ruby-Paketen hängen auch vom Zustand des Baums außerhalb des Paketverzeichnisses ab, weshalb man eine Zuordnung zwischen Versionen und Commits braucht.
Diese Abstraktion ist undicht, sodass Railway sie den Nutzern offenlegen muss. Der Nutzer wollte eigentlich nur
yarn add new-fancy-nodejs-package-with-linked–native-depsausführen, stößt dann aber darauf, verschiedene Zustände des nixpkgs-Repositories aufeinander abstimmen zu müssen.Für eng begrenzte Anwendungsfälle kann es in Ordnung sein, Nix ohne Nixpkgs zu verwenden; bei einer Plattform wie Railway scheint das aber schwer zu rechtfertigen.
pkgfunktionierte im Großen und Ganzen gut, aber eines Tages wollte ich vim aus den ports mit benutzerdefinierten USE-Flags kompilieren, bekam über 20 Abhängigkeiten, wurde bei jedemmake menuconfignach Optionen gefragt, und dann scheiterte Paket 16 von 23 sinngemäß mit „das braucht jenes, und jenes braucht Fubar3.32.1, aber Fubar3 ist zugunsten von Fubar4 deprecated“. Da habe ich aufgegeben.Ich verstehe, dass die Core-OS-Entwickler nicht alle mehr als 10.000 Pakete unterstützen können. Aber es sollte auch klar gesagt werden, dass die Wahrscheinlichkeit eines Scheiterns hoch ist, wenn man tatsächlich benutzerdefinierte Features aktiviert. Oder man setzt als Kriterium, dass ein unabhängig erzeugter Standard-Build erfolgreich sein muss, bevor etwas in den ports auftaucht, und alles, was nicht kompiliert, wird aus der ports-Liste entfernt.
Die Nix-Sprache könnte man stundenlang kritisieren, aber sie ist alt, war damals wohl der beste Versuch, und inzwischen lohnt es sich vielleicht nicht mehr besonders, sie zu ändern. Das Nix-Build-System wirkt ziemlich primitiv und baut oft Dinge neu, die eigentlich nicht neu gebaut werden müssten. Zum Beispiel hängt ein erheblicher Teil des Builds einer NixOS-Installations-ISO von der an den Kernel übergebenen Kommandozeile
console=ttyS2,1500000n8ab; ändert man nur die Geschwindigkeit der seriellen Schnittstelle, braucht es etwa 3 Minuten Build-Zeit. Das ist zwar lächerlich, aber deshalb höre ich nicht auf, Nix zu nutzen; in meinen eigenen Builds würde ich so etwas nur nicht zulassen.Nix für Docker-Images ist meiner Ansicht nach der Bereich, in dem Nix am schlechtesten ist. Vor langer Zeit habe ich Go-Software gebaut und musste dem Container-Image das
pg_dump-Binary von Postgres hinzufügen. Auf Vorschlag des Infrastruktur-Teams verwendeten wir Nix, und aus einem komprimierten 50-MB-Image mit Go-Binary wurde ein unerklärliches 1,5-GB-Monster.pg_dumpist 464 KB groß. Am Ende habe ich mit Bazel undrules_debianein apt-Paket installiert; auf distroless wurde das deutlich sauberer und kleiner. Aus meiner tatsächlichen Nix-Erfahrung fühlt es sich so an, als würden Nix-Systeme immer 1,4 GB groß werden. Die Installations-ISO ist 1,4 GB, und eine frisch installierte Maschine ebenfalls.Der Fall „ich will ein großes C++-Projekt bauen“ ist bereits ein gut ausgetretener Pfad, und wenn man C++ durch Rust ersetzt, ändert das am Kern nichts. Es gibt Build-Systeme, die die Bibliothekssituation weniger schmerzhaft machen; sie sind ebenfalls so komplex wie Nix, passen für diesen Zweck aber teils besser. Nix landet an einer sehr generischen Stelle, weil es versucht, ein Build-System für fremde Software und nixpkgs zu sein. Build-Systeme, die dafür entworfen wurden, eigene Software zu bauen, sind darin normalerweise besser. Persönlich bin ich mit Bazel zufrieden und werde außer
go buildfür reine Go-Projekte wohl kaum etwas anderes verwenden, aber es gibt viele Optionen. In 99 % der Fälle sollte man statt Nix so etwas verwenden und eine Flake schreiben, damit Leute über home-manager aktuelle Versionen installieren können.Der Teil zur Versionsauswahl klingt seltsam. Die Version von nixpkgs ergibt Sinn, wenn man ein System ausführt oder baut, aber wenn man als Plattform eine Runtime oder einen Compiler bereitstellt, braucht man einen Ansatz wie bei devenv, bei dem man Versionen direkt bereitstellt.
Wenn man ein altes System baut, nur um altes nodejs bereitzustellen, verpasst man Sicherheits-Patches für Abhängigkeiten. Devenv handhabt das zum Beispiel mit https://github.com/cachix/nixpkgs-python nach dem Motto „alle Python-Versionen mit Nix stündlich aktuell halten“.
Dass Railway bei jedem Build eine Deployment-ID-Umgebungsvariable injiziert, hätte auch in einer Schicht nach der Installation passieren können. Man kann Pakete auch auf mehrere Schichten aufteilen, und es gibt Automatisierung für Bündel, um die Zahl der Schichten zu reduzieren.
„An Nix selbst liegt es nicht, sondern daran, wie wir es verwenden“ ist ein gutes Beispiel für das richtige Werkzeug für die richtige Aufgabe. Nix ist für manche Einsatzzwecke großartig und für andere schrecklich.
Das Problem ist, dass die Lernkurve von Nix so steil ist, dass man, wenn man genug versteht, um es beurteilen zu können, schon so viel Zeit investiert hat, dass ein Zurück zu schade wirkt, und man es dann mit Gewalt an die ursprünglichen Bedürfnisse anpasst.
Wegen dieses Paradigmas ist es für KI sehr einfach, eine
shell.nixoderconfiguration.nixpassend zu einer Spezifikation zu erstellen. Darin können zum Beispiel Python-Pakete, Linux-Pakete, Umgebungsvariablen, Pfadeinträge usw. enthalten sein.Ich nutze so etwas oft, um im Repository eine Umgebung mitzuliefern, die das jeweilige Paket vollständig unterstützt. Mit flakes wäre es reproduzierbarer, aber ich verstehe
flake.nixderzeit als so etwas wie eineshell.nixmit Versions-Pinning und lerne es noch.Es wirkt, als wolle man Versionen mit Gewalt an Stellen hineinpressen, an denen es keine Versionen gibt. Wie einen würfelförmigen Klotz in ein rundes Loch zu stecken.
Dass eine „Standardversion“ Dinge kaputtmacht, die davon abhängen – was soll das heißen? Das ist, als würde man bei Docker den Tag
:latestverwenden und sich wundern, dass bei jedem neuen Server eine andere Version als das frühere „Standard“-Image hochkommt und alles kaputtgeht.Keine der Erklärungen in diesem Blogpost ergibt für mich Sinn. Es wirkt, als hätten die Leute keine Ahnung, was eine „Version“ von Software ist.
Auch „es gibt keine Möglichkeit, Nix-Abhängigkeiten in separate Schichten aufzuteilen“ verstehe ich nicht. Natürlich kann man
/nix/storein beliebig viele Schichten aufteilen. Ich frage mich, ob sie überhaupt wissen, wie man Container und Nix grundsätzlich verwendet.Angesichts dieser offensichtlichen Unerfahrenheit überrascht es nicht, dass die vorgeschlagene Lösung nach fauligem Fisch riecht. Das ist klassisches NIH-Syndrom, und es sieht sehr danach aus, dass dieselben Probleme, die sie mit Nix nicht gelöst haben, auch auf die neue „Lösung“ übergreifen werden.
Wie andere gesagt haben, scheinen nix2container und flakes all ihre Probleme zu lösen.
Was Versionsverwaltung angeht: flakes, die ich vor drei Jahren geschrieben habe, bauen heute noch mit exakt denselben Versionen und derselben Ausgabe wie beim ersten Mal.
Es klingt allerdings so, als wollten sie als Plattform an den Markt gehen und Investoren einsammeln.
Edit: Ich habe gerade auf GitHub bei nixpacks nachgesehen, und sofort fiel mir auf, dass sie
rustPlatformaus nixpkgs verwenden statt oxalicas rust-overlay[0], das man mit einer oberflächlichen Suche nach Rust-Problemen gefunden hätte.rust-overlayist eines der nützlichsten und leistungsfähigsten Overlays, die ich je verwendet habe.[0] https://github.com/oxalica/rust-overlay
nix2container[1] kann Abhängigkeiten tatsächlich in separate Schichten aufteilen. Man kann explizit Schichten erstellen, die nur einen Teil der für das Image benötigten Abhängigkeiten enthalten; Beispiele dafür gibt es in diesem Abschnitt: https://github.com/nlewo/nix2container?tab=readme-ov-file#is...Wenn Images zum Beispiel bash verwenden, kann man explizit eine Schicht mit dem bash-Closure erstellen. Diese Schicht wird von allen Images wiederverwendet und nur dann neu gebaut und neu gepusht, wenn sich dieser bash-Closure ändert.
Dass Images wegen einer einzelnen
/nix/store-Schicht groß werden, trifft auf die Standardfunktionnixpkgs.dockerTools.buildImagezu, aber nicht auf nix2container odernixpkgs.dockerTools.streamLayeredImage. Diese Tools schreiben die Schichten nicht in den Nix store, sondern erzeugen ein Skript, das bestehende Store-Pfade nutzt, um das Image tatsächlich zu pushen. Die Implementierung von nix2container erstellt eine JSON-Datei, die die Nix-store-Pfade aller Schichten beschreibt, und Skopeo konsumiert dieses JSON, um das Image an den Docker-Daemon, eine Registry, podman usw. zu pushen.Zur Einordnung: Ich bin der Autor von nix2container.
[1] https://github.com/nlewo/nix2container
Das Kernproblem hier ist das Festhalten an der von Sprach-Paketmanagern geförderten Haltung einer maßgeschneiderten Versionssuppe. Dieser Ansatz ist völlig untragbar.
Die Alternative Mise scheint nicht in der Lage zu sein, Versionsbeschränkungen zwischen Paketen zu verstehen, und sie scheint auch keine Tests auszuführen, ob jedes installierte Paket mit den Versionen in seiner Umgebung gut funktioniert. Dann bekommt man überhaupt nicht dasselbe.
Eine maßgeschneiderte Versionssuppe ist nicht nachhaltig, aber einer der Gründe, warum Leute sie trotzdem weiterverwenden, ist, dass sie meistens gut funktioniert. Einer der Gründe, warum sie gut funktioniert, ist, dass Bibliotheken auf Betriebssystemebene aus einer viel konservativeren anderen Welt kommen und möglichst vermeiden wollen, Abwärtskompatibilität zu brechen.
Deshalb kann man ein stabiles und gut gepflegtes Betriebssystem als Basis nehmen, darauf mit Tools wie mise oder asdf eine maßgeschneiderte Versionssuppe aus Werkzeugen und Sprach-Runtimes schichten und dann die App ausführen. Das geht fast nie kaputt. Wenn es kaputtgeht, fummelt man an Versionen und kleinen Fixes herum, bis es wieder läuft, und macht weiter. Dass es kaputt war, ist ärgerlich, aber nicht wichtig. Alles, was Reibung erhöht, Lernen erfordert oder mehr Arbeit verlangt, ist Zeitverschwendung.
Umgekehrt gibt es auch Leute, die nach einer Lösung suchen, die verhindert, dass es jemals wieder kaputtgeht. Für diese Leute ist das Problem wichtig, also ist es in Ordnung, wenn die Lösung Reibung, Lernen und zusätzliche Arbeit erfordert. Diese Leute wollen Nix.
Die meisten gehören zur ersten Gruppe, also wird ein Unternehmen wie Railway, das wachsen will, am Ende eine Lösung wählen, die zu dieser Gruppe passt.
Cargo.lock-Datei mit Nix zu bauen. nixpkgs geht in die entgegengesetzte Richtung zur maßgeschneiderten Versionssuppe, aber Nix selbst kann auch mit diesem Ansatz ordentlich umgehen.Aus meiner Erfahrung als DevOps/SRE heraus landet man, wenn jemand ein System zur Verwaltung von Abhängigkeiten usw. bauen will, meist auf einem von zwei Wegen. Python kann als Beispiel dienen.
Option 1: „Nehmen wir ein großes gemeinsames Monorepo.“ Der Vorteil ist, dass alles an einem Ort ist, das Benötigte enthalten ist und alle dasselbe verwenden, wodurch sich Probleme wie Schwachstellen leichter beheben lassen. Der Nachteil ist, dass immer jemand eine spezielle Version will, gestaffelte Rollouts schwierig sind und Änderungen leicht zum Big Bang werden, und dann kommt die Frage: „Wie bauen wir eine kleine Docker-Version?“
Option 2: „Alle bekommen ihre eigene conda/venv.“ Der Vorteil ist, dass jeder genau das bekommt, was er will, keine unnötigen Pakete verwendet und stufenweise Upgrades einfacher sind. Der Nachteil ist, dass es irgendwann heißt: „Wie viele conda-Umgebungen gibt es hier eigentlich?“, Bibliotheken verschiedener Gruppen möglicherweise nicht mit derselben Kombination von Python-Bibliotheken getestet werden und niemand weiß, wo die unterschiedlichen conda-Umgebungen liegen, wodurch Schwachstellenmanagement zum Albtraum wird.
Deshalb bin ich immer skeptisch, wenn jemand sagt: „Dieser neue Ansatz löst alles.“ Je länger ich im Beruf bin, desto wahrer wird der Satz: „Es gibt keine Lösungen, nur Trade-offs.“
Selbst mit nur wenig Nix-Erfahrung wirken die Argumente hier auf mich nicht besonders stimmig.
Es heißt: „Für Beitragende, die mit der Versionsverwaltung von Nix nicht vertraut sind, ist sie weder klar noch wartbar“, und „Node und Python unterstützen nur noch die neuesten Major-Versionen“. Ich verstehe nicht, warum das nicht wartbar sein soll. Falls es daran liegt, dass man eine Liste verfügbarer Versionen pflegen muss, frage ich mich, ob sich das nicht automatisieren lässt.
Darüber hinaus verstehe ich nicht, warum Railway überhaupt definieren muss, wie Nutzer Nix verwenden. Ist nicht einer der Kernpunkte von Nix, dass man eine leere Maschine mit exakt den gewünschten Paketversionen konfigurieren kann? Ich verstehe nicht, warum Railway sich zwischen Nutzer und diese Versionen schalten und sie einschränken muss.
Falls die Architektur so ist, dass Nutzer Nix gar nicht direkt zu Gesicht bekommen, bleibt die ursprüngliche Frage trotzdem bestehen: Kann man die Liste der Paketversionen nicht automatisieren?
Ehrlich gesagt wirken die genannten Gründe nicht besonders belastbar. Vielleicht ist die Person gegangen, die Nix eingeführt hat, und die Verbliebenen mochten es nicht sonderlich. Die Sprache selbst ist nicht gerade toll, und die alte Dokumentation war auch nicht großartig.
Trotzdem kenne ich den von ihnen gewählten Stack nicht gut genug, aber ich frage mich, ob er ein ähnliches Maß an Determinismus bietet wie Nix. Falls nicht, könnte ihnen das später auf die Füße fallen oder den Betrieb erschweren.
/nix/store-Schicht, in der alle Nix-bezogenen Pakete und Bibliotheken liegen, die für Build und Runtime benötigt werden.“Das klingt ein bisschen wie: „Wir konnten ein Auto nicht dazu bringen, vorwärtszufahren, also geben wir Autos auf.“ Das ist eine der Dinge, die Nix am zuverlässigsten kann. Es erkennt die Runtime-Abhängigkeiten, die vom Ergebnis-Binary tatsächlich referenziert werden, automatisch per Matching der
/nix/store-Hash-Strings.Wenn sie das nicht hinbekommen haben, haben sie es ziemlich ungewöhnlich eingesetzt oder etwas gravierend falsch gemacht. Mir fällt kaum ein, wie man Nix überhaupt daran hindern könnte, das automatisch zu lösen.
Deshalb würde ich ihre Nix-Erfahrung nicht allzu tiefgehend verallgemeinern. Die Punkte zur Versionsverwaltung sind sehr allgemeine Probleme, die alle haben; wenn sie versucht hätten, diese zu lösen, wäre es interessanter gewesen.
Nix bietet keine Garantie für beliebige Versionen, sondern eine Commit-Garantie. Bei Randfällen wird man wegen glibc-Änderungen oder kollidierenden Shared Libraries Schmerzen bekommen.
Vielleicht ist es etwas spät, aber ich biete gern Consulting an, um das in der idiomatischen Nix-Weise zum Laufen zu bringen. Das Produkt sieht cool aus.
Und nicht nur das: auch deren Abhängigkeiten, und wiederum deren Abhängigkeiten, immer weiter, sodass es häufig zu groß angelegten Rebuilds kommt.
Konflikte bei Shared Libraries vermeidet man damit zwar, aber diese Lösung ist extrem verschwenderisch und kann auch die Entwicklung schmerzhaft machen. Das sieht man am Staging-Prozess von nixpkgs.
Trotzdem dürfte es immer noch in einem Zustand sein, der „mit hoher Wahrscheinlichkeit korrekt funktioniert“, mehr als 95 % der Software da draußen.
Ich verstehe nicht, warum sie nicht eine eigene Derivation erstellen konnten, statt vom nixpkgs-Hash abhängig zu sein.