Edge Functions у Netlify переехали с V8 isolatesизолированных сред исполнения JavaScript, где несколько функций делят один процесс и рантайм на MicroVMлёгкую виртуальную машину с собственным ядром, запускаемую под управлением гипервизора Firecracker. Переезд дал примерно 5-кратное ускорение по медиане и улучшил безопасность и надёжность. Ниже — как устроена механика: почему Firecracker вообще годится для запуска одной функции на одну MicroVM, что именно происходит между приходом запроса и отправкой ответа, как снимается и восстанавливается снапшот и какие компромиссы за это платятся.
Почему одна функция — одна MicroVM
Каждая функция запускается в собственной Firecracker MicroVM. Такая VM создаётся менее чем за миллисекунду и стартует примерно за 2 мс на p99, потому что запускает урезанное Linux-окружение, а не полную ОС. Именно это сочетание и делает Firecracker пригодным: по своим свойствам он соединяет безопасность и изоляцию рабочей нагрузки традиционных VM со скоростью, гибкостью и эффективностью контейнеров.
Изоляция достигается многослойно. Первый слой — KVMподсистема ядра Linux, дающая аппаратную виртуализацию и граница виртуализации Firecracker. Для защиты в глубину процесс дополнительно ограничивается на уровне ОС: seccomp-фильтры запрещают нежелательные системные вызовы, cgroups и namespaces изолируют ресурсы, а привилегии сбрасываются через jailerвспомогательный процесс, который настраивает требующие повышенных прав ресурсы, сбрасывает привилегии и запускает Firecracker уже как непривилегированный процесс.
Эмуляция устройств сведена к минимуму. Гость получает хранилище и сеть через эмулированные VirtIO Net и VirtIO Block, плюс последовательную консоль и частичный контроллер клавиатуры — последний нужен, чтобы гость мог запросить перезагрузку VM (мягкую или жёсткую). Дополнительно гость видит контроллеры прерываний и таймер, которые поддерживает KVM.
Внутренняя архитектура процесса Firecracker
Каждый процесс Firecracker обслуживает ровно одну MicroVM и запускает потоки API, VMMпрограмма, которая эмулирует железо для гостевой ОС, и vCPU. Поток VMM предоставляет модель машиныописание того, из каких виртуальных компонентов состоит MicroVM — сколько у неё процессоров, сколько памяти и какие устройства к ней подключены, минимальную модель legacy-устройствустаревших устройств, которые нужны гостевой ОС только на раннем этапе загрузки, сервис метаданных MicroVM и эмулируемые VirtIO-устройства Net, Block и Vsock с ограничением скорости ввода-вывода. Потоки vCPU создаются через KVM и выполняют цикл KVM_RUN, исполняя синхронные и memory-mapped операции ввода-вывода к моделям устройств.
Вся эмуляция устройств в потоке VMM управляется одним epollмеханизм ядра Linux для ожидания событий сразу на множестве файловых дескрипторов-циклом событий, который заодно принимает запросы от потока API. Когда MicroVM ставят на паузу через API, поток VMM перестаёт опрашивать этот цикл и обслуживает только запросы API, пока VM не возобновят.
Блочные устройства Firecracker бэкендятся файлами на хосте. Чтобы гость мог их смонтировать, эти файлы должны быть предварительно отформатированы файловой системой, которую поддерживает ядро гостя.
Изоляция устроена так же многослойно, как описано выше, но с важной деталью про момент применения: seccomp-фильтры устанавливаются автоматически и загружаются на уровне каждого потока до исполнения любого гостевого кода. Jailer настраивает ресурсы, требующие повышенных прав (например, cgroup и chroot), сбрасывает привилегии и затем делает exec() в бинарник Firecracker, который дальше работает как непривилегированный процесс и может обращаться только к ресурсам, предоставленным привилегированной третьей стороной.
Путь одного запроса: от приёма до ответа
Запрос приходит на edge-узел, ближайший к клиенту. Узел завершает TLSпротокол шифрования транспортного соединения-соединение и сверяет путь запроса с маршрутами Edge Functions для этого деплоя. Если маршрут совпал, запрос пересылается на compute-узел внутри сети.
Перед отправкой edge-узел записывает спецификацию машины, которая будет исполнять функцию. Спецификация задаёт лимиты CPU, памяти и соединений. Хэш спецификации вместе с информацией о сайте вычисляется в service ID. Спецификация путешествует с запросом при каждом запросе.
Compute-узел, получив запрос со спецификацией и service ID, сначала проверяет, существует ли сервис с таким ID. Если да — пересылает запрос в сервис, чтобы тот отправил его в MicroVM. Если нет — создаёт сервис и проверяет, все ли образы из спецификации есть на диске; отсутствующие забирает с edge-узла и пишет на диск.
Затем edge-узел выбирает compute-узел для сервиса с помощью rendezvous hashingсхема выбора узла по хэшу, при которой один и тот же ключ всегда попадает на один и тот же узел. Один и тот же сервис каждый раз попадает на один и тот же узел — это и держит MicroVM «тёплой», а код уже на диске и в кэше после первого чтения. Равномерное распределение запросов по всему «рою» дало бы больше холодных стартов.
У такой привязки есть обратная сторона: она же формирует горячую точку, где одна занятая функция конкурирует за ресурсы со всем остальным на этой машине, а сервис, забирающий большую долю трафика региона, насыщает узел в ущерб другим сервисам. Поэтому после определённого порога «липкость» ослабляют и сервис распределяют по срезу узлов — так всплески трафика одного клиента поглощаются, не задевая другие сервисы, попавшие на тот же узел по хэшу.
После выбора узла тот подтягивает код функции. Узел, уже обслуживавший функцию, имеет его; узел, видящий её впервые, забирает один раз и кэширует, так что эту цену платит только первый запрос.
Создание и старт MicroVM
Файлы функции монтируются как несжатый образ EROFSread-only файловая система ядра Linux, рассчитанная на компактное хранение и быстрый доступ и затем отображаются в память, поэтому VM читает только те части бандла, которые реально использует, а не загружает всё целиком.
Когда MicroVM загружается и JavaScript-сервер начинает слушать порт, снимается снапшот MicroVM. Пока функцию не вызывают, MicroVM масштабируются до нуля вместо простоя. При следующем вызове новая MicroVM стартует из этого снапшота. Снапшот тоже отображается в память, поэтому VM может начать выполнение, не дожидаясь полного чтения снапшота обратно в память. Загрузку, снятие снапшота, восстановление и масштабирование до нуля обеспечивает Unikraftпродукт, на базе которого собраны compute-узлы и который управляет жизненным циклом MicroVM поверх Firecracker.
На стороне обработки ответа compute-узлы запускают локальные DNS-резолверы. Собираются метрики: время загрузки, время до первого открытия порта, время до запуска пользовательского кода. Действуют несколько circuit breakersпредохранитель, который при сбоях быстро перенаправляет трафик и выводит узел из эксплуатации, чтобы быстро перенаправлять запросы и выводить compute-узлы из эксплуатации.
Весь этот путь на «тёплом» экземпляре добавляет около 6 мс. Ничто из него не покидает сеть, и всё происходит между приходом запроса и отправкой ответа.
Как строится MicroVM при загрузке
Порядок шагов в сборке MicroVM важен, потому что от него зависят адреса устройств и корректность восстановления.
Сначала создаётся KVM-контекст с возможностями CPU-шаблона, затем KVM VM. После этого создаются vCPU, и только затем регистрируются DRAM-регионы. Если задана hotplug-память, выделяется адрес virtio-memустройство, позволяющее добавлять и убирать память у работающего гостя и регистрируется hotpluggable-регион.
Legacy-устройства присоединяются в строгом порядке: boot timer, balloon, block, net, pmem, vsock, entropy, virtio-mem, затем aarch64 legacy, vmgenid, vmclock. Boot timer обязан быть первым:
// The boot timer device needs to be the first device attached in order
// to maintain the same MMIO address referenced in the documentation
// and tests.Смысл в том, что адрес MMIO, на который ссылаются документация и тесты, зависит от порядка присоединения — если boot timer присоединить не первым, адрес сместится.
Согласованность TSC при загрузке и восстановлении
Сборка и загрузка MicroVM сами по себе TSCсчётчик тактов процессора, по которому гость измеряет время не синхронизируют: они строят MicroVM и затем запускают vCPU, которые стартуют в состоянии Paused, переводя их в работу.
Синхронизация TSC нужна только при восстановлении из снапшота. Каждый vCPU хранит собственный TSC offsetсмещение, которое вычитается из физического счётчика тактов, чтобы гость видел своё время в MSRрегистр процессора, доступный только ядру. При восстановлении эти регистры восстанавливаются по отдельности, поэтому у vCPU могут оказаться разные смещения: один vCPU будет отсчитывать время от одной точки, другой — от другой. Если гость мигрирует между такими vCPU, время для него может пойти назад — а на этом ломаются таймеры, планировщик и всё, что измеряет интервалы. Поэтому после восстановления состояния каждого vCPU вызывается синхронизация смещений: первый vCPU берётся за эталон, проверяется поддержка атрибута KVM_VCPU_TSC_OFFSET (иначе синхронизация пропускается), читается его TSC offset и присваивается всем vCPU. Цель — сохранить восстановленную временную шкалу vCPU0 и не допустить движения времени назад при миграции гостя между vCPU.
Тест test_synchronize_tsc_offsets проверяет, что после синхронизации у всех vCPU TSC offset равен offset'у первого, и что последующее восстановление состояния VM сохраняет эти синхронизированные смещения.
Управление жизненным циклом vCPU
На aarch64 старт vCPU начинается с проверки clock_realtime: если он включён, возвращается ошибка UnsupportedClockRealtime. Затем строятся mpidrs и восстанавливается состояние KVM. После восстановления устройств создаётся VMM, vCPU переводятся в собственные потоки, а состояние VMM устанавливается в Paused.
Сам старт vCPU переводит терминал в raw/non-blocking режим, создаёт Barrierпримитив синхронизации, на котором потоки ждут, пока все не дойдут до этой точки на число vCPU плюс один, для каждого vCPU задаёт mmio_bus (и pio_bus на x86_64) и запускает поток, после чего ждёт на барьере.
Жизненным циклом управляют через события. Пауза рассылает событие Pause и ждёт ответа Paused с таймаутом; возобновление рассылает Resume и ждёт Resumed; завершение рассылает Finish и затем очищает handles, что приводит к join потоков. Синхронизация с потоками vCPU идёт через ожидание ответа с таймаутом для паузы и возобновления и через очистку handles для завершения.
Снапшоты: что сохраняется и что проверяется
Создание снапшота начинается с сохранения состояния MicroVM: сначала сохраняется состояние устройств, затем состояние vCPU, KVM и VM. Порядок здесь принципиален — состояние устройств сохраняется до состояния KVM, потому что некоторые устройства могут изменить VirtIO-транспорт и послать прерывание гостю; если сохранить состояние KVM раньше, это прерывание никогда не будет доставлено гостю при возобновлении из снапшота.
Полученный объект состояния записывается в файл состояния: файл открывается, снапшот сохраняется, буфер сбрасывается, и при соответствующем флаге выполняется синхронизация на диск. Затем память гостя записывается в файл памяти. После записи памяти память очередей virtio помечается как грязная для всех активированных устройств.
Проверки согласованности выполняются не при создании, а при восстановлении снапшота. Проверка вендора CPU на x86_64 и идентификатора производителя на aarch64 сравнивают ID хоста и снапшота и при несовпадении или невозможности получить ID только логируют предупреждение, не возвращая ошибку. Проверка целостности состояния памяти требует, чтобы регионы памяти не были пусты, был хотя бы один DRAM-регион, у каждого DRAM-региона был ровно один plugged-слот и этот слот был plugged; иначе возвращаются ошибки NoMemory, NoDramMemory, DramMemoryTooManySlots или DramMemoryUnplugged. Без памяти или с неправильной конфигурацией DRAM восстановление невозможно.
Дополнительно файл состояния снапшота проверяется на целостность с помощью 64-битного CRCконтрольная сумма для обнаружения случайного повреждения данных, который вычисляется до загрузки снапшота. Это лишь частичная мера защиты от случайного повреждения — сами файлы диска и памяти тоже нужно защищать.
Восстановление MicroVM
Восстановление начинается с чтения состояния MicroVM из файла. Затем применяются переопределения сети и vsock, обновляется конфигурация машины (число vCPU, размер памяти, SMT, шаблон CPU, отслеживание грязных страниц, huge pages), выбирается PCI по состоянию снапшота и выполняется проверка согласованности.
Выбор бэкенда памяти зависит от типа бэкенда. Для файлового бэкенда открывается файл памяти и берётся память из него. Для UFFDмеханизм ядра Linux, позволяющий обрабатывать обращения к ещё не загруженным страницам памяти по требованию создаётся анонимная память, создаётся UFFD, регистрируются регионы и выполняется UFFD-хендшейк.
UFFD-хендшейк сериализует отображения бэкенда в JSON, подключается к Unix-сокету по указанному пути и отправляет эти байты вместе с файловым дескриптором UFFD. После отправки сокет «забывается», чтобы Rust не закрыл файловый дескриптор.
Формат заголовка снапшота проверяется при загрузке: magic должен совпадать с ожидаемым идентификатором, а версия — иметь тот же major и minor не больше текущего. Дополнительно проверяется CRC, и размер файла ограничивается сверху.
Совместимость хоста и гостя
Снапшот можно загрузить в другом процессе Firecracker, но при этом возможны потери сетевых и vsock-пакетов, а состояние сетевых соединений не гарантируется. Открытые vsock-соединения при создании снапшота закрываются, но слушающие сокеты в госте остаются активными и могут принимать новые соединения после возобновления.
Для хостов рекомендуется использовать cgroups V2: при cgroups V1 наблюдается высокая задержка восстановления снапшота. На arm64 снапшот работает для гостей с GICv2 и GICv3, но восстановление между разными версиями GIC невозможно.
Снапшоты должны возобновляться на идентичной программно-аппаратной конфигурации, на которой были созданы. Лишь в ограниченных случаях допускается восстановление на идентичном железе с более новой версией ядра хоста — без гарантий и без рекомендации для продакшена.
Отдельный вопрос — безопасность повторного использования одного снапшота. Если MicroVM A завершается после создания снапшота, а из снапшота возобновляется единственная MicroVM B, то уникальные идентификаторы, случайные числа и криптографические токены используются только один раз — такой сценарий считается безопасным. Если же из одного снапшота работают и MicroVM B, и MicroVM C, эти значения могут быть использованы дважды — такой сценарий считается небезопасным, и MicroVM A тоже небезопасна, если возобновит работу.
Для безопасного повторного использования применяется VMGenID: при возобновлении Firecracker обновляет 16-байтовый идентификатор и уведомляет гостя, что позволяет Linux версий >= 5.18 пересеять внутренний PRNGгенератор псевдослучайных чисел. Но остальное состояние — уникальные идентификаторы, кэшированные случайные числа, криптографические токены — всё равно реплицируется между MicroVM, возобновлёнными из одного снапшота, и пользователям нужно реализовать дедупликацию. VMClock обрабатывает восстановление из снапшотов через поле vm_generation_counter.
Full и diff снапшоты
Full-снапшот создаётся тем же API-вызовом, но с типом снапшота по умолчанию Full. При успехе файл состояния содержит модель устройств и эмуляции, а файл памяти — полную копию памяти гостя.
Diff-снапшот задаётся типом Diff. Тогда файл памяти содержит только страницы, изменённые с момента последнего создания снапшота или с момента создания MicroVM — что позже. Если отслеживание грязных страниц включено, для определения изменённых страниц используется журнал грязных страниц KVM. Если не включено — применяется другой механизм определения изменённых страниц, который может дать больший (но всё ещё разрежённый) файл памяти.
Слои diff-памяти можно сливать поверх базового файла: после слияния база становится возобновляемым снапшотом памяти на момент создания этого слоя, и процедуру повторяют для каждого следующего слоя. При этом нельзя смешивать файлы состояния от разных вызовов создания снапшота — нужно использовать файл состояния из того же вызова, что и последний слитый слой памяти.
Поле sync_snapshot_files управляет тем, синхронизируются ли файлы состояния и памяти на диск до возврата запроса. По умолчанию оно включено, что гарантирует сохранность при падении хоста. Выключение ускоряет создание, оставляя данные в page cache хоста, — теряется только устойчивость к падению хоста. Блочные backing-файлы всегда синхронизируются независимо от этого поля.
RPC-интерфейс и управление жизненным циклом
Публичный интерфейс VMM — это перечисление действий, где каждый вариант несёт свои параметры. Запросы маршрутизируются двумя контроллерами в зависимости от стадии.
До загрузки MicroVM разрешены действия, настраивающие будущую машину: конфигурация источника загрузки, логгера, метрик, последовательного порта, вставка блочных, pmem- и сетевых устройств, загрузка снапшота, обновление конфигурации машины и запуск MicroVM. Все остальные действия на этой стадии возвращают ошибку OperationNotSupportedPreBoot.
После загрузки разрешены действия, управляющие работающей MicroVM: создание снапшота, пауза, возобновление, отправка CTRL+ALT+DEL, обновление блочных, сетевых, pmem-устройств и размера hotplug-памяти, а также hotplug устройств. Действия, настраивающие машину, на этой стадии возвращают ошибку OperationNotSupportedPostBoot.
Методы настройки до загрузки помечают, что машина ещё на стадии загрузки. Загрузка снапшота при уже пройденной стадии загрузки возвращает ошибку LoadSnapshotNotAllowed.
Создание снапшота разрешено только после загрузки и только когда MicroVM в состоянии Paused. Пауза приостанавливает vCPU и также приостанавливает эмуляцию устройств: сервер перестаёт опрашивать цикл событий и обслуживает только API-запросы до возобновления.
Запуск MicroVM завершает стадию загрузки: при успехе результат сохраняется и контроллер заменяется на runtime-контроллер. Загрузка снапшота переводит VMM в состояние восстановления: если стадия загрузки уже пройдена, возвращается ошибка; иначе выполняется восстановление из снапшота, и при ошибке восстановления процесс считается слишком «грязным» для восстановления. Если запрошено возобновление после загрузки снапшота, оно тоже выполняется, и ошибка возобновления так же помечает процесс как невосстановимый.
Actions API и упорядоченное завершение
Действия выполняются через PUT-запросы на ресурс /actions, где в теле указывается тип действия. Запуск MicroVM включает её и стартует гостевую ОС, не имеет payload и может быть успешно вызван только один раз. Сброс метрик сбрасывает их по требованию пользователя.
Отправка CTRL+ALT+DEL запрашивает упорядоченное завершение MicroVM со стороны хоста. Поскольку Firecracker завершается при выключении или сбросе гостя, это действие может вызвать чистое завершение.
На x86_64 действие посылает последовательность CTRL+ALT+DEL, которую большинство Linux-дистрибутивов трактуют как мягкую перезагрузку: выполняют упорядоченное завершение и сбрасывают CPU, что Firecracker наблюдает и завершается. Для этого эмулируется стандартная AT-клавиатура через контроллер i8042, и в гостевом ядре нужны соответствующие опции поддержки драйверов.
На aarch64 действие внедряет нажатие виртуальной кнопки питания через контроллер PL061 GPIO и gpio-keys, после чего гость выключается через PSCI SYSTEM_OFF, что Firecracker видит как событие завершения. В отличие от x86_64, CPU не сбрасывается. Кнопка удерживается нажатой, пока гость не прочитает линию GPIO обратно, после чего Firecracker отпускает её. Нажатие, сделанное во время паузы, доставляется после возобновления, а захваченное в снапшоте остаётся ожидающим на восстановленной MicroVM — так же, как нажатие клавиши на x86_64 остаётся в очереди до потребления гостем.
Память и прерывания
Регистрация DRAM-регионов резервирует по одному слоту на регион и создаёт DRAM-регион. Регистрация hotpluggable-региона сначала проверяет, что длина региона кратна размеру слота, вычисляет число слотов, резервирует их и создаёт hotpluggable-регион. Счётчик слотов увеличивается атомарно и возвращает отказ, когда достигнут предел числа слотов памяти. Тест проверяет, что при попытке зарегистрировать больше регионов, чем допустимо, возвращается ошибка о нехватке слотов, а до этого предела регистрация проходит успешно.
MSI-прерывания реализованы через таблицу маршрутов. Регистрация маршрута создаёт запись с типом MSI, заполняет адрес и данные из записи таблицы MSI-X, а при поддержке соответствующей возможности добавляет идентификатор устройства, после чего вставляет запись по ключу GSI с флагом маскирования. Группа MSI-X создаётся выделением нужного числа GSI и созданием вектора для каждого. Маршруты отправляются в KVM, причём берутся только незамаскированные записи.
Тесты проверяют: число векторов; что изначально все векторы выключены, включение включает их, а выключение выключает; что можно вызвать срабатывание для допустимых индексов, а для недопустимого возвращается ошибка; что уведомитель есть для допустимых индексов и нет для недопустимого; что обновление с недопустимым индексом даёт ошибку; что обновление и регистрация меняют GSI-конфиг только своего маршрута, при этом флаг маскирования соответствует определённым индексам, а включённым оказывается только первый вектор; что при сохранении и восстановлении GSI совпадают, но флаг включённости сбрасывается; что GSI вне диапазона отклоняется; что после создания, настройки и удаления группы таблица маршрутов пуста, а аллокатор GSI возвращается в исходное состояние.
Регион steal time и присоединение устройств
Регион steal timeвремя, которое гость провёл в очереди на физический процессор, отданный другим задачам выделяется размером, равным размеру структуры, умноженному на число vCPU, с выравниванием на размер структуры и заданной политикой размещения. Затем каждый vCPU по индексу регистрируется по адресу, смещённому на индекс, умноженный на размер структуры.
Присоединение устройств подчиняется правилу: мьютекс устройства не должен быть заблокирован в момент присоединения, иначе будет deadlockвзаимная блокировка, при которой потоки ждут друг друга бесконечно. Поэтому идентификатор устройства берётся под блокировкой, блокировка снимается, и только затем устройство присоединяется. Это касается balloon, vsock, entropy и других устройств. Для pmem-устройств дополнительно: если устройство назначено корневым, в командную строку ядра вставляется указание корневого устройства и режим ro или rw в зависимости от того, доступно ли оно только для чтения.
Почему это быстрее: компромиссы и ограничения
Скорость запуска MicroVM оплачивается несколькими компромиссами.
Изоляция строится послойно: граница виртуализации KVM и Firecracker, затем ограничение процесса через seccomp-фильтры, cgroups и namespaces, и сброс привилегий через jailer. Jailer запускается отдельным процессом, настраивает ресурсы, требующие повышенных прав, сбрасывает привилегии и делает exec() в бинарник Firecracker, после чего тот работает непривилегированно. Каждую MicroVM можно дополнительно поместить в cgroup: через cpuset задаётся привязка к узлу, чтобы предотвратить миграцию и лишнюю конкуренцию за общие ресурсы, а через cpu — своя квота процессорного времени для справедливого распределения.
Стоимость снапшотов асимметрична: возобновление из снапшота оптимизировано по скорости, тогда как создание снапшота требует дополнительных циклов CPU для синхронной записи страниц памяти в файл снапшота. При использовании cgroups V1 наблюдается высокая задержка восстановления снапшота — рекомендуется разворачивать снапшоты на хостах с cgroups V2.
Наконец, снапшоты должны возобновляться на идентичной программно-аппаратной конфигурации, на которой были созданы; восстановление на идентичном железе с более новой версией ядра хоста допускается лишь в ограниченных случаях, без гарантий и без рекомендации для продакшена.
Что даёт новая модель исполнения
Compute-узлы собираются из базового образа, опубликованного Unikraft, и устанавливают набор пакетов. Они собираются отдельно от edge-узлов, чтобы edge-узлы оставались лёгкими и быстрыми, можно было использовать разные типы инстансов и масштабировать узлы независимо.
Настоящая VM с настоящей файловой системой снимает большинство причин для ограничений npm-пакетов, которые сейчас работают в edge-функциях в бете с оговорками про нативные бинарники и импорт файлов во время выполнения. Документированные лимиты — 50 мс CPU на запрос, 512 МБ памяти и 20 МБ сжатого кода — происходили из модели исполнения на изолятах, и теперь их можно пересмотреть. А поскольку вычисления выполняются внутри собственной сети, становится возможным строить то, что зависит от контроля сетевого пути, вместо обращения к третьей стороне через интернет.
Что из этого следует на практике
- Холодный старт определяется не только временем загрузки VM. Заявленные «менее миллисекунды на создание» и «около 2 мс на p99 на старт» относятся к самой MicroVM. Полный путь на тёплом экземпляре — около 6 мс, и в него входят выбор узла, подтягивание кода, монтирование образа и запуск. Первый запрос к функции на новом узле дополнительно платит за загрузку кода.
- «Липкость» — это стратегия кэширования, а не только балансировка. Привязка сервиса к узлу через rendezvous hashing держит MicroVM тёплой и код в кэше. Ослабление привязки после порога — компромисс: оно защищает от горячей точки, но может увеличить число холодных стартов.
- Снапшот — не бесплатная операция. Возобновление оптимизировано по скорости, а создание требует синхронной записи страниц памяти. Если устойчивость к падению хоста не критична, отключение синхронизации файлов ускоряет создание, оставляя данные в page cache.
- Diff-снапшоты требуют дисциплины при слиянии слоёв. Файлы состояния от разных вызовов создания снапшота смешивать нельзя — нужен файл состояния из того же вызова, что и последний слитый слой памяти.
- Повторное использование снапшота небезопасно по умолчанию. Если из одного снапшота работают несколько MicroVM, уникальные идентификаторы и токены могут быть использованы дважды. VMGenID пересевает PRNG ядра, но остальное состояние всё равно реплицируется — дедупликацию нужно реализовать самостоятельно.
- Порядок шагов при сборке VM — часть контракта. Boot timer обязан присоединяться первым, иначе сместится MMIO-адрес, на который ссылаются документация и тесты. Состояние устройств сохраняется до состояния KVM, иначе прерывание от устройства потеряется при возобновлении.
- Стадия жизненного цикла определяет допустимые действия. До загрузки можно настраивать машину, после — только управлять работающей. Создание снапшота возможно только в состоянии Paused, а пауза останавливает и эмуляцию устройств.
Где смотреть в коде
- rpc_interface.rs: set_vsock_device
- rpc_interface.rs: handle_request
- rpc_interface.rs: balloon_config
- vm.rs: register_msi
- builder.rs: build_microvm_for_boot
- rpc_interface.rs: new
- builder.rs: build_and_boot_microvm
- builder.rs: build_microvm_from_snapshot