Когда заходит речь о запуске виртуальных машин в Kubernetes через KubeVirt, первый вопрос, который возникает у инженеров: «А какой там оверхед?» Давайте разберём этот вопрос детально, рассмотрев каждую подсистему отдельно: вычисления, хранилище и сеть.

Оверхед на вычисления: спойлер — его нет

Чтобы понять, почему оверхеда на CPU практически нет, нужно разобраться, что такое контейнеризация с точки зрения ядра Linux.

В ядре существуют так называемые неймспейсы (namespaces) — механизм, позволяющий запускать процессы с изолированными подсистемами: отдельной сетью, отдельным mount-пространством, отдельным PID-пространством и так далее.

Ключевой момент: каждый процесс в Linux обязан запускаться в каком-то неймспейсе. Будь то хостовый неймспейс или изолированный — для ядра разницы нет. Процесс обрабатывается одинаково.

Контейнер — это просто процесс, запущенный в изолированных Linux kernel namespaces плюс cgroups. Последние — стандартный механизм ядра для ограничения ресурсов процесса. Cgroups используются не только в контейнеризации, но и в обычном systemd. Классический libvirt тоже применяет cgroups для ограничения ресурсов виртуальных машин.

А что такое виртуальная машина с точки зрения системы? Это всего лишь Linux-процесс QEMU с дополнительными разрешениями и доступом к /dev/kvm.

Если виртуалка — это просто процесс, почему бы не запускать его в контейнере? Именно это KubeVirt и делает.

Разницы нет, запущен QEMU в хостовом неймспейсе ядра или в отдельном, который создал Kubernetes. Оверхеда на вычисления нет.

Небольшая оговорка про libvirt

Есть небольшой оверхед на запуск отдельного libvirt-демона, который KubeVirt использует для настройки параметров виртуалки. Но он минимален.

Интересный архитектурный выбор: обычно запускается один libvirt на хост, управляющий множеством виртуалок. Проблема в том, что иногда он зависает, блокируя управление всеми VM на сервере. В KubeVirt пошли другим путём — отдельный libvirtd-демон на каждую виртуалку. Цена изоляции — дополнительные ресурсы на работу демона рядом с основным процессом VM. Но расход небольшой: libvirtd чаще выступает как shim-прослойка для простых операций: запуск/остановка VM, снятие снапшота, hotplug дисков и интерфейсов, запуск live-миграции.

Хранилище: зависит от подхода

В QEMU есть множество способов подключить виртуальные тома к виртуалке.

Классические варианты:

  • Создать файл (raw или qcow2) в файловой системе и передать его QEMU

  • Взять готовое блочное устройство и отдать его QEMU

В обоих случаях используется ядро в качестве посредника — файловая система или блочное устройство должны существовать в хостовой системе.

Прямое подключение: QEMU умеет самостоятельно обращаться к storage и подключать тома напрямую из user-space, минуя ядро. Поддерживаются различные драйверы: Ceph, iSCSI, NBD и другие. Например, Vitastor работает именно по такому принципу — QEMU устанавливает соединение напрямую.

Философия KubeVirt

Основной принцип KubeVirt — быть максимально нативным к Kubernetes. У команды даже есть своеобразный razor: «Если это может быть полезно и для виртуалок, и для контейнеров, это должно быть реализовано в Kubernetes, а не в KubeVirt».

Иногда это замедляет разработку VM-специфичных фич.

В контексте хранилища KubeVirt полностью опирается на сущности Kubernetes — в первую очередь на CSI-драйверы. Kubernetes ожидает, что каждый том — это Filesystem или BlockDevice. Оба режима поддерживаются и не имеют дополнительного оверхеда.

Однако прямое подключение QEMU к storage сейчас не поддерживается — в Kubernetes для этого нет подходящих абстракций.

Реальность такова, что крупный энтерпрайз чаще использует не прямое подключение, а общую файловую систему (NFS от NetApp) или блочные устройства (iSCSI). KubeVirt, кстати, умеет работать с iSCSI напрямую.

Итог по хранилищу: если сравнивать с типичными решениями — оверхеда нет. Если упираться в специфику драйверов QEMU — прямое подключение будет быстрее, но менее универсально. Generic-решения поддерживают только стандартные варианты, а под каждого вендора придётся пилить и поддерживать свою специфику.

Сеть: здесь есть нюансы

У каждого контейнера (или процесса в терминах ядра Linux) может быть свой отдельный сетевой стек с собственными интерфейсами. Для связи контейнерного network namespace с хостовым используются veth-интерфейсы — виртуальный «патчкорд», один конец которого находится в хостовом неймспейсе, другой — в контейнерном. Это база для всех контейнеров.

Снаружи логику обслуживает CNI-плагин: добавляет интерфейс в Linux bridge (как Flannel) или вешает на него eBPF-программу (как Cilium). С точки зрения системы виртуалка — такой же контейнер, как и все остальные.

