MAATRIX / Блог / Предел числа виртуалок на узле: считаем не по памяти, а по вводу-выводу

Предел числа виртуалок на узле: считаем не по памяти, а по вводу-выводу

MAATRIX

Планирование плотности виртуалок на гипервизоре почти всегда начинается с деления RAM хоста на объём памяти одной VM, иногда — с добавлением расчёта по vCPU. Диск в этой арифметике обычно не участвует вообще, кроме проверки, что места хватает. А зря: если каждая VM делает случайные операции чтения и записи, совокупный IOPS упирается в физический предел накопителя намного раньше, чем заканчивается память или процессорное время. Разберём, как посчитать реальную плотность с учётом ввода-вывода, почему одна «шумная» виртуалка может испортить жизнь всем соседям и что с этим делать на уровне гипервизора.

Почему память и CPU — не единственный потолок

Память и CPU ведут себя предсказуемо в том смысле, что их можно померить и разделить. У хоста есть конкретное число гигабайт и ядер, у VM — конкретный запрос на каждый ресурс, и деление одного на другое даёт первое приближение к числу машин. С диском эта логика ломается по двум причинам.

Первая — диск не делится линейно между VM. Гигабайт памяти, отданный одной виртуалке, физически недоступен другой (если не считать балунинг и оверкоммит, у которых своя механика и свой набор рисков). А операции ввода-вывода — это общая очередь на общий физический ресурс: все VM пишут и читают на один и тот же массив дисков одновременно, и накопитель обслуживает запросы по очереди, а не параллельно в неограниченном количестве. У SSD и NVMe параллелизм намного выше, чем у HDD, но он тоже конечен — у контроллера есть предел одновременно обрабатываемых команд, у NAND-кристаллов есть предел параллельных операций.

Вторая причина — потребление диска у VM непостоянно и плохо предсказуемо по паспортным данным виртуалки. VM с 2 ГБ RAM может генерировать больше случайных IOPS, чем VM с 16 ГБ RAM — например, лёгкий контейнер с базой данных под нагрузкой пишет постоянно, а тяжёлая VM с редким batch-процессом может почти не трогать диск. Память и CPU привязаны к конфигурации VM напрямую (вы сами задаёте, сколько выделить, и логика оверкоммита по каждому из этих двух ресурсов своя, с разной ценой ошибки), а нагрузка на диск — это следствие поведения приложения внутри, которое конфигурацией VM не описывается вообще.

В результате нередкая картина: хост с 256 ГБ RAM и 32 ядрами формально тянет по памяти и CPU полсотни средних VM, а на практике упирается в диск на пятнадцатой, потому что три из пятнадцати оказались базами данных с активной случайной записью. Подробная методика расчёта по всем трём ресурсам сразу — в статье сколько виртуалок влезет на сервер; здесь разберём именно дисковую часть расчёта подробно, потому что на практике это чаще всего самое узкое и самое непрозрачное место.

Как посчитать плотность виртуалок по IOPS

Общая формула проста: число VM, которое выдержит хост по диску, равно доступному запасу IOPS хранилища, делённому на среднее потребление IOPS одной VM, с поправкой на запас прочности.

N_io ≈ (IOPS_disk_sustained × safety_factor) / IOPS_per_VM_avg

Каждая переменная в этой формуле — источник ошибок, если брать её «на глаз».

IOPS_disk_sustained — не паспортная пиковая цифра производителя и не число из синтетического бенчмарка на пустом диске. Нужна устойчивая (sustained) цифра на случайном чтении/записи смешанным паттерном при глубине очереди, близкой к реальной у вас на хосте — этому посвящена отдельная статья fio для честного замера диска, где разобрано, как не обмануть себя одним прогоном с настройками по умолчанию. Важный нюанс: у многих SSD и NVMe после исчерпания буфера или SLC-кэша производительность записи заметно проседает по сравнению с «бумажной» цифрой — если планировать по паспортному максимуму, легко попасть именно в этот провал под длительной нагрузкой.

safety_factor — запас, обычно в диапазоне 0,6–0,8 от измеренного устойчивого IOPS, а не 1,0. Причины запаса: пиковые нагрузки нескольких VM могут совпасть по времени (ночной бэкап у трёх виртуалок сразу), у гипервизора и файловой системы хоста есть собственные фоновые операции (журналирование, TRIM, для ZFS — scrub и запись метаданных), и часть очереди нужно оставить свободной, чтобы задержка не улетала в потолок при кратковременных всплесках — устойчивая работа диска с постоянно заполненной очередью почти всегда означает растущий await, даже если формальная загрузка устройства ещё не выглядит стопроцентной.

IOPS_per_VM_avg — самая сложная величина, потому что она не задаётся конфигурацией VM, а измеряется по факту. Ниже — как это сделать.

