3 puntos por GN⁺ 2023-08-26 | 1 comentarios | Compartir por WhatsApp
  • Hacer arrancar el kernel de FreeBSD 14 en Firecracker VMM, una microVM centrada en Linux para AWS Lambda, fue un caso que dejó al descubierto supuestos ocultos y cuellos de botella en la ruta de inicialización del SO dentro de un entorno de virtualización mínimo
  • El primer arranque quedó bloqueado por diferencias en el formato ELF Note del PVH boot mode, hypercalls exclusivos de Xen y diferencias en el diseño de memoria de Firecracker, y se resolvió con cambios en el código PVH de FreeBSD
  • Como Firecracker no ofrece ACPI, cambió la ruta para obtener información de CPU e interrupciones, y se añadió la opción MPTABLE_LINUX_BUG_COMPAT para alinearse con un bug del manejo de MPTable en Linux
  • Durante la conexión de la consola serial y de los dispositivos Virtio salieron a la luz el comportamiento del UART, el parseo de la línea de comandos del kernel y las restricciones de I/O de disco no alineado, y FreeBSD aplicó hw.broken_txfifo, una mitigación para vaciar el FIFO y un manejo de bounce basado en busdma
  • Incluyendo parches aún no integrados, el kernel de FreeBSD arranca en menos de 20 ms en una VM con 1 CPU y 128 MB de RAM; lo pendiente es integrar el soporte PVH, separar el código de Xen, reducir más la configuración del kernel y evaluar un port de Firecracker a FreeBSD

Por qué quisieron ejecutar FreeBSD en Firecracker

  • Firecracker es un VMM que crea y administra microVMs de bajo overhead sobre Linux KVM para entornos serverless como AWS Lambda
  • El trabajo de portarlo a FreeBSD comenzó en junio de 2022
  • La motivación era comprobar al mismo tiempo las limitaciones de FreeBSD y de Firecracker
    • Mientras continuaban los trabajos para mejorar la velocidad de arranque de FreeBSD, querían ver hasta dónde podía acelerarse sobre un hipervisor mínimo
    • Portar FreeBSD a una plataforma nueva deja al descubierto bugs tanto de FreeBSD como de la propia plataforma
    • AWS Lambda actualmente solo soporta Linux y, más allá de si se adopta o no Lambda, que FreeBSD soporte Firecracker es una condición previa necesaria
    • Firecracker en sí mismo era una plataforma interesante y querían comprobar cómo funcionaba en la práctica

Los primeros obstáculos hasta ejecutar el kernel

  • Firecracker fue diseñado originalmente para ejecutar kernels Linux, pero en 2020 recibió un parche para soportar PVH boot mode además de linuxboot
  • Como FreeBSD ya soportaba arranque PVH en Xen, intentaron reutilizar esa misma ruta en Firecracker
  • El primer problema fue que, después de cargar el kernel de FreeBSD en memoria, Firecracker no podía encontrar el kernel entry point
    • El protocolo de arranque PVH guarda ese valor en un ELF Note
    • En ELF Note existen PT_NOTE y SHT_NOTE, y FreeBSD no estaba entregando el formato que Firecracker esperaba
    • Con una pequeña modificación al linker script del kernel de FreeBSD, Firecracker pudo empezar a ejecutar el kernel
  • La ejecución del kernel volvió a detenerse alrededor de 1 microsegundo después

