Виртуальная машина и контейнер решают одну задачу - запустить приложение в изолированном окружении, - но делают это на разных уровнях. Виртуальная машина изолирует целый компьютер вместе с его операционной системой. Контейнер изолирует только процесс, а ядро берёт у хоста.
Из этой разницы следует всё остальное: почему контейнер стартует за секунду, а виртуальная машина за минуту; почему в контейнере нельзя запустить Windows на Linux-хосте; и почему на нормальном сервере обычно работает и то и другое одновременно.
У виртуальной машины своё ядро, у контейнера - общее с хостом.
Всё остальное - следствия. Своё ядро означает полноценную операционную систему в образе, отсюда гигабайты на диске и десятки секунд на загрузку. Общее ядро означает, что в образе лежит только приложение с библиотеками, отсюда мегабайты и мгновенный старт. Оно же означает, что контейнер не может быть другой операционной системой, чем хост.
| Параметр | Виртуальная машина | Контейнер |
|---|---|---|
| Что изолируется | Целая машина с ядром | Процесс и его файловая система |
| Ядро | Собственное | Общее с хостом |
| Размер образа | Гигабайты | От единиц мегабайт до сотен |
| Время старта | Десятки секунд, как обычная загрузка ОС | Доли секунды - секунды |
| Накладные расходы | Заметные: под каждую ОС свои процессы и память | Минимальные: обычный процесс с ограничениями |
| Другая ОС на том же хосте | Да: Windows на Linux-хосте и наоборот | Нет: только та же система, что у ядра хоста |
| Плотность на узле | Десятки | Сотни |
| Изоляция | Аппаратная, на уровне гипервизора | Программная, средствами ядра |
| Управление | Гипервизор | Движок контейнеров плюс оркестратор |
| Что чинят при обновлении | Обновляют систему внутри машины | Пересобирают образ и перезапускают контейнер |
Виртуализация делит один физический сервер на несколько независимых машин. Занимается этим гипервизор - слой, который распределяет между машинами процессорное время, память, диск и сеть, а заодно не даёт им лезть друг к другу.
Каждая виртуальная машина видит перед собой обычный компьютер: свой BIOS, свои диски, свою сетевую карту. Внутри стоит полноценная операционная система, и она не знает (точнее, ей не обязательно знать), что железо ненастоящее.
Гипервизоры бывают двух типов: те, что ставятся прямо на железо, и те, что работают как программа внутри обычной ОС. Разница между ними и что выбирать - разобрано в статье про типы гипервизоров.
Контейнер - это обычный процесс на хосте, которому ядро выдало собственное представление о мире. Он видит свою файловую систему, свой список процессов, свою сеть - и не видит соседей. Механизмы, которые это обеспечивают, встроены в ядро Linux: пространства имён отвечают за изоляцию, контрольные группы - за ограничение ресурсов.
Никакой второй операционной системы внутри нет. Когда говорят «контейнер с Ubuntu на сервере с Rocky Linux», имеют в виду, что в образ положили файлы и библиотеки Ubuntu, но ядро работает хостовое.
Отсюда главное практическое свойство: образ собирается один раз и одинаково запускается на ноутбуке разработчика, на тестовом стенде и на бою. Классическое «у меня работало» перестаёт быть аргументом.
Названия часто путают, хотя разница ощутимая.
Один контейнер запускается командой. Сотня контейнеров на десятке узлов руками не управляется - нужен оркестратор.
Оркестратор берёт на себя то, что иначе пришлось бы делать администратору вручную: раскладывает контейнеры по узлам с учётом свободных ресурсов, поднимает упавшие, добавляет и убирает копии под нагрузку, выкатывает новую версию по частям и откатывает, если пошло не так, держит внутреннюю сеть и балансировку между копиями.
Фактический стандарт здесь - Kubernetes. Проект начинался в Google, но в 2015 году был передан фонду CNCF и с тех пор развивается сообществом. Есть и более простые варианты - Docker Swarm, Nomad; они разумны там, где масштаб не требует всей сложности Kubernetes. Сравнение - в статье про оркестрацию контейнеров.
Это тот раздел, которого обычно не хватает в сравнениях. Контейнеры не «лучше» и не «пришли на смену» - у них есть чёткие границы применимости.
Нужна другая операционная система. Ядро общее, значит, на Linux-хосте не запустится контейнер с Windows. Когда Docker работает на Windows или macOS, под ним всё равно крутится Linux-машина - в Windows это WSL2 или Hyper-V. То есть виртуализация никуда не делась, она просто спрятана.
Нужно своё ядро или модули ядра. Специфическая версия ядра, собственные модули, драйверы устройств, ядерные параметры под конкретное приложение - всё это уровень виртуальной машины, а не контейнера.
Нужна жёсткая изоляция. Общее ядро - это общая поверхность атаки: уязвимость в ядре потенциально касается всех контейнеров на узле сразу. Там, где по требованиям безопасности нужны разные контуры, границу проводят гипервизором, а не пространствами имён. Промежуточный вариант тоже существует - Kata Containers запускает каждый контейнер в микро-виртуалке, совмещая привычный интерфейс с аппаратной изоляцией.
Приложение не готово к контейнеру. Старые системы, которые хранят состояние на локальном диске, требуют графического окружения или лицензируются по MAC-адресу, в контейнер переезжают тяжело. Виртуальная машина принимает их как есть.
Сетевая часть у двух технологий устроена по-разному, и это одно из мест, где различие видно руками.
Виртуальная машина получает сетевую карту. Гипервизор создаёт ей виртуальный сетевой адаптер со своим MAC-адресом и подключает его к виртуальному коммутатору. Для сети снаружи такая машина выглядит обычным устройством: у неё свой адрес, её видно в списке, ей можно выдать адрес по DHCP и назначить номер VLAN.
Контейнер по умолчанию сидит за внутренней сетью хоста. Движок создаёт пару виртуальных интерфейсов и мост, контейнеры общаются между собой внутри этого моста, а наружу выходят через трансляцию адресов. Снаружи контейнера как отдельного устройства не видно - виден только хост и опубликованные порты.
Практическое следствие: чтобы к сервису в контейнере пришли снаружи, порт надо явно опубликовать. Забытая публикация - одна из самых частых причин «контейнер работает, но недоступен». Обратная ситуация тоже встречается: опубликовали порт на все интерфейсы, и внутренний сервис неожиданно оказался доступен из внешней сети.
В оркестраторах поверх этого работает свой сетевой слой, который даёт каждому набору контейнеров собственный адрес и внутреннее имя. Но принцип остаётся: у виртуальной машины сетевая идентичность своя, у контейнера - заимствованная у хоста, пока её явно не выдали.
Ответ на вопрос «что выбрать» на реальном сервере обычно звучит как «оба, слоями».
Типичная конфигурация небольшой компании: на железе стоит гипервизор, на нём несколько виртуальных машин, и уже внутри одной-двух из них живут контейнеры. Гипервизор даёт границу между контурами - бухгалтерия отдельно, разработка отдельно, - а контейнеры внутри контура дают скорость выкатки и плотность.
Так делают не из любви к сложности, а по практическим причинам. Виртуальные машины умеют то, чего контейнеры не умеют: снимки состояния, живую миграцию между узлами, резервное копирование целиком одним файлом. Контейнеры умеют то, чего не умеют машины: подниматься за секунду и занимать мегабайты. Слоёная схема берёт и то и другое.
Разговор про «накладные расходы» часто ведут в процентах, но полезнее понимать механику - тогда видно, где разница есть, а где её нет.
Процессор. Здесь разницы почти нет ни у той, ни у другой технологии. Код виртуальной машины выполняется на настоящем процессоре при поддержке аппаратной виртуализации, код в контейнере - это вообще обычный процесс. Если приложение считает, оно считает с одинаковой скоростью.
Память. Вот здесь разница принципиальная. Каждой виртуальной машине нужен свой комплект операционной системы, и это память, потраченная не на приложение. Десять машин - десять ядер, десять наборов служб. Десять контейнеров пользуются одним ядром хоста, и в память попадает только полезная нагрузка.
Диск. У контейнеров образы собираются слоями, и общие слои переиспользуются: двадцать контейнеров на одном базовом образе не займут двадцать его копий. У виртуальных машин каждый диск самостоятелен, хотя платформы умеют экономить связанными клонами.
Ввод-вывод. Тут всё зависит от настройки. Виртуальная машина с паравиртуальными драйверами работает с диском и сетью почти как физическая; с эмулированными - заметно медленнее. У контейнера доступ к диску прямой, но слоёная файловая система добавляет свои накладные расходы на запись, поэтому нагруженные данные выносят в тома.
Общий вывод для планирования: контейнеры экономят память и дисковое пространство. Процессорное время они не экономят - если узел упирается в процессор, переход на контейнеры этого не изменит.
| Задача | Что брать | Почему |
|---|---|---|
| Несколько сервисов одного приложения, частые обновления | Контейнеры | Быстрая сборка и выкатка, откат за секунды |
| Разные операционные системы на одном сервере | Виртуальные машины | Своё ядро у каждой |
| Изоляция контуров по требованиям безопасности | Виртуальные машины | Граница на уровне гипервизора |
| Старое приложение «как есть» | Виртуальная машина | Не требует переупаковки |
| Тестовые стенды, которые часто создают и удаляют | Контейнеры | Секунды вместо минут, мегабайты вместо гигабайт |
| Инфраструктура компании целиком | Оба, слоями | Гипервизор для границ, контейнеры внутри для скорости |
Вопрос возникает почти сразу после первого удачного опыта с контейнерами. Ответ зависит не от технологии, а от самого приложения.
Переезд оправдан, когда приложение не хранит состояние на локальном диске (или его легко вынести), настраивается переменными окружения или конфигом, пишет журналы в стандартный вывод, обновляется часто и запускается в нескольких копиях.
Переезд не оправдан, когда приложение хранит данные вперемешку с собой, требует графической консоли, лицензируется по параметрам железа, нуждается в специфических модулях ядра или ставится инсталлятором, который тянет за собой половину системы. Такое проще оставить в виртуальной машине, и это нормальное инженерное решение.
Промежуточный вариант, который часто оказывается лучшим: переносить в контейнеры не всё приложение, а его новые части. Старое ядро системы остаётся в машине, новые сервисы вокруг него собираются в контейнерах. Так делают чаще, чем полные переезды, и рисков в этом заметно меньше.
Общая формулировка «обе технологии изолируют приложения» верна, но скрывает существенную разницу.
У виртуальной машины граница проходит по гипервизору и поддерживается процессором. Выйти за неё сложно, и такие уязвимости - события заметные.
У контейнера граница проходит по ядру, и ядро одно на всех. Отсюда практические правила, которые в облачных статьях обычно опускают:
Виртуальная машина хранится файлом-диском: VMDK у VMware, QCOW2 у QEMU и KVM, VHDX у Hyper-V. Файл можно скопировать, положить в резерв, перенести на другой узел. Общее хранилище для нескольких машин делают на NFS, iSCSI или кластерном томе - в зависимости от платформы.
У контейнеров устроено иначе. Сам контейнер по умолчанию не хранит ничего: остановили и удалили - изменения внутри пропали. Это не недостаток, а осознанное свойство: контейнер должен быть заменяемым. Данные выносят наружу:
Образы хранятся в реестрах. И здесь есть момент, который стоит учитывать при планировании: доступ к публичному Docker Hub из России ограничен, ограничения менялись несколько раз с 2024 года и по-разному работают у разных провайдеров. Практический вывод простой - не строить сборку и выкатку на прямых обращениях к Docker Hub. Рабочие варианты: настроить зеркала, использовать реестры российских облаков или поднять собственный реестр внутри периметра. Последнее полезно и без всяких блокировок: свой реестр быстрее, не зависит от чужой доступности и даёт контроль над тем, что именно попадает в прод.
Виртуальную машину обновляют изнутри, как обычный сервер: ставят пакеты, перезагружают. Машина остаётся той же, со всей накопленной историей - и с накопленным мусором тоже.
Контейнер не обновляют. Его заменяют: правят описание сборки, собирают новый образ, кладут в реестр, перезапускают контейнер из нового образа. Старый удаляют. Если новая версия сломалась, откат - это запуск предыдущего образа, то есть секунды.
Разница в подходе принципиальная и объясняет, почему контейнеры прижились в разработке. Виртуальная машина - это питомец, которого лечат. Контейнер - расходник, который меняют.
Способ запустить приложение в изолированном окружении, не поднимая для этого отдельную операционную систему. Приложение вместе со всеми библиотеками упаковывается в образ, а ядро берётся у сервера, на котором образ запускают.
Ради скорости и плотности. Там, где виртуальная машина стартует минуту и занимает гигабайты, контейнер поднимается за секунду и занимает мегабайты. На одном узле их помещается на порядок больше.
На двух механизмах ядра Linux. Пространства имён дают процессу отдельное представление о файловой системе, сети, списке процессов. Контрольные группы ограничивают, сколько процессора, памяти и дискового ввода-вывода он может занять.
Нет. У виртуальной машины своё ядро и своя операционная система, у контейнера - ни того ни другого, он пользуется ядром хоста. Термин «контейнерная виртуализация» встречается, но описывает он виртуализацию на уровне операционной системы.
Да, и чаще всего так и делают. Гипервизор задаёт границы между контурами, контейнеры внутри дают скорость выкатки.
Виртуальная машина: её граница держится на гипервизоре и процессоре. У контейнеров ядро общее, поэтому там, где изоляция критична, контуры разносят по машинам.
Нет. Docker запускает контейнеры на одном узле. Kubernetes управляет контейнерами на множестве узлов: раскладывает, перезапускает, масштабирует. Одно не заменяет другое.
Слоёная схема - гипервизор снаружи, контейнеры внутри - требовательнее к памяти и дискам, чем кажется на этапе планирования: под каждую виртуальную машину уходит свой комплект системы, а образы и тома контейнеров растут быстро. Если собираете узел под такую конфигурацию, посмотрите готовые сборки в разделе сервер с виртуализацией или напишите нам - рассчитаем память и дисковую подсистему под ваше число машин и контейнеров.
ITTELO, 11+ лет на рынке серверов.
Телефон: +7 (800) 551-80-12
Почта: info@ittelo.ru