Почему инфраструктура big tech обычно состоит из самописных решений
Привет! Предлагаю поговорить о том, почему крупные IT‑компании так любят создавать в своей инфраструктуре собственные решения. Казалось бы, напрашивается ответ: NIH‑синдром и ничего более. Но такой ответ вряд ли может считаться сколько-нибудь полным, а тем более претендующим на объективность.
Меня зовут Дмитрий, я CTO в команде Yandex Platform Engineering. Наша задача — помогать инженерам выстраивать весь цикл разработки от написания кода до эксплуатации сервисов и делать его эффективнее. Такая работа включает настройку процессов: мы не просто делаем нечто as a service, но и помогаем эти самые as a service внедрять внутри компании. И всё это работает на масштабах Яндекса: нашими сервисами пользуются тысячи разработчиков.
Это далеко не все инструменты в нашей инфраструктуре, но сегодня поговорим об этих примерах.
Часто для решения задачи мы не внедряем готовые, а разрабатываем собственные инструменты. Например, ещё будучи программистом в команде, я занимался системой количественного мониторинга на C++ и Python и помогал масштабировать её до десятков миллиардов обрабатываемых метрик. Так что на собственном опыте знаю, какие мотивы и пути развития ведут к появлению самописных инструментов. Постараюсь выделить системные причины их создания на конкретных примерах наших решений.
История #1: внутреннее облако Яндекса
Постановка задачи. Наше внутреннее облако Runtime Cloud, или сокращённо RTC, является частью Deploy Platform. Задача RTC — дать внутренним пользователям удобные инструменты деплоя и управления трафиком. Пользователи RTC — те самые инженеры, которые разрабатывают сервисы Яндекса. А все десятки тысяч созданных приложений нужно где‑то запускать, направлять туда запросы пользователей, балансировать нагрузку, разбирать инциденты и так далее.
Потребность во внутреннем облаке возникла ещё в начале 2010-х годов: в этот период число сервисов уже исчислялось сотнями, суммарное количество аллоцированных ядер росло на десятки процентов каждый год. Иметь выделенные серверы под каждый конкретный сервис стало дорогим удовольствием, и нам потребовались инструменты, которые позволяют запускать приложения разных сервисов на одном сервере. На старте мы предъявляли к этим инструментам несколько требований:
Нужно автоматизировать рутинные действия: ведь это позволило бы сэкономить ресурсы эксплуатации и снизить количество инцидентов при выкладках.
Важно было повысить утилизацию облака, тем более что по ночам оно простаивало, поскольку большая часть сервисов имела суточный паттерн в своей нагрузке. Ситуация осложнялась тем, что далеко не все сервисы Яндекса в те годы жили в облаке, и с постоянным ростом числа серверов проблема становилась всё острее.
Было необходимо быстро докатывать фичи или фиксы багов до пользователя или добавлять мощностей в сервисы, так как это напрямую влияло на скорость развития Яндекса.
Была нужна поддержка IPv6-only: для предоставления сквозной связности между подами наши дата‑центры строились IPv6-only, поскольку на наших масштабах диапазона адресов IPv4 нам бы не хватило.
По сути, нам был необходим Kubernetes (и со временем RTC сильно к нему приблизился). Но вот проблема — k8s был анонсирован только в 2014 году. На тот момент существовал ещё Apache Mesos, но он был слишком сырым.
Реализация базовых функций. Мы начали решение проблемы со своеобразного MVP — простого набора инструментов, который представлял собой скорее набор кубиков, автоматизирующих рутинные действия, например:
выделение ресурсов на кластере;
доставка нужной версии приложения и различных артефактов до кластера;
запуск нужной версии приложения на кластере.
Со временем появилась возможность описать из этих кубиков полноценный граф выкладки сервиса (что напоминало continuous delivery). Через какое‑то количество итераций это привело к появлению в 2013 году Nanny — системы управления сервисами, которые запущены в RTC.
Важной базовой частью Nanny стало внедрение изоляции приложений по потребляемым ресурсам. Первое время мы запускали приложения разных сервисов без изоляции по ресурсам, это приводило к большому числу эксплуатационных проблем и инцидентов.
Из готовых решений на тот момент был доступен либо LXC, который уже толком не развивался, либо Docker, который тогда не умел в IPv6-only. К тому же он рестартовал все контейнеры при обновлении dockerd, и это делало невозможным обновление самого dockerd без влияния на пользователя. Поэтому примерно в середине 2014-го мы начали разработку своей системы контейнеризации Porto поверх cgroup ядра Linux. Уже в 2015-м большинство приложений и часть системных агентов на серверах, переехали в контейнеры Porto.
Решение проблем утилизации. На тот момент управление ресурсами во внутреннем облаке делалось через коммит в репозиторий. Но это тормозило развитие Яндекса и шло в противоречие с задачей повышения утилизации. Для её решения необходимо было заселить в облака нашу систему map reduce, а именно YTsaurus — собственную внутреннюю систему для хранения больших данных и распределённых вычислений. Но её нужно было не просто завести во внутреннее облако, но потом ещё и запустить рядом с приложениями пользователей.
Для заезда YTsaurus в RTC нужна было возможность управлять подами динамически, а не через коммиты в репозиторий. Поэтому в 2018 году мы разработали Yandex Planner — систему, которая управляет вычислительными ресурсами кластера. Yandex Planner интегрировали с существующей в то время системой деплоя Nanny, этим разблокировали миграцию YTsaurus и сделали RTC полноценным облаком. К тому моменту в RTC было несколько десятков тысяч серверов, а с заездом map reduce это число выросло в разы.
Новые болезни роста. Параллельно с этим k8s развился в гораздо более зрелое решение: например, с 2017 года стал одним из сервисов AWS. Но он по‑прежнему не закрывал все наши потребности:
Масштаб в десятки тысяч серверов — одна инсталляция k8s до сих пор не вмещает в себя наши объёмы.
Поддержку dual stack IPv6 и IPv4 — в k8s она появилась только в 2020 году, а IPv6 был для нас критичен с самого начала.
Поддержку вложенных контейнеров, так как мы решили делать отдельные планировщики под batch и compute. В своё время мы сравнивали эффективность этого варианта с вариантом общего планировщика — удобнее и выгоднее было не пытаться сделать общий планировщик для всего.
YTsaurus активно использовал возможность создания вложенных Porto‑контейнеров, вместо того чтобы делать единый планировщик. Конечно же, поддержку того же dual stack мы могли бы реализовать самостоятельно в k8s. Но опыт разработки ядра Linux показывал, что далеко не всё получится отправить в опенсорс, а мы стремимся держать дельту с upstream ядра минимальной, чтобы упростить обновление на новые версии.
Наше решение сегодня. По архитектуре RTC действительно сильно напоминает Kubernetes. Пользователь создаёт спецификацию, в которой декларативно описывает свой сервис и указывает, как поднять приложение и в каких дата-центрах. В каждом дата‑центре поднята независимая инсталляция Yandex Planner, который с одной стороны выполняет роль базы данных для всех объектов кластера с удобным API, а с другой — роль планировщика подов. На каждом сервере в дата‑центре запущен агент, получающий спецификации подов из Yandex Planner и запускающий их через нашу собственную систему контейнеризации Porto.
На текущий момент в RTC поднято несколько десятков тысяч сервисов, аллоцирующих более 5 миллионов ядер на более чем 100 тысяч серверов. За сутки в спецификации сервисов вносится более 100 тысяч изменений.
Планы. Что если k8s сможет переварить наши объёмы? Экосистема k8s в какой‑то момент стала обгонять нас с точки зрения функциональности. Не лучше ли было переехать с расчётом, что готовые инструменты со временем обеспечат нужный нам объём?
На практике мы остаёмся специфичным потребителем для k8s, потому что такие объёмы есть лишь у малого числа компаний, у каждой из которых уже свои решения. Здесь нужно помнить про ещё одну немаловажную деталь — проблему миграции. По данным отчёта Gartner Building a Multiplatform Application Modernization Business Case за июль 2018 года, 90% современных приложений всё ещё будут использоваться к 2025, а технический долг на развитие этих систем составит более 40% IT‑бюджетов. Как показывает наш опыт миграции сервисов пользователей в Yandex Planner, это близко к реальности: в 2023 году около 100k ядер всё ещё ждало своей очереди на переезд в Yandex Planner.
В 2021 году мы выбирали свою стратегию развития и прикидывали, сколько стоит переезд из одной системы деплоя в другую. Миграция на ванильный k8s была бы для Яндекса очень дорогой задачей суммарной стоимостью в сотни человеко‑лет
Вот таким нехитрым образом мы оказались со своим внутренним облаком, от которого вряд ли получится отказаться в ближайшие 5 лет, даже если озадачиться такой целью.
Что делать с недостающей функциональностью внутреннего облака по сравнению с k8s? На практике у наших пользователей есть возможность воспользоваться Managed Kubernetes в Yandex Cloud. Этим вариантом в первую очередь пользуются те проекты, которым важно соблюдать жёсткие требования compliance, — это небольшая доля команд, меньше процента. Остальные в целом не видят для себя большой выгоды в переезде по указанным выше причинам.
Вместе с тем мы активно смотрим на k8s и думаем о том, как приблизиться к общепринятым стандартам. В некоторых задачах мы уже активно пробуем k8s: для bootstrap самого облака или же для организации IaaC на масштабах всего Yandex. В идеале хотелось бы стремиться к переиспользованию интерфейса k8s, но при этом сохранить собственную реализацию, максимально заточенную под наши задачи. Осталось лишь придумать, как этого добиться на практике.
А как у других. Поскольку в этой статье мы говорим о big tech в целом, стоит подчеркнуть, что наша ситуация не уникальна. Для примера можно посмотреть на историю Twine или Borg. По сути, Kubernetes был вдохновлён именно Borg’ом.
История #2: монорепозиторий
Задачи и требования к решению. Основная цель нашего монорепозитория под названием Arcadia такая же, как у внутреннего облака, — дать удобные инструменты разработки. В случае с репозиторием это включает целую экосистему разработки:
систему контроля версий,
интерфейс к нему,
систему сборки,
CI/CD.
Arcadia начала появляться примерно в те же годы, что и внутреннее облако в Яндексе. Одной из причин появления монорепозитория была необходимость переиспользовать код внутри Яндекса. На тот момент этому мешало наличие нескольких систем сборки. А для работы на масштабах всего Яндекса нужна была единая система с поддержкой эффективной распределённой сборки. Кроме этого она должна была быть стабильной и удобной.
Реализация единой системы сборки. Наша собственная система сборки появилась в 2013 году, когда речь шла только про код на C++. До её появления мы использовали CMake —, но он не растягивался на масштабы Arcadia из‑за скорости работы. Своя система сборки на всём монорепозитории отрабатывала на порядок быстрее. А иных опенсорс‑вариантов, способных решать нашу задачу, не было: например, Bazel появился сильно позже, в 2015 году.
Масштабирование системы контроля версий. Ранее в Яндексе для контроля версий использовался SVN. У SVN огромная ёмкость, но всё‑таки она была ограничена, а её поддержка стоила трудов. Кроме того, мы понимали что со временем упрёмся в функциональность и удобство SVN. Например, та же возможность скачать лишь нужную часть репозитория или сделать селективный checkout по факту была реализована на эвристиках. Поэтому в 2016 мы начинаем эксперименты с другими системами контроля версий, отличными от SVN.
Сперва выбор пал на Mercurial, но мы столкнулись с проблемой скорости. Понадобилось полтора года, чтобы попытаться довести Mercurial до production, но результаты оказались неудовлетворительными. Например, в какой‑то момент мы дошли до необходимости переписывать куски самого Mercurial для поддержки FUSE, иначе пришлось бы приносить весь репозиторий каждому разработчику на ноутбук.
В итоге оказалось, что написать своё решение с нуля будет дешевле. В 2019 году появляется новая система контроля версий для пользователей в Arcadia с git‑like UX. Фундаментом системы является FUSE (filesystem in user space) вместо селективного checkout. А в качестве масштабируемой БД выступает YDB, что сильно упрощает эксплуатацию в сравнении с Mercurial.
Здесь нам часто задают вопрос: почему не взять git? С ним мы тоже упёрлись в ограничения масштаба и функциональности: если импортировать только лишь транк Arcadia в git, то git status на таком масштабе будет отрабатывать минуты. А стабильной реализации FUSE поверх git не было: VFS for Git по факту не развивается, а EdenFS со временем превратился в Sapling, но это тоже произошло сильно позже.
Решение сегодня и планы. Чтобы начать разрабатывать, внутреннему пользователю достаточно создать папку в нашем монорепозитории, написать код и подсказать системе, как нужно собрать его приложение, положив рядом сборочный манифест. В результате пользователь получает пул‑реквесты, настроенный за него CI и возможность переиспользовать любой код в компании.
И немного про масштабы. На текущий момент trunk содержит 10 миллионов файлов, размер репозитория в целом превышает 2 TiB, а каждую неделю делается более 30 тысяч коммитов.
В такой собственноручно созданной экосистеме нам по инерции приходится делать с нуля многие компоненты. Но сейчас она движется в сторону совместимости с мировыми стандартами. Например, система контроля версий поддерживает работу через Git для заранее выбранного набора проектов.
А как у других. Опять, если посмотреть на big tech в целом, то для примера стоит обратить внимание на Sapling и Piper.
Что общего у этих историй
Так для чего же компаниям big tech приходится изобретать своё, и почему не получается заменить самописные решения на общепринятый стандарт?
Инновации. Крупным компаниям часто приходится что‑то придумывать для решения тех проблем, которые встанут перед лицом большинства лишь в перспективе. Так могут появиться инновационные решения, которые в теории имеют шанс стать общепринятыми стандартами.
Далеко не всегда решаемая компанией проблема встаёт перед кем‑либо кроме самой компании. Иногда опыт big tech позволяет и вовсе избавиться от таких проблем, потому что индустрия решит пойти иным путём. Предсказать развитие рынка удаётся не всегда, и в результате у разных примеров самописных решений совершенно разная судьба.
Пример удачного проекта, который вначале своего пути был «велосипедом» — ClickHouse, без которого мир OLAP стал бы гораздо беднее. Но далеко не все проекты таковы. Тот же Porto, разработка которого изначально велась в опенсорс, не обрёл какой‑либо популярности по многим причинам. Хотя до сих пор некоторые фичи являются уникальными, например, возможность создания вложенных контейнеров.
Масштабы. В каком‑то плане этот пункт можно считать специализацией предыдущего, потому что проблема масштаба встаёт далеко не перед каждой компанией. Раньше и 640 кбайт хватало всем, правда?
По факту одной из важнейших причин развития Arcadia и внутреннего облака, был экспоненциальный рост нагрузки на систему. По этой причине появились наша собственная система контроля версий и Yandex Planner. Система контроля версий создана в ответ на потребность в удобном VCS, который может позволить пользователям без боли работать с монорепозиторием, содержащим несколько десятков миллионов файлов в trunk. Yandex Planner — в ответ на необходимость эффективно работать с кластерами, которые содержат десятки тысяч нод и миллионы подов.
С такими объёмами у общедоступных инструментов по‑прежнему есть проблемы (ведь это довольно редкий сценарий, и в него часто просто невыгодно вкладываться).
Инерция. Представим себе некоторый приватный инструмент, решающий определённую проблему. В любой активно используемый инструмент вкладываются ресурсы, чтобы лучше адаптировать его под нужды компании. По факту у инструмента появляется специализация под конкретные задачи компании. Этот процесс может длиться годами.
Теперь представим себе, что наконец‑то появился общепринятый стандарт для решения той самой проблемы. В этом случае специализация может всё равно играть решающую роль при выборе в пользу самописного решения. Для примера возьмём системы сборки. В Arcadia мы используем свою систему, хотя и есть bazel от Google. Концептуально они похожи, но если начать копать в детали, то многие важные сценарии сделаны очень по‑разному, ведь паттерны нагрузки у каждого workload могут быть совершенно разными. Поэтому уже потраченные ресурсы с высокой вероятностью придётся вложить ещё раз для кастомизации нового общепринятого стандарта.
Миграции. Если предыдущий пункт касается проблемы адаптации проекта под пользователей, то теперь перейдём к вопросу миграции самих пользователей. На мой субъективный взгляд, миграции стоит назвать следующей после нейминга проблемой в IT. Если представить, что у нас уже есть некоторый приватный инструмент, а мы хотим заменить его на общепринятый стандарт, то мы неизбежно придём к необходимости миграций.
Из истории развития внутреннего облака мы знаем много примеров миграций. На больших масштабах миграции зачастую выливаются в долгострои, на время которых приходится поддерживать оба инструмента. А при необходимости участия большого числа пользователей неизбежно возникают административные проблемы. Сделать миграции без участия пользователя обязательно стоит попытаться, но далеко не всегда это возможно.
Business Continuity. Справедливости ради, этот пункт приобрёл достаточный вес не так давно. Раньше он всерьёз рассматривался гораздо меньшим числом компаний, которые сразу обращали внимание на риски вендорлока. Тем не менее, зависеть в критичных процессах от вендора, который может в любой момент прервать сотрудничество — довольно рисково. Яркий тому пример — продукты JetBrains, IDE которых более недоступны для некоторых компаний. Или тот же Github Enterprise.
Самописные решения обычно не подвержены этой проблеме. С одной стороны, у нас остаётся опенсорс. Но с другой, нет никаких гарантий, что опенсорс-проектам с вами по пути: например, Corona в своё время появилась из‑за невозможности закоммитить патчи, нужные для масштабирования Hadoop в upstream.
При этом юридическая сторона вопроса касается и опенсорс-решений: например, коммиты в golang или k8s требует подписать CLA. Не будет ли с этим проблем в будущем?
NIH. Да, помимо объективных причин бывает и так, что принимаемые решения не получается назвать прагматичными. NIH‑синдром во всей красе.
Например, мы пытались сделать в ядре Linux свой планировщик в попытке исключить влияние batch на compute. На практике ничего хорошего из этого не вышло, можно было обойтись и существующими возможностями ядра Linux. Но чем крупнее трудозатраты, тем больше сил вкладывается в проработку и постановку задачи, и тем меньше шансов пострадать от NIH‑синдрома.
Подведём итоги. Как видим, самописные решения для крупных компаний — зачастую насущная необходимость. В перспективе многие из них сольются с мировыми стандартами по мере взросления последних, а остальные — станут частью истории. В любом случае делать очередное самописное решение или же воспользоваться готовым — это по‑прежнему сложный вопрос, на который нельзя дать ответ без знания контекста и оценки стоимости такого проекта.