Depuración inicial y eliminación de dependencias de Xen

  • Si el kernel moría antes de que se inicializaran el depurador del kernel y la consola serial, las funciones de depuración de FreeBSD ayudaban muy poco
  • La única información que daba el proceso de Firecracker era que el guest de FreeBSD había producido un triple-fault
  • Insertaron la instrucción hlt en distintos puntos del código de arranque del kernel para acotar el lugar del fallo, en una especie de “kernel bisection”
    • Si se llegaba a hlt, Firecracker seguía corriendo pero el uso de CPU del host caía a 0%
    • Si Firecracker terminaba, se podía concluir que el fallo había ocurrido antes de ese punto
  • La primera causa fue un Xen hypercall
    • El entry point PVH de FreeBSD en realidad era código pensado para arrancar dentro de Xen y asumía que se ejecutaba en ese entorno
    • Como KVM, que es lo que usa Firecracker, no ofrece hypercalls de Xen, la VM fallaba al invocarlos
    • Al principio simplemente comentaron esos hypercalls y después lo cambiaron para llamarlos solo tras verificar la firma de Xen en CPUID
  • Consultar el mapa de memoria física era una función esencial que antes resolvía un hypercall de Xen
    • Desde PVH version 1, el puntero al mapa de memoria se entrega mediante la página start_info
    • FreeBSD se modificó para usar el mapa de memoria de PVH version 1 en vez del hypercall de Xen
  • Las diferencias entre el layout de memoria de Firecracker y Xen también causaron problemas
    • Xen carga primero el kernel y luego coloca la página start_info al final
    • Firecracker pone la página start_info en una dirección baja fija y después carga el kernel
    • El código PVH de FreeBSD asumía que el espacio inmediatamente posterior a start_info podía usarse como scratch space, y en Firecracker eso sobrescribía la pila inicial del kernel
    • Se resolvió asignando el scratch space después de todas las regiones de memoria inicializadas por el hipervisor

Ausencia de ACPI y compatibilidad con MPTable

  • En x86, FreeBSD normalmente obtiene por ACPI la información sobre discos, adaptadores de red, CPU y controladores de interrupciones
  • Firecracker, por diseño, adopta una implementación mínima y no ofrece ACPI
  • En su lugar, FreeBSD puede usar la estructura MPTable de la antigua Intel MultiProcessor Specification
    • No viene incluida por defecto en la configuración de kernel GENERIC
    • En una configuración liviana de kernel para Firecracker pudieron usarla añadiendo device mptable
  • El MPTable que entrega Firecracker no seguía el estándar, sino la forma que Linux acepta
    • Linux tenía bugs en la manera de encontrar y parsear MPTable
    • Firecracker fue diseñado con el objetivo de arrancar Linux y expone un layout no estándar que Linux soporta
    • FreeBSD, que tenía una implementación independiente y apegada al estándar, no podía encontrar ese MPTable mal ubicado y, aun cuando lo encontraba, no podía parsear un MPTable inválido
  • Se añadió a FreeBSD la opción de kernel options MPTABLE_LINUX_BUG_COMPAT
    • Sirve cuando se necesita compatibilidad bug-for-bug con el manejo de MPTable en Linux
    • Con esa opción, el arranque de FreeBSD en Firecracker pudo avanzar más

