- Railway เปิดตัว Railpack ระบบบิลด์ใหม่ที่มาแทน Nixpacks เดิม
- Railpack มอบความสามารถที่ดีกว่า Nixpacks เดิมในด้าน การจัดการเวอร์ชันที่ละเอียดกว่า, ขนาดอิมเมจที่เล็กลง และการแคชที่ดีขึ้น
- แนวทางจัดการเวอร์ชันแบบอิง commit ของ Nixpacks แสดงข้อจำกัดต่อความต้องการที่หลากหลายของผู้ใช้และการขยายระบบ
- Railpack ปรับปรุงความเสถียรและความยืดหยุ่นของสภาพแวดล้อมการบิลด์ด้วย การผสานรวม BuildKit, การปกป้องตัวแปรแวดล้อมลับ และการรองรับภาษาและเฟรมเวิร์กที่หลากหลาย
- ปัจจุบันรองรับ Node, Python, Go, PHP, static HTML และกำลังขยายการรองรับเฟรมเวิร์กและภาษาอย่างต่อเนื่อง
ภาพรวมและที่มา
- Railway เปิดตัวระบบบิลด์ยุคถัดไปชื่อ Railpack
- Railpack เป็นเครื่องมือใหม่ที่พัฒนาขึ้นจากประสบการณ์การใช้ Nixpacks บิลด์แอปมากกว่า 14 ล้านแอปบนแพลตฟอร์ม Railway
- แม้ Nixpacks จะเหมาะกับผู้ใช้ 80% แต่ผู้ใช้มากกว่า 200,000 คนพบกับ ข้อจำกัด ที่ทำให้ใช้งานได้ไม่สะดวก
- Railway มองว่าจำเป็นต้องมี การอัปเกรดครั้งใหญ่ เพื่อรองรับการขยายตัวของฐานผู้ใช้และสร้างสภาพแวดล้อมการบิลด์ที่ยั่งยืน
จุดปรับปรุงสำคัญของ Railpack
- การจัดการเวอร์ชันแบบละเอียด: รองรับการระบุเวอร์ชันระดับ
major.minor.patch สำหรับแต่ละแพ็กเกจ เพื่อแก้ข้อจำกัดของแนวทางเวอร์ชันที่ไม่ชัดเจนของ Nix
- ขนาดอิมเมจเล็กลง: ลดขนาดอิมเมจบิลด์พื้นฐานได้สูงสุด 38% สำหรับ Node และ 77% สำหรับ Python ทำให้ดีพลอยได้เร็วขึ้น
- การแคชที่ดีขึ้น: ผสานรวมกับ BuildKit โดยตรงเพื่อควบคุมเลเยอร์และไฟล์ซิสเต็ม เพิ่มอัตรา cache hit และแชร์แคชข้ามสภาพแวดล้อมได้
- ปัจจุบันมีการใช้งาน Railpack build แล้วทั้งบน railway.com และบริการส่วนกลาง
ปัญหาในการใช้งาน Nixpacks
- วิธีจัดการเวอร์ชันแพ็กเกจของ Nix เป็นโครงสร้างแบบ อิง commit โดยให้เฉพาะ major เวอร์ชันล่าสุด และแต่ละเวอร์ชันจะสอดคล้องกับ commit เฉพาะในรีโพซิทอรี nixpkgs
- มีความไม่มีประสิทธิภาพจากการที่ต้องจัดการแม้แต่ patch เวอร์ชันย่อยทั้งหมดด้วยตนเอง อีกทั้งผู้ร่วมพัฒนาก็เข้าถึงได้ยากเพราะระบบเวอร์ชันไม่เป็นธรรมชาติ
- แม้แต่ภาษาอย่าง Node หรือ Python ก็สุดท้ายรองรับเพียง major เวอร์ชันล่าสุดเท่านั้น
- เมื่ออัปเดตเวอร์ชัน การเปลี่ยน commit hash อาจ ส่งผลต่อเวอร์ชันของแพ็กเกจอื่นไปพร้อมกัน ทำให้ความน่าเชื่อถือของผู้ใช้ลดลงและอาจเกิดการบิลด์ล้มเหลวแบบไม่คาดคิด
- ใน Nixpacks dependency ทั้งหมดจะถูกรวมอยู่ในเลเยอร์เดียวของ
/nix/store ทำให้แบ่งอิมเมจหรือย่อขนาดได้อย่างมีประสิทธิภาพยาก
- ในด้าน การแคช ทุกครั้งที่มีการ inject ตัวแปรแวดล้อม เลเยอร์จะถูก invalidate เสมอ จึงใช้แคชได้ไม่เต็มประสิทธิภาพ
ไม่ใช่ปัญหาของ Nix เอง แต่เป็นข้อจำกัดของวิธีใช้งาน
- ปัญหาไม่ได้มาจากการออกแบบของ Nix เอง แต่เกิดจากวิธีใช้งานและการทำ abstraction ของ Railway
- Railway พยายามออกแบบให้ผู้ใช้ไม่จำเป็นต้องเข้าใจแนวคิด derivation หรือโครงสร้างเวอร์ชันภายในของ Nix แต่ตัดสินว่าในทางปฏิบัติทำไม่ได้
- เพื่อแก้ปัญหาข้างต้น จึงเริ่มพัฒนา Railpack
สถาปัตยกรรมทางเทคนิคของ Railpack
- โค้ดเบสเปลี่ยนจาก Rust → Go: เปลี่ยนมาใช้ Go เพื่อใช้ประโยชน์จาก BuildKit และเพิ่มความสามารถในการรองรับระบบนิเวศ
- BuildKit LLB และ frontend: สร้าง BuildKit LLB และ frontend แบบคัสตอมขึ้นเอง เพื่อควบคุมโครงสร้างของอิมเมจบิลด์อย่างละเอียด → อิมเมจพื้นฐานของ Node และ Python เบากว่า Nixpacks อย่างมาก
- จัดการเวอร์ชันด้วย Mise: ใช้ Mise สำหรับติดตั้งแพ็กเกจและตีความเวอร์ชัน และในอนาคตรองรับแหล่ง executable อื่นได้ง่าย
- หากบิลด์สำเร็จ จะมีการทำ lock-in ของ dependency ณ เวลานั้น → แม้ Node เวอร์ชันเริ่มต้นจะเปลี่ยนจาก 22 เป็น 24 บิลด์เดิมก็จะไม่พัง
- ใช้ฟีเจอร์ secret ของ BuildKit เพื่อปรับปรุง ความปลอดภัย/การจัดการตัวแปรแวดล้อม
ขั้นตอนการบิลด์ของ Railpack
- Analyze: วิเคราะห์โค้ดเพื่อระบุแพ็กเกจที่ต้องใช้ คำสั่งรัน และคำสั่งเริ่มต้น
- Plan: สร้างแผนการบิลด์ในรูปแบบที่ serialize เป็น JSON ได้ (ประกอบด้วยหลายขั้นตอน โดยแต่ละขั้นขึ้นกับผลลัพธ์ของขั้นก่อนหน้าหรืออิมเมจทั้งหมด)
- Generates: สร้างกราฟการบิลด์ของ BuildKit (อ้างอิงจาก input/output)
กลยุทธ์การบิลด์ด้วย BuildKit
- ขณะที่ Dockerfile ทำงานแบบลำดับเดียว BuildKit สามารถประมวลผลหลายคำสั่งแบบขนานและควบคุม input/output ของแต่ละขั้นตอนได้อย่างละเอียด
- Railpack นิยามทุกขั้นตอนของการบิลด์จากผลการวิเคราะห์โค้ด และระบุความสัมพันธ์การพึ่งพาระหว่างแต่ละขั้นในระดับต่ำอย่างละเอียด
- จากนั้นจะแปลงแผนนี้เป็นกราฟ BuildKit LLB และทำการ resolve
- เมื่อมีการเปลี่ยนตัวแปรแวดล้อมหรือค่าอื่น จะเมานต์ไฟล์ด้วย hash ของค่านั้น หากโค้ดและตัวแปรไม่เปลี่ยนก็รับประกัน cache hit
- ผลลัพธ์คือ Railpack สามารถควบคุมวิธีสร้างอิมเมจได้อย่างสมบูรณ์
ความสามารถใหม่ที่เกิดขึ้นจากการใช้ Railpack
- รองรับการบิลด์/ดีพลอยเว็บไซต์แบบ static ของ Vite, Astro, CRA และ Angular แบบ ไม่ต้องตั้งค่า
- การผสานรวมอย่างใกล้ชิดกับกระบวนการบิลด์ ใน Railway UI
- รองรับ ภาษาเวอร์ชันล่าสุด ได้โดยไม่ต้องรอการออกรีลีสของ Railpack เอง
- มี การเพิ่มประสิทธิภาพการแคชข้ามสภาพแวดล้อม สำหรับแต่ละโปรเจกต์
- ปัจจุบันรองรับ Node, Python, Go, PHP, static HTML และกำลังขยายการรองรับเฟรมเวิร์กและภาษาอย่างต่อเนื่อง
โอเพนซอร์สและแผนในอนาคต
- Railpack เปิดให้ใช้งานในสถานะ Beta และสามารถใช้งานได้ทันทีเพียงเปิดใช้งาน
- มีเอกสารทางการ โค้ดจริง และช่องทางซัพพอร์ตสาธารณะให้ที่ railpack.com
- ในอนาคตมีแผนให้ความสำคัญกับการรองรับเชิงลึกสำหรับภาษายอดนิยมก่อน และจะขยายขอบเขตเพิ่มเติมหลังจากวาง core API และระดับ abstraction ให้มั่นคง
1 ความคิดเห็น
ความเห็นจาก Hacker News
ผมชอบ Nix มาก แต่ก็อยากให้เชื่อว่าผมไม่ได้ยึดติดทางอารมณ์กับการตัดสินใจไม่ใช้ Nix แต่อย่างใด อย่างไรก็ตาม ผมรู้สึกว่าคำบ่นบางข้อในบทความนี้ยังไม่ค่อยเข้าใจและควรอธิบายเพิ่ม เช่นที่บอกว่า “ปัญหาใหญ่ที่สุดของ Nix คือการจัดการเวอร์ชันแพ็กเกจแบบอิงคอมมิต” Nixpkgs เป็นทรัพยากรที่ยอดเยี่ยมก็จริง แต่ Nix กับ Nixpkgs ไม่ใช่สิ่งเดียวกัน ถ้าจะดึงเวอร์ชันอะไรก็ได้ของ toolchain มาใช้ Nixpkgs อาจไม่เหมาะมาก แต่ใน Nix ยังมีวิธีอื่นอีก ตัวอย่างเช่น มีเครื่องมือ Nix สำหรับดึง Rust เวอร์ชันใดก็ได้ที่ทำมาได้ดีมาก อีกเรื่องคือคำพูดที่ว่า “ไม่สามารถแยก dependency ของ Nix ออกเป็นเลเยอร์แยกได้” ซึ่งผมคิดว่าไม่สมเหตุสมผลเลย เพราะจริง ๆ จะแยกแบบไหนก็ได้ตามต้องการ เครื่องมือ Docker ของ Nixpkgs ก็รองรับเรื่องนี้ด้วย ส่วนการย้ายโค้ดเบสจาก Rust ไป Go ไม่ได้เกี่ยวกับ Nix โดยตรง แต่ก็น่าสนใจ ปกติการเปลี่ยนภาษาไม่ใช่สิ่งที่ตัดสินใจกันง่าย ๆ มักเกิดขึ้นตอนตั้งใจจะสร้างใหม่ตั้งแต่แรก ผมสงสัยว่า Railpacks กับ Nixpacks อาจเป็นงานของคนละกลุ่มกันด้วย ผมเคยเห็นเหมือนกันว่าเกิดอะไรขึ้นเมื่อคนที่ไม่ค่อยรู้จัก Nix ต้องมารับมือโซลูชัน Nix ที่ยังทำไม่เสร็จในองค์กร ภาพที่ออกมาไม่ค่อยดีนัก และคนส่วนใหญ่ก็ไม่อยากเรียนรู้ Nix ด้วย เลยเป็นเหตุผลว่าทำไมที่ทำงานเดิมของผมถึงแทบไม่ใช้ Nix เพื่อหลีกเลี่ยงสถานการณ์แบบนี้
ผมเองก็ชอบใช้ Nix แต่ทุกครั้งที่พูดคุยถึงปัญหาการใช้งานพื้นฐานของ Nix ก็มักได้รับคำตอบแค่ว่า “มีวิธีเลี่ยงนะ” (แต่ต้องเพิ่มโค้ดอีกหลายสิบถึงหลายร้อยบรรทัดด้วยเอกสารที่ไม่ดี ภาษาแปลก ๆ ข้อความ error ที่แย่ และความรู้กระจัดกระจายที่มีแต่คนเคยใช้เท่านั้นที่รู้) จนเริ่มเหนื่อยใจ ปัญหาส่วนใหญ่ของ Nix ไม่ได้มาจากความเป็น Turing-complete แต่มาจากการที่มันไม่มีความสามารถพื้นฐานแบบ API ที่ใช้ง่ายและเป็นธรรมชาติให้มา ถ้าการใช้ Nix ในทุกโปรเจกต์ค่อย ๆ กลายเป็นการหมกมุ่นอยู่กับการแก้ปัญหาของ Nix เอง ก็ไม่มีเหตุผลมากนักที่จะต้องใช้มัน ทั้งที่มีเครื่องมือกระแสหลักที่มีเอกสารครบถ้วนกว่าอยู่แล้ว ในทางปฏิบัติผู้คนจึงมักเลือก Docker มากกว่า ผิดหวังมากที่ Nix ยังยึดกับความบริสุทธิ์เชิงอุดมคติมากกว่าจะลงมาแก้ปัญหาประสบการณ์นักพัฒนาแบบเป็นรูปธรรมภายในเวลาที่สมเหตุสมผล แน่นอนว่าทุกคนช่วยกันแบบสมัครใจ แต่ก็น่าเสียดายที่ความพยายามทางเทคนิคเหล่านี้กลับใช้งานจริงได้ไม่เต็มที่เพราะ UX ที่ออกแบบมาไม่ดี
ผมไม่ได้ใช้ Nix แต่รู้สึกว่าคำกล่าวที่ว่า “Nix ≠ Nixpkgs” นั้นดูห่างไกลจากความเป็นจริง สำหรับผู้ใช้ส่วนใหญ่ ถ้าทางเลือกอื่นต้องอาศัยการค้นคว้าและความพยายามเพิ่ม สุดท้าย Nixpkgs ก็คือ Nix นั่นเอง ส่วนเรื่อง “แยกเป็นเลเยอร์แยกได้” ก็สงสัยเหมือนกันว่านี่มันใช้งานง่าย ตรงไปตรงมา และเป็นพฤติกรรมปริยายจริงหรือไม่
ประเด็นสำคัญคือผู้ใช้ของ Railway เป็นนักพัฒนาที่อยากระบุเวอร์ชันของแพ็กเกจที่ตัวเองต้องการ ภายใต้โครงสร้างของ Nix และ Nixpkgs การตรึงเวอร์ชันแพ็กเกจตัวหนึ่งหมายถึงการตรึงคอมมิตของต้นไม้ nixpkgs ทั้งก้อน และเพราะการ build ของแพ็กเกจพวก node/python/ruby มักพึ่งพาของนอกต้นไม้ด้วย จึงต้องมีการแมปเวอร์ชันกับคอมมิตไว้ abstraction นี้ไม่สมบูรณ์นัก ทำให้ผู้ใช้อาจต้องจัดสภาพของต้นไม้ให้ตรงกัน แม้แค่จะพิมพ์ “yarn add package” ก็ตาม การใช้แค่ Nix โดยไม่ใช้ Nixpkgs อาจโอเคในกรณีใช้งานที่จำกัด แต่สำหรับแพลตฟอร์มอย่าง Railway มันเป็นตัวเลือกที่ลำบาก
ผมไม่ค่อยเข้าใจประเด็นถกเถียงเรื่องการจัดการเวอร์ชัน ผมเพิ่งเริ่มใช้ Nix แต่ก็มีแพ็กเกจจากคอมมิตที่ระบุไว้ชัดเจนอยู่แน่นอน
ผมคิดว่าเขาอธิบายได้ตรงประเด็นดี Nixpkgs กับ Nix ต่างกันก็จริง แต่ในความเป็นจริง Nixpkgs คือข้อได้เปรียบที่แท้จริง ตอนใช้ NixOS นี่เป็นครั้งแรกที่ผมได้ใช้ Linux kernel เวอร์ชันล่าสุดตั้งแต่วันปล่อย Debian Stable ก็ดีอยู่ แต่ให้ความรู้สึกเหมือนย้อนกลับไปหลายปีเสมอ อย่างไรก็ตาม ภาษา Nix มีจุดให้วิจารณ์เยอะ มันเป็นภาษาที่เก่าและถึงจะทำดีที่สุดแล้ว ผมก็ไม่คิดว่าจำเป็นต้องเปลี่ยน ส่วนระบบ build ของ Nix นั้นค่อนข้างโบราณจนรู้สึกว่ามีการ rebuild ที่ไม่จำเป็นเยอะ ตัวอย่างเช่น ถ้าเปลี่ยนแค่ command line ที่ส่งให้ kernel ใน NixOS install ISO (เช่น ความเร็วพอร์ตคอนโซล) ก็อาจเกิดเหตุการณ์ประหลาดที่ build ใช้เวลาราว 3 นาที ฟังดูตลกดีแต่ก็ไม่ได้ทำให้ผมเลิกใช้ Nix แค่เป็นพฤติกรรมที่ผมจะไม่ยอมรับในระบบ build ของตัวเองแน่นอน ส่วนการใช้ Nix เพื่อสร้าง Docker image นั้นสำหรับผมถือว่าแย่มาก ครั้งหนึ่งผมแค่อยากใส่ไบนารี
pg_dumpของ Postgres ลงไปกับไบนารีที่เขียนด้วย Go แต่ทีม infra แนะนำให้ใช้ Nix สุดท้ายไบนารี Go ที่บีบอัดแล้วมีแค่ 50MB กลายเป็น image สัตว์ประหลาดขนาด 1.5GB ทั้งที่pg_dumpมีแค่ 464KB สุดท้ายผมใช้ Bazel กับ rules_debian และ distroless แล้วงานออกมาสะอาดกว่ามาก ระบบ Nix ส่วนใหญ่ให้ความรู้สึกว่า 1.4GB เป็นค่าปริยาย การ build โปรเจกต์ C++ ใหญ่ ๆ ก็ไม่ได้โดดเด่นเป็นพิเศษด้วย Nix ตรงกันข้าม ระบบสำหรับ build ซอฟต์แวร์ของตัวเองมักตอบโจทย์ความต้องการเฉพาะได้ดีกว่า ผมชอบ Bazel และสำหรับโปรเจกต์ Go ก็อยากใช้แค่go buildเฉย ๆ ใน 99% ของกรณีผมจะเลือกใช้เครื่องมือพวกนี้แทน Nix แต่ถ้าต้องการเรื่องการอัปเดตหรือ deployment ก็อาจเขียน flake ไว้ใช้กับ home-manager ได้การเลือกเวอร์ชันดูแปลก ๆ อยู่บ้าง เวอร์ชันของ nixpkgs นั้นชัดเจนว่ามีเหตุผลเมื่อใช้เพื่อรันระบบหรือ build ระบบ แต่ถ้าเป็นแพลตฟอร์มที่ให้ runtime/compiler ก็ควรให้เวอร์ชันโดยตรงแบบ devenv ตัวอย่างเช่น nixpkgs-python ให้ “Python ทุกเวอร์ชัน อัปเดตล่าสุดรายชั่วโมงด้วย Nix” ได้ เรื่องที่ Railway inject deployment ID environment variable ลงไปในทุก build ก็ทำในเลเยอร์หลังติดตั้งก็ได้เช่นกัน และแพ็กเกจก็แยกเป็นหลายเลเยอร์ได้ รวมถึงทำ automation ปรับจำนวนเลเยอร์ได้ด้วย
ในฐานะคนมีประสบการณ์ DevOps/SRE ผมเห็นบ่อยว่าเวลาใครพยายามสร้างระบบจัดการ dependency มักจะไหลไปทางใดทางหนึ่งจากสองแบบนี้ (ยกตัวอย่างจาก Python) ทางเลือก 1: “monorepo + สภาพแวดล้อมร่วม” ข้อดีคือจัดการง่าย แพตช์ความปลอดภัยสะดวก และรวมศูนย์ได้ ข้อเสียคือจะมีใครสักคนอยากได้เวอร์ชันพิเศษเสมอ rollout แบบค่อยเป็นค่อยไปทำได้ยาก และมีปัญหากับการ build image แบบบาง ทางเลือก 2: “แต่ละคนมี conda/venv ของตัวเอง” ข้อดีคือปรับแต่งเฉพาะรายได้ ตัดแพ็กเกจที่ไม่จำเป็นออกได้ และอัปเกรดแบบค่อยเป็นค่อยไปได้ ข้อเสียคือมีสภาพแวดล้อมมากเกินไป ไม่ได้ตรวจสอบความเข้ากันได้ข้ามกัน และการดูแลความปลอดภัยกลายเป็นฝันร้าย สุดท้ายยิ่งทำงานมานานยิ่งรู้สึกว่า “ไม่มีทางออก มีแต่ trade-off”
ผมคิดว่าคำพูดที่ว่า “ตัว Nix เองไม่ได้มีปัญหา แต่ปัญหาอยู่ที่วิธีนำไปใช้” เป็นตัวอย่างที่ดีของแนวคิดว่าให้ใช้เครื่องมือให้เหมาะกับงาน Nix ยอดเยี่ยมในบางที่ แต่แย่มากในบางที่ ปัญหาคือมันใช้เวลาเรียนรู้นาน พอเริ่มคุ้นและถึงเวลาตัดสินใจ เราก็มักเสียดายเวลาที่ลงทุนไปแล้วจนเปลี่ยนทิศทางได้ยาก สุดท้ายเลยฝืนใช้ Nix กับเป้าหมายเดิมต่อไป
shell.nixหรือconfiguration.nixตามสเปกได้ก็เพราะโครงสร้างแบบนี้ ผมเองก็มักทำ env แยกตาม repository ให้รวมทุกอย่างไว้ครบอยู่แล้ว และถ้าใช้ flakes ก็น่าจะได้สภาพแวดล้อมที่ทำซ้ำได้มากขึ้น (flake.nixคล้ายshell.nixแต่รองรับการตรึงเวอร์ชันด้วย…)มันดูเหมือนกำลังพยายามยัดแนวคิดเรื่องเวอร์ชันเข้าไปในที่ที่เดิมไม่มีเวอร์ชันอยู่แล้ว จะบอกว่า dependency พังเพราะ “เวอร์ชันปริยาย” เหรอ? ฟังดูเหมือนใช้แท็ก
:latestของ Docker แล้วปล่อยให้เซิร์ฟเวอร์พังทุกครั้งที่มีการเปลี่ยนแปลง ผมไม่ค่อยเข้าใจเนื้อหาบล็อกนี้ อีกทั้งก็ไม่เห็นด้วยกับประโยคที่ว่า “ไม่สามารถแยก dependency ของ Nix ออกเป็นเลเยอร์แยกได้” เพราะจริง ๆ แล้วสามารถแยก/nix/storeได้มากเท่าที่ต้องการ และดูเหมือนผู้เขียนจะไม่ค่อยเข้าใจว่าจะใช้ container กับ Nix อย่างไรด้วย ถ้าความสามารถยังไม่พอแบบนี้ ทางเลือกที่เสนอมาก็น่าจะวนกลับไปเจอปัญหาเดิมอยู่ดี นี่เป็นตัวอย่างคลาสสิกของอาการ NIH (ทำเครื่องมือเองทั้งที่ไม่จำเป็น)การไม่ใช้ Nix ในที่ที่มันไม่เหมาะเป็นเรื่องปกติ แต่ผมก็ยังรู้สึกแปลกที่เลือกจะสร้างใหม่ทั้งหมดตั้งแต่ต้นจนจบ ทั้งที่เป็นระบบที่ใช้งานได้อยู่แล้ว และปัญหาหลายอย่างคนอื่นก็แก้ไปแล้ว ถ้าลองค้นดูอีกนิดก็น่าจะรู้จัก
nix2containerหรือ flakes ซึ่งน่าจะแก้ปัญหาได้เกือบทั้งหมด เรื่องเวอร์ชันก็เช่นกัน flakes ที่เขียนไว้เมื่อ 3 ปีก่อนทุกวันนี้ก็ยัง build ได้เหมือนเดิมและผลลัพธ์ก็ไม่เปลี่ยน มันเลยให้กลิ่นเหมือนการเปลี่ยนแพลตฟอร์มเพื่อเร่งออกสู่ตลาดหรือดึงเงินลงทุนมากกว่า อนึ่งผมไปดู GitHub ของ nixpacks มาแล้ว เห็นว่าใช้แค่rustPlatformและถ้าปัญหาอยู่ที่ Rust จริง ๆ rust-overlay ก็น่าจะเป็นคำตอบแทบจะตรงตัวถ้าจะคิดว่าทำแบบไหนถึงจะดึง VC ได้ง่ายกว่า ชื่อว่า “แพลตฟอร์ม deployment” ก็ดูได้เปรียบกว่าเป็นแค่ wrapper ของ nix
ตรงข้ามกับคำกล่าวที่ว่า “ไม่สามารถแยก dependency ของ Nix ออกเป็นเลเยอร์แยกได้” nix2container ทำสิ่งนั้นได้ตรงตัว เช่น ถ้าต้องการ image ที่มี bash ก็สามารถสร้างเลเยอร์ที่มี bash แยกออกมาต่างหากได้ และเลเยอร์นี้จะถูก build/push ใหม่ก็ต่อเมื่อ bash เปลี่ยนเท่านั้น ส่วนประโยคที่ว่า “เพราะ dependency เลยเกิด image ขนาดใหญ่ในเลเยอร์
/nix/storeเดียว” ก็จริงกับฟังก์ชันnixpkgs.dockerTools.buildImageแต่ไม่จริงกับnix2containerหรือnixpkgs.dockerTools.streamLayeredImageเครื่องมือนี้จริง ๆ แล้วจะสร้างสคริปต์ขึ้นมาเพื่อใช้ push image ผ่านมันnix2containerจะสร้าง path ของทุกเลเยอร์เป็น JSON แล้วใช้ Skopeo เพื่อ push image ไปยัง Docker, registry, podman ฯลฯ (ขอออกตัวว่าผมเป็นคนเขียน nix2container)อยากบอกว่าขอบคุณ
nix2containerมากจริง ๆ ผมใช้มัน deploy ไป AWS (ECR) แล้วเวลาสลับระหว่าง build ลดลงเหลือแค่เลขหลักวินาทีเราเองก็กำลังจะทดสอบ
nix2containerเพราะปัญหาขนาด Docker image เช่นกัน ขอบคุณที่ทำเครื่องมือดี ๆ ขึ้นมาผมคิดว่าปัญหาหลักตรงนี้คือท่าทีที่ยังอยากยึดติดกับ “ซุปเวอร์ชันแบบปรับเอง” ที่ตัวจัดการแพ็กเกจของภาษาแต่ละภาษาส่งเสริมอยู่ (ซึ่งแนวทางนี้ไม่ยั่งยืน) ทางเลือกอย่าง Mise เองก็ไม่ได้เข้าใจข้อจำกัดด้านเวอร์ชันระหว่างแพ็กเกจ และไม่ได้ทดสอบแต่ละแพ็กเกจเลย ดังนั้นจะคาดหวังความน่าเชื่อถือในระดับเดียวกันไม่ได้แน่
จริงอยู่ที่ “ซุปเวอร์ชันแบบปรับเอง” ไม่ยั่งยืน แต่เหตุผลที่คนยังใช้มันต่อคือมันใช้งานได้จริง ไลบรารีระดับ OS มักถูกดูแลอย่างอนุรักษ์นิยมมากจนไม่ค่อยพัง และต่อให้เอาเครื่องมืออย่าง mise หรือ asdf มาประกอบเวอร์ชันแบบเฉพาะทางบนชั้นนั้น ส่วนใหญ่ก็มักยังทำงานได้ ถ้าพังก็มักแก้ได้ทันทีด้วยการปรับเวอร์ชันหรือคอนฟิก ถึงจะน่ารำคาญแต่ก็ไม่ใช่เรื่องคอขาดบาดตาย ระบบที่ต้องอาศัยการเรียนรู้หรือแรงพยายามเพิ่มจึงถูกมองว่าเสียเวลา อย่างไรก็ตาม คนที่ให้ความสำคัญกับสภาวะ “ไม่พัง” มากกว่ากลับมักชอบ Nix แม้จะมีเส้นโค้งการเรียนรู้และความไม่สะดวกอยู่ก็ตาม และสำหรับบริการที่ต้องรองรับผู้ใช้จำนวนมากอย่าง Railway สุดท้ายก็ต้องเลือกโดยคำนึงถึงกลุ่มแรกมากกว่า คือความง่ายและความคุ้นชิน
ผมสงสัยว่า “ซุปเวอร์ชันแบบปรับเอง” หมายถึงอะไร และทางเลือกคืออะไร
ทั้งสองอย่างทำได้ดีพอ ๆ กัน เช่น แพ็กเกจ Rust สามารถ build ด้วย Nix ได้ง่ายจากข้อมูลใน
Cargo.lockNixpkgs อาจขัดกับการผสมเวอร์ชันแบบกำหนดเอง แต่ตัว Nix เองทำเรื่องนี้ได้ดีพอNix ไม่ได้ให้การรับประกันแบบเวอร์ชันอิสระใดก็ได้ แต่มันรับประกันในระดับคอมมิต สำหรับเคสขอบอย่างการเปลี่ยนแปลง
glibcหรือการชนกันของ shared library ก็อาจทำให้ปวดหัวได้ ตอนนี้อาจสายไปแล้วก็ได้ แต่ถ้าต้องการคำปรึกษาเรื่องการใช้ Nix ให้สวยงามกว่านี้ ผมก็ช่วยได้ ส่วนตัวคิดว่าตัวผลิตภัณฑ์นั้นเจ๋งดีNix ป้องกันการชนกันของ shared library ได้แข็งแรงมาก แต่ในทางกลับกัน การเปลี่ยนแปลงเล็กน้อยมาก ๆ (เช่น คอมเมนต์หรือเอกสาร) ก็ทำให้ต้อง rebuild ทุกอย่างที่เกี่ยวข้องลงไปถึง dependency ชั้นล่างทั้งหมด ผลคืออาจเกิดการ rebuild ครั้งใหญ่และทำให้การพัฒนาเจ็บปวดได้ ลองดูขั้นตอน staging ของ nixpkgs จะเห็นภาพ
ผมเข้าใจคุณค่าของ Nix ดี เพียงแต่คิดว่าคำว่า “พังยับ” อาจจะเกินจริงไปหน่อย จริงอยู่ว่าจะเสียการรับประกันบางอย่างไปเมื่อเทียบกับ Nix แต่ก็น่าจะยังมีโอกาสทำงานได้ดีกว่าซอฟต์แวร์ส่วนใหญ่อยู่มาก
ผมไม่เข้าใจว่าทำไมถึงต้องยึดกับแฮชของ nixpkgs ทั้งที่สามารถสร้าง derivation ของตัวเองได้
น่าสนใจดีที่หลายคอมเมนต์มีบรรยากาศแบบ “จริง ๆ แล้ว Nix แก้ได้ทุกอย่างนะ แค่คุณต้องเป็นผู้เชี่ยวชาญแบบผม”
ถ้าบริษัทหนึ่งทำเทคโนโลยีและธุรกิจทุกอย่างด้วย JavaScript อยู่แล้ว แต่กลับไม่เข้าใจแนวคิดแกนหลักที่มีอยู่เดิมอย่างฟังก์ชันหรืออาร์เรย์ จนถึงขั้นทำ NIH (พัฒนาภาษาใหม่ตามสเปกตัวเอง) ขึ้นมา นั่นก็น่าจะเป็นปัญหาภายในมากกว่า
นี่เป็นบรรยากาศเดิม ๆ ที่วนซ้ำทุกครั้งที่มีการพูดถึง Nix
นี่แหละคือบรรยากาศที่ Nix มีอยู่เสมอ ทั้งเรื่องเล่าแบบ “ฉันจะไปกอบกู้โลก” และเวลาใครบอกว่า “ฟีเจอร์ที่ฉันต้องการมันทำไม่ได้” ก็มักจะได้รับคำตอบกลับมาว่า “เพราะคุณใช้มันไม่ถูกเอง”