Пример расчёта, чисто иллюстративный — цифры ниже не результат замера конкретного диска, а демонстрация принципа, у вас на входе должны быть ваши собственные значения из fio и мониторинга: если устойчивый случайный IOPS хранилища на смешанном чтении/записи по вашему тесту составил условно 40 000, безопасный запас — 70% (28 000), а типичная VM в вашем парке потребляет в среднем 800 IOPS с редкими всплесками, то по диску хост выдержит около 35 VM такого профиля. Если среди этих VM есть заметно более «тяжёлые» по IO (базы данных, очереди сообщений, файловые шары с мелкими файлами), их считают отдельно, а под однотипный «лёгкий» пул применяют такую усреднённую оценку.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Шумный сосед: как одна VM забирает диск у всех

Плотность, посчитанная по средним значениям, разваливается, если один арендатор ресурса ведёт себя не так, как остальные. В контексте диска это называют «шумным соседом» (noisy neighbor): одна VM — например, с полным бэкапом, реиндексацией базы или batch-джобом — начинает генерировать на порядок больше операций ввода-вывода, чем обычно, и забирает себе бо́льшую часть общей очереди устройства. Остальные VM на том же хранилище при этом не теряют выделенную им память или CPU — у них просто резко растёт задержка (await) на каждой дисковой операции, потому что их запросы в очереди хранилища стоят позади чужих. Общий разбор этого явления по всем видам ресурсов (не только диску) — в статье шумный сосед на гипервизоре.

Специфика именно диска в том, что у него, в отличие от CPU (где guest видит steal time напрямую в top), нет способа изнутри VM понять, что задержка растёт из-за соседа, а не из-за собственной нагрузки. Различить эти две причины можно только снаружи, с хоста.

Именно поэтому расчёт плотности по среднему IOPS без ограничений — это расчёт для идеального мира, где все VM ведут себя предсказуемо. На практике без явных лимитов одна VM с аномальным паттерном обнуляет весь запас прочности, заложенный в safety_factor, для остальных пятнадцати. Планирование плотности по I/O имеет смысл только вместе с механизмом, который не даёт одной VM выйти за пределы своей доли — об этом ниже.

Как измерить, кто сколько ест: iostat, io.stat, iotop

Прежде чем что-то ограничивать или планировать, нужно знать текущее фактическое потребление — не то, что вы думаете, а то, что показывает хост.

Общая картина по физическому устройству — iostat на хосте (пакет sysstat):

iostat -x 1 5

Колонки r/s и w/s — операции чтения и записи в секунду, await — средняя задержка в мс, %util — доля времени, когда устройство было занято хотя бы одним запросом. Стабильно высокий await при не запредельном %util — верный признак, что очередь у диска глубокая и кто-то создаёт на неё давление.

Разбивка по конкретной VM сложнее, потому что hyperviser видит не «виртуалку», а блочное устройство или файл, в который эта VM пишет. На Proxmox с LVM-хранилищем каждая VM — это отдельный логический том, и его можно смотреть отдельно:

lsblk -o NAME,MAJ:MIN,SIZE | grep vm--
iostat -x 1 /dev/pve/vm-101-disk-0

На ZFS-хранилище похожая картина через zpool iostat -v (по датасетам) или через zfs iostat, если версия поддерживает. Если хранилище — общий пул без разбивки по VM на уровне блочных устройств (например, единый файл образов на NFS), различить VM можно только на уровне cgroup процесса qemu, у которого есть собственный io-контроллер в cgroup v2:

cat /sys/fs/cgroup/qemu.slice/101.scope/io.stat

Файл io.stat показывает по каждому устройству (в формате MAJ:MIN rbytes=... wbytes=... rios=... wios=...) суммарные счётчики именно этого процесса — то есть именно этой VM, если она запущена штатно через qemu под Proxmox или libvirt. Дельта этих счётчиков между двумя снятиями, делённая на интервал времени, и есть фактический IOPS конкретной VM — это и есть та величина IOPS_per_VM_avg, которую нужно подставлять в формулу плотности, а не оценка «на глаз» по типу нагрузки.

Для оперативного контроля живьём удобен iotop -oPa на хосте — он показывает процессы qemu (по одному на VM) с накопленным чтением и записью за время работы утилиты, что быстро выявляет, какая VM прямо сейчас грузит диск сильнее остальных.

Ограничение IOPS: throttling на уровне диска VM и cgroup

Измерить недостаточно — нужно ограничить, иначе один нетипичный день у одной VM (миграция базы, полный ресинк, восстановление из бэкапа) съедает запас прочности у всех остальных. Подробный разбор настройки лимитов с упором на цифры и типовые сценарии — в статье лимиты дисковых операций для виртуалок; здесь — сама механика, чтобы было понятно, что именно ограничивается и на каком уровне.

Для полноценных VM (KVM/Proxmox) throttling обычно задаётся не через cgroup хоста напрямую, а на уровне эмулируемого блочного устройства в конфигурации диска — так ограничение живёт вместе с VM и переезжает вместе с ней при миграции. В Proxmox это делается через qm set или GUI (вкладка диска → Advanced → IO limits):

qm set 101 --scsi0 local-lvm:vm-101-disk-0,iops_rd=1000,iops_wr=1000,iops_rd_max=2000,iops_wr_max=2000,mbps_rd=100,mbps_wr=100