Soporte para consola serial y dispositivos Virtio

  • Uno de los pocos dispositivos emulados que ofrece Firecracker es el puerto serial
    • En la configuración típica, la entrada y salida estándar del proceso Firecracker pasan a ser la entrada y salida del puerto serial de la VM
  • El kernel de FreeBSD arrancó con el disco raíz integrado en la imagen del kernel, y ya se podía leer la salida de la consola del kernel
  • Al pasar al arranque del espacio de usuario, la salida de consola se detenía en 16 caracteres
    • Era el mismo síntoma que un bug antiguo del UART en QEMU
    • Como no llegaba una interrupción cuando el transmit FIFO del UART quedaba vacío, FreeBSD no podía seguir escribiendo después de 16 bytes
    • Se resolvió compilando como variable de entorno del kernel la mitigación existente hw.broken_txfifo="1"
  • La entrada de consola tampoco funcionaba
    • Firecracker asumía que el receive FIFO del UART emulado estaba lleno y por eso no leía la consola
    • Durante la inicialización del UART, FreeBSD llenaba el receive FIFO con basura para medir su tamaño y luego lo vaciaba usando el FIFO Control Register
    • Firecracker no implementaba el FIFO Control Register, por lo que el FIFO permanecía lleno
    • FreeBSD se modificó para que, si LSR_RXRDY seguía activo después de vaciar el FIFO, leyera y descartara caracteres uno por uno hasta limpiarlo
  • Para usar disco y red se necesitaban dispositivos Virtio block/network
    • Firecracker los expone como dispositivos mmio
    • La configuración de kernel FreeBSD para Firecracker añadió device virtio_mmio
  • FreeBSD originalmente esperaba descubrir dispositivos mmio mediante FDT, pero Firecracker entrega en la línea de comandos del kernel indicadores como virtio_mmio.device=4K@0x1001e000:5
    • Se añadió a FreeBSD código para parsear esos indicadores y crear nodos de dispositivo virtio_mmio
    • Una vez creados, el proceso normal de probe de dispositivos de FreeBSD determina el tipo de dispositivo Virtio y conecta el driver adecuado
  • Cuando había varios dispositivos Virtio aparecía un problema al parsear la línea de comandos del kernel
    • Firecracker pasa varios pares key=value al estilo Linux
    • FreeBSD parseaba la línea de comandos del kernel como variables de entorno, así que si había dos virtio_mmio.device= con el mismo nombre solo sobrevivía uno
    • El código de parseo temprano del entorno del kernel se modificó para conservar variables duplicadas agregando sufijos numéricos, como virtio_mmio.device= y virtio_mmio.device_1=
  • Después de un apagado anormal, si en el siguiente arranque corría fsck, ocurría un kernel panic
    • fsck es uno de los pocos casos en FreeBSD que genera I/O de disco no alineado a página
    • La implementación de Virtio en Firecracker solo acepta un único data buffer y no soporta la forma habitual de Virtio de dividir un buffer que cruza límites de página en varios segmentos
    • El driver Virtio block de FreeBSD se modificó para usar busdma, y las solicitudes no alineadas pasaron a manejarse con bounce mediante buffers temporales para adaptarse a esa limitación de Firecracker

Optimizaciones de arranque que Firecracker dejó en evidencia

  • Una vez que FreeBSD empezó a funcionar en Firecracker, quedó más claro dónde reducir tiempos de arranque y uso de memoria
  • En una VM con 128 MB de RAM, casi la mitad de la memoria del sistema estaba en estado wired y los procesos terminaban con frecuencia
    • La investigación mostró que busdma estaba reservando 32 MB para bounce pages
    • Dada la limitación de Firecracker, cada I/O de disco solo necesitaba una bounce page de 4 KB como máximo
    • Un parche para limitar la reserva de bounce pages en dispositivos que soportan pocos segmentos de I/O redujo el uso de memoria a 512 KB
  • Una optimización del generador de números aleatorios del kernel redujo el tiempo de arranque
    • En una VM, la entropía basada en dispositivos de hardware puede no ser efectiva
    • En x86 se usa RDRAND como fuente de entropía de respaldo, pero se pedía poca entropía por solicitud y solo una vez cada 100 ms
    • Cambiar eso para solicitar suficiente entropía como para sembrar por completo el generador Fortuna ahorró 2.3 segundos
  • El manejo del Host ID también se aceleró
    • Normalmente el boot loader establece smbios.system.uuid con base en información de BIOS o UEFI
    • Firecracker no tiene boot loader, así que no se entregaba ningún ID
    • Se cambió el comportamiento para que, si el hardware entrega un ID incorrecto, se muestre una advertencia y se espere 2 segundos, pero si no hay ID en absoluto, el sistema continúe rápido y en silencio
  • También se acotó la condición de espera de IPv6 DAD
    • FreeBSD esperaba Duplicate Address Detection siempre que IPv6 estuviera habilitado en una interfaz de red
    • La interfaz loopback siempre tenía IPv6 habilitado
    • Se cambió para esperar DAD solo cuando alguna interfaz distinta de loopback tenga IPv6, lo que ahorró 2 segundos
  • Se eliminaron tiempos de espera fijos durante reinicio y apagado
    • El comportamiento de esperar 1 segundo tras el mensaje Rebooting... para dar tiempo a que terminara printf y a que se leyera la salida pasó a ser el sysctl kern.reboot_wait_time, cuyo valor por defecto ahora es 0
    • También se eliminó la espera adicional de 1 segundo después de que el BSP recibiera la señal de detener a las otras CPU durante apagado o reinicio
  • Se analizó el flame chart de arranque usando TSLOG
    • El entorno mínimo de Firecracker tenía poco ruido y facilitaba ver los cuellos de botella restantes
    • Como la ejecución de la VM era muy rápida, a menudo se podía compilar un nuevo kernel, ejecutarlo y generar el flame chart en menos de 30 segundos
  • El análisis con TSLOG permitió recortar varios cuellos de botella de unos pocos milisegundos
    • Reducir de 100000 a 1000 el loop de calibración de lapic_init ahorró 10 ms
    • Cambiar ns8250_drain para que no llamara DELAY por cada carácter, sino solo cuando hiciera falta después de revisar LSR_RXRDY, ahorró 27 ms
    • Hacer que Firecracker implementara el CPUID leaf que informa la frecuencia del TSC y del reloj local APIC ahorró 20 ms
    • Cambiar kern.nswbuf, que siempre era 256, a 32 * mp_ncpus ahorró 5 ms en una VM de 1 CPU
    • Reemplazar el bubblesort de mi_startup por quicksort puede ahorrar 2 ms, pero al 22 de agosto de 2023 todavía no se había integrado
    • Cambiar vm_mem de inicializar de inmediato las estructuras vm_page de toda la memoria física a una inicialización diferida puede ahorrar 2 ms, y a esa misma fecha aún no se había integrado
    • Añadir MAP_POPULATE al mmap de la guest memory de Firecracker puede ahorrar 2 ms al reducir el costo de que Linux cree estructuras de página en el primer acceso, y al 22 de agosto de 2023 tampoco se había integrado

