![image](https://habrastorage.org/getpro/habr/post_images/bf6/a02/f8f/bf6a02f8fe47b9df95c0960632d09325.png)
Практика показывает, что любой процесс, в определенной степени, всегда можно оптимизировать. Это вполне можно отнести и к виртуализации. Возможностей оптимизации тут достаточно много, и задача эта многогогранна.
В пределах данной статьи я хочу ознакомить вас с методиками сайзинга виртуальных машин, а так же о методах оптимизации их работы. Материал будет техническим и рекомендуется к ознакомлению всем специалистам по vSphere.
Для начала хотелось бы рассказать о двух технологиях, основным образом влияющих на производительность vSphere. Это технология NUMA и технология работы шедулера гипервизора ESXi.
Про NUMA существует достаточно много подробных статей, пересказывать эту информацию не вижу смысла, ограничусь лишь базовым описанием для целостности материала. Итак, NUMA – Non Uniform Memory Access. На русский язык это можно перевести как Неравноценный Доступ к Памяти.
Современные многосокетные сервера, по сути, представляют собой несколько изолированных односокетных компьютеров, объединенных на одной материнской плате. Каждый процессор монопольно владеет своими слотами оперативной памяти, и только он имеет к ней доступ. Аналогично, каждый процессор имеет свои персональные PCI-E шины, к которым подключены различные устройства материнской платы, а так же слоты расширения PCI-E. Процессоры соединены между собой высокоскоростной шиной обмена данными, по которой они получают доступ к «чужим» устройствам, делая запрос на это соответствующему процессору-хозяину. По понятным причинам, доступ процессора к «своей» памяти происходит гораздо с меньшими накладными расходами, чем к «чужой». Пока это все, что необходимо знать о данной технологии.
vSphere прекрасно знает про NUMA и старается размещать виртуальные ядра машин на тех физических процессорах, в чьей памяти сейчас находится оперативная память виртуальной машины. Но тут возникают подводные камни. Производители серверов любят включать в BIOS по умолчанию эмуляцию NUMA. То есть сервер представляется операционной системе как НЕ NUMA устройство, и vSphere не может использовать свою оптимизацию для управления данной технологией. В документации по vSphere рекомендуется отключать (Disable) данную опцию в BIOS, это позволяет vSphere самостоятельно разбираться с вопросом.
Рассмотрим потенциальные проблемы, которые могу возникнуть с NUMA. Предполагаем, что vSphere его видит и корректно с ним работает. Для примера будем рассматривать 2-процессорную систему, как самый простой вариант NUMA. Предположим, что на физическом сервере 64 GB оперативной памяти, по 32 на каждом сокете.
1. Мы создаем виртуальную машину (ВМ) с одним виртуальным ядром (vCPU). Естественно, такую виртуальную машину сможет исполнить только один физический процессор. Рассмотрим ситуацию, когда ВМ надо дать 48 GB оперативной памяти. 32 GB памяти ВМ заберет у «своего» физического процессора, а еще 16 ей вынужденно придется забрать у «чужого». И при доступе к этим «чужим» гигабайтам мы получаем гарантированно высокие задержки, что ощутимо снизит быстродействие виртуальной машины и увеличит нагрузку на шину передачи данных между физическими процессорами. Исправить ситуацию можно, если дать виртуальной машине 2 виртуальных сокета по 1 ядру каждый. Тогда vSphere распределит 2 vCPU ВМ по разным физическим процессорам, взяв у каждого по 24 GB оперативной памяти. Дав виртуальной машине 1 виртуальный сокет мы лишим её возможности грамотно использовать 2 физических процессора.
2. Вариант, когда мы создаем в плюс к первому пункту вторую ВМ на 10 GB оперативной памяти и один vCPU. Если бы эта ВМ была одна, то никаких проблем нет, она вполне умещается в одну NUMA ноду с 32 GB оперативной памяти. Но у нас уже есть первая машина, которая у обоих нод NUMA отъела по 24 GB памяти, и у каждой ноды осталось свободно всего по 8 GB. В данной ситуации либо первая, либо вторая ВМ начнут использовать часть «чужой» памяти, хотя вроде бы каждая из них сконфигурирована правильно с точки зрения NUMA. Это очень характерная ошибка и необходимо очень досконально просчитывать вашу виртуальную инфраструктуру при проектировании, а так же комплексно подходить к конфигурированию виртуальных машин при эксплуатации.
Хотелось бы отметить, что vSphere имеет свою четкую логику при работе с NUMA и Hyperthreading.
Если у ВМ всего 1 виртуальный сокет, то при увеличении vCPU, исполнение машины будет производиться на одном физическом процессоре исключительно на его физических ядрах без использования технологии Hyperthreading. При превышении количества vCPU над количеством физических ядер процессора, ВМ продолжит исполняться в пределах этого физического процессора, но уже с использование Hyperthreading. Если количество vCPU превысило количество ядер процессора с учетом Hyperthreading, то начнут использоваться ядра соседних NUMA нод (других физических процессоров), что приведет к потере производительности (если указать неверное количество виртуальных сокетов). В случае, когда физический процессор сильно нагружен, и свободных физических ядер не осталось, то в любом случае будет использоваться технология Hyperthreading (если иное не указано в конфигурации виртуальной машины). Если посмотреть на цифры, то в среднем ВМ теряет порядка 30-40% производительности, если она работает на чистом Hyperthreading по сравнению с чистыми физическими ядрами. Но сам физический процессор имеет общую производительность примерно на 30% больше с технологией Hyperthreading, чем без нее (используя только физические ядра). Данный показатель очень зависит от типа нагрузки и оптимизации приложений ВМ к многопоточной работе.
Если у ВМ более одного виртуального сокета, то vSphere будет оптимизировать работу такой ВМ, размещая исполняемые ядра и оперативную память на разных физических процессорах сервера.
По понятным причинам, ситуация с нагрузкой физического сервера постоянно меняется. Зачастую, возникает неравномерность нагрузки на физических процессорах сервера. vSphere следит за этим. Шедулер анализирует положение дел, вычисляет накладные расходы на перемещение ВМ или её части на другую NUMA ноду (перемещение памяти, ядер). Сравнивает эти расходы с потенциальной выгодой от перемещения и принимает решение — оставлять все как есть или же перемещать. Иными словами, при любой ситуации vSphere старается оптимизировать работу виртуальных машин. Наша задача состоит в том, чтобы упростить vSphere эту задачу и не ставить её в безвыходное положение.
О каких потерях может идти речь при неправильной конфигурации машин с NUMA нодами? Руководствуясь собственным опытом, могу сказать, что потери могут доходить до 30% общей производительности физического сервера. Много это или мало – решать вам.
Теперь, когда мы немного разобрались с NUMA и Hyperthreading, мне хотелось бы чуть подробнее рассказать о работе шедулера с ядрами виртуальных машин, vCPU.
Я не буду вдаваться в совсем уж глубины, это мало кому интересно, но постараюсь рассказать про принципы и методику работы данного механизма. Итак, основной механизм гипервизора работает следующим образом. В оперативной памяти постоянно работает процесс, обслуживающий работу виртуальных машин. Этот процесс можно представить как конвейер состояния READY и хранилище состояния WAIT. Опустим другие, менее значимые состояния виртуальных машин, они сейчас не принципиальны.
Для легкости восприятия, предлагаю воспринимать все vCPU виртуальных машин как цепочку. Каждое звено цепи – это ядро vCPU (world, в терминах vSphere). Таких world у машины столько, сколько у нее vCPU. Есть еще 2 невидимых, служебных world, сопутствующих каждой виртуальной машине. Один отвечает за обслуживание машины в целом, второй за её ввод-вывод. Реальное потребление вычислительной мощности этими служебными мирами совершенно незначительное, а потребление ими оперативной памяти можно оценить по показателю overhead у каждой виртуальной машины. Стоит отметить, что при создании ВМ размером в весь физический хост, с равным количеством виртуальных и физических ядер, могут возникнуть некоторые потери производительности. Этого момента я коснусь чуть ниже.
Конвейер READY, это «труба», в которую по очереди сброшены все такие цепочки. Таймслот – это время, в течение которого виртуальные машины исполняются физическим процессором (исполняются их vCPU). На это время виртуальная машина практически без потерь, аналогично физическому серверу, использует физические процессоры аппаратного сервера (pCPU). Максимальная величина таймслота искусственно ограничена величиной порядка 1 миллисекунды. После окончания таймслота, vCPU ВМ принудительно помещаются шедулером в очередь конвейера READY. Шедулер имеет возможность менять очередность виртуальных машин в конвейере READY, приоритет каждой машины вычисляется исходя из её текущей фактической нагрузки, её прав на ресурсы (Shares), как давно машина была на физическом процессоре и еще нескольких менее значимых параметров. Логично предположить, что если ВМ на хосте одна, то она всегда будет проходить конвейер очереди READY без задержек т.к. у нее нет конкурентов на ресурсы со стороны других ВМ.
Каким образом шедулер располагает миры виртуальных машин на физических ядрах? Представим себе таблицу, которая имеет по ширине столько столбцов, сколько ядер у нашего физического сервера (с учетом Hyperthreading). По высоте каждая строка будет соответствовать очередному такту процессора(ов) сервера. Давайте представим себе 2-процессорный сервер, по 6 физических ядер на процессор, + Hyperthreading. Всего получим 24 ядра на сервер. Предположим, что у сервера высокая нагрузка, и vSphere вынуждена использовать Hyperthreading, так будет проще считать. Допустим, у нас есть несколько виртуальных машин:
• 4 штуки по 1 ядру
• 4 штуки по 2 ядра
• 2 штуки по 8 ядер
• 1 штука по 16 ядер
Итак, наступает первый таймслот, шедулер выбирает претендентов. Пусть первой будет машина на 16 ядер. Первые 16 физических ядер (pCPU) заняты, осталось 8. Туда поместились, допустим, 4 машины по 2 ядра (причем машина на 16 ядер должна иметь 2 виртуальных сокета, чтобы корректно работать с NUMA). Всё, таймслот заполнен полностью, это идеальный вариант. Мы не теряем производительность, pCPU не простаивают. Остальные виртуальные машины в это время не работают и находятся в очереди ожидания READY. Предположим, что «счастливые» виртуальные машины получили одинаковый по величине таймслот (хотя в реальности у всех ВМ таймслот разный и зависит от множества факторов). Итак, наступает второй таймслот, и шедулеру надо его заполнить.
Остались не обслуженными машины:
• 4 штуки по 1 ядру
• 2 штуки по 8 ядер
Начинаем заполнять, 2 машины по 8 ядер, 4 машины по 1, осталось 4 свободных pCPU, можно положить туда две 2-ядерных машины, которые уже отработали в первом таймслоте. Больше мы туда уместить ничего не можем, не помещается. Таймслот опять заполнен полностью и мы не теряем мощность.
![image](https://habrastorage.org/getpro/habr/post_images/ea1/58f/0d6/ea158f0d663202326c90e8a97164bf44.png)
Аналогичным образом шедулер будет и далее наполнять таймслоты мирами виртуальных машин, стараясь сделать это максимально эффективно, чтобы уменьшить «дыры» в заполнении и повысить КПД виртуальной среды.
Ниже представлен негативный вариант расположения виртуальных машин на хосте. Одна большая машина размером в хост, и одна малая, с 1 vCPU. При равных правах на ресурсы и равных потребностях к производительности эти машины получат одинаковое количество таймслотов, то есть поделят между собой процессорное время. Т.к. обе машины не смогут работать одновременно (не умещаются в один таймслот), то работать они будут по очереди, причем малая машина будет работать в пустом таймслоте, где кроме нее больше никого не будет (таймслот израсходуется практически впустую). Большая машина при всем желании не сможет получить процессорное время, даже если оно этой машине необходимо. Например, при частоте центрального процессора 3 ГГц, обе эти машины смогут максимум получить по 1.5 ГГц.
![image](https://habrastorage.org/getpro/habr/post_images/b27/e1e/569/b27e1e569f4bc3eeae266112fd14c9d8.png)
Виртуализация позволяет создать на хосте несколько виртуальных машин, и может сложиться ситуация, когда суммарное количество vCPU всех машин будет больше количества pCPU физического хоста. Это вполне нормальное явление, но нужно четко осознавать, что одновременно все vCPU не смогут получить 100% от своей завяленной мощности. Иными словами, если у вас на одном хосте виртуализации находятся несколько нагруженных машин и их общее количество vCPU больше чем pCPU хоста, то с высокой долей вероятности эти машины будут мешать друг другу, что снизит их суммарную производительность.
Сейчас хотелось бы вернуться к созданию виртуальной машины величиной в весь хост. Хорошо, пусть такая ВМ заняла собой целиком первый таймслот и отработала его. Теперь вспоминаем про системные миры, сопутствующие этой машине. Их тоже надо исполнять, как надо исполнять сам гипервизор и его собственные системные процессы. То есть надо и им выдавать таймслоты и занимать в них некоторое количество pCPU. А что в это время делать нашей большой ВМ? Правильно, ожидать освобождения ВСЕХ pCPU, чтобы иметь возможность там уместиться. То есть мы гарантированно теряем производительность на служебных задачах гипервизора (таймслотах), а это плохо (вспоминаем пример выше с большой и малой машинами). Для примера, программный iSCSI инициатор под высокой нагрузкой потребляет до 6 ГГц процессорной мощности. Это было бы не так заметно в случае с малыми ВМ т.к. они работали бы параллельно служебным процессам (в этих же таймслотах). А для большой ВМ так не получится т.к. она занимает весь таймслот целиком, все его pCPU, и не может уместиться в таймслот, если хоть один его pCPU уже кем-то занят, пусть и системным процессом.
О каких потерях может идти речь при неправильном конфигурировании виртуальной инфраструктуры и размещении машин по нодам? От нуля до бесконечности (в теории). Все зависит от конкретной ситуации.
Отдельно хотелось бы озвучить главное правило при сайзинге виртуальных машин: давайте виртуальной машине МИНИМАЛЬНО возможные ресурсы, при которых она сможет выполнять свои задачи. Не нужно давать ВМ 2 ядра, если хватает одного. Не нужно давать 4, если хватает 2-х (лишние ядра занимают место в таймслоте). Аналогично с памятью, не стоит выдавать машине лишнего. Возможно, другой машине может не хватить, не говоря уже о возникающих проблемах с живой миграцией (по сути копированием объема памяти ВМ) и NUMA.
Теперь, разобравшись с механизмом размещения vCPU виртуальных машин на pCPU таймслота, давайте вспомним про NUMA и его правила размещения. Для шедулера гипервизора все эти правила имеют значение при заполнении таймслота т.к. pCPU таймслота могут относиться к разным NUMA нодам. Теперь, помимо сложностей учета NUMA при конфигурировании виртуальных машин на хосте, мы получили и ограничения, накладываемые методикой работы шедулера гипервизора с таймслотами. Если мы хотим получить хорошую производительность ВМ, нужно обращать внимание на все подводные камни и руководствоваться следующими правилами:
• Стараться не создавать машины-гиганты (по сравнению с размером хоста)
• Для больших машин надо учитывать ограничения, накладываемые технологией NUMA
• Не стоит злоупотреблять с количеством vCPU по отношению к pCPU хоста
• Несколько мелких или средних машин всегда будут иметь преимущество в гибкости и общей производительности перед огромными машинами
Напоследок хотелось бы сказать о работе шедулера гипервизора с вводом-выводом. При обращении виртуальной машины к своему виртуальному аппаратному обеспечению, гипервизор приостанавливает работу ВМ, снимает её с pCPU и ставит в хранилище WAIT. В таком состоянии машина не работает, она просто ждет. В это время гипервизор трансформирует («подделывает») команды виртуального устройства гостевой машины в реальные команды, соответствующие командам гипервизора, после чего гипервизор возвращает виртуальную машину в конвейер READY. Аналогичная «заморозка» виртуальной машины происходит и при ответе виртуального устройства машине (гипервизору необходимо снова трансформировать ответ, но уже в обратную сторону ). Чем больше команд ввода-вывода производит виртуальная машина, тем чаще она находится в «заморозке» WAIT, и тем меньше её производительность. Чем более «старые» виртуальные устройства ввода-вывода использует виртуальная машина, тем сложнее гипервизору трансформировать команды, и тем дольше ВМ находится в состоянии WAIT.
VMWare прямо и официально не рекомендует виртуализовывать приложения с гиперактивным вводом-выводом. Уменьшить негативное влияние от состояния WAIT можно с помощью использования паравиртуальных устройств для виртуальной машины. Это 10-гигабитная сетевая карта VMXNET3 и паравиртуальный SCSI контроллер жесткого диска PVSCSI. Так же уменьшению влияния WAIT и общему повышению производительности способствует применение в физических серверах аппаратных устройств, предназначенных для ускорения работы виртуальных машин. Это различные сетевые и HBA адаптеры с поддержкой аппаратного iSCSI offload, прямого доступа к памяти, сетевые карты с поддержкой виртуализации и т.д.
На этом я хотел бы остановиться. Надеюсь, информация в данной статье была вам интересна, и вы сможете более эффективно подойти к построению или эксплуатации своей виртуальной инфраструктуры.