1 คะแนน โดย GN⁺ 4 시간 전 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • 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 ของ NixOS
    • image.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: 128MiB
    • linux-6.18.35-modules: 144MiB
    • systemd-260.1: 60MiB
    • perl-5.42.0: 56MiB
    • grub-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 = false
    • networking.firewall.enable = false
    • environment.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 = true
    • system.etc.overlay.mutable = false
    • services.userborn.enable = true
  • ผลสุดท้าย ISO ลดลงเหลือ 183MiB

สถานะสุดท้ายและข้อจำกัด

  • จากจุดเริ่มต้น 458MiB ลดลงมาเหลือ 183MiB ซึ่งเกือบหนึ่งในสามของต้นฉบับ
  • ถึงอย่างนั้นก็ยังยากจะเรียกผลลัพธ์นี้ว่า “ดี”
  • ไม่เหมาะกับเดสก์ท็อปที่ต้องใช้งานจริงหรือสภาพแวดล้อมสำคัญ
    • ฟังก์ชันที่ถูกถอดออกทั้งหมดล้วนมีเหตุผลที่มันมีอยู่
  • แต่ถ้าต้องการอิมเมจบูตเล็ก ๆ สำหรับการทดลองและทำงานเพียงไม่กี่อย่าง ก็ถือว่าใช้เป็นแนวทางอ้างอิงได้
  • หากคัดลอกการตั้งค่าสุดท้ายไปใช้ตรง ๆ ก็อาจพบว่าฟังก์ชันที่จำเป็นต่อเป้าหมายของตัวเองหายไป

ช่องว่างสำหรับการลดขนาดต่อ

  • งานรอบนี้เน้นไปที่สิ่งที่ “ลบออกได้เลย” หรือมีทางทดแทนค่อนข้างชัดเจน
  • ยังมีส่วนที่ต้องทำงานลึกกว่านี้อีก
    • ตอนนี้ทั้ง systemdMinimal และ systemd ยังถูก bundle มาด้วยกัน
    • ถ้าพยายามเอาออกอย่างใดอย่างหนึ่ง จะทำให้เส้นทางการ build อื่นพัง
  • ยังมีรายการเล็ก ๆ อีกหลายส่วนที่ถอดออกได้ และเมื่อรวมกันแล้วก็อาจลดขนาดได้อย่างมีนัยสำคัญ
  • การ optimize เพิ่มเติมต้องอาศัยการตรวจสอบและทดลองอีกมาก

1 ความคิดเห็น

 
GN⁺ 4 시간 전
ความคิดเห็นจาก 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

    • ไม่แน่ใจว่านั่นเกี่ยวอะไรกับ การตั้งค่าแบบ declarative หรือ VM ที่ทำซ้ำได้
  • ขอแนะนำงานพูดใน NixCon เรื่องการย่อ NixOS ให้เล็กลงเพื่อเป็น ทางเลือกแทน Yocto: https://youtu.be/AsXY61laNb8
    รายละเอียดไม่ลึกเท่าที่หวังไว้ แต่สิ่งที่ได้ยินจาก Óli กับ Matthew โดยตรงที่งานนั้นน่าทึ่งมาก เลยสงสัยว่ามีบทความสรุปไว้หรือเปล่า

  • เวลาจะทำ footprint ให้เล็กบน NixOS มักรู้สึกอึดอัดนิดหน่อยเสมอ
    ขนาดฝั่ง SSH น่าจะลดได้ด้วยการตั้งค่าดังนี้

    programs.ssh.setXAuthLocation = false;  
    security.pam.services.su.forwardXAuth = lib.mkForce false;  
    fonts.fontconfig.enable = false;  
    

    และยังสามารถ import "${nixpkgs}/nixos/modules/profiles/minimal.nix" ได้ด้วย ซึ่งมี optimization บางส่วนแบบเดียวกับในบทความ

    • ในกรณีใช้งานที่ทำให้เขียนโพสต์ต้นฉบับขึ้นมานั้น จริง ๆ แล้ว แทบไม่จำเป็นต้องใช้ ssh เลย
      ถึงอย่างนั้น ในกรณีส่วนใหญ่แนวทางนี้ก็น่าจะสมเหตุสมผลกว่า
      "${nixpkgs}/nixos/modules/profiles/minimal.nix" เคยเห็นมาก่อนแล้วและรู้สึกว่าไม่ได้ช่วยมากอย่างที่คาด เลยไม่ได้คิดจะใส่มันตอนเริ่มสำรวจ พอนึกขึ้นได้อีกทีก็ทำไปครึ่งทางแล้ว ก็เลยรู้สึกว่าเอามาแทรกตอนต้นทั้งที่ควรอยู่ตั้งแต่แรกจะไม่ค่อยซื่อตรงนัก
  • ช่วงนี้รู้สึกแปลกที่ Perl ถูกดึงเข้ามาในระบบสมัยใหม่บ่อยเกินไป แม้แต่ ISO ขนาดเล็กก็ยังมี Perl และถ้าจะคอมไพล์อะไรจริงจังตั้งแต่ต้นก็มักลงเอยที่ openssl -> Perl

    • มีงานที่พยายามลดและจำกัด dependency ของ Perl ในระบบพื้นฐานให้เข้มงวดขึ้น: https://github.com/NixOS/nixpkgs/…
  • ยังไม่ทันอ่านก็เดาไว้ก่อนเลยว่าน่าจะเป็นเพราะ สคริปต์ Perl งี่เง่าที่ไม่มีใครเขียนใหม่เป็น C
    แก้ไข: ใช่จริงด้วย

  • ตั้งแต่ NixOS 26.05 เป็นต้นไป initrd เริ่มต้นจะใช้ systemd เพราะมี กรณีใช้งานของ initrd จำนวนมากที่ระบบปฏิบัติการสมัยใหม่ควรรองรับ
    systemdMinimal คือไบนารี systemd ที่คอมไพล์ด้วยแฟลกและ dependency ที่น้อยกว่า จึงช่วยให้ initrd มีขนาดเล็กลงได้
    แต่ถ้าเป้าหมายคือ ISO แบบมินิมัล ก็ดูเหมือนว่าสามารถทำให้ทั้งสองอย่างพึ่งพาไบนารีตัวเดียวกันได้เหมือนกัน