1 puntos por GN⁺ 2023-10-24 | 1 comentarios | Compartir por WhatsApp
  • 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 .wasm no 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 .wasm puede 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 slight normal 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 libc u otras bibliotecas de bajo nivel
  • 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
    • 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

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-bindgen usa 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 wasmi no estaba soportado en wit-bindgen, se extendió wit-bindgen para que pudiera manejar 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-server de Spiderlightning
  • La siguiente parte tratará Rust async, Redis y algunos errores extraños

1 comentarios

 
GN⁺ 2023-10-24
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.

    • RedHat viene analizando Linux-as-unikernel desde 2018: https://research.redhat.com/blog/article/unikernel-linux-ukl...
      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.
    • Suponiendo un entorno de la familia Linux, primero se crea una aplicación compilada estáticamente, se coloca como único archivo en initramfs, se le pone simplemente el nombre /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.
    • También vale la pena revisar Unikraft: https://unikraft.org
      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
    • Para OCaml existe el framework MirageOS: https://mirage.io/
      Si quieres aprender OCaml y también quieres un unikernel, es una ruta posible.
    • Hay esencialmente tres formas de crear un unikernel: minimizar un sistema operativo de propósito general existente, eludir el sistema operativo o construirlo desde cero.
      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.

    • Aunque cueste creerlo, en los 90 los navegadores web se consideraban, en general, exploradores de documentos de hipertexto, no sustitutos de sistemas operativos.
      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”.
    • Ingenuamente, espero que la web se divida entre apps WASM en sandbox y contenido documental que ni siquiera necesite JS.
      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.
    • Creo que para que esto funcionara hacía falta JavaScript o algo parecido. De lo contrario, el ecosistema probablemente se habría infectado con algo como Java.
  • 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?

    • Estrictamente hablando, creo que nunca aparecerá. En un sentido muy limitado, porque WASM no está lo suficientemente especificado para eso
      Pero sí parece posible que alguien construya algo del estilo “por fuera es wasm, pero en realidad debajo tiene RISC-V”
    • Seguro que alguien lo hará. Existieron máquinas Lisp y también CPUs dedicadas a JVM
      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?

    • Sobre WASM no voy a hablar. Siento que si lo hago me voy a poner en modo “estos jóvenes de hoy...”
      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
    • Las micro VM pueden competir con los contenedores Linux en algunas cargas de trabajo, y tienen la ventaja de no exponer el kernel de Linux a código menos confiable
      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
    • Supongo que sirve para ampliar los lugares a los que puede llegar WASM. Después del navegador y los contenedores Docker, ahora incluiría incluso sistemas operativos ligeros que se pueden poner en dispositivos embebidos
  • 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...

    • La lógica del video era que, si el navegador de todos modos es un solo proceso y todo corre dentro de ese proceso, esa separación no hace falta
      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ó
    • La evolución de JavaScript/WASM va de un diseño para apps que corren en el navegador → escribir apps de escritorio y servidor → escribir sistemas operativos o kernels
      Es difícil señalarlo con exactitud, pero algo de esto suena familiar. La pista es que también empieza con “J”
    • JavaStation fue una computadora de red desarrollada por Sun Microsystems entre 1996 y 2000, pensada para ejecutar únicamente aplicaciones Java
      https://en.wikipedia.org/wiki/JavaStation
    • La memoria virtual y la paginación no sirven solo para protección, seguridad y aislamiento de procesos. También ofrecen un conjunto de abstracciones para el uso eficiente de la memoria física y la administración de memoria
      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
    • Eliminar el soporte de mapeo de memoria virtual tiene cada vez menos sentido cuanto más lo pienso
      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