Станислав Сидристый — Реактивная сборка огромного проекта

Станислав Сидристый — Реактивная сборка огромного проекта59:15

Информация о загрузке и деталях видео Станислав Сидристый — Реактивная сборка огромного проекта

Автор:

DotNext — конференция для .NET‑разработчиков

Дата публикации:

30.08.2024

Просмотров:

853

Описание:

При стремительном росте проекта возникает ряд трудностей не только у разработки, но и у DevOps. Спикер решил помочь коллегам и сделал очень быструю сборку всех проектов. В итоге удалось ускорить сборку с 400 машиноминут (20 агентов, 20 минут) до диапазона (30 сек, 1 мин 30 сек) в зависимости от количества изменений. Через что пришлось пройти, все неудачные варианты и результат — в докладе.

Транскрибация видео

Спикер 4

Так, а мы продолжаем доклады в нашем зале.

Хочу представить Станислава Сидристова, который вы, наверное, уже все знаете, потому что он несменный спикер и на DotNext, и на других конференциях, и даже проводил свой семинар в Пселериуме.

Спикер 3

В общем, не даю о себе забыть.

Спикер 4

Именно так.

Сегодняшний доклад будет про реактивную сборку огромного проекта, который является неким продолжением, наверное, вчерашней темы Build a Squad, которую вы тоже могли увидеть.

Только нацелен на сборку именно огромных проектов.

Спикер 3

Прошу.

Да, спасибо.

Значит...

Да, как сказали, сегодня мы будем пытаться ускорить сборку реально супер-мега-солюшена.

Даже не одного, а целых 100.

Чтобы по итогу наша сборка, которая собирала огромный-огромный-огромный проект, она была для разработчиков как кнопка типа Just Build.

Кнопку нажимаешь, и все.

И через полминуты у тебя уже результат.

Не надо ждать два часа или час, пока это все завершится.

Зовут меня Станислав Сидористый, работаю системным архитектором в Центре речевых технологий.

И что нам дано?

Дан большой монорепозитарий.

в котором исторически люди работали, писали, зарплату получали, радовали заказчика.

Более 100 файлов решений, то есть более 100 solutions.

Это, чтобы вы понимали, такой большой продукт, нацеленный на то, чтобы работать с большим горизонтальным штабированием.

Но, скажем так, в силу специфики заказчиков,

у которых этот продукт работает, грубо говоря, в бункере, условно говоря.

То есть в какой-то комнате под ключом, туда имеет доступ только какой-то сотрудник.

Мы не можем туда прийти, чтобы что-то пофиксить.

Логи надо аккуратно собирать, чтобы, не дай бог, там ничего конфиденциального не было, потому что иначе им нам просто не отдадут.

И т.д.

и т.п.

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

То есть такого у нас нет.

Мы релизимся раз в квартал, и это прям такой срез большой, функционал, который готов к этому моменту.

Поэтому, ну, скажем так, вот такое решение, да, когда много селюшенов в одном инорепе, это удобно.

Вот, становится.

Ну, естественно, там, где 100 файлов решений, там более 1000 файлов проектов.

Потому что не будет же solutions одного делать.

Хорошо.

И выпуск идет общим срезом.

Раньше были, да, вот то, что было дано, отдельные беды под каждый сервис.

То есть TeamCity, там билды настроены, под каждый сервис свой билд.

Дальше есть билд, который называется buildall, и в качестве dependency ему заведены все сервисы.

Ну, тебе надо...

Собрать что-то ты собираешь.

Если надо все собрать, ты собираешь все.

Не хитро.

Не хитро, просто как топор.

Работает.

Естественно, какие проблемы от этого?

Например, у нас есть куча проектов, которые на друга смотрят, друг от друга зависят.

И если изменяется какой-то nugget-пакет, либо какой-то общий проект меняется, то естественным образом надо перебилдить все зависимости.

А как вообще понять, когда у тебя просто есть build all, либо build отдельных сервисов, а ты меняешь какой-то nugget пакет, где-то меняешь, выпускаешь новую версию.

И как понять, какие сервисы в итоге надо пересобрать?

Это называется удача.

В итоге запускаешь build all, на всякий случай.

Жизнь боль, много времени было потрачено зря, впустую и т.д.

и т.п.

И постоянно все это время, пока я на самом деле работал в этой компании, будоражила мысль, что можно сделать просто кнопку Build.

Но самая главная проблема была, как понять, что поменялось.

То есть нажимаешь Build, она как-то должна понять, какие сервисы поменялись и пересобрать только их.

Остальное ничего не трогать.

Проект поменял, который в Nuget складывается.

Нужно понять, что вот он поменялся, он же будет Nuget пакетом.

Этот Nuget пакет где-то используется.

В каких сервисах?

В каких других Nuget пакетах?

Как эту цепочку построить, чтобы она все пересобрала?

И много времени было на это все угроблено.

Да, первичная проблема — трудно отслеживать, что было изменено, трудно отслеживать, что нужно пересобрать.

И версии сервисов должны расти по-хорошему.

