Разработка и эксплуатация ядра Linux в нашей инфраструктуре. Доклад Яндекса

Внутреннее облако Яндекса состоит из сотен тысяч серверов в нескольких дата-центрах России и Европы. Все эти серверы работают под управлением ядра Linux. Из доклада старшего разработчика Дмитрия Монахова вы узнаете, как и зачем Яндекс разрабатывает и эксплуатирует ядро, в чем уникальная специфика ядерных задач, которые нужно решать на наших масштабах, и почему мы выбрали стратегию максимальной открытости ядра.

— Здравствуйте, все! Забавно: в последний раз я выступал в Яндексе несколько лет назад, работал в другой компании, но тоже рассказывал про фрагментацию, правда файловых систем. Сейчас рассказываю про ядро Linux.

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

Введение

Для начала, что такое внутреннее облако Яндекса? Нас очень часто путают с внешним облаком Яндекса, с Yandex.Cloud, о нем все знают.

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

Внутреннее облако Яндекса — это 100 тысяч серверов в пяти дата-центрах Европы и России. Железо обновляется каждые три года. Недавно у нас начали появляться GPU-кластеры: быстрые, надежные, хорошие.

Стратегии разработки ядра Linux

Вопрос, нужно ли именно нам что-то разрабатывать? Может быть, мы возьмем классическое ядро от какого-то дистрибутива и будем жить отлично? Нет, потому что речь идет об эксплуатации ядра на таких объемах. Представьте: железо разное, приложение разные, все разное, а ядро одно, и оно должно выдерживать такие нагрузки.

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

Но есть хорошие новости — так называемое «квантование сервера». Мы в основном привыкли делить сервер по ядрам. Что, если мы сэкономим 1% использования CPU на моем телефоне? Мне это ничего не даст, можно в резюме такую статью добавить, патч кому-то показать, но это ничего не даст. Однако если мы сможем сэкономить 1% на современном сервере, на новом EPYC, — это будет означать, что мы можем сэкономить уже целое ядро.

Если мы что-то смогли оптимизировать и докатить это до продакшена, то сэкономим 100 тысяч ядер. При нынешней цене одного ядра в районе 40 долларов мы снизим расходы практически на четыре миллиона долларов.

Не верите, что в ядре есть вещи, которые можно экономить? Есть. Классическая история. В procfs есть функция форматирования текста. Когда вы в procfs что-то печатаете, приложения постоянно это делают под капотом.

Там есть две исторические функции: printf и put something, то есть форматирование текста. Вторая практически в два раза более эффективна, потому что там нет лишнего копирования памяти. Я сделал очень простой пример, академический, чтобы показать вам, что это возможно: взял функцию форматирования и заменил в ней оставшиеся printf на put something.

