จะสร้าง ISO ของ NixOS ให้เล็กลงกว่านี้ได้ไหม?
(natkr.com)- NixOS ทำให้สร้าง VM หรือ ISO ได้ง่ายจากการตั้งค่าเพียงอย่างเดียว แต่แม้แต่ live image ที่เกือบจะมินิมัลก็ยังถูกสร้างมาที่ขนาดเริ่มต้น 458MiB ซึ่งต่างจาก Alpine VM ISO ที่มีขนาดประมาณ 66MiB มาก
- ขนาดส่วนใหญ่กินอยู่ใน nix-store.squashfs ซึ่งภายในมี Python 3.13.13, Linux modules, systemd, Perl, GRUB, เอกสาร และ dependency ที่เกี่ยวกับ Nix
- หลังจากไล่ปิด
nix.enable = false,documentation.enable = falseและลบregister-nix-pathsออก ขนาด ISO ก็ลดจาก 458MiB → 384MiB → 360MiB และ dependency ของ Boost ก็หายไปด้วย - เมื่อตัด OpenSSH client, แพ็กเกจเริ่มต้น, เครื่องมือติดตั้ง GRUB, kernel modules สำหรับรันไทม์ และ activation path ที่อิง Perl ออก ขนาดสุดท้ายก็ลดลงเหลือ 183MiB
- เหมาะเป็นแนวทางอ้างอิงสำหรับอิมเมจบูตขนาดเล็กเพื่อการทดลอง แต่เพราะตัดฟังก์ชันที่จำเป็นออกไปมาก จึงยากจะนำไปใช้ตรง ๆ กับเดสก์ท็อปหรือสภาพแวดล้อมสำคัญ
การสร้าง ISO จากการตั้งค่า NixOS
- NixOS สามารถสร้าง VM จากการตั้งค่าได้อย่างง่ายดาย
nixos-rebuild build-vmจะสร้าง VM จากการตั้งค่าระบบปัจจุบัน- หากใช้
pkgs.nixosก็สามารถสร้าง VM จากการตั้งค่าใดก็ได้ แม้จะไม่ใช่การตั้งค่าของระบบก็ตาม
- ตัวอย่างพื้นฐานจะสร้าง VM แบบมินิมัลที่มีเพียง
system.stateVersion = "26.05"และservices.getty.autologinUser = "root" - VM นี้ทำงานแบบ thin VM
- ใน disk image จะมีเฉพาะไฟล์ที่สร้างขึ้นจากภายใน VM โดยตรง
- ส่วนที่เหลือ เช่น
/nix/storeจะถูก mount มาจาก host OS
- ถ้าโฮสต์ไม่มี Nix หรือจะไปรันบนโฮสต์ระยะไกลหรือไฮเปอร์ไวเซอร์ทั่วไป ก็จำเป็นต้องใช้ ISO แบบ self-contained
- สามารถ build ISO ได้โดย import โมดูล
iso-image.nixของ NixOSimage.baseName = lib.mkForce "nixos"ใช้กำหนดชื่อไฟล์ ISO ที่จะได้- ตัวอย่างการรันคือ
qemu-system-x86_64 --cdrom .../nixos.iso -m 1G --accel kvm - ถ้าไม่ใช่สภาพแวดล้อม Linux สมัยใหม่บน amd64 อาจต้องเปลี่ยนสถาปัตยกรรมหรือวิธีเร่งความเร็ว
จุดเริ่มต้น: ISO ขนาด 458MiB
- ผลลัพธ์ของการ build ISO แบบพื้นฐานมีขนาด 458MiB
- อิมเมจนี้ยังไม่มี
vimรวมอยู่ด้วย- หลังบูตแล้วถ้ารัน
vimจะขึ้นว่าcommand not found
- หลังบูตแล้วถ้ารัน
- Alpine VM ISO ที่ยกมาเทียบมีขนาดประมาณ 66MiB
- Damn Small Linux ถูกยกมาเป็นตัวอย่างของระบบที่เคยให้เดสก์ท็อปที่ใช้งานได้จริงในขนาดที่เล็กกว่ามาก
- เป้าหมายไม่ใช่การทำให้ถึงระดับ Damn Small Linux แต่เป็นการดูว่า ISO ของ NixOS จะลดขนาดลงได้อีกแค่ไหน
วิเคราะห์ขนาดภายใน ISO
- เมื่อลอง mount ISO แล้วดูด้วย
duขนาดจะแยกออกมาได้ดังนี้nix-store.squashfs: 416MiB- initrd: 26MiB
- kernel: 13MiB
- ISO ทั้งหมด: 458MiB
- ปัจจัยหลักที่กินพื้นที่คือ nix-store.squashfs ซึ่งเป็น user space หลัก
- เมื่อลอง mount squashfs จะเห็นพาธที่ดูเหมือน Nix store อยู่ภายใน
python3-3.13.13: 128MiBlinux-6.18.35-modules: 144MiBsystemd-260.1: 60MiBperl-5.42.0: 56MiBgrub-2.12: รวมหลายรายการประมาณ 62MiB- ยังมีเอกสารอย่าง
nix-manual-2.34.7,nixos-manual-htmlรวมอยู่ด้วย
- เนื่องจาก ISO ถูก build บนโฮสต์ จึงสามารถตามพาธ store ภายใน ISO กลับไปดูต่อใน
/nix/storeของโฮสต์ได้ - ใช้
nix why-dependsเพื่อตรวจสอบที่มาของ dependency- Boost ถูกดึงเข้ามาผ่านเส้นทางของ Nix daemon
- ไล่จาก
nix-daemon.conf,nix,libnixutil.soไปจนถึงboost-1.89.0
การถอด Nix และเอกสารออก
- ลองใช้
nix.enable = falseเพื่อนำ Nix เองออกจากอิมเมจ - และใช้
documentation.enable = falseเพื่อปิดเอกสาร - ผลลัพธ์แรกคือขนาดลดจาก 458MiB → 384MiB
- แต่ Boost ยังอยู่
register-nix-paths.serviceพยายามลงทะเบียนเนื้อหาใน store ของ ISO ตอนบูต- เส้นทางนี้จึงดึง Nix และ Boost กลับเข้ามาอีก
- จึงลบ service นี้โดยตั้ง
systemd.services.register-nix-paths = lib.mkForce {} - ผลคือ ISO ลดเหลือ 360MiB และ
nix why-dependsก็ยืนยันว่าไม่มี dependency ของ Boost แล้ว
การถอด OpenSSH และแพ็กเกจเริ่มต้น
- ใช้วิธีคล้ายกันเพื่อล้าง
environment.defaultPackagesได้เช่นกัน - การเอา
sshออกทำได้ยากกว่าmodules/programs/ssh.nixจะเพิ่ม OpenSSH เข้าไปในenvironment.corePackages- หาออปชันอย่าง
programs.ssh.enableสำหรับควบคุมสิ่งนี้ไม่พบ services.openssh.enableเป็นการตั้งค่าฝั่งเซิร์ฟเวอร์ ไม่ใช่ออปชันสำหรับเอาไคลเอนต์ออก
- แม้จะตัด
programs/ssh.nixออกด้วยdisabledModulesได้ แต่โมดูลอื่น ๆ คาดหวังว่าต้องมีออปชันprograms.sshอยู่ ทำให้เกิด error ต่อเนื่อง - วิธีแก้คือสร้าง stub option เป็นโมดูลแยกต่างหาก ซึ่งมีออปชัน
programs.sshแต่ไม่ได้นำไปใช้options.programs.ssh = lib.mkOption {};- แล้วค่อยใช้
disabledModules = [ "programs/ssh.nix" ];เพื่อตัดโมดูล SSH ตัวจริงออก
- ระหว่างทางยังใช้การตั้งค่าต่อไปนี้ร่วมด้วย
documentation.man.enable = falsenetworking.firewall.enable = falseenvironment.defaultPackages = lib.mkForce []
บันทึกเรื่องโครงสร้างโมดูลของ NixOS
- โมดูลของ NixOS โดยหลักมีอยู่ 3 ส่วน
- รายการระดับโมดูล:
imports,disabledModules - การนิยามออปชัน:
options.* - ส่วน implementation:
config.*
- รายการระดับโมดูล:
- โมดูลที่ไม่ได้กำหนดออปชันสามารถใช้รูปแบบย่อ โดยเขียนพร็อพเพอร์ตีของ implementation โดยไม่ต้องมีคำนำหน้า
config.ได้ - เพื่อให้การตั้งค่าส่วนที่เหลือยังคงใช้รูปแบบย่อได้ จึงแยก stub option ของ
programs.sshไปไว้อีกโมดูลหนึ่งที่ import เข้ามา
การถอดเครื่องมือติดตั้ง GRUB
- หนึ่งในรายการใหญ่ที่ยังเหลือคือไฟล์ที่เกี่ยวกับ GRUB ราว 62MiB
- ตัว bootloader เองยังจำเป็น แต่เห็นว่าไม่จำเป็นต้องรวมเครื่องมือติดตั้งทั้งหมดไว้ด้วย
- พรีเซ็ต ISO ของ NixOS จะ bundle ทั้ง GRUB เวอร์ชัน UEFI และ BIOS มาให้
- ไม่มีออปชันชัดเจนสำหรับปิด จึงใช้วิธีที่ค่อนข้างแรงด้วยการรีเซ็ตค่าต่อไปนี้
system.extraDependencies = lib.mkForce []environment.systemPackages = lib.mkForce config.environment.corePackages
- ไม่ได้ล้าง
environment.systemPackagesจนหมด- เพราะถ้าไม่มี
bashแล้ว getty อาจ crashloop ต่อเนื่อง จึงยังต้องคงcorePackagesไว้เพื่อให้เชลล์ยังพอใช้งานได้
- เพราะถ้าไม่มี
การถอด kernel modules
linux-6.18.35-modulesมีขนาด 144MiB คิดเป็นประมาณหนึ่งในสี่ของขนาดทั้งหมด- ใน NixOS ดูเหมือนไม่มี hook ที่ดีสำหรับจำกัด kernel modules ที่จะใช้ตอนรันไทม์
- จึงใช้วิธีลบโฟลเดอร์
kernel-modulesออกจาก system output แทนsystem.systemBuilderCommands = lib.mkAfter "rm $out/kernel-modules";
- วิธีนี้ทำให้ การโหลดโมดูลตอนรันไทม์แทบจะถูกปิดไปเลย
- โมดูลที่จำเป็นต้องใส่ไว้ใน
boot.initrd.kernelModulesหรือavailableKernelModules
- โมดูลที่จำเป็นต้องใส่ไว้ใน
- หลังเปลี่ยนแบบนี้ ยังบูตได้ แต่เสียความสามารถในการสลับไปใช้ความละเอียดหน้าจอที่สะดวกกว่า
- ขนาด ISO ลดลงเหลือ 197MiB
การถอด Perl และความสามารถทดแทนแบบทดลอง
- แม้จะลดมาเยอะแล้ว แต่ก็ยังมี Perl ขนาด 56MiB อยู่
- เมื่อตรวจด้วย
nix why-dependsพบว่า Perl ถูกใช้ตอน activate ระบบเพื่อจัดการผู้ใช้และ/etc - การตัดการจัดการผู้ใช้และ
/etcออกไปทั้งหมดทำไม่ได้ - จึงหันไปใช้ความสามารถเชิงทดลองเพื่อแทนที่เส้นทางเดิม
- การจัดการ
/etcใช้วิธี overlay - การจัดการผู้ใช้ใช้ userborn แบบเนทีฟ
- การจัดการ
- การตั้งค่าที่ใช้มีดังนี้
system.etc.overlay.enable = truesystem.etc.overlay.mutable = falseservices.userborn.enable = true
- ผลสุดท้าย ISO ลดลงเหลือ 183MiB
สถานะสุดท้ายและข้อจำกัด
- จากจุดเริ่มต้น 458MiB ลดลงมาเหลือ 183MiB ซึ่งเกือบหนึ่งในสามของต้นฉบับ
- ถึงอย่างนั้นก็ยังยากจะเรียกผลลัพธ์นี้ว่า “ดี”
- ไม่เหมาะกับเดสก์ท็อปที่ต้องใช้งานจริงหรือสภาพแวดล้อมสำคัญ
- ฟังก์ชันที่ถูกถอดออกทั้งหมดล้วนมีเหตุผลที่มันมีอยู่
- แต่ถ้าต้องการอิมเมจบูตเล็ก ๆ สำหรับการทดลองและทำงานเพียงไม่กี่อย่าง ก็ถือว่าใช้เป็นแนวทางอ้างอิงได้
- หากคัดลอกการตั้งค่าสุดท้ายไปใช้ตรง ๆ ก็อาจพบว่าฟังก์ชันที่จำเป็นต่อเป้าหมายของตัวเองหายไป
ช่องว่างสำหรับการลดขนาดต่อ
- งานรอบนี้เน้นไปที่สิ่งที่ “ลบออกได้เลย” หรือมีทางทดแทนค่อนข้างชัดเจน
- ยังมีส่วนที่ต้องทำงานลึกกว่านี้อีก
- ตอนนี้ทั้ง
systemdMinimalและsystemdยังถูก bundle มาด้วยกัน - ถ้าพยายามเอาออกอย่างใดอย่างหนึ่ง จะทำให้เส้นทางการ build อื่นพัง
- ตอนนี้ทั้ง
- ยังมีรายการเล็ก ๆ อีกหลายส่วนที่ถอดออกได้ และเมื่อรวมกันแล้วก็อาจลดขนาดได้อย่างมีนัยสำคัญ
- การ optimize เพิ่มเติมต้องอาศัยการตรวจสอบและทดลองอีกมาก
1 ความคิดเห็น
ความคิดเห็นจาก Lobste.rs
มีโมดูลที่ทำมาเพื่อจุดประสงค์นี้โดยตรง ต้องคอมไพล์พอสมควร แต่สามารถสร้าง initrd แบบสแตนด์อโลนเต็มรูปแบบที่รวม user space ทั้งหมดของ NixOS ได้ในขนาดราว 80MiB เมื่อบีบอัดด้วย zstd
งานนี้ไม่ได้จำกัดแค่ initrd แบบสแตนด์อโลนเท่านั้น แต่ยังใช้ลดขนาด NixOS แบบใดก็ได้ด้วย น่าจะเอาไปใช้กับ ISO ติดตั้งได้เหมือนกัน
https://github.com/wucke13/minimal-nixos/
ระบบพื้นฐานของ TinyCore Linux คือ Core ขนาด 17MB
ถ้าต้องการ X และ FLTK/FLWM ด้วย จะเป็น TinyCore ขนาด 23MB และถ้าต้องการ window manager กับแอปเพิ่มอีก จะมี CorePlus ขนาด 248MB
http://www.tinycorelinux.net/downloads.html
ขอแนะนำงานพูดใน NixCon เรื่องการย่อ NixOS ให้เล็กลงเพื่อเป็น ทางเลือกแทน Yocto: https://youtu.be/AsXY61laNb8
รายละเอียดไม่ลึกเท่าที่หวังไว้ แต่สิ่งที่ได้ยินจาก Óli กับ Matthew โดยตรงที่งานนั้นน่าทึ่งมาก เลยสงสัยว่ามีบทความสรุปไว้หรือเปล่า
เวลาจะทำ footprint ให้เล็กบน NixOS มักรู้สึกอึดอัดนิดหน่อยเสมอ
ขนาดฝั่ง SSH น่าจะลดได้ด้วยการตั้งค่าดังนี้
และยังสามารถ import
"${nixpkgs}/nixos/modules/profiles/minimal.nix"ได้ด้วย ซึ่งมี optimization บางส่วนแบบเดียวกับในบทความถึงอย่างนั้น ในกรณีส่วนใหญ่แนวทางนี้ก็น่าจะสมเหตุสมผลกว่า
"${nixpkgs}/nixos/modules/profiles/minimal.nix"เคยเห็นมาก่อนแล้วและรู้สึกว่าไม่ได้ช่วยมากอย่างที่คาด เลยไม่ได้คิดจะใส่มันตอนเริ่มสำรวจ พอนึกขึ้นได้อีกทีก็ทำไปครึ่งทางแล้ว ก็เลยรู้สึกว่าเอามาแทรกตอนต้นทั้งที่ควรอยู่ตั้งแต่แรกจะไม่ค่อยซื่อตรงนักช่วงนี้รู้สึกแปลกที่ Perl ถูกดึงเข้ามาในระบบสมัยใหม่บ่อยเกินไป แม้แต่ ISO ขนาดเล็กก็ยังมี Perl และถ้าจะคอมไพล์อะไรจริงจังตั้งแต่ต้นก็มักลงเอยที่ openssl -> Perl
ยังไม่ทันอ่านก็เดาไว้ก่อนเลยว่าน่าจะเป็นเพราะ สคริปต์ Perl งี่เง่าที่ไม่มีใครเขียนใหม่เป็น C
แก้ไข: ใช่จริงด้วย
ตั้งแต่ NixOS 26.05 เป็นต้นไป initrd เริ่มต้นจะใช้ systemd เพราะมี กรณีใช้งานของ initrd จำนวนมากที่ระบบปฏิบัติการสมัยใหม่ควรรองรับ
systemdMinimalคือไบนารี systemd ที่คอมไพล์ด้วยแฟลกและ dependency ที่น้อยกว่า จึงช่วยให้ initrd มีขนาดเล็กลงได้แต่ถ้าเป้าหมายคือ ISO แบบมินิมัล ก็ดูเหมือนว่าสามารถทำให้ทั้งสองอย่างพึ่งพาไบนารีตัวเดียวกันได้เหมือนกัน