Привет. Если ты обратил внимание на этот материал, то значит ты начинаешь разбираться в мире Jenkins. Самым сложным в любом деле является начало. На первом этапе окружает много незнакомых и непонятных терминов, сложно понять логику процесса и последовательность действий. Кажется, что это какая-то магия. Чтобы первые шаги были чуточку легче, я опишу простой пример, который можно использовать в качестве основы для реальных задач.
Я буду отталкиваться от того, что у тебя уже установлен Jenkins. Установка Jenkins’а неплохо описана и показана в сети. Мы же будем смотреть на то, как создается Jenkins Pipeline или труба (конвейер) Jenkins.
Для чего всё это
Что вообще такое Jenkins? Jenkins это крутая программа, которая реализует практики DevOps. Практики DevOps, в свою очередь, это логичные действия, которые позволяют повысить качество создаваемых продуктов. Под «качеством» будем понимать сразу много преимуществ – уменьшение количества ошибок в продукте, снижение Time-to-Market, повышение пользовательской лояльности и т.д.
Jenkins является «конвейером» поставки ПО от среды разработки в промышленную среду. «Конвейер», конечно же, условный. Jenkins выполняет шаг за шагом простейшие операции над кодом словно это автомобильный завод, на котором ваш фольксваген или форд движется по цехам и проходит разные этапы превращения металла в автомобиль. Такой «конвейер» в Jenkins называют Pipeline.
О DevOps и его практиках
DevOps, грубо говоря, регламентирует «завод» и «конвейер» на нём.
Одной из практик DevOps является CI/CD/CDP. Эта практика описывает шаги «конвейера».
CI (Continuous Integration, непрерывная интеграция) – начальная стадия «конвейера» по сборке кода и загрузке собранного ПО в среду разработки.
CD (Continuous delivery, непрерывная поставка) – является продолжением CI. В этой практике производится автоматизированное развертывание на тестовую среду продукта и разнообразные тесты над ним.
CDP (Continuous Deployment, непрерывное развертывание) – поставка результатов работы CI и CD практик в промышленную среду.
Jenkins, в свою очередь, может реализовать CI/CD/CDP на практике.
Описание примера
Теперь, имея представление что такое Jenkins и для чего он нужен, сделаем тестовую трубу, которая реализует в очень урезанном виде CI/CD практику.
В качестве разворачиваемого продукта будем использовать SSIS-пакет. SSIS (SQL Server Integration Services) – это инструмент создания решений по интеграции и преобразованию данных. То есть, решение по переносу данных из одного источника в другой с возможностью преобразования этих данных в процессе переноса.
Подобных примеров в сети я почти не встречал, поэтому убью сразу двух зайцев – построю трубу Jenkins на интересном примере из моей практики. Принцип создания труб одинаковый, поэтому вы сможете применить пример из статьи для своих целей.
Чтобы было ещё интереснее, сделаем задачу со звёздочкой – в одном SSIS-проекте будет сразу несколько SSIS-пакетов.
Опишу шаги, которые будет делать труба:
Чекаут из Git (в моём случае Bitbucket)
Сборка SSIS-проекта
Развертывание собранного файла проекта в среду разработки.
Мой подопытный SSIS-кролик выглядит следующим образом:
Внутри проекта «AUDIT_Import_ALL» находится четыре SSIS-пакета с расширением «.dtsx». Если этот проект собрать, то получится один файл с именем «AUDIT_Import_ALL» и расширением «.ispac». Данный файл предназначен для развертывания проекта в MS SQL Server.
Подготовительные шаги
Проект «AUDIT_Import_ALL» загружен в Bitbucket. В репозиторий проекта я добавлю папку, содержащую Jenkinsfile.
Jenkinsfile – это простой текстовый файл с кодом на языке Groovy, который используется для конфигурации трубы Jenkins.
Теперь я создам тестовую трубу в самом Jenkins. Для этого на главной странице пространства Jenkins нажимаю «New Item», далее выбираю Pipeline и задаю имя.
После нажатия «ОК», открывается окно с настройками. Не будем подробно останавливаться на этом окне. Для тестового примера мне никакие настройки здесь не нужны, кроме раздела с расширенными настройками. А именно, окно настройки «Pipeline».
В нём я свяжу Jenkinsfile, который находится в Bitbucket, с только что созданной трубой Jenkins. Для этого в пункте «Definition» выбираем «Pipeline script from SCM». Этим пунктом я сообщаю Jenkins что мой Jenkinsfile лежит в SCM (source control management – система контроля версий).
На следующем шаге я выбираю SCM, где лежит мой файл. В моём случае это Git (Bitbucket). Далее я прописываю путь до репозитория в SSH формате (возможно и в HTTPS) и указываю учетные данные в поле «Credentials» (или добавляю новую учетную запись через кнопку «Add»).
Jenkinsfile лежит в master-ветке репозитория в Bitbucket, поэтому я не трогаю пункт «Branch Specifier (blank for ‘any’)». Так же, не трогаю следующие два пункта. А вот
пункт «Script Path» мне необходимо поменять, потому что по умолчанию Jenkins будет
искать Jenkinsfile в корне репозитория. Отдельная папка нужна затем, что файлов для работы Jenkins-трубы может быть несколько и содержать их в корне проекта будет просто не удобно.
Jenkins SSIS стенд
Хочу затронуть архитектурное решение моего Jenkins-стенда. Для тех, кто не будет строить DevOps для SSIS, можно перейти сразу к следующему разделу.
Архитектурно Jenkins строится по принципу master-slave. Есть один ведущий узел, который управляет множеством ведомых. У меня уже была развернута master-нода Jenkins. К ней я подключил slave-ноду, которая была создана под задачу DevOps SSIS. На этом сервере находится программа SSISBuild.exe, предоставляемая Microsoft как раз для наших нужд. SSISBuild предназначена для удаленной сборки SSIS-проектов и не требует установленной Visual Studio или среды выполнения SSIS.
Сервер, куда я буду развертывать SSIS-проект, содержит установленный MS SQL Server с установленной службой Integration Services. На этом сервере установлена вторая программа для DevOps SSIS – SSISDeploy.exe. SSISDeploy предназначена для развертывания файлов проектов (файлов с расширением ispac).
Более подробную информацию о SSISBuild и SSISDeploy вы сможете найти по ссылке.
Jenkinsfile
Итак, труба настроена, связь с Bitbucket установлена, необходимые сервера на связи. Осталось совсем чуть-чуть до запуска. Разберем Jenkinsfile чтобы понимать, как будут происходить те шаги, которые я описал в начале статьи.
Наиболее фундаментальной частью конвейера является «step». По сути, шаги говорят Jenkins, что делать, и служат базовым строительным блоком для синтаксиса конвейеров.
Все операции с конвейером должны быть заключены в блок «pipeline»:
pipeline {
/* Ваши инструкции для конвейера */
}
Блок «pipeline» содержит разделы. Первым разделом является «agent». Раздел «agent» указывает, где будет выполняться труба Jenkins. Раздел должен быть определен на верхнем уровне внутри блока «pipeline».
В моём случае конвейер будет работать на сервере TestNode:
agent {
node {
label 'TestNode'
}
}
Раздел «options» позволяет настраивать параметры конвейера непосредственно из Jenkinsfile. Эти параметры также можно задавать в графическом интерфейсе Jenkins.
ansiColor('xterm')
timestamps()
disableConcurrentBuilds()
timeout(time: 1, unit: 'HOURS')
buildDiscarder(logRotator(artifactDaysToKeepStr: '7', artifactNumToKeepStr: '10', daysToKeepStr: '7', numToKeepStr: '50'))
}
Раздел «parameters» задает список параметров, которые пользователь должен предоставить при запуске трубы. Заданные пользователем значения становятся доступными для шагов конвейера через объект params. Я создам два булевых параметра, с помощью которых можно будет управлять запуском шагов конвейера:
parameters {
booleanParam(defaultValue: true, description: 'Build SSIS', name: 'BUILD')
booleanParam(defaultValue: false, description: 'Deploy SSIS', name: 'DEPLOY')
}
Раздел «environment» определяет глобальные и локальные (в пределах конкретного «step») переменные в Jenkinsfile. Глобальные переменные удобно использовать для подстановки статических значений:
environment {
SSISBuildPath="C:\\SSIS_DEV_OPS\\SSISBuild.exe"
SSISDeployPath="C:\\SSIS_DEV_OPS\\ssisdeploy.exe"
SOLUTION = "AUDIT_Import_ALL\\AUDIT_Import_ALL.dtproj"
}
Раздел «stages» является местом, где будет происходить основная часть работы конвейера. Раздел делится на этапы, которые содержат описание шагов. У меня будет три этапа:
stages {
// Извлекаем проект из Bitbucket
stage('Checkout') { }
// Собираем проект. Получаем на выходе пакетный файл AUDIT_Import_ALL.ispac
stage('Build') { }
// Загружаем архив с проектом на удаленный сервер. Деплоим его на MS SQL Server
stage('Deploy') { }
}
Теперь рассмотрим шаги на каждом этапе.
1) Checkout
stage('Checkout') {
steps {
// Удаление всех файлов из рабочего каталога на сервере TestNode
bat 'del /F /S /Q *.*'
// Удаление всех папок из рабочего каталога на сервере TestNode
bat 'for /d %%x in (.\\*) do @rd /s /q %%x'
// Вывод echo в консоль Jenkins
echo 'step Git Checkout'
// Извлечение из системы контроля версий в рабочий каталог на сервере TestNode
checkout scm
}
}
2) Build
stage('Build') {
// Команда when позволяет конвейеру определять, следует ли выполнять этап в зависимости от заданного условия.
when {
// Заданное условие expression. Оно задается пользователем при запуске конвейера и передается в скрипт через параметр "BUILD"
expression {
return params.BUILD
}
}
// Начало шага сборки SSIS-проекта
steps {
// Вызов функции PrintStage(). Её мы рассмотрим далее.
PrintStage()
echo "step Build Solution"
/* Вызов SSISBuild.exe на TestNode. Кстати, здесь применяется три подстановки:
- переменная среды ${WORKSPACE} - абсолютный путь рабочей области.
- глобальные переменные ${SSISBuildPath} и ${SOLUTION} - они задавались в разделе environment */
bat "${SSISBuildPath} -p:${WORKSPACE}\\${SOLUTION}"
}
}
3) Deploy
stage("Deploy to Dev Env") {
when {
expression {
return params.DEPLOY
}
}
steps {
PrintStage()
// Использование стандартного плагина Jenkins для архивации собранного SSIS-проекта
zip zipFile: 'archive.zip', archive: false
// Самое интересное. Для деплоя полученного файла будем применять службу удаленного управления Windows WinRM в PowerShell
// Используем учетную запись 'WINRM_PASS' для извлечения SecretText из Jenkins. Учетная запись заведена в Jenkins. Как её добавить я покажу ниже
withCredentials([string(credentialsId: 'WINRM_PASS', variable: 'WINRM_PASS')]) {
// Применение скриптового синтаксиса внутри декларативного.
script {
// Определяем переменные
def err
def stdout = powershell label: 'deploy', returnStdout: true, script: '''
# Задаем пароль учетной записи WinRM в SecureString
$pw = convertto-securestring -AsPlainText -Force -String $env:WINRM_PASS
# Задаем учетные данные для создания сессии WinRM
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist "Domain\\User",$pw
# Открываем сессию
$s = New-PSSession -ComputerName <server> -Credential $cred
#Создаем папку на удаленном сервере для копирования архива
$remotePath = \'D:\\DIGAUD\\TestSSISPipeline\'
$job = Invoke-Command -Session $s `
-ScriptBlock {
if (!(Test-Path -Path \'D:\\DIGAUD\\TestSSISPipeline\')) {mkdir $Using:remotePath}
} `
-AsJob
Wait-Job $job
$r = Receive-Job $job
# Копируем архив
$path = Get-Location
$dest = Join-Path $path "archive.zip"
Copy-Item -Path $dest `
-Destination $remotePath `
-ToSession $s
# Распаковываем архив
$job = Invoke-Command -Session $s `
-ScriptBlock {
Expand-Archive -LiteralPath \'D:\\Jenkins\\TestSSISPipeline\\archive.zip' -DestinationPath \'D:\\Jenkins\\TestSSISPipeline\'
} `
-AsJob
Wait-Job $job
$r = Receive-Job $job
#Деплоим
$job = Invoke-Command -Session $s `
-ScriptBlock {
C:\\SSIS_DEV_OPS\\ssisdeploy.exe -s:\"D:\\Jenkins\\TestSSISPipeline\\AUDIT_Import_ALL\\bin\\Development\\AUDIT_Import_ALL.ispac\" -d:\"catalog;/SSISDB/AUDIT_Import_ALL;mssql_server,port\" -at:win
} `
-AsJob
Wait-Job $job
$r = Receive-Job $job
Remove-PSsession $s
'''
}
}
}
}
}
Раздел «post» определяет дополнительные действия, которые будут выполняться после завершения работы основного кода конвейера или этапа (в зависимости где стоит post).
post {
// Код ниже будет выполнен вне зависимости от статуса сборки трубы или этапа
always {
script {
# Устанавливаю результат сборки трубы
currentBuild.result = currentBuild.result ?: 'SUCCESS'
# Уведомляю Bitbucket о результате сборки
notifyBitbucket()
}
}
}
На этом мой блок «pipeline» в Jenkinsfile заканчивается.
Далее располагается метод «PrintStage», который встречался на этапах Build и Deploy:
// Метод, который принимает на вход один текстовый аргумент. По умолчанию аргумент пустой
void PrintStage(String text=""){
// Применение тернарного оператора
// Если аргумент text пустой, в консоль Jenkins будет выведено десять звёздочек, название этапа, десять звёздочек
// Если аргумент text не пустой, в консоль Jenkins будет выведено его значение
text=="" ? println ('* '*10 + env.STAGE_NAME.toUpperCase() + " *"*10) : println (text)
}
Запуск конвейера
Теперь всё готово к запуску. Открываю окно нашего конвейера и нажимаю кнопку «Собрать с параметрами».
Откроется окно с параметрами, которые я определил в разделе «parameters». Выбираю этапы, которые хочу запустить и нажимаю «собрать».
Далее, труба начинает собираться и если всё сделано правильно, то Jenkins нарисует красивую картинку:
Как добавить учетную запись в Jenkins
Для этого на главной странице вашего пространства нажмите кнопку «Credentials», далее выбрать «Folder».
Наведите курсор на пункт «Global credentials (unrestricted)» и у вас появится «галочка» для вызова выпадающего списка. Нажмите на неё. Всплывет маленькое окошко «Add credentials» - нажимайте!
Здесь вы можете задать вашу учетную запись. В моём примере с учетной записью WinRM, я использовал «Secret text» для хранения пароля.
Выводы
По итогу, мы сделали трубу, которая собирает и деплоит SSIS-пакеты на MS SQL Server. Данный пример является учебным , но его можно взять за основу и доработать под ваши задачи. Например, если вы делаете веб-приложение на ASP.NET, то вместо ssisdeploy можно использовать webdeploy. По аналогии можно добавить шаги с разнообразными тестами, созданием release notes, загрузкой дистрибутива в централизованное хранилище и т.д.
Какие преимущества даёт Jenkins? Он автоматизирует многие рутинные процессы, связанные с разработкой программного обеспечения. Например, запуск трубы с прохождение различных тестов при коммите в нужную ветку в Bitbucket. Такой подход уменьшит количество ошибок. Применение Jenkins объединяет процессы сборки, тестирования и внедрения и создает четко выстроенный подход к созданию продукта.
Буду рад, если моя статья была кому-то полезна.
Если у вас остались вопросы оставляйте их в комментариях – я обязательно отвечу.
Комментарии (6)
unsignedchar
05.03.2022 17:17-1А это точно groovy? НЯП, groovy близкий родственник java, а тут что то декларативное.
funca
05.03.2022 17:28Pipeline DSL (domain-specific language) based on the Groovy programming language. https://www.jenkins.io/pipeline/getting-started-pipelines
sshikov
05.03.2022 17:47+2Это вполне обычный груви (в частности, внутри декларативных блоков вполне можно писать что угодно, с учетом того, что администратор может запретить какие-то классы и методы). Просто разработка DSL — это возможность груви практически с рождения.
funca
06.03.2022 19:03+1Для declarative pipeline, у них навернут свой собственный парсер поверх грувячьего. Фактически это отдельный движок (подробности тут https://github.com/jenkinsci/pipeline-model-definition-plugin). Ну то есть если попытаться скормить такой код сразу groove, то он может и не подавится, но и толку тоже не будет.
Писать на чистом groove все ещё можно (scripted pipeline), но тогда теряются многие плюшки экосистемы.
sshikov
06.03.2022 20:49+1Я бы сказал бы, не вникая в устройство, что это выглядит как типовой билдер. Во всяком случае, если бы мне реализацию такого движка предложили бы написать, я бы с чего-то такого начал. А движок — это по сути какая-то штука, создающая контекст для билдера. Посмотрю при случае, спасибо.
anger32
Только в теги нужно Groovy, а не Python.