Пятница, 5 часов вечера. Разраб сидит перед монитором, и усталым немигающим взглядом смотрит на экран. Идёт сборка пакета... Ему пришлось прервать кодинг, чтобы студия, в которой он работал, могла приступить к сборке. Он понимает, что когда вернется к работе, мысль потеряется, вдохновение пропадёт, а качество кода, наверное, станет хуже.
Иногда он читает про CI/CD, даже как-то работал в докере. Ему интересно, можно ли автоматизировать сборку пакета? А что, если после коммита автоматически запускать сборку где-то на сервере? Может, в докере?
Разраб болен, и это первые симптомы. В нём прогрессирует DevOps. Следующие несколько дней он не будет спать и не сможет писать код. Он будет настраивать пайплайны...
Эффективная разработка программного обеспечения требует не только качественного кода, но и быстрой и надежной сборки пакетов. Делать это вручную утомительно, затратно по времени и может привести к ошибкам при обновлении разработки. С другой стороны, автоматическая сборка не только решает эти проблемы, но и позволяет разработчику сосредоточиться на написании кода.
Лучшим способом настроить автоматическую сборку является её внедрение в процесс непрерывной интеграции. В этой статье я хочу поделиться своим опытом настройки автоматической сборки в конвейере CI/CD на базе и средствами Gitlab'а с использованием докер-контейнеров и без них.
При настройке автоматической сборки пакетов разработки разумно начать с окружения, в котором эта сборка будет проходить. К вопросу можно подойти двумя путями: с использованием средств контейнеризации и без них. Эволюция CI-конвейера, настройкой которого занимался автор этой статьи, включала оба варианта, поэтому я расскажу как про первый, так и про второй. Рассмотрим их плюсы и минусы, после чего вы сможете решить, какой вас устраивает в большей степени.
Наиболее привычным и близким к ручной сборке пакета является вариант без использования контейнеров. Такой подход имеет свои плюсы:
...и минусы:
Этот способ включает перенос на стенд, где будет проходить сборка, папки с Development studio, а также установку компонент, требуемых для её работы: .NET SDK 6.0, .NET Framework 4.6.1 developer pack, .NET Framework 4.8 developer pack и Git. Все установщики входят в состав дистрибутива и расположены внутри Redist.
Когда все готово, можно провести тестовый запуск среды разработки. Если запуск успешен - то окружение настроено.
В 2023 году про Docker знает каждый, кто имеет отношение к разработке или администрированию. Будучи простым, универсальным и гибким инструментом, он решает целый ряд задач:
А ещё docker можно использовать для автоматической сборки пакетов разработки Directum RX. Ведь зачем делать это на отдельной ВМ, когда это можно сделать внутри контейнера, так ведь? Или нет? Чтобы разобраться, рассмотрим плюсы и минусы такого подхода.
Плюсы:
Минусы:
Если перечисленные плюсы для вас значат больше, чем минусы, то можно приступать к настройке. Для Создания контейнера в котором будет проходить сборка пакета, нам нужен образ этого контейнера. Наиболее распространенным подходом к созданию образов является написание Dockerfile -- инструкции, содержащей шаги к созданию нужного образа.
Dockerfile начинается с инструкции FROM. Она указывает на то, какой образ мы используем в качестве базового. Development studio, в которой и будет осуществляться сборка, может работать только на Windows и требует для этого .NET SDK 6.0. Поэтому наш выбор - mcr.microsoft.com/dotnet/sdk:6.0.417-1-windowsservercore-ltsc2022:
FROM mcr.microsoft.com/dotnet/sdk:6.0.417-1-windowsservercore-ltsc2022
Среда разработки требует наличия .NET Framework 4.6.1 developer pack. Скачаем установщик (в нашем примере - из Gitlab package registry) и запустим его с параметрами /q и /norestart. После этого установщик можно удалить:
RUN C:\Windows\System32\curl.exe -O --header "Private-Token: <Ваш токен>" https://gitlab.example.ru/api/v4/projects/1/packages/generic/rx-windows/4.7.34.0/NDP461-DevPack-KB3105179-ENU.exe &&\
NDP461-DevPack-KB3105179-ENU.exe /q /norestart &&\
powershell Remove-Item -Path "C:\NDP461-DevPack-KB3105179-ENU.exe" &&\
Среда также требует наличия .NET Framework 4.8 developer pack. Делаем всё то же, что и с 4.6.1.:
RUN C:\Windows\System32\curl.exe -O --header "Private-Token: <Ваш токен>" https://gitlab.example.ru/api/v4/projects/1/packages/generic/rx-windows/4.7.34.0/ndp48-devpack-enu.exe &&\
ndp48-devpack-enu.exe /q /norestart &&\
powershell Remove-Item -Path "C:\ndp48-devpack-enu.exe" &&\
# Git
Последнее что требуется студии - это Git. Он также понадобиться чтобы подтягивать из репозитория собираемые решения:
RUN C:\Windows\System32\curl.exe -O --header "Private-Token: <Ваш токен>" https://gitlab.example.ru/api/v4/projects/1/packages/generic/rx-windows/4.7.34.0/Git-install.exe &&\
Git-install.exe /VERYSILENT /NORESTART &&\
powershell Remove-Item -Path "C:\Git-install.exe" &&\
Ключевой компонент нашего образа - это Development studio. Дополнить ей образ можно скачав и распаковав туда архив со средой разработки.
RUN C:\Windows\System32\curl.exe -O --header "Private-Token: <Ваш токен>" https://gitlab.example.ru/api/v4/projects/1/packages/generic/rx-windows/4.7.34.0/DevelopmentStudio.zip &&\
powershell Expand-Archive -Path "C:\DevelopmentStudio.zip" -DestinationPath "C:\DevelopmentStudio" &&\
powershell Remove-Item -Path "C:\DevelopmentStudio.zip"
Также добавим инструкцию при запуске контейнера чтобы все команды внутри сразу выполнялись в powershell:
CMD ["powershell"]
Если объединить все вместе, получим следующий Dockerfile:
FROM mcr.microsoft.com/dotnet/sdk:6.0.417-1-windowsservercore-ltsc2022
# .NET Framework 4.6.1 developer pack
RUN C:\Windows\System32\curl.exe -O --header "Private-Token: <Ваш токен>" https://gitlab.example.ru/api/v4/projects/1/packages/generic/rx-windows/4.7.34.0/NDP461-DevPack-KB3105179-ENU.exe &&\
NDP461-DevPack-KB3105179-ENU.exe /q /norestart &&\
powershell Remove-Item -Path "C:\NDP461-DevPack-KB3105179-ENU.exe" &&\
# .NET Framework 4.8 developer pack
C:\Windows\System32\curl.exe -O --header "Private-Token: <Ваш токен>" https://gitlab.example.ru/api/v4/projects/1/packages/generic/rx-windows/4.7.34.0/ndp48-devpack-enu.exe &&\
ndp48-devpack-enu.exe /q /norestart &&\
powershell Remove-Item -Path "C:\ndp48-devpack-enu.exe" &&\
# Git
C:\Windows\System32\curl.exe -O --header "Private-Token: <Ваш токен>" https://gitlab.example.ru/api/v4/projects/1/packages/generic/rx-windows/4.7.34.0/Git-install.exe &&\
Git-install.exe /VERYSILENT /NORESTART &&\
powershell Remove-Item -Path "C:\Git-install.exe" &&\
# Development studio
C:\Windows\System32\curl.exe -O --header "Private-Token: <Ваш токен>" https://gitlab.example.ru/api/v4/projects/1/packages/generic/rx-windows/4.7.34.0/DevelopmentStudio.zip &&\
powershell Expand-Archive -Path "C:\DevelopmentStudio.zip" -DestinationPath "C:\DevelopmentStudio" &&\
powershell Remove-Item -Path "C:\DevelopmentStudio.zip"
CMD ["powershell"]
Каждая команда RUN создаёт новый слой образа. Для сокращения их числа все команды RUN были объединены в одну.
Dockerfile готов! Теперь образ нужно собрать и опубликовать в реестр контейнеров, откуда его можно будет использовать в пайплайнах CI/CD (например, в Gitlab container registry). Для этого воспользуйтесь командами:
Исполнителем этапов CI/CD в Gitlab является Gitlab runner. Процесс его установки хорошо описан в документации, однако для корректной работы стоит обратить внимание на некоторые моменты.
Без докера:
С докером:
Внедрить автоматическую сборку пакета в процесс непрерывной интеграции означает создать и настроить соответствующий этап в конвейере CI/CD. В Gitlab это делается изменением содержимого файла .gitlab-ci.yml, который содержит описание всех этапов.
Начнём с постановки задачи. Этап должен удовлетворять следующим условиям:
Содержимое .gitlab-ci.yml будет немного отличаться в зависимости от того, настраиваем мы автоматическую сборку в контейнере или нет. Независимо от этого, файл следует сохранить в корень репозитория с прикладной разработкой и закоммитить в Gitlab. Если все сделано правильно, то в разделе CI/CD -> Pipelines на странице проекта в Gitlab'е появится CI/CD-конвейер с нашим этапом. Все настройки этапа можно и нужно менять в соответствии с потребностями на проекте. Если какие-то из них не понятны - настоятельно рекомендую в них разобраться, для этого есть подробная документация.
Если мы планируем собирать пакеты разработки вне контейнеров, то при отсутствии других этапов, итоговый .gitlab-ci.yml может выглядеть следующим образом:
stages:
- build
build-job:
variables:
GIT_STRATEGY: none
BASE_PROJECT_NAME: "base"
BASE_REPOSITORY_URL: "https://gitlab-ci-token:${CI_JOB_TOKEN}@$CI_SERVER_HOST/$CI_PROJECT_NAMESPACE/$BASE_PROJECT_NAME"
BASE_REPO_BRANCH: "master"
PACKAGE_PATH: "$CI_PROJECT_DIR/package.dat"
XML_TEMPLATE_PATH: "$CI_PROJECT_DIR/$CI_PROJECT_NAME/package.xml"
# Названия веток при коммитах в которых жоба запускается автоматически
JOB_AUTO_RUN_BRANCH_PATTERN: "/^master$|^test$/"
tags:
- directum-build
stage: build
script:
# Редачим настройку "GIT_ROOT_DIRECTORY" в _ConfigSettings.xml у Development studio
- $xmlpath = "C:/DirectumLauncher/etc/_builds/DevelopmentStudio/bin/_ConfigSettings.xml"
- $xml = [xml](Get-Content $xmlpath)
- $node = $xml.settings.var | where {$_.name -eq "GIT_ROOT_DIRECTORY"}
- $node.value = $CI_PROJECT_DIR
- $xml.Save($xmlpath)
# Удаляем старые артефакты проекта
- if(Test-Path "package.dat") {Remove-Item "package.dat" -Force}
- if(Test-Path "package.xml") {Remove-Item "package.xml" -Force}
# Подтягиваем изменения для сборки пакета
- | #work
if (Test-Path "$CI_PROJECT_NAME")
{
git -C $CI_PROJECT_NAME remote set-url origin $CI_REPOSITORY_URL
git -C $CI_PROJECT_NAME pull --all
git -C $CI_PROJECT_NAME checkout $CI_COMMIT_BRANCH
}
else
{
git clone -b $CI_COMMIT_BRANCH $CI_REPOSITORY_URL $CI_PROJECT_NAME
}
- | #base
if (Test-Path "$BASE_PROJECT_NAME")
{
git -C $BASE_PROJECT_NAME remote set-url origin $BASE_REPOSITORY_URL
git -C $BASE_PROJECT_NAME pull --all
git -C $BASE_PROJECT_NAME checkout $BASE_REPO_BRANCH
}
else
{
git clone -b $BASE_REPO_BRANCH $BASE_REPOSITORY_URL $BASE_PROJECT_NAME
}
# Запускаем сборку пакета
- C:/DirectumLauncher/etc/_builds/DevelopmentStudio/bin/DevelopmentStudio.exe -c $XML_TEMPLATE_PATH -d $PACKAGE_PATH --increment-version false
rules:
- if: $CI_COMMIT_BRANCH !~ $JOB_AUTO_RUN_BRANCH_PATTERN
when: manual
- if: $CI_COMMIT_BRANCH =~ $JOB_AUTO_RUN_BRANCH_PATTERN
artifacts:
expire_in: 1 mos
paths:
- package.dat
- package.xml
Файл снабжен комментариями, но для лучшего понимания кратко разберемся в происходящем. Начнём с переменных:
Все остальные переменные являются предопределенными и объявляются неявно. О том, что каждая значит можно узнать из документации.
Блок script содержит команды выполняемые Gitlab runner в среде выполнения. Наш скрипт содержит следующие команды:
Блок rules включает условие для автоматического запуска задания на сборку пакета. В данном случае - это соответствие названия ветки regex-паттерну из $JOB_AUTO_RUN_BRANCH_PATTERN. Если ветка не соответствует паттерну, то, при необходимости, задание можно запустить вручную.
Artifacts содержит описание файлов и папок которые являются результатом работы задания. В нашем .gitlab-ci.yml описаны следующие артефакты:
Если у нас подготовлен контейнер, и мы хотим проводить сборку в нём, то итоговый .gitlab-ci.yml будет выглядеть немного иначе.
stages:
- build
build-job:
variables:
GIT_STRATEGY: none
BASE_PROJECT_NAME: "base"
BASE_REPOSITORY_URL: "https://gitlab-ci-token:${CI_JOB_TOKEN}@$CI_SERVER_HOST/$CI_PROJECT_NAMESPACE/$BASE_PROJECT_NAME"
BASE_REPO_BRANCH: "master"
PACKAGE_PATH: "$CI_PROJECT_DIR/package.dat"
LOGS_PATH: "$CI_PROJECT_DIR/logs"
XML_TEMPLATE_PATH: "$CI_PROJECT_DIR/$CI_PROJECT_NAME/package.xml"
# Названия веток при коммитах в которых жоба запускается автоматически
JOB_AUTO_RUN_BRANCH_PATTERN: "/^master$|^test$/"
tags:
- directum-docker-build
stage: build
allow_failure: false
image: registry-gitlab.example.ru/directum/rx/sugero_development_studio
script:
# Редачим _ConfigSettings.xml у Development studio
- $xmlpath = "C:/DevelopmentStudio/bin/_ConfigSettings.xml"
- $xml = [xml](Get-Content $xmlpath)
- $node = $xml.settings.var | where {$_.name -eq "GIT_ROOT_DIRECTORY"}
- $node.value = $CI_PROJECT_DIR
- $node = $xml.settings.var | where {$_.name -eq "LOGS_PATH"}
- $node.value = $LOGS_PATH
- $xml.Save($xmlpath)
# Подтягиваем изменения для сборки пакета
- C:\Program` Files\Git\cmd\git clone -b $CI_COMMIT_BRANCH $CI_REPOSITORY_URL $CI_PROJECT_NAME
- C:\Program` Files\Git\cmd\git clone -b $BASE_REPO_BRANCH $BASE_REPOSITORY_URL $BASE_PROJECT_NAME
# Запускаем сборку пакета
- $process = $(Start-Process -FilePath C:\DevelopmentStudio\bin\DevelopmentStudio.exe -ArgumentList "-c $XML_TEMPLATE_PATH -d $PACKAGE_PATH --increment-version false" –PassThru)
- $process.WaitForExit()
- | #При неуспешной сборке жоба завершится с ошибкой
if ($process.ExitCode -ne 0)
{
echo "Процесс сборки пакета завершился с ошибкам"
exit 1
}
rules:
- if: $CI_COMMIT_BRANCH !~ $JOB_AUTO_RUN_BRANCH_PATTERN
when: manual
- if: $CI_COMMIT_BRANCH =~ $JOB_AUTO_RUN_BRANCH_PATTERN
artifacts:
expire_in: 1 mos
when: always
paths:
- package.dat
- package.xml
- $LOGS_PATH
В предыдущей части было дано описание .gitlab-ci.yml, используемому при альтернативном подходе. Поскольку оба решают одну задачу, то файлы очень похожи. Поэтому ниже мы рассмотрим только отличия.
Отличия начинаются с новой переменной "LOGS_PATH". Дело в том, что при появлении ошибок сборки внутри контейнера, их намного сложнее проанализировать, т.к. контейнер вместе с содержимым уничтожается сразу после завершения этапа. Для решения этой проблемы можно сохранять лог-файл среды разработки в качестве еще одного артефакта. Для этого мы дополнительно вносим изменения в параметр "LOGS_PATH" конфигурационного файла студии и немного меняем содержимое блока artifacts, чтобы на выходе иметь ещё один артефакт.
Второе отличие: поскольку с каждым запуском этапа у нас запускается новый контейнер, то вместо git pull мы всегда используем git clone.
Последним отличием является запуск сборки с помощью Start-Process. Дело в том, что в отличии от сборки на ВМ, в контейнере runner не ждёт её завершения и сразу завершает этап, причём без ошибок. Спасением и находкой стал запуск студии через Start-Process. Он позволил не только явно дожидаться окончания процесса, но и проверять его результат с помощью ExitCode.
В результате несложных операций мы автоматизировали сборку пакетов разработки, повысили оперативность и надёжность передачи разработки, организовали хранение пакетов средствами Gitlab'а, да и в целом поступили как настоящие DevOps'ы. Разработчик рад, что больше не отвлекается на рутинные задачи, ПМ рад уверенности в том, что машина не забудет про пакет и не ошибётся при сборке. Мир стал чуточку лучше, а жить в нём стало чуточку проще.
Понедельник. 3 часа дня. Разработчик кодит. Прошёл ровно месяц после последнего собранного вручную пакета. Многое изменилось за это время: подход команды к разработке, распределение обязанностей в коллективе, изменился и сам разраб. Внедрив процесс автоматической сборки на проекте и продемонстрировав её эффективность, он стал помогать с этим другим командам. Он собирается продолжать развиваться как DevOps и уже думает о том, какой процесс будет автоматизировать следующим. А мы с вами будем с нетерпением этого ждать и, кто знает, может ещё о нём услышим...
Автор не DevOps, он только учится. Если у вас возникли предложения, пожелания или вы заметили ошибку, пожалуйста, оставляйте комментарии. Я внимательно с ними ознакомлюсь и отмечу лайкусиком дополню статью.
Также хочу выразить благодарность Алексею Присяжному — самому злостному переносчику DevOps'а в сообществе Directum. Эта статья подготовлена по следам его выступления на DDC в далеком 2022 году.
Спасибо за внимание и всех благ!
Добрый день!
Звучит супер интересно.
Подскажите, пожалуйста, а можно ли как-то решить вопрос с локальной сборкой на тестовом контуре? Недавно столкнулись с тем, что с пакетами "База знаний", "Проекты", "Agile" сборка стала весить неприлично много - 300мб+, хотя стандартная разработка не превышает 100-110мб. Следовательно локальные сборки и публикации стали занимать более продолжительное время. Особенно на виртуальных машинах (возможно нехватка ресурсов сказывается).
Условно говоря, имеем 3 разработчиков, у каждого развернут свой локальный DRX, в который разработчики вливают собственно свою разработку. Возможно ли процесс развертывания автоматизировать с помощью CI/CD и будет ли в этом профит?
Артём, не до конца понял в чём проблема. Под развёртыванием вы имеете ввиду публикацию? Автоматизировать публикацию можно, для этого следует использовать DeploymentToolCore. Это будет ещё одним этапом пайплайна использующим пакет собранный на предыдущем этапе. Профит от этого безусловно будет
Что касается размеров пакета разработки, то в него можно просто не включать решения которые не менялись с последней публикации, тогда он будет меньше.
Станислав, если не включать решения, которые не менялись, при публикации разве они не удаляются из той системы, куда публикуется такой пакет?
Станислав, Кстати о проблеме - заключается в том, что публикация (НЕ hot deploy) на локальный (непосредственно у разработчика на машине) стенд бывает занимает до 5-10 минут, что в масштабе рабочего дня довольно долго, т.к. таких публикаций может быть десятки.
Артём, https://club.directum.ru/webhelp/directumrx/4.7/web/index.html?sds_publikatsiya.htm см. часть "Разница в процессах публикации с помощью среды разработки и утилиты"
5-10 минут без hot deploy это норма. Пользуйтесь hot deploy: https://club.directum.ru/webhelp/directumrx/4.8/web/index.html?sungerodev_fastdeploywindow.htm
Артём, 5-10 еще ладно... у меня может и 20 целиком это проходить...) как будто я полную перепубликацию делаю, пока решения не нашел, возможно не хватает частоты ядра на машине разработки.
Станислав, можно подумать, что hot deploy я запускаю по своему желанию)
Станислав, это печально. Директум писал, что в 4.8 ускорили публикацию, не удалось случайно это проверить?
Артём, по второй ссылке перечислены условия при которых публикация происходит быстро, без перезапуска веб-сервера, так что можно сказать что в ваших силах проводить ее по своему желанию.
Возможности убедиться в ускорении публикаций на новой версии, к сожалению, не было. CI настраивал для 4.7
Станислав, спасибо за статью, обязательно сделаем себе. А если тестовый сервер установлен со средной разработки, что-то поменяется?
Евгений, при сборке тестовый сервер не нужен, поэтому не повлияет. В зависимости от того зачем вам на тесте дев среда, возможно, вам не нужно собирать на тест пакеты. Если они нужны только в прод, то можно изменить переменную JOB_AUTO_RUN_BRANCH_PATTERN на "/^master$/" - тогда пакеты будут собираться только при коммитах в мастер ветку
Станислав, да, пакеты для теста собирать не нужно, но хотелось бы автоматом обновлять ветку и производить публикацию. Такое возможно?
Дев.среда на тесте сделана из-за того, что в случае ошибок, мы получаем более расширенное логирование, вплоть до строки где возникла ошибка.
Евгений, для расширенного логирования, достаточно при сборке пакета поставить галочку "Передать как отладочный пакет". Студия не нужна https://club.directum.ru/webhelp/directumrx/4.4/web/index.html?sungerodev_createdevpackagewindow.htm
Автоматом обновлять ветку можно, а вот проводить публикацию через дев среду - вряд ли. Впрочем, это можно делать через DeploymentToolCore, вот с ним автоматическая публикация возможна. Но это уже тема для статьи про CD а не CI
Станислав, Отладочный пакет работает только на Linux же
Евгений, могу ошибаться, но на моей памяти всегда работало и для винды и для линукс. Из справки действительно следует что фича работает только на Linux - я направил пожелание об исправлении в саппорт.
Станислав, статья отличная, но вот только почему то не реализуется никак
Пробую через докер, скачал образ, правда mcr.microsoft.com/dotnet/sdk:6.0.420-windowsservercore-ltsc2019
Залил по заветам необходимый софт. Собрал и закинул образ, при чем даже запуск происходит devStud, но в логах ошибки не пойму куда копать, может автор подскажете?
Контейнер windows, запускается на Docker Desktop под windows.
Например:
Константин, судя по логам, возможно, стоит попробовать запустить скрипт с правами администратора. О том как это сделать есть инфа тут например: https://ru.stackoverflow.com/questions/1085516/%D0%9A%D0%B0%D0%BA-%D0%B7%D0%B0%D0%BF%D1%83%D1%81%D1%82%D0%B8%D1%82%D1%8C-%D0%BA%D0%BE%D0%BC%D0%B0%D0%BD%D0%B4%D1%83-%D0%B2-powershell-%D1%81-%D0%BF%D1%80%D0%B0%D0%B2%D0%B0%D0%BC%D0%B8-%D0%B0%D0%B4%D0%BC%D0%B8%D0%BD%D0%B8%D1%81%D1%82%D1%80%D0%B0%D1%82%D0%BE%D1%80%D0%B0
Если не поможет то не смогу подсказать что-то другое. Проблема либо в коде разработки либо в базовом образе.
Авторизуйтесь, чтобы написать комментарий