Estado actual y trabajo pendiente

  • FreeBSD arranca en Firecracker y lo hace muy rápido
  • Incluyendo parches no integrados de FreeBSD y de Firecracker, el kernel de FreeBSD puede arrancar en menos de 20 ms en una VM con 1 CPU y 128 MB de RAM
  • El trabajo pendiente se concentra en ordenar el soporte PVH y reducir la configuración del kernel
    • Hay que integrar los parches mencionados arriba
    • El soporte de PVH boot mode debe fusionarse en el mainline de Firecracker
    • El código de arranque PVH está mezclado con el soporte de Xen y hay que separarlo
    • El kernel arm64 de FreeBSD actualmente no puede compilarse sin soporte para PCI o ACPI, y eliminar esas dependencias incorrectas permitiría crear un kernel FreeBSD/Firecracker más pequeño
    • Se están usando 25 µs para comprobar si hace falta reservar memoria para GPU Intel, así que una configuración de kernel más pequeña podría recortar algunos microsegundos adicionales al arranque
  • A más largo plazo, también existe la posibilidad de portar Firecracker para que corra sobre FreeBSD
    • Firecracker fue escrito asumiendo el uso de Linux KVM
    • No parece haber una razón fundamental por la que no pudiera adaptarse para usar la parte de kernel del hipervisor bhyve de FreeBSD
  • Para experimentar, se puede compilar el kernel FreeBSD 14.0 para amd64 con la configuración FIRECRACKER y usar la rama feature/pvh del proyecto Firecracker
    • Si esa rama ya no existe, significa que el código fue fusionado al árbol principal de Firecracker

