Привет, Хабр! На связи команда разработки App.Farm в РСХБ-Интех. Хотели бы представить вам следующую часть цикла статей об App.Farm CI, одной из подсистем нашего продукта — PaaS App.Farm.
App.Farm — платформа по типу PaaS для стандартизации процесса разработки бизнес-приложений: от хранения исходного кода до запуска сервисов. App.Farm CI — подсистема обеспечивающая хранение кода, артефактов, автоматизацию сборки.
Предыдущие части:

Какие темы затронем в этой части:
-
Архитектура подключаемых flow
-
Архитектура сборки
-
Базовый набор flow
-
Требования к flow
-
Статическая верификация
Дисклеймер: это история развития нашего продукта, наш опыт и грабли, на которые наступили, здесь не будет глубокого технического сравнения тех или иных технологий.
Архитектура подключаемых flow
Для автоматизации сборки мы выбрали Gitlab CI, поэтому для конфигурации flow будем использовать синтаксис Gitlab CI на базе YAML. Официальная документация для ознакомления с этим синтаксисом: https://docs.gitlab.com/ci/yaml/.
В предыдущих частях мы рассказывали, что нашей целью является предоставление пользователям CI/CD как сервис, в том числе и его конфигурацию. Например, «Разрабатываешь на Java? — Вот держи готовый конвейер для сборки, публикации и развертывания Java-приложений!». Этот подход мы выбрали по следующим причинам:
-
Стандартизация процесса разработки: все команды, использующие один стек, применяют одинаковые подходы по упаковке и публикации своих приложений.
-
Фокусировка команд разработки на бизнесе: разработчик заботится только о бизнес-логике своего приложения, обо всем остальном (сборка, публикация, развертывание) позаботится платформа.
-
Централизованное развитие flow: платформа отвечает за работоспособность и развитие flow, изменения должны применяться централизованно для всех команд.
-
Общий стандарт как фундамент для развития платформы: когда есть закрепленные правила в работе и определенный функционал, обеспечивающий их, можно на основе этих правил дальше развивать платформу.

Осуществить эту задумку позволил механизм include, предоставляющий возможность разместить написанные нами CI-скрипты в подконтрольном нам проекте. Для пользователей нам оставалось лишь дать строку для подключения этих скриптов в файле .gtilab-ci.yaml
своего проекта.
Синтаксис Gitlab CI в виде YAML отлично подходит для быстрого прототипирования и написания CI для своего проекта, имеет довольно низкий порог входа для реализации типичных сценариев. На просторах интернета существует множество готовых сниппетов под конкретные проблемы. Однако у нас стояла не тривиальная задача — сделать CI-скрипты как сервис и реализовать это на базе YAML. Нам не хотелось иметь в репозитории один uber-yaml на все случаи жизни, хотелось подойти к организации проекта также, как мы пишем, например, бэкенд. Поэтому начали с продумывания структуры каталогов. Для организации скриптов внутри проекта решили также использовать механизм include
, а для переиспользования одних и тех же блоков в нескольких местах — extends
.
Создали проект и определили верхнеуровневые каталоги в нем для размещения скриптов:
-
bricks — подключаемые конфигурации, которые не влияют на stages конвейера. Это запчасти, которые могут быть переиспользованы при описании stages конвейера.
-
bases — подключаемые конфигурации, которые влияют на stages конвейера, формируются из bricks-конфигураций.
-
flow — конфигурации, подключаемые пользователями в свой проект, формируются как набор stages из bases-конфигураций.
Внутри flow мы разделили конфигурации на Delivery и Deploy на каталоги ci-cdl
и ci-cdp
соответственно. Этим мы сразу разделяем пользователей при онбординге на тех, кому нужна только сборка и доставка артефакта в хранилище артефактов, и тех, кому нужны сборка и развертывание в кластере. Внутри дополнительно разделяем структуру на платформенные и пользовательские конфигурации, то есть те, которые используем для разработки самой платформы, и те, которые будут отданы пользователям.
Забегая вперед, хочется отметить, что сейчас мы находимся в процессе разделения текущего Gitlab на два — платформенный и пользовательский, в платформенный Gitlab будут вынесены все репозитории платформенных компонент, чтобы четко разделить окружения разработки самой платформы и пользовательских окружений.
Конечным файлом в иерархии flow является конфигурация под конкретную технологию, которую будет подключать пользователь. Пример содержимое файла .gitlab-ci.yaml
пользовательского проекта:
include: - project: 'rshbintech/integrations/ckpr/ci-cd/base' file: '/flow/ci-cdp/svc/jvm.yml'
Если заглянуть в содержимое файла /flow/ci-cdp/svc/jvm.yml
можно увидеть список stages:
include: - local: /bases/workflow.yml - local: /bases/variables/images.yml - local: /bases/verify/svc/jvm.yml - local: /bases/dockerfilegen/jvm-image.yml - local: /bases/onbuild/settings/jvm.yml - local: /bases/onbuild/dependency/long.yml - local: /bases/onbuild/build/long.yml - local: /bases/test/code-quality/jvm.yml - local: /bases/test/security/devsecops.yml - local: /bases/onbuild/unit-test/long.yml - local: /bases/onbuild/package/long.yml - local: /bases/onbuild/publish/image-long.yml - local: /bases/deploy/k8s.yml - local: /bases/deploy/k8s-manual.yml - local: /bases/test/e2e/trigger.yml - local: /bases/test/allure/source-project-report.yml - local: /bases/release/long.yml - local: /bases/publish-prod/oci-image.yml - local: /bases/deploy-prod.yml - local: /bases/deploy/dynamic.yml stages: - verify_static - devsecops_init_dojo - devsecops_antivirus_scan - devsecops_code_scan - dockerfilegen - dependency - devsecops_sca_scan - devsecops_codebase_scan - build - test - package - publish - devsecops_image_scan - devsecops_container_scan - devsecops_artifact_scan - deploy - deploy_dynamic - e2e - deploy_report - analyse_report - release - sign - publish_prod - deploy_prod
На примере стейджа test можно рассмотреть, из чего он формируется. Рассмотрим этот стейдж в части проверки качества кода. Для его обеспечения подключается конфигурация /bases/test/code-quality/jvm.yml
:
include: - local: /bricks/test/code-quality.yml code_quality: stage: test extends: - .code_quality_sonarqube variables: SONAR_EXCLUSIONS: 'app/**/target/generated-sources/**/*.java,app/**/target/generated-test-sources/**/*.java' dependencies: - build
Здесь можно обратить внимание, что мы попадаем в каталог bases, в котором есть описание стейджа test и задания code_quality, при этом сам файл имеет нейминг jvm.yml
, то есть в зависимости от технологии могут быть разные настройки проверки качества кода, например, перечень каталогов для исключения из проверки.
Дополнительно шаг подключает расширения .code_quality_sonarqube
из конфигурации /bricks/test/code-quality.yml
, рассмотрим его содержимое:
.code_quality_sonarqube: image: $IMAGE_NAME__SONARSCANNER:$IMAGE_TAG__SONARSCANNER variables: SONAR_EXCLUSIONS: "" script: - sonar-scanner -Dsonar.exclusions=${SONAR_EXCLUSIONS} dependencies: []
Здесь мы уже видим конечную конфигурацию, подключаемый образ и выполняемый скрипт. Таким образом, такую конфигурацию можно подключить в любой технологии и конфигурировать ее можно с помощью переменных окружения.
В подобной канве мы реализовали и другие stages. Нам удалось структурировать содержимое flow и заложить некоторый запас прочности на развитие этих конфигураций. И все же нужно понимать, что с точки зрения Gitlab на выходе у него получится effective-yaml, который будет содержать YAML-полотно, в котором разрезолвлены все include и extend. Такой YAML можно посмотреть, перейдя в Gitlab на страницу Build/Pipeline editor и перейти на вкладку Full configuration. На текущий момент полная наша конфигурация занимает около 6000 строк.
Несмотря на все плюсы такого подхода, конечно же, со временем выяснились и шероховатости такой CI-конфигурации:
-
Невозможность нормально локально отладиться для воспроизведения проблемы или проверки нового функционала.
-
Невозможность удержать в голове все условия и комбинацию include и extend при реализации или исправлении flow.
-
Отсутствие нормальных способов реализовать ветвление логики.
-
Сложность внесения изменений с ростом количества скриптов.
В общем YAML как-будто бы становится не очень пригодным инструментом для построения CI-платформы.
Сейчас мы живем с этим подходом, хотя есть план реализации CI 2.0, где мы хотим перейти на собственный DSL для написания CI-конфига. В качестве базы думали использовать https://tekton.dev/ — Cloud Native CI/CD, в котором уже есть все примитивы для реализации CI на базе Kubernetes.

