การสร้าง Unikernel ที่รัน WebAssembly - ตอนที่ 1
(flavio.castelli.me)- ในงาน Hackweek 22 ของ SUSE ได้สร้าง POC ของ unikernel ที่รันโมดูล WebAssembly และสรุปขั้นตอนการพัฒนาออกเป็นหลายตอน
- หากต้องพอร์ตแอปพลิเคชันทั่วไปไปยัง unikernel โดยตรง ต้องจัดการให้ dependency ต่าง ๆ รองรับด้วย แต่ แพลตฟอร์ม WebAssembly มีขอบเขตความสามารถที่ runtime ต้องจัดเตรียมไว้อย่างชัดเจนกว่า
- แอปพลิเคชัน Spiderlightning ต้องการเพียงความสามารถอย่าง Key/Value และไม่ว่าฝั่งโฮสต์จะติดตั้งด้วย Redis หรือ Azure Cosmos DB โมดูล
.wasmเดียวกัน ก็ไม่จำเป็นต้องรับรู้ความแตกต่าง - พื้นฐานที่ใช้คือ Rust unikernel RustyHermit และเนื่องจาก Wasmtime กับ Wasmer คอมไพล์ไม่ได้ จึงเลือกใช้ wasmi ซึ่งเป็น runtime ที่เขียนด้วย Rust ล้วน
- เพื่อให้รองรับ Component Model และ WIT ได้ตรงกัน จึงเพิ่มการรองรับ wasmi ให้กับ wit-bindgen แล้วสแคฟโฟลด์ความสามารถ Key/Value ฝั่งโฮสต์เพื่อรัน
keyvalue-demo
โปรเจกต์ Hackweek และเป้าหมาย
- ระหว่างช่วง Hackweek 22 ของ SUSE ได้ทำโปรเจกต์ สร้าง unikernel ที่รัน WebAssembly
- เนื้อหาการพัฒนาทั้งหมดมีความยาวเกินกว่าจะใส่ไว้ในบทความเดียว จึงแบ่งออกเป็นหลายบท และบทความนี้คือภาคแรก
- โค้ด POC เปิดเผยไว้ในที่แยกต่างหาก แต่ในเนื้อหาที่ให้มายังไม่มี URL ลิงก์จริง
เหตุผลที่ใช้ Unikernel ร่วมกับ WebAssembly
- สำหรับนักพัฒนาแอปพลิเคชัน การ พอร์ตไปยัง unikernel เป็นภาระที่สูง
- ทั้งแอปพลิเคชันและ dependency ทั้งหมดต้องรองรับ unikernel เป้าหมาย
- อาจต้องมีการแพตช์ภายในสแตกแอปพลิเคชันทั้งหมด
- ฝั่งผู้ดูแลรักษา unikernel เองก็ต้องใช้พลังงานอย่างมากในการทำให้สามารถรันแอปพลิเคชันใด ๆ ได้อย่างราบรื่น
- เพราะคาดเดาได้ยากว่าแอปพลิเคชันของผู้ใช้จะเรียกใช้ system primitive แบบใดบ้าง
- ในทางกลับกัน หากกำหนดเป้าหมายเป็นแพลตฟอร์ม WebAssembly อย่าง Spin หรือ Spiderlightning ชุด ความสามารถ ที่ runtime ต้องมีจะชัดเจนขึ้น
- ในสถานการณ์ของ Spiderlightning แอปพลิเคชันอาจร้องขอความสามารถด้านที่เก็บข้อมูลแบบ Key/Value จาก runtime
- ไม่ว่าโฮสต์จะติดตั้งความสามารถนั้นด้วย Redis หรือ Azure Cosmos DB ก็โปร่งใสต่อแอปพลิเคชัน
- โมดูล
.wasmเดียวกันจึงสามารถรันบนโฮสต์คนละ implementation ได้
สถาปัตยกรรมเป้าหมาย
- หากแอปพลิเคชันบน unikernel สามารถรันโมดูล WebAssembly และรองรับชุด API ของ Spiderlightning ได้ ก็จะสามารถรันแอปพลิเคชัน Spiderlightning เดียวกันได้ทั้งบน runtime
slightปกติและบน unikernel ดังกล่าว - นักพัฒนาแอปพลิเคชันไม่จำเป็นต้องทำงานเพิ่ม และตัวโมดูล Wasm เองก็ไม่จำเป็นต้องรู้ว่ากำลังรันอยู่ที่ใด
- ความซับซ้อนจะไปรวมอยู่ที่นักพัฒนา unikernel แต่ขอบเขตที่ต้องพัฒนาก็ชัดเจนกว่าการ “รองรับการรันทุกแอปพลิเคชัน” มาก
การพัฒนาบนพื้นฐาน RustyHermit
- เลือกใช้ RustyHermit เป็นพื้นฐาน
- เป็น unikernel ที่เขียนด้วย Rust
- รวมอยู่ใน Rust nightly จึงให้ประสบการณ์พัฒนาที่คล้ายกับการเขียนแอปพลิเคชัน Rust ทั่วไป
- การบิลด์แอปพลิเคชันบน RustyHermit ค่อนข้างตรงไปตรงมา
- แม้เอกสารจะกระจัดกระจายอยู่บ้าง แต่มีคุณภาพดี และตัวอย่างช่วยได้มาก
- อย่างไรก็ตาม ไม่อาจคาดหวังได้ว่า Rust crate ทุกตัวจะทำงานบน RustyHermit ได้โดยตรง และข้อจำกัดนี้ก็ส่งผลต่อการพัฒนา POC
การเลือก WebAssembly runtime
- Wasmtime ซึ่งเป็นตัวเลือกที่ชอบ เดิมทีไม่สามารถบิลด์บน RustyHermit ได้
- dependency จำนวนมากคาดหวัง
libcหรือไลบรารีระดับล่างอื่น ๆ
- dependency จำนวนมากคาดหวัง
- wasmer ก็มีปัญหาเดียวกัน
- แม้จะพิจารณา WebAssembly Micro Runtime ด้วย แต่สุดท้ายตัดสินใจใช้ runtime ที่เขียนด้วย Rust เพื่อรักษา “ประสบการณ์ RustyHermit แบบเต็มรูปแบบ”
- สุดท้ายจึงเลือก wasmi ซึ่งเป็น WebAssembly runtime ที่เขียนด้วย Rust ล้วน
- ทำงานบน RustyHermit ได้ดี
- การออกแบบได้รับแรงบันดาลใจจาก Wasmtime ทำให้สามารถนำความรู้เดิมกลับมาใช้ซ้ำได้มาก
WebAssembly Component Model และ WIT
- Spiderlightning ใช้ข้อเสนอ WebAssembly Component Model
- เพื่อมอบความสามารถให้กับ WebAssembly guest
- และเปิดให้โฮสต์ใช้ความสามารถที่ WebAssembly guest มีให้ได้
- การสื่อสารระหว่างโฮสต์กับ guest ใช้ชนิดข้อมูลที่กำหนดไว้ด้วย Wasm Interface Type
- เดโมนี้ใช้ Component Model ตามลำดับดังนี้
- guest ขอให้โฮสต์เริ่ม HTTP server พร้อมส่ง HTTP route ที่ต้องการลงทะเบียนและชื่อฟังก์ชัน handler ภายใน
- ใช้ชนิด
http-serverโดย guest ใช้ความสามารถที่โฮสต์มีให้
- ใช้ชนิด
- โฮสต์จัดการคำขอ HTTP ที่เข้ามาตามข้อมูล routing ที่ guest ให้ไว้
- HTTP handler เป็นฟังก์ชันที่ WebAssembly guest เปิดเผยออกมา
- เซิร์ฟเวอร์ใช้ความสามารถที่ guest มีให้ และสื่อสารผ่านชนิด
http-handler
- HTTP handler บางตัวมีการโต้ตอบกับที่เก็บข้อมูลแบบ Key/Value
- ในกรณีนี้ guest ก็ใช้ความสามารถที่โฮสต์มีให้เช่นกัน และนิยามด้วยชนิด
keyvalue
- ในกรณีนี้ guest ก็ใช้ความสามารถที่โฮสต์มีให้เช่นกัน และนิยามด้วยชนิด
- guest ขอให้โฮสต์เริ่ม HTTP server พร้อมส่ง HTTP route ที่ต้องการลงทะเบียนและชื่อฟังก์ชัน handler ภายใน
การขยาย wit-bindgen และการรันเดโม
- สำหรับ WIT แต่ละชนิด จำเป็นต้องมีทั้งโค้ดฝั่ง guest ที่มีลักษณะเป็น SDK และโค้ด implementation ฝั่งโฮสต์
- wit-bindgen เป็นเครื่องมือ CLI ที่สร้างโค้ดฝั่งโฮสต์/guest จากไฟล์
.wit - ใน POC นี้ จำเป็นต้อง implement เฉพาะ อินเทอร์เฟซฝั่งโฮสต์ ภายใน unikernel เท่านั้น
- โค้ดที่
wit-bindgenสร้างจะใช้ WebAssembly runtime เพื่อทำงานระดับล่าง- โค้ดที่สร้างขึ้นจึงขึ้นอยู่กับทั้งภาษาโปรแกรมและ WebAssembly runtime ฝั่งโฮสต์
- เนื่องจาก
wasmiยังไม่ได้รับการรองรับในwit-bindgenจึงได้ขยายwit-bindgenให้จัดการ wasmi ได้- โค้ดอยู่ที่ fork บรันช์ wasmi
- จากนั้นจึงสแคฟโฟลด์โค้ดฝั่งโฮสต์สำหรับความสามารถ Key/Value และเพิ่ม implementation ของ host trait แบบง่าย ๆ
- โค้ดฝั่งโฮสต์ในตอนนั้นทำหน้าที่เพียงพิมพ์ข้อมูลดีบัก
- ในสถานะนี้ สามารถรัน keyvalue-demo ของโปรเจกต์ Spiderlightning ได้โดยไม่ต้องแก้ไข
เกริ่นถึงตอนถัดไป
- มีวิดีโอบันทึกการทำงานที่แอปพลิเคชันบน unikernel รันเดโม Spiderlightning
http-server - ในตอนถัดไป จะพูดถึง Rust async, Redis และข้อผิดพลาดแปลก ๆ บางอย่าง
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
นึกถึง https://www.destroyallsoftware.com/talks/the-birth-and-death... ขึ้นมาทันทีเลยไหม?
ถ้าคนที่ไม่ใช่แฮ็กเกอร์ระบบปฏิบัติการอยากได้ unikernel แนวทางที่สมบูรณ์ที่สุดคืออะไร?
ตัวเลือกที่นึกออกก็มี ทำแอปพลิเคชันให้เป็น Linux kernel module แล้วโหลดขึ้นเคอร์เนลปกติและไม่สนใจ user space, ลดทอน Linux ลงอย่างหนักแล้วต่อโค้ดของตัวเองเข้าไป, เริ่มจากโปรเจกต์ unikernel บน GitHub, หรือ削ระบบปฏิบัติการอื่นอย่าง FreeBSD ลง
ชอบภาพที่เครื่อง x64 ใน VM ที่เชื่อมกับการ์ดเครือข่ายทำงานเหมือนทรัพยากรคำนวณทั่วไป และส่งข้อมูลผ่านเครือข่ายเพื่อมอบหมายงาน ตอนนี้ยังไม่คุ้มเท่าไรเพราะยุ่งยากกว่า daemon ใน user space แต่ถ้าสักวันมีเวลา ก็สงสัยว่าควรเริ่มการแฮ็กระดับระบบปฏิบัติการจากตรงไหนดี
Unikernel Linux (UKL) เริ่มจากความพยายามที่จะใช้ประโยชน์จากความสามารถในการปรับแต่งของ Linux และตั้งเป้าเป็นเคอร์เนลที่ครอบคลุมตั้งแต่ระบบปฏิบัติการทั่วไปไปจนถึง unikernel ที่เจาะจงแอปพลิเคชัน/ฮาร์ดแวร์ มีการกล่าวถึง io_uring และ eBPF ในฐานะพื้นที่ที่เกี่ยวข้องด้วย โดย io_uring ช่วยกระจายต้นทุนของ system call ส่วน eBPF เป็นอีกวิธีหนึ่งในการรันโค้ดใน kernel space แม้จะมีข้อจำกัด
โค้ด: https://github.com/unikernelLinux/ukl
UKL เป็นแพตช์เล็ก ๆ สำหรับ Linux และ glibc ที่ช่วยให้สร้างโปรแกรมจำนวนมากเป็น unikernel ได้โดยไม่ต้องแก้ไข โปรแกรมจะถูกลิงก์กับ Linux kernel และ vmlinuz สุดท้าย แล้วรันใน kernel space สามารถบูตบน bare metal หรือ VM ได้ และใช้ฟีเจอร์กับไดรเวอร์แทบทั้งหมดของ Linux ได้
/initง่าย ๆ แล้วผูกเข้ากับเคอร์เนลเพื่อบูตแบบนั้นแอปจะเป็น PID 1 และโดยพฤตินัยเป็นโปรเซสเดียว ยกเว้น kernel thread ไม่กี่ตัว แล้วก็ทำได้ตามที่ต้องการ
รองรับหลายภาษาและแอป, x86/ARM64, QEMU/Firecracker และยังสามารถรัน ELF ที่ build บน Linux เป็น unikernel ได้ด้วย: https://unikraft.org/guides/bincompat
Discord อยู่ที่ https://unikraft.org/discord
ถ้าอยากเรียน OCaml และก็อยากได้ unikernel นี่เป็นเส้นทางที่เป็นไปได้
อ่านรายละเอียดได้ในเอกสารของ Unikraft: https://unikraft.org/docs/concepts/design-principles#approac...
เป็นโปรเจกต์ที่ดี WASM น่าสนใจเพราะถูกออกแบบมาตั้งแต่แรกโดยคำนึงถึง sandbox และ portability
ถ้า WASM ออกมาแทน JavaScript ในยุค 90 ก็คงดี และรู้สึกว่า WASM จะกลืนโลก สิ่งที่อยากได้ที่สุดคือ ความยั่งยืนระยะยาว ตอนนี้มีโปรแกรมจำนวนมากที่รันไม่ได้อีกแล้ว เกมเก่าเป็นตัวอย่างชัดเจน สเปกที่เรียบง่ายมีโอกาสอยู่รอดได้นานกว่า เลยกังวลเล็กน้อยกับการเพิ่มฟีเจอร์ใหม่ ๆ แต่อนาคตของไบนารีก็ดูน่าสนใจ
มีเหตุผลที่ตอนแรก JS ถูกจำกัดไว้แค่สคริปต์พื้นฐานอย่าง click handler หรือการตรวจสอบฟอร์ม การที่มันขยายเป็นอย่างอื่น ไม่ได้เกิดจากข้อบกพร่องในการออกแบบของ JS เท่านั้น แต่ยังเกิดจากกรณีใช้งานที่ถูกยัดเยียดเข้าไปด้วย การใช้เบราว์เซอร์เป็นกลไกส่งมอบแอปแบบนี้ห่างไกลจากสิ่งที่ Tim Berners-Lee หรือ Marc Andreesen จินตนาการไว้
ตอนนั้นฝ่าย “เครือข่ายคือคอมพิวเตอร์” นำเสนอ X client แบบบางเพื่อแอปที่สมบูรณ์กว่า: https://en.wikipedia.org/wiki/Network_Computer
สำหรับ WASM ความรู้สึกค่อนข้างซับซ้อน ตอนนี้มีม่านของกระแส hype และความแปลกใหม่ปกคลุมอยู่มาก ถ้าปฏิบัติต่อเว็บเบราว์เซอร์เป็นแค่ viewport สำหรับภาพฝันด้านภาษาที่ UI designer และ developer ชอบในแต่ละช่วง จะเกิดเรื่องแย่ ๆ มากมายในด้านอย่าง accessibility และ screen reader
กระแสที่มอง WASM นอกเบราว์เซอร์เป็น VM สากลก็เป็นเส้นทางที่เคยเดินมาแล้วเมื่อ 30 ปีก่อน นั่นคือสิ่งที่ JVM พยายามทำ แต่ตอนนี้ดูเหมือนไม่ “คูล” แล้ว
ไม่ค่อยแน่ใจว่าพื้นที่ตรงกลางควรมีหน้าตาอย่างไร หรือทำไมเราควรต้องการมัน แต่ในความเป็นจริง WASM ก็คงจะกลืนเนื้อหาเอกสารไปด้วย และถ้าเป็นแบบนั้น ad blocker กับ reading mode ก็มีโอกาสจบเห่
ชอบมากจริง ๆ ในบรรดาเทคโนโลยีที่ลิงก์ไว้มีหลายอย่างที่ไม่เคยเห็นมาก่อน เลยบุ๊กมาร์กไว้ทั้งหมด
ต่อไปอยากลองตั้งค่า การเชื่อมต่อ WireGuard ให้กับไฮเปอร์ไวเซอร์ การสร้างการเชื่อมต่ออาจผ่านอะไรอย่าง Tailscale ก็ได้
ถ้าทำแบบนั้น WebAssembly บนเครื่องนี้ก็จะคุยกับ WebAssembly บนเครื่องนั้นโดยตรง ไม่ใช่รูปแบบที่โปรเซสเปิดการเชื่อมต่อ TCP ไปยังตำแหน่งใดก็ได้ แต่เป็นโครงสร้างที่สื่อสารตามคอนฟิกและสิทธิ์ที่ถูกส่งต่อมา
ถึงจะช้าไปหน่อย แต่มีใครเคยคิดเรื่องรัน Zephyr เป็นยูนิเคอร์เนล ไหม? https://docs.zephyrproject.org/latest/boards/x86/acrn/doc/in...
อีกนานแค่ไหนกว่า ฮาร์ดแวร์เฉพาะสำหรับ WASM จะออกมา?
แต่ของประมาณว่า “ภายนอกเป็น wasm แต่จริง ๆ ข้างล่างเป็น RISC-V” น่าจะมีใครสักคนทำได้
แต่คิดว่าฮาร์ดแวร์แบบนั้นจะเป็นตลาดเฉพาะเสมอ เพราะโดยทั่วไปแล้วการรัน WASM บนฮาร์ดแวร์สำเร็จรูปทั่วไปน่าจะเร็วกว่า ตัว WASM เองถูกออกแบบมาให้รันได้เร็วบนฮาร์ดแวร์สำเร็จรูปอยู่แล้ว และเศรษฐกิจจากขนาดของโปรเซสเซอร์อเนกประสงค์ก็ดีกว่ามาก
International Conference on Functional Programming ตอนแรกก็เป็นงานประชุมชื่อ Functional Programming and Computer Architecture แต่ภายหลังก็ค้นพบวิธีคอมไพล์ภาษาเชิงฟังก์ชันแบบประเมินผลแบบขี้เกียจอย่าง Haskell ให้มีประสิทธิภาพบนฮาร์ดแวร์เดิม
เครื่อง Lisp และเครื่อง Java ก็คล้ายกัน เหตุผลหนึ่งที่ทุกวันนี้ไม่ค่อยเห็นของแบบนั้นแล้วคือ เทคโนโลยีคอมไพเลอร์ ไล่ทันแล้ว
กรณีใช้งานของ ยูนิเคอร์เนลกับ WASM คืออะไร?
ผมมองว่าคุณค่าของยูนิเคอร์เนลคือ 1) ประสิทธิภาพ: ตัดสิ่งที่ไม่จำเป็นออก และดึงสิ่งที่จำเป็นเข้ามาอยู่ใน “ring 0” เพื่อบีบเอาเพิ่มอีกแม้ไม่กี่ไซเคิล, 2) การทำให้เรียบง่าย: มีโอกาสลดความซับซ้อนด้วยการตัดส่วนที่ไม่จำเป็นออก, 3) ความปลอดภัย: เช่นกัน คือมีโอกาสเปลี่ยนพื้นผิวการโจมตีด้วยการลดสิ่งที่ไม่จำเป็น
แต่คิดว่าไม่ใช่แนวทางที่เหมาะกับการเขียนไมโครเซอร์วิสหรือเว็บแอปที่หลายคนในฟอรัมนี้ทำกัน การใช้งานน่าจะใกล้กับการสร้าง องค์ประกอบโครงสร้างพื้นฐาน อย่างฐานข้อมูลหรือโหลดบาลานเซอร์มากกว่า
ดังนั้นผู้ให้บริการเอดจ์คลาวด์บางรายจึงแปลง Docker image เป็นไมโคร VM เมื่อรัน
แต่ WASM ภายในไมโคร VM ที่เอดจ์อาจแข่งขันกับ WASM แบบแซนด์บ็อกซ์ของเอดจ์ได้ยาก จากมุมมองผู้ให้บริการ อย่างหลังน่าจะเพิ่มฟังก์ชันขอบเขตที่มีประโยชน์และการผสานรวมได้ง่ายกว่า
อย่างที่เคย “คาดการณ์ไว้” ใน Birth & Death of Javascript เมื่อนานมาแล้ว ภาพที่วาดไว้คือ สักวันหนึ่งจะมียูนิเคอร์เนลที่รัน รันไทม์แบบ garbage collection ที่ปลอดภัยใน kernel space และเมื่อเป็นเช่นนั้นก็จะสามารถตัดการรองรับการแมปหน่วยความจำเสมือนออกจาก CPU เพื่อให้เร็วขึ้นได้
ในปี 2014 ผู้เขียนคาดไว้ว่าเป็น JS กับ asm.js แต่ตอนนี้ WASM ดูเหมือนจะเป็นเส้นทางนั้น น่าตื่นเต้นดี ฮ่า ๆ
https://www.destroyallsoftware.com/talks/the-birth-and-death...
แต่หลังจากนั้นเราก็ได้เรียนรู้ว่าเบราว์เซอร์แบบโปรเซสเดียวเป็นฝันร้ายด้านความปลอดภัย และเบราว์เซอร์ยุคนี้ก็ไม่ใช่โปรเซสเดียวอีกต่อไป เพื่อให้ทำ sandboxing ได้อย่างถูกต้อง
ถึงอย่างนั้นก็น่าสนใจดีที่วิดีโอนั้นใกล้เคียงคำตอบที่ถูกต้องแค่ไหน และผิดไปในลักษณะใด
ชี้ให้ชัดคงยาก แต่รู้สึกคุ้น ๆ ตรงไหนสักแห่ง คำใบ้คือสิ่งนั้นก็ขึ้นต้นด้วย “J” เช่นกัน
https://en.wikipedia.org/wiki/JavaStation
ปริมาณการใช้งานเสมือนของโปรเซสอาจเกิน RSS ได้ แม้จะไม่ใช่เพราะ swapping ก็ตาม และระบบปฏิบัติการกับ allocator ก็ทำงานร่วมกันเพื่อจัดการกรณีทั่วไปได้ค่อนข้างฉลาด
ดังนั้นจึงยากจะมองว่าการตัดสิ่งนี้ออกจะให้ประสิทธิภาพดีขึ้นโดยอัตโนมัติ โดยเฉพาะถ้าต้องผ่านชั้น WASM VM ที่ค่อนข้างช้า
สำหรับบางแอปพลิเคชัน เช่น ฐานข้อมูล การรันเป็นยูนิเคอร์เนลหรืออยู่ใกล้เคอร์เนลมากขึ้นเพื่อเข้าถึง MMU โดยตรงอาจให้ประโยชน์มาก: https://github.com/tuhhosg/exmap & https://github.com/viktorleis/vmcache & https://www.cs.cit.tum.de/fileadmin/w00cfj/dis/_my_direct_up...
แต่สำหรับแอปพลิเคชันทั่วไปที่สมมติว่ามีมาตรฐาน POSIX หรือถือว่าสภาพแวดล้อมการรันดูเหมือนคอมพิวเตอร์ทั่วไปสมัยใหม่ ก็ยังน่าสงสัย สุดท้ายคงต้องเขียนงานจำนวนมากที่ชั้น VMM เคยทำกลับมาใหม่ในโค้ดผู้ใช้
เอนจิน JS พึ่งพา VMM และ WASM ก็เป็นเช่นนั้นในหลายรูปแบบ โปรแกรมที่ไม่ใช่ embedded แทบทุกตัวที่มีความซับซ้อนมากพอ ล้วนตั้งสมมติฐานเกี่ยวกับ VMM อย่างละเอียดอ่อน โดยเฉพาะเทคโนโลยี VM บางส่วนรอบ ๆ micro VM ก็ใช้ VMM ด้วย และยูนิเคอร์เนลจะมีความหมายจริง ๆ ก็ต่อเมื่อใช้เป็น VM เท่านั้น