1 comentarios

 
GN⁺ 2023-08-26
Opiniones en Hacker News
  • No sabía bien que las VM de Firecracker no eran una simple tecnología de contenedores Linux, sino máquinas virtuales completas.
    Al principio puede sonar ineficiente, pero al ver casos de uso reales como fly.io, sorprende que las microVM sean tan pequeñas y aun así lo bastante potentes.

    • Si quieres saber más, conviene ver nuestro paper de NSDI'20 (https://www.usenix.org/conference/nsdi20/presentation/agache), que explica por qué elegimos este camino, y el código fuente/la documentación de Firecracker (https://github.com/firecracker-microvm/firecracker).
      Gracias a KVM y al soporte mínimo de hardware (sin PCI, ACPI, etc.), el código fuente de Firecracker es bastante simple y relativamente legible incluso para quienes no son especialistas.
    • Un proveedor cloud de nivel empresarial como AWS no va a permitir que contenedores de distintos clientes en ECS o Lambda convivan dentro de una sola VM.
      Esa es precisamente la razón de ser de Firecracker.
    • Firecracker no es una máquina “completa”, porque eliminó muchas cosas que Lambda y, casualmente, el caso de uso de fly.io no necesitan.
      ACPI, mencionado en el artículo, es un ejemplo. Aun así, es correcto decir que virtualiza hardware y no el kernel, y como inicializa tan rápido, creo que la mayoría de los usuarios no notarían la diferencia si cambiaran containerd normal por firecracker-containerd.
    • KVM es sorprendente.
      Además de Firecracker, varias microVM como crosvm, cloud-hypervisor y Dragonball de Kata se están desarrollando hoy sobre KVM.
    • Me sorprende que no sea estándar crear un microkernel que imite el espacio de usuario de Linux o de *NIX ajustándose al subconjunto de hardware virtual que ofrecen Firecracker y QEMU.
      No tengo la impresión de que implementar un nuevo target en un lenguaje de programación sea tan difícil, así que si se creara un target tipo sistema operativo similar a WASI/WASM y se enviaran PR a los lenguajes compatibles, parecería posible reducir la mayor parte del overhead. La parte más difícil sería imitar con suficiente precisión el espacio de usuario de Linux, pero como la superficie es tan amplia, el camino de crear un target de sistema operativo similar parece, de hecho, el mejor.
  • Si el parche de Colin entra en FreeBSD y Firecracker, el tiempo total de arranque del kernel quedará por debajo de 20 ms.
    Vivimos en una época realmente difícil de creer.

    • Me da curiosidad cómo se compara esto con Linux sobre Firecracker.
      Con una búsqueda rápida aparecen números, pero son datos de hace algunos años, y tampoco está claro si la forma de medir el tiempo de arranque es la misma o si la definición de “tiempo de arranque” coincide, así que no sé si son comparables.
  • Es la reciente presentación de Colin en BSDCan que se publicó hace unos días.
    https://youtu.be/MT3cdeuRTzs?si=l6baNriUjcvy0ZOE

    • Como referencia, esto es casi el mismo contenido.
      Después de la presentación en BSDCan, FreeBSD Journal le dijo “fue una buena charla, ¿podrías convertirla en artículo?”, y luego de que saliera el artículo en FreeBSD Journal, ;login: preguntó si podía republicarlo.
  • qemu tiene microvm, inspirada en firecracker.
    https://qemu.readthedocs.io/en/latest/system/i386/microvm.ht...

    • Me pregunto cuántos de estos rodeos hacen falta en QEMU.
      Claro que algunos son correcciones de bugs de FreeBSD, así que no queda otra que necesitarlos.
  • Es interesante que una parte importante de la espera de 1 segundo resulte no ser realmente necesaria.
    Me pregunto cuántos administradores de sistemas realmente tomaron alguna acción significativa cuando el sistema se detenía por un UUID de máquina incorrecto.

    • Probablemente una proporción bastante grande de los administradores de sistemas que se toparon con esa espera de 1 segundo hicieron algo.
      En cambio, lo de “mostrarle al usuario un mensaje diciendo que se va a reiniciar, esperar 1 segundo para que pueda leer la consola y luego reiniciar” es un poco distinto.
  • No quiero sonar pedante, pero me pregunto para qué casos de uso sirven cosas como las instancias de Firecracker.
    Uso FreeBSD en todo, desde servidores en colocación hasta mi PC personal, y soy más bien un viejo administrador Unix que un desarrollador. Prefiero bare metal, pero celebro las tecnologías futuras que contribuyen al sistema operativo. Aun así, aunque escucho buzzwords como Lambda o Firecracker, no tengo claro para qué se usan realmente. Entiendo Docker y los contenedores, y apenas entiendo k8s, pero no sé por qué habría que levantar una VM y luego eliminarla de inmediato si uno simplemente puede levantar una VM y usarla cuando la necesite. ¿Es puramente por experiencia cloud o para reducir costos?

    • Las instancias de aplicación se crean como parte del ciclo de vida de solicitud/respuesta.
      Esto permite que cualquier nodo del plano de cómputo pueda manejar tráfico de cualquier aplicación. Una aplicación puede crecer para consumir dinámicamente los recursos de cómputo libres del plano según cambien los patrones de tráfico, y no consume recursos cuando no está procesando tráfico. Aumentar la capacidad del plano de cómputo significa poner más nodos en línea. No se me ocurre un caso de uso claro fuera de administrar muchos despliegues a gran escala, y en entornos que no son de “escala”, será una tecnología escondida bajo el límite del proveedor.
    • El caso de uso principal son las API de uso ocasional.
      Si operas un servicio cuya API no se usa con frecuencia, pero que debe responder rápido cuando la llaman, Lambda o un enfoque similar encajan bien. De hecho, muchas API para apps móviles caen en esta categoría, y no quieres mantener una máquina ociosa el 99% del tiempo solo para responder a esas llamadas de API.
    • “Simplemente levantar una VM y usarla cuando se necesite. Siempre está encendida y siempre lista” significa que también siempre se cobra.
    • Casi todas las empresas pueden beneficiarse del escalado, porque el tráfico no es constante las 24 horas del día.
      La mayoría no lo hace porque el esfuerzo supera el ahorro, pero el potencial existe. Cosas como Lambda y Firecracker lo hacen mucho más fácil.
  • Es una lástima que ni AWS ni macOS en ARM admitan virtualización anidada
    Si la admitieran, habría sido mucho más fácil desarrollar y desplegar tecnología basada en Firecracker

    • Según entiendo, sí se puede virtualizar en las instancias .metal
      De hecho, la virtualización es posible en cualquier tipo de instancia, pero entiendo que solo en las instancias .metal se puede usar aceleración por hardware
    • Como referencia, la instancia a1.metal de AWS es bastante pequeña, así que resulta razonable en costo/beneficio para trabajar con tecnologías de virtualización
  • Firecracker es sorprendente, pero tiene muchos casos excepcionales que necesitan documentación
    Se agradece mucho que Colin Percival haya compartido esto. En especial me gusta la frase “después de recoger toda la fruta al alcance de la mano”; para Colin, eso se refiere a los parches personalizados de bus_dma. Ahora cualquiera puede disfrutar gratis de que “el kernel de FreeBSD arranque en menos de 20 ms con 1 CPU y 128 MB de RAM”. Si estás acostumbrado a DevOps con clústeres de k8s o mucho Docker, es algo realmente sorprendente

  • Estuve probando un poco Firecracker y, aunque el tiempo de arranque cumple lo prometido, la experiencia de uso es bastante difícil
    Por ejemplo, después de lograr que arrancara y celebrarlo, me desanimó darme cuenta de que para configurar la red había que seguir otro tutorial largo

    • Claramente hay espacio para aportar mucho valor creando herramientas de automatización para esto
      Sería genial poder descargar y ejecutar un único binario que levante tanto una interfaz web como una API, permita configurarlo rápido y descargue automáticamente lo necesario
  • “El kernel de FreeBSD puede arrancar en menos de 20 ms en una máquina virtual con 1 CPU y 128 MB de RAM”
    Vaya, ¿cómo se podría lograr lo mismo en hardware real, sin una VM? ;)

    • Incluso en hardware real, el arranque del kernel ya es suficientemente rápido y normalmente tarda menos de 1 segundo
      Lo lento es todo lo demás. Por ejemplo, en mi máquina es Startup finished in 14.552s (firmware) + 2.885s (loader) + 741ms (kernel) + 23.116s (initrd) + 11.191s (userspace) = 52.488s