Стадия для сборки артефакта на сервере

Следующие три стадии уже непосредственно используют подключенный к проекту 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". В процессе занятия пройдёмся по метрикам и конечным точкам для работы с приложением. Ощутим мощь актуатора и даже напишем свой "индикатор здоровья". Зарегистрироваться.

Комментарии (0)