Здесь iops_rd/iops_wr — устойчивый лимит, а _max-варианты — допустимый краткий всплеск (burst) сверх устойчивого значения, если у диска есть запас. Такая двухуровневая модель полезна ровно потому, что реальная нагрузка редко бывает ровной: разумно дать VM возможность отработать короткий всплеск (например, старт приложения или прогрев кэша), но не позволить ей удерживать пиковую нагрузку постоянно.

Для контейнеров (LXC) throttling дешевле реализовать напрямую через io-контроллер cgroup v2, потому что ядро общее с хостом:

echo "253:0 rbps=104857600 wbps=104857600 riops=2000 wiops=2000" > /sys/fs/cgroup/lxc/101/io.max

где 253:0 — major:minor целевого блочного устройства (смотрится через lsblk -d -o MAJ:MIN,NAME). Это тот же контроллер cgroup, что ограничивает у контейнера память и CPU, просто применённый к дисковому вводу-выводу.

Важная разница между двумя подходами: лимит на уровне диска VM (qm set) точечный — можно дать разным VM разные лимиты в зависимости от их профиля. Лимит через cgroup процесса qemu целиком грубее — он режет всю VM как один процесс, без разделения по её виртуальным дискам, если их несколько. Для большинства сценариев с полноценными VM правильнее настраивать лимиты именно на уровне диска, а cgroup-ограничение процесса оставлять как грубый предохранитель на случай, если конфигурация диска почему-то не сработала.

Как распределять диски между группами VM

Throttling ограничивает потолок одной VM, но не решает задачу целиком — вторая часть решения на уровне архитектуры хранилища: не держать всех соседей на одном физическом ресурсе, если их профили сильно различаются.

Практические варианты разделения:

ПодходКогда уместенОграничение
Один общий пул (RAID/ZFS) для всех VMОднородная лёгкая нагрузка, простая инфраструктураОдин шумный сосед влияет на всех
Отдельный быстрый пул (NVMe) под latency-чувствительные VM (БД)Есть явно выделяющиеся по нагрузке VMДороже по месту, нужен явный процесс распределения
Отдельные тома/датасеты на общем массиве с собственными лимитамиZFS/LVM с разными datasets под группы VMНе изолирует физически, изолирует логически
Физически раздельные накопители под группыМаксимальная изоляция, критичные нагрузкиХуже используется место, дороже

Уровень RAID тоже часть этого расчёта, а не только вопрос отказоустойчивости. При случайной записи массивы с чётностью (RAID5, RAID6) платят так называемым write penalty — на каждую логическую запись приходится несколько физических операций (чтение блока, чтение чётности, запись блока, запись чётности), тогда как RAID10 обходится без этого штрафа за счёт прямого зеркалирования. Точный множитель штрафа зависит от контроллера, размера страйпа и конкретной реализации, поэтому берите его не как готовую цифру, а как повод замерить fio именно на вашей итоговой конфигурации массива, а не экстраполировать с одиночного диска.

Отдельно стоит логика распределения по типу нагрузки, а не только по объёму: VM с базой данных и активной случайной записью логичнее размещать на выделенном NVMe-пуле небольшого числа «соседей», а десятки лёгких статических VM — на общем пуле подешевле, где даже пиковая нагрузка одной из них не создаёт заметного давления на остальных из-за их числа и предсказуемости. Смешивать тяжёлую по IO нагрузку с лёгкой на одном физическом хранилище без throttling — самый частый источник жалоб «сервер иногда необъяснимо тормозит», хотя формально ресурсов памяти и CPU в избытке.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Как понять заранее, какой у VM будет IOPS, если она ещё не запущена?

Точно — никак, по спецификации VM это не считается. Для новых VM закладывайте консервативную оценку по классу нагрузки (веб — низкий, БД под записью — высокий) и корректируйте план по факту через неделю-две реальной работы, замеряя io.stat или iostat по конкретному тому.

Достаточно ли ограничить только запись, если чтение обычно быстрее?

Нет, если у диска общая очередь на устройство — чтение и запись конкурируют за неё одинаково. NVMe с раздельными очередями снижает конкуренцию, но не убирает её полностью на общем контроллере.

Нужно ли считать плотность по IOPS, если на хосте только SSD/NVMe, а не HDD?

Да, просто предел на порядки выше. Плотность VM на NVMe обычно упирается в диск позже, чем на HDD, но при достаточном числе виртуалок с активной случайной записью упирается и туда — разница в масштабе нагрузки, при которой это происходит, а не в принципе.

Влияет ли размер блока (block size) внутри VM на реальный IOPS-бюджет?

Существенно. Мелкие случайные операции (4–8 КБ, типично для баз данных) исчерпывают IOPS-бюджет диска намного быстрее, чем крупные последовательные (сотни КБ — МБ), даже при одинаковой пропускной способности в мегабайтах в секунду — поэтому при планировании ориентируйтесь на профиль конкретного приложения, а не на общий трафик.

Что произойдёт, если превысить реальный предел IOPS хранилища без throttling?

Задержка на всех VM хранилища начинает расти нелинейно — не пропорционально превышению, а резко, потому что очередь запросов к устройству растёт быстрее, чем устройство успевает её разгребать. Характерный симптом — рост await при ещё не самом высоком %util.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →