Хабрахабр

Steal: кто крадёт у виртуалок процессорное время

Хочу рассказать простым языком о механике возникновения steal внутри виртуальных машин и о некоторых неочевидных артефактах, которые нам удалось выяснить при его исследовании, в которое мне пришлось погрузиться как техдиру облачной платформы Mail.ru Cloud Solutions. Привет! Платформа работает на KVM.

Это время считается только в гостевых операционных системах в средах виртуализации. CPU steal time — это время, в течение которого виртуальная машина не получает ресурсы процессора для своего выполнения. Но мы решили разобраться, даже поставили целый ряд экспериментов. Причины, куда деваются эти самые выделенные ресурсы, как и в жизни, весьма туманны. Не то чтобы мы теперь всё знаем о steal, но кое-что интересное сейчас расскажем.

1. Что такое steal

Итак, steal — это метрика, указывающая на нехватку процессорного времени для процессов внутри виртуальной машины. Как описано в патче ядра KVM, steal — это время, в течение которого гипервизор выполняет другие процессы на хостовой ОС, хотя он поставил процесс виртуальной машины в очередь на выполнение. То есть, steal считается как разница между временем, когда процесс готов выполниться, и временем, когда процессу выделены процессорное время.

При этом гипервизор не уточняет, какие именно другие процессы он выполняет, просто «пока занят, тебе времени уделить не могу». Метрику steal ядро виртуальной машины получает от гипервизора. Ключевых моментов здесь два: На KVM поддержка подсчёта steal добавлена в патчах.

  • Виртуальная машина узнаёт о steal от гипервизора. То есть, с точки зрения потерь, для процессов на самой виртуалке это непрямое измерение, которое может быть подвержено различным искажениям.
  • Гипервизор не делится с виртуалкой информацией о том, чем другим он занят — главное, что он не уделяет время ей. Из-за этого сама виртуалка не может выявить искажения в показателе steal, которые можно было бы оценить по характеру конкурирующих процессов.

2. Что влияет на steal

2.1. Вычисление steal

По сути, steal считается примерно так же, как и обычное время утилизации процессора. Информации о том, как считается утилизация, не много. Наверное, потому что большинство считает этот вопрос очевидным. Но здесь тоже бывают подводные камни. Для ознакомления с этим процессом можно прочитать статью Brendann Gregg: вы узнаете о куче нюансов при расчете утилизации и о ситуациях, когда этот подсчёт будет ошибочным по следующим причинам:

  • Перегрев процессора, при котором пропускаются такты.
  • Включение/выключение турбобуста, в результате которого изменяется тактовая частота процессора.
  • Изменение продолжительности кванта времени, происходящее при использовании технологий энергосбережения процессора, например SpeedStep.
  • Проблема подсчёта среднего: оценка утилизации в течение одной минуты на уровне 80 % может спрятать кратковременный бурст в 100 %.
  • Циклическая блокировка (spin lock) приводит к тому, что процессор утилизирован, но пользовательский процесс не видит продвижения по своему выполнению. В результате расчётная утилизация процессора процессом будет стопроцентной, хотя физически процессорное время процесс потреблять не будет.

Статьи, описывающей подобный подсчёт для steal, я не нашел (если знаете — поделитесь в комментариях). Но, судя по исходникам, механизм расчёта такой же, как и для утилизации. Просто в ядре добавляется еще один счётчик, непосредственно для процесса KVM (процесса виртуальной машины), который считает длительность пребывания процесса KVM в состоянии ожидания процессорного времени. Счётчик берет информацию о процессоре из его спецификации и смотрит, все ли его тики утилизированы процессом виртуалки. Если все, то считаем, что процессор занимался только процессом виртуальной машины. В ином случае мы информируем, что процессор занимался чем-то ещё, появился steal.

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

2.2. Типы виртуализации на KVM

Вообще говоря, есть три типа виртуализации, и все они поддерживаются KVM. От типа виртуализации может зависеть механизм возникновения steal.

В этом случае работа операционной системы виртуальной машины с физическими устройствами гипервизора происходит примерно так: Трансляция.

  1. Гостевая операционная система посылает своему гостевому устройству команду.
  2. Драйвер гостевого устройства принимает команду, формирует запрос для BIOS устройства и отправляет её в гипервизор.
  3. Процесс гипервизора производит трансляцию команды в команду для физического устройства, делая её, в том числе, более безопасной.
  4. Драйвер физического устройства принимает модифицированную команду и отправляет её уже в само физическое устройство.
  5. Результаты выполнения команд идут обратно по тому же пути.

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

В этом случае устройство на аппаратном уровне понимает команды из операционной системы. Аппаратная виртуализация. Но, к сожалению, он поддерживается далеко не всеми физическими устройствами, гипервизорами и гостевыми операционками. Это самый быстрый и хороший способ. На текущий момент основные устройства, которые поддерживают аппаратную виртуализацию, — это процессоры.

Самый распространённый вариант виртуализации устройств на KVM и вообще самый распространенный режим виртуализации для гостевых операционных систем. Паравиртуализация (paravirtualization). Недостаток этого способа виртуализации — необходимость модификации ядра гостевой операционной системы, чтобы оно могло взаимодействовать с гипервизором с помощью этого API. Особенность его в том, что работа с некоторыми подсистемами гипервизора (например, с сетевым или дисковым стеком) или выделение страниц памяти происходит с использованием API гипервизора, без трансляции низкоуровневых команд. В KVM это API называется virtio API. Но обычно это решается за счет установки специальных драйверов на гостевую операционную систему.

Это позволяет ускорить выполнение всех инструкций внутри виртуальной машины. При паравиртуализации, по сравнению с трансляцией, путь до физического устройства значительно сокращается за счёт отправки команд напрямую из виртуальной машины в процесс гипервизора на хосте. Именно поэтому внутрь виртуальных машин ставятся virtio-драйверы. В KVM за это отвечает virtio API, который работает только для определенных устройств, вроде сетевого или дискового адаптера.

Это создаёт некоторые спецэффекты, которые могут привести к появлению на steal. Обратная сторона такого ускорения — не все процессы, которые выполняются внутри виртуалки, остаются внутри неё. Подробное изучение этого вопроса рекомендую начать с An API for virtual I/O: virtio.

2.3. «Справедливый» шедулинг

Виртуалка на гипервизоре является, фактически, обычным процессом, который подчиняется законам шедулинга (распределения ресурсов между процессами) в ядре Linux, поэтому рассмотрим его подробнее.

6. В Linux используется так называемый CFS, Completely Fair Scheduler, начиная с ядра 2. Чтобы разобраться с этим алгоритмом, можно почитать Linux Kernel Architecture или исходники. 23 ставший диспетчером по умолчанию. Чем больше процессорного времени требует процесс, тем меньше этого времени он получает. Суть CFS заключается в распределении процессорного времени между процессами в зависимости от длительности их выполнения. Это гарантирует «честное» выполнение всех процессов — чтобы один процесс не занимал все процессоры постоянно, и остальные процессы тоже могли выполняться.

Давние пользователи Linux наверняка помнят замирание обычного текстового редактора на десктопе во время запуска ресурсоемких приложений типа компилятора. Иногда такая парадигма приводит к интересным артефактам. CFS считает, что это нечестно, поэтому периодически останавливает текстовый редактор и даёт процессору обработать задачи компилятора. Так получалось, потому что нересурсоемкие задачи десктопных приложений конкурировали с задачами, активно потребляющими ресурсы, такими как компилятор. Собственно, это рассказ не про то, как всё плохо в CFS, а попытка обратить внимание на то, что «честное» распределение процессорного времени — не самая тривиальная задача. Это поправили с помощью механизма sched_autogroup, но остались многие другие особенности распределения процессорного времени между задачами.

Это нужно, чтобы выгнать зажравшийся процесс с процессора и дать поработать другим. Ещё один важный момент в шедулере — preemption. При этом сохраняется весь контекст таски: состояние стека, регистры и прочее, после чего процесс отправляется ждать, а на его место встает другой. Процесс изгнания называется context switching, переключение контекста процессора. Частое переключение контекста может говорить о проблеме в ОС, но обычно оно идет непрерывно и ни на что особенно не указывает. Это дорогая операция для ОС, и используется она редко, но по сути ничего плохого в ней нет.

Правильно это или нет — сложный вопрос, который при разных нагрузках решается по-разному. Такой длинный рассказ нужен для объяснения одного факта: чем больше ресурсов процессора пытается потребить процесс в честном шедулере Linux, тем быстрее он будет остановлен, чтобы другие процессы тоже могли поработать. В Sun Solaris было пять различных классов шедулеров. В Windows до недавнего времени шедулер был ориентирован на приоритетную обработку десктопных приложений, из-за чего могли зависать фоновые процессы. Подробное изучение этого вопроса рекомендую начать с книг вроде Solaris Internals: Solaris 10 and OpenSolaris Kernel Architecture или Understanding the Linux Kernel. Когда запустили виртуализацию, добавили шестой, Fair share scheduler, потому что предыдущие пять работали с виртуализацией Solaris Zones неадекватно.

2.4. Как мониторить steal?

Мониторить steal внутри виртуальной машины, как и любую другую процессорную метрику, просто: можно пользоваться любым средством съема метрик процессора. Главное, чтобы виртуалка была на Linux. Windows почему-то такую информацию своим пользователям не предоставляет. 🙁


Вывод команды top: детализация нагрузки на процессор, в крайней правой колонке — steal

Можно попробовать спрогнозировать steal на хостовой машине, например, по параметру Load Average (LA) — усредненного значения количества процессов, ожидающих в очереди на выполнение. Сложность возникает при попытке получить эту информацию с гипервизора. Методика подсчёта этого параметра непростая, но в целом, если пронормированный по количеству потоков процессора LA больше 1, это говорит о том, что сервер с линуксом чем-то перегружен.

Очевидный ответ — процессора. Чего же ждут все эти процессы? Вспомните, как отваливается NFS и как при этом растёт LA. Но ответ не совсем правильный, потому что иногда процессор свободен, а LA зашкаливает. Но на самом деле, процессы могут ожидать окончания любой блокировки, как физической, связанной с устройством ввода/вывода, так и логической, например мьютекса. Примерно так же может быть и с диском, и с другими устройством ввода/вывода. Туда же относятся блокировки на уровне железа (того же ответа от диска), или логики (так называемых блокировочных примитивов, куда входит куча сущностей, mutex adaptive и spin, semaphores, condition variables, rw locks, ipc locks...).

К примеру, 100 процессов конкурируют за один файл, и тогда LA=50. Ещё одна особенность LA в том, что оно считается как среднее значение по операционной системе. Но для иного криво написанного кода это может быть нормальным состоянием, при том, что плохо только ему, а другие процессы в операционке не страдают. Такое большое значение, казалось бы, говорит о том, что операционке плохо.

Если вы попытаетесь разобраться, то обнаружите, что в статьях на Википедии и прочих доступных ресурсах описаны только самые простые кейсы, без глубокого объяснения процесса. Из-за этого усреднения (причём не меньше, чем за минуту), определение чего-то бы то ни было по показателю LA — не самое благодарное занятие, с весьма неопределёнными результатами в конкретных случаях. Кому лень на английском — перевод его популярной статьи про LA. Всех интересующихся отправляю, опять же, сюда, к Brendann Gregg  — далее по ссылкам.

3. Спецэффекты

Теперь остановимся на основных кейсах появления steal, с которыми мы сталкивались. Расскажу, как они вытекают из всего вышесказанного и как соотносятся с показателями на гипервизоре.

Самое простое и частое: гипервизор переутилизирован. Переутилизация. Внутри всех виртуалок всё тормозит. Действительно, много запущенных виртуалок, большое потребление процессора внутри них, большая конкуренция, утилизация по LA больше 1 (в нормировке по процессорным тредам). В общем, всё логично и понятно. Steal, передаваемый с гипервизора, также растёт, надо перераспределять нагрузку или кого-то выключать.

На гипервизоре одна единственная виртуалка, она потребляет небольшую его часть, но даёт большую нагрузку по вводу/выводу, например по диску. Паравиртуализация против одиноких инстансов. И откуда-то в ней появляется небольшой steal, до 10 % (как показывают несколько проведённых экспериментов).

Steal тут появляется как раз из-за блокировок на уровне паравиртуализированных драйверов. Случай интересный. Из-за обработки прерывания на гипервизоре для виртуалки это выглядит как отправленный запрос, она готова к исполнению и ждёт процессора, но процессорного времени ей не дают. Внутри виртуалки создаётся прерывание, обрабатывается драйвером и уходит в гипервизор. Виртуалка думает, что это время украдено.

Хотя, с точки зрения виртуалки, он должен сразу вернуться. Это происходит в момент отправки буфера, он уходит в kernel space гипервизора, и мы начинаем его ждать. Скорее всего, в этой ситуации могут быть и другие механизмы (например, обработка ещё каких-нибудь sys calls), но они не должны сильно отличаться. Следовательно, по алгоритму расчёта steal это время считается украденным.

Когда одна виртуалка страдает от steal больше других, это связано как раз с шедулером. Шедулер против высоконагруженных виртуалок. Если виртуалка потребляет немного, она почти не увидит steal: её процесс честно сидел и ждал, надо ему давать побольше времени. Чем сильнее процесс нагружает процессор, тем скорее шедулер его выгонит, чтобы остальные тоже могли поработать. Если виртуалка производит максимальную нагрузку по всем своим ядрам, её чаще выгоняют с процессора и стараются не давать много времени.

Тогда операционная система на гипервизоре, за счёт честной оптимизации, будет давать всё меньше процессорного времени. Ещё хуже, когда процессы внутри виртуалки пытаются заполучить больше процессора, потому что не справляются с обработкой данных. И чем больше ядер, тем хуже попавшей под раздачу машине. Этот процесс происходит лавинообразно, и steal подскакивает до небес, хотя остальные виртуалки его могут почти не замечать. Короче говоря, больше всего страдают высоконагруженные виртуалки со множеством ядер.

Если LA примерно 0,7 (то есть, гипервизор, кажется недозагружен), но внутри отдельных виртуалок наблюдается steal: Низкий LA, но есть steal.

  • Уже описанный выше вариант с паравиртуализацией. Виртуалка может получать метрики, указывающие на steal, хотя у гипервизора всё хорошо. По результатам наших экспериментов, такой вариант steal не превышает 10 % и не должен оказывать существенного влияния на производительность приложений внутри виртуалки.
  • Неверно считается параметр LA. Точнее, в каждый конкретный момент он считается верно, но при усреднении за одну минуту получается заниженным. Например, если одна виртуалка на треть гипервизора потребляет все свои процессоры ровно полминуты, то LA за минуту на гипервизоре будет 0,15; четыре такие виртуалки, работающие одновременно, дадут 0,6. А то, что полминуты на каждой из них был дикий steal под 25 % по показателю LA, уже не вытащить.
  • Опять же, из-за шедулера, решившего, что кто-то слишком много ест, и пусть этот кто-то подождёт. А я пока попереключаю контекст, пообрабатываю прерывания и займусь другими важными системными вещами. В итоге одни виртуалки не видят никаких проблем, а другие испытывают серьезную деградацию производительности.

4. Другие искажения

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

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

В конкретной системе вы можете обнаружить что-то ещё. В общем, причин искажений может быть много. Начать лучше с книг, на которые я дал линки выше, и съема статистики с гипервизора утилитами типа perf, sysdig, systemtap, коих десятки.

5. Выводы

  1. Какое-то количество steal может возникать из-за паравиртуализации, и его можно считать нормальным. В интернете пишут, что эта величина может составлять 5-10 %. Зависит от приложений внутри виртуалки и от того, какую нагрузку она даёт на свои физические устройства. Тут важно обращать внимание на то, как себя чувствуют приложения внутри виртуалок.
  2. Соотношение нагрузки на гипервизоре и steal внутри виртуалки не всегда однозначно взаимосвязаны, обе оценки steal могут быть ошибочными в конкретных ситуациях при разных нагрузках.
  3. Шедулер плохо относится к процессам, которые много просят. Он старается давать меньше тем, кто просит больше. Большие виртуалки — зло.
  4. Небольшой steal может быть нормой и без паравиртуализации (с учётом нагрузки внутри виртуалки, особенностей нагрузки соседей, распределения нагрузки по тредам и прочих факторов).
  5. Если вы хотите выяснить steal в конкретной системе, приходится исследовать различные варианты, собирать метрики, тщательно их анализировать и продумывать, как равномерно распределять нагрузку. От любых кейсов возможны отклонения, которые надо подтверждать экспериментально или смотреть в дебагере ядра.
Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»