4 पॉइंट द्वारा GN⁺ 2024-03-17 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • अगर आपका environment पहले से Docker images को deployment unit की तरह इस्तेमाल करता है, तो Nix workflow को बहुत बदले बिना deterministic builds और dependency pinning को आज़माने का एक entry point बन सकता है
  • सामान्य docker build अक्सर Ubuntu repositories या external downloads जैसे public internet state पर निर्भर करता है, इसलिए समय बीतने के बाद वही image दोबारा reproduce करना मुश्किल हो जाता है
  • Nix को package level पर ज़रूरी dependencies का पता होता है, इसलिए dockerTools.buildLayeredImage के साथ ऐसा setup संभव है जिसमें सिर्फ बदली हुई layers ही फिर से upload हों
  • Go-आधारित douglas-adams-quotes उदाहरण दिखाता है कि pkgs.buildGoModule से binary बनाकर nix build.#docker, docker load <./result के जरिए उसे सामान्य Docker image की तरह load और deploy किया जा सकता है
  • अगर monorepo की कई services layers और Nix cache साझा करें, तो पुराने commits की image reproduction और repeated deployments में build time और cost कम की जा सकती है

Docker image build में Nix जोड़ने की वजह

  • Nix में एक साथ तीन भूमिकाएँ हैं: package manager, language, और operating system
    • Nix language से package build instructions लिखे जाते हैं
    • Nix package manager उन्हीं instructions के आधार पर software, tools, NixOS images, container images आदि बना सकता है
  • मौजूदा CI/CD pipeline में Nix लाना आसान नहीं है
    • Nix कई developers के परिचित तरीकों से अलग है
    • जब तक environment ऐसा न हो जहाँ सब कुछ नए सिरे से शुरू किया जा सके, जैसे कोई नया startup या homelab, तब तक adoption barrier काफ़ी बड़ा हो सकता है
  • जो organizations पहले से Docker इस्तेमाल करती हैं, वे container image build से Nix अपनाना शुरू कर सकती हैं और existing deployment flow को ज़्यादा बिगाड़े बिना experiment कर सकती हैं

Docker build कहाँ डगमगाता है

  • Docker और containerization का व्यापक adoption हो चुका है, इसलिए Docker images अब इंटरनेट के de facto general-purpose package format की तरह इस्तेमाल होती हैं
    • Fly.io, Railway, Render जैसे platforms arbitrary VM images या tarball के रूप में Linux programs की बजाय Docker images का उपयोग करते हैं
  • Docker builds हमेशा deterministic नहीं होते
    • इंटरनेट पर आमतौर पर दिखने वाले ज़्यादातर Dockerfiles सही build हो जाते हैं, लेकिन failure का छोटा सा अनुपात भी production issues में बदल सकता है
  • सबसे बड़ी कमजोरियों में से एक यह है कि Docker build public internet तक पहुँचता है
    • Ubuntu repositories से packages डाउनलोड करने के लिए इसकी ज़रूरत होती है
    • बाद में वही image फिर से बनानी हो तो उस समय की Ubuntu repository की exact state वापस लाना मुश्किल होता है
    • Ubuntu 18.04 उस साल support end के करीब है, इसलिए कौन-सी चीज़ उस version पर निर्भर है यह outage के बाद ही पता चल सकता है
  • साधारण तरीके से Docker image में packages जोड़ने पर wasted space पैदा हो सकती है
    • अगर build की शुरुआत में apt-get upgrade चलाया जाए, तो container image के अंदर files replace हो सकती हैं
    • replace होने से पहले वाली files layer में shadow copies की तरह जमा रह सकती हैं

Nix Docker layers को कैसे संभालता है

  • Nix को पहले से पता होता है कि कौन-सी dependencies चाहिए, और वह उन्हें कम-से-कम Docker layers में बाँट सकता है
    • सिर्फ code की एक line बदलने से apt या npm को dependencies फिर से install करने की ज़रूरत नहीं पड़ती
    • image update में सिर्फ वास्तव में बदले हुए न्यूनतम हिस्से शामिल किए जा सकते हैं
  • dockerTools Nix packages और उनकी dependencies को Docker image में डालने के लिए tools का एक set है
  • Nix से बनी Docker images मोटे तौर पर दो प्रकार की होती हैं
    • non-layered image: program, dependencies, TLS root certificates जैसी अतिरिक्त चीज़ों को एक folder में रखकर single-layer image के रूप में expose किया जाता है
    • layered image: हर dependency को अलग image layer में रखा जाता है ताकि सिर्फ बदले हुए हिस्से ही upload करने पड़ें
  • Docker के अंदर भी content-addressed storage होता है, लेकिन सिर्फ docker build से उसका पूरा फायदा उठाना मुश्किल है
    • Nix की layered images package-wise layers इस्तेमाल करती हैं, इसलिए glibc जैसी dependency को सिर्फ एक बार upload करना काफ़ी है
    • हाँ, अगर उस library में बदलाव करना पड़े, तो वह layer दोबारा upload करनी होगी

