Записывайте рецепт микровиртуалок Firecracker. Берем два популярных метода изоляции многопользовательской нагрузки — виртуальные машины и контейнеры. Выжимаем лучшее из обоих подходов, максимально упрощаем, тестируем на настоящем хайлоаде. В итоге получаем непробиваемую изоляцию виртуалок, которые можно запускать за сотни миллисекунд. Именно это решение работает под капотом AWS Lambda и Fargate, запуская в облаке миллионы serverless-функций и контейнеров каждую секунду. Оно называется Firecracker.



Этот инструмент микровиртуализации доступен в OpenSource. Если ваши задачи требуют мульти-тенантной изоляции, (ну, например, вы решили сделать собственное облако), Firecracker — это то, что надо.

Василий Пантюхин, архитектор Amazon Web Services, расскажет об архитектуре Firecracker, о том, как он используется AWS Lambda, сравнит его с альтернативными решениями и приведет примеры интеграции.

Дисклеймер: всё, что ниже — это личное мнение Василия, и оно может не совпадать с позицией Amazon Web Services.


Природные особенности публичных облаков


Одно из фундаментальных свойств публичных облаков — мультитенантность.

Кто-то переводит этот термин как «мультиарендность» или «многопользовательское окружение». Но с моей точки зрения, это не отражает суть. Мультитенантность — это когда разные пользователи и их нагрузки, живут на одной и той же физической инфраструктуре, делят ее между собой, но ничего друг о друге не знают. От многопользовательских нагрузок мультитенантность отличается принципиально более жесткими требованиями к изоляции ресурсов и доступа к ним.

Еще одно свойство, которое присуще публичным облакам — это хайлоад.

Поверьте мне, в случае с AWS это колоссальные нагрузки! На скриншоте пример реальных данных. Не скажу, за какое время и в каком регионе измерялись эти миллионы «попугаев», но это не какой-то аврал, а обычный для нас режим работы.



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

Виртуальные машины или контейнеры?


Есть два базовых подхода, которые потенциально могут помочь. Это виртуальные машины и контейнеры. У каждого есть свои плюсы и минусы.



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

  • безопасности, когда одна виртуалка не имеет возможности получить доступ к данным в другой машине;
  • ресурсов, когда я заказал 8 ГБ памяти, то надеюсь, что эта RAM будет моей и никто не сможет претендовать на ресурс, за который я заплатил.

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



Можно ли сказать, что контейнеры не безопасны? Нет, даже несмотря на наличие серьезных уязвимостей.

Я считаю, что на сегодняшний день правильно «сваренный» контейнер дает достаточный уровень безопасности.

Проблема в другом — разделяемое ядро принципиально не гарантирует, что когда-то в будущем не появятся уязвимости, которые нарушат изоляцию тенантов. Публичное облако не может себе этого позволить даже в теории.

Налицо очередное противоречие, и нужно искать решение. Самый простой способ — взять все лучшее у мамы — виртуальной машины, и у папы — контейнера, скрестить и получить нечто конвергентное. Собственно, мы это и сделали. Получился Firecracker.

Firecracker уже используется в продакшн. В том самом хайлоаде критически важных сервисов, например, AWS Lambda (serverless-функции) и AWS Fargate (serverless-контейнеры).

Проблемы старой AWS Lambda


AWS Lambda — это сервис serverless-функций. Берем функцию на Java, Go, Python или другом языке, кидаем ее в Lambda, и она волшебным образом исполняется. Выделять и управлять ресурсами не нужно. Как это было реализовано раньше?



Для каждого аккаунта AWS выделяли одну или несколько отдельных виртуальных машин EC2 для изоляции функций, которые принадлежат разным тенантам. Такая виртуалка потребляет ресурсы, даже когда ничего не делает. Предположим, что мы запускаем функцию раз в 10 минут, а она выполняется за 200 мс как обычная Lambda. Получается, что в течение часа EC2-машина используется всего лишь несколько секунд. При этом даже во время исполнения она не потребляет все доступные ресурсы — утилизация ниже плинтуса. Это бешено невыгодно.



Как решили проблему утилизации?


Разработали с нуля свое собственное решение Firecracker. Это очевидно из названия доклада:)

