Чим контейнер відрізняється від віртуальної машини (VM)?
Container і Virtual Machine обидва ізолюють робочі навантаження, але роблять це на абсолютно різних рівнях стеку. Container це процес на kernel хоста; VM це повна операційна система, що крутиться на hypervisor.
Теорія
TL;DR
- Container = ізольований процес на kernel хоста через Linux namespaces і cgroups. ~10 MB оверхеду, стартує за мілісекунди.
- VM = повна гостьова OS на hypervisor (KVM, VMware, Hyper-V). Сотні MB лише на guest kernel і init, стартує за секунди.
- Щільність на 128 GB сервері: 100-200 container vs 10-15 VM з того самого заліза.
- Ізоляція: VM сильніша за замовчуванням (окремий kernel на tenant). Container поділяє kernel з хостом, тому kernel CVE потенційно зачіпає всі container на машині.
- Беремо container, коли треба app-level пакування, швидкі деплої, щільність microservices. Беремо VM, коли треба жорстка ізоляція, інша OS (Windows guest на Linux host), або legacy-софт, що очікує повну машину.
Швидкий приклад
# Container: від "нічого на диску" до робочого веб-сервера за ~2 секунди
$ time docker run -d -p 80:80 nginx
real 0m1.842s
# VM: той самий nginx, але гостьова OS має спочатку завантажитися
$ time vagrant up
real 0m38.504s
# (і це вже після того, як box image скачаний)Той самий софт, той самий результат (порт 80 з nginx). Різниця у два порядки, бо container перевикористовує твій kernel, а VM привезла свій.
Головна різниця
Container фундаментально це процес хоста з обмеженою видимістю. Linux kernel робить роботу ізоляції через namespaces (окремі в'юхи процесів, мережі, файлової системи, користувачів) і cgroups (ліміти CPU, пам'яті, I/O). VM, навпаки, використовує hypervisor для віртуалізації самого заліза: кожна VM думає, що має свої CPU, RAM і диск, і запускає повноцінну гостьову OS поверх. Тому одне вимірюється у мегабайтах і мілісекундах, а інше у сотнях мегабайт і секундах.
Таблиця порівняння
| Аспект | Container | Virtual Machine |
|---|---|---|
| Примітив ізоляції | Linux namespaces + cgroups | Hypervisor (KVM, VMware, Hyper-V) |
| Гостьова OS | Немає, поділяє kernel хоста | Повна гостьова OS зі своїм kernel |
| Час старту | Мілісекунди | Секунди (після того, як image локальний) |
| Оверхед пам'яті | ~10 MB baseline на container | Сотні MB на guest OS + застосунки |
| Щільність (128 GB host) | 100-200 інстансів | 10-15 інстансів |
| Сила ізоляції | Process-level, слабша | Hardware-level, сильніша |
| Розмір image | Десятки MB до кількох GB | Кілька GB мінімум (повна OS) |
| Інша OS, ніж у хоста | Ні (поділяє Linux kernel) | Так (Windows guest на Linux host тощо) |
| Радіус ураження при збої | Kernel CVE впливає на всі container | Одна VM може впасти, інші продовжують |
| Типовий час життя | Секунди-дні (замінні) | Місяці (довгоживучі сервери) |
| Найкраще для | Microservices, CI/CD, швидкі деплої | Multi-tenant clouds, інша OS, legacy |
Коли container правильний вибір
- Ти шипиш багато малих сервісів, що поділяють однакову Linux-основу. Process-level ізоляція достатня, hardware-level зайва.
- Частота деплоїв висока: PR збирає image, CI запускає проти нього, прод замінює. Швидкий старт container тримає цикл швидким.
- Потрібен паритет середовища від лептопа до проду, і ти контролюєш kernel хоста.
- Щільність важлива: впихнути 100 сервісів на одну машину, масштабуватися горизонтально.
Коли VM все ще правильний вибір
- Multi-tenant інфраструктура, де ти не довіряєш робочим навантаженням. Kernel-експлойт у container це tenant-crossing проникнення; у VM воно стримане.
- Інша OS, ніж у хоста. Windows на Linux-хості потребує VM. Container так не вміє.
- Жорсткі compliance-режими (PCI DSS, частина healthcare), що вимагають hardware-level ізоляції.
- Legacy-софт, написаний під повну машину: він очікує systemd, справжній init, kernel-модулі, raw block-пристрої.
На практиці більшість продакшен-сетапів використовують обидва. AWS EC2 і подібні хмари знизу крутяться на VM; застосунки всередині цих VM запускаються як container на Kubernetes. VM дає cloud-провайдеру tenant ізоляцію, container дає команді щільність і швидкість.
Як ізоляція реально працює
Для container Linux kernel тримає окремі namespaces на процес. PID namespace змушує container думати, що process 1 це його власний init; mount namespace дає йому свою в'юху файлової системи; network namespace дає приватні інтерфейси. Cgroups обліковують і лімітують CPU, пам'ять, I/O. Все це enforce'ить один kernel: твій.
Для VM hypervisor сидить між залізом і гостем. KVM (Linux), VMware ESXi або Hyper-V віртуалізують CPU, пам'ять, пристрої. Гостьова OS бутиться нормально, ніби на справжньому залізі, з власним kernel, init, драйверами. Дві VM на одному фізичному хості не бачать пам'яті одна одної, бо hypervisor enforce'ить hardware-level межі.
Цей додатковий шар і є причиною того, чому VM повільніші і важчі, і чому ізоляція у них сильніша.
Типові помилки
«Container це легка VM»
Ні. Container це процес хоста з додатковими обмеженнями. Немає guest kernel, немає init у традиційному сенсі, немає віртуалізованого заліза. Ставлення до container як до маленької VM (запуск кількох сервісів через systemd всередині одного container, наприклад) суперечить дизайну і створює проблеми з логуванням, сигналами і заміною.
Брати VM для ефемерних навантажень
# НЕПРАВИЛЬНО: піднімати VM під кожен CI-білд
# (5-10 хвилин на boot втрачено в кожному пайплайні)
# ПРАВИЛЬНО: container для короткоживучих навантажень
docker run --rm node:22 npm test
# Готово за секунди, нічого не залишилось.VM створювалися для довгоживучих серверів. Використання одної на 30-секундний тестовий ран витрачає більшу частину її життя на boot.
Довіряти container-ізоляції для multi-tenant навантажень
Якщо ти запускаєш недовірений код від різних клієнтів на одному хості, тільки container не вистачить. Постав VM-межу між tenant'ами (gVisor і Firecracker це проміжні варіанти, що додають тонкий VM-шар саме для цього). Звичайний Docker на shared host, де один tenant поряд з іншим, це один kernel CVE від проникнення.
Плутати розмір image з оверхедом
Docker image на 1 GB не з'їдає 1 GB RAM. Шари поділяються на диску і в пам'яті між container від того самого image. Три container з того самого image на 1 GB все ще використовують одну копію шарів. З VM так не виходить: кожна отримує свій disk image, свою пам'ять.
Реальне застосування
- AWS EC2: інстанс, що ти орендуєш, це VM (Xen або Nitro hypervisor). Container-платформи поверх (ECS, EKS) кладуть твої container всередину цих VM. Два шари ізоляції: VM між tenant'ами, container між застосунками.
- Firecracker (AWS Lambda, Fargate): microVM, заточений під container-подібний старт. ~125 мс на boot, ~5 MB оверхеду. Дає Lambda-функціям VM-level ізоляцію без VM-level затримки.
- Google Cloud Run: container під капотом, але кожен запит отримує sandbox-середовище. Той самий патерн: швидкість container, посилена ізоляція.
- Локальна розробка: container всюди, VM майже ніколи. Docker Desktop на Mac насправді крутить маленьку Linux VM за лаштунками, бо container'ам потрібен Linux kernel, але ти цього не бачиш.
Питання для поглиблення
Q: Якщо container слабші на ізоляції, чому хтось взагалі використовує їх у multi-tenant сетапах?
A: Більшість і не використовують, принаймні не наївно. Публічні хмари кладуть container всередину per-tenant VM (або microVM типу Firecracker), щоб поєднати швидкість container з VM-level межами. Всередині одного tenant, де ти довіряєш своїм навантаженням, звичайні container нормально.
Q: Що таке microVM і як він заходить між container і повною VM?
A: microVM це урізана VM, оптимізована під швидкий boot і мінімальний оверхед, типово 100-200 мс старту і кілька MB RAM. Firecracker (AWS) і Cloud Hypervisor добре відомі приклади. Кейс саме у проміжку між container і VM: коли треба сильніша ізоляція ніж у container, але час boot повної VM неприйнятний.
Q: Чи можна запускати container на Windows або Mac, якщо обом потрібен Linux kernel?
A: Так, але прихована Linux VM робить роботу. Docker Desktop на macOS використовує крихітну Linux VM через macOS virtualization framework. Windows може крутити Linux container через WSL 2 (теж Linux VM) або Windows-native container (що використовують Windows kernel features замість Linux).
Q: Чому VM іноді здаються швидшими за container у бенчмарках?
A: Зазвичай для прикладних навантажень не швидші, але кілька кейсів існує. Якщо навантаження I/O-важке, і container використовує network-mounted volume, а VM локальний віртуальний диск, VM може виграти. Змінна тут шлях до сховища, а не примітив ізоляції.
Q: (Senior) Коли б ти обрав Kata Containers, gVisor або Firecracker замість стандартного runc?
A: Коли навантаження вимагає сильнішої ізоляції за namespaces+cgroups, але ти все ще хочеш container UX. gVisor перехоплює системні виклики у userspace (добре для зменшення радіусу ураження, повільніше для syscall-важких застосунків). Kata запускає кожен container у легкій VM (близько до нативної швидкості, повна kernel-ізоляція). Firecracker спеціально під serverless, використовується Lambda і Fargate. Рішення зазвичай зводиться до: недовірений код → microVM-class ізоляція; довірений код → стандартний runc.
Приклади
Те саме навантаження у двох світах
# Container шлях: легко і швидко
$ docker run -d --name redis-c -p 6379:6379 redis:7
# Стартонув за ~1 секунду. Пам'ять: ~15 MB.
# VM шлях: повний гість, повний boot
$ vagrant init ubuntu/jammy64
$ vagrant up
$ vagrant ssh -c "sudo apt-get install redis && sudo systemctl start redis"
# Сумарно: 60+ секунд, сотні MB на гостьову OSТой самий Redis, той самий порт, той самий результат. Container поділяє твій kernel і перевикористовує базові шари. VM привозить свій kernel, init, package manager і idle-демони ще до того, як redis запуститься.
Шарова архітектура: VM + container
+-------------------------------+
| Фізичний сервер (128 GB RAM) |
| |
| +-----------+ +-----------+ |
| | VM A | | VM B | | <- KVM hypervisor ізолює tenant
| | (Tenant 1)| | (Tenant 2)| |
| | | | | |
| | +-------+ | | +-------+ | |
| | |Cont. 1| | | |Cont. 1| | | <- Docker ізолює застосунки в tenant
| | |Cont. 2| | | |Cont. 2| | |
| | +-------+ | | +-------+ | |
| +-----------+ +-----------+ |
+-------------------------------+Так виглядає більшість публічних cloud-Kubernetes сетапів. Hypervisor enforce'ить tenant-межу; Docker (або containerd) enforce'ить app-межу. Ти отримуєш VM-level ізоляцію між клієнтами і container-level щільність всередині слайсу кожного клієнта.
Як обрати на співбесіді
Відповідь, яку хочуть почути, рідко це «завжди container» або «завжди VM». Це decision rule:
- Довірені навантаження на спільній інфраструктурі → container
- Недовірені навантаження (публічний PaaS, multi-tenant clouds) → VM або microVM-межа між tenant, container всередині
- Інша OS, ніж у хоста → VM
- Legacy-софт, що очікує реальну машину → VM
- Все інше зі спільною Linux-основою → container
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.
Коментарі
Ще немає коментарів