Проблема
Традиционно, для реализации CI/CD сценариев DevOps-инженеры используют различные платформы, такие как Jenkins, TeamCity, Azure DevOps и т.д. Их конфигурирование для сборки, версионирования, создания релизов решений может быть сложным и трудоемким, особенно если решение состоит из множества проектов/единиц развёртывания.
Обычно для настройки сборки решений в .NET используется большое количество разнородных скриптов, что создает ряд проблем:
-
Сложность поддержки: использование такого количества скриптов делает их поддержку более сложной и трудоемкой. Каждый скрипт может иметь свою синтаксическую структуру и требовать специфических знаний для его изменения или исправления ошибок.
-
Отсутствие стандартизации: в случае использования скриптов, каждый разработчик может использовать свой собственный подход к настройке процесса. Это приводит к отсутствию стандартов и единого подхода в организации, затрудняя совместную работу и повышает сложность обслуживания.
-
Непредсказуемость: нет уверенности, что процесс будет проходить одинаково на всех серверах сборки, поскольку он зависит от настроек окружения и установленных там SDK. Более того, сборка или запуск тестов могут проходить на одной ОС, а развёртывание — на другой, приводя к непредсказуемым ошибкам.
-
Зависимость от инструментов: для настройки CI/CD с использованием скриптов обычно требуется определенный набор инструментов, создавая зависимость от них и усложняя переносимость настроек.
-
Отсутствие контроля версий: нередко бывает так, что скрипты не хранятся в системе контроля версий, тем самым затрудняя отслеживание изменений и воспроизведение конфигурации для определенной версии приложения.
-
Отсутствие вовлеченности разработчиков: поскольку, зачастую, настройка CI/CD выполняется DevOps-инженерами, разработчики не могут вносить изменения в процесс сборки напрямую. Более того, разработчики могут не знать, как настроен весь процесс, что делает его менее прозрачным и увеличивает время реакции на изменения или проблемы.
-
Зависимость от внешних поставщиков: в постоянно изменяющихся условиях рынка, коробочные решения для CI/CD могут устаревать, переставать соответствовать требованиям организации или просто уходить с рынка. Всё это может приводить к необходимости перехода на другие решения и влечёт дополнительные затраты на переобучение и перенастройку процесса.
Варианты решения
Что можно сделать, чтобы решить эти проблемы?
Перейти на подход «сборка через код», являющийся практикой автоматизации процессов сборки и развёртывания приложений, используя код в рамках самого приложения вместо ручной настройки и скриптов в CI/CD платформе.
Так мы и решили сделать в компании Монополия, когда столкнулись с описанными выше проблемами. Был проведён анализ рынка и рассмотрены различные инструменты для реализации требуемого подхода в .NET.
Cake
Платформа для автоматизации сборки и развертывания приложений, написанная на C#.
Плюсы:
-
Гибкость: Cake позволяет настроить сложные сценарии сборки, используя собственный синтаксис.
-
Позволяет определять настройки сборки в виде кода, который хранится в репозитории проекта.
-
Поддерживает .NET Framework, .NET Core, Mono, Xamarin, Unity, и т.д.
-
Поддерживает различные CI/CD платформы, такие как Jenkins, TeamCity, Azure DevOps и другие.
-
Позволяет использовать различные инструменты, такие как MSBuild, .NET CLI, NuGet, Git, Docker и другие.
-
Открытый исходный код.
Минусы:
-
Сложный синтаксис: несмотря на C# в своей основе, Cake использует собственный синтаксис для определения настроек сборки, что может быть препятствием для новых пользователей.
-
Сложность настройки: настройка Cake может быть сложной и требовать специфических знаний.
-
Отсутствие интеграции с IDE: Cake не интегрируется с IDE, что делает его менее удобным для использования. Поддержки IntelliSense нет.
-
Проблемная документация: документация Cake может быть трудна для понимания и не всегда содержит достаточно информации для решения конкретных проблем.
Тем не менее многие из описанных выше минусов нивелируются использованием библиотеки Cake.Frosting, позволяющую использовать C# вместо собственного синтаксиса и имеет поддержку IDE. Более подробно можно прочитать в статье.
Fake
Платформа для автоматизации сборки и развертывания приложений, написанная на F#.
По возможностям и недостаткам похожа на Cake. Отличается от него тем, что использует функциональный подход в связке с языком F# и предоставляет более высокоуровневый DSL (Domain-Specific Language).
PSake
Платформа для автоматизации сборки и развертывания приложений, написанная на PowerShell.
По возможностям и недостаткам похожа на предыдущие, отличаясь тем, что теперь разработчикам необходимо владеть языком PowerShell. Это привносит дополнительные сложности и высокий порог входа. Также можно отметить ограниченную поддержку сообщества и документации. Зависимость от PowerShell может быть проблемой, поскольку он может быть не всегда доступен для использования по тем или иным причинам.
Kotlin DSL
Встроенный в TeamCity способ настройки сборки и развёртывания на языке Kotlin.
-
В отличие от Cake, имеет встроенную поддержку IDE (но только IntelliJ IDEA и Android Studio).
-
Не поддерживает другие CI/CD платформы и не может быть использован вне TeamCity.
-
Требует знания языка Kotlin… и не все .NET разработчики готовы его изучать.
Ручное конфигурирование на PowerShell
Возможно, один из самых простых способов настройки сборки и развёртывания приложений.
-
Множество возможностей и вариантов настройки.
-
Простота прототипирования всего CI/CD процесса.
-
Требует знания языка PowerShell.
-
В результате получается много скриптов, которые хоть и могут храниться в репозитории, но их отладка и поддержка могут быть сложны для новых разработчиков.
-
Сложнее версионировать скрипты и распространять изменения, т.к. каждое решение будет содержать их полную копию.
Nuke Build
Платформа для автоматизации сборки и развертывания приложений, написанная на C#.
-
Главный конкурент Cake по части гибкости конфигурирования и возможностей.
-
Большое количество встроенных инструментов для интеграции с множеством CI/CD платформ и сервисов.
-
Более дружелюбный и простой в использовании ввиду того, что для конфигурирования использует C# и имеет поддержку IDE (Rider и Visual Studio).
-
Для запуска требуется только .NET SDK и, необязательно, скриптовый движок (PowerShell, Bash).
Тем не менее, данный инструмент тоже имеет ряд ограничений:
-
Некоторый порог входа для изучения особенностей написания сценариев сборки.
-
Ограниченная документация.
Сборка через код на Nuke Build
Потратив некоторое время на изучение различных инструментов и прототипирование «build as code» подхода на PowerShell, было решено остановиться на Nuke Build.
Главным преимуществом Nuke стала возможность использовать знакомый синтаксис C# для настройки сборки, что сделало весь процесс более прозрачным и понятным для разработчиков. Конфигурация сборки хранится прямо в решении — любой разработчик имеет возможность узнать подробности процесса или внести изменения в него, не выходя из своей IDE.
Более того, был унифицирован весь процесс и всё, что теперь требуется для развёртывания новых сервисов — создать новый проект Nuke и подключить нашу общую библиотеку, содержащую основные сценарии.
Анатомия Nuke Build
Nuke Build состоит из нескольких основных компонентов:
-
.NET проект, в котором определены сценарии сборки. Это обычное консольное приложение со ссылкой на NuGet-пакет Nuke Build.
-
Цели (targets), определяющие последовательность задач, которые должны быть выполнены для достижения конкретной цели сборки. Например, цель может быть связана со сборкой решения или развертыванием приложения. Цели позволяют разработчикам организовать задачи в логические группы и определить порядок их выполнения (сценарии).
-
Задачи (tasks), представляющие собой отдельные действия, которые могут быть выполнены в рамках сценария сборки. Например, задача может компилировать код, запускать тесты, создавать пакеты или развертывать приложение. Nuke Build предоставляет широкий набор встроенных задач, а также возможность создания пользовательских задач.
-
Параметры (parameters), позволяющие передавать значения в сценарии сборки из командной строки или других источников. Они могут использоваться для настройки поведения сценария сборки в зависимости от конкретных требований проекта или окружения.
Таким образом, создав проект и определив в нем сценарии сборки, разработчики могут использовать их для выполнения самых различных задач.
Внедрение
Давайте рассмотрим пример внедрения подхода сборки через код с использованием Docker’а на Nuke build. Сборка решений в Docker обеспечивает максимальную воспроизводимость процесса, убирая зависимость от окружения.
Был определён единый сценарий сборки для всех сервисов компании, который включает в себя следующие цели:
-
Сборка Docker-файлов для каждой единицы развёртывания:
-
Сборка проектов.
-
Запуск unit-тестов.
-
Формирование артефактов сборки (NuGet-файлы).
-
Формирование итогового Docker-образа для дальнейшего развёртывания.
-
-
Запуск интеграционных тестов (если это применимо для решения).
-
Публикация NuGet-артефактов.
-
Публикация Docker-образов.
-
Создание релиза по итогам сборки в Octopus Deploy.
Пример реализации
Для реализации такого сценария, был разработан общий проект, распространяемый как NuGet-пакет, в котором заранее определены все необходимые цели, задачи и их связи. Я подготовил пример такого проекта, который можно найти здесь.
Опустим в данной статье сам процесс создания проекта Nuke Build, т.к. это очень просто и хорошо описано в документации или на хабре.
Наш основной сценарий сборки содержит набор из взаимосвязанных компонентов:
-
-
Базовый компонент, определяющий основные параметры сборки, от которого унаследованы все остальные компоненты.
-
-
-
Цели для взаимодействия с Docker:
-
Сборка Docker-файлов. Один Docker-файл на единицу развёртывания в рамках решения.
-
Публикация полученных Docker-образов в репозиторий.
-
-
-
-
Запуск интеграционных тестов, если задано значение параметра
ExecuteIntegrationTestsкакtrue.
-
-
-
Публикация NuGet-артефактов в репозиторий.
-
-
-
Создание релиза в Octopus Deploy.
-
Nuke Build поддерживает различные варианты версионирования из коробки, но, для сохранения совместимости с текущими процессами, были воссозданы существующие варианты семантического и GitFlow версионирования.
-
-
Для общего понимания концепции, рассмотрим в деталях реализацию цели публикации NuGet-артефактов в репозиторий:
[ParameterPrefix(nameof(NuGet))] public interface INuGetBuild: IBaseBuild { [Parameter("NuGet url"), Required] Uri Url => this.GetValue(() => Url); [Parameter("NuGet feed name"), Required] string FeedName => this.GetValue(() => FeedName); [Parameter("NuGet API key"), Required, Secret] string ApiKey => this.GetValue(() => ApiKey); AbsolutePath NuGetArtifactsPath => ArtifactsPath / "nuget"; Target PushNuGetArtifacts => _ => _ .TryDependsOn<IIntegrationTestsBuild>(x=> x.RunIntegrationTests) .Executes(() => { var nuGetPushUrl = Url.Combine($"nuget/{FeedName}/packages"); DotNetTasks.DotNetNuGetPush(settings => settings .SetTargetPath(NuGetArtifactsPath / "*.nupkg") .SetSource(nuGetPushUrl.ToString()) .SetApiKey(ApiKey) .EnableSkipDuplicate() .EnableForceEnglishOutput()); var pushedArtifacts = NuGetArtifactsPath.GetFiles("*.nupkg") .Select(x => x.Name); Log.Information("Nuget artifacts were successfully pushed: {Artifacts}", pushedArtifacts); }); }
-
ParameterPrefix — атрибут, позволяющий задать префикс для параметров, которые будут использоваться в сценарии сборки. В нашем случае это:
-
NuGetUrl — URL репозитория NuGet.
-
NuGetFeedName — имя веб-канала NuGet.
-
NuGetApiKey — API-ключ для доступа к репозиторию.
-
-
NuGetArtifactsPath — путь к артефактам сборки, которые будут опубликованы в репозиторий.
-
Отмечу, что тип AbsolutePath, поставляемый вместе с Nuke Build, позволяет работать с путями в кроссплатформенном формате.
-
-
PushNuGetArtifacts — цель, определяющая последовательность операций, которые должны быть выполнены для достижения цели публикации NuGet-артефактов.
-
TryDependsOn — метод, позволяющий определить зависимость цели от другой цели. В нашем случае — это успешный прогон интеграционных тестов.
-
DotNetTasks.DotNetNuGetPush — задача, выполняющая публикацию артефактов в репозиторий. Одна из многих встроенных задач Nuke Build.
-
Также хочется обратить внимание на то, что все компоненты сборки представляют собой интерфейсы с реализацией методов по умолчанию. Это официальный способ разработки распространяемых сценариев и целей сборки.
После того как были определены все необходимые компоненты, появилась возможность использовать их в проектах, просто подключив общую библиотеку и вызвав нужную конечную цель:
class Build : NukeBuild, IDefaultBuildFlow { public string ServiceName => "DockerTestsSample"; public ApplicationVersion Version => this.UseSemanticVersion(major: 1, minor: 0); public bool ExecuteIntegrationTests => true; public IReadOnlyList<DockerImageInfo> DockerImages { get; } = new[] { new DockerImageInfo(DockerImageName: "docker-tests-sample", DockerfileName: "Dockerfile"), }; private Target RunBuild => _ => _ .DependsOn<IDefaultBuildFlow>(x => x.Default) .Executes(() => { }); public static int Main() => Execute<Build>(x => x.RunBuild); }
В данном примере определяются:
-
Имя сервиса для использования в сценарии сборки.
-
Тип версионирования и сама версия.
-
Необходимость запуска интеграционных тестов.
-
Список Docker-образов для сборки.
-
Цель сборки, зависящая от цели по умолчанию, определенной в общей библиотеке.
Описание Docker-файла выходит за рамки данной статьи, но его содержимое можно посмотреть здесь.
Запуск сценария и передача параметров осуществляются в зависимости от операционной системы или предпочтений разработчика: Bash, CMD, PowerShell, утилита Nuke Build, IDE и т.д.
Итоги
Таким образом, был успешно реализован унифицированный подход сборки через код на Build Nuke, переведено множество сервисов компании на него и достигнуты следующие преимущества:
-
Полная предсказуемость. Сборка и тесты выполняются на том же окружении, что и продуктивное.
-
Разработчики теперь сами настраивают процесс сборки так, как нужно им, без лишних обращений к DevOps-инженерам и ожидания выполнения задач.
-
Все сервисы собираются и тестируются одинаково, что позволяет упростить их поддержку и обслуживание. Больше никакой мешанины из скриптов разной степени давности.
-
Уменьшение TTM (Time To Market) для новых сервисов.
-
Полный контроль над артефактами сборки.
-
Версионированием занимаются сами разработчики.
-
Улучшения/изменения процесса сборки распространяются через одну общую библиотеку.
-
Был форсирован переход на развёртывание сервисов в Docker.
-
Независимость от любого CI решения.
Дополнительные материалы
-
Доклад моего коллеги, Анатолия Кулакова: Build as Code
-
Доклад Романа Булдыгина — Анатомия Nuke
-
GitHub-респозиторий с примером реализации.
P.S. Если хочется погрузиться в данную тему еще больше, могу порекомендовать грядущий доклад Анатолия Кулакова на DotNext 2023.
ссылка на оригинал статьи https://habr.com/ru/articles/760198/
Добавить комментарий