Архитектура сборки
Одной из целью автоматизации разработки в нашем случае были упаковка, тиражирование артефактов и развертывание в среде исполнения. В качестве среды исполнения нами был выбран Kubernetes, поэтому для запуска пользовательских приложений их нужно было предварительно собрать в OCI-образ с исполняемым артефактом внутри. Поэтому нам недостаточно было реализовать CI-скрипты, нужны были еще и Dockerfile под каждую из технологий.
Написание Dockerfile, так же как и CI-скриптов, мы не хотели давать на откуп пользователям, по тем же причинам которые описаны в предыдущей главе. Мы решили предсоздавать Dockerfile на одном из первоначальных шагов конвейера, в следующих частях мы подробно расскажем, как мы это делаем. Вместе с этим мы фейлим конвейер, если пользователь разместил свой Dockerfile в проекте. Таким образом мы взяли под контроль сборку образов, что позволило нам:
-
Управлять базовыми образами: каждый Dockerfile основывается на базовом образе, который мы тщательно готовим и проверяем через отдел ИБ. В том числе мы можем централизованно обновлять базовые образы при необходимости.
-
Контролировать содержимое и внешние зависимости: базовых образов может быть несколько — для сборки и для рантайма, мы отслеживаем, чтобы ни в какую из этих стадий не попало ничего лишнего.
-
Использовать оптимизации и лучшие практики: например, использовать Multi-stage build.
-
Обогащать образы необходимыми компонентами: при необходимости мы можем поместить в образ необходимые компоненты или сертификаты, которые необходимы для дальнейшей эксплуатации образа.
В качестве раннера для исполнения CI-заданий мы решили использовать Kubernetes Executor, потому что экспертиза по Kubernetes у нас уже была, и его же мы планировали использовать в качестве среды исполнения для пользовательских приложений. Встал вопрос, в чем собирать образы в Kubernetes и на тот момент мы выбрали в качестве сборщика Kaniko https://github.com/GoogleContainerTools/kaniko. У Kaniko есть интеграция с Kubernetes и Gitlab, а также ключевая функция — кэширование слоев. Кэширование было важно для оптимизации времени выполнения конвейера, в особенности компенсации временных потерь из-за так называемого «межджобья» — временными интервалами между завершением одной Kubernetes Job и началом следующей.
Упрощенный пример Dockerfile для JVM-flow для понимания количества стадий:
FROM ${IMAGE_NAME__MAVEN}:${IMAGE_TAG__MAVEN} as deps WORKDIR /app COPY pom.xml . RUN mvndependency:resolve -DskipTests FROM deps as build COPY src src RUN mvn clean compile -DskipTests FROM build as test RUN mvn test RUN export COV_REP_FILE=target/site/jacoco/index.html FROM build as package RUN mvn package -DskipTests FROM ${IMAGE_NAME__JRE}:${IMAGE_TAG__JRE} as runtime COPY --from=package /app/target/demo-backend.jar /app USER app CMD exec java -jar demo-backend.jar
На этом примере можно увидеть, почему необходимо брать под контроль одновременно и CI-скрипты, и Dockerfile: в Dockerfile появляются определенные targets, которые маппятся на stages в CI.
После того как мы ввели в эксплуатацию описанные выше инструменты мы начали замечать следующее: с ростом количества пользователей на конвейере мы стали испытывать проблемы с Kaniko, у нас начались коллизии с кэшом. То есть Kaniko мог подсунуть слой из кэша конвейера другого приложения. Мы предпринимали некоторые попытки бороться с этим, но в итоге решили отказаться от Kaniko из-за этой и других его проблем, а именно:
-
Медленная работа.
-
Своя нестандартная реализация OCI, из-за чего некоторые вещи работали не так, как ожидалось.
-
Ощущение полузаброшенности проекта, в котором многие критичные issue никто не фиксит.
По итогу мы перешли на Buildkit https://github.com/moby/buildkit, который поддерживает параллельную сборку, имеет ядро современного докера в части сборки (buildx), работает rootless и т.д.
Переход на buildkit решил все наши проблемы, которые были с Kaniko. Со временем пришлось добавить собственных доработок и сделать обертку над buildkit, о которой мы расскажем в будущих статьях.

