- PVSM.RU - https://www.pvsm.ru -
Утилита werf [1] создана так, чтобы её было легко интегрировать с любыми CI/CD-системами. Подробнее об этом процессе в общем случае читайте в эпилоге этой статьи, но основное её содержимое — практический пример по организации CI в Jenkins и Bitbucket.
Подразумевается, что в результате наших действий мы ожидаем получить следующее:
Поехали!
Для реализации задуманного в статье будут задействованы:
В Jenkins для проектов используется multibranch pipeline.
Начнем с того, что подключим к Jenkins репозиторий, в котором будет храниться наша Shared Library [4]. Shared Library — это единая библиотека, что может содержать в себе код для исполнения CI и хранится отдельно в своем собственном репозитории. Это значительно упрощает процесс модернизации и работы над CI (вместо использования для хранения CI стандартного Jenkinsfile
, который нужно подкладывать в каждый проект).
Итак, подключаем: Manage Jenkins → Configure System → Global Pipeline Libraries.
Нужно указать имя, ветвь репозитория, из которой Jenkins будет забирать код библиотеки, а в Source Code Management указать адрес и доступ до репозитория (в нашем случае — SSH-ключ для доступа ReadOnly).
Теперь приступим к описанию самой библиотеки. Структура очень проста и состоит всего из трёх директорий:
vars
— директория для глобальных методов библиотеки, что будут вызываться из пайплайна;src
— тоже директория для скриптов, но в основном используется для вашего кастомного кода;resources
— всё, что не является скриптом и может понадобиться в исполнении.
Для наших целей в Jenkins будет достаточно только нескольких методов в директории vars
, потому как мы настроим сам werf, что и сделает всю основную работу.
К тому же, хотелось бы, чтобы весь пайплайн был полностью описан внутри библиотеки, а в Jenkinsfile
мы передавали только некоторые параметры деплоя, которые в 99,9% случаев вообще не будут меняться.
Итак, реализуем 2 метода.
Для вызова утилиты werf — -runWerf.groovy
.
#!/usr/bin/env groovy
def call(String dockerCreds, String werfargs){
// логин в registry
// первый аргумент - url (пуст, т.к. используем DockerHub)
// второй - имя Jenkins-секрета, где лежат доступы (login, password)
docker.withRegistry("", "${dockerCreds}") {
sh """#!/bin/bash -el
set -o pipefail
type multiwerf && source <(multiwerf use 1.1 stable --as-file)
werf version
werf ${werfargs}""".trim()
}
}
Все параметры в библиотеку для пайплайна передаются как Map
, что удобно:
#!/usr/bin/env groovy
def call( Map parameters = [:] ) { // функция принимает в качестве аргумента Map с параметрами
def namespace = parameters.namespace // имя неймспейса для выката
// имя ключа по умолчанию для расшифровки секретов (если не указан в параметрах)
def werf_secret_key = parameters.werfCreds != null ? parameters.werfCreds : "werf-secret-key-default"
// имя секрета по умолчанию для логина в docker registry
def dockerCreds = parameters.dockerCreds != null ? parameters.dockerCreds : "docker-credentials-default"
// получаем имя проекта из имени multibranch pipeline
def PROJ_NAME = "${env.JOB_NAME}".split('/').first()
// имя registry в docker hub или адрес до кастомного registry
def imagesRepo = parameters.imagesRepo != null ? parameters.imagesRepo : "myrepo"
if( namespace == null ) { // единственный обязательный аргумент и проверка на его наличие
currentBuild.result = 'FAILED'
return
}
pipeline {
agent { label 'werf' }
options { disableConcurrentBuilds() } // запрещаем параллельную сборку для пайплайна
environment { // переменные для работы werf
WERF_IMAGES_REPO="${imagesRepo}"
WERF_STAGES_STORAGE=":local"
WERF_TAG_BY_STAGES_SIGNATURE=true
WERF_ADD_ANNOTATION_PROJECT_GIT="project.werf.io/git=${GIT_URL}"
WERF_ADD_ANNOTATION_CI_COMMIT="ci.werf.io/commit=${GIT_COMMIT}"
WERF_LOG_COLOR_MODE="off"
WERF_LOG_PROJECT_DIR=1
WERF_ENABLE_PROCESS_EXTERMINATOR=1
WERF_LOG_TERMINAL_WIDTH=95
PATH="$PATH:$HOME/bin"
WERF_KUBECONFIG="$HOME/.kube/config"
WERF_SECRET_KEY = credentials("${werf_secret_key}")
}
triggers {
// Execute weekdays every four hours starting at minute 0
cron('H 21 * * *')
// для werf cleanup, что будет чистить registry и хост-раннер от устаревших кэшей и образов
}
stages {
stage('Checkout') {
steps {
checkout scm // получаем код из репозитория
}
}
stage('Build & Publish image') {
when {
not { triggeredBy 'TimerTrigger' } // чтобы stage не запускался по крону
}
steps {
script {
// запуск нашего метода из runWerf.groovy
runWerf("${dockerCreds}","build-and-publish")
}
}
}
stage('Deploy app') {
when {
not { triggeredBy 'TimerTrigger' }
}
environment {
// название окружения, куда осуществляется деплой (важно для шаблонизации Helm-чарта)
WERF_ENV="production"
}
steps {
runWerf("${dockerCreds}","deploy --stages-storage :local --images-repo ${imagesRepo}")
}
}
stage('Cleanup werf Images') {
when {
allOf {
triggeredBy 'TimerTrigger'
branch 'master'
}
}
steps {
sh "echo 'Cleaning up werf images'"
runWerf("${dockerCreds}","cleanup --stages-storage :local --images-repo ${imagesRepo}")
}
}
}
}
}
Примечания:
discover
у Jenkins. После наших манипуляций в следующей главе это будет происходить автоматически.werf-secret-key-default
и docker-credential-default
, хранятся в Jenkins Credentials:
Сам Jenkinsfile
, что находится внутри репозитория с проектом, в большинстве случаев теперь выглядит так:
@Library('common-ci') _
multiStage ([
namespace: 'yournamespace'
])
Имя метода — это название файла в каталоге vars
.
Если необходимо выкатывать на несколько окружений, можно добавить условие для определенных веток в самом начале, где идет определение пространства имен. И убрать проверку на наличие аргумента namespace в Map
, а также само его определение по умолчанию.
Пример реализации:
def namespace = "test"
def werf_env = "test"
if (env.JOB_BASE_NAME == 'master') {
namespace = "stage"
werf_env = "stage"
}
if (env.TAG_NAME) {
namespace = "production"
werf_env = "production"
}
# и добавляем в environment стадии
environment {
WERF_ENV="${werf_env}"
}
Если вы хотите автоматический запуск stage со всех веток, а с тегов в production — только при нажатии кнопки в Jenkins, то можно использовать такое условие: currentBuild.rawBuild.getCauses()[0].toString().contains('UserIdCause')
. Оно позволяет отследить, сборка была запущена человеком или началась как событие от webhook'а.
По умолчанию Jenkins сам не умеет интегрироваться в Bitbucket. Для этого нужно установить уже упомянутые плагины:
Если вы используете cloud-версию Bitbucket, то нужно только поставить разрешение на создание webhook'ов автоматически.
Также требуется создать служебного пользователя с доступом к репозиториям, т.к. Jenkins будет обнаруживать весь репозиторий через API. Это касается настройки как для cloud-версии, так и для собственного Bitbucket-сервера.
Пример из глобальных настроек Jenkins:
Далее понадобится настроить source
в Multibranch Pipeline, что происходит в интерактивном режиме. Это означает, что, когда вы добавите credentials bitbucket пользователя и имя команды или пользователя с проектами, которых мы будем работать, Jenkins найдет все доступные пользователю репозитории и позволит выбрать один из списка.
В самом репозитории мы полагались на поиск только определенных веток, т.к. не уверены, как много веток может быть, а Jenkins может надолго «задуматься», если начнет исследовать каждую ветку. Это накладывает определенные ограничения, т.к. теги тоже попадают под регулярное выражение. Однако Java Regular Expressions — довольно гибкие, так что большой проблемы нет.
Альтернативный путь: если есть желание совсем отделить теги от веток, можно добавить еще один абсолютно такой же Source
в репозиторий и настроить его только на обнаружение тегов.
Итак, конфигурация:
После этого Jenkins с помощью сервис-аккаунта сам сходит в Bitbucket и создаст webhook:
Теперь при каждом коммите Bitbucket будет триггерить пайплайны (но только для тех веток и тегов, что мы отфильтровали) и даже посылать статус пайплайна обратно в Bitbucket в последнем столбце коммита:
Статусы — кликабельные: при нажатии перекидывают в нужный пайплайн в Jenkins
Последний штрих — про Jenkins, который находится за nginx proxy и работает с определенного location. Тогда нужно в основных настройках исправить его location, чтобы он сам знал, как выглядит его endpoint:
Без этого ссылки на pipeline в Bitbucket будут генерироваться некорректно.
В статье рассмотрен вариант настройки CI с использованием Jenkins, Bitbucket и werf. Это очень общий пример, который не является панацеей для организации процесса разработки, однако даёт представление о том, как вообще подойти к построению своего CI с использованием werf.
Важная деталь: даже учитывая, что статус пайплайна отдается в Bitbucket, нам всё равно приходится «ходить» в Jenkins, чтобы разобрать результат в случае неудачи. Выкат по тегу через webhook, очевидно, может отрабатывать только один раз: любой откат на предыдущий тег придется делать вручную из Jenkins.
У данного подхода также есть большой плюс — это гибкость. Мы буквально можем прописать в CI всё что угодно. Хотя и порог вхождения для того, чтобы понимать, как именно это сделать, чуть выше, чем у других CI-систем.
Общий подход к интеграции werf с CI/CD-системами описан в документации [5]. Вкратце рекомендуемые для любых проектов шаги сводятся к следующим:
DOCKER_CONFIG
для исключения конфликтов между параллельными job'ами на одном runner'е (подробнее здесь [6]).WERF_IMAGES_REPO
, WERF_STAGES_STORAGE
, а также необходимых параметров, которые варьируются в зависимости от имплементации [9]. Утилита werf должна знать, с какой реализацией работает, так как часть требует использования нативного API. Стоит отметить, что по умолчанию werf пытается определить, с какой имплементацией работает, исходя из адреса registry, но это задача часто невыполнима (и тогда требует явного указания имплементации).WERF_TAG_*
: используя переменные окружения CI, определяем, чем инициирован текущий job, и выбираем подходящую опцию тегирования или всегда используем content-based тегирование (рекомендованный путь).WERF_ADD_ANNOTATION_*
. Среди этих аннотаций могут быть произвольные данные, которые помогут вам работать и отлаживать ресурсы приложения в Kubernetes. Мы пришли к тому, что все ресурсы должны содержать следующий набор:
WERF_ADD_ANNOTATION_PROJECT_GIT
— адрес проекта в Git;WERF_ADD_ANNOTATION_CI_COMMIT
— коммит, соответствующий выкату;WERF_ADD_ANNOTATION_JOB
или WERF_ADD_ANNOTATION_PIPELINE
— адрес job или pipeline (зависит от CI-системы и желания), который связан с выкатом.WERF_LOG_COLOR_MODE=on
— включение цветного вывода (werf запускается не в интерактивном терминале, по умолчанию цвета отключены);WERF_LOG_PROJECT_DIR=1
— вывод полного пути директории проекта;WERF_LOG_TERMINAL_WIDTH=95
— установка ширины вывода (werf запускается не в интерактивном терминале, по умолчанию ширина равна 140).
За время применения werf в большом количестве проектов у нас сформировался набор решений, который унифицирует конфигурацию, решает общие проблемы и делает сопровождение проще и нагляднее. В настоящий момент все описанные выше шаги с учетом этих решений уже встроены в команду werf ci-env
для GitLab CI/CD [11] и GitHub Actions [12]. Пользователям других CI-систем необходимо реализовывать аналогичные действия самостоятельно — подобно тому, как описано в этой статье для примера с Jenkins.
Читайте также в нашем блоге:
Автор: Andrey Koregin
Источник [16]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/jenkins/359866
Ссылки в тексте:
[1] werf: https://ru.werf.io/
[2] Basic Branch Build Strategies Plugin: https://plugins.jenkins.io/basic-branch-build-strategies
[3] Bitbucket Branch Source Plugin: https://plugins.jenkins.io/cloudbees-bitbucket-branch-source
[4] Shared Library: https://www.jenkins.io/doc/book/pipeline/shared-libraries/
[5] документации: https://ru.werf.io/documentation/using_with_ci_cd_systems.html
[6] здесь: https://ru.werf.io/documentation/reference/working_with_docker_registries.html#%D0%B0%D0%B2%D1%82%D0%BE%D1%80%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F-docker
[7] GitLab Container Registry: https://docs.gitlab.com/ee/user/packages/container_registry/
[8] GitHub Docker Package: https://github.com/features/packages
[9] варьируются в зависимости от имплементации: https://ru.werf.io/documentation/reference/working_with_docker_registries.html
[10] environment в GitLab: https://docs.gitlab.com/ee/ci/environments/
[11] GitLab CI/CD: https://ru.werf.io/documentation/advanced/ci_cd/gitlab_ci_cd.html
[12] GitHub Actions: https://ru.werf.io/documentation/advanced/ci_cd/github_actions.html
[13] GitLab CI для непрерывной интеграции и доставки в production. Часть 1: наш пайплайн: https://habr.com/ru/company/flant/blog/332712/
[14] Организация распределенного CI/CD с помощью werf: https://habr.com/ru/company/flant/blog/504390/
[15] Запускаем тесты на GitLab Runner с werf — на примере SonarQube: https://habr.com/ru/company/flant/blog/526702/
[16] Источник: https://habr.com/ru/post/529750/?utm_source=habrahabr&utm_medium=rss&utm_campaign=529750
Нажмите здесь для печати.