Go service उदाहरण: Douglas Adams Quotes

  • यह example service Douglas Adams के quotes देने वाला एक Go program है
  • Go module project में pkgs.buildGoModule से Nix package define किया जाता है
bin = pkgs.buildGoModule {
  pname = "douglas-adams-quotes";
  inherit version;
  src = ./.;
  vendorHash = null;
};
  • यह definition Go module template का उपयोग करके Go compiler, CGo के लिए C compiler, और external dependency downloads को set up करती है
    • pname package का नाम है
    • version service के Git commit से अपने-आप generate होता है
    • src = ./.; current working directory के source code का उपयोग करता है
    • अगर standard library के अलावा कोई dependency नहीं है, तो vendorHash = null रखा जा सकता है
    • अगर external dependencies हैं, तो सभी dependencies का hash देना होगा या gomod2nix इस्तेमाल किया जा सकता है
  • package को इस command से build किया जाता है
nix build .#bin
  • Docker layered image dockerTools.buildLayeredImage से बनाई जा सकती है
docker = pkgs.dockerTools.buildLayeredImage {
  name = "registry.fly.io/douglas-adams-quotes";
  tag = "latest";
  config.Cmd = "${bin}/bin/douglas-adams-quotes";
};
  • config.Cmd में built server binary देने पर ज़रूरी चीज़ें साथ में copy हो जाती हैं
    • glibc और execution के लिए आवश्यक items भी शामिल हो जाते हैं
  • CA root certificates जैसे अतिरिक्त packages को 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";
};
  • image को इस command से बनाकर Docker daemon में load किया जाता है
nix build .#docker
docker load < ./result
  • dive में खोलने पर दिखता है कि हर layer nixpkgs के अलग packages जोड़ती है, और आखिर में entries image root में symbolic links के रूप में जुड़ जाती हैं

Monorepo में layers साझा करने का असर

  • अगर एक ही repository में कई services हों, तो Nix से बनी Docker images layers साझा कर सकती हैं
    • यह बिना किसी अलग configuration के होता है
    • सिर्फ Docker से ऐसा करने पर अक्सर कई shared base images बनानी पड़ती हैं, जिनमें कुछ services के लिए अनावश्यक tools और extra contents भी शामिल हो सकते हैं
  • उदाहरण के तौर पर Xe/x repository 10 साल के side projects, experiments, और tools वाला monorepo है
    • इसके कई projects लगभग तीन platforms पर deploy हैं
    • common deployment base की ओर converge करने के लिए इन्हें Docker images में पैक करने का काम चल रहा है
  • जब किसी एक service का update push किया जाता है, तो उसके साथ दूसरे services के साथ shared कुछ updates भी push हो जाते हैं
    • यह तरीका कई projects में समय और cost बचाता है

सिर्फ Docker cache से पाना मुश्किल हिस्सा

  • Docker caching का इस्तेमाल करके सैद्धांतिक रूप से Nix जितनी efficient image builds की जा सकती हैं
  • लेकिन Docker तरीके से वही असर पाने के लिए build stages को maintain करना मुश्किल हो सकता है
    • shared libraries को अलग layers में install करना पड़ता है
    • build के दौरान network stack फिर से enable करना पड़ता है, जिससे reproducibility कमज़ोर होती है
    • search paths, compiler flags, और CGo settings को फिर से adjust करना पड़ता है
  • Nix का dockerTools.buildLayeredImage packages को container में डालने की details खुद संभाल लेता है, जिससे configuration सरल हो जाती है

पुराने समय की reproduction और Nix cache

  • Nix का एक बड़ा फ़ायदा यह है कि पुराने समय के software को ठीक उसी तरह दोबारा build किया जा सकता है
    • बाद में किसी खास customer environment के bug को reproduce करना हो, तो वही Docker image फिर से बनाई जा सकती है
    • package build software और उसकी पूरी dependency state को एक समय-बिंदु पर pin करने का काम करता है
  • XeDN कई सालों से बनाया जा रहा project है, और 14 महीने पुराना version इस command से build किया जा सकता है
nix build github:Xe/x/567fdc2#xedn-docker
  • यह command GitHub repo Xe/x के एक specific commit से xedn-docker target build करती है
    • Docker daemon में load करने पर उस समय जैसी byte-for-byte वही image बनती है
    • इस example में Go 1.19 भी शामिल है
  • सामान्य Docker build से वही reproducibility पाने के लिए बहुत अधिक storage cost लग सकती है
  • Nix cache, Nix commands के outputs को store करता है ताकि बाद में उन्हें फिर से build न करना पड़े
    • हर developer laptop पर nokogiri जैसे package को दोबारा build करने की ज़रूरत नहीं पड़ती
    • cloud में पहले से built results सीधे लिए जा सकते हैं
  • Garnix सभी flakes projects की CI में इस्तेमाल होता है और हर commit का build status report करता है
    • homelab machines की configuration भी Garnix से build की जाती है, इसलिए रोज़ शाम update करते समय local build की बजाय Garnix cache से latest configuration ली जाती है