Таким образом мы настроили сборку взяв под контроль формирование Dockerfile.
С ростом пользователей мы стали сталкиваться с тем, что при сборке все-таки может потребоваться кастомизация образов. Например, каким-то приложениям требуется особый пакет для сборки, но мы не можем включить этот компонент в базовый образ или в спецификацию Dockerfile, потому что это нужно только одной команде, а всем остальным нет. Эту проблему мы решили реализацией так называемого «белого списка» пакетов. Пользователи обращаются к администраторам платформы за необходимостью установки какого-либо пакета, администратор проверяет допустимость установки данного пакета, если все ок, то включает его в белый список. Пользователь в свою очередь в своем .gitlab-ci.yaml
указывает необходимый пакет в специальной переменной. При выполнении CI выполняется проверка указанного пакета в белом списке, если он там есть, то при сборке в Dockerfile выполняется доустановка пакетов, по требованию пользователя.
Подводя итог про реализацию сборки, хотелось бы затронуть раннер на базе Kubernetes Executor. В нашей реализации используется общий раннер, мы его называем shared, который обслуживает все пользовательские проекты. Под каждую джобу в конвейере в Kubernetes создается Job, в которой выполняется логика CI Job. И это отлично работало, когда все джобы в конвейере были условно-быстрыми. Спустя время у нас появился пользователь с монолитом, который мог собираться в течении суток, т.е. буквально build выполнялся 20 часов. Несколько запущенных конвейеров на таком репозитории могли утилизировать все ресурсы раннера, которые шарились между всеми пользователями. Это приводило к ожиданиям в выполнении конвейеров у других команд. Для решения этой проблемы мы придумали System Runner, его суть в том, что раннер резервируется под определенную систему и выполняется на изолированном узле, на котором столько вычислительных ресурсов, сколько принесла команда разработки этой системы. Таким образом для этого монолита закреплен индивидуальный раннер и узлы в Kubernetes для исполнения, что позволило избежать аффекта долгих сборок на другие конвейеры.
Базовый набор flow

Мы придумали и реализовали архитектуру CI-конфигураций и сборки приложений. Все было готово к тому, чтобы начать писать на этом CI flow под конкретные технологии. Мы провели ресерч в компании, чтобы понять, какие технологии используются для разработки в командах. Конвертировали этот перечень в список, расставили приоритеты и получили на тот момент такую картину:
-
Java/Kotlin
-
C#/.NET
-
Angular
-
React
-
Python
-
NodeJS
-
Golang
Реализация всех этих flow покрыла бы 90% всей внутренней разработки, а также приложений подрядчиков. Плюс мы сами на чем-то тоже разрабатывали. Для нас на тот момент были актуальны Java\Kotlin, Angular, Golang — это те технологии, которые мы использовали для написания тех или иных платформенных компонент. Забегая вперед, скажем, что теперь практически вся разработка ведется исключительно на Golang, а WEB-UI на React.
Очевидно, что мы не стали делать всё и сразу, нам нужен был прототип и пилотная команда. В качестве пилотного flow мы выбрали JVM-flow, который имел наивысший спрос. Чтобы еще сузить скоуп работы, было решено сосредоточиться на одном сборщике — maven. К тому же он более формализован и лучше поддается контролю с точки зрения анализа манифеста, в отличие от Gradle. А так как вся политика нашей разработки «все что не разрешено — запрещено«, то maven и pom.xml больше подходил под формализацию требований к командам разработки.
Дальше на примере реализации JVM-flow мы рассмотрим какие требования мы выставили командам разработки, зачем вообще эти требования и как мы контролировали их исполнение. Все подобные шаги мы и сейчас проходим при реализации и внедрении новых flow.
Требования к flow
Откуда вообще появляются какие-то требования к командам разработки? Давайте рассмотрим процесс разработки нового flow.
Для реализации flow нам нужно написать под него Dockerfile и набор CI-скриптов для конвейера. Dockerfile предполагает написание конкретных инструкций, например, таких как «скопировать каталог с исходным кодом с хостовой машины», значит нужно указать как минимум путь и имя каталога с исходным кодом, чтобы наш докерфайл не зафейлился на первом шаге. Далее нам необходимо с помощью package-менеджера выполнить такие шаги, как скачивание зависимостей, запуск unit-тестов, сборка, публикация артефакта и т.д. То есть здесь уже появляется конкретная имплементация package-менеджера, на примере JVM-flow это может быть maven. А package-менеджеру нужен файл манифеста, с которым он работает, в случае с maven — это pom.xml. То есть нам нужно в Dockerfile скопировать еще и манифест сборщика, у него должно быть определенное имя и местоположение.
Исходя из этого, у нас, как минимум, к пользователю формируются следующий базовый набор правил:
-
При наличии исходного кода его следует расположить в определенную директорию с определенным именем.
-
Проект должен поддерживать определенный package-менеджер.
-
В проекте должен существовать манифест для package-менеджера с определенным именем в определенной директории.

То есть говоря о каких-то «требованиях», мы не имеем ввиду какие-то экстраординарные зубодробительные правила, из-за которых команде разработки придется переписать свой проект, чтобы заехать на платформу. Требования, которые мы формируем на этапе реализации flow, обычно можно разделить на следующие группы:
-
Требования, на которые опирается Dockerfile, CI-конфигурация или среда исполнения. Это по сути можно отнести к функциональным возможностям и ограничениям, то есть если flow реализовано на maven, то оно не может собрать проект на Gradle. Сюда же относятся все случаи, на которые завязаны инструкции в Dockerfile, такие как расположения и нейминг каталогов, манифестов, исполняемого файла.
-
Требования, которые приняты в общемировом комьюнити к оформлению проекта на этой технологии. Наша основная цель при реализации flow — сделать его максимально соответствующим мировым практикам, если мы ведем речь о Dockerfile, то мы переиспользуем рекомендованные базовые образы, практики и подходы, годами выработанные в комьюнити и показавшие себя с лучшей стороны. Также максимально искореняем специфику тех или иных команд или организацией, чтобы в итоге этот flow или Dockerfile можно было заопенсорсить и его мог as is использовать кто угодно в своих проектах (если он также следуют общепринятым тенденциям в комьюнити). Это практика, кстати, для нас стала такой «лакмусовой бумажкой» хороших и не очень проектов, которые постепенно стали прибывать на платформу: если команда писала проект, забивая на элементарные правила гигиены, то им было сложно заехать на платформу. И напротив, те, кто писал проекты качественно, — залетали только так.
-
Требования ИБ. Пункт говорит сам за себя, для нас это сводится к своевременному обновлению базовых образов и версий тех или иных технологий, используемых во flow. Например, поддерживаются только определенный перечень версий Java.
-
Требования для организации порядка, в основном распространяются к метаинформации о проекте. Вот этот перечень требований — уже то, что привнесли именно мы. Сюда можно отнести требования к красивому, читаемому, информативному оформлению проекта в Gitlab. Это обязательное наличие файлов README.md, CONTRIBUTING.md, CHANGELOG.md, в корне проекта, и не просто пустых файлов, а с релевантным содержимым. Без этих требований можно было бы обойтись, если бы все оформляли качественно свои репозитории, но наша практика показала, что проекты выглядят безлико, в них отсутствует описание, предназначение, ответственные, его имя — это три буквы. Это точно действующий проект или какой-то мусор, который забыли удалить? Чтобы реже задаваться этим вопросом, мы ввели эти требования.
Многие из требований становятся общими для всех технологий, например, того, что касается оформления проектов. Это удобно и для нас и для пользователей, через какое-то время команды «на автомате» независимо от технологии оформляют проекты как положено.
Таким образом для нового flow мы определяем:
-
Пакетный менеджер, который будет использоваться для сборки.
-
Базовый образ, его операционную систему, компонентный состав, версию.
-
Layout проекта, принятый в комьюнити: где что должно быть расположено и как называться, например, каталог src в корне, pom.xml в корне.
-
Dockerfile, а именно набор стейджей для flow, реализуем их опираясь на общепринятые практики в комьюнити.
-
CI-скрипты, в которых имплементируем логику конвейера, она опирается на git-workflow, принятый в платформе и описанный в предыдущей статье, и на стейджи в Dockerfile.
Определив эти вещи, мы фиксируем их в документации для пользователей. То есть по сути требования к flow — это некий инструктаж для пользователя, как нужно оформить свой проект, чтобы у тебя все собралось на конвейера без проблем. Вот пример базовых требований для JVM-flow:

