Много команд, много продуктов, каждый автоматизирует релизный цикл, у каждого свой набор скриптов. Дублирование, отсутствие обмена практиками и повторное изобретение колеса приводит к тому, что команды тратят много времени на построение своего CI/CD. И все это поддерживает микросервисную архитектуру, где автоматизации нужно уделить особое внимание.
Меня зовут Владимир Цыбров, я релиз-инженер Леруа Мерлен и сегодня расскажу, какие в компании были подходы к построению пайплайнов и улучшению качества их кода. Спойлер: мы использовали подход InnerSource и предоставили командам self-service-инструменты.
Предыстория
Начну с того, как в компании развивалось направление IT. Изначально вся разработка велась во Франции — там писали монолит, который закрывал потребности всех магазинов Леруа Мерлен в мире. Но потом поняли, что это нам не подходит, и в какой-то момент приняли решение развивать разработку в России. Подробнее об этом в статье.
Монолит решили распилить на микросервисы и написать много новых продуктов. На это у нас тоже есть статья.
В 2016 в компании было всего четыре разработчика. Через год — 30. Сейчас в российском бизнес-юните порядка 250 разработчиков и 750 других инженеров.
Теперь почти все необходимое для работы компании разрабатывается и хостится в России. Мы занимаемся всем, что связано с сайтом, мобильными приложениями для клиентов и сотрудников, а также с сервисами вроде проектирования кухни или вызова мастеров.
Как мы деплоим приложения?
Код хранится в облачном GitHub, артефакты — в Artifactory, приложения разворачиваются в Kubernetes. CI/CD-процесс построен на Jenkins и пайплайнах в Jenkinsfile, разложенных по репозиториям. То есть мы не накликиваем руками в UI Дженкинса, а пишем сценарии на Groovy, придерживаясь подхода «deploy as code». Это унифицирует код для всех сред и позволяет переиспользовать его на разных проектах. К хранению пайплайнов у нас было несколько подходов, в каждом разные проблемы, но об этом чуть позже.
Что было с CI/CD во времена зарождения IT в компании?
Когда айтишников было мало, а выделенных релиз-инженеров не было вовсе, релизным циклом занимались сами разработчики. Команды были автономны, ни от кого не зависели, ничего не ждали и не согласовывали.
Для наглядности расскажу, как происходил процесс интеграции проекта у абстрактного нового разработчика. Сначала необходимо было или поднять свой Jenkins, или спросить коллег, есть ли он у них, и въехать к ним. Потом сделать свой пайплайн или bash-скрипты с нуля. Как правило, эти пайплайны получались низкого качества, и все разработчики делали похожую работу по несколько раз. Исправления ошибок и новые фичи нужно было дублировать и внедрять в каждый пайплайн. Код был некачественный, потому что все делалось «на коленке» в свободное время, не было выделенных релиз-инженеров, стандартов и структуры.
Для исправления ситуации в компании начала появляться команда DevOps-пропаганды, которая несла философию DevOps в массы, предоставляла общие инструменты, вроде Jenkins, и помогала продуктовым командам. Со своей стороны разработчики решили объединить усилия и сделать один большой общий пайплайн. Важно отметить, что в то время разработка велась одним отделом, который писал весь код. Отдел состоял примерно из 30 разработчиков. В итоге новые фичи в пайплайне стали появляться сразу во всех проектах. Разработчикам не обязательно было разбираться в пайплайне, он работал «из коробки». По крайней мере, по задумке. У этого пайплайна сильно возросло качество кода, он лежал в одном месте и версионировался.
Переход на стандартизированный CI/CD
Вернемся к нашему абстрактному разработчику, который хочет перейти от предыдущего подхода к новому. Для начала ему нужно запустить единый пайплайн. Скорее всего, ничего не получится, вылезет много ошибок, придется обращаться к коллегам, чтобы все исправить. В ходе исправления ошибок его проект будет подогнан под общий стандарт. Но при этом, если он захочет сделать какую-то новую фичу в пайплайне, это будет довольно сложно — придется все согласовывать с другими командами и тестировать в нескольких проектах.
Что получили в итоге? Пайплайн разросся на более чем 1000 строк, из которых только 300 занимал блок считывания параметров, необходимых для запуска и кастомизации сценария. Это было связано с тем, что релизный цикл со временем стал немного отличаться у разных команд, а код был общий. И мы столкнулись с различными проблемами в связи с увеличением числа разработчиков. Требовалось все больше времени для внедрения новых фич. Нужно было это все тестировать на различных кейсах и разбираться в целом в большом пайплайне.
К проблемам добавилось расширение штата. Не было ответственных за пайплайн. Кроме того, у нас в компании произошли изменения. Общий отдел разработки начал разделяться на продуктовые команды, которые стали самостоятельными, и им нужно было больше свободы в релизном цикле.
Вследствие подхода мне как релиз-инженеру приходилось много работать, бегать между командами, решать одинаковые или похожие проблемы и с утра до вечера разгребать рабочий мессенджер. Трудно было работать в потоке, не оставалось времени на решение проактивных задач.
Также в одном из последних выпусков «Техрадара» от ThoughtWorks подобный подход, под названием «разделение владельцев кода приложения и кода пайплайна», был переведен в статус on hold и признан «болезненным и бесполезным». «Техрадар» — это визуализация, которая позволяет понять, какая технология хороша, а какая не очень.
CI/CD в режиме InnerSource
Мы долго думали, как объединить плюсы двух подходов, то есть получить централизованный и качественный код, сохранив свободу построения релизного цикла командами. В итоге появилось комплексное решение из нескольких инструментов, в котором важную роль занимают InnerSource, GitHub и общая библиотека для Jenkins. Мы начали вносить туда универсальный код в виде блоков и вызывать его позже в каких-то других пайплайнах.
Как устроена библиотека Jenkins-методов?
Внутри репозитория лежат groovy-файлы с методами, разбитыми по технологиям. Допустим, если мы берем файл kubernetesUtils.groovy, то там находятся методы, связанные только с Kubernetes, которые можно вызвать.
Как развивать такой масштабный проект? InnerSource и GitHub
На библиотеку с пайплайнами завязалось много сотрудников и команд. Любая ошибка приведет к остановке релизного цикла у нескольких команд и большим потерям времени.
В компании используется подход InnerSource — это аналог open source, но в рамках одной компании. Код на GitHub доступен всем нашим сотрудникам из разных бизнес-юнитов по умолчанию, проекты документированы, а вклад в соседние репозитории поощряется.
В репозитории с библиотекой есть правило, что перед каждым значительным вкладом необходимо завести новый тикет в GitHub Issue. Это не относится к поправкам в документации или маленьким баг-фиксам. Внутри него можно обсудить, необходимо ли реализовывать эту фичу, как это лучше сделать и есть ли какой-то опыт у других команд.
Весь вклад происходит по GitHub Flow. Если абстрактный разработчик захочет что-то добавить в библиотеку, то, после того как заведен issue, он создает ветку и ведет в ней разработку.
Далее разработчик открывает пул-реквест. И всплывает новая проблема — становится много ревью и иногда начинаются споры о стиле кода. Тут нас спасают линтеры — при каждом коммите запускаются триггеры, линтеры проверяют новый groovy-код и внутри GitHub пишут отчет о том, что с этим кодом не так.
Например, если строка длиннее 120 символов, пул-реквест невозможно смержить, пока это не исправить. То же самое произойдет, если юнит-тесты не будут успешны.
Дальше происходит обсуждение, в ходе которого код приводится к одному стилю. Важно, что мы делаем акцент на способности кода к переиспользованию и обратной совместимости. Потом разработчик тестирует новую фичу на различных кейсах уже в боевых пайплайнах. И если все в порядке — делает релиз библиотеки, соблюдая правила семантического версионирования.
Один из главных минусов нашего подхода — высокий порог вхождения. В библиотеке стало сложно ориентироваться, потому что она сильно разрослась, в ней много файлов и документации, сложно найти нужное и понять, как оно работает. Чтобы с этим разобраться, приходится спрашивать либо у мейнтейнеров, либо у тех, кто много работал с пайплайнами. Мейнтейнеры библиотеки у нас отвечают за планирование ее развития и проверяют новые пул-реквесты, а состоят как из релиз-инженеров, так и из разработчиков.
Если кто-то захочет сделать вклад в библиотеку, ему необходимо будет время на то, чтобы оформить пул-реквест и issue. Для этого нужно прочитать правила вклада в репозиторий и соблюсти множество нюансов. Если пул-реквест будет оформлен некорректно, он не получит одобрения мейнтейнеров и не пройдет автоматических проверок.
Еще один минус — ревью у нас происходит долго, потому что релиз-инженеры и разработчики в основном занимаются этой библиотекой в свободное от основных задач время.
Как выстроилась иерархия в пайплайнах с библиотекой?
На низшей ступени находится Jenkins-файл, который лежит в репозитории с приложением. Обычно там всего несколько строчек: импорт библиотек (основной и проектных) и вызов какого-то метода.
В середине иерархии находится пайплайн проекта, который представляет собой отдельную общую библиотеку, только маленькую, под один проект.
Внутри этой библиотеки обычно только один метод, скелет из условий и параметров, которые уже вызывают методы из общей библиотеки.
Вверху иерархии находится общая библиотека, куда попадает только самый качественный и универсальный код.
Несколько примеров, основанных на вызовах методов из общей библиотеки.
Один из пайплайнов написала довольно сильная команда. Он очень сложный, перегруженный, его непросто переиспользовать в других командах. Но в нем много интересных фич — например, канареечное тестирование, автоматический откат неудачных релизов и развертывание изолированной среды для тестирования в Kubernetes. Часть наработок этой команды была вынесена в общую библиотеку, и теперь все могут переиспользовать их методы для работы с Jira, Slack и Kubernetes.
У другой команды особенность пайплайна, построенного на GitHub Flow, заключается в том, что нет необходимости заходить в Jenkins и все процессы происходят по триггерам GitHub и комментариям в пул-реквесте.
Еще один пример — пайплайн от разработчика-энтузиаста. Он основан на нашем старом большом пайплайне, только переделанном под новые реалии. В нем также сделан скелет с вызовом новых методов из общей библиотеки, и этот пайплайн часто форкают и используют другие команды.
В двух последних пайплайнах почти все вызываемые методы, например сборки и деплоя в Kubernetes, общие и лежат в общей библиотеке.
Вернемся к нашему абстрактному разработчику, который захотел использовать подход, описанный выше. Что он будет делать?
Для начала возьмет один из готовых пайплайнов или форкнет пайплайн соседской команды. Потом, если пайплайн не заведется, прочитает документацию и подгонит его под себя.
Пример успешной интеграции инструмента с помощью библиотеки
Подробнее расскажу как интегрировали инструмент HashiCorp Vault. Это сложный для разработчиков инструмент с высоким порогом вхождения. Он необходим для того, чтобы не хранить секреты (пароли от баз, токены, сертификаты) в git-репозиториях и ci/cd-инструментах. Первый раз его интеграция в релизный цикл заняла около месяца. Позже мы написали внутреннюю документацию, и по ней разработчики смогли интегрировать его менее чем за 2 недели.
Далее был написан метод для нашей библиотеки, который позволил одним вызовом выполнять бо́льшую часть работы. И с этим методом интеграция с Vault уже занимает от нескольких часов до пары дней, в зависимости от уровня компетенций в команде.
Что дал нам новый подход?
Выбранный подход дал множество плюсов.
Теперь код хранится в одном месте, он более высокого качества, его использует большое количество команд.
Разработчики снова управляют своим релизным циклом, при этом не изобретают свой пайплайн с нуля, а вызывают готовые блоки из библиотеки.
Разработчики и релиз-инженеры обмениваются знаниями по релизному циклу. Последние при контрибьюте в репозиторий сильно повышают свои навыки программирования благодаря ревью от разработчиков.
Снизилась нагрузка на релиз-инженеров и разработчиков при появлении новых сотрудников и подрядчиков. Коллеги могут зайти в библиотеку и быстрее разобраться в особенностях CI/CD, потому что там есть документация и готовые методы.
Боли и как мы их снимаем
Напомню:
переход на новую версию может быть неудобным;
один из главных минусов нашего подхода — высокий порог вхождения;
в библиотеке сложно ориентироваться;
необходимо время, чтобы оформить пул-реквест и issue;
долгое ревью.
Что мы с этим делаем?
Завели канал в Slack, уведомляющий о новых issue и других важных событиях вроде открытого пул-реквеста или нового релиза. Это позволило нам нагнать заинтересованных людей на ревью и оповещать пользователей о том, что добавилось что-то новое, что-то изменилось. Также мы иногда проводим встречи мейнтейнеров, чтобы обсудить дальнейшее развитие библиотеки.
Боль с порогом вхождения мы пытаемся снять расширением документации. Сейчас документация ориентирована больше на тех, кто хочет сделать вклад в библиотеку. Пишем документацию с различными способами использования и примерами вызовов методов.
Что дальше?
В первую очередь мы хотим добавить в библиотеку бота, который помог бы с соблюдением правил вклада. Это тот большой чек-лист правил, которые необходимо соблюсти перед мерджем. Мы хотим, чтобы бот отписывал комментарии и помогал разработчику делать вклад без привлечения мейнтейнеров.
Также в планах начать собирать метрики об использовании методов из библиотеки и на основе этих данных строить матрицу зрелости команд. Например, в библиотеке есть метод для работы с Kubernetes, который позволяет делать канареечное развертывание, и если мы видим, что команда использует этот метод, значит, она, скорее всего, далеко продвинулась в развитии своего релизного цикла и является довольно зрелой.
Кроме того, у нас есть «хотелка» развивать ChatOps на базе этой библиотеки и Slack-ботов, чтобы включить в релизный цикл сообщения в Slack. Это позволит деплоиться через сообщения и сократить входы и накликивания в UI Jenkins, добавить аппрув релиза от менеджера или тимлида через сообщение и т. п.
Выводы
Итак, мы тестировали три подхода к использованию пайплайнов, в каждом были свои плюсы и минусы.
В первом подходе каждая команда писала свои пайплайны. Команды были свободны, но такой подход приводил к дубликации кода по компании, и качество этого кода страдало. Каждая команда изобретала свой велосипед.
Второй подход подразумевал большой общий пайплайн, в который было сложно внести правки, не навредив соседней команде. Потом мы пришли к подходу, который объединял плюсы предыдущих двух, на базе библиотеки для Jenkins, особого вида Jenkins Job и особого вида вклада в библиотеку.
Подход InnerSource помог в развитии CI/CD в компании. Качество кода пайплайнов и их функционал заметно выросли.
Предоставление self-service-инструментов для команд намного более продуктивно, чем стремление что-то делать за эти команды. Это принесло нам много плодов, в том числе интересные фичи от команд, которые потом распространяются на всю компанию.
А как развиваются общие подходы к CI/CD в ваших компаниях?
nenvoy
Спасибо за интересную статью, довольно полезно и познавательно!