Русский
Русский
English
Статистика
Реклама

Настройка GitLab CI CD для Java приложения

Из-за прекращения поддержи Bitbucket Setver пришлось переехать на GitLab.


В Bitbucket Server не было встроенного CI/CD, поэтому использовали Teamcity. Из-за проблемы интеграции Teamcity с GitLab, мы попробовали GitLab Pipline. И остались довольны.


Disclamer: У меня не так много опыта в CI/CD, так что статья скорее для новичков. Буду рад услышать конструктивную критику с предложениями по оптимизации скрипта сборки :)


Коротко о Gitlab Pipline


Если говорить простыми словами: сборка делится на задачи. Какие-то задачи выполняются последовательно и передают результаты следующим задачам, какие-то задачи распаралеливаются: например можно одновременно деплоить на сервер и в nexus.


Раннеры и задачи


Не обязательно один раннер отвечает за все задачи в сборке. Если на проекте два раннера, то первую задачу может взять один ранер, после ее выполнения вторую задачу возьмет другой раннер.


Раннеры бывают разных типов. Мы рассмотрим executor docker. Для каждой задачи создается новый чистый контейнер. Но между контейнерами можно передавать промежуточные результаты это называется кэширование.


Кэширование и его особенности


Механизм кэширования разрабатывал какой-то одаренный человек. Поэтому сразу разобраться, как оно работает, будет не просто. В отдельной статье можно прочитать о кэшировании подробнее.


Каждый раннер хранит кэш в папке /cache. Для каждого проекта в этой папке создается еще папка. Сам кэш хранится в виде zip архива.


Из-за наличия у каждого раннера своей папки для кэша, возникает проблема. Если один раннер кэшировал папку в первой задаче, а второй раннер взялся за выполнение второй задачи, то у него не окажется кэша из первой задачи. Чуть далее рассмотрим как это исправить.


Можно определить только один набор папок для кэша. Также только для всего набора можно выбрать политику кэширования. Например если вы уверены, что задача не изменит кэш, то можно его не загружать. Но нельзя запретить загружать например две папки из четырех.


Нет возможности установить глобальный кэш и добавить к нему дополнительные папки для конкретных задач.


Так же стоит знать, что кэш не удаляется после окончания сборки. При следующей такой же сборке старый кэш будет доступен.


Эти особенности усложняют создание инструкций для CI CD.


Артефакты


Помимо кэша между сборками можно передавать артефакты.


Артефакт это файлы, которые считаются законченным продуктом сборки. Например .jar файлы приложения.


В отличие от кэша, артефакт передается между раннерами. Из-за этого многие злоупотребляют использованием артефактов там, где стоит использовать кэш.


Следуйте следующим правилам:


  1. Если текущее задание может выполняться самостоятельно, но в присутствии контента работа будет идти быстрее, используйте кэш.
  2. Если текущее задание зависит от результатов предыдущего, то есть не может выполняться само по себе, используйте артефакты и зависимости.

Установка Gitlab Runner


Перед установкой ранера создадим папку с конфигурацией, чтобы не приходилось для изменений лезть в контейнер.


mkdir ~/runner_name

Само создание ранера выполняется в одну команду. Можно создать сколько угодно ранеров.


docker run -d --name gitlab-runner-name \  --restart always \  -v /var/run/docker.sock:/var/run/docker.sock \  -v /home/user/runner_name:/etc/gitlab-runner:z \  gitlab/gitlab-runner:latest

Мало создать раннер, теперь его нужно зарегистрировать в GitLab. Зарегистрировать можно на уровне всего GitLab, тогда сборки будут выполняться для любого проекта; на уровне группы выполнятся только для группы, и на уровне проекта.


Заходим в контейнер.


sudo docker exec -ti gitlab-runner-name bash

Внутри контейнера выполним команду регистрации. Регистрация происходит в интерактивном режиме.


gitlab-runner register

Отвечаем на вопросы:


Runtime platform                                    arch=amd64 os=linux pid=27 revision=888ff53t version=13.8.0Running in system-mode.                            Enter the GitLab instance URL (for example, https://gitlab.com/):http://git.company.name/Enter the registration token:vuQ6bcjuEPqc8dVRRhgYEnter a description for the runner:[c6558hyonbri]: runner_twoEnter tags for the runner (comma-separated):Registering runner... succeeded                     runner=YJt3v3QgEnter an executor: parallels, shell, virtualbox, docker+machine, kubernetes, custom, docker, docker-ssh+machine, docker-ssh, ssh:dockerEnter the default Docker image (for example, ruby:2.6):maven:3.3.9-jdk-8Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded! 

Тут все просто:


  1. Адрес вашего gitlab.
  2. Токен авторизации. Посмотреть его можно в настройках гурппы/проекта в разделе CI/CD Runners.
  3. Название раннера.
  4. Теги ранера, можно пропустить нажав Enter.
  5. Исполнитель сборки. Вводим docker.
  6. Образ, который будет использоваться по умолчанию, если не установлен другой.

После этого в настройках проекта можно посмотреть доступные раннеры.


Добавленный ранер GitLab


После регистрации, в папке /home/user/runner_name появится файл с настройками конфигурации config.toml. Нам нужно добавить docker volume для кэширования промежуточных результатов.


volumes = ["gitlab-runner-builds:/builds", "gitlab-runner-cache:/cache"]

Проблема кэширования.
В начале статьи я рассказал о проблеме кеширования. Ее можно решить с помощью монтирования одного volume к разным раннерам. То есть во втором своем раннере так же укажите volumes = ["gitlab-runner-cache:/cache"]. Таким образом разные раннеры будут иметь единый кэш.


В итоге файл конфигурации выглядит так:


concurrent = 1check_interval = 0[session_server]  session_timeout = 1800[[runners]]  name = "runner_name"  url = "gitlab_url"  token = "token_value"  executor = "docker"  [runners.custom_build_dir]  [runners.cache]    [runners.cache.s3]    [runners.cache.gcs]    [runners.cache.azure]  [runners.docker]    tls_verify = false    image = "maven:3.3.9-jdk-8"    privileged = false    disable_entrypoint_overwrite = false    oom_kill_disable = false    disable_cache = false    volumes = ["gitlab-runner-builds:/builds", "gitlab-runner-cache:/cache"]    shm_size = 0

После изменения перезапускаем раннер.


docker restart gitlab-runner-name

Что хотим получить от CI CD?


У нас на проекте было 3 контура:


  • dev-сервер, на него все попадает сразу после MR;
  • пре-прод-сервер, на него все попадает перед попаданием на прод, там проходит полное регресс тестирование;
  • прод-сервер, собственно сама прод среда.

Что нам было необходимо от нашего CI/CD:


  • Запуск unit-тестов для всех MergeRequest
  • При мерже в dev ветку повторный запуск тестов и автоматический деплой на dev-сервер.
  • Автоматическая сборка, тестирвоание и деплой веток формата release/* на пре-прод-сервер.
  • При мерже в master ничего не происходит. Релиз собирается при обнаружении тега формата release-*. Одновременно с деплоем на прод-сервер будет происходить загрузка в корпоративный nexus.
  • Бонусом настроим уведомления о статусе деплоя в Telegram.

Настройка GitLab CI для Maven


Что делать если у вас Gradle? Загляните в оригинал статьи, там я рассказываю про настройку и для Gradle. Она не сильно отличается.


Создайте в корне проекта файл .gitlab-ci.yml.


Настройку буду объяснять блоками, в конце прикреплю единый файл.


Вы можете указать нужный образ, вместо дефолтного образа у раннера.


image: maven:latest

Устанавливаем место хранения папки .m2 через переменные среды variables. Это понадобится, когда будем настраивать кэширование.


// ... ... ... ... ...variables:  MAVEN_OPTS: "-Dmaven.repo.local=./.m2/repository"// ... ... ... ... ...

Далее указываются этапы сборки. Они позволяют группировать задачи для одновременного выполнения.


// ... ... ... ... ...stages:  - build  - test  - package  - deploy  - notify// ... ... ... ... ...

  • build стадия сборки.
  • test стадия тестирования.
  • package стадия упаковки в jar.
  • deploy стадия деплоя на сервер.
  • notify стадия уведомления о провале.

Указываем непосредственно задачи для каждой стадии.


Сборка Build


// ... ... ... ... ...build:  stage: build  only:    - dev    - merge_requests    - /^release\/.*$/  except:    - tags  script:    - 'mvn --settings $MAVEN_SETTINGS compile'  cache:    paths:      - ./target      - ./.m2// ... ... ... ... ...

Раздел script выполняет linux команды.


Переменная GitLab CI/CD $MAVEN_SETTINGS необходима для передачи файла settings.xml, если вы используете нестандартные настройки, например корпоративные репозитории. Переменная создается в настройках CI/CD для группы/проекта. Тип переменной File.


Раздел only указывает для каких веток и тегов выполнять задачу. Чтобы не собирать каждую запушенную ветку устанавливаем: dev, merge_requests и ветки формата /release/*.


Раздел only не разделяет ветка это или тег. Поэтому мы указываем параметр except, который исключает теги. Из-за этого поведения за сборку на прод отвечают отдельные задачи. В противном случае, если бы кто-то создал тег формата release/*, то он бы запустил сборку.


Для защиты от случайного деплоя в прод джуном, рекомендую установить защиту на ветки dev, master, /release/*, а так же на теги release-*. Делается это в настройках проекта GitLab.


Раздел cache отвечает за кэширование. Чтобы каждый раз не выкачивать зависимости добавляем в кэш папку ./target и ./.m2.


Запуск unit тестов test


Создаем задачу для запука тестирования.


// ... ... ... ... ...test:  stage: test  only:    - dev    - merge_requests    - /^release\/.*$/  except:    - tags  script:    - 'mvn --settings $MAVEN_SETTINGS test'  cache:    paths:      - ./target      - ./.m2// ... ... ... ... ...

Ничем не отличается от стадии сборки, только команда мавена test. Кэш необходимо указать и в этой задаче, чтобы получить его и отправить дальше.


Упаковка package


Следом добавляем задачу упаковки с выключенными тестами. Она уже выполняется только для веток dev и release/*. Упаковывать Merge Request смысла нет.


// ... ... ... ... ...package:  stage: package  only:    - dev    - /^release\/.*$/  except:    - tags  script:    - 'mvn --settings $MAVEN_SETTINGS package -Dmaven.test.skip=true'  artifacts:    paths:      - target/*.jar  cache:    policy: pull    paths:      - ./target      - ./.m2// ... ... ... ... ...

Обратите внимание на policy: pull и artifacts. В следующих задачах не нужны исходники и зависимости, так что policy: pull отключает кэширование. Для сохранения результатов сборки используем artifacts, который сохраняет .jar файлы и передает их следующим задачам.


Деплой deploy


Теперь осталось задеплоить артефакт на dev-сервер с помощью ssh и scp.


// ... ... ... ... ...deploy_dev_server:  stage: deploy  only:    - dev  except:    - tags  before_script:    - which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )    - eval $(ssh-agent -s)    - echo "$SSH_PRIVATE_KEY" | ssh-add -    - mkdir -p ~/.ssh    - chmod 700 ~/.ssh    - ssh-keyscan $DEV_HOST >> ~/.ssh/known_hosts    - chmod 644 ~/.ssh/known_hosts  script:    - ssh $DEV_USER@$DEV_HOST "[ ! -f $DEV_APP_PATH/app_name.jar ] || mv $DEV_APP_PATH/app_name.jar $DEV_APP_PATH/app_name-build-$CI_PIPELINE_ID.jar"    - scp target/app_name.jar $DEV_USER@$DEV_HOST:$DEV_APP_PATH/    - ssh $DEV_USER@$DEV_HOST "sudo systemctl stop app_name_service && sudo systemctl start app_name_service"    - sh ci-notify.sh // ... ... ... ... ...

Не забудьте создать переменные в GitLab CI CD:


  • $DEV_USER пользователь системы, от чьего имени будет происходить деплой.
  • $DEV_HOST ip адрес сервера.
  • $DEV_APP_PATH путь до папки приложения.
  • $SSH_PRIVATE_KEY приватный ключ.

Раздел before_script отвечает за выполнение настроек перед основным скриптом. Мы проверяем наличие ssh-agent, устанавливаем его при отсутствии. После чего добавляем приватный ключ, устанавливаем правильные права на папки.


В разделе script происходит деплой на сервер:


  1. Проверяем наличие старого jar и переименовываем его. $CI_PIPELINE_ID это глобальный номер сборки Pipeline.
  2. Копируем новый jar на сервер.
  3. Останавливаем и запускаем службу, отвечающую за приложение.
  4. Отправляем уведомление об успехе в телеграм. Об этом ниже.

Как создавать службы linux для Spring Boot приложения, я написал в отдельной статье.


На пре-прод делаем по аналогии, только меняем переменные на $PRE_PROD_*.


// ... ... ... ... ...deploy_pre_prod:  stage: deploy  only:    - /^release\/.*$/  except:    - tags  before_script:    - which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )    - eval $(ssh-agent -s)    - echo "$SSH_PRIVATE_KEY" | ssh-add -    - mkdir -p ~/.ssh    - chmod 700 ~/.ssh    - ssh-keyscan $PRE_PROD_HOST >> ~/.ssh/known_hosts    - chmod 644 ~/.ssh/known_hosts  script:    - ssh $PRE_PROD_USER@$PRE_PROD_HOST "[ ! -f $PRE_PROD_APP_PATH/app_name.jar ] || mv $PRE_PROD_APP_PATH/app_name.jar $PRE_PROD_APP_PATH/app_name-build-$CI_PIPELINE_ID.jar"    - scp target/app_name.jar $DEV_USER@$PRE_PROD_HOST:$PRE_PROD_APP_PATH/    - ssh $PRE_PROD_USER@$PRE_PROD_HOST "sudo systemctl stop app_name_service && sudo systemctl start app_name_service"    - sh ci-notify.sh // ... ... ... ... ...

Настройка деплоя на прод


Для прод-деплоя можно объединить сборку, тестирование и упаковку в одну задачу.


// ... ... ... ... ...package_prod:  stage: package  only:    - /^release-.*$/  except:    - branches  script:    - 'mvn --settings $MAVEN_SETTINGS package'  artifacts:    paths:      - target/*.jar// ... ... ... ... ...

Мы защищаемся от срабатывания на ветки формата release-*, нам нужно срабатывание только по тегу.


Деплой аналогичен пре-проду, изменяется только only и переменные.


Дополнительно появляется задача с отправкой артефакта в корпоративный nexus. Деплой на сервер и в нексус работает параллельно.


// ... ... ... ... ...deploy_prod:  stage: deploy  only:    - /^release-.*$/  except:    - branches  ...deploy_nexus_server:  stage: deploy  only:    - /^release-.*$/  except:    - branches  script:    - 'mvn --settings $MAVEN_SETTINGS deploy -Dmaven.test.skip=true'// ... ... ... ... ...

Итоговый .gitlab-ci.yml:


image: maven:latestvariables:  MAVEN_OPTS: "-Dmaven.repo.local=./.m2/repository"stages:  - build  - test  - package  - deploy  - notifybuild:  stage: build  only:    - dev    - merge_requests    - /^release\/.*$/  except:    - tags  script:    - 'mvn --settings $MAVEN_SETTINGS compile'  cache:    paths:      - ./target      - ./.m2test:  stage: test  only:    - dev    - merge_requests    - /^release\/.*$/  except:    - tags  script:    - 'mvn --settings $MAVEN_SETTINGS test'  cache:    paths:      - ./target      - ./.m2package:  stage: package  only:    - dev    - /^release\/.*$/  except:    - tags  script:    - 'mvn --settings $MAVEN_SETTINGS package -Dmaven.test.skip=true'  artifacts:    paths:      - target/*.jar  cache:    policy: pull    paths:      - ./target      - ./.m2deploy_dev_server:  stage: deploy  only:    - dev  except:    - tags  before_script:    - which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )    - eval $(ssh-agent -s)    - echo "$SSH_PRIVATE_KEY" | ssh-add -    - mkdir -p ~/.ssh    - chmod 700 ~/.ssh    - ssh-keyscan $DEV_HOST >> ~/.ssh/known_hosts    - chmod 644 ~/.ssh/known_hosts  script:    - ssh $DEV_USER@$DEV_HOST "[ ! -f $DEV_APP_PATH/app_name.jar ] || mv $DEV_APP_PATH/app_name.jar $DEV_APP_PATH/app_name-build-$CI_PIPELINE_ID.jar"    - scp target/app_name.jar $DEV_USER@$DEV_HOST:$DEV_APP_PATH/    - ssh $DEV_USER@$DEV_HOST "sudo systemctl stop app_name_service && sudo systemctl start app_name_service"deploy_pre_prod:  stage: deploy  only:    - /^release\/.*$/  except:    - tags  before_script:    - which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )    - eval $(ssh-agent -s)    - echo "$SSH_PRIVATE_KEY" | ssh-add -    - mkdir -p ~/.ssh    - chmod 700 ~/.ssh    - ssh-keyscan $PRE_PROD_HOST >> ~/.ssh/known_hosts    - chmod 644 ~/.ssh/known_hosts  script:    - ssh $PRE_PROD_USER@$PRE_PROD_HOST "[ ! -f $PRE_PROD_APP_PATH/app_name.jar ] || mv $PRE_PROD_APP_PATH/app_name.jar $PRE_PROD_APP_PATH/app_name-build-$CI_PIPELINE_ID.jar"    - scp target/app_name.jar $DEV_USER@$DEV_HOST:$DEV_APP_PATH/    - ssh $PRE_PROD_USER@$PRE_PROD_HOST "sudo systemctl stop app_name_service && sudo systemctl start app_name_service"package_prod:  stage: package  only:    - /^release-.*$/  except:    - branches  script:    - 'mvn --settings $MAVEN_SETTINGS package'  artifacts:    paths:      - target/*.jardeploy_prod:  stage: deploy  only:    - /^release-.*$/  except:    - branches  before_script:    - which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )    - eval $(ssh-agent -s)    - echo "$SSH_PRIVATE_KEY" | ssh-add -    - mkdir -p ~/.ssh    - chmod 700 ~/.ssh    - ssh-keyscan $PROD_HOST >> ~/.ssh/known_hosts    - chmod 644 ~/.ssh/known_hosts  script:    - ssh $PROD_USER@$PROD_HOST "[ ! -f $PROD_APP_PATH/app_name.jar ] || mv $PROD_APP_PATH/app_name.jar $PROD_APP_PATH/app_name-build-$CI_PIPELINE_ID.jar"    - scp target/app_name.jar $PROD_USER@$PROD_HOST:$PROD_APP_PATH/    - ssh $PROD_USER@$PROD_HOST "sudo systemctl stop app_name_service && sudo systemctl start app_name_service"

Бонус: уведомления о деплое в Telegram


Полезная фишка для уведомления менеджеров. После сборки в телеграм отправляется статус сборки, название проекта и ветка сборки.


В задачах деплоя последней командой пропишите:


// ... ... ... ... ...script:  - ...  - sh ci-notify.sh // ... ... ... ... ...

Сам файл необходимо добавить в корень проекта, рядом с файлом .gitlab-ci.yml.


Содержимое файла:


#!/bin/bashTIME="10"URL="http://personeltest.ru/aways/api.telegram.org/bot$TELEGRAM_BOT_TOKEN/sendMessage"TEXT="Deploy status: $1%0A-- -- -- -- --%0ABranch:+$CI_COMMIT_REF_SLUG%0AProject:+$CI_PROJECT_TITLE"curl -s --max-time $TIME -d "chat_id=$TELEGRAM_CHAT_ID&disable_web_page_preview=1&text=$TEXT" $URL >/dev/null

Скрипт отправляет запрос к API Telegram, через curl. Параметром скрипта передается emoji статуса билда.


Не забудьте добавить новые параметры в CI/CD:


  • $TELEGRAM_BOT_TOKEN токен бота в телеграмм. Получить его можно при создании своего бота.
  • $TELEGRAM_CHAT_ID идентификатор чата или беседы, в которую отправляется сообщение. Узнать идентификатор можно с помощью специального бота.

Необходимо создать задачи для уведомления о падении сборки.


// ... ... ... ... ...notify_error:  stage: notify  only:    - dev    - /^release\/.*$/  script:    - sh ci-notify.sh   when: on_failurenotify_error_release:  stage: notify  only:    - /^release-.*$/  except:    - branches  script:    - sh ci-notify.sh   when: on_failure// ... ... ... ... ...

По сложившейся традиции у нас отдельная задача для прод-среды. Благодаря параметру when: on_failure задача отрабатывает только, когда одна из предыдущих завершилась неудачно.


Удобно, что теперь и код и сборка находятся в одном месте. Несмотря на недостатки GitLab CI, пользоваться им в целом удобно.

Источник: habr.com
К списку статей
Опубликовано: 19.02.2021 14:12:20
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Java

Devops

Gitlab-runner

Gitlab-ci

Gitlab

Maven

Nexus

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru