FreeBSD virtual environment management and repository

2020-10 upd: we reached the first fundraising goal and rented a server in Hetzner for development! Thank you for donating !

Bhyve vs jail

Контейнеры против гипервизоров: анатомия виртуализации для самых маленьких

Здесь дан небольшой обзор bhyve(8) и сравнение в подходе виртуализации и контейниризации.

Bhyve - это гипервизор второго типа (на основе базовой ОС), разработан Peter Grehan и Neel Natu в 2010 году и на данный момент, насколько известно, является единственным гипервизором в своем роде, исходный код которого опубликован под максимально либеральной BSD-лицензией (при этом, коммерческих гипервизоров в мире >= 3, GPL-licensed >= 3).

Различия в работе

Контейнер виртуализирует ОС-окружение на уровня ядра, те изоляция одного контейнера/процесса выполняется ядром, обслуживающим всю ОС. Если паникует ядро, падают все контейнеры. Кроме этого, в контейнере нельзя запустить систему, отличную от ОС хоста. Каждый процесс в контейнере представлен для ядра как обычный процесс в системе. Доступ к памяти, процессору и устройствам происходит напрямую. На всем участке кода ядра в ответственных местах присутствуют кейсы на jailed-проверки вида:

if (jailed(cred))
	jail_related_area
	

или

if (prison_check(td, cred) == 0)
	allow_action_for_jail
	

Гипервизор же виртуализирует машину полностью. Те, с точки зрения гостя, все ресурсы выглядят как настоящее оборудование. Виртуальная машина и все процессы в ней выглядят для хоста как 1 процесс. Ядра гостей и хоста изолированы друг от друга. Гипервизоры в отличие от контейнеров, позволяют запускать другие ОС внутри виртуальной машины. Чтобы понять тот оверхед, который накладывается гипервизорами, вспомним немного истории из жизни виртуальных систем и какие проблемы при этом возникали и как решались. Виртуализация затрагивает 3 главных компонента - CPU, память и I/O.

CPU

Одна из проблем виртуализации CPU - обезопасить основную систему от выполнения некоторых опасных инструкций, которые могут затрагивать глобальное состояние системы или выполнение некоторых I/O операций, которые в состоянии вызвать крах основной системы.

1) Один из вариантов виртуализировать CPU - это эмуляция CPU. Так работает например QEMU и это крайне медленный способ работы. Тем не менее, на часть небезопасных операций в этом случае может быть установлена заглушка.

2) Direct execution. Способ 2 - предоставить выполнение инструкции гостя на реальном процессоре. Данный метод граздо быстрее первого, но требует binary translation, интерпретации и модификации некоторых опасных инструкций на-лету, что достаточно трудоемко с точки зрения реализации. Тем не менее, подобный метод работал в старых версиях VmWare.

3) Паравиртуализация. Также как метод 2, предоставляет возможность выполнить инструкции гостя на реальном процессоре, однако требует модифицированной версии гостевой системы, что в настоящее время нерелевантно. Тем не менее, такой подход был популярен в старых версиях Xen

4) Аппаратная виртуализация, Intel VT-x или AMD-V (SVM). Представляет из себя 2 режима работы CPU: VMX root mode (режим супервизора) и VMX non-root mode (режим гостя, непривилегированный).

Также как в случае с методами 2 и 3, выполнение инструкций происходит напрямую на реальном процессоре, при этом блокируя нежелательные инструкции средствами самого процессора. Что позволяет разработчикам виртуальных систем заявлять, что гвесты на их гипервизорах работают с такой же скоростью как будучи не в невиртуализированной среде. Что является правдой (и заслуга в этом не гипервизоров, а VT-x архитектуры). При этом все основные задачи гипервизоров сводятся к предоставлению интерфейса по управлению виртуальными машинами и получения статистики, а также вызов соответствующих интрукций для перехода и выхода для конкретного гвеста в/из non-root mode.

Память

Виртуализация памяти также прошла некоторую стадию эволюции, начиная от маппинга областей памяти между VM и физическими страницами

и механизмом теневых страниц (Shadow pages):

до аппаратного уровня поддержки, Nested Paging (Intel EPT). Основной принцип работы которой заключается в постоении промежуточной таблицы соответствий для Guest physical и Host physical, с помощью которой MMU в 2 шага транслирует соответствующие страницы.

I/O, VT-d

Кроме CPU и памяти, требуется виртуализизировать I/O, для того, чтобы немодифицированные ОС были в состоянии запускаться и работать с таким (эмулированным) оборудованием, как:

  • SATA контроллеры
  • NIC адаптеры
  • USB, Serial порты
  • VGA адаптером
  • Irq контроллерами (LAPIC, IO-APIC)
  • Clock (HPET,TSC)
  • и тд.

Поскольку устройства требуют физический адрес для DMA, а гостевым ОС недоступны физические страницы, для виртуальных систем созданы паравиртуализированные драйвера для I/O операций: virtio, которые в даный момент являются стандрартом де-факто. Также, для PCI устройств и DMA, существует поддержка на уровне процессора (IOMMU (VT-d)) принцип который также строится в построении таблиц трансляций памяти для DMA между гостевыми адресами и физическими.

Bhyve

Теперь подытожим, какую роль во всем занимает FreeBSD, которая как известно, долго запрягает, но быстро ездит. Гипервизор Bhyve успешно "переждал" ;-) все предшествующие сложные этапы эволюции развития и переписывания гипервизоров и в данный момент, при своей работе использует VT-x, VT-d и virtio-драйвера в гостевых системах, представляя из себя современный гипервизор, работающий по алгоритму:

while (1) {
  ioctl (VM_RUN, &vmexit);
  switch (vmexit.exit_code) {
    case IOPORT_ACCESS
	emulate_device(vmexit.ioport)
	..
}
	

Где все самое сложное вынесено на уровень оборудования.

На момент написания статьи, помимо virtio для дисковой подсистемы, реализован AHCI режим. Виртуальные дисковые носители в данный момент могут представлять из себя как файловый образ, так и размещаться на ZFS-томах (zvol). Также, эмулируется Serial консоль и 2 типа PCI шины ввода-вывода: hostbridge и amd_hostbridge (Intel / AMD соответственно).

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

VirtIO или AHCI

В данной главе приведены результаты выполнения diskinfo(8) с соответствующими показателями для одного и того же диска внутри виртуальной машины, но подключенного в первом варианте через virtio, а во втором - через AHCI.

VirtIO:

Seek times:
        Full stroke:      250 iter in   0.020636 sec =    0.083 msec
        Half stroke:      250 iter in   0.017950 sec =    0.072 msec
        Quarter stroke:   500 iter in   0.033842 sec =    0.068 msec
        Short forward:    400 iter in   0.026702 sec =    0.067 msec
        Short backward:   400 iter in   0.027631 sec =    0.069 msec
        Seq outer:       2048 iter in   0.121300 sec =    0.059 msec
        Seq inner:       2048 iter in   0.122890 sec =    0.060 msec
Transfer rates:
        outside:       102400 kbytes in   0.071878 sec =  1424636 kbytes/sec
        middle:        102400 kbytes in   0.070914 sec =  1444003 kbytes/sec
        inside:        102400 kbytes in   0.070714 sec =  1448087 kbytes/sec

AHCI:

Seek times:
        Full stroke:      250 iter in   0.023152 sec =    0.093 msec
        Half stroke:      250 iter in   0.023843 sec =    0.095 msec
        Quarter stroke:   500 iter in   0.042602 sec =    0.085 msec
        Short forward:    400 iter in   0.034761 sec =    0.087 msec
        Short backward:   400 iter in   0.034904 sec =    0.087 msec
        Seq outer:       2048 iter in   0.161184 sec =    0.079 msec
        Seq inner:       2048 iter in   0.162722 sec =    0.079 msec
Transfer rates:
        outside:       102400 kbytes in   0.094570 sec =  1082796 kbytes/sec
        middle:        102400 kbytes in   0.086803 sec =  1179683 kbytes/sec
        inside:        102400 kbytes in   0.093864 sec =  1090940 kbytes/sec

Чем меньше seek time, тем лучше (VirtIO демонстрирует лучшие показатели)

Если же нужна разница в попугаях относительно трех последних значений (скорость передачи данных, чем больше - тем лучше), то натравив ministat(1) на эти цифры увидим, что VirtIO и в этом параметре превосходит в скорости AHCI на 6%:

x vrates - VirtIO
+ arates - AHCI
+------------------------------------------------------------------------------------------------+
|     + +                    +                                                           x    xx |
||______M_____A____________|                                                          |__AM_||
+------------------------------------------------------------------------------------------------+
    N           Min           Max        Median           Avg        Stddev
x   3       1424636       1448087       1444003     1438908.7      12528.03
+   3       1082796       1179683       1090940     1117806.3     53741.256
Difference at 95.0% confidence
        -321102 +/- 88441.8
        -22.3157% +/- 6.14645%

О настоящем оверхеде.

Теперь мы подошли вплотную в вопросу, которому посвящена статья - есть ли разница в оверхеде, в чем она выражается и что лучше - гипервизоры или контейнеры. И если в теории, разница между контейнерами (минимум оверхеда) и гипервизорами ясна, то на практике это рассчитать и предсказать достаточно сложно.

Относительно процессорных мощностей действительно, инструкции на процессоре выполняются с той же скоростью, как если бы они работали в нативной режиме, поскольку они выполняются напрямую. Однако необходимо иметь ввиду, что при аггресивном I/O (например, постоянная/продолжительная/высокая нагрузка на сеть или на дисковую подсистему) провоцирует гиганское количество переключений/прерываний ( VMEnter/VMExits ) в работе VTx/VTd (фунционал virtio-драйверов тоже не бесплатен) и тем самым, делает виртуализированные системы менее эффективными.

Другими словами стоит ожидать, что чем выше I/O, тем в геометрической прогрессии эффективность гипервизорных систем будет падать.

Для примера, запущенный процесс bhyve, если на него взглянуть из ktrace(1) или dtrace(1), будет генерировать следующий флуд системных вызовов:

 22506 vcpu 1   CALL  ioctl(0x3,0xc0787601,0x7fffff7fbe40)
 22506 vcpu 0   RET   ioctl 0
 22506 vcpu 0   CALL  ioctl(0x3,0xc0787601,0x7fffff9fce40)
 22506 vcpu 0   RET   ioctl 0
 22506 vcpu 0   CALL  ioctl(0x3,0xc0787601,0x7fffff9fce40)
 22506 vcpu 0   RET   ioctl 0
 22506 vcpu 0   CALL  ioctl(0x3,0xc0787601,0x7fffff9fce40)
 22506 vcpu 0   RET   ioctl 0
 22506 vcpu 0   CALL  ioctl(0x3,0xc0787601,0x7fffff9fce40)
 22506 vcpu 1   RET   ioctl 0
 22506 vcpu 0   RET   ioctl 0
 22506 vcpu 1   CALL  ioctl(0x3,0xc0787601,0x7fffff7fbe40)
 22506 vcpu 0   CALL  ioctl(0x3,0xc0787601,0x7fffff9fce40)
 22506 vcpu 1   RET   ioctl 0
 22506 vcpu 1   CALL  ioctl(0x3,0xc0787601,0x7fffff7fbe40)
 22506 vcpu 1   RET   ioctl 0
 22506 vcpu 1   CALL  ioctl(0x3,0xc0787601,0x7fffff7fbe40)
 22506 vcpu 1   RET   ioctl 0
 22506 vcpu 1   CALL  ioctl(0x3,0xc0787601,0x7fffff7fbe40)
 22506 vcpu 1   RET   ioctl 0
 22506 vcpu 1   CALL  ioctl(0x3,0xc0787601,0x7fffff7fbe40)

Из которого сложно сделать какой-то вывод. Но можно для наглядности воспользоваться pmc(3), чтобы посмотреть картину на уровне процессора, ядра и системных вызовов и увидеть работу системы изнутри. Для этого запустим все отличные от VM процессы на другом ядре (в примере, все процессы автора запущены в клетке KDE4, которой установлен cpuset_setaffinity на 3 ядро), а сам bhyve запустим на 2 ядре, чтобы при профилировании нам не попадало ничего лишнего, только bhyve:

% cpuset -c -l 2 cbsd bstart f10
% pmcstat -c 2 -O sys.stat -S instructions

Собрав немного статистики (даже не нагружая систему большим количеством I/O операций), прерываем pmcstat и на выходе имеем картинку ОС в разрезе со всеми ее внутренностями:

которая говорит сама за себя.

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

Одним их хороших, если не идеальных кандидатов на роль виртуализации можно привести, к примеру, сервисы авторизации, тикет-системы, LDAP/Active Directory, почтовые релеи и тд - т.е. такие ресурсы, которые значительную часть своей жизни простаивают и при этом они не могут запускаться по каким-то причинам в хост-системе в качестве контейнера (например сервер под управлением Unix, а почтовый релей - на Microsoft Exchange).

Если же нагрузка на I/O высокая, или же у вас нет контр-доводов (live migration и тп) иметь систему, отличную от ОС хост-системы или другой архитектуры, предпочтения следует отдавать контейнеризации. Не смотря на рекламные слоганы о 99%-ой виртуальности инфраструктуры от менеджеров отдельных коммерческих продуктов виртуализации, вряд ли кто-то из них предпочтет получить виртуальный автомобиль взамен настоящего ;)