seq_printf -> seq_puts,seq_put_c,seq_put_decimal_ull
Заменяем все seq_printf в proc_pid_status
@@ -430,7 +429,7 @@ static inline void task_thp_status(..
-    seq_printf(m, "THP_enabled:t%dn", thp_enabled);
+    seq_put_decimal_ull(m, "THP_enabled:t", thp_enabled);

Что я получил? 11% экономии.

$ ya tool sre funclatency -i100 seq_printf
avg = 460 nsecs, total: 0.961 secs, count: 2084891

$ perf record -g -e probe:seq_printf -a sleep 10
$ perf report
      + 15.03% show_mountinfo
      + 5.82% memcg_stat_show
      + 4.64% blkg_print_stat_bytes
         + 3.25% udp6_seq_show
         + 2.73% netstat_seq_show
         + 1.48% snmp_seq_show
           1.01% snmp6_seq_show
      - 5.26% proc_single_show
         + proc_pid_status
      - 0.73% show_vfsmnt
         + 0.69% _ext4_show_option

Казалось бы, это синтетический пример. Но давайте посмотрим, сколько сейчас printf у нас на сервере потребляет CPU. Я зашел на случайный сервер и посчитал довольно грубо, но все-таки соотносимо с реальностью. Из 100 секунд я нашел примерно секунду, которую использует printf. То есть уже сейчас можно заменить одну функцию на другую и сэкономить один процент.

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

Когда мы поняли, что разрабатывать и оптимизировать полезно, что это превращается в реальные деньги для компании, надо понять, как именно мы будем разрабатывать.

Классический способ начать — выбрать, какую ветку мы берем: самую свежую из trunk или какую-то очень надежную, старую. Со старой веткой все понятно, она известна, все баги там исправлены, огромная база знаний в LKML. Если вы нашли какой-то баг, скорее всего он уже исправлен, его достаточно именно найти.

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

С новыми ветками ядер другие плюсы. Очевидно, что там новые фичи, все хорошо работает на бумаге, но количество багов неизвестно, не проверено временем, и это тоже большая проблема. Самое главное, что может являться шоу-стоппером для выбора свежей ветки: вам надо постоянно апгрейдить системы, а для некоторых юз-кейсов это не подходит.

Допустим, вы отправляете марсоход Perseverance на Марс. Вам нужны не свежие версии, а чтобы все стабильно работало. Не стоит апгрейдить ваш марсоход где-то на орбите.

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

Минимальный патч обладает многими свойствами — мы в синке с мейнстримом, автоматически получаем тестирования от LKML, банально получаем ревью: на то, что мы тут наделали, хотя бы посмотрят люди.

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

Различия в подходах компаний к разработке ядра Linux

Прежде чем переходить к тому, как это делает Яндекс, посмотрим, как это делают другие компании.

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

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

По оси Y — размер патча, по Х — свежесть ревизии. Samsung где-то наверху и слева, потому что использует старую версию ядра, ему не нужна свежая для телевизоров, у него большой приватный патч. NVIDIA и Mellanox вынуждены поддерживать большое количество релизов, поэтому они так расплывчаты.

Это немного не наша ситуация, потому что мы говорим об инфраструктурном облаке Яндекса. Мы скорее сервис-провайдеры.

Как сервис-провайдеры решают такую задачу? Есть две стратегии — полностью закрытая и максимально открытая. Закрытая стратегия — это, когда мы берем что-то из опенсорса и делаем полный fork, полную самоизоляцию из-за огромных масштабов компании.

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

Классическим примером является Amazon. Они как советские ученые-ядерщики: что-то разрабатывают, но что это — секрет, об этом ничего неизвестно.

Amazon я поместил сюда.

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

Классические примеры — Google и Facebook. В Google работает Теодор Цо, Эндрю Мортон. В Facebook — Крис Мэйсон, Йенс Аксбо, приятные люди, прекрасные, делают много и для комьюнити, и для Google, и для Facebook. Надо понимать, что я в данном аспекте говорю не про весь софт, а именно про ядро. Ни Google, ни Facebook особенно нет смысла скрывать какие-то вещи на уровне ядра. У них есть много приватных наработок в user space, в железе, но на уровне ядра не очень много.

Еще примеры — компании чуть поменьше масштабом, но которые тоже очень много делают для мейнстрима. RedHat, Intel, Acronis, Virtuozzo — мои прекрасные бывшие коллеги, которым передаю привет.

У Red Hat патч больше, потому что они поддерживают огромное количество драйверов, у Facebook и Google чуть меньше, они поддерживают более свежие ветки.

Размер работающей у нас с ядром команды чуть меньше, поэтому мы стараемся, чтобы наш приватный патч был еще меньше. При этом стараемся жить на свежих ветках так же, как Google, Facebook, Red Hat.

Инфраструктурное облако Яндекса (RTC) в дателях

Теперь давайте, поняв, чего мы хотим, вернемся к тому, зачем мы разрабатываем ядро. Руководителям не так важно, чем huge page отличаются от обычных page, а ticket spinlock от обычного spinlock. Они хотят, чтобы ядро на всех 100 тысячах серверов работало надежно, пользователи были рады и деньги экономились.

Мы подходим к этому как хакеры — видим свою основную задачу так: нормируем любой баг на боль пользователя, которая у него стреляет. На мой телефон приходят все падения, которые случаются у нас на кластере, и не все из них одинаково полезны. Если что-то упало один раз в год, наверное, пропустим. Важно только то, что действительно досаждает нашим пользователям — разработчикам Яндекса.

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

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

Дальше классическая история, с которой сталкиваются все компании: ловушка надежности какого-нибудь центрального компонента. Есть ядро, мы в нем сделали несколько прекрасных фич, но не знаем, можно ли его катить в прод. Чтобы это сделать, надо, чтобы оно было надежным, но чтобы оно было надежным, его сначала надо протестировать на проде.

Замкнутый круг, это очень сложной задача для коллег из нашего внешнего облака, потому что у них нагрузка — пользовательские ВМ, а ронять пользовательские ВМ нельзя. А мы можем иногда апгрейдить наши сервера, я объясню, как.

Какие пользователи бывают в нашем runtime cloud?

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

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

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

Модель разработки ядра Linux в Яндексе

Теперь, когда мы поняли, что можем разрабатывать и внедрять что-то в продакшен, давайте смотреть, как мы это делаем.

Делаем очень просто — базируемся на стабильных ветках мейнстрима, которые делает прекрасный Greg K-H. Сейчас у нас в Stable последняя ветка «мажорника» 4.x, мы внедряем свежую 5.x. То есть нет старых версий, как у производителей железа. Все довольно новое, и это приятно.

Какие принципы мы воспитываем при разработке? Отдавать как можно больше в мейнстрим, уменьшать размер приватного патча. Если мы что-то разработали, это само по себе не является секретом, ценностью, которую мы должны хранить. Отдаем и не волнуемся. Это позволяет уменьшать боль при миграции и тратить время на действительно полезные фичи, а не на перекладывание патчей из старого ядра в новое.

Вторая фишка, которая нам очень помогает: у нас нет четкой точки, когда можно сказать, что до этого ядро было тестовое, а сейчас оно продуктовое и не имеет права падать. Мы можем наращивать качество постепенно. Дальше я объясню, как это происходит.

Конечно, у нас не всё совпадает с мейнстримом. Есть небольшой набор приватных патчей. Либо они по какой-то причине не были отправлены, либо их не приняли, либо мы еще не успели их отдать.

Первое из очевидного: мы починили что-то у себя, и оно еще не доехало до мейнстрима, еще не взяли. Второй большой кластер — доработки в cgroups. Так как мы живем в контейнерах, нам надо очень эффективно использовать cgroups, и некоторые фичи не доехали до мейнстрима. Например, каждый думает, что гарантии по CPU должны быть устроены по-разному, комьюнити не договорилось, поэтому мы реализовали их по-своему.

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

Еще один большой класс, который у нас есть: алгоритм самолечащейся сети, живущей на EBPF — стандартном интерфейсе хуков на сети. Сетевой пакет от сервера до пользователя может идти несколькими путями, через несколько свитчей, провайдеров и так далее. Нам надо оптимизировать эти случаи, управляя потоком трафика.

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

Дальше у нас есть небольшие патчи, которые упрощают эксплуатацию ядра, например сейчас в OOPs не печатается hostname. Нормальным людям это не надо: если мой ноутбук упал, я и так знаю, что он упал. У нас слишком много серверов, иначе мы потеряем всё.

Как мы тестируем свое ядро? Представьте: мы что-то разработали, сэкономили, безумно оптимизировали, у нас есть классное свойство, что мы большие. У нас огромный кластер pre-stable из тысячи машин, собранное ядро сразу выезжает на этот собранный кластер.

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

Когда мы уже начинаем внедрять ядро в продакшен, проблемы масштаба нас начинают сильно дисциплинировать, потому что даже редкие баги, как я уже рассказывал, очень больно стреляют по ногам. Более того, никакие ручные подходы не работают, поверьте мне. Либо надо не спать, не есть, а только чинить всё руками. Пробовали — не работает. Всё, что можно автоматизировать, должно быть автоматизировано, реакция на любую железную ошибку — робот уводит хост из продакшена, сломался диск, сеть — все это должно быть автоматизировано.

Как мы наращиваем нашу уверенность в том, что ядро, которое мы произвели, надежно? Мы поделили наш флот на сегменты и выкатываем постепенно. Сначала на тестовый выкатываем не опробованное ядро, прошедшее минимальный тестинг, дальше pre-stable, на котором уже живет какая-то продакшен-нагрузка, но этот сегмент имеет право падать чуть чаще, чем обычный продакшен. И сегмент самого продакшена, на который мы аккуратно выкатываем по степени двойки, тем самым наращивая надежность и ловя баги на ранней стадии, а не выкатывая сразу на всех, чтобы дата-центры не отключались.

Самая прикольная штука, доступная во внутреннем облаке Яндекса: нам доступны эксперименты. Если мы наступили на баг, который стреляет только на продакшене, мы можем себе позволить провести там эксперимент. Выбираем небольшое количество хостов, даже если они упадут, это не нарушит работоспособность всего сервиса. Мы спокойно проверяем на нем любые гипотезы, которые могут прийти нам в голову, тем самым находим даже самые сложные баги. Это уникальная фича, очень прикольно.

Что мы за последнее время сделали? Мы выкатываем свежую пятерку на продакшен, пользователи ловят ошибки, не критично. Очень гордимся тем, что смогли масштабироваться на новое железо — по факту мы сделали скачок в количестве ресурсов на серверах в два-четыре раза. AMD EPYC, у которых 256 HT-трейдов, — это огромное количество, и взять такой сервер на тестирование прекрасно, ядро на нем компилится меньше чем за минуту.

Следующая очень большая задача — настройка GPU/HPC кластеров. Она по нам стреляет, это и хорошо, и плохо.

Планы на будущее

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

Первая история. Прочитав статью Google, мы поняли, что почему-то не классифицируем нашу нагрузку. Сервисы бывают разные, потребляют разные ресурсы, и стоило бы характеризовать нагрузку at scale. Мы начали это делать, начали стрелять редкие баги, на Intel один баг, на AMD другой. Вроде почти починили.

Нам надо научиться классифицировать нагрузку через perf-events на всем флоте. Когда мы поймем, к каким классам принадлежат наши сервисы, то сможем аккуратно их расселить, чтобы они друг другу не мешали.

Допустим, сейчас невозможно ограничить сервис по bandwidth памяти. Он может прийти и съесть весь bandwidth, все остальные будут страдать. Нам это надо доделать, внедрить и отдать в мейнстрим, чтобы можно было удобно пользоваться. Предсказуемый IPC по CPU и так далее.

Делаем улучшения по IO. Мы выбрали такую мейнстрим-ветку, в которой io_uring еще был недостаточно хорошо допилен. Сейчас думаем, что с этим делать: переезжать на более свежую или тащить патчи из мейнстрима.

Следующая огромная задача, которая перед нами всегда стояла и будет стоять, — это железо.

Нам надо всегда быть готовыми к удвоению железа примерно завтра. Это означает, что надо убирать глобальные локи, надо увеличивать Per-CPU, и мы очень стараемся в ближайшее время все-таки убрать из мейнстрима тот последний inode_hash_lock, который мешает не только мне, но и вам, я уверен. Глобальный inode_hash_lock в ядре — это некрасиво и должно быть исправлено.

Еще одна наша задача — поддержка нового железа. ARM, RISC-V уже стучатся в наши двери, сервера почти готовы, и нам надо быть готовыми к их использованию. У нас здесь пока очень мало экспертизы, мы ее растим. Если среди вас есть специалисты и вы готовы что-то подсказать, я буду рад послушать.

Больше GPU-кластеров. Революция BERT произошла, тензоры завоевывают мир, нужно учиться эксплуатировать их максимально эффективно, они стоят гигантских денег, это большая задача.

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

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

Source link

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

Ваш адрес email не будет опубликован.