- Durante Hackweek 22 de SUSE, se creó un POC de unikernel que ejecuta módulos WebAssembly, y el proceso de implementación se documenta en varias partes
- Portar una aplicación común directamente a un unikernel exige ajustar también sus dependencias, pero una plataforma WebAssembly tiene límites más claros sobre qué funcionalidades debe ofrecer el runtime
- Una aplicación Spiderlightning solo requiere capacidades como Key/Value, y aunque el host las implemente con Redis o Azure Cosmos DB, el mismo módulo
.wasmno necesita conocer la diferencia - La base es el unikernel en Rust RustyHermit; como Wasmtime y Wasmer no compilaban, se eligió wasmi, un runtime en Rust puro
- Para alinearse con Component Model y WIT, se agregó soporte para wasmi a wit-bindgen y luego se generó el esqueleto de la funcionalidad Key/Value del lado del host para ejecutar
keyvalue-demo
Proyecto de Hackweek y objetivo
- Durante Hackweek 22 de SUSE, se llevó a cabo el proyecto Construir un unikernel que ejecute WebAssembly
- El proceso completo de implementación era demasiado largo para incluirlo en un solo artículo, por lo que se dividió en varios; este es la primera parte
- El código del POC está publicado en otro lugar, pero el texto proporcionado no incluye la URL real del enlace
Por qué usar unikernels junto con WebAssembly
- Para los desarrolladores de aplicaciones, portar a un unikernel representa una carga importante
- La aplicación y todas sus dependencias deben soportar el unikernel de destino
- Puede ser necesario aplicar parches dentro de toda la pila de la aplicación
- Los mantenedores de unikernels también deben dedicar mucha energía a hacer que aplicaciones arbitrarias se ejecuten sin problemas
- Esto se debe a que es difícil predecir qué primitivas del sistema usará la aplicación del usuario
- En cambio, si se apunta a plataformas WebAssembly como Spin o Spiderlightning, el conjunto de capacidades que debe proporcionar el runtime queda más claro
- En un escenario con Spiderlightning, la aplicación puede solicitar al runtime una capacidad de almacenamiento Key/Value
- Para la aplicación es transparente si el host implementa esa capacidad con Redis o con Azure Cosmos DB
- El mismo módulo
.wasmpuede ejecutarse sobre distintas implementaciones de host
Arquitectura objetivo
- Si una aplicación de unikernel ejecuta módulos WebAssembly y soporta el conjunto de APIs de Spiderlightning, la misma aplicación Spiderlightning puede ejecutarse tanto en el runtime
slightnormal como en ese unikernel - El desarrollador de la aplicación no necesita hacer trabajo adicional, y el módulo Wasm tampoco necesita saber dónde se está ejecutando
- La complejidad se concentra en el desarrollador del unikernel, pero el alcance de implementación es mucho más claro que “soportar la ejecución de cualquier aplicación”
Implementación basada en RustyHermit
- Se eligió RustyHermit como base
- Es un unikernel escrito en Rust
- Está incluido en Rust nightly, por lo que ofrece una experiencia de desarrollo similar a escribir una aplicación Rust común
- Compilar aplicaciones para RustyHermit es relativamente directo
- La documentación está algo dispersa, pero es de buena calidad, y los ejemplos ayudan bastante
- No se puede esperar que todos los crates de Rust funcionen tal cual en RustyHermit, y esta limitación influyó en el desarrollo del POC
Elección del runtime WebAssembly
- Wasmtime, que era la opción preferida, no compila sobre RustyHermit
- Muchas dependencias esperan
libcu otras bibliotecas de bajo nivel
- Muchas dependencias esperan
- wasmer tiene el mismo problema
- También se consideró WebAssembly Micro Runtime, pero se decidió mantener una “experiencia RustyHermit completa” usando un runtime escrito en Rust
- Finalmente se eligió wasmi, un runtime WebAssembly en Rust puro
- Funciona bien sobre RustyHermit
- Su diseño está inspirado en Wasmtime, por lo que permitió reutilizar mucho conocimiento existente
WebAssembly Component Model y WIT
- Spiderlightning usa la propuesta WebAssembly Component Model
- Proporciona capacidades a los guests WebAssembly
- Permite que el host consuma capacidades expuestas por los guests WebAssembly
- La comunicación entre host y guest usa tipos definidos con Wasm Interface Type
- La demo usa Component Model con el siguiente flujo
- El guest solicita al host iniciar un servidor HTTP y le pasa la ruta HTTP que debe registrar y el nombre de la función interna del handler
- Usa el tipo
http-server, y el guest consume una capacidad proporcionada por el host
- Usa el tipo
- El host procesa las solicitudes HTTP entrantes usando la información de ruteo proporcionada por el guest
- El handler HTTP es una función expuesta por el guest WebAssembly
- El servidor consume una capacidad proporcionada por el guest y se comunica mediante el tipo
http-handler
- Algunos handlers HTTP interactúan con el almacenamiento Key/Value
- En este caso también el guest usa una capacidad proporcionada por el host, definida mediante el tipo
keyvalue
- En este caso también el guest usa una capacidad proporcionada por el host, definida mediante el tipo
- El guest solicita al host iniciar un servidor HTTP y le pasa la ruta HTTP que debe registrar y el nombre de la función interna del handler
Extensión de wit-bindgen y ejecución de la demo
- Para cada tipo WIT se necesita código tipo SDK del lado del guest y código de implementación del lado del host
- wit-bindgen es una herramienta CLI que genera código de host/guest a partir de archivos
.wit - En este POC solo hacía falta implementar la interfaz del lado del host dentro del unikernel
- El código generado por
wit-bindgenusa un runtime WebAssembly para realizar operaciones de bajo nivel- El código generado depende del lenguaje de programación y del runtime WebAssembly del lado del host
- Como
wasmino estaba soportado enwit-bindgen, se extendiówit-bindgenpara que pudiera manejar wasmi- El código está en el fork de la rama wasmi
- Luego se generó el esqueleto del código del lado del host para la capacidad Key/Value y se agregó una implementación simple del trait del host
- El código del host solo imprimía información de depuración
- En este estado fue posible ejecutar sin modificaciones el keyvalue-demo del proyecto Spiderlightning
Adelanto de la siguiente parte
- Hay una grabación de la aplicación de unikernel ejecutando la demo
http-serverde Spiderlightning - La siguiente parte tratará Rust async, Redis y algunos errores extraños
1 comentarios
Opiniones de Hacker News
¿No les viene de inmediato a la mente https://www.destroyallsoftware.com/talks/the-birth-and-death...?
Si alguien que no es hacker de sistemas operativos quiere un unikernel, ¿cuál sería el enfoque más completo?
Las opciones que se me ocurren son convertir la aplicación en un módulo del kernel de Linux y montarla sobre un kernel normal ignorando el espacio de usuario; recortar Linux de forma agresiva y pegarle el código propio; partir de un proyecto de unikernel en GitHub; o reducir otro sistema operativo como FreeBSD.
Me gusta la idea de que, en una VM conectada a una tarjeta de red, una máquina x64 actúe como un recurso de cómputo de propósito general, y que se le asignen trabajos enviándole datos por la red. Todavía no le he visto gran valor porque es más engorroso que usar un daemon en espacio de usuario, pero si algún día tengo tiempo, me da curiosidad saber por dónde convendría empezar con el hacking a nivel de sistema operativo.
Unikernel Linux (UKL) comenzó como un intento de aprovechar la configurabilidad de Linux, y apunta a ser un kernel que abarque desde sistemas operativos de propósito general hasta unikernels especializados para aplicaciones y hardware. También se mencionan áreas relacionadas como io_uring y eBPF: io_uring distribuye el costo de las llamadas al sistema, y eBPF es otra forma, aunque limitada, de ejecutar código en el espacio del kernel.
Código: https://github.com/unikernelLinux/ukl
UKL es un pequeño parche para Linux y glibc que permite compilar muchos programas como unikernels sin modificarlos. El programa se enlaza con el kernel de Linux y el vmlinuz final, se ejecuta en el espacio del kernel, puede arrancar en bare metal o en una VM, y puede usar casi todas las funciones y drivers de Linux.
/init, se empaqueta con el kernel y se arranca.Entonces la app pasa a ser PID 1 y, en la práctica, el único proceso; salvo algunos hilos del kernel, puedes hacer lo que quieras.
Soporta varios lenguajes y apps, x86/ARM64, QEMU/Firecracker, y también puede ejecutar como unikernel un ELF compilado en Linux: https://unikraft.org/guides/bincompat
El Discord está en https://unikraft.org/discord
Si quieres aprender OCaml y también quieres un unikernel, es una ruta posible.
Hay más detalles en la documentación de Unikraft: https://unikraft.org/docs/concepts/design-principles#approac...
Es un buen proyecto. Me gusta que WASM haya sido diseñado desde el principio pensando en sandboxing y portabilidad.
Ojalá WASM hubiera aparecido en los 90 en lugar de JavaScript, y creo que WASM se va a comer el mundo. Lo que más deseo es la persistencia. Hoy hay muchos programas que ya no se pueden ejecutar, y los juegos antiguos son el ejemplo típico. Una especificación simple tiene más posibilidades de sobrevivir mucho tiempo, así que agregar nuevas funciones me inquieta un poco, pero el futuro de los binarios se ve interesante.
Había una razón por la que JS al principio estaba limitado a scripting básico, como manejadores de clics o validación de formularios. Que se haya convertido en otra cosa no se debe solo a defectos de diseño de JS, sino también a los usos en los que fue encajado a la fuerza. Usar el navegador como mecanismo de entrega de este tipo de apps está bastante lejos de lo que Tim Berners-Lee o Marc Andreesen imaginaban.
En esa época, el bando de “la red es la computadora” proponía clientes X ligeros para apps más ricas: https://en.wikipedia.org/wiki/Network_Computer
Tengo sentimientos encontrados sobre WASM. Ahora mismo está cubierto por una gran capa de hype y novedad. Si se trata al navegador web solo como un viewport para la fantasía lingüística que prefieran en cada momento los diseñadores de UI y desarrolladores, pasan muchas cosas malas en áreas como accesibilidad y lectores de pantalla.
La tendencia de tratar WASM fuera del navegador como una VM universal también es un camino que ya se recorrió hace 30 años. Eso era lo que intentaba hacer la JVM, aunque ahora parece que ya no es “cool”.
No sé bien cómo debería verse el terreno intermedio, ni por qué deberíamos quererlo. Pero, siendo realistas, WASM terminará devorando incluso el contenido documental, y entonces los bloqueadores de anuncios y los modos de lectura probablemente estarán acabados.
Me gustó mucho. Había varias tecnologías enlazadas que no había visto, así que las marqué todas
Como siguiente paso, me gustaría probar configurar una conexión WireGuard en el hipervisor. El establecimiento de la conexión podría pasar por algo como Tailscale
Entonces el WebAssembly de esta máquina hablaría directamente con el WebAssembly de aquella otra. No sería una forma en la que un proceso abre una conexión TCP hacia una ubicación arbitraria, sino una estructura que se comunica con base en la configuración y los permisos transmitidos
Aunque llegue tarde, ¿alguien ha pensado en ejecutar Zephyr como unikernel? https://docs.zephyrproject.org/latest/boards/x86/acrn/doc/in...
¿Cuánto faltará para que aparezca hardware dedicado para WASM?
Pero sí parece posible que alguien construya algo del estilo “por fuera es wasm, pero en realidad debajo tiene RISC-V”
Pero creo que ese tipo de hardware siempre se quedará en un nicho. En general, ejecutar WASM sobre hardware común de mercado será más rápido. WASM en sí fue diseñado para correr rápido en hardware existente, y las economías de escala de los procesadores de propósito general son mucho mejores
La International Conference on Functional Programming al principio también era una conferencia llamada Functional Programming and Computer Architecture, pero después se encontraron formas de compilar eficientemente lenguajes funcionales de evaluación diferida como Haskell para hardware existente
Con las máquinas Lisp y Java pasa algo parecido. Una de las razones por las que ya casi no se ven esas cosas es que la tecnología de compiladores las alcanzó
¿Cuáles son los casos de uso de unikernels y WASM?
Creo que el valor de los unikernels está en 1) rendimiento: descartar lo innecesario y llevar lo necesario a “ring 0” para exprimir aunque sea unos ciclos más, 2) simplificación: la posibilidad de reducir la complejidad eliminando partes innecesarias, 3) seguridad: también la posibilidad de cambiar la superficie de ataque reduciendo lo innecesario
Dicho eso, no creo que sea un enfoque adecuado para escribir microservicios o apps web, que es lo que hace mucha gente en este foro. Su uso está más cerca de crear componentes de infraestructura como bases de datos y balanceadores de carga
Por eso algunos proveedores de edge cloud convierten las imágenes Docker en micro VM al ejecutarlas
Sin embargo, en el edge, WASM dentro de una micro VM puede tener dificultades para competir con WASM sandboxeado del edge. Desde el punto de vista del proveedor, es probable que esto último facilite agregar integraciones y funciones de frontera útiles
Como se “anticipaba” hace mucho en Birth & Death of Javascript, la idea era que algún día aparecería un unikernel que ejecutara un runtime con recolección de basura seguro en el espacio del kernel, y entonces se podría eliminar del CPU el soporte de mapeo de memoria virtual para hacerlo más rápido
En 2014, el autor pensaba en JS y asm.js, pero ahora WASM parece ser ese camino. Promete, jaja
https://www.destroyallsoftware.com/talks/the-birth-and-death...
Pero después aprendimos que los navegadores de un solo proceso son una pesadilla de seguridad, y los navegadores actuales ya no son de un solo proceso para poder tener un sandboxing adecuado
Aun así, es interesante ver lo cerca que estuvo ese video de la respuesta correcta, y también de qué manera se equivocó
Es difícil señalarlo con exactitud, pero algo de esto suena familiar. La pista es que también empieza con “J”
https://en.wikipedia.org/wiki/JavaStation
El uso virtual de un proceso puede superar su RSS aunque no sea por swapping, y el sistema operativo y el asignador trabajan juntos para manejarlo de forma bastante inteligente en los casos comunes
Por eso no es claro que eliminar esto genere automáticamente una ganancia de rendimiento. Más aún si se pasa por una capa de VM de WASM bastante lenta
En algunas aplicaciones, por ejemplo bases de datos, puede haber grandes beneficios al correr como unikernel o más cerca del kernel, con acceso directo a la MMU: https://github.com/tuhhosg/exmap & https://github.com/viktorleis/vmcache & https://www.cs.cit.tum.de/fileadmin/w00cfj/dis/_my_direct_up...
Pero para aplicaciones comunes que asumen el estándar POSIX o que dan por hecho que el entorno de ejecución se parece a una computadora moderna de propósito general, me genera dudas. Al final, parece que se terminaría reescribiendo en código de usuario mucho de lo que hacía la capa VMM
Los motores de JS dependen del VMM, y WASM también lo hace de varias maneras. Casi cualquier programa no trivial que no sea embebido presupone de forma sutil el VMM. En particular, algunas tecnologías de VM alrededor de las micro-VM también usan VMM, y los unikernels realmente tienen sentido cuando se usan como VM