FreeBSD no Firecracker
(usenix.org)- O trabalho de inicializar o kernel do FreeBSD 14 no Firecracker VMM, um microVM centrado em Linux para o AWS Lambda, é um caso em que um ambiente mínimo de virtualização revelou pressupostos ocultos e gargalos no caminho de inicialização do SO
- O primeiro boot foi bloqueado por diferenças no formato ELF Note do PVH boot mode, por hypercalls específicas do Xen e por diferenças no layout de memória do Firecracker, e foi resolvido com mudanças no código PVH do FreeBSD
- Como o Firecracker não fornece ACPI, o caminho para obter informações de CPU e interrupções mudou, e foi adicionada a opção
MPTABLE_LINUX_BUG_COMPATpara compatibilidade com um bug no tratamento de MPTable do Linux - No processo de conexão do console serial e dos dispositivos Virtio, vieram à tona o comportamento da UART, o parsing da linha de comando do kernel e a restrição de I/O de disco desalinhado; o FreeBSD aplicou
hw.broken_txfifo, um contorno para esvaziamento do FIFO e tratamento de bounce baseado embusdma - Incluindo patches ainda não commitados, em uma VM com 1 CPU e 128 MB de RAM o kernel do FreeBSD inicializa em menos de 20 ms; os itens restantes são integrar o suporte a PVH, separar o código do Xen, criar uma configuração de kernel menor e avaliar o porte do Firecracker para FreeBSD
Por que tentar rodar FreeBSD no Firecracker
- Firecracker é um VMM que cria e gerencia microVMs de baixo overhead sobre Linux KVM para ambientes de execução serverless como o AWS Lambda
- O trabalho de portabilidade para FreeBSD começou em junho de 2022
- A motivação era verificar conjuntamente os limites tanto do FreeBSD quanto do Firecracker
- Dando continuidade ao trabalho de melhoria da velocidade de boot do FreeBSD, queria-se ver até onde ele poderia ficar mais rápido em um hipervisor mínimo
- Portar o FreeBSD para uma nova plataforma revela bugs tanto no FreeBSD quanto na própria plataforma
- O AWS Lambda atualmente suporta apenas Linux e, independentemente da adoção ou não pelo Lambda, o suporte do FreeBSD ao Firecracker é um pré-requisito necessário
- O próprio Firecracker é uma plataforma interessante, então havia o desejo de verificar se ele funcionaria de fato
Primeiros obstáculos até executar o kernel
- O Firecracker foi originalmente projetado para executar kernels Linux, mas em 2020 houve um patch adicionando suporte a PVH boot mode além de
linuxboot - Como o FreeBSD já suportava boot PVH no Xen, tentou-se usar o mesmo caminho no Firecracker
- O primeiro problema foi que, depois de carregar o kernel do FreeBSD na memória, o Firecracker não encontrava o kernel entry point
- O protocolo de boot PVH armazena esse valor em uma ELF Note
- Existem
PT_NOTEeSHT_NOTEem ELF Note, e o FreeBSD não fornecia o formato que o Firecracker procurava - Com uma pequena alteração no script de linkedição do kernel do FreeBSD, o Firecracker passou a conseguir iniciar a execução do kernel do FreeBSD
- A execução do kernel voltou a parar cerca de 1 microssegundo depois
Depuração inicial e remoção de dependências do Xen
- Quando o kernel morria antes da inicialização do depurador do kernel e do console serial, os recursos de depuração do FreeBSD quase não ajudavam
- A única informação fornecida pelo processo do Firecracker era que o guest FreeBSD havia causado um triple-fault
- Foi feita uma “bisseção do kernel”, inserindo a instrução
hltno meio do código de inicialização do kernel para estreitar o ponto de falha- Ao chegar em
hlt, o Firecracker continuava em execução, mas o uso de CPU no host ficava em 0% - Se o Firecracker encerrasse, era possível concluir que a falha havia ocorrido antes daquele ponto
- Ao chegar em
- A primeira causa era uma Xen hypercall
- O ponto de entrada PVH do FreeBSD era, na prática, código para boot no Xen e assumia estar sendo executado dentro do Xen
- O KVM usado pelo Firecracker não fornece hypercalls do Xen, então a chamada fazia a VM travar
- Inicialmente, as hypercalls do Xen foram comentadas; depois, o código foi alterado para chamá-las somente após verificar a assinatura do Xen em
CPUID
- A consulta ao mapa de memória física era uma função essencial antes feita por hypercall do Xen
- A partir do PVH version 1, o ponteiro para o mapa de memória é passado pela página
start_info - O FreeBSD foi alterado para usar o mapa de memória do PVH version 1 em vez de uma hypercall do Xen
- A partir do PVH version 1, o ponteiro para o mapa de memória é passado pela página
- As diferenças de layout de memória entre Firecracker e Xen também causaram problemas
- O Xen primeiro carrega o kernel e depois posiciona a página
start_infono final - O Firecracker coloca a página
start_infoem um endereço baixo fixo e carrega o kernel em seguida - O código PVH do FreeBSD assumia que o espaço logo após
start_infoera scratch space e, no Firecracker, isso sobrescrevia a pilha inicial do kernel - A solução foi alocar o scratch space depois de todas as regiões de memória inicializadas pelo hipervisor
- O Xen primeiro carrega o kernel e depois posiciona a página
Ausência de ACPI e compatibilidade com MPTable
- No x86, o FreeBSD normalmente obtém informações de discos, adaptadores de rede, CPUs e controladores de interrupção via ACPI
- O Firecracker, de propósito, opta por uma implementação mínima e não fornece ACPI
- Em vez disso, o FreeBSD pode usar a estrutura MPTable da antiga Intel MultiProcessor Specification
- Ela não vem incluída por padrão na configuração de kernel
GENERIC - Em uma configuração de kernel leve para Firecracker, foi possível usá-la adicionando
device mptable
- Ela não vem incluída por padrão na configuração de kernel
- A MPTable fornecida pelo Firecracker não seguia o padrão, mas sim o formato aceito pelo Linux
- O Linux tinha bugs no modo de localizar e fazer parsing da MPTable
- Como o Firecracker foi projetado com o objetivo de inicializar Linux, ele fornece um posicionamento não padrão suportado pelo Linux
- O FreeBSD, implementado de forma independente conforme o padrão, não encontrava a MPTable mal posicionada e, mesmo quando a encontrava, não conseguia fazer parsing de uma MPTable inválida
- Foi adicionada ao FreeBSD a opção de kernel
options MPTABLE_LINUX_BUG_COMPAT- Ela é usada quando é necessária compatibilidade bug-for-bug com o tratamento de MPTable do Linux
- Com essa opção, o boot do FreeBSD no Firecracker avançou mais
Console serial e suporte a dispositivos Virtio
- Um dos poucos dispositivos emulados fornecidos pelo Firecracker é a porta serial
- Em uma configuração comum, a entrada e saída padrão do processo Firecracker se tornam a entrada e saída da porta serial da VM
- O kernel do FreeBSD inicializava com o disco raiz incluído na imagem do kernel, e já era possível ler até mesmo a saída do console do kernel
- Ao passar para o boot do espaço de usuário, a saída do console parava em 16 caracteres
- Era o mesmo sintoma de um antigo bug de UART do QEMU
- Quando o transmit FIFO da UART ficava vazio, não vinha interrupção, então o FreeBSD não conseguia escrever mais depois de 16 bytes
- A solução foi compilar no ambiente do kernel o contorno já existente no kernel do FreeBSD,
hw.broken_txfifo="1"
- A entrada do console também não funcionava
- O Firecracker concluía que o receive FIFO da UART emulada estava cheio e não lia o console
- Durante a inicialização da UART, o FreeBSD tentava medir o tamanho do FIFO preenchendo o receive FIFO com valores lixo e depois esvaziando-o pelo FIFO Control Register
- O Firecracker não implementa o FIFO Control Register, então o FIFO continuava cheio
- O FreeBSD foi alterado para, se
LSR_RXRDYainda permanecesse após o esvaziamento do FIFO, ler e descartar os caracteres um a um para esvaziá-lo
- Para usar disco e rede, eram necessários dispositivos Virtio block/network
- O Firecracker os expõe como dispositivos
mmio - A configuração de kernel FreeBSD para Firecracker recebeu
device virtio_mmio
- O Firecracker os expõe como dispositivos
- O FreeBSD originalmente esperava descobrir dispositivos
mmiovia FDT, mas o Firecracker passa na linha de comando do kernel diretivas comovirtio_mmio.device=4K@0x1001e000:5- O FreeBSD adicionou código para fazer parsing dessa diretiva e criar nós de dispositivo
virtio_mmio - Quando o nó de dispositivo é criado, o processo existente de probe de dispositivos do FreeBSD identifica o tipo de dispositivo Virtio e conecta o driver adequado
- O FreeBSD adicionou código para fazer parsing dessa diretiva e criar nós de dispositivo
- Quando havia vários dispositivos Virtio, surgiu um problema de parsing da linha de comando do kernel
- O Firecracker passa vários pares
key=valueao estilo do Linux - O FreeBSD faz parsing da linha de comando do kernel como variáveis de ambiente, então, se houvesse dois
virtio_mmio.device=com o mesmo nome, apenas um permanecia - O código inicial de parsing do ambiente do kernel foi alterado para preservar variáveis duplicadas adicionando um sufixo numérico, como
virtio_mmio.device=evirtio_mmio.device_1=
- O Firecracker passa vários pares
- Após um desligamento anormal, ocorria kernel panic quando o
fsckrodava no boot seguinte- O
fscké um dos raros casos no FreeBSD que gera I/O de disco não alinhado à página - A implementação Virtio do Firecracker aceita apenas um único buffer de dados e não suporta o modo comum do Virtio de dividir buffers que cruzam limites de página em vários segmentos
- O driver Virtio block do FreeBSD foi alterado para usar
busdma, e requisições desalinhadas foram ajustadas à limitação do Firecracker por meio de tratamento de bounce com buffer temporário
- O
Otimizações de boot reveladas pelo Firecracker
- Quando o FreeBSD começou a funcionar no Firecracker, os pontos para reduzir tempo de boot e uso de memória ficaram claros
- Em uma VM com 128 MB de RAM, quase metade da memória do sistema ficava em estado
wired, e processos eram encerrados com frequência- A investigação mostrou que
busdmaestava reservando 32 MB para bounce pages - Pela limitação do Firecracker, cada I/O de disco precisava no máximo de uma única bounce page de 4 KB
- Um patch que limita a reserva de bounce pages para dispositivos que suportam apenas um pequeno número de segmentos de I/O reduziu o uso de memória para 512 KB
- A investigação mostrou que
- A otimização do gerador de números aleatórios do kernel reduziu o tempo de boot
- Em VMs, entropia baseada em dispositivos de hardware pode não ser eficaz
- O
RDRANDdo x86 é usado como fonte de entropia reserva, mas a quantidade de entropia por requisição era pequena e só era solicitada uma vez a cada 100 ms - A mudança para solicitar o bastante para semear completamente o gerador de números aleatórios Fortuna reduziu 2,3 segundos
- O tratamento de Host ID também ficou mais rápido
- Normalmente, o boot loader define
smbios.system.uuidcom base em informações de BIOS ou UEFI - No Firecracker não há boot loader, então nenhum ID é fornecido
- O comportamento foi alterado para esperar 2 segundos após um aviso quando o hardware fornece um ID inválido, mas prosseguir de forma silenciosa e rápida quando não há ID algum
- Normalmente, o boot loader define
- A condição de espera por IPv6 DAD foi restringida
- O FreeBSD esperava pela Duplicate Address Detection quando IPv6 estava habilitado em uma interface de rede
- A interface loopback sempre tinha IPv6 habilitado
- A espera por DAD foi alterada para acontecer somente quando há IPv6 em interfaces que não sejam loopback, reduzindo 2 segundos
- Tempos de espera fixos nos processos de reboot e desligamento foram removidos
- No reboot, o comportamento de esperar 1 segundo após a mensagem
Rebooting...para completar oprintfe permitir leitura foi transformado no sysctlkern.reboot_wait_time, com valor padrão 0 - Também foi removido o comportamento de o BSP esperar mais 1 segundo depois de receber o sinal de parada de outras CPUs ao desligar ou reiniciar
- No reboot, o comportamento de esperar 1 segundo após a mensagem
- O TSLOG foi usado para analisar flame charts do boot
- O ambiente mínimo do Firecracker tinha pouco ruído, facilitando ver os gargalos restantes
- Como a execução da VM era muito rápida, muitas vezes era possível compilar um novo kernel, executá-lo e gerar o flame chart em menos de 30 segundos
- A análise com TSLOG reduziu vários gargalos na casa de milissegundos
- A redução do loop de calibração de 100000 iterações em
lapic_initpara 1000 reduziu 10 ms - A alteração em
ns8250_drain, que chamavaDELAYa cada caractere, para atrasar somente quando necessário após verificarLSR_RXRDY, reduziu 27 ms - Fazer o Firecracker implementar a CPUID leaf que informa as frequências do TSC e do clock local APIC reduziu 20 ms
- Mudar
kern.nswbuf, antes sempre fixo em 256, para32 * mp_ncpusreduziu 5 ms em uma VM de 1 CPU - Trocar o bubblesort em
mi_startuppor quicksort pode reduzir 2 ms, e em 22 de agosto de 2023 ainda não havia sido commitado - Alterar
vm_mem, que inicializava imediatamente as estruturasvm_pagede toda a memória física, para inicialização lazy pode reduzir 2 ms, e na mesma data ainda não havia sido commitado - Adicionar
MAP_POPULATEaommapda memória do guest no Firecracker pode reduzir 2 ms ao diminuir o custo de criação, pelo Linux, das estruturas de página no primeiro acesso à página, e na mesma data ainda não havia sido commitado
- A redução do loop de calibração de 100000 iterações em
Estado atual e trabalho restante
- O FreeBSD inicializa no Firecracker e funciona muito rápido
- Incluindo patches ainda não commitados do FreeBSD e do Firecracker, em uma VM com 1 CPU e 128 MB de RAM o kernel do FreeBSD consegue inicializar em menos de 20 ms
- O trabalho restante se concentra em organizar o suporte a PVH e reduzir a configuração do kernel
- É preciso commitar os patches mencionados acima
- É preciso integrar o suporte a PVH boot mode ao mainline do Firecracker
- O código de PVH booting está misturado ao suporte a Xen, então é necessário separá-lo
- O kernel arm64 do FreeBSD atualmente não pode ser compilado sem suporte a PCI ou ACPI; remover dependências incorretas pode tornar o kernel FreeBSD/Firecracker menor
- Como são gastos 25 µs verificando a necessidade de reserva de memória para GPUs Intel, uma configuração de kernel menor pode reduzir mais alguns microssegundos do tempo de boot
- No prazo mais longo, também há a possibilidade de portar o Firecracker para rodar no FreeBSD
- O Firecracker foi escrito pressupondo o uso de Linux KVM
- Acredita-se que não exista uma razão fundamental que impeça adaptá-lo para usar a parte de kernel do hipervisor bhyve do FreeBSD
- Para experimentar, é possível compilar o kernel do FreeBSD 14.0 com a configuração de kernel amd64
FIRECRACKERe usar o branchfeature/pvhdo projeto Firecracker- Se esse branch não existir mais, significa que o código foi integrado à árvore mainline do Firecracker
1 comentários
Opiniões no Hacker News
Eu não sabia bem que as VMs do Firecracker não eram uma simples tecnologia de contêineres Linux, mas sim máquinas virtuais completas.
À primeira vista isso pode soar ineficiente, mas, olhando para casos de uso reais como o da fly.io, é surpreendente como microVMs são muito pequenas e ainda assim suficientemente poderosas.
Graças ao KVM e ao suporte mínimo de hardware (sem PCI, ACPI etc.), o código-fonte do Firecracker é bem simples e relativamente legível até para quem não é especialista.
É exatamente por isso que o Firecracker existe.
O ACPI citado no texto é um exemplo. Ainda assim, é correto dizer que ele virtualiza hardware, não o kernel; e a inicialização é tão rápida que acho que a maioria dos usuários não perceberia diferença ao trocar o containerd comum pelo firecracker-containerd.
Além do Firecracker, várias microVMs como crosvm, cloud-hypervisor e o Dragonball do Kata estão sendo desenvolvidas hoje sobre o KVM.
Não tenho a impressão de que implementar um novo alvo para linguagens de programação seja tão difícil; então, se fosse criado um alvo semelhante a um sistema operacional, como WASI/WASM, e PRs fossem enviados às linguagens compatíveis, provavelmente seria possível eliminar a maior parte do overhead. A parte mais difícil seria emular o espaço de usuário Linux com precisão suficiente, mas, como a superfície é enorme, o caminho de criar um alvo semelhante a um sistema operacional parece até o melhor.
Quando o patch do Colin entrar no FreeBSD e no Firecracker, o tempo total de boot do kernel ficará abaixo de 20 ms.
Vivemos em uma era realmente difícil de acreditar.
Uma busca rápida mostra alguns números, mas são dados de alguns anos atrás, e não está claro se o método de medição do tempo de boot é o mesmo, nem se a definição de “tempo de boot” é igual; então não sei se dá para comparar.
É a apresentação recente do Colin na BSDCan, publicada alguns dias atrás.
https://youtu.be/MT3cdeuRTzs?si=l6baNriUjcvy0ZOE
Depois da apresentação na BSDCan, o FreeBSD Journal disse “foi uma boa palestra, você poderia transformá-la em artigo?”, e, depois que o artigo saiu no FreeBSD Journal, a ;login: perguntou se poderia republicá-lo.
O qemu tem uma microvm inspirada no firecracker.
https://qemu.readthedocs.io/en/latest/system/i386/microvm.ht...
Claro que algumas são correções de bugs do FreeBSD, então seriam inevitáveis.
É interessante ver que uma boa parte da espera de 1 segundo na verdade acaba não sendo estritamente necessária.
Fico pensando quantos administradores de sistema realmente tomaram alguma ação significativa quando o sistema parou por causa de um UUID de máquina incorreto.
Já o caso de “mostrar uma mensagem ao usuário dizendo que vai reiniciar, esperar 1 segundo para que ele consiga ler o console e então reiniciar” é um pouco diferente.
Não quero soar pedante, mas fico me perguntando para quais casos de uso servem coisas como instâncias do Firecracker.
Uso FreeBSD em tudo, de servidores em colocation a PCs pessoais, e sou mais um velho administrador Unix do que um desenvolvedor. Prefiro bare metal, mas recebo bem tecnologias futuras que contribuam para o sistema operacional. Ainda assim, ouço buzzwords como Lambda ou Firecracker e não sei bem onde isso é usado na prática. Entendo Docker e contêineres, e mal entendo k8s; mas, se posso simplesmente subir uma VM e usá-la quando necessário, por que eu precisaria subir uma VM e destruí-la logo em seguida? É puramente uma questão de experiência em nuvem ou redução de custos?
Isso permite que qualquer nó do plano de computação processe tráfego de qualquer aplicação. Uma aplicação pode crescer dinamicamente para consumir recursos computacionais ociosos do plano conforme os padrões de tráfego mudam e, quando não está atendendo tráfego, não usa recursos. Aumentar a capacidade do plano de computação significa colocar mais nós online. Fora gerenciar muitas implantações em grande escala, não consigo pensar em muitos outros casos de uso; em ambientes que não são de “escala”, isso provavelmente é uma tecnologia escondida abaixo da fronteira do fornecedor.
Se você opera um serviço cuja API não é usada com frequência, mas que precisa responder rapidamente quando chamada, Lambda ou algo semelhante se encaixa bem. Na prática, muitas APIs de apps de celular entram nessa categoria, e você não quer manter uma máquina ociosa 99% do tempo só para responder a essas chamadas de API.
A maioria simplesmente não faz isso porque o esforço supera a economia, mas o potencial existe. Coisas como Lambda e Firecracker tornam isso muito mais fácil.
É uma pena que nem a AWS nem o macOS em ARM ofereçam suporte a virtualização aninhada
Se oferecessem, desenvolver e implantar tecnologias baseadas no Firecracker teria sido muito mais fácil
Na verdade, a virtualização é possível em qualquer tipo de instância, mas, pelo que sei, só dá para usar aceleração de hardware nas instâncias .metal
O Firecracker é incrível, mas há muitos casos excepcionais que precisam ser documentados
Sou muito grato a Colin Percival por compartilhar isso. Gosto especialmente da frase “depois de colher todos os frutos mais baixos”; para Colin, isso se refere aos patches customizados de bus_dma. Agora qualquer pessoa pode aproveitar gratuitamente o fato de que “o kernel do FreeBSD inicializa em menos de 20 ms com 1 CPU e 128 MB de RAM”. Se você está acostumado a DevOps usando clusters k8s ou muitos Dockers, isso é realmente impressionante
Mexi um pouco no Firecracker e o tempo de boot sai como prometido, mas a experiência de uso é bem árdua
Por exemplo, depois de conseguir dar boot e comemorar, desanimei ao perceber que, para configurar a rede, ainda teria que seguir outro tutorial longo
Seria ótimo se desse para baixar e executar um único binário que levantasse tanto uma interface web quanto uma API, permitisse configurar tudo rapidamente e baixasse sozinho o que fosse necessário
“O kernel do FreeBSD pode inicializar em menos de 20 ms em uma máquina virtual com 1 CPU e 128 MB de RAM”
Caramba, como conseguiríamos a mesma coisa em hardware real, sem VM? ;)
O lento é todo o resto. Por exemplo, na minha máquina é
Startup finished in 14.552s (firmware) + 2.885s (loader) + 741ms (kernel) + 23.116s (initrd) + 11.191s (userspace) = 52.488s