Не каждый раз, а только тогда, когда они реально были изменены.

То есть, если они растут каждый раз, то, ну, грош цена вот такому билду.

Ну, пересобрал что-то, да, но там все равно непонятно, то есть, ну, по версиям.

Ну, вот, что конкретно было изменено.

Вот, они все равно будут там, например, версия 10. и билд каунтер.

Вот, и по билд каунтерам, ну, не понять же, что были изменения.

Деф релиз версии как-то проставлять тоже хочется, тоже автоматически.

Итак, что же делать?

Первое, да?

Ну, наверное, все же знают, что если F5 нажать, там типа вот файлик поменял, F5 нажал, он только нужный проект и пересобрал .NET.

Думаешь, ну блин, это выглядит как готовое решение на самом-то деле.

Можно попробовать пойти обычным .NET путем.

Есть прошлый билд, сделали какие-то изменения, есть текущий билд.

И по идее MSBuild сам должен как-то понять по датам изменения файла, что все, короче, что-то там поменялось.

Но не тут-то было.

Гид не сохраняет даты изменения файлов.

То есть как бы, если у вас выкачана репа, и вы с ней работаете, и вы там видите дату модификации файлов, это лично ваши даты модификации файлов.

А туда ничего не уходит.

Это как бы считается, может, лишняя метаинформация, я уж не знаю.

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

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

И они от этого отказались.

Ну, давайте починим.

Как же так?

Кстати, была ссылка на Анатолия Кулакова.

И он сказал, типа, Nuke Build используйте.

А я, видимо, на прошлом докладе сказал, что мне Яндекс.Зен начал предлагать фитнес для людей за 50.

Видимо, не зря, потому что я все писал на БШ.

Сейчас я переписываю на Nuke Build.

Конечно, жизнь просто красками заиграла.

Потому что на большом, как ни старайся, в любом случае получится просто ужас.

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

Ну хорошо.

Первое.

Мы выкачали репозиторий и исполняем команду «найди все файлы» и «touch –d –1 день» означает «проставь им всем дату модификации вчера».

То есть всей репей дает модификации вчера.

Дальше.

Откуда-то достаем бинрелиз прошлого билда.

То есть нам же вот это нужно, чтобы MSBuild понял, что что-то поменялось или нет.

Мы достаем MSBuild с прошлого билда откуда-то, из кармана.

Не важно, пока не важно как.

И дальше у гита запрашиваем...

разницу между предыдущим коммитом и текущим коммитом, отправляем это все опять на команду touch-a-d, это значит на сейчас.

То есть предыдущей командой мы выставили у всей репы дату модификации вчера, а для разницы между двумя коммитами мы выставляем дату сейчас.

И типа для MSBuild по идее должно быть норм, типа вот это старое, а это сегодня.

Должно быть супер, подумал я.

Ну и типа билдим.

Нет, не сработало.

Оказывается, нужен еще object-релиз.

Потому что все эти даты он хранит там.

И получается, нужно с одной стороны хранить релиз, а с другой стороны хранить эту информацию.

И все это будет 20 гектаров весить.

Но как-то по 20 гектаров на каждый билд это ого-го, подумал я. Оно, конечно, будет работать.

Но как бы оно по сети будет это все выкачивать, закачивать, это явно будет не супербыстро.

Ну и плюс, сисадмины мне скажут большое спасибо.

Это непонятно что в Docker Registry, да?

И я же там это буду хранить.

Ну, как в качестве файлового хранилища, в некотором смысле.

Блин, не очень-то.

Все будет медленно.

Можно попробовать сверять только bin-релиз.

По идее, это тоже должно быть неплохо, подумал я. Хорошо.

Докер файл билда.

From registry as previous version.

То есть то, что в предыдущий раз было закоммичено.

Берем это как предыдущую версию.

Берем SDK.

На некотором следующем шаге копируем из предыдущей версии под папку last.

И запускаем билд релиз.

После чего делаем div last с последней версии.

И смотрим, если div пустой, значит все круто.

А если div не пустой, значит у нас есть разница.

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

Вот это изначально проблема была.

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

Достали предыдущий, взбудили текущий, сравнили.

Логично звучит же, да?

Звучит логично.

В первый раз, это такой классный способ расстроить человека, в первый раз оно сработало.

Я такой, типа, сработало.

Типа, здорово.

Вообще, сейчас пойду, там все, ну, блин, классно.

Народ порадуется, я там порадуюсь, там все.

Здорово.

Еще раз запускаю, еще раз сработает.

Третий раз запускаю, не работает.

В смысле не работает?

А что изменилось-то?

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

Не работает.

И тут до меня доходит, что .NET, он же когда компилирует, он же многопоточно компилирует, чтобы быстро.

А многопоточно, это значит, что порядок, он не гарантируется, правильно?

И DLL внутри, они структурно разные.

То есть они содержат одинаковый код, по разным местам расположены.

Какой первый поток завершился, тот и сложил в бинаре.

Я такой, блин, да что же такое-то?

Это же вообще никак не исправить.

