Если вы работаете с Jenkins и не слышали про Declarative Pipelines, то я как раз попытаюсь объяснить что к чему. Declarative Pipeline — это развитие старых Pipelines. Declarative Pipelines должны заменить старые Pipelines, они предоставляют более простой синтаксис и инструменты для более удобной работы, такие как автоматическая проверка синтаксиса при вводе, форматирование через API или CLI, интеграцию с Docker, визуальный редактор BlueOcean.
Обычно, declarative pipelines представляют в виде Jenkinsfile в корне репозитория, но их можно создавать из любых файлов, а также напрямую, через Job DSL. И так, у вас есть работающий Jenkins сервер с уже настроенной seed job. Если нет, то запустите его в docker:
docker volume create jenkins
docker run --name jenkins -ti \
-v jenkins:/var/jenkins_home \
-p 127.0.0.1:8080:8080 \
jenkins/jenkins:lts
Скопируйте из консоли пароль администратора, перейдите на http://localhost:8080 и продолжите установку.
Перед тем, как приступить к написанию pipeline, продумайте какие этапы (stages) и шаги (steps) будут присутствовать. Можете даже нарисовать схему, что за чем будет выполняться. Например.
- Сборка.
- Клонирование git репозитория с кодом.
- Настройка переменных окружения и выставления имени сборки/билда.
- Сборка образа docker.
- Загрузка его в репозиторий.
- Деплой.
Создайте через веб интерфейс Jenkins Pipeline: http://localhost:8080/view/All/newJob
Теперь разберем синтаксис pipeline по частям. Рассмотрим обертку и параметры.
Так мы определяем, что pipeline declarative
pipeline {
Задаем слэйв, на котором будет запускаться билд, подробнее можете прочитать здесь: https://jenkins.io/doc/book/pipeline/syntax/#agent
agent {
node {
label "devops"
}
}
Определяем параметры, с которыми будем запускать pipeline. Хочу обратить внимание на то, что, если вы определяете pipeline через Job DSL, то параметры лучше задать там, т.к. при первом запуске параметры, определенные внутри pipeline недоступны. В данном примере мы опрелеляем один параметр типа string с пустым значением по умолчанию. Подробнее здесь https://jenkins.io/doc/book/pipeline/syntax/#parameters
parameters {
string(
name: 'app',
defaultValue: '',
description: 'application name')
}
Опции самой pipeline, по порядку:
- хранить только последние 30 сборок
- если сборка занимает больше часа, то прервать ее и считать неуспешной
- включить метки времени для каждой строки вывода, см. https://wiki.jenkins.io/display/JENKINS/Timestamper
- подсвечивать цветной вывод, см. https://wiki.jenkins.io/display/JENKINS/AnsiColor+Plugin
options {
buildDiscarder(logRotator(numToKeepStr: '30'))
timeout(time: 1, unit: 'HOURS')
timestamps()
ansiColor('xterm')
}
Получаем пользователя и пароль в виде строки, разделенной двоеточием из Jenkins credentials
environment {
ARTIFACTORY = credentials('artifactory')
}
Когда будет запускаться pipeline, в нашем случае тригером будет служить push в master, подробнее здесь https://jenkins.io/doc/book/pipeline/syntax/#triggers Сам репозиторий задается на уровень выше или в Job DSL, или через UI
triggers {
pollSCM('* * * * *')
}
Далее будут идти непосредственно этапы и шаги сборки и деплоя. Stages - необходимый блок, даже если у вас всего один этап и шаг, нужно его обернуть в
stages
stages {
Определяем первый этап сборки
stage('Build docker image')
У нас будет несколько шагов, первым делом клонируем вспомогательный репозиторий с кодом terraform для деплоя
steps {
git([
url: "ssh://git@github.com/myorg/deployment.git",
branch: 'master',
credentialsId: 'github-ssh-key'
])
Определим еще переменных окружения, тут можно развернуться во всю силу groovy
script {
env.registry = "777.dkr.ecr.eu-west-1.amazonaws.com/java"
env.app_name = "${app}".replace("-service", "")
currentBuild.displayName = "#${env.BUILD_NUMBER} - ${app}"
env.artifactory_url = "https://artifactory.local/artifactory"
}
Сборка будет осуществляться посредством bash скрипта, который мы видим ниже. Обратите внимание на двойное экранирование, это требование Jenkins
sh('''#!/bin/bash
echo "Trying to obtain latest version"
VERSION=`curl --connect-timeout 10 -u "\$ARTIFACTORY" "\${artifactory_url}/api/search/latestVersion?a=${app}&repos=libs-snapshot-local&v=*&g=services"` || exit 1
if `echo \$VERSION | grep -q '^{'`; then
echo "version not found"
echo "\$VERSION"
exit 1
else
echo "version: \$VERSION found"
echo \$VERSION > version
fi
echo "Download artifact"
snapshot=`echo \$VERSION | cut -d- -f1`
curl -q --connect-timeout 10 -O -u "\$ARTIFACTORY" "\${artifactory_url}/libs-snapshot-local/services/${app}/\$snapshot-SNAPSHOT/${app}-\$VERSION.jar"
echo "Login to ECR"
eval `aws ecr get-login --no-include-email`
echo "Building docker image"
docker build --pull --force-rm \\
--build-arg app=${app} \\
--build-arg app_name=\$app_name \\
--build-arg version=\$VERSION -t \$registry/\${app_name}:latest .
echo "Pushing docker image"
aws ecr create-repository --repository-name="java/${app_name}" || true
docker tag \$registry/\${app_name}:latest \$registry/\${app_name}:\$VERSION
docker push \$registry/\${app_name}:latest
docker push \$registry/\${app_name}:\$VERSION
echo "Cleanup"
rm -v ${app}-\$VERSION.jar
''')
}
}
Теперь деплой из только что загруженного образа
stage('Terraform deploy') {
steps {
sh('''#!/bin/bash
VERSION=`cat version`
rm -v version
sed -i 's/service/'${app}'/g' backend.conf
[ -d .terraform ] && rm -rf .terraform
[ -f terraform.tfstate.backup ] && rm terraform.tfstate.backup
terraform init -backend-config=backend.conf -force-copy
terraform env select qa || terraform env new qa
terraform plan \\
-var-file=qa.tfvars \\
-var="service_name=\"${app_name}\"" \\
-var="version=\"\$VERSION\"" -out=ecs-deploy.plan
terraform apply ecs-deploy.plan
echo "Cleanup"
git checkout -- .
rm -v ecs-deploy.plan
''')
}
}
Убедимся, что деплой прошел успешно, см. http://docs.aws.amazon.com/cli/latest/reference/ecs/wait/services-stable.html
stage('ECS wait for deploy') {
steps {
sh('''#!/bin/bash
aws ecs wait services-stable --services java-${app_name}
''')
}
}
}
Теперь настроим уведомления, чтоб знать о неудачных сборках. Уведомления будут приходить на почту.
post {
failure {
// отправим последние 50 строк неудачной сборки
script {
env.logs = currentBuild.rawBuild.getLog(50).join('\n')
}
emailext (
subject: "FAILURE: ${env.JOB_NAME} Build # ${env.BUILD_NUMBER} ",
body: """FAILED: Job ${env.JOB_NAME} [${env.BUILD_NUMBER}]':
Check console output at ${env.BUILD_URL}console
...
${env.logs}
""",
to: 'builds@myorg.com,me@myorg.com'
)
}
}
}
Вы увидели пример pipeline, используемого в QA. Конечно, можно еще добавить шаги с выполнениями тестов и т.п. Если вы хотите настроить больше этапов, шагов, уведомлений и действий, обратитесь к документации вашего Jenkins по http://localhost:8080/pipeline-syntax/