Что происходит внутри контейнера

В 95% случаев для виртуалки создаётся TAP-интерфейс, который виден в хостовой системе как обычный сетевой интерфейс. Вопрос в том, как соединить его с внутренним veth-интерфейсом.

Популярные варианты:

Bridge: в ядре создаётся отдельный Linux bridge, в который добавляются внутренний конец veth и TAP-интерфейс виртуалки.

Masquerade: создаётся iptables-правило, копирующее трафик из одного интерфейса в другой.

В обоих случаях есть оверхед по latency — каждое дополнительное звено в цепочке требует дополнительных тактов процессора для обработки пакетов.

Как обойти ограничения

Оверхед небольшой, но есть способы его избежать:

  • macvtap-cni — биндится напрямую на физический интерфейс машины

  • SR-IOV — аппаратная виртуализация сети

Как и в случае с хранилищем, QEMU может быть настроен на прямую коммуникацию с SDN в user-space, минуя ядро. KubeVirt имеет ограниченную поддержку этого режима, так как опирается на спецификацию CNI.

Различные CNI-драйверы (например, Kube-OVN) реализуют параллельное API для offloading сети из ядра.

Стоит отметить, что по части сети KubeVirt имеет мощный интерфейс для написания плагинов с произвольной логикой подключения.

Выводы

KubeVirt — это компромисс между производительностью и универсальностью. Если вам нужен максимум производительности и вы готовы поддерживать специфичные решения — можно выжать больше. Если важнее стандартизация, vendor neutrality и простота поддержки — KubeVirt отлично справляется со своей задачей.


P.S. Статья основана на обсуждении в профессиональном сообществе. Благодарю участников за ценные инсайты.

Присоединяйтесь к нашему комьюнити