Там есть, конечно, флажок Secure Intel Build, типа включить, типа, ну, однопоточный, прибить гвоздями.

Но он будет очень медленно это делать.

То есть это просто ужас будет.

Думаю, нет, не подходит.

Не подходит, все отличается.

Долго.

Что же делать?

И тут я об этом варианте изначально думал.

Но он же сложный.

Билдить вручную сложно.

Тем более непонятно как.

Я от него отказывался до последнего, но пришлось.

Хорошо.

Гипотеза.

Состояние любого проекта.

Вот есть CS Project.

Состояние любого проекта.

Чего зависит?

Вот он не изменен.

Ну первое, от его исходников, от всех CS-файлов.

Второе, от всех его зависимостей.

То есть первое, из чего он состоит, и второе, от чего он зависит.

Звучит просто.

То есть от других проектов, от NuGet-пакетов, и от всяких DLL-ек и прочих Linux-овых библиотек.

А также еще от секции contents.

То есть это та секция, которая копируется в папку из источников, в таргет, в директорию.

Вот.

И, кстати, пока что мы еще не нашли других.

А, и еще от файла dockerfile, который не включен никуда, но он тоже как бы участвует.

Вот.

И думаешь, ну, блин, вроде звучит хорошо.

Хорошо, думаю.

И дальше, то есть мне нужно какое-то число, которое характеризует, ну, как бы срез, да?

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

А она уже где-то у меня зарегистрирована.

Если она зарегистрирована, значит билд уже был.

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

А если там нет ничего, значит, мне надо пересобрать и, соответственно, зарегистрировать и в этот файлик записать.

Хорошо подумал.

Ладно.

Значит, дальше.

Как это выглядит?

Значит, первое.

Мы берем у CS Project список всех нугетов.

Считаем, вернее, сначала сортируем по имени, кстати говоря.

Потому что если не сортировать по имени, то, соответственно, опять пойдет вот эта вот фигня, что...

что-то там местами поменялось и версия изменилась.

Сортируем по имени, по версии.

Дальше список секций с Proj.

Если собирать MSBuild, он же умный, он сначала, если вы собираете какой-нибудь проект, либо solution,

Он сначала смотрит его зависимости, потом уходит в те зависимости, уходит в те зависимости, и так до самого конца.

Собирает сначала самые дальние, и потом те, что ближе, ближе, ближе, ближе.

И когда он подходит уже к концу, ну как бы все уже собрано, кроме корневого, остается собрать корневой.

И, соответственно, если собирать MSBuild с помощью такой хитрой штуки, чтобы она считала версию, то к моменту сборки нашего проекта будут подсчитаны хэш-суммы у CS Project, от которых мы зависим.

Соответственно, мы берем список CS Project и забираем у них хэш-версии, которые у них были уже подсчитаны.

Берем список DLL, сортируем, считаем от них.

Исходники считаем от них.

Контент считаем от них.

От этого всего считаем общую хэш-сумму.

И все.

Вот это, собственно говоря, и есть то, что мы искали.

Хэш-версия.

Прекрасно, подумал я. Дальше, соответственно.

Вот мы бьюдим, да?

Каким-то образом.

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

Четвертый билд, наш сервис не менялся, менялся какой-то другой.

Пятый, наш сервис наконец-таки поменялся.

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

Вот.

И можно даже вот эти вот версии потом не ставить.

То есть только вот первую брать, которая 34.

Первая.

45, 300, 64.

А остальные уже не проставлять теги.

Вот.

Прекрасно.

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

Из них забираем соседей.

И забираем максимальный номер.

Это вот есть, грубо говоря, та версия сервиса, под который мы будем деплоиться, например.

Или отдавать в тестирование, или еще чего-то.

Ну, как бы, вроде выглядит-то все хорошо.

Отлично.

Как считать хэш-версию?

Нужно же, чтобы она считалась в рамках, собственно говоря...

В MSBuild, как я и говорил, если запустить на каком-то проекте, то он соберет сначала все зависимые, а потом уже требуемые.

И это тоже самое относится и ко всем зависимостям.

И таким образом гарантируется, что когда мы собираем какой-то проект, то к его сборке уже все готово.

Плюс у MSBuild есть язык описания шагов сборки.

То есть это когда можно написать свою таску, которая делает что-то, и встроить ее в csproj, сказав, что она depends on какая-то другая таска.

И MSBuild ее подхватит и выполнит ваш код.

Собственно говоря, он так и работает.

Что получим?

Не будет компиляции, если она не нужна.

То есть мы даже избавляемся от самой сборки.

Мы меняем версию только если что-то изменилось.

То есть если мы из Docker Registry по хэш-коду можем забрать соседние теги, и эти теги существуют, значит билд уже был.

Мы просто берем этот тег и используем его для деплоя.

Ну и, соответственно, план действий.

Сначала напишем msbuild task для подсчета хеш-версии.

Дальше пишем еще один task для того, чтобы обойти проекты, чтобы посчитать эту хеш-версию.