Тем не менее на этапе фиксации требований мы понимали, что никто не будет сначала читать документацию, а потом исправно идти делать то, что там написано. Все мы идем читать инструкцию, когда уже что-то сломалось. И надо было что-то с этим делать, а именно…
Статическая верификация
При реализации flow мы задумались о том, как обеспечить выполнение поставленных требований, ведь все просто на них забьют или забудут. А потом уже никто переделывать не будет. И мы придумали сделать верификатор исполнения требований — project-verifier
.

Верификатор в нашем представлении должен был решить следующие проблемы:
-
Обеспечить соблюдение требований.
-
Снизить нагрузку на саппорт, показав пользователю внятную ошибку или недоработку в его проекте.
-
Направить пользователя по адресу в случае возникновения проблем: в документацию, в проблемное место в проекте.
-
Оповещать пользователей о предстоящем прекращении поддержки каких-либо подходов, версий, компонент, используемых в их проекте.
Мы не стали искать готовых решений, потому что реализовать подобный инструмент не показалось проблемой. Мы решили его написать на Golang в формате CLI-утилиты, которая бы встраивалась первым шагом во все наши конвейеры. Архитектуру верификатора мы реализовали схожую с фреймворками unit-тестирования, где каждый тест (в нашем случае верификация) проверял исполнение одного требования. Такие проверки можно было бы группировать по пакетам для определенного flow. Плюсом к этому набросали примитивный движок, который исполнял эти проверки просто друг за другом. В результате выполнения показываем пользователю отчет о выполненных проверках и их статус.
Пример проверки, которая проверяет наличие файла pom.xml
в корне проекта для JVM-флоу.
func (v *MavenManifest) CheckExistsPom(conf *pkg.Config) verifier.Check { return verifier.Check{ Id: "001", Name: "Наличие файла манифеста Maven " + manifestFilename, Blocking: true, Predicate: func() verifier.CheckResult { return verifier.MakeCheckResult( utils.CheckFileExists(conf.ProjectDir, manifestFilename), ) }, } }
Каждая из проверок раскрашивается тегом, например, common
, java
, maven
, таким образом определяется принадлежность проверки к группе. Далее в CI-конфигурации при вызове верификатора ему в качестве параметра передается список тегов, согласно которым нужно выполнить все проверки, которые им соответствуют.
Пример вызова верификатора для JVM-flow, где необходимые теги передаются в переменной PROJECT_TYPE
:
verify_static: stage: verify_static image: $IMAGE_NAME__VERIFIER_CLI:$IMAGE_TAG__VERIFIER_CLI script: - /project-verifier --log-level debug . variables: PROJECT_TYPE: common,jvm,maven,psvc,service
В результате выполнения пользователь в логах джобы verify_static
получает следующий лог (представлена часть проверок pom.xml):
[JV01000] + Успешно пройдена: 'Проверка корневого манифеста пакетного менеджера Maven (pom.xml)' [JV01001] + Успешно пройдена проверка 'Наличие файла манифеста Maven pom.xml' [JV01002] + Успешно пройдена проверка 'Наличие содержимого в файле манифеста Maven pom.xml' [JV01003] + Успешно пройдена проверка 'Зачитывание содержимого в POM-формате файла манифеста Maven pom.xml' [JV01004] + Успешно пройдена проверка 'Соответствие кода артефакта (artifactId) в файле pom.xml и кода проекта в Gitlab(kotlin-spring-boot-otlp)' [JV01005] + Успешно пройдена проверка 'Соответствие группы артефакта (поле groupId) в файле pom.xml и пути к проекту в Gitlab (examples/jvm)' [JV01006] + Успешно пройдена проверка 'Соответствие версии артефакта одному из значений (${artifactVersion}, ${revision}) в файле pom.xml' [JV01007] + Успешно пройдена проверка 'Допустимая версия Java в файле pom.xml' [JV01008] + Успешно пройдена проверка 'Допустимая версия Kotlin в файле pom.xml' [JV01009] + Успешно пройдена проверка 'Допустимый тип артефакта в файле pom.xml' [JV01010] + Успешно пройдена проверка 'Наличие наименования проекта в файле pom.xml' [JV01011] + Успешно пройдена проверка 'Наличие описания проекта в файле pom.xml' [JV01012] + Успешно пройдена проверка 'Наличие секции свойств проекта в файле pom.xml' [JV01013] + Успешно пройдена проверка 'Значение свойства - версия артефакта в файле pom.xml' [JV01014] + Успешно пройдена проверка 'Наименование исполняемого jar-файла проекта в файле pom.xml'
У верификатора есть следующие результаты выполнения:
-
Ошибок и предупреждений нет: все хорошо, джоба зеленая, конвейер выполняется дальше.
-
Выявлены ошибки: конвейер завершается с ошибкой.
-
Выявлены предупреждения: джоба «желтеет», но конвейер идет дальше.
Режим предупреждений мы добавили в процессе развития платформы, когда мы вынуждены обновить или заменить какой-то компонент в CI централизованно. Мы не делаем это сиюминутно, на этот счет у нас есть процесс, при котором мы еще поддерживаем обновляемый компонент какой-то время. На время переходного периода мы в том числе уведомляем пользователей в конвейере с помощью верификатора: если в проекте используется нечто, что мы выпилим из поддержки, мы завершаем джобу с предупреждением.
С точки зрения CI-конвейера завершить джобу с предупреждением можно с помощью конструкции allow_failure
, в которой можно указать exit code, отличный от нуля, который не будет завершать конвейер. Пример:
verify_static: stage: verify_static image: $IMAGE_NAME__VERIFIER_CLI:$IMAGE_TAG__VERIFIER_CLI script: - /project-verifier --log-level debug . allow_failure: # Допустимый exit-code, с которым выходит project-verifier в случае непрохождения рекомендательной проверки exit_codes: - 10
Оглядываясь назад, хотелось бы сказать, что верификатор — это было гениальное изобретение, это очень простой и быстрый в реализации механизм, который закрыл нам просто очень много головной боли и недопустил хаоса и беспорядка на платформе. А простота его реализации позволяет очень быстро писать новые проверки и использовать этот проект для онбординга новых сотрудников в нашу команду, на котором они начинают делать базовые задачки, погружаясь в стек платформы.
В следующих частях мы расскажем более подробно о том какие еще есть базовые шаги в наших flow и какие технологии для них используются.
ссылка на оригинал статьи https://habr.com/ru/articles/885270/
Добавить комментарий