Стадия для сборки артефакта на сервере
Следующие три стадии уже непосредственно используют подключенный к проекту shell gitlab-runner, поэтому в них блок tags уже носит не декоративный характер, а действительно используется gitlab для выбора, на каком именно runner запускать текущую стадию.
Прежде чем продолжать, следует привести файл build.gradle приложения. Так будет удобнее делать пояснения к отдельным стадиям.
plugins {
id 'org.springframework.boot' version '2.7.4'
id 'io.spring.dependency-management' version '1.0.12.RELEASE'
id 'java'
id "com.dorongold.task-tree" version '2.1.0'
id 'nebula.integtest' version '9.6.2'
id 'com.google.cloud.tools.jib' version '3.3.0'
id 'org.sonarqube' version '3.4.0.2513'
id 'jacoco'
}
jacocoTestReport {
reports {
xml.enabled true
}
}
test.finalizedBy jacocoTestReport
tasks.named('sonarqube').configure {
dependsOn test
}
sonarqube {
properties {
property "sonar.projectKey", "your project key"
property "sonar.qualitygate.wait", true
property 'sonar.organization', 'your organization'
property 'sonar.login', 'your sonar login'
}
}
group = 'com.yamangulov'
version = '0.0.1'
sourceCompatibility = '11'
repositories {
mavenCentral()
}
ext {
set('testcontainersVersion', "1.17.3")
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'io.springfox:springfox-boot-starter:3.0.0'
implementation 'org.liquibase:liquibase-core:4.14.0'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
runtimeOnly 'org.postgresql:postgresql'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.testcontainers:junit-jupiter'
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
integTestImplementation 'org.springframework.cloud:spring-cloud-contract-wiremock:2.1.3.RELEASE'
integTestImplementation 'org.testcontainers:postgresql'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.4'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.13.4'
}
dependencyManagement {
imports {
mavenBom "org.testcontainers:testcontainers-bom:${testcontainersVersion}"
}
}
tasks.named('test') {
useJUnitPlatform()
}
tasks.named('integrationTest') {
useJUnitPlatform()
}
tasks.named('bootJar') {
launchScript()
}
Конкретно на данной стадии нас интересуют строка id 'com.google.cloud.tools.jib' version '3.3.0'
Это ничто иное, как подключение специального плагина, который позволяет создать из проекта docker-образ вашего приложения и поместить его в локальное хранилище. После установки плагина у вас появятся новые таски в gradle, одна из которых используется для этой операции:
Разумеется, вы можете воспользоваться этой таской для того, чтобы локально вручную запускать сборку образа в локальное хранилище, причем при каждом запуске будет собираться новая версия образа, что можно увидеть по изменению хэша образа с одним и тем же именем, под которым вы его собираете. Но для нас это будет разрыв пайплайна для одной ручной операции, в то время как вполне можно запустить любую таску gradle прямо из пайплайна, оформив это, как отдельную стадию. Вот пример того, как это можно сделать (добавим новую стадию к предыдущей):
stages:
- feature
- feature-artefact
feature development:
tags:
- feature
stage: feature
image: gradle:7.5.0-jdk11
script:
- cd rest-service
- ./gradlew assemble
feature docker image:
tags:
- feature
stage: feature-artefact
script:
- cd rest-service
- ./gradlew jibDockerBuild
Обратите внимание, что во второй стадии отсутствует секция image - в этом случае gitlab не будет создавать среду исполнения стадии "у себя", а обратится к зарегистрированным для этого проекта runner и выберет из них именно тот, который имеет тэг feature, и попытается запустить стадию на нем. Если это shell runner, то стадия будет выполняться в терминале сервера, на котором runner зарегистрирован, под специальной учетной записью gitlab-runner, автоматически создаваемой при регистрации. Здесь следует отметить, что нужно внимательно следить за тем, чтобы не возникало конфликтов runner, зарегистрированных для проекта - а именно, советую делать так, чтобы тэги не пересекались, то есть каждый уникальный тэг имелся только у одного runner (что не запрещает одному runner иметь несколько разных тегов, если это зачем-то нужно), если пересекающихся по тэгам раннеров больше одного, насколько мне известно, gitlab пайплайн вполне может запуститься, но выберет тэг для исполнения стадии случайным образом. Поэтому лучше не экспериментируйте, и следите за этим моментом строго.
Стадия развертывания стенда для тестирования фичи и очистка кубера после тестирования
Для настройки следующей стадии нам потребуется helm chart для того, чтобы было удобно развертывать и, при необходимости, уничтожать тестовые стенды в kubernetes всякий раз, когда нам это нужно. Хотя это и уводит немного в сторону, сделаем краткое отступление о том, как я это делал. Очень часто java-разработчики (и не только) в процессе работы создают для удобства файл docker-compose.yml с набором взаимосвязанных сервисов, необходимых для отладки и тестирования приложения. Например, нужна база данных, нужно поднять миграции в приложении для нее, нужно провести после этого модульное тестирование и так далее. Затем может потребоваться перенести всю эту конструкцию в kubernetes. Кто-то имеет большой опыт и пишет деплойменты "на лету". Для меня же удобнее оказалось воспользоваться helm, а деплойменты сконвертировать из docker-compose.yml файла при помощи удобного средства kompose https://kompose.io/getting-started/. В частности, я воспользовался командой:
kompose -f docker-compose.yml convert -c
После создания чарта helm можно воспользоваться им для развертывания стендов - хоть тестировочных, хоть продуктовых, как вам угодно. И разумеется, поскольку это все выполняется консольными командами, почему бы не использовать их в очередной стадии, что я и сделал. Вот как это выглядит:
stages:
- feature
- feature-artefact
- feature-install-stand
feature development:
tags:
- feature
stage: feature
image: gradle:7.5.0-jdk11
script:
- cd rest-service
- ./gradlew assemble
feature docker image:
tags:
- feature
stage: feature-artefact
script:
- cd rest-service
- ./gradlew jibDockerBuild
feature install stand:
tags:
- feature
stage: feature-install-stand
script:
- cd rest-service/.chart/
- helm install docker-compose-feature docker-compose --namespace=yamangulov-feature --values=docker-compose/values-yamangulov-feature.yaml
Чтобы было понятнее содержание скрипта, приведу скриншот структуры моего helm чарта:
Не углубляясь в детали, скажу, что в нем используются шаблоны с подстановками, которые позволяют создавать отдельные конфигурации в разных namespace, если задать соответствующие входные параметры для команды helm. Например, на этой стадии мы создаем отдельный набор подов для моего приложения командой:
helm install docker-compose-feature docker-compose --namespace=yamangulov-feature --values=docker-compose/values-yamangulov-feature.yaml
То есть в данном случае мы создаем чарт с именем docker-compose-feature из шаблона чарта docker-compose в пространстве имен yamangulov-feature, подгружая при этом дополнительный файл values-yamangulov-feature.yaml, содержащий дополнительные значения переменных для шаблона именно в этом namespace. Поскольку содержание шаблона чарта и чартов может быть интересным и полезным для многих, а также для изложения некоторых планируемых стадий пайплайна, приведу его содержание полностью.
Теперь, если мы запустим пайплайн (для этого достаточно внести какое-то изменение в проект и запушить его в gitlab), мы увидим, как выполняются все стадии одна за другой. Выглядит это примерно так:
зеленым цветом отмечаются успешно пройденные стадии пайплайна, красным - ошибки, после чего пайплайн останавливается, коричневый цвет с восклицательным знаком - особые стадии с некритическими ошибками, в которых пайплайн не останавливается, а переходит в выполнению следующей стадии несмотря на возникновение ошибок (это нам пригодится чуть позже)
Теперь, если мы проверим kubernetes, мы увидим наш развернутый в нужном namespace чарт:
Ну вот, казалось бы, все нормально, стенд для тестирования фичи развернут, и его можно предоставить тестировщикам для проверки развернутого приложения. И действительно это так, но только на первый взгляд все в порядке. А теперь давайте представим себе, что тестирование показало какие-то недостатки и проект возвращен на доработку. Разработчик исправил недостатки и снова запушил проект в gitlab. Пайплайн запустился на выполнение снова, и тут вы увидите, что он вываливается с ошибкой! В логе ошибки кубер сообщает вам что такой чарт уже существует и отказывается создавать его заново. Ага, вы меняете команду следующим образом:
helm upgrade docker-compose-feature docker-compose --install --namespace=yamangulov-feature --values=docker-compose/values-yamangulov-feature.yaml
то есть вы указываете helm обновить чарт, если он существует и создать заново, если он не существует. Должно работать, но в данном случае не работает. Вы снова получите ошибку, и содержание ее расскажет вам, что при выполнении команды helm upgrade не удается пересоздать уже созданные ingress хосты с определенными именами в предыдущем запуске чарта. Существует вот такая особенность helm и мы ее обнаружили. Чтобы решить эту проблему, самый простой и очевидный способ - вставить перед стадией развертывания стадию удаления нашего чарта при условии, если чарт существует и был ранее установлен - вот тогда гарантированно удаляются созданные ingress хосты вместе с остальными элементами чарта, то есть чарт удаляется абсолютно полностью начисто, что нам и нужно для чистоты свежего развертывания, чтобы не осталось никаких следов от предыдущего. Вот так теперь будет выглядеть наш исправленный пайплайн:
stages:
- feature
- feature-artefact
- feature-clean-stand
- feature-install-stand
feature development:
tags:
- feature
stage: feature
image: gradle:7.5.0-jdk11
script:
- cd rest-service
- ./gradlew assemble
feature docker image:
tags:
- feature
stage: feature-artefact
script:
- cd rest-service
- ./gradlew jibDockerBuild
feature clean stand:
tags:
- feature
stage: feature-clean-stand
allow_failure: true
script:
- cd rest-service/.chart
- helm uninstall docker-compose-feature --namespace=yamangulov-feature
feature install stand:
tags:
- feature
stage: feature-install-stand
script:
- cd rest-service/.chart/
- helm install docker-compose-feature docker-compose --namespace=yamangulov-feature --values=docker-compose/values-yamangulov-feature.yaml
Здесь команда helm uninstall docker-compose-feature --namespace=yamangulov-feature
как раз и служит для полного предварительного удаления чарта перед его новой установкой, если он уже существовал. В стадии feature-clean-stand есть еще одна особенность - если стенд НЕ существовал на момент ее выполнения, то уже УДАЛЕНИЕ стенда вываливается с ошибкой, в результате чего обычная стадия остановит весь пайплайн, что нам совсем не нужно. Поэтому в стадию добавлена опция
allow_failure: true
которая как раз и заставит стадию работать так, как нам нужно. Если стенд уже был удален ранее (например, DevOps "пошалил" и удалил его вручную, либо же пайплайн запускается в самой первой итерации при разработке фичи, когда стенд вообще еще ни разу не создавался), при ошибке мы увидим в пайплайне как раз тот самый коричневый цвет с восклицательным знаком, и пайплайн спокойно перейдет к следующей стадии без остановки.
В продолжении статьи будет рассказано о том, как я делал стадии для выполнения тестов в sonarqube, smoke и регрессионного тестирования
Также рекомендую к посещению два бесплатных урока от OTUS, которые пройдут уже совсем скоро:
2 ноября. "Scope бинов в Spring". Расскажем, какие бывают Scope и для чего они могут понадобится. На примере проследим как работает Scope "Request", и, если успеем, разберем создание своего собственного Scope. Зарегистрироваться.
9 ноября. "Spring Actuator". В процессе занятия пройдёмся по метрикам и конечным точкам для работы с приложением. Ощутим мощь актуатора и даже напишем свой "индикатор здоровья". Зарегистрироваться.