И, соответственно, перейдем немножко, посмотрим, как это все работает в итоге.

Так, переходим мы вот сюда.

Значит, а нет, не сюда даже.

Вот сюда сначала.

То есть как это все выглядит, ну...

Есть папка с .NET-кодом.

И как известно, что если положить вот такие файлики в корень, то .NET-сборщик сначала обойдет их, соберет оттуда всякие тасочки, какие-то проперти, приотачит их к вашему csproj-файлу и исполнит этот csproj-файл, начнет его бьюдить.

То есть таким образом, если много-много-много csproj файлов, то можно переопределить какой-нибудь property.

Или, например, мы это используем для того, чтобы для всех проектов в solution задать единую версию для всех NuGet пакетов.

То есть у нас есть место, где они перечислены.

Сейчас...

Да, вот здесь вот они просто перечислены.

А в оконечных сервисах там просто вместо версии пишется переменная нужная.

И вот оно там таким образом используется, чтобы не разъезжался код.

И, соответственно, где бы мы какие-то изменения не внесли, то вот этот функционал подсчета хэш-версии, он среагирует.

Потому что он работает на базе MSBuild, на основе которого сам билд работает.

Ну, хорошо.

Выглядит это примерно таким образом.

Сейчас я поменьше что-то сделаю.

Так, вот.

Я забираю таску свою, которая у меня лежит в той же рейпе.

По кондишену, что у нас включен build hash version true.

Вот.

И две таски.

Collect hash versions.

Она срабатывает на каком-то проекте.

И run security build.

Она для того, чтобы для какого-то solution или проекта обеспечить рекурсивный проход по всем зависимым проектам.

Можно сделать то же самое на базе обычного билда дотнетовского, но он намного более сложный.

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

Если, например, проект совершен не изменился, зачем мне реставрировать нугеты?

Мне просто нужно пробежаться.

И, соответственно, таргет, если срабатывает GenerateHashVersions, то он сначала обходит все зависимые проекты и запускает таргет GenerateHashVersions рекурсивно.

А потом, когда он обошел все зависимые проекты, он вычисляет хеш-версии.

Выглядит очень коротко, но я к этим двум строчкам очень долго шел.

Потому что вот этот вот язык, ну как бы не то, что язык, вот этот MS Build, ну язык, да, в некотором смысле, он практически никак не задокументирован.

Вот, то есть это просто, ну, идешь сквозь туман.

Как что-то надо сделать.

Вот, единственная вещь, которая способна помочь, вот, я сразу, MSBuild Structured Log Viewer.

Вот эта штука.

Потому что билды... Что-нибудь есть?

Нет, хотел показать.

Все, видимо, вычистил с собой.

Вот.

Ну, там запущу сборщик.

Пусть соберет.

Значит, он смотрит не просто логи MSBuild, а вообще все логи структурированные, которые он складывает.

То есть если вы напишете, что у вас уровень логов, например, какой-то минимал в консоль, но при этом укажете, что binary logger складывать в этот файлик, то в этом файлике будет вообще все.

До дичайших подробностей.

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

Собрать структурные логи и посмотреть их вот этой вот приложухой, это очень полезно.

Кстати, этот функционал встроен в райдер.

Если кто-то задавался вопросом,

А что такое... Сейчас Alt-F сделаю.

А что такое Build Advanced Build Actions Build with Diagnostics?

Это как раз-таки он складывает в структурной логе файлик и открывает вот этот Microsoft Build Structured Log Viewer.

Как раз он это и делает.

Ну хорошо.

Так, переходим обратно.

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

Это все, что надо добавить в directory build targets.

А что надо еще добавить?

Нужно написать, собственно говоря, сами таски.

Как эти таски выглядят?

Ну, вот у нас на самом деле не так много кода здесь.

То есть вот всего три файлика, один из которых — это helper.

Давайте, run security build.

Очень простой, принимает на вход список проектов.

То есть любая таска, если ей ничего не передать, она вообще понятия не имеет, в каком контексте она работает.

Вот этот контекст передаете снаружи, то есть вот здесь вот.

И вот она запускается, да, и список проектов вот такой.

А список таргетов вот такой.

Ну, собственно говоря, вот она здесь это все и приняла.

Дальше ее MSBuild запускает.

Спикер 7

Сейчас.

Спикер 3

Ну, тут ничего такого, вообще ничего сложного.

То есть для всего списка проектов, на которых нас запустили, мы достаем имена их референсов, то есть на кого они ссылаются.

Дальше возводим флаг hasChangedTrue.

И пока есть изменения какие-то, то есть пока мы... Как выразиться-то?

Пока мы идем по этому списку, мы просто берем и формируем с помощью dictionary string на hashSetString.

Эти зависимости оформляем в виде дерева.

Все, больше ничего.

После чего мы пробегаемся по этому списку и билдим.

Запускаем MSBuild, отдаем тот список таргетов, который нам передали.

То есть тот список таргетов, под которым надо эти проекты скомпилять.

И все.

Ничего здесь такого.

А самое важное — это CollectHashVersions.

