Learning Platform
Глоссарий Troubleshooting
Урок 03.03 · 20 мин
Начальный
dockervmvirtualizationcontainers

VM vs контейнер

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

В этом уроке посмотрим, что внутри VM и контейнера, чем они отличаются по ресурсам, и когда какой инструмент правильный.


Архитектура: что внутри

VM vs контейнер: что находится между приложением и железом
VM стекСлева — стек виртуальной машины. Внизу железо, далее hypervisor, далее N гостевых ОС со своими ядрами, далее приложения
Контейнер стекСправа — стек контейнера. Внизу железо, далее одна хостовая ОС с одним ядром, далее N контейнеров (процессов с namespaces) с приложениями
App + libs + guest OS + guest kernelВ VM каждый guest несёт свой kernel (Linux/Windows), свою init-систему (systemd), свои библиотеки. Это полная ОС
App + libs (image layers)В контейнере нет kernel — он один на хосте. Контейнер тащит только userspace: libc, приложение, его зависимости
Hypervisor (KVM/Hyper-V/ESXi)Промежуточный слой, который эмулирует железо для гостевых ОС. Жирный слой, требующий привилегий ring -1 / VT-x
Container runtime (runc)Тонкий слой, который делает clone() + namespaces + cgroups. Не эмулирует ничего. Это userspace утилита
Host OS + kernelХостовая ОС с ядром, на котором крутится hypervisor (или который сам является hypervisor типа 1)
Host OS + kernelТо же — хостовая ОС с ядром, которое ВСЕ контейнеры разделяют. Одно ядро на всех

Главное отличие — уровень виртуализации:

Kernel space vs user space — ring 0 и ring 3
  • VM виртуализирует железо. Гостевая ОС думает, что у неё свой CPU, своя RAM, свои диски. Под капотом hypervisor транслирует это всё на реальное железо хоста.
  • Контейнер виртуализирует userspace вид ядра. Процесс думает, что он один в системе, у него свой PID 1 и свой /. Но ядро одно — хостовое.

Из этого следует всё остальное.


Накладные расходы — RAM, диск, время старта

Числа примерные и зависят от софта внутри, но порядок такой:

ПараметрVM (Ubuntu 24.04 minimal)Контейнер (alpine с одной программой)
Disk overhead800-1500 MB5-30 MB
RAM overhead256-512 MB5-20 MB (только сам процесс)
Boot time15-60 секунд100-500 миллисекунд
Density (на 16 ГБ RAM хосте)~10-20 VM~500-2000 контейнеров

Это объясняет, почему весь cloud-нативный мир (Kubernetes, CI-раннеры, serverless под капотом) построен на контейнерах: можно поднять и опустить контейнер за полсекунды, а VM за это время не успеет даже начать загрузку.

Memory hierarchy — registers, cache, RAM, disk и реальная latency

Конкретный пример: GitHub Actions раннер крутит каждый job в отдельной VM. Время старта VM — около 30 секунд из 5-минутного бюджета job’а, это 10%. Если бы это был контейнер — было бы 1%.


Изоляция: VM сильнее

Контейнер слабее изолирован, чем VM. Это фундаментально.

В VM граница между гостем и хостом проходит через hypervisor. Чтобы вырваться из VM, нужно эксплуатировать уязвимость hypervisor’а — это редкие, дорогие баги, обычно с CVE на полгода работы команды Red Hat / VMware.

В контейнере граница проходит через namespaces в ядре. Уязвимость в ядре = выход из контейнера. Уязвимостей в Linux kernel находят десятки в год. Многие из них так или иначе могли быть использованы для container escape.

Поэтому:

  • Для multi-tenant сценариев (запуск кода разных клиентов на одной машине, как у AWS Lambda или CI типа GitHub Actions для public репозиториев) — обычно используется VM, или специальный изолирующий runtime (gVisor, Kata Containers, Firecracker), который запускает контейнер внутри минимальной VM.
  • Для single-tenant (свой проект, свой Postgres, свой Airflow на своём сервере) — контейнеры безопасны достаточно.

Когда нужна именно VM

Несмотря на повсеместное распространение контейнеров, VM остаются нужны в нескольких случаях:

Случаи, когда VM остаётся правильным выбором
Windows guestLinux-контейнер не запустит Windows-приложение. Windows-контейнеры существуют, но это отдельная история и работают они только на Windows-хосте
Свой kernelЕсли приложению нужен специфический kernel (старая версия для legacy ПО, или kernel module который не загрузишь на хосте) — нужна VM
Kernel-driverЕсли приложение содержит kernel module (драйвер железа, kernel-level security) — контейнер не подойдёт, нужна VM с полным контролем над ядром
Strong isolationМногоарендный сценарий (multi-tenant), где нельзя доверять коду клиентов. Cloud providers используют micro-VM (Firecracker) — это компромисс между VM и контейнером
macOS dev (Docker Desktop, OrbStack)На macOS Docker крутит контейнеры внутри одной Linux-VM. Это деталь хоста, но факт — на mac за контейнерами стоит VM
Полная ОС с сервисамиЕсли тебе нужен полноценный init, набор сервисов, и привычная Linux-машина — иногда проще одна VM, чем 10 контейнеров и orchestrator

Когда контейнер достаточно

Большинство современных задач разработки и production отлично решаются контейнерами. Особенно для Data Engineer:

  • Локальный Postgres для разработки. docker run -d -p 5432:5432 postgres:16.4 — есть Postgres за 5 секунд. VM здесь будет overkill.
  • CI integration tests. testcontainers-python поднимает Postgres/Kafka/Redis для теста, тест работает, контейнеры уезжают. С VM это было бы 20 секунд старта на каждый тест.
  • Airflow worker. Один контейнер = один процесс worker’а. Несколько контейнеров = горизонтальное масштабирование.
  • ETL-задача как образ. Build однажды, deploy куда угодно (k8s, ECS, Airflow KubernetesPodOperator).
  • Reproducible dev environments. Один docker-compose up — и у всей команды одинаковое окружение, неважно у кого Mac, у кого Linux.

Гибрид: VM-внутри-контейнера и наоборот

В реальности часто стек получается комбинированный:

  • macOS Docker Desktop / OrbStack — Docker-контейнеры запускаются внутри Linux VM (потому что macOS не имеет Linux kernel). Когда ты на маке запускаешь docker run, на самом деле контейнер крутится в VM, которую ты не видишь.
  • AWS Fargate, Google Cloud Run — managed контейнерные платформы, под капотом используют micro-VM (Firecracker на AWS) для изоляции между разными клиентами.
  • gVisor, Kata Containers — runtime’ы, которые запускают контейнеры в очень лёгких VM (~100 МБ overhead вместо 500 МБ). Используются там, где нужна и легкость контейнеров, и сильная изоляция.
  • k8s nodes — это VM, на которых крутятся контейнеры. Облачный k8s — это GCP/AWS-VM с containerd внутри.

То есть на практике «VM vs container» — это не дихотомия, а слои. Снизу часто VM (для isolation и multi-tenancy), сверху контейнеры (для гибкости и плотности).


Попробуй сам

Если у тебя macOS с Docker Desktop или OrbStack, посмотри, сколько ресурсов берёт Linux VM, в которой крутятся контейнеры:

# OrbStack
orb info

Или для Docker Desktop — открой settings -> Resources, увидишь, сколько RAM и CPU выделено VM.

Запусти 10 контейнеров nginx:

for i in {1..10}; do
  docker run -d --rm --name nginx-$i nginx:alpine
done
docker stats --no-stream

Посмотри MEM USAGE — каждый nginx ест ~3-8 МБ. Это и есть «плотность контейнера»: 10 одинаковых nginx занимают меньше памяти, чем одна Linux VM с одним nginx внутри.

Убери:

docker rm -f $(docker ps -q --filter "name=nginx-")

Проверка знанийKnowledge check
Почему контейнеры стартуют на порядок быстрее VM и потребляют меньше памяти?
ОтветAnswer
Главная причина — отсутствие отдельного ядра. VM полностью эмулирует железо: hypervisor запускает гостевую ОС с её собственным kernel, init-системой (systemd), набором базовых сервисов. Это означает 256-512 МБ overhead на каждую VM и 15-60 секунд на загрузку. Контейнер же — это просто процесс хостового ядра с обёртками namespaces и cgroups. Нет загрузки ядра, нет init-системы, нет эмуляции железа. Контейнер стартует за время, нужное на clone() + монтирование overlayfs + exec() главного процесса — обычно 100-500 миллисекунд. Память тратится только на сам процесс + его зависимости в userspace, без накладных расходов на kernel guest. На 16 ГБ хосте можно поднять 10-20 VM или 500-2000 контейнеров — отсюда плотность и применимость в cloud-native подходе.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 5. Главное архитектурное отличие контейнера от виртуальной машины:

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5