Контейнеризация против виртуализации: метрики производительности
Разница в производительности между контейнерами и виртуальными машинами определяется не только скоростью CPU.

В большинстве типовых нагрузок Docker приближается к bare-metal по вычислениям и потреблению памяти, тогда как KVM добавляет отдельный слой guest OS, виртуальные устройства и расходы гипервизора.
На практике архитектурный выбор чаще упирается в четыре метрики:
- CPU overhead;
- потребление RAM;
- latency сети;
- скорость дискового I/O.
Контейнеризация vs виртуализация по производительности приложений не сводится к тезису «Docker быстрее KVM». Контейнеры выигрывают в плотности размещения и времени запуска. Виртуальные машины дают более сильную изоляцию и поддерживают отдельные операционные системы. При этом контейнеры могут терять преимущество на сетевых и дисковых операциях без настройки runtime, драйвера хранения и сетевой схемы.
Контейнеры экономят ресурсы за счёт общего kernel. Виртуальные машины платят за независимую guest OS.
Архитектурная разница: kernel против гипервизора
Контейнер — это изолированный процесс на уровне операционной системы. Docker использует kernel хоста, namespaces и cgroups. Внутри контейнера нет отдельного ядра и полноценной guest OS. Образ содержит application runtime, библиотеки и зависимости, но не второй экземпляр Linux kernel.
Виртуальная машина работает иначе. KVM предоставляет гостевой системе виртуальный CPU, память, диски, сетевые устройства и другие hardware abstractions. Внутри ВМ запускается самостоятельная операционная система. Даже если приложение ещё не стартовало, guest OS уже занимает RAM и дисковое пространство.
Это различие определяет базовую себестоимость запуска:
| Параметр | Docker-контейнер | ВМ на KVM |
|---|---|---|
| Kernel | Общий kernel хоста | Отдельный kernel guest OS |
| Guest OS | Не требуется | Требуется |
| Изоляция | Process-level isolation | Hardware virtualization |
| RAM до запуска приложения | Низкая, зависит от процесса и runtime | Для Debian guest OS обычно 512 МБ–2 ГБ |
| Диск до запуска приложения | Образ и writable layer | Около 10–32 ГБ под guest OS |
| Время запуска | Обычно секунды и меньше, зависит от image и приложения | Дольше из-за boot guest OS |
| Запуск другой ОС | Ограничен kernel хоста | Возможен при наличии совместимой виртуализации |
| Плотность размещения | Высокая | Ниже из-за overhead каждой ВМ |
KVM не является медленным по определению. В ряде нагрузок он передаёт инструкции CPU почти напрямую и использует hardware virtualization. Но виртуализация должна обслуживать не только приложение. Она обслуживает guest kernel, виртуальные устройства, memory mapping и I/O path.
В тестах IBM контейнеры демонстрировали производительность CPU и памяти, близкую к bare-metal, и опережали KVM в большинстве рассмотренных сценариев. Это ожидаемый результат для задач, где основная нагрузка приходится на вычисления и память, а приложение не требует сложной виртуальной аппаратной конфигурации.
Отдельный случай — специфические вычисления. В исследовании IBM на Linpack для KVM фиксировалось снижение производительности относительно bare-metal примерно до 50% в отдельных сценариях. Это не универсальный коэффициент потерь для всех приложений. Он показывает другое: усреднённая оценка «overhead около нескольких процентов» неприменима к каждой нагрузке.
CPU и RAM: где контейнеры получают основной выигрыш
CPU overhead контейнеров обычно ниже, потому что процесс выполняется непосредственно в kernel-среде хоста. Между приложением и физическим CPU нет полноценной guest OS. cgroups ограничивают и распределяют CPU time, но не создают отдельный вычислительный контур с собственной системой планирования.
Для большинства stateless-сервисов это даёт предсказуемый профиль:
- application process использует ядра хоста;
- лимиты CPU задаются через cgroups;
- нет постоянного расхода RAM на guest kernel;
- не требуется резервировать память под idle-состояние отдельной ОС;
- масштабирование выполняется запуском новых экземпляров процесса.
ВМ должна содержать минимально работоспособную операционную систему даже в том случае, если целевое приложение использует небольшую долю выделенных ресурсов. Базовая Debian ВМ может занимать от 512 МБ до 2 ГБ RAM и от 10 до 32 ГБ диска ещё до запуска application stack.
Это влияет на плотность размещения. При одинаковом объёме RAM контейнеров на том же оборудовании можно разместить значительно больше. В фактуре приводится преимущество по плотности до 10 раз и более. Однако это значение нельзя переносить на любой кластер. Оно зависит от:
- размера guest OS;
- резервов памяти hypervisor;
- лимитов и requests контейнеров;
- количества sidecar-процессов;
- logging и monitoring agents;
- page cache;
- характера нагрузки;
- требований к отказоустойчивости.
Плотность контейнеров не равна полезной пропускной способности. Если на один физический узел разместить слишком много workloads, начнут конкурировать не только CPU, но и memory bandwidth, storage queue и network interrupts. Высокая плотность без capacity planning превращается в resource contention.
Что происходит при росте числа экземпляров
В контейнерной модели новый экземпляр приложения добавляет:
- process tree;
- memory footprint самого приложения;
- writable layer;
- сетевой namespace;
- служебные структуры runtime;
- логирование и метрики.
В модели ВМ новый экземпляр добавляет полный bootable environment:
- guest kernel;
- init system;
- system services;
- виртуальные устройства;
- файловую систему guest OS;
- page cache;
- управляющие агенты.
Поэтому container orchestration хорошо подходит для микросервисов, CI/CD и burst-сценариев. Изменение версии сервиса сводится к замене image и запуску нового workload. ВМ требует управления образом диска, состоянием guest OS, обновлениями и патчами внутри каждой системы.
Но shared kernel создаёт ограничение. Linux-контейнер не становится универсальной заменой ВМ для Windows workload или другой операционной системы с несовместимым kernel. Если архитектура требует запуска Windows на Linux-хосте, аппаратной виртуализации или жёсткой изоляции tenant-сред, KVM остаётся отдельным классом решения.
Сеть: NAT убирает часть преимущества Docker
Сетевой overhead часто недооценивают. В простом CPU benchmark контейнер может показывать результат, близкий к bare-metal. В production path приложение работает через virtual Ethernet pair, bridge, firewall rules, service discovery и, нередко, NAT.
Docker-публикация порта через docker run -p добавляет обработку Network Address Translation. Пакет проходит дополнительный путь. Это увеличивает latency и создаёт CPU overhead на сетевом стеке. На небольшом числе запросов разница может быть незаметной. На высоком packet rate она становится частью capacity model.
Режим --net=host устраняет NAT overhead. Контейнер использует сетевой стек хоста. Это сокращает путь передачи пакетов, но меняет модель изоляции:
- контейнер получает доступ к network namespace хоста;
- повышаются требования к контролю портов;
- усложняется совместное размещение сервисов;
- часть преимуществ network isolation теряется;
- конфликт портов становится ответственностью deployment-модели.
Выбор режима зависит от профиля нагрузки. Для сервиса с низкой сетевой активностью bridge network обычно рациональнее. Для высокочастотного обмена с жёстким latency budget host networking может дать измеримый эффект. Универсального правила нет, потому что результат определяется не названием технологии, а конкретным network path.
Сравнивать Docker и KVM по задержке сети нужно на одинаковых условиях. Иначе результат смешивает несколько факторов:
1. тип виртуального адаптера;
2. режим NAT или bridge;
3. MTU;
4. правила firewall;
5. virtio-конфигурацию;
6. packet size;
7. число соединений;
8. TLS termination;
9. service mesh;
10. physical NIC и interrupt handling.
В KVM сетевой overhead возникает из-за виртуального устройства и переходов между guest и host. В Docker — из-за network namespace, bridge, NAT и правил маршрутизации. В обоих случаях результат можно изменить настройкой. Поэтому значение имеет не только runtime, но и способ подключения workload к сети.
Сетевой benchmark без фиксации режима NAT, bridge и host network не даёт архитектурного вывода.
Дисковый I/O: слабое место контейнерной модели
Для CPU-bound workloads контейнеры обычно близки к физическому хосту. Дисковый I/O сложнее. Docker использует storage driver и copy-on-write filesystem. Конкретный путь зависит от OverlayFS, ZFS, devicemapper или другого backend. Поведение меняется при чтении, последовательной записи, random write, создании большого числа файлов и работе с database workload.
Основной источник overhead — writable layer контейнера. Запись проходит через механизм copy-on-write. При изменении файла может потребоваться копирование данных из нижнего read-only слоя в writable layer. Это не означает, что любой файл в контейнере будет работать медленно. Но для интенсивного transactional I/O модель должна быть определена заранее.
Для stateful-сервисов обычно применяют volume, подключённый к хостовой файловой системе или отдельному storage backend. Это позволяет вынести данные из ephemeral layer контейнера. Но volume не делает I/O автоматически эквивалентным bare-metal. Остаются:
- filesystem overhead;
- queue depth;
- caching;
- storage driver;
- тип диска;
- sync semantics;
- параметры mount;
- latency внешнего хранилища.
Исследование Zheng Li и коллег из Lund University показывает, что Docker может уступать по скорости storage transactions без дополнительной настройки. Это принципиальный нюанс. Контейнеризация не гарантирует ускорение дисковых операций только потому, что у процесса нет guest OS.
Как интерпретировать I/O benchmark
Скорость последовательного чтения не описывает поведение базы данных. Для архитектурного решения нужны разные профили:
- random read;
- random write;
- sequential read;
- sequential write;
- fsync latency;
- latency при насыщении queue;
- операции с большим числом мелких файлов;
- параллельные транзакции;
- восстановление после сбоя.
Результат должен фиксироваться в единицах, связанных с приложением:
- IOPS;
- throughput;
- p95 и p99 latency;
- transaction rate;
- error rate;
- время восстановления.
Универсальных миллисекундных значений для Docker и KVM нет. Они зависят от SSD или NVMe, драйвера, файловой системы, storage backend и режима монтирования. Нельзя переносить результат одного теста на любой production cluster.
Для database workload архитектурная схема обычно выглядит так:
- контейнер используется для упаковки database process;
- данные хранятся на отдельном persistent volume;
- backup и replication не зависят от writable layer;
- latency измеряется на целевой storage-системе;
- I/O limits задаются отдельно от CPU limits;
- recovery time проверяется как самостоятельная метрика.
ВМ, в свою очередь, может использовать virtual disk на thin или thick provisioning, virtio-драйвер и отдельный storage pool. Слой виртуализации добавляет overhead, но изоляция диска и guest OS бывает проще для операционного управления. Это особенно актуально, когда требуется существующая модель backup, snapshot и disaster recovery для целой ВМ.
Время запуска и эффективность CI/CD
Время запуска — отдельная метрика, не сводимая к CPU overhead. Контейнер стартует после загрузки image, создания namespace, подключения filesystem layer и запуска процесса. ВМ должна пройти boot guest OS, инициализацию system services и запуск приложения.
Для autoscaling это различие критично. Контейнеры подходят для сценариев, где требуется быстро добавить replicas. ВМ эффективнее в моделях, где workload живёт долго, а скорость масштабирования не является главным ограничением.
В автоматизации веб-интерфейсов Docker также показывает практическое преимущество. В тестах QF-Test выполнение шагов в контейнеризованной среде происходило более чем в два раза быстрее, чем в полноценной ВМ. Это не универсальный результат для любой системы тестирования. Он отражает сценарий, где расходы на guest OS и виртуальные устройства заметны относительно самой тестовой операции.
Для CI/CD контейнеры дают несколько операционных преимуществ:
- reproducible build environment;
- versioned image;
- изоляцию зависимостей;
- быстрый запуск runner;
- простое удаление среды после job;
- одинаковый runtime для pipeline stages;
- интеграцию с registry и deployment pipeline.
ВМ сохраняет преимущества, когда pipeline должен проверять:
- kernel-level поведение;
- systemd и boot sequence;
- драйверы;
- несколько операционных систем;
- сетевые режимы на уровне guest;
- desktop software в отдельной ОС;
- сценарии, требующие полной аппаратной модели.
Практическая модель часто комбинирует оба подхода. Kubernetes или другой container platform работает внутри виртуальных машин. В этом случае контейнеры обеспечивают плотность и скорость управления workload, а KVM задаёт границу инфраструктурной изоляции и позволяет разделять разные окружения.
Изоляция: производительность не отменяет границы безопасности
Контейнеры изолируют процессы, но используют общий kernel. ВМ разделяют guest OS и виртуальное hardware boundary. Это разные уровни доверия.
Для сервисов одной организации контейнерной изоляции часто достаточно. Она эффективна для:
- микросервисов;
- web API;
- workers;
- batch jobs;
- CI/CD runners;
- stateless-приложений;
- сервисов с единым Linux kernel.
Для недоверенных workloads, разных клиентов и требований к жёсткой изоляции ВМ предоставляет более сильную границу. Это может уменьшить плотность, увеличить время запуска и добавить расходы RAM, но снижает радиус потенциального воздействия между окружениями.
Архитектурный комитет должен разделять две оценки:
1. Производительность. Сколько CPU, RAM, I/O и network capacity получает workload.
2. Модель изоляции. Что произойдёт при компрометации процесса, контейнера или guest OS.
Нельзя оптимизировать только первую метрику. Снижение расходов на RAM не компенсирует неподходящую security boundary.
Как выбрать среду под тип нагрузки
Выбор между Docker и KVM можно формализовать по характеру workload.
Контейнеризация рациональна, если:
- приложения используют тот же тип kernel, что и host;
- требуется высокая плотность размещения;
- workload масштабируется горизонтально;
- важен быстрый startup;
- pipeline строится вокруг CI/CD;
- сервисы stateless или используют внешний persistent storage;
- network latency контролируется через bridge или host network;
- application state не хранится в writable layer;
- команда управляет container runtime и observability.
Виртуализация рациональна, если:
- требуется отдельная guest OS;
- workload использует Windows или другой несовместимый kernel;
- нужна более жёсткая изоляция tenant-сред;
- необходимо запускать legacy software;
- приложение зависит от системных сервисов guest OS;
- требуется отдельный kernel lifecycle;
- среда должна быть максимально независима от host user space;
- production-модель уже построена вокруг VM backup и disaster recovery.
Гибридная схема рациональна, если:
- кластер контейнеров должен быть изолирован на уровне ВМ;
- разные команды используют общий physical cluster;
- часть workloads требует Linux containers, а часть — отдельных ОС;
- нужно отделить control plane и data plane;
- есть требования к разным SLA;
- stateful-сервисы нуждаются в специализированном storage path.
В гибридной схеме не следует считать overhead дважды автоматически. Контейнеры внутри ВМ действительно получают расходы guest OS, но сохраняют преимущества контейнерного packaging, deployment и масштабирования внутри своего guest environment.
Оптимальный стек под метрики
Если приоритетом является плотность и скорость управления приложениями, базовый стек строится вокруг контейнеров:
- Docker или совместимый container runtime;
- Linux host;
- OverlayFS для image layers;
- persistent volumes для stateful данных;
- bridge или host networking в зависимости от latency;
- cgroups для CPU и RAM limits;
- registry для versioned images;
- CI/CD pipeline для сборки и rollout;
- monitoring CPU, memory, I/O и network;
- отдельные SLO для startup и recovery.
Если приоритетом является изоляция операционных систем и независимый kernel, стек строится вокруг KVM:
- KVM;
- management layer для lifecycle ВМ;
- virtio для сетевых и дисковых устройств;
- шаблоны guest OS;
- централизованные patch и backup policy;
- storage pool с контролируемой latency;
- мониторинг hypervisor и каждой guest OS;
- capacity planning по RAM, CPU и I/O;
- отдельные SLA для host и guest layers.
Минимальный набор измерений перед миграцией должен включать:
1. CPU utilization и CPU steal time.
2. RAM usage, page faults и swap activity.
3. Network throughput и p95/p99 latency.
4. Disk IOPS, throughput и fsync latency.
5. Время запуска workload.
6. Время масштабирования до целевого числа replicas.
7. Поведение при saturation.
8. Время восстановления после сбоя.
9. Плотность размещения при сохранении SLA.
10. Стоимость эксплуатации host и guest layers.
Тестировать следует не только пустой контейнер и не только синтетический benchmark. Нужен application-level тест с реальным профилем запросов, целевым storage и штатной сетевой схемой. Иначе измеряется не архитектура, а изолированный фрагмент runtime.
Итог
Docker обычно выигрывает у KVM по CPU overhead, RAM footprint, плотности размещения и времени запуска. В тестах CPU и памяти контейнеры близки к bare-metal. В автоматизации веб-интерфейсов контейнерная среда показывала более чем двукратное преимущество относительно полноценной ВМ.
KVM остаётся необходимым, когда workload требует отдельной операционной системы, собственного kernel или более жёсткой isolation boundary. Его накладные расходы выражаются не только в снижении вычислительной производительности. Основная стоимость — guest OS, резерв памяти, виртуальные устройства, disk image и более сложное масштабирование.
Контейнеризация против виртуализации по производительности приложений должна оцениваться через профиль нагрузки, а не через общий рейтинг технологий. Для CPU-bound и stateless-сервисов преимущество обычно на стороне контейнеров. Для storage-heavy workloads результат зависит от driver и backend. Для сетевых сервисов критичны NAT, bridge и latency budget. Для разных операционных систем контейнеры не заменяют ВМ.
Практическое решение сводится к четырём вопросам:
- какой kernel требуется приложению;
- какой уровень изоляции определён SLA и security model;
- где находится bottleneck — CPU, RAM, сеть или storage;
- какая архитектура сохраняет производительность после настройки, а не только в базовом benchmark.
Если ответы указывают на общий Linux kernel, горизонтальное масштабирование и высокий deployment rate, оптимальным выбором будет контейнерный стек. Если требуется независимая ОС и жёсткая граница между окружениями, следует принимать расходы KVM как часть архитектуры, а не пытаться устранить их настройками Docker.