Она тоже очень простая.

Она на вход принимает все свои зависимости, которые только может принять.

А это контентс, это, соответственно, список нугет-пакетов, список исходников, ну вот эти все вещи.

Вот она их приняла с помощью property.

Тут ничего такого.

А дальше она просто запускает подсчет хеш-версий.

Потом объединяет все полученные хеш-версии, подсчитывает общую хеш-версию и записывает это в файлик.

Там типа bin-release.mad5.ver.

Там хеш-версия считает проекта.

Все.

Это что касается вот этой части.

А дальше вступает в роль более печальная часть.

Она написана как раз на баше.

Но мы не будем ее очень плотно рассматривать.

Спикер 7

Дальше она, соответственно, лежит... Сейчас...

Спикер 6

Да, вот оно.

Спикер 3

Все увеличенное.

Смотреть не очень удобно.

Файлик build version.

Это версия скрипта.

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

Лежит файл версии, от которой сервисы собираются.

Это мажорная версия.

То есть после нее будет build counter вставляться.

Дальше есть вишка со списком всех сервисов.

Это единственное то, что сейчас мне не очень нравится.

Просто сервис такой-то лежит там-то, сервис такой-то лежит там-то.

Не нравится, потому что это простыня, ее надо поддерживать.

Я ее в ближайшее время переделаю, чтобы...

Она сама анализировала.

Просто на основании, например, что в csproj файле есть property с таким-то именем.

Она выставлена в true.

Тогда этот файлик уйдет.

Практически весь.

Это вот это.

И сам скрипт.

А скрипт работает.

Вот он вот так жутко выглядит, я уж не буду рассказывать.

Я просто скажу, как он работает.

Вот он, там куча всякого дебоши.

Смысл в чем?

Первый шаг, который мы делаем.

Мы генерируем solution на основе csproj файла.

Сolution, в котором содержатся вообще все сервисы.

Это шаг номер один.

Шаг номер два.

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

То есть пропертиями указываем, что таргет у нас генерирует hash versions.

И пропертий выставляем, что с помощью пропертий мы генерируем hash версии.

Куда больше не ходим.

И эта штука отрабатывает буквально за 5 секунд.

То есть она пробегается по всем проектам, уходит вглубь.

Она, соответственно, рекурсивно считает все остальные проекты.

А если она видит, что MD5 файл уже лежит, она вглубь не уходит, потому что туда уже считали.

И таким образом получается, что она все просчитывает.

Для каждого сервиса все его хэш-суммы.

Дальше.

Следующий шаг.

Она лезет в Nexus и спрашивает, типа, вот для этого сервиса есть такой хеш-код, тег?

Вот, Nexus, например, говорит, да, есть.

Он говорит, окей.

У этого тега какие, ну, там, братья, да, вот, какие соседи, да?

Для этого, получается, вот есть image, вот, для него выставлено, например, там, сколько-то тегов, вот, и нужно написать...

Ну, как бы соседей найти.

Ну, какие-то соседи.

Он говорит, ну хорошо, забираю соседа.

И вот эта версия, это версия, с которой реально сервис собран.

Но он уже собран, поэтому дальше я не собираю, я скипаю.

Просто логирую, куда надо, что сервис под такой-то версией.

Например, в CSV-файл, который идет в артефакты.

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

Если, например, она понимает, что там версия отсутствует в Nexus, она уже все, она берет этот сервис, вернее, проект сервиса и добавляет его в список тех, которые она будет бьюдить.

После чего, когда она уже по всему пробежалась, она берет тот список, который она будет билдить, например, там уже не 95 сервисов, а всего 4.

И берет и это самое.

Из него формирует еще один solution.

Второй.

Уже коротенький.

И вот его уже отправляет на сборку.

Понятно, да?

Концепция очень простая.

Все.

Больше ничего не происходит.

Нет, здесь скажу.

Больше ничего не происходит.

Какие результаты?

Первый результат очень важный.

Это то, что билды реально очень сильно сократились.

То есть если раньше, когда вот этот buildall происходил,

Он шел минут 45.

То есть он очень долго шел.

И причем он не просто шел 45 минут или минут 20.

Все зависит еще от того, как Nexus отвечает на этот момент.

Еще какие-то подсистемы отвечают.

Кто-то может в параллели что-то там где-то делает.

На каждой ноде, на каждом агенте вот это все происходит.

Агентов 20.

Вот.

И, ну там, отправил сборку.

Вот.

И из другого отдела.

Кто, короче, все билд-агенты забрал?

Туда и сюда надо ждать, короче.

Блин, ну что, невозможно вот так вот.

Вот.

А там, например, какой-то корневой нагет поменялся.

Но это в любом случае все пересобрать, например, да, очень долго.

Вот.

Второе.

А, и оно в итоге стало на одном агенте, не на 20 агентах, а ровно на одном агенте отрабатывать максимум минуты за 4.

То есть это значит, что по-настоящему, по-настоящему, если все пересобрать на одном салюшене, это очень быстро.