Deployment artifact के रूप में निष्कर्ष

  • Docker image builder के रूप में Nix को Docker के image builder से बेहतर माना जा सकता है
  • Nix आपको result specify करने देता है, और उस result तक पहुँचने की detailed procedure को खुद step-by-step लिखने की ज़रूरत नहीं पड़ती
  • जो environments पहले से Docker इस्तेमाल करते हैं, उनके लिए Docker image build से Nix अपनाना एक व्यावहारिक adoption path है
  • Nix से बनी Docker images monorepo के अलग-अलग हिस्सों के बीच layers साझा कर सकती हैं
  • binary cache की वजह से पहले से built पुराने code को दोबारा build करने की ज़रूरत नहीं पड़ती
  • अंतिम output एक सामान्य container image होती है, जिसे AWS, Google Cloud, Fly.io जैसे platforms पर deploy किया जा सकता है

1 टिप्पणियां

 
GN⁺ 2024-03-17
Hacker News की राय
  • मैंने Nix को पसंद करने की कई बार कोशिश की, लेकिन अब लगता है कि हार मान लेनी चाहिए
    मेरे पास 2 सिस्टम हैं जिन पर Nix चल रहा है, लेकिन उन्हें छूने से डर लगता है, और पहले दोनों बिगड़ गए थे तो मुझे उन्हें शुरू से फिर से install करना पड़ा था। सिद्धांत रूप में Nix idempotent और deterministic है, लेकिन असल में आपको समझना पड़ता है कि वह किस चीज़ के बारे में deterministic है, और अगर आप उन सभी हिस्सों के व्यवहार को गहराई से नहीं जानते जिन पर यह निर्भर करता है, तो आपको अजीब नतीजे, विचित्र और बेकार errors, या फिर कोई feedback ही नहीं मिलने जैसी स्थिति का सामना करना पड़ता है
    Documentation पूरी और तकनीकी रूप से सही हो सकती है, फिर भी वह बेहद मुश्किल और उलझाऊ है, और tutorials आपको सिर्फ 80% रास्ते तक ले जाते हैं। उससे आगे जाएँ तो आपको खुद बनाना पड़ता है, जिसके लिए सालों का अनुभवजन्य ज्ञान और frustration चाहिए—अब मैं उतना झेलना नहीं चाहता। Docker की खूबी उलटे उसका chaos ही है: shell और distro package manager के बारे में बस मोटा-मोटी जानकर भी आप लगभग कुछ भी बना सकते हैं, और समस्या आए तो दशकों पुराने tools की वजह से diagnose और fix करना कहीं आसान होता है
    Nix, Emacs की तरह है: धैर्य और गहरे, दूरदर्शी ज्ञान के साथ आप कुछ भी कर सकते हैं, लेकिन लगता है या तो इसमें पूरी तरह डूबना पड़ेगा या दूरी बनाकर देखते रहना पड़ेगा

    • मैं भी इसी रास्ते से गुज़रा हूँ, और deterministic builds पसंद हैं, लेकिन मुझे लगता है Dockerfile की सबसे बड़ी समस्या यह है कि औसत developer को वह deterministic लगता है
      उसी मशीन पर लगातार दो बार build करें तो वही result आता है, लेकिन समय बीतने पर package manager और base image tags update हो जाते हैं, तो एक महीने बाद rebuild का result पूरी तरह अलग हो सकता है। जब team करीब 40 containers manage करती है, तो containers ठीक करना काम का बड़ा हिस्सा बन जाता है
      इसलिए सिद्धांत में Nix perfect होना चाहिए, लेकिन यह इतना अलग है कि vendor tools Nix पर नहीं चलते, और error आने पर web पर जल्दी solution ढूँढना मुश्किल होता है। इसी frustration की वजह से मैंने https://www.stablebuild.com बनाया, जो Ubuntu, Debian, Alpine containers पर Docker-based deterministic builds देता है। इसमें immutable Docker Hub pull-through cache, Ubuntu/Debian/Alpine package repositories की daily full copies, लोकप्रिय PPAs और PyPI index की daily copies, और arbitrary files/URLs के लिए immutable cache शामिल हैं
      व्यवहार में यह इस्तेमाल करने और debug करने में आसान है, software compatibility भी व्यापक है, और StableBuild पर migrate किए गए containers में non-determinism से पैदा हुई समस्याएँ 0 रहीं
    • Documentation सिर्फ मुश्किल नहीं है; खासकर नए CLI और flakes वाली तरफ तो कई जगह सीधे-सीधे गलत भी है, और ये edge cases भी नहीं हैं
      मैंने यह समझने में समय लगाया कि nix develop documentation के मुताबिक क्यों behave नहीं करता और उसे वैसा behave कैसे कराया जाए। Docs कहते हैं कि default रूप से आप build-time environment में जाते हैं, लेकिन असली default sealed नहीं है, और command-line option names भी confusing हैं, इसलिए knowledge source से निकालनी पड़ी
      Reproducibility तोड़ने वाले edge cases को भी और साफ़ तौर पर cover किया जाए तो अच्छा होगा। Floating-point code operation order के प्रति sensitive होता है, और operating system preemption से state leak भी हो सकती है; obvious चीज़ें भी explicitly न लिखें तो लोग खुद अपने पैर पर कुल्हाड़ी मारेंगे
    • बात इतनी भी खराब नहीं है, लेकिन standard NixOS setup में भी user accounts और system internals के अंदर non-reproducible state बहुत ज़्यादा बची रहती है
      “Erase your darlings” setup इस्तेमाल करें तो user account के बाहर की non-reproducible state का ज्यादातर हिस्सा हटाया जा सकता है। थोड़ा झंझट है, लेकिन NixOS में क्या ऐसा है जो झंझट वाला नहीं है
      https://grahamc.com/blog/erase-your-darlings/
      User account के अंदर की मुझे परवाह नहीं, और Home Manager मुझे पसंद नहीं
    • काम पर हम Docker और NixOS दोनों इस्तेमाल करते हैं, लेकिन ऊपर बताई समस्याएँ मैंने कभी नहीं झेली हैं
      Docker ठीक है, लेकिन Mac पर performance अच्छी नहीं है; Nix अच्छा है क्योंकि कई machines पर चीज़ों को एक ही तरह install और run कराना मामूली काम हो जाता है। Nix documentation खराब है, लेकिन ChatGPT-4 Nix issues solve करने में शानदार रहा
      Nix problems में से 90% मुझे इसलिए हुईं कि मैं “Nix way” के बाहर की चीज़ें करने की कोशिश कर रहा था
    • Fedora Atomic जैसी immutable distro एक बार आज़माने लायक है
      मैंने लगभग हर distro package manager इस्तेमाल किया है, और कोई खास काम किए बिना भी किसी-न-किसी तरह सबको तोड़कर देखा है। Fedora Kinoite layer add/remove करने, daily updates, और Silverblue से rebase करने तक में भी अच्छी तरह टिका हुआ है। निजी तौर पर लगता है rpm-ostree, Nix की जगह ले लेगा
  • मुझे लगता है कि Nix और NixOS की हालत GitHub से पहले वाले git जैसी है
    मूल idea पुराने तरीकों, जैसे SVN या Docker, की तुलना में ज्यादा गंभीर computer science पर आधारित है, और अंदरूनी plumbing में अभी भी समस्याएँ हैं, लेकिन वह बदतर नहीं है; हालांकि user-facing tools और documentation अभी mainstream adoption के स्तर के नहीं हैं
    flox के release से शायद यह बदल गया हो: https://flox.dev. यह लगभग seamless है, और DE Shaw background देखते हुए यह चौंकाने वाला भी नहीं है
    Nix, flakes और nix-command के बिना ज्यादा मायने नहीं रखता, लेकिन दोनों को documentation में experimental बताया गया है और default में बंद भी हैं। Documentation बेहतर हो रहा है, लेकिन अभी भी कमी है, और nixlang ठीक से सीख लेने पर शानदार है, पर mainstream entry barrier के तौर पर स्वीकार्य नहीं हो सकता। nix-env -iA foo असल में ज्यादातर मामलों में वह behavior नहीं देता जो आप चाहते हैं, इसलिए Nix विज्ञापन में बताए गए package manager से ज्यादा उन कंपनियों का secret weapon है जो in-house expertise संभाल सकती हैं
    flox “एक बार try करो और तुरंत बेहतर experience पाओ” वाली barrier को कम करता है। Nix/NixOS या कुछ वैसा ही अंततः Docker को Subversion की तरह पीछे धकेल देगा, लेकिन GitHub जैसा moment आने तक यह नहीं होगा, और फिर एक साथ होगा
    इस thread में Nix को लेकर ज्यादातर complaints technically गलत हैं, लेकिन पूरी तरह समझ में आती हैं, और इससे भी अहम बात यह है कि यह user की गलती नहीं है। मुझे पता है कि git/GitHub के बिना वाली दुनिया को जानने वाली पीढ़ी कम होती जा रही है, लेकिन शुरुआती Google hiring bar पास कर चुके लोगों के सामने Linus को यह समझाते सुनना अच्छा होगा कि वे ऐसे tool में रुचि क्यों लें जो उन्हें complex लगता है
    https://youtu.be/MjIPv8a0hU8?si=QC0UnHXRdMpp2tI4

    • मैं flox.dev से Ron हूँ, और इस post की वजह से हमारी पूरी team खूब हँसी
      हम इसे कुछ समय से बना रहे हैं, और अगर कुछ ऐसा हो जिसे इसे बेहतर बनाने के लिए priority देनी चाहिए, तो सुनना चाहेंगे
    • मुझे लगता है कि developer tool के सफल होने के लिए, सबसे आम tasks में engineer के पास tool को गलत तरीके से इस्तेमाल करने के कम से कम तीन तरीके होने चाहिए, फिर भी वह implicit technical debt छोड़ते हुए “done” तक पहुँच सके
      git में यह है, लेकिन Nix में अभी नहीं है, इसलिए मुझे पक्का नहीं कि GitHub जैसा moment मदद करेगा या नहीं
  • इस blog post में यह explanation नहीं है कि shared Docker layers क्यों useful हैं
    वजह caching है: जितनी ज्यादा images एक ही layers share करती हैं, उतनी ज्यादा चीजें cache की जा सकती हैं, जिससे container startup तेज होता है
    Docker यहाँ कमजोर इसलिए है क्योंकि caching का फायदा पाने के लिए image build करते समय layers को मौजूदा layers से जितना हो सके उतना समान होना पड़ता है। उदाहरण के लिए, अगर आज apt-get install python3 चलाने पर कोई नया update नहीं है, तो उसे कल जैसी ही layer बनानी चाहिए, लेकिन Docker layers file hash से cache होती हैं, इसलिए creation time जैसे metadata तक, हर file बिल्कुल समान होनी चाहिए
    Nix पहले से dependencies को hash के रूप में store करता है, इसलिए version और settings समान हों तो layer हमेशा समान बनती है

    • इसे ऐसे कहना ज्यादा सही लगता है: Dockerfile format layers के बीच hierarchical relationship force करता है
      Dependencies आम तौर पर tree नहीं बल्कि graph बनाती हैं, इसलिए यह जल्दी ही परेशान करने लगता है। Nix या शायद Bazel जैसे alternative tools में यह constraint नहीं होता, और वे dependency graph को Docker layers पर map करके fine-grained caching हासिल कर सकते हैं। Dockerfile से इसे express नहीं किया जा सकता
    • Docker में अगर layer cached है, तो apt-get वाली layer अपने आप invalidate नहीं होती
      यह सिर्फ --no-cache इस्तेमाल करने पर या parent layer में change होने पर बदलती है
  • पिछले 2–3 दिनों से Darwin पर Docker image build करने की कोशिश में जूझ रहा था, और यह लेख ऐसा लगा जैसे ब्रह्मांड मेरा मज़ाक उड़ा रहा हो
    Nix जिस लक्ष्य के लिए चाहिए, उसके लिए साफ़ तौर पर बेहतरीन tool है, लेकिन इसमें कुछ अंधेरे और छोड़े हुए कोने हैं जो आत्मा चूस लेते हैं। मुझे यह पसंद है, लेकिन कभी-कभी ऐसा लगता है जैसे मैं Rick के Compilerland adventure में घसीटा गया Morty हूँ

    • बड़ी समस्या Docker के design में ही है
      Docker मूल रूप से Linux binaries रखने वाली जेल जैसा है। Linux पर binary build करके container में डाल दो, बस; Nix code भी सरल रहता है। अगर code build कर सकते हैं, तो container बनाना बस एक और step है
      लेकिन Docker को Linux binaries चाहिए, और Mac पर Docker Desktop एक Linux VM चलाता है, सारा काम उसी के अंदर करता है, और फिर यह बात छिपा देता है। Nix ऐसा नहीं करता, इसलिए आपके पास दो विकल्प हैं
      पहला, cross-compile करना; लेकिन इसके लिए glibc तक cross-compile होनी चाहिए, और community dependencies में से ज़्यादातर भले हो जाएँ, कुछ packages ऐसे हो सकते हैं जिनके authors ने cross-compilation की परवाह न की हो। ऊपर से standard Nix cache भरने वाला Hydra cross-compilation builds नहीं करता, इसलिए लंबा build चलकर आखिर में fail भी हो सकता है
      दूसरा, Mac से एक Linux builder जोड़ना और x86_64-linux build jobs उसे भेजना। यह physical machine, VM, यहाँ तक कि NixOS Docker container भी हो सकता है
      तरीका 1 सही तरीका जैसा दिखता है, लेकिन तरीका 2 ज़्यादा practical है। जो समस्या आ रही है वह शायद तरीका 1 आज़माने की वजह से है, और इसमें सिर्फ़ Nix ही नहीं बल्कि cross-compilation का भी काफ़ी अनुभव चाहिए। अच्छा होता अगर Hydra Darwin से Linux के लिए cross-compilation भी build करके cache उपलब्ध कराता और टूटने से बचाता, लेकिन इससे cost भी बढ़ेगी। तरीका 2 वाला solution आज़माना बेहतर लगता है
      लगता है इसका official solution था: https://ryantm.github.io/nixpkgs/builders/special/darwin-bui...
    • Orbstack इस्तेमाल करें तो यह काम बेदाग़ चलता है
      कुछ stacks में cross-compilation बहुत मजबूत नहीं होती, इसलिए Docker से {aarch64,amd64} x {linux,darwin} cross-compile करता हूँ। Darwin aarch64 पर कई Docker instances चला कर सब compile कर रहा हूँ, और experience अच्छा रहा
    • क्या आप इसी तरह के 20-minute adventure की बात कर रहे हैं?
      https://github.com/gytis-ivaskevicius/high-quality-nix-conte...
    • macOS निश्चित रूप से ज़्यादा rough है, और वहाँ मैं colima इस्तेमाल करता हूँ, जो ठीक-ठाक चलता है
      एक-दो bugs हैं, लेकिन वे मुख्यतः volumes से जुड़े लगते हैं, और Docker image build ठीक से handle कर लेता है। ज़्यादा rough हिस्सा speed है, क्योंकि hardware और Docker को Linux VM emulate करना पड़ता है—दोनों मिलकर अच्छा-खासा झटका देते हैं
  • Nix से Java एप्लिकेशन के लिए Docker image बनाने का अनुभव बहुत अच्छा नहीं रहा
    gradle2nix के deprecated होने के बाद Gradle-आधारित Java एप्लिकेशन के लिए कोई स्पष्ट विकल्प नहीं दिखता। मैंने पहले एक दोस्त से कहा था कि एक साधारण Spring Boot एप्लिकेशन की यथासंभव छोटी Docker image बनाकर देखें, लेकिन Nix से बना परिणाम Nix के बिना बनी image से दोगुने आकार का था
    कोड यहाँ देखा जा सकता है: https://github.com/jossephus/Docker_challenge/blob/main/flak...

    • वजह यह है कि इसमें दो JDK शामिल हो जाते हैं
      zulu और वह JDK दोनों आ जाते हैं जिसे gradle jdk argument के रूप में शामिल करता है। nixpkgs के gradleGen को देखें तो बात समझ आ जाएगी। gradle2nix के लिए अफसोस है, और कम hacky सुधार पर काम चल रहा है
    • मैंने 10 साल से ज्यादा समय से Java इस्तेमाल नहीं किया है, इसलिए ज्यादा मदद नहीं कर सकता, लेकिन एक बार Python और सभी dependencies, busybox, tini तक शामिल करके एप्लिकेशन को 70MB container में रखा था
      मोटे तौर पर यह कुछ ऐसा था: https://gist.github.com/takeda/17b6b645ad4758d5aaf472b84447b...
      सब कुछ musl से link किया, Python compile करते समय एप्लिकेशन में इस्तेमाल न होने वाले सभी packages disable किए, और boto3/botocore के unused हिस्से हटाए। सिर्फ boto3/botocore ही 100MB से ज्यादा हैं
      Nix packages मूल रूप से NixOS operating system को target करते हैं, इसलिए सामान्य स्थिति में, जहाँ disk space पर्याप्त होता है, सभी features enabled रहने की उम्मीद होती है। इस कारण बहुत सारी गैर-जरूरी dependencies साथ आ जाती हैं। इसके विपरीत Alpine images Docker के लिए design की गई हैं, जहाँ packages में अतिरिक्त features बंद करना लक्ष्य होता है, इसलिए परिणाम छोटा होता है
      छोटी images बनाने के लिए override के जरिए अनावश्यक चीजें बंद करनी होंगी। उदाहरण के लिए zulu में alsa, fontconfig, freetype, xorg, cups, gtk, cairo, ffmpeg जैसी चीजें आती हैं। दोस्त ने सिर्फ जरूरी files सावधानी से निकालकर container में रखी थीं, जबकि Nix वाली तरफ zulu package पूरा और उसकी सारी dependencies bundle हो गईं
      पहले इसे ठीक करें कि सिर्फ एक JDK शामिल हो, फिर ऊपर दिए तरीकों से JDK का size और घटाया जा सकता है। openjdk_headless इस्तेमाल करना शायद और आसान हो
      https://github.com/NixOS/nixpkgs/blob/master/pkgs/developmen...
    • न्यूनतम Java OCI container के मामले में jib को हराना मुश्किल है
      https://github.com/GoogleContainerTools/jib/tree/master/jib-...
    • openjdk_headless Spring के लिए गैर-जरूरी GTK और X dependencies को skip कर देता है
    • मैंने खुद challenge में हिस्सा लेकर Nix code को थोड़ा साफ किया, और मुख्य बात बहुत छोटा JRE बनाना लगती है
      Zulu की जगह Nixpkgs के headless OpenJDK build को आधार बनाया, जिससे GUI library dependencies हट गईं, और pkgs.jre_minimaljlink से custom minimal JRE बनाया
      image size 161MB है, जो demo_jlink image से थोड़ा बड़ा है। वजह यह है कि एप्लिकेशन चलाने के लिए वास्तव में जरूरी सभी modules शामिल करने पर JRE करीब 90MB हो जाता है। Dockerfile_jlink में jdeps call सभी modules detect नहीं कर पा रहा था और सिर्फ java.base से JRE बना रहा था। अगर मेरे minimal JRE में भी सिर्फ java.base डालें, तो JRE size करीब 50MB होता है, और टूटी हुई container image Podman के हिसाब से 117MB है
      dockerTools.buildImage call में गलत copyToRoot भी हटा दिया। config.Cmd के string context भर से app image में आ जाता है, जबकि पुराना code app को एक बार और copy कर रहा था। साथ ही इसे dockerTools.buildLayeredImage में बदला, ताकि हर store path अलग image layer में जाए; कई container images के बीच dependencies share करते समय यह space scalability के लिए अच्छा है, लेकिन single image experiment पर इसका असर नहीं है
      बाकी काम ज्यादातर JRE size optimize करना है। glibc अगली सबसे बड़ी dependency है, करीब 30MB, शायद इसलिए कि Nixpkgs glibc को कई locales और character encodings support करने के लिए build करता है। इसे अलग derivation या output में बाँटकर selectable बनाना संभव है या practical है, यह नहीं पता। अगर कई images dockerTools.buildLayeredImage से बनती हैं, तो एक ही Nixpkgs commit इस्तेमाल करने की शर्त पर glibc और बाकी dependencies सभी share हो जाएँगी
      https://github.com/max-privatevoid/hackernews-docker-challen...
  • अगर आपने पहले ही Nix अपना लिया है तो बहुत बढ़िया, और अच्छा होगा अगर Nix या Guix जैसे declarative package management और ज़्यादा व्यापक हों
    अगर आप पहले से Docker इस्तेमाल कर रहे हैं लेकिन Nix को धीरे-धीरे अपनाना चाहते हैं, तो इस प्रेज़ेंटेशन वाला तरीका भी है: https://youtu.be/l17oRkhgqHE
    configuration और container build को तुरंत पूरी तरह Nix पर ले जाने के बजाय, Dockerfile को बनाए रखते हुए उससे Nix configuration build करवाई जा सकती है। सबसे बड़ी कमी यह है कि layers का बिल्कुल लाभ नहीं मिल पाता, और फायदा यह है कि Dockerfile को धीरे-धीरे बदलते हुए मौजूदा Docker infrastructure या automation को reuse किया जा सकता है

    • यह लेख भी वही तरीका इस्तेमाल करता है
      [1] https://mitchellh.com/writing/nix-with-dockerfiles
    • लेख की एक मुख्य धुरी यह है कि Docker builds reproducible नहीं होते, लेकिन Nix reproducible है
      मगर लगता है कि उस non-reproducibility का बड़ा हिस्सा शायद इसलिए है कि Docker layers में से किसी एक के लगातार उपलब्ध रहने की गारंटी नहीं होती। अगर ऐसा है, तो यह भी जानना चाहूंगा कि क्या Nix में package versions के repository में हमेशा बने रहने की कोई गारंटी है
  • थोड़ा अलग विषय है, लेकिन इन slides की illustrations बनाने वाली illustrator Annie Ruygt ने YC startups RethinkDB(rethinkdb.com) और Pachyderm(pachyderm.io) के logos और brand art भी बनाए थे
    वे Pachyderm में काम कर चुकी हैं, और Pachyderm को पूर्व RethinkDB engineers ने शुरू किया था। उनका काम वाकई अच्छा है

  • कुछ समय पहले infrastructure team की recommendation पर मैंने Nix से CI base image build करने की कोशिश में करीब आधा दिन लगाया था, लेकिन image बहुत बड़ी थी और linking issues की वजह से कुछ हिस्से काम नहीं कर रहे थे
    खास तौर पर खीझाने वाली बात यह थी कि multi-architecture image बनाते समय वह दूसरी architecture की कोई चीज़ सच में run करने की कोशिश करता था, और qemu सिर्फ hardware virtualization को support करता है। build machine और workstation VM हैं, इसलिए वह उपलब्ध नहीं है, जबकि binfmt-misc मौजूद है। अगर उसने arm64 mkdir को fork/exec करके /tmp बनाया होता तो काम हो जाता, लेकिन Docker layer तो सिर्फ tar file है, इसलिए नीचे की तरह directory बनाई जा सकती है
    echo "tmp uid=0 gid=0 time=0 mode=0755 type=dir" | bsdtar -cf - @-
    बहुत संभावना है कि यह exact layer कहीं पहले से मौजूद हो, इसलिए user को इसे download करने की भी ज़रूरत न पड़े
    हर बार Nix आज़माते हुए लगता है कि बस कुछ और महीनों में इसे नियमित रूप से इस्तेमाल किया जा सकेगा। nixpkgs में लगभग हर package है जिसकी जरूरत होती है और workstation पर install भी अच्छी तरह हो जाता है। लेकिन “bash, python, build-essential, Bazel चाहिए” जैसी requirement Docker image builder का लक्ष्य जैसी नहीं लगती। अगर उपयोग सिर्फ एक Go binary को Docker image में डालने का है, तो Nix की जरूरत नहीं। distroless लीजिए और application को tar file में डाल दीजिए, वही container बन जाता है। व्यक्तिगत रूप से मैं Bazel का rules_oci इस्तेमाल करता हूं, जिसके अंदर भी लगभग यही काम होता है, बस इतनी extra intelligence होती है कि कई architectures के लिए binaries build करके image index YAML बनाता है और registry में push कर देता है

    • दूसरी architectures के लिए binaries को सच में execute किए बिना भी cross-compile कर पाना चाहिए
      बेशक package की build files को इसे support करना होगा। qemu सिर्फ hardware virtualization support करता है, यह बात भी सही नहीं लगती। जिन architectures में सिर्फ software emulation संभव है, वहां भी qemu इस्तेमाल किया जा सकता है
      minimal example यहां cover किया गया है: https://discourse.nixos.org/t/how-do-i-get-a-shell-nix-with-...
      मैं यह तक नहीं कहूंगा कि यह pkgCross इस्तेमाल करने जितना आसान होना चाहिए, लेकिन सामान्य process में आपको कौन-सी specific समस्या मिली, यह जानने में दिलचस्पी है
      https://nix.dev/tutorials/cross-compilation.html
    • आप शायद official Nix Docker image की बात कर रहे हैं, और वह सचमुच बड़ी है
      हमने कुछ projects में इसे कई साल तक इस्तेमाल किया है, लेकिन अगर size समस्या है, तो लेख में बताए गए तरीके से केवल specified चीजों वाली बहुत छोटी images बनाई जा सकती हैं
      [1] https://hub.docker.com/r/nixos/nix/tags
    • Docker image के लिए cross-compilation ही वह use case था जिसके लिए मैंने Nix इस्तेमाल किया था
      musl तक इस्तेमाल किया, और CI में तेज़ी व consistency से ऐसी images build कीं जो किसी भी दूसरे tool से बनी images से सबसे छोटी थीं, और caching भी अच्छी तरह काम करती थी। कौन-सी चीज़ execute हुई थी, यह मुझे ठीक से समझ नहीं आ रहा
    • जानना चाहूंगा कि final Nix और Docker files कैसी दिखती थीं, और क्या किसी अजीब third-party binary को support करने के लिए आपको buildFHSEnv इस्तेमाल करना पड़ा था
      लगता है Nix को ऐसे और लेखों की जरूरत है जो बताते हों कि मौजूदा systems से टुकड़ों में, smoothly migration कैसे करें
  • एक platform engineer के तौर पर मैं Nix को पसंद करना चाहता हूं, लेकिन यह बाकी सभी के लिए आसान नहीं है
    मुझे लगता है कि developer experience भी अभी काफी खराब है। उदाहरण के लिए, devbox add python@3.11 की तरह packages जोड़ सकने की वजह से मुझे devbox का developer experience ज्यादा पसंद है
    120 लाइनों वाले flake.nix को देखकर इसे ठीक-ठीक “ज्यादा आसान” कहना भी मुश्किल है
    https://github.com/Xe/douglas-adams-quotes/blob/main/flake.n...

    • वह तुलना थोड़ी अनुचित है
      लिंक किया गया flake Go binary बनाने का तरीका define करता है, उस binary को entry point के तौर पर इस्तेमाल करने वाली Docker image define करता है, और Go binary चलाने वाली systemd service बनाने वाला NixOS module भी define करता है। साथ ही इसमें यह जांचने वाला NixOS test भी शामिल है कि वह NixOS module अपेक्षा के अनुसार systemd service बनाता है या नहीं
      NixOS test framework काफी प्रभावशाली है, और tests NixOS को साथ में चलाने वाली QEMU VM के अंदर चलते हैं। लिंक किए गए लेख से संबंधित चीजें सिर्फ Go binary और Docker image definition, और आसपास का कुछ boilerplate हैं
    • उन 120 लाइनों को representative example मानना मुश्किल है, और अगर यह single service होती तो शायद नया module बनाए बिना inline लिखा गया होता
      साथ ही कई लाइनें inline systemd service description से 1:1 मेल खाती हैं, इसलिए आप कोई भी system इस्तेमाल करें, वे गायब नहीं होंगी। इसमें overrides के जरिए कई systems declare करने का एक अच्छा तरीका भी शामिल है
      यह example “एक मामूली काम को ऐसे करें जैसे किसी बड़े गंभीर project में करते” के ज्यादा करीब है, और अगर इसे one-off की तरह लिया जाए तो लगता है इसे करीब 40 लाइनों तक घटाया जा सकता है
    • ये 120 लाइनें काफी सारे काम कर रही हैं
    • एक binary और systemd configuration संभालने के लिए 120 लाइनों का code लगा है
      वह binary और configuration file Linux में मिलने वाले “flat pack” के सबसे करीब हैं, और यह example अच्छी तरह दिखाता है कि हमने जंगल और पेड़ों में से क्या मिस किया है
  • Guix में भी guix pack -f docker नाम का एक आसान और अच्छा Docker option है
    Guix का एक फायदा यह भी है कि वह अपनी dedicated language के बजाय पहले से इस्तेमाल होने वाली language, Guile/Scheme, का इस्तेमाल करता है
    [1] https://guix.gnu.org/manual/en/html_node/Invoking-guix-pack....