Комментарии (13)


  1. poige
    21.01.2026 10:08

    странное построение — как минимум одно, начинается всё вполне пристойно:

    Когда заходит речь о запуске виртуальных машин в Kubernetes через KubeVirt, первый вопрос, который возникает у инженеров: «А какой там оверхед?»

    но дальше эта благопристойность вдруг поворачивается вот так:

    Чтобы понять, почему оверхеда на CPU практически нет, нужно разобраться, что такое контейнеризация с точки зрения ядра Linux.

    — так ненавязчиво и беззастенчиво сходу спутывается виртуализация и контейнеризация.

    Кратко обозначу — претензия не про неверные данные про наличие/отсутствие оверхеда, претензия — про подмену понятий в едином повествовательном порыве.


    1. poige
      21.01.2026 10:08

      Теперь уже более предметно в плане именно что overhead'а CPU-RAM связки — без I/O, и именно что про виртуализацию. Тут уж я довольно беззастенчиво загрузил одну приличную эл-эл-модель и получил вот такое — могу сказать, что, конечно, что-то такое я и сам слышал-знал, но эт всё же, не мой day-to-day knowledge domain, так что не обессудьте. В ответ на мой запрос «но если отойти от кривых формулировок — чё там про оверхед по CPU на виртухах, и правда ведь почти нет, если исключить vm-enter/exit, которого в пр-цпе не будет в контейнерах? Я бы ещё заметил, что процессы внутри виртухи opaque для host планировщика. Давай сфокусируемся на CPU/RAM, I/O к делу не относится»:

      «…

      Лимитирующие факторы, которые остаются даже «в пустоте»

      1. VM-exit никуда не деваются: любые CPUID, MSR, I/O-порт, INIT-IPI, APIC-EOI → гипервизор. В контейнере это прямые обращения к ядру, без world-switch.

      2. Page-walk всегда двойной: при каждом TLB-miss расходится по EPT. Даже если exit нет, это +4-20 доп. memory-access на miss.

      3. Планировщик видит только vCPU, поэтому host не может расставить реальные процессы по NUMA/LLC, пиринг потоков делает только гость. На CPU-heavy это редко больно, но на latency-чувствительных сервисах хвост растягивается именно из-за двойного планирования.

      Итог

      При compute-bound нагрузках и грамотной конфигурации (hugepages, vCPU pinning, no overcommit) оверхед KVM по CPU действительно «мал», то есть ≈ 3–5 %.
      Полного нуля нет и быть не может: VM-exit и двухмерный page-walk — механически встроены в аппаратную виртуализацию; они же отличают VM от контейнера.

      …»


      1. andreynekrasov
        21.01.2026 10:08

        Вы не поняли, что в статье речь не о сравнении виртуализации и контейнеризации, а о запуске виртуалок вне кубера и в кубере.


        1. poige
          21.01.2026 10:08

          Это вполне возможная трактовка, но она во-первых — на уровне «капитан очевидность», а во-2-х, тогда уж нужно сравнивать не с Kubernetes — который не более чем фреймворк-запускалка контейнеров, а — «vanilla Linux process vs он-же-в контейнере», сам k8s тут ничего нового не добавляет. Так что исходное «странный тезис» остаётся в силе — менее странным он от этого не становится, возможно даже наоборот.


          1. andreynekrasov
            21.01.2026 10:08

            на уровне «капитан очевидность»

            imho в этой заметке нет каких то вторых-третьих слоёв, искать их там нет смысла :) просто "а что будет, если запускать виртуалки в кубере".

            > сам k8s тут ничего нового не добавляет

            это краткое содержание этой статьи :)

            vanilla Linux process vs он-же-в контейнере

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


            1. poige
              21.01.2026 10:08

              просто в статье нет каких то вторых-третьих слоёв, искать их там нет смысла :)

              О да, но стоит разделять слойность и двусмысленность. Первой нет, а вот вторая — как уже очевидно, во все поля. А так, вообще, если автор реально решил посветить себя описанию оверхеда контейнеризации — через «кубер» — то всё и того хуже. ;)


          1. kvaps Автор
            21.01.2026 10:08

            Спасибо за критику. Для эксперта данная статья действительно покажется на уровне «капитан очевидность» и она намеренно такая.

            Нам часто поступают вопросы вроде «вы запускаете виртуалки в контейнерах, а какой там оверхед?», я постарался максимально подробно разобрать этот вопрос в формате рассуждения.


            1. poige
              21.01.2026 10:08

              np, единственное (всё же) — если ЦА не эксперты и даже не medium, для них тем более нужно гораздо чётче, без прыжков между разными понятиями, потому что им такой винегрет разобрать тем более не удастся.


      1. iwram
        21.01.2026 10:08

        Согласен.

        Маркетологи компании, отдайте обратно аккаунт Андрею. В дополнение можно почитать доку https://docs.kernel.org/virt/kvm/api.html

        If KVM_CAP_SYNC_REGS is defined, these fields allow userspace to access certain guest registers without having to call SET/GET_*REGS. Thus we can avoid some system call overhead if userspace has to handle the exit. Userspace can query the validity of the structure by checking kvm_valid_regs for specific bits. These bits are architecture specific and usually define the validity of a groups of registers. (e.g. one bit for general purpose registers)

        И другие "уменьшаторы" накладных расходов описаны на популярном ресурсе.

        Печально, что от обычной нормальной базы пытаются уйти в другие абстракции - которые на более высоком уровне якобы решат проблемы лежащие ниже.


  1. andreynekrasov
    21.01.2026 10:08

    В обоих случаях есть оверхед по latency

    cilium про netkit пишет, что "latency as low as host"


    1. kvaps Автор
      21.01.2026 10:08

      Там проблема не в cilium, а в том что происходит внутри контейнера:

      {CNI magic}  [veth]<--->[veth]<--->[bridge]<--->[tap]<--->[eth0]
      ^-------host--------^ ^---------------pod-------------^ ^--vm--^
      

      Вот на все эти дополнительные интерфейсы тратятся драгоценные контекст свитчи.


      1. poige
        21.01.2026 10:08

        Вот на все эти дополнительные интерфейсы тратятся драгоценные контекст свитчи.

        Опять же, чтобы яснее по терминам: «…

        Context switch (переключение контекста) — это когда планировщик ядра заменяет текущий исполняющийся поток (thread) на другой поток на данном CPU. Что сохраняют/восстанавливают: регистры (GPR, флаги, PC/IP, SP), иногда FPU/SSE/AVX (лениво), kernel stack ptr, планировочные структуры; при смене адресного пространства — mm/CR3/ASID и, возможно, TLB (или его часть).

        …»

        А теперь по сути: «…

        Пересечение veth/bridge/tap в ядре обычно даёт softirq-работу, но не сам по себе context switch; он появится только если планировщик запустит другой поток (например, ksoftirqdqemu-vcpu, и т.п.).

        Дополнительные виртуальные интерфейсы добавляют CPU-циклы (обработку в softirq), но не производят автоматических context-switch «на каждом хопе». Context switch появляется только когда ядро вынуждено: …

        …»


  1. angapov
    21.01.2026 10:08

    У меня уже накопился богатый опыт с OpenShift Virtualization и Migration Toolkit for Virtualization в проде, я могу сказать только, что в целом после Вмвари это все пока выглядит как топор против бензопилы. В целом работает, но усилий требуется намного больше.

    По сути поста в кубевирте есть один довольно неочевидный слой оверхеда, когда используются Filesystem-backed volumes типа NFS. В этом случае qemu создает на файловом вольюме qcow2 файлик, который презентуется виртуалке как диск. Внутри виртуалки уже создается вторая файловая система. И вот этот двойной слой файловой системы вносит, во-первых, сторадж оверхед в размере порядке 6% места, а во-вторых довольно ощутимо снижает иопсы, порядка 10-20%. Поэтому по возможности использовать надо только Block-based сторадж.