Да, то есть оно замедляется просто за счет того, что оно растаскивается по, ну вот именно, что типа вот у нас есть разные билды, у нас все красиво в Team City, там туда-сюда, у каждого сервиса свой лог есть билда, ну вот это вот все.

А на самом-то деле, если все обратно слопнуть, все намного лучше.

Вот.

А почему?

Потому что многие сервисы, они же не так, что абсолютно с нуля написаны.

Они переиспользуют какие-то проекты, которые включены в кучу других сервисов.

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

Ну правильно, да?

То есть если в 95 сервисах

Используется какой-то общий проект.

Он используется, например, в 50 сервисах.

Но это просто значит, что он 50 раз будет пересобран, потому что он на разных агентах пересобирается.

А здесь как бы один раз.

И это как бы круто.

История по спирали.

Это вот второй момент.

Еще один момент очень крутой, который вылез тоже вот

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

Есть такая сложная структура.

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

И запускает билд вот это вот, например, с 7.

А по факту он задевает вообще половину, например.

И он может еще так, знаете, после этого еще подумать.

Так, билд нажал, ого.

Так, откатил изменения.

Например, переструктурировал проекты, чтобы они меньше задевали.

Да?

Вот.

Раз, раз, раз, раз.

Пересобрал, о, теперь два.

Вот, как и хотел изначально.

Вот.

А то так это ж, ну, так вот поменяешь, а потом люди, которые ездят в командировки и устанавливают, они ж матерять будут, что там, ну...

Вроде как заявлено какое-то малое количество изменений, а в результате половину надо просто опустить и эту половину снова поднять.

А там кэши начинают подниматься, там еще что-то.

А что изменилось?

И ничего не изменилось на самом-то деле.

Это как сайд-эффект, такой большой-большой плюс от такого подхода.

Ну и, конечно же...

Я когда, ну, там, Анатолий написал, увидел, что у него доклад Build This Code, вот, а у меня же он, видите, на баше много там все, вот именно башового, вот, там всякие пайпы, там, регексы там местами, вот, пуш, ремай, там, ну, короче, вот, все как надо, все как вот.

Я баш выучил.

Я его не знал до этого, я его выучил.

Очень советую выучить Bash.

Ну, конечно, не для того, чтобы потом переписать на другое.

Он мне посоветовал Nuke.

И вот, чтобы вы понимали, я начал переписывать.

Вот сейчас он мне здесь.

Спикер 6

Nuke.

Это именно билды.

Спикер 7

Сейчас.

Это через райдер.

Сейчас попробую открыть.

Спикер 3

Если на этой ветке.

Спикер 7

Надеюсь, на этой.

Спикер 3

Там все намного, конечно, проще стало.

Спикер 6

А, другая ветка.

Сейчас.

Казалось, внезапно решил показать.

То есть там таска осталась.

потому что без нее никуда.

Спикер 3

И половина переделана.

Немножко по-другому.

Но в целом теперь это выглядит, конечно, намного проще.

То есть, например, build targets.

Прочти CSV-шку с файла.

Вот таргет.

Удали все md5.

Вот таргет.

Вот он ровно все это делает.

Это таргет.

Сейчас.

То есть root directory.

Возьми все md5.ver файлы, на каком бы уровне вложенности они бы не находились.

Для каждого из них выполнить просто удали файл.

Вот и все.

Counter++.

В конце напишешь, сколько файлов ты удалил.

Вот.

Generate build all sln.

Вот.

Запусти вот этот метод.

Он там коротенький.

Просчитай хэш-версии.

Что для этого надо?

Удалить старые, сгенерировать build.osln и посчитать новые.

Понятно, да?

Вот.

Compile.

Ну вот тоже.

Достаточно коротко.

То есть оно написано вроде как на нашем языке, да?

И оно очень легко читается.

И при этом очень коротко, потому что Nuke Build заточен именно под билды.

Выглядит очень хорошо.

Так.

Ну, наверное, все.

Вот.

Что я хотел сказать.

Единственное, наверное, покажу, как это выглядит на TeamCity.

На TeamCity это выглядит вот так.

Я это, в общем-то, так и назвал.

Билд называется JustBuild.

Я это оформил в виде тестов.

Потому что у TeamCity единственный способ как-то структурированно на UI вывести то, что ты хочешь, это тесты.

То есть если ты хочешь какие-то подбилды, то это нужно какие-то билды запускать.

Но здесь же нет, здесь одним билдом идет.

Поэтому я оформил это в виде тестов.

То есть есть какой-то ряд сервисов, и success означает, что билд был.

А если результат игнор, это значит, что билда не было.

Вот и все.

Если failed, то значит, что там какая-то ошибка.

И надо смотреть, соответственно, что там произошло.

Ну и, соответственно, если справа посмотреть, вот они там 41 секунда, 2 минуты, 4 минуты, 2 минуты.

Ну где-то там тормозил Nexus, ушел вперед.

Это не из-за сборки.

А здесь что-то произошло.

Лезем, смотрим, все подсвечено хорошо.

Заходим внутрь.