Чтобы начать разрабатывать новый продукт, нужно техническое задание. В нем много текста, но если выделить только смысл, то получится следующее.

  • Работает на KVM в качестве гипервизора. Кто работает с AWS, знает, что у нас два любимых гипервизора. Виртуальные машины предыдущих поколений работают на Xen. С конца 2017 года под капотом всех машин живет KVM.
  • Запускается максимально быстро. На эталонном железе требованием было полноценная загрузка микровиртуалки за 125 мс.
  • Минимальные накладные расходы на виртуализацию. В эталонной архитектуре одна микровиртуалка Firecracker дополнительно потребляет всего лишь 5 MB памяти. 
  • Возможность максимально плотного запуска. Расчетные параметры — полная загрузка 5 микровиртуалок на ядро в секунду. Это требование принципиально именно для таких сервисов как AWS Lambda. Функции должны быстро взлетать, отрабатывать и умирать, освобождая ресурсы для следующих.
  • Возможность переподписки. Именно возможность — не обязательно ее использовать. По сути, это выделение виртуальных ресурсов в большем объеме, чем они физически доступны. Это значит, что на сервере есть 16 ГБ RAM, и вы на нем одновременно запускаете 4 виртуальные машины, каждая из которых уверена, что ей доступно 8 ГБ памяти. 

AWS Lambda с Firecracker под капотом


Что изменилось в новой версии AWS Lambda? С точки зрения конечного пользователя ничего не поменялось. Миграция на обновленную архитектуру в продакшн прошла полностью прозрачно и незаметно для потребителей. Функции Lambda короткоживущие — сделать это было нетрудно.

Как же выглядит современная архитектура?

На самом нижнем уровне теперь находится не виртуальная машина, а физическая bare-metal.

Такие серверы позволяют полноценно использовать все функции CPU, например, Intel VT. Это дает дополнительные преимущества при использовании виртуализации на вышестоящих уровнях.



Поверх железки стоит хостовая операционка с модулем KVM в ядре. Выше запускаются микровиртуалки Firecracker со своими собственными гостевыми ОС. Ну а в них уже реализованы компоненты самого сервиса AWS Lambda.

Раньше под каждого заказчика, который использует функции Lambda, мы выделяли отдельные EC2 виртуальные машины с низкой утилизацией. Новый подход позволяет запускать легковесную микровиртуалку, только когда это действительно нужно. Изоляция ресурсов Firecracker друг от друга позволяет нам делать это на общем железе с максимальной плотностью. Утилизация ресурсов принципиально улучшилась.

Что в коробке?


Я обещал анбоксинг, поэтому пройдемся по основным компонентам Firecracker, рассмотрим каждый из них в отдельности, а потом соберем все вместе.



Принципы дизайна


Когда мы только начали обсуждать разработку Firecracker, мы договорились придерживаться двух принципов.

Избавиться от всего ненужного. Почему классические виртуалки медленно грузились? В частности, потому что код перегружен. Им приходится поддерживать огромное количество легаси-устройств. Но зачем эмулировать устройство, которое еще 10 лет назад уже никто не использовал? Незачем, поэтому избавляемся от всего лишнего. Мы решаем конкретную узкую задачу и любая функциональность, которая не помогает в решении этой задачи, считается ненужной.

Избавьтесь от всего лишнего и сфокусируйтесь на одной задаче.

Упростить то, что осталось. Даже то, что осталось, должно быть простым, насколько это возможно.

На чем писали?


Firecracker написан на модном Rust, потому что:

  • он позволяет писать более безопасный код, в частности, с точки зрения памяти;
  • Производительность и накладные расходы сопоставимы с современными С++;
  • Rust давно используется, с 2015 года на нем написано много классных хайлоад-решений.

Железо


Повторю — Firecracker требует установки на реальное железо. В качестве эталонной конфигурации была выбрана bare-metal машина i3.metal.


Примечание: доклад был в начале апреля 2019. На тот момент поддерживалась только Intel платформа. В мае добавили альфа поддержку AMD, а в июне ARM. AMD, возможно, будет немного дешевле, чем Intel, а поддержка ARM открывает интересные возможности для работы с мультитенантным IoT. 

Если заказывать i3.metal или другую bare-metal машину в AWS, то для экспериментов это будет слишком мощная и дорогая конфигурация. Поэтому, если решитесь установить Firecracker на них, то не забудьте после экспериментов обязательно погасить эти машины. Иначе в конце месяца придет приличный счет.

