Привет! Меня зовут Константин Белкин, я Teamlead SRE в РСХБ‑Интех. Сегодня расскажу вам, что такое CI/CD на платформе App.Farm, какие методологии мы используем в работе, как работает платформа, какие инструменты мы предоставляем разработчикам и каким образом мы организовали CI/CD в РСХБ для наших любимых разработчиков.
Статья основана на докладе, который я читал на митапе RSHB Meetup: Думай как DevOps в большой компании, прошедшем 29 августа в стенах РСХБ‑Интех.
Смотреть доклад
Сейчас все компании строят платформы, маленькие или большие, которые наполняется своими продуктами. Платформы упрощают жизнь разработчикам. Цель нашей App.Farm аналогичная — упростить жизнь и стимулировать внутреннюю разработку. На момент зарождения проекта РСХБ погряз в вендорах, и это надо было исправлять. Один из верных путей выхода из ситуации — дать разработчику простые и понятные инструменты, чтобы он мог писать бизнес‑логику и развертывать ее где‑то в виде продукта.
Основные принципы, на которых строится CI/CD — это GitOps, IaC и CI/CD.
GitOps — это методология, предполагающая, что все есть код. Если все есть код, значит, мы и описать все это можем в виде кода и выгрузить в инструментарий, который все это обработает и выведет на продуктивный контур или зону разработки.
Вот так выглядит примерный типовой маршрут.
Разработчик пишет код, закидывает его в Git, там что-то билдится, собирается image
, есть какие-то infra-конфиги, которые тоже собираются и попадают в Git. У нас также есть GitOps тулы, которые в итоге выливают все это на один или несколько кластеров. У нас для application приложения используется один кластер.
Если говорить про IaC-подход, он просит полностью описать свою инфраструктуру как код, указать адреса серверов, какой инструментарий поставить, какие роли накинуть и так далее. Соответственно, код для управления инфраструктурой может писаться в разных стилях: декларативным, либо императивным.
Мы используем декларативный подход. Он же может использоваться при разработке приложения или развертывании инфраструктуры.
Третий подход, который мы используем — CI/CD Flow. Мы поставляем разработчикам не гигантские YAML или у нас нет команды в каждом отделе, который бы описывал свой CI/CD процесс. Мы подумали, что будет проще сосредоточить всю компетенцию в части CI/CD вокруг платформенной команды и поставлять 3 строки. Если разработчик хочет писать на Java или вендор хочет заехать в платформу, он может прописать три строки, все заедет и будет работать.
Наш типовой pipeline выглядит так.
В начале мы проводим верификацию. Потом, соответственно, проходит билдинг, тесты, чеки, проверки, DevSecOps, паблишинг и, в конце концов, все уезжает в деплой. Все это обязано большим количеством дополнительного инструментария.
В платформе AppFarm мы поставляем следующие инструменты как код.
Платформенная документация — инструмент обмена знаний. Это, наверное, самое важное, что есть вообще в CI/CD-подходах. Ты должен рассказать разработчикам или другим девопсам, как твоим инструментарием вообще пользоваться. Flow создается тоже с помощью кода.
Есть основные данные, которые мы используем в платформе. Это исходный код самой платформы и кодовая база, конфигурация запуска этого приложения, дополнительные файлики, которые нужны, чтобы приложение вообще запустилось. И есть дополнительные части, которые тоже мы описываем как код: kafka кластера, линки, связи, взаимосвязи между собой. Ролевая модель и дашборды мониторинга для сервиса — если надо и база данных. Секрет мы храним отдельно в Vault.
Итак, здесь вы можете видеть табличку, какие вообще flow мы поддерживаем на данный момент.
У нас java является самым популярным языком в банке. На нем пишется почти 30% приложений. Поэтому мы его поддержку реализовали в первую очередь. Ну и, конечно же, для frontend‑приложений для JS + TS сделали то же самое. Последняя фича, которую мы внедрили, это поддержка прямого развертывания докер‑контейнеров от вендоров. Многие сейчас поставляют свои приложения в докер‑формате, и мы можем спокойно их принять и с ними поработать. Для этого сделали специализированный пайплайн.
Как я уже говорил, мы используем декларативный подход и описываем с помощью него конечный результат. А дальнейший наш подноготный инструментарий приведет его к действительному результату, который мы получим в каком‑то виде.
Как выглядит декларативное описание сервиса.
Мы придумали свою манифестацию, то есть у нас все попадает в конечном итоге в kubernetes. Но есть специальный обработчик, который обрабатывает короткие манифесты. То есть разработчику не нужно знать спецификацию kubernetes, как написать deployment, описание сущности service, как составить какой‑нибудь virtualService, написать NetworkSet и NetworkPolicy и так далее. Он может написать по документации условно 16 строчек кода, параметризовать свой сервис, передать какие‑то переменные, которые нужны для его запуска, и поработать с этим.
Кроме того, мы генерируем различные дополнительные сущности. Раньше, чтобы оформить, например, kafka кластер, надо было идти в отдел управления системным администрированием, заказать сервер, согласовать доступы, доступ на сам сервер получить и развернуть там Kafka. С декларативным подходом стало все проще. Ты просто пишешь пару строчек, говоришь, что мне нужен Kafka‑кластер для информационной системы. После оператор сходит в ресурс‑пул, заберет эти ресурсы из твоего ресурс‑пула и развернет тебе готовый Kafka‑кластер для твоей работы. Все, дальше подключай и работай с топиками.
Вот пример, как выглядит декларативность у нас в рамках системы.
Мы достигаем декларативного подхода с помощью специального платформ‑оператора, который мы написали. Он обслуживает специальные K8S сущности. Если создать маленькое описание, которое состоит из восьми строк, довести его в кубер‑кластер с помощью deploy tool, то платформ‑оператор сходит, вычитает это значение из кастомного ресурса и раскидает его на две сущности. Он создаст namespace и создаст ресурс‑квоту по значениям, которые здесь описаны.
Есть и другой пример.
Разработчик декларирует линк, говорит, что я буду ходить из сервиса frontend_for_asp
в сервис asp_net_service
, который здесь тоже расположен, проливает пайплайн, попадают, соответственно, платформенные сущности, появляется сущность платформенного сервиса, сущность деплоймента и сущность кастомного линка.
Платформ‑оператор, говорит:
— Ага появился линк.
— Что надо сделать?
— Надо сделать Virtual‑service.
Пошел, на основе короткой сущности этих вот шести строчек создал большой манифест и раскатал его. Тем самым обеспечил доступ из frontend в backend.
У нас в платформе недоступно никакое динамическое создание сущностей. То есть вы не можете прийти руками что-нибудь создать сами в kubernetes. Надо всегда все описывать декларативно с помощью языковой модели, которую мы вам предлагаем, и на основе этого получать сетевые доступы, роли, разрешения и так далее. Все должно быть описано, это нам дает чувство безопасности. Ты всегда знаешь, куда твой сервис должен уходить, а куда не должен. Нет такого, что ты не понимаешь вообще с чем сервис взаимодействует.
Инструментарий
У нас инструментарий включает несколько компонентов: где мы храним код, как мы разворачиваем все средства, где мы храним наши артефакты. Когда мы начинали проект, был выбор из пяти решений на рынке — GitHub, GitLab, Gitea, Bitbucket, Gogs. Сейчас большинство уже ушли с рынка, но open‑source остались. Дальше мы хотели выбрать какую‑то CI‑систему. У нас здесь выбор был довольно большой, что‑то мы знали, что‑то нет. В конечном итоге наш выбор пал на GitLab, GitLab CI и Nexus в качестве хранилища.
Почему мы их выбрали? Потому что мы с ними уже работали раньше, и многие из вас наверняка работали. Есть большая база наработанная, и это в целом хорошие качественные продукты, которые на рынке уже не один год присутствуют.
Итоговый состав следующий: GitLab, у него под капотом Crunchy Postgres, Minio, которое является объектным хранилищем для GitLab, в качестве CI‑инструмента это GitLab CI, ну и Nexus.
Преимущества Open Source для всех ясны и понятны. Мы с ним можем работать, можем его менять, знаем что он безопасен, его легко проверить, при этом мы не вендорлокаемся на поставщике ПО. Мы можем всегда перейти на какие‑нибудь другие open‑source продукты. Это бесплатно, но, конечно, это спорный вопрос. Вы всегда платите за это человеческим ресурсом. Сейчас тренд на использование открытого ПО в России, поэтому это хорошо.
Чтобы воссоздать всю систему CI/CD нам кроме каких‑то глобальных инструментов, которые мы используем, нужно еще как‑то облегчить жизнь разработчикам и улучшить безопасность наших CI/CD‑пайплайнов. Мы написали несколько средств, которые нужны для работы. Давайте поговорим про каждый из них.
Итак, верификатор Verifier. Он нужен для того, чтобы разработчика направлять на путь истинный. Может быть такое, что разработчик что-то свое решил в проекте воссоздать вне соответствия с документацией. Верификатор статически проверит, а все ли он правильно заполнил, чтобы собрать его проект. То есть мы на первом этапе запускаем проект в верификатор, проверяем и можем остановить его заранее до появления ошибок, пока он там не собрался.
После наступает очередь DockerfileGen. Многие разработчики могут писать докер файлы, но мы решили, что не надо им позволять это делать. Поэтому мы за разработчика выбираем, какой докер файл ему использовать. Для этого у нас есть DockerfileGen. Это очень полезно, в том числе с точки зрения безопасности. Ты можешь просто взять и, в случае каких-то поломок в общем пайплайне, ну точнее в общих образах, пересобрать образ, чтобы избавить его от каких-то vulnerabilities или других проблем. Тебе не надо как DevSecOps бегать по всем проектам.
Есть такой у нас еще такой продукт, как Signer. Он занимается перекладкой в продуктив, создает ключ с одной стороны, выкладывает его в репу и при перекладывании проверяет, что образ действительно тот, который едет на прод.
Buildjit‑Journey — это наш продукт для сборки. Он пришел на замену Kaniko.
Kube‑deploy‑apps — это наш инструментарий, позволяющий нам деплоить что‑либо в платформу, то есть там описаны YAML‑манифесты, шаблонизация происходит, и он же формирует сущности, деплойменты, сервисы и все остальное.
BASE, BRICKS, JOBS
Давайте поговорим, как мы собираем CI/CD пайплайн. Мы избрали для себя подход довольно нетривиальный. Большие манифесты и многостраничники — это не очень хорошо, с ними тяжело работать. Оказалось, что есть такой принцип — BRICKS. Он лежит в основе разделения по функциональным особенностям. Можно поделить YAML‑манифест на короткие манифестации, которые будут нести каждый в себе какую‑то свою джобу. Таким образом мы сможем из разных кусочков, переиспользуя одни и те же кусочки в разных пайплайнах, собирать разные пайплайны для разных языков. Если хотите, можете почитать на GitLab про это и как вообще можно оптимизировать свои YAML‑манифесты. Ссылка на доку GitLab.
Итак, как выглядит структура нашего проекта, который используется, чтобы сделать один маленький include на три строчки.
В начале идут bricks. Это как раз те кусочки, которые мы можем переиспользовать. Они представляют собой какие‑то deploy‑функции, build‑функции, test‑функции и так далее. Верификация тоже туда входит. Далее base — это основа, то, без чего пайплайн точно работать не будет. Туда уже входят какие‑то основные компоненты, images, их версии, это переменные для работы пайплайна, какие‑то deploy‑функции, которые точно нужны для работы, build‑компоненты, test‑компоненты и все остальное.
Из этих первых двух кусков, bricks и base, мы создаем flow. В рамках flow есть несколько типов: CDL (delivery flow), CDP (publishing flow), и когда нам просто нужно CDP без всего остального. Обычно это для вендоров используется. Нам просто надо развернуть сервис из какой‑нибудь джарки, а джарку запустить в контейнере и все. На самом деле, это редкий кейс, но им пользуются. В конце мы получаем собранные lang для языков, то есть для Java, для Go, Dotnet и так далее. Если захотел только доставлять до продуктива, то используйте CDL. Если хочешь паблишиться в платформу, используй CDP.
Как выглядит в итоге подключаемый YAML‑манифест.
За тремя строчками скрывается вот такой набор stages и includes, которые в итоге превращаются в такую стену джобов.
Тут можно видеть, что какие‑то проверки не прошли, но в целом проект собрался и даже уехал на продакшн.
Подключение итогового манифеста, как я показывал раньше, выглядит именно так, в виде трех строк, где мы указываем просто flow, что это паблишинг, что это сервис, написанный на Java.
Какие вообще тут есть плюсы. Конечно, это удобство со стороны пользователя, то есть в части документации мы условно описываем — подключи три строки, и все будет классно. Не надо портянку пайплайнов себе писать, какие‑то дополнительные манифесты, что‑то еще расписывать. Это, соответственно, минимизация полей. Мы всегда знаем, что в проекте должно быть три строки и больше ничего. То есть если кто‑то навешивает еще какую‑то логику, а такие у нас тоже есть деятели, это плохо. Мы говорим, убирайте это, это нам не нужно.
И в конце концов, это не код, это ссылка на код, поэтому за ним скрывается гигантский YAML в 5000 строк.
Проблемы
Проблем было очень много, но я вынесу две основополагающие, из‑за которых вставали «коммунальные» пайплайны на неопределенный срок.
Первая проблема, с которой мы столкнулись, это проблема запутанности кэшей. Помните, я говорил про Kaniko? Она нам породила эту проблему, и так появился Buildjit‑Journey. Мы использовали Kaniko, ускоряющую сборку, потому что она кэширует docker‑образа, укладывает их в Nexus, и на основе кэшей и других каких‑то stages забирает, грубо говоря, берет кэши и переиспользует их при дальнейшей сборке.
В конечном итоге, что стало происходить. Kaniko сошла с ума и начала в JVM‑проекты подкладывать куски Angular и не только. Все это начало превращаться в кашу. В продуктив мог выехать собранный контейнер, в котором были куски от Java, куски от Angular, и как все это работало вообще непонятно.
Мы подумали, а давайте что‑нибудь придумаем с этим и подумаем, как дальше жить. Шел 2021 год, мы смотрели, что в трендах вообще есть. Увидели такое готовое решение, как BuildKit от Moby, создателя Docker. Посмотрели, в принципе, работает неплохо. Нашли очень хорошую статью японского исследователя, почитали. Оказалось, что BuildKit намного быстрее работает, чем Kaniko — на 15–30%. Мы решили удариться в эту историю, но нужно было гибко выкинуть Kaniko и привезти BuildKit.
У Kaniko есть логика entrypoint для запуска. А у BuildKit этого нет. Соответственно, чтобы собрать BuildKit и чтобы он начал работу, надо было написать wrapper. Это обертка над BuildKit и BuildKitD, чтобы он мог представляться и клиентом, и демоном одновременно. Почему это нужно? Мы используем Istio, у нас везде MTLS, и надо было встать с сертификатами в разрезе для всех сборок.
Соответственно, подключили в конечном итоге как замену Kaniko и получили довольно быстрые пайплайны. У нас в среднем пайплайн, если просто для разработчика, собирается за 3–4 минуты если это первая сборка, дальнейшие по 2 минуты. (Не в часы пик, там сборка растягивается от 6 до 10 минут).
Была еще вторая проблема. Мы заявляем, что надо правильно писать код, поэтому ввели SonarQube. У нас даже эту тему проверяют наверху, сколько было проверок сделано. Поэтому мы используем SonarQube и статичные проверки в нем. Среднее время проверки в SonarQube занимает 7 секунд (в средне статистическом микросервисе). Мы проверяем 25 различных технологий, которые там внедряются, это JSON, SQL, Java проекты и так далее.
В один момент у нас начали из‑за SonarQube тормозить жутко сборки. Среднее время на одну проверку увеличилось до трех минут. Так как мы используем Community LTS‑версию, у нас был всего лишь один воркер. Представляете, куча разработчиков разрабатывает, и они все встают в очередь. Буквально за 20 минут создавалась очередь на 16 часов вперед.
В поисках проблемы мы откатили последний релиз SonarQube и решили подкинуть ему ресурсов. И в этой части тоже была наша фатальная ошибка. Как оказалось, SonarQube обладает очень интересной особенностью — чем больше процессора ты ему отдаешь, тем медленнее он будет работать. Как будто толстеет, кричит, что больше не может двигаться и молит о помощи. В конечном итоге у нас пайплайны начали из‑за этого замедляться.
Мы эту проблему не доисследовали. Она, возможно, оказалась на уровне ядре линукса, поскольку мы использовали старую версию Linux 3.12. Но мораль истории в том, что надо катить все последовательно, ресурсы вези отдельно, обновления вези отдельно. И всегда обращать внимание на все факторы и смотреть изменения в diff.
Бизнес-выгода
Основным средством достижения положительного результата, при построении «коммунальных» пайплайнов является документация. Мы должны очень четко давать разработчику информацию, как он должен вести свой проект, как работает платформа, какие манифесты можно применять для работы. Поэтому мы внедрили качественный продукт в качестве документации, который называется Docsify.
Так выглядит наше руководство по платформе. Это заглавная страница, на самом деле там целая куча текста. Можно книгу выпустить страниц на 500.
Что представляет себя документация. Это технологический стек на основе Docsify. Хорошая технология, позволяющая структурировать информацию с Markdown. Техпису, чтобы писать документацию на Docsify, достаточно просто изучить Markdown. Подключили мы туда еще движок Commento для возможности оставлять комментарии для улучшения документации. Такая дока может дорабатываться пользователями через Merge‑request. Вот ссылка на проект Docsify, можете забрать.
Какие плюсы принесло внедрение единых подходов CI/CD.
Теперь не нужны девопc‑специалисты в каждый отдел. Обычно всегда есть какая‑то девопс‑служба, которую продают в отделы. У нас есть один отдел поддержки на всю платформу. В платформе работает весь банк. Соответственно, мы все унифицировали до одной службы, единой разработки и единых стандартов. Мы все знаем, все видим, за всем наблюдаем.
Переход разработчика от одного проекта в другой занимает минимум времени. Он пользовался технологией CI/CD в одном отделе, перешел в другой — здесь то же самое, все хорошо и привычно.
Уменьшили технологический зоопарк. У нас есть фиксированные базы данных, которые мы готовы подключить, брокеры, которых мы готовы обслужить. Есть фиксированная служба обмена сообщения по всей платформе. Соответственно, вовлечение новых разработчиков тоже занимает минимум времени. Можно очень быстро работать с документацией и посмотреть, что тебе нужно, есть классный поиск.
Разработчики сосредотачиваются на бизнес‑логике, а не на инфраструктуре. Они не пишут докер‑файлы и не думают, как развернуть Kafka, подключить Zookeeper, развернуть IBM MQ или что‑то еще. Все это развертывается на основе манифестов, в которые ты просто пишешь три строки, и у тебя все готово.
Напоследок, давайте по цифрам поговорим. У нас на платформе работает 2300 разработчиков, всего 3620 пользователей, если туда добавить менеджмент, админов систем и так далее. В разработке находится сейчас 5518 проектов, 1,123 млн пайплайнов было запущено за все время работы и сделано 23 689 запросов на поддержку. Было сделано 273 697 MR и 1,598 млн коммитов.
Тут немножко графиков. По ним видно, что мы постоянно в росте, буквально каждый день.
Это в общих чертах. Подробнее базовое описание технической составляющей App.Farm можно почитать в предыдущей статье.