И я еще такую штуку сделал.

Там, где иррор, она еще огоньком подсвечивает.

Я дальтоник, может, поэтому я не вижу.

Но если огоньком, то вот прям очень хорошо видно.

Выглядит, в общем-то, ничем не хуже.

Единственное,

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

И я поэтому сначала по разным файликам распихивал логи, а потом друг за другом их просто вычитал и plain текстом как бы выложил.

Ну вот, это единственный минус.

А так вот, пожалуйста.

Так, все, да?

Все, выводы.

Значит, ну, выводов, выводов, вывод главный, что все просто хэппи.

Вот, хэппи вообще все.

То есть начиная от разработчика, которому теперь вообще париться не надо.

Он просто одну кнопку нажал и все.

И эта система, она ему все подсветила.

Что было изменено, как изменено.

Все видно.

Хэппи-тестирование.

Тестирование вообще... Просто приходил, вообще круто.

Типа, блин, спасибо.

Это просто... У нас, кстати, автосборка запускается.

Она теперь просто вот так вот отщелкивает и все.

Раньше она отрабатывала, не знаю сколько, мы ее ночью ставили, чтобы никому не мешать.

А теперь оно просто и всё, короче.

И вот оно просто запускается и работает.

Типа, вообще круто.

Там начальство просто, ну, естественно, там у них это time to market, там customer happiness, вот эти вот все.

Но всё сразу же, естественно, стало лучше, эти все параметры.

И уменьшилось количество ошибок.

Потому что раньше ошибки были, что разработчик что-то пропустил,

Что-то недобил, делал туда-сюда.

Не очень удобно.

Здесь же, кстати, можно... Нугеты, насчет нугетов.

Нугеты тоже точно так же легко подцепляются.

Плюс один шаг, грубо говоря.

Нугеты точно так же.

Они сначала собираются

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

Вот, апаем там версию и уже собираем сервисы, которые поменялись в связи с этим.

Предварительно, например, поставив версию что-то при релизе.

Можно автоматом сделать подчи.

Релиз просто сделать.

Просто берешь и релизишь весь список под единой версией, не обращая внимания, были ли изменения.

А подчи как сделать?

Точно так же.

Берешь, смотришь, когда был сделан предыдущий релиз,

Например, по версии определяешь, по тегу, что тег не build counter содержит, а какие-то маленькие числа, например, меньше сотни build counter.

Если build counter меньше сотни, значит это релиз версии.

Если больше сотни, если какие-то тысячи, значит это build counter.

Например, таким образом.

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

Под каким коммитом.

Смотрит, на каком коммите, смотрит, какой сейчас коммит, строит дифф, и автоматом получает список сервисов, которые были изменены между предыдущим релизом и почем.

И перестраивать только нужные изменения.

Автоматом, например, отправляя в джиру список сервисов, которые для текущего патча выпускаются.

И здесь исключается человеческий фактор полностью.

То есть мы приходим к такой хорошей сейсидии здорового человека.

Когда люди этим не занимаются.

Попыткой не ошибиться.

Вот.

Все.

Всем спасибо.

Спикер 4

Спасибо большое за доклад.

У нас сейчас есть еще немножко времени, мы можем позадавать вопросы, а потом перейдем в дискуссионную зону.

У кого-нибудь есть, что спросить?

Спикер 5

Да, спасибо за доклад.

Интересно, у нас нечто в этом же роде есть.

Проблема, я имею в виду, по сборке.

Немного мне осталось непонятно, транзитивные сборки, они тоже включаются или же только те, которые явно указаны в референсах?

Имеется в виду, у нас есть какой-то нугет, который под капотом еще 20 нугетов, у него еще у тех 20 и 50.

Если не меняется что-то внутри, потягивает или...

Спикер 3

Нет, это тоже.

Первый шаг по Nougat, поскольку проект огромный, огромное количество этих Nougat, и первый шаг по Nougat был это внедрение файлика с версиями.

Дальше второй шаг.

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

То есть, например, если релизится 5 нугетов для какого-то сервиса, то они просто релизятся под единой версией.

Были изменения, не было изменений.

То есть потребителю важно, чтобы все в целом опнулось.

И это резко сократило количество билдов.

Прям очень проще стало.

А следующий шаг, соответственно, тоже по тому же принципу.

Просто скрипт уже будет сложнее.

В местах, где есть зависимость от Nuget пакетов,

Лезем в Nuget репозиторий, смотрим, какие там метаданные у него.

Смотрим, под какими коммитами было собрано.

Считаем опять дифференс.

Если разница есть, значит, Nuget был изменен.

Значит, мы его пересобираем.

И так же идет... Если он основан на каких-то Nuget, то тоже для них та же процедура рекурсивно происходит.

То же самое.

Спикер 6

Спасибо.

Так, еще там.

Спикер 2

Спасибо за доклад.

Вопрос тоже про Nuget, не очень понятно.

