FreeBSD en Firecracker
(usenix.org)- 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_COMPATpara 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 enbusdma - 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_NOTEySHT_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
hlten 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
- Si se llegaba a
- 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
- Desde PVH version 1, el puntero al mapa de memoria se entrega mediante la página
- 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_infoal final - Firecracker pone la página
start_infoen 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_infopodí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
- Xen carga primero el kernel y luego coloca la página
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
- No viene incluida por defecto en la configuración de kernel
- 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_RXRDYseguí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
- Firecracker los expone como dispositivos
- FreeBSD originalmente esperaba descubrir dispositivos
mmiomediante FDT, pero Firecracker entrega en la línea de comandos del kernel indicadores comovirtio_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
- Se añadió a FreeBSD código para parsear esos indicadores y crear nodos de dispositivo
- Cuando había varios dispositivos Virtio aparecía un problema al parsear la línea de comandos del kernel
- Firecracker pasa varios pares
key=valueal 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=yvirtio_mmio.device_1=
- Firecracker pasa varios pares
- Después de un apagado anormal, si en el siguiente arranque corría
fsck, ocurría un kernel panicfsckes 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
wiredy los procesos terminaban con frecuencia- La investigación mostró que
busdmaestaba 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
- La investigación mostró que
- 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
RDRANDcomo 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.uuidcon 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
- Normalmente el boot loader establece
- 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 terminaraprintfy a que se leyera la salida pasó a ser el sysctlkern.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
- El comportamiento de esperar 1 segundo tras el mensaje
- 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_initahorró 10 ms - Cambiar
ns8250_drainpara que no llamaraDELAYpor cada carácter, sino solo cuando hiciera falta después de revisarLSR_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, a32 * mp_ncpusahorró 5 ms en una VM de 1 CPU - Reemplazar el bubblesort de
mi_startuppor quicksort puede ahorrar 2 ms, pero al 22 de agosto de 2023 todavía no se había integrado - Cambiar
vm_memde inicializar de inmediato las estructurasvm_pagede 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_POPULATEalmmapde 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
- Reducir de 100000 a 1000 el loop de calibración de
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
FIRECRACKERy usar la ramafeature/pvhdel proyecto Firecracker- Si esa rama ya no existe, significa que el código fue fusionado al árbol principal de Firecracker
1 comentarios
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.
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.
Esa es precisamente la razón de ser de Firecracker.
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.
Además de Firecracker, varias microVM como crosvm, cloud-hypervisor y Dragonball de Kata se están desarrollando hoy sobre KVM.
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.
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
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...
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.
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?
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.
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.
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
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
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
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? ;)
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