ЯдроКодаподготовка к экзаменам
Научная библиотека

Загружаем научный разбор

Подготавливаем текст, источники и редакционные примечания без изменения разметки страницы.

Каталог статейМатериал и источники

Firecracker: зачем контейнеру отдельное ядро и какова цена microVM

Автор: · Обновлено

Исследование Firecracker и собственная модель плотности размещения: границы изоляции, стоимость microVM, холодный старт и ограничения сравнений.

Сервис запускает решения участников учебного соревнования. Один участник присылает короткую программу, другой — собственный исполняемый файл с неизвестным поведением. Оба задания удобно упаковать в образы, ограничить по времени и удалить после проверки. Однако удобство упаковки не отвечает на вопрос, какое ядро обслуживает их системные вызовы и какая граница отделяет задания друг от друга.

Здесь полезно различать три решения: как доставить программу, как изолировать её выполнение и как выбрать сервер. Образ помогает с первым, среда исполнения — со вторым, планировщик — с третьим. Замена одного слоя не обязана отменять остальные. Поэтому вопрос «Firecracker или Docker?» слишком широк: сначала нужно назвать функцию, которую собираются заменить.

Какую задачу решали авторы

В работе Agache и соавторов, NSDI 2020 Firecracker описан как специализированный монитор виртуальных машин, использующий KVM. Цель — запускать многочисленные короткие нагрузки разных клиентов с аппаратной виртуализацией и небольшими расходами на каждую среду. Минимальная модель устройств и ограниченный набор возможностей уменьшают сложность VMM. Это сознательная специализация, а не универсальная виртуальная машина для любой гостевой ОС.

Авторы рассматривают архитектуру, опыт интеграции в AWS Lambda и экспериментальные сравнения запуска, памяти и выполнения. Они описывают отдельное гостевое ядро и защиту процесса VMM на хосте. Эти слои дополняют друг друга. Результаты относятся к конфигурациям статьи: время загрузки небольшой гостевой системы не равно времени готовности произвольного пользовательского сервиса. Публикация и PDF исследования.

Нарисуем собственную границу доверия

Для учебного проверяющего сервиса сформулируем допущение: программа участника недоверенная, а загрузчик заданий и хранилище результатов принадлежат оператору. В обычной Linux-контейнеризации обращения приложения к ядру обслуживает ядро среды, где запущен контейнер. В варианте с microVM между приложением и хостом появляется гостевое ядро и граница виртуализации. Сам образ приложения от этого не превращается в гостевую ОС: его ещё нужно поместить в подготовленную среду и обеспечить запуск.

Перечень доверенных частей становится конкретнее: конфигурация хоста, KVM, VMM, его ограничение прав, загрузочный образ, управляющий API. Добавление границы не делает эти части безошибочными. Оно меняет путь, который должен пройти сбой или атака. Например, ошибка внутри приложения и ошибка виртуального сетевого устройства находятся по разные стороны выбранной границы и требуют разного разбора.

В нашей системе стоит отдельно решить, какие задания могут делить одну microVM. Если объединить все работы одного студента, расход памяти на оболочку распределится между ними. Но сбой внутри этой оболочки затронет всю группу, а обновление ядра придётся согласовывать с её жизненным циклом. Если выдавать отдельную среду каждому запуску, управление станет дороже, зато единица очистки и единица задания совпадут. Это проектный выбор нашего примера, а не рекомендация авторов использовать определённое число гостей.

Эксперимент: фиксированная цена на каждое задание

Следующая программа использует только стандартную библиотеку Python 3. Все числа придуманы для расчёта. Значения 4 и 80 МиБ не являются измерениями Firecracker и другой VM: это две условные стоимости оболочки. Нас интересует зависимость плотности от отношения полезной памяти к постоянным расходам. Сохраните код в density.py и выполните python3 density.py.

from math import ceil

budget_mib = 2048
host_reserve_mib = 256

def capacity(payload_mib, overhead_mib, group_size=1):
    available = budget_mib - host_reserve_mib
    def required(count):
        return count * payload_mib + ceil(count / group_size) * overhead_mib
    count = 0
    while required(count + 1) <= available:
        count += 1
    return count

small = capacity(64, 4)
large = capacity(64, 80)
grouped = capacity(64, 80, group_size=4)
assert (small, large, grouped) == (26, 12, 20)
assert grouped > large
assert capacity(512, 4) == capacity(512, 80) == 3
print(small, large, grouped)

Вывод 26 12 20 показывает три следствия допущений. При маленькой полезной нагрузке стоимость оболочки сильно влияет на количество заданий. Группировка экономит оболочки. При полезной памяти 512 МиБ выбранный бюджет вмещает три задания в обоих вариантах: меньшие расходы не обязательно меняют целочисленную вместимость конкретного сервера.

Модель считает выделенную память и оставляет фиксированный резерв. Она не учитывает разделяемые страницы, дисковый кеш, CPU, сетевые очереди и скачки потребления. В ней также нет цены восстановления после падения группы. Поэтому сравнивать число 20 с числом 26 как полную оценку качества двух архитектур нельзя. Это проверка одной зависимости при явно заданной границе учёта.

Холодный старт состоит из разных ожиданий

Для того же проверяющего сервиса заведём временные точки: задание принято, сервер выбран, необходимые данные доступны, гостевая среда загружена, процесс запущен, первый результат готов. Разность между последними двумя точками показывает работу приложения, но пользователь ждёт от первой до последней. Если очередь планировщика длится секунды, выигрыш на загрузке гостя может почти не изменить пользовательскую задержку.

Обратная ошибка — измерять только прогретую среду и объявлять проблему запуска решённой. После обновления образа, появления нового арендатора или потери узла прогретых экземпляров может не хватить. Для нашего сервиса нужны раздельные серии: повторный запуск в готовой среде, новый гость с локальным образом, новый гость с получением данных. Серии нельзя смешивать без указания долей. Среднее по тысячам быстрых запусков легко скрывает редкие задержки именно для новых участников.

В экспериментальном журнале полезно хранить размер задания, время в очереди, идентификатор образа и причину создания новой среды. Тогда изменение политики прогрева можно проверить на том же наборе заданий. Без этих полей нельзя отличить ускорение VMM от случайного роста доли попаданий в пул готовых сред. Не стоит автоматически считать готовность процесса готовностью сервиса: сетевой обработчик мог ещё не загрузить данные.

Что проверять перед переносом идеи

Начните с совместимости: необходимые ядро, архитектура процессора, устройства, сеть, файловая система и способ возврата результатов. Затем проверьте полный путь задания и очистку его состояния. В нашем примере остаточный файл предыдущего студента нарушит изоляцию данных даже при корректной виртуализации CPU. Удаление гостя и отзыв доступа к внешнему хранилищу — разные операции, которые требуется согласовать.

Отдельная проверка касается управляющего слоя. Кто может создать гостя, передать ему диск и открыть интерфейс управления? Ошибка прав в этом слое не исправляется коротким временем запуска. Текущую архитектуру и состав компонентов следует сверять с проектной документацией Firecracker, поскольку статья фиксирует систему своего времени.

Наша модель не измеряет защищённость. Нельзя присвоить системе «процент безопасности» по числу разрешённых вызовов или размеру VMM. Для решения нужны сценарии отказа и противник с определёнными возможностями: случайно зациклившаяся программа, злонамеренный бинарный файл, скомпрометированный управляющий процесс. У этих случаев разные предпосылки и разные необходимые барьеры.

Задача для самостоятельного разбора

Добавьте в capacity ограничение CPU: каждому заданию нужно 0,25 условного ядра, доступно четыре ядра. Посчитайте вместимость как минимум двух ограничений. Объясните, почему выигрыш по памяти перестал давать такой же выигрыш по числу заданий. Затем предложите метрику для цены группировки: например, сколько незавершённых работ теряется при отказе одного гостя. Не подставляйте её в формулу памяти — это отдельный критерий решения.

Практическая база для продолжения — [трек Docker](/lessons/without-university/docker-containerization), а механизм планирования разобран в [уроке Linux о планировщике и лимитах CPU](/lessons/without-university/linux-developer-foundations/linux-developer-26). После них полезно сравнивать варианты по одной таблице требований: граница доверия, совместимость, время готовности, плотность и восстановление. Такой разбор объясняет, какую задачу решает microVM в вашем приложении, прежде чем выбирать конкретную реализацию.

Источники

Формат и права

Формат
Авторский разбор

Атрибуция

Самостоятельный русскоязычный разбор ЯдроКода по указанным первичным источникам. Учебные модели, примеры и выводы редакции отделены от результатов исследований. Материал не является переводом или перепечаткой.

Код, данные и иллюстрации

Текст, учебные данные и Python-примеры созданы для этой публикации. Чужие программные реализации, таблицы, схемы и иллюстрации не воспроизводятся.