Вот, допустим, у нас есть какой-то Nuget пакет, мы там корневой, мы в нем сделали изменения, нам для того, чтобы воспользоваться другим проектом, надо опубликовать, какую-то сборку сделать, чтобы он опубликовался в репозитории, Nuget залился с новой версией.

Потом у нас там три проекта от него зависят, нам надо войти в три проекта там руками.

Или автоматически они как-то версии?

Автоматически.

То есть версия там как-то указана, что они до последней версии сами все апдейтятся.

Спикер 3

Ну, смотрите, то есть поскольку все версии указаны именно вот в этом файлике, да, и нигде больше, то есть там build, а, ну, packages props, по-моему, или targets, не помню.

то она как?

Вот вы что-то поменяли, да?

Она станцует от сервисов.

То есть сервисы ссылаются на Nuget пакеты, которые ссылаются на другие, которые ссылаются на другие.

И она уже, когда собирает сервис и идет по проектам, если она видит, что проект какой-то зависит от Nuget пакета какого-то,

то, соответственно, она смотрит, были ли там реальные изменения.

Если они были, то она его пересобирает под какого-нибудь, если это не релиз, то под какую-то превью-версию.

А дальше она берет эту версию и, например, кладет себе в «Соварь».

А проекты, которые от него зависят, она дополнительно через командную строку говорит "-p", версия этого пакета такая-то.

И он уже перекрывает ту, которая была получена из PackagesProps.

И собирает уже под другой.

А в конце, соответственно, это еще не сделано, но в конце можно сделать, например, pull request автоматически.

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

Спикер 2

А, то есть в ветку пока обновленные версии пакетов сами не попадают, не заезжают.

Спикер 3

Да, да, да.

И чтобы само было, то есть она должна закоммитать это.

А это уже на человека.

То есть я-то тебе вот собрала, но все на превью-версиях.

Вот оно работает.

Но дополнительно я тебе сделал pull-request, в котором в этом файлике версия апнулась.

Спикер 2

А что делать, если там какие-то изменения были в пакете, и там теперь оно не собирается?

Ну то есть изменили что-то в пакете?

Спикер 3

Ну все сломано, билд сломан.

Спикер 2

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

Спикер 3

Это можно с помощью какого-нибудь флажка в TeamCity.

Там можно сделать флажок, когда он типа промтит, спрашивает.

Ну, говорите, что вот это вот свойство сборки, оно идет через промт чекбоксом.

И когда запускаете, он вас, типа, спросит, типа, да, нет.

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

Но здесь суть в том, что поскольку у вас работает ваш скрипт, написанный, например, на ньюкбилде, вы полностью контролируете вообще все, что вы хотите.

То есть вы уже отвязываетесь от того, что вам поставили границы.

Это можно, остальное нельзя.

Вот теперь вот все можно.

То есть если ваш флоу это подразумевает, то замечательно.

Если вам достаточно, что...

вы пересобрали Nugeta, а потом просто смотрите, вернее пересобрали Nugeta, потом поставили сами рукой, строчку поменяли на новую версию, потом полностью все пересобрали, вот, то, ну, значит, вот так.

Спасибо.

Тут можно итеративно идти.

То есть, ну, от самого простого варианта к более так навороченному, навороченному.

Спикер 1

Спасибо большое за доклад.

Интересно было посмотреть на чужие костыли, которые работают и всем приносят удачи.

Хотел еще спросить...

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

То есть мы же не можем релизить проекты?

Да, конечно.

То есть тесты вы тоже запускаете?

Спикер 3

Да, да, да.

То есть первый шаг как раз отрабатывает вот эта вещь.

То есть тут можно как?

Поскольку таргеты указываются в нюкбилде, или там в башскрипте можно сделать аргумент, а башом уже разрулить.

то скрипту, если, например, передать строчку типа «Только хеш-версии»,

он может на них и остановиться.

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

Можно так.

А для тестирования что важно?

Для тестирования важно на текущем коммите какой срез версии.

Поэтому они просто запускают в качестве зависимости JustBuild, который, если что-то изменилось, компилирует.

Если ничто не изменилось, ничего не компилирует.

Но в любом случае у него в качестве артефакта сборки это файлик сервиса CSV, в котором первая колонка — это имя сервиса, и вторая колонка — это имя имиджа в докере.

Антралия тополя —

Слэш, имя сервиса, двоеточие, тег.

Они этот файлик забирают и в своем скрипте деплоят на стенд.

И все.

В лучшем случае у них это все отрабатывает за 20 секунд.

А дальше деплой идет секунд 40.

И дальше пошли у них автотесты работать.

Спикер 1

А каких-то юнит-тестов?

То есть у проектов нету сразу прям на стенд релизить?

Спикер 3

Юнит-тесты есть.

То есть это тоже, то есть их можно точно так же запустить.

То есть вы .NET бьют, а дальше запускайте тест.

Это не вопрос.

Спикер 4

Спасибо.

Так, дорогие участники, у нас время сейчас кончилось тут.

Поэтому прошу всех пройти в дискуссию, где вы сможете продолжить, задать все вопросы, которые у вас остались.

Спикер 3

Спасибо, Реп.