Есть ли вариант дешевле? Да, можно загружать Firecracker в виртуальном окружении. Но уже не в AWS — мы принципиально не поддерживаем на EC2 вложенную (nested) виртуализацию. Зато это можно делать в GCP, Azure, DigitalOcean или использовать Proxmox, Parallels, VMware Fusion. Все будет работать, если придерживаться требований, в частности, по версии ядра гостевой ОС.

Ядро


Принципиальный элемент решения — модуль ядра KVM.

На всякий случай в качестве отступления опишу, что такое KVM. Это не гипервизор целиком, а только его часть. Ее основные задачи — это настройка виртуального процессора (vCPU) и старт виртуальной машины.



Но этого не хватит для полноценной работы. Кроме процессора нужны еще какие-то устройства. Их эмуляция происходит в user space.

VMM


Чтобы появились хотя бы базовые девайсы, нужен еще один компонент — Virtual Machine Manager (VMM). До Firecracker основным VMM-вариантом был QEMU.



Мы серьезно рассматривали возможность использования QEMU, но решили разработать свой «велосипед». Для этого было несколько причин.

Управление разработкой. QEMU — это большой зрелый продукт, в котором больше миллиона строк кода. Для внесения изменений нужно просмотреть слишком много исходников. В разработке и принятии решений о развитии участвует очень много людей.

Безопасность. QEMU умеет эмулировать много девайсов. Именно поэтому у него достаточно большая поверхность атаки. Наша задача — сделать микровиртуалку. С точки зрения безопасности мы обязаны поверхность атаки минимизировать.

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

Устройства


Firecracker реализует VMM, который используется для эмуляции устройств. Мы эмулируем следующие устройства:

  • Консоль. Полезная и нужная вещь, хотя ее можно и отключать.
  • Клавиатура. Мы сделали хитрую клавиатуру — всего лишь с одной кнопкой «Reset». Мы просто не доверяем программному «Reset» тех ОС, которые потенциально могут запускаться в Firecracker. Поэтому сделали железный.
  • Драйверы Virtio для диска и сети. Virtio — это очень примитивное устройство. По сути, это «ring buffer». Гостевая система что-то записала в буфер, нажала на звонок, а хостовая система прочитала данные из этого буфера через файл. 



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

Ну и еще нам нужна возможность контроля потребления ресурсов. За это отвечает rate limiter.

Девайсы есть. Но как ими управлять и настраивать конфигурацию микровиртуалок? Здесь нам помогут API управления.

Управление


В Amazon есть несколько нерушимых фундаментальных принципов. Один из них заключается в том, что любые сервисы общаются друг с другом только через API. Не важно, это внешние сервисы, которые вы используете как пользователь, или наши внутренние сервисы. Не бывает такого, что один сервис идет напрямую в базу данных другого сервиса — это запрещено и наказуемо. API тред во Firecracker как раз используется для доступа к настройкам и функциональности микровиртуалок через REST API.



Мы придерживаемся Open API спецификации, так что можно пользоваться Swagger.

Метаданные


Есть такое зло — hard coding. Это когда прямо в код зашиваются какие-то данные, например, логин и пароль доступа к другому ресурсу. Это, конечно, не допустимо. Периодически нам нужно передавать какие-то данные внутрь микровиртуалки. Это делается через сервис метаданных.



Требуемую информацию через сокет мы передаем в трэд метаданных IMDS. Чтобы получить эту информацию внутри микровиртуалки, нужно по REST API запросить http://169.254.169.254/latest/meta-data. Пользователи AWS уже знают этот «волшебный» IP. Точно также микровиртуалки могут получить детальную информацию о конфигурации самих себя.

Логи


Обязательно нужно иметь возможность выбрасывать логи микровиртуалки во внешний мир до того, как она умерла. Делается это через обычный юниксовый FIFO.



Дополнительная изоляция


Главный плюс виртуальной машины в изоляции. Кажется, что этого должно быть достаточно, но мы пошли дальше. На всякий случай, для запуска в продакшн рекомендуется проложить дополнительный слой изоляции, который называется Jailer.



Это стандартные меры предосторожности:

  • cgroups для ограничения ресурсов;

  • namespace для изоляции процессов;
  • seccomp — аналог файрвола для системных вызовов;
  • и, конечно, всеми любимый chroot.

Интеграция с другими сервисами


Есть ли уже готовые решения на базе Firecracker? Конечно, да.

OSV


Эта операционная система заточена под запуск всего одного приложения. За счет этого у нее предельно просто организованы работа с памятью и планировщик. Производительная и легкая в управлении ОС теперь работает и поверх Firecracker.

Containerd


Также мы сделали интеграцию с containerd. Если вы с ним уже работаете, то нужен минимум усилий, чтобы запускать контейнеры, которые обернутые в Firecracker для изоляции.



Вместо обычного shim, containerd обращается к Firecracker shim. Он уже управляет runc, через агента, который установлен внутри микровиртуалки. Проверено, работает.

Kata Containers


Когда мы только объявили о выходе Firecracker, один из самых популярных вопросов заказчиков был:

— Механизм изоляции контейнеров уже придуман — это Kata Containers. Почему вы их не использовали? Зачем разрабатывать свое?
— Kata работает с QEMU, а мы не хотим работать с QEMU. Поэтому мы решили сварить свое.

Но получилось интересно. Разработчики из Kata встретились с разработчиками Firecracker и договорились об интеграции. Потому что все видят в этом огромный плюс. Kata Containers v1.5 уже поддерживает и QEMU, и Firecracker.

Получается, что мы не конкурируем, а гармонично дополняем друг друга. В Kubernetes с v1.12 можно определить runtimeClassName как Kata FC или Kata QEMU, и запускать свои контейнеры в соответствующей изоляции.



Как же выбрать изоляцию, которая подходит для вашей задачи — Firecracker или QEMU? Мое личное мнение, что здесь принципиально то, какое у вас приложение — pets или cattle?



Kata с QEMU это решение для pets нагрузок. QEMU большая, многофункциональная система. Потенциально она предлагает больше вариантов поддержки и «лечения» ваших любимых pets.

Firecracker подойдет, когда нагрузка относится к категории cattle. В случае, если ваше stateless-приложение изначально спроектировано для гибкого горизонтального масштабирования и сбой одного или нескольких контейнеров не критичен. Обеспечивая надежную изоляция, он позволяет максимально быстро загружать необходимое количество новых контейнеров взамен вышедших из строя.

Что в итоге?


Мы начали с противоречий. Кажется, что разрешение противоречия — это подход «и нашим и вашим», что-то среднее, что удовлетворит всех. Но опыт говорит, что компромисс не обязателен.

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

Мы уже используем Firecracker в продакшн нескольких наших сервисов. Главный плюс, который мы получили — улучшение утилизации сервисов. Это позволило значительно сэкономить. Частью этой экономии мы поделились с заказчиками. Например, после внедрения Firecracker цены на serverless-контейнеры AWS Fargate в январе 2019 упали на 35-50%. Выгода видна невооруженным глазом, поэтому нет никаких сомнений, что мы будем и дальше развивать этот продукт и делиться наработками с Open Source. То, что Firecracker уже начали интегрировать с другими решениями говорит о растущем интересе со стороны комьюнити.

Если у вас идея или готовый продукт в описании которого есть слова «мультитенантная изоляция» — это явный индикатор того, что стоит поэкспериментировать с микровиртуалками Firecracker.

Этот доклад отлично иллюстрирует принцип, которого мы стараемся придерживаться на конференциях Онтико, — получать мировой опыт от русскоговорящих специалистов. И дело не только в возможном языковом барьере, а в привычной нам культуре делиться техническими деталями. В ноябре на HighLoad++ Василий выступит снова, а компанию ему составят, например, Артемий Колесников из Facebook, Vittorio Cioe из Oracle и, конечно, Петр Зайцев из Percona. Будут у нас и доклады на английском языке, подпишитесь на рассылку — будем рассказывать, когда к сорока принятым докладам добавятся новые.

Комментарии (2)


  1. beduin01
    19.09.2019 12:55

    Хотелось бы услышать аргументы о преимуществах FireCracker перед Docker.


    1. Hen
      20.09.2019 09:53

      Докер замечательной решение, грузится быстро, легко управлять и пр. Но для мульти-тенантных решений оно не подходит. Облако принципиально рассматривается, как очень агрессивная среда. Мы исходим из того, что попытки получить доступ к ресурсам других тенантов будут происходить постоянно (на самом деле так и есть). Поэтому докер, который использует shared kernel, не подходит. Очень велика вероятность, что кто-нибудь найдет способ использовать это (умышленно или нет) для нарушения изоляции VPC и аккаунтов.