Построение Pipeline в Jenkins

Построение CI/CD Pipeline для автоматизации тестирования и деплоя в Jenkins. Сбор артефактов и уведомления по электронной почте.

  • 00:01:05 - Создание Jenkinsfile
  • 00:04:41 - Добавление GitHub-репозитория в Jenkins
  • 00:11:42 - Просмотр multibranch-проекта
  • 00:13:37 - Вывод временных меток в логах
  • 00:16:30 - Валидация Jenkinsfile
  • 00:18:02 - Переменная окружения CI
  • 00:19:39 - Доработка docker-compose pull
  • 00:20:43 - Команды init и down c защитой в post
  • 00:22:33 - Первый и повторный запуск
  • 00:26:01 - Установка git и make на машину-агент
  • 00:27:00 - Сравнение производительности
  • 00:28:25 - Этап valid
  • 00:28:53 - Параллельный запуск линтеров
  • 00:31:36 - Анализаторы и тесты
  • 00:32:44 - Переменная REGISTRY для реестра
  • 00:36:14 - Генерация тегов с номером сборки
  • 00:41:13 - Сборка production образов
  • 00:42:13 - Запуск Smoke и E2E тестов
  • 00:43:08 - Оптимизация числа потоков
  • 00:46:01 - Аутентификация в Docker-реестре и push
  • 00:49:55 - Автодеплой на production-сервер
  • 00:54:46 - Имя пользователя для деплоя
  • 00:55:31 - Отключение интерактивной SSH-проверки
  • 00:56:19 - Генерация SSH-ключа для деплоя
  • 01:04:14 - Подключение через плагин SSH Agent
  • 01:06:58 - Работа со Staging-сервером
  • 01:09:14 - Сохранение артефактов
  • 01:14:38 - Добавление Email-уведомлений
Скрытый контент
Комментарии (84)
Arunas

чудотворный урок, спасибо.

Ответить
Дмитрий Елисеев

Спасибо! Стараемся.

Ответить
fedot

Спасибо, большое тебе Дима, ты конечно мастер. А сколько времени уходит если не секрет, что бы все скрипты заработали? Ведь на поиск правильно работающего в данной конфигурации рецепта, может уйти куча времени, иногда с виду простая вещь может упереться так, что мама не горюй, записал бы может стрим какой отдельно, как что то сложное настраивал, было бы тоже весьма познавательно.

Ответить
Дмитрий Елисеев

С GitHub Actions просидел так три дня. В итоге и запись видео заняла шесть часов, так как каждый прогон Pipeline шёл несколько минут и в случае ошибок после каждого исправления приходилось всё перезапускать.

Ответить
fedot

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

Ответить
Andrey

Дмитрий, спасибо большое за урок! Вопрос, будет ли рассмотрен вариант использоваться докер образа с Git lab или вообще Git lab как инструмента? Там же есть встроенная возможность и репозиторий для docker организовать и ci\cd настраивать...

Ответить
Дмитрий Елисеев

Да, скоро рассмотрим.

Ответить
Yevhenii Lykholai

Огромное спасибо за труд.

Ответить
Merlin

А сколько всего планируется уроков?

Ответить
Дмитрий Елисеев

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

Ответить
Александр

Спасибо!

Ответить
Valentin

Этот урок просто бесценный, спасибо

Ответить
Андрей

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

Ответить
Дмитрий Елисеев

Для этого вместо Docker Compose мы в следующих эпизодах будем использовать Docker Swarm. И он уже задеплоит проект на все подключенные серверы.

Ответить
Андрей

А что если у нас есть shared данные. Например, изображения загруженные пользователями. Понятное дело, что можно заиметь ftp сервер какой-нибудь и грузить все туда. Но если все таки без сервера, т.е. хранить изображения надо где-то в отдельной папке, то как тогда ее монтировать в контейнер? Причем надо не забывать, что у нас может быть staging (или даже несколько staging).

Ответить
Дмитрий Елисеев

Обычно их помещают на отдельный файловый сервер по S3 или FTP.

А если без него, то просто монтируем папку public/upload через volume. И для тестов отдельной командой вроде:

docker run --rm php-cli copy docker/testing/demo/photos public/upload/photos

заполняем тестовыми данными.

Ответить
Андрей

А почему в Jenkinsfile для make deploy мы явно указываем BUILD_NUMBER=${env.BUILD_NUMBER} make deploy? Ведь остальные переменные по типу HOST мы же не указываем....

Ответить
Дмитрий Елисеев

В принципе да. Если имя переменной совпадает, то можно не присваивать повторно и работать сразу с ${BUILD_NUMBER} без env.

Ответить
Александр

Замечательный урок. Спасибо.

Остался только один вопрос как сделать возможность автоматического отката к прошлой рабочей версии или как сделать возможность хранить на сервере файлы допустим только 4-х или пяти предыдущих деплоев что бы откатить вручную и при этом не забивать дисковое пространство?

Ответить
Дмитрий Елисеев

возможность автоматического отката к прошлой рабочей версии

Например, в Prod объявить подсекцию post { failure { ... } } по аналогии со сбором артефактов и там вызвать команду make auto-rollback, которая выполнит перезапуск из прошлой рабочей папки по симлинку cd site && docker-compose up ...

не забивать дисковое пространство

В папке деплоя у нас хранятся только два файла на ~5 КБ. Тысяча таких папок займёт всего 5 МБ.

Ответить
Александр

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

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

Ответить
Дмитрий Елисеев

Тогда можно поробовать деплоить через Deployer или Capistrano. Они как раз будут оставлять только нужное число папок.

Ответить
Анатолий

При сборке не находится sshagent java.lang.NoSuchMethodError: No such DSL method 'sshagent' found among steps Потом все таки досмотрел видео, когда установил модуль сам)

Ответить
Дмитрий

Дима помоги, у меня проблема: в момент docker-build в production/Dockerfile есть строчка COPY out/buildServer/stats.json app/out/buildServer/stats.json

файл stats.json - это результат работы stats-webpack-plugin, и он обязательно появляется, но не успевает появиться до момента COPY

как дождаться либо здесь, внутри Dockerfile, или в Makefile?

Ответить
Дмитрий

первое что на ум приходит

wait-stats:
	sh -c "until [ -f ./out/buildServer/stats.json ] ; do sleep 1 ; done"

в Makefile использовать перед билдом контейнеров

Ответить
Дмитрий

но интересно есть ли подобное решение внутри Dockerfile?

Ответить
Дмитрий Елисеев

Внутри нет. Сборка производится в копии нашей папки, отправленной демону. Там файл не появится.

Ответить
Николай

Дмитрий, добрый день! Во время деплоя jenkins при сборке образа на основе jenkinsci/blueocean не устанавливается docker-compose. Ошибка во время компиляции "Could not build wheels for pynacl which use PEP 517 and cannot be installed directly". Не подскажите, в чем может быть проблема?

Ответить
Дмитрий Елисеев

Увы, но не подскажу.

Ответить
Руслан

RUN apk add --no-cache py-pip musl-dev python3-dev libffi-dev openssl-dev gcc libc-dev rust cargo \

&& pip3 install --upgrade pip \
&& pip3 install docker-compose
Ответить
Николай

И еще одна проблема (уже после установки docker-compose через curl). Во время запуска пайплайна на стадии "Init" возникает ошибка:

console output

  • make init
  • docker-compose down -v --remove-orphans
  • make: docker-compose: Operation not permitted
  • make: *** [Makefile:46: docker-down-clear] Error 127

Не подскажите, в чем может быть проблема?

Ответить
Дмитрий Елисеев

Возможно, что текущему пользователю и файлу docker-compose не проставлена группа docker.

Ответить
Николай

Проблема была с образом jenkinsci/blueocean. Попробовал более ранний образ. Команда make перестала выдавать ошибку только со сборкой на основе jenkinsci/blueocean:1.24.7. А до этого ошибка была и из-под пользователя jenkins (который по умолчанию) и из-под root.

Ответить
Ярослав

Я также попробовал более старый образ, но на локалке контейнер jenkins у меня падает с ошибкой:

java.io.FileNotFoundException: /var/jenkins_home/war/WEB-INF/lib/cli-2.289.1.jar (Permission denied)

У тебя сразу заработало или еще что-то надо было проставить?

Ответить
Николай

У меня ошибка была именно на проде, на локалке все было нормально. Дополнительно ничего не делал, только образ сменил на 1.24.7. Возможно, ошибка из-за

volumes:
 - jenkins-data:/var/jenkins_home

Можно найти нужный вольюм (docker volume ls) и удалить (docker volume rm volume_name), чтобы он пересоздался при рестарте docker-compose. Но тогда слетят настройки jenkins (доступ к репозиторию, пароли ключи и т.д.), будет полностью переустановленный jenkins. Возможно, потребуется пересобрать сам образ на основе jenkinsci/blueocean

Ответить
Ярослав

Спасибо за ответ. Настройки не проблема восстановить, так как я уже устанавливал и настраивал его на разные сервера. Рука и голова набиты:))

Ответить
slo_nik

Добрый вечер, Дмитрий.

При выполнении шага "make init" возникают такие ошибки:

ERROR: for mailer  Get "registry-1.docker.io/v2/": net/http: TLS handshake timeout
error pulling image configuration: Get "registry-1.docker.io/v2/library/node/blobs/sha256:18f4bc97573275625b5337c93ac43f9b920a09fb4cb030e25a0c0ff42dd6b3dc": net/http: TLS handshake timeout

Если выполнить curl запрос к этим адресам, или перейти в браузере, то в ответ получаю следующее сообщение:

{"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":[{"Type":"repository","Class":"","Name":"library/node","Action":"pull"}]}]}

Как победить данную проблему?

Ответить
slo_nik

При выполнении шага "make init" возникают такие ошибки:

Добавил для jenkins playbook docker-login

Предыдущая ошибка ушла, но появилась новая, ругается на docker.dind

HTTPSConnectionPool(host='docker', port=2376): Read timed out. (read timeout=60)
Ответить
slo_nik

В обшем ничего не получилось)))

Рекомендации из интернета не помогли, пробовал увеличить лимит по выполнению сборки через COMPOSE_HTTP_TIMEOUT: 120 и DOCKER_CLIENT_TIMEOUT: 120, все сборки завершались ошибкой

HTTPSConnectionPool(host='docker', port=2376): Read timed out. (read timeout=60)

Взял ещё один свой проект, там поменьше сервисов собирается в docker-compose-prod.yml

Первая сборка прошла успешно, а вот вторая вывалилась всё с теме же ошибками:

ERROR: for support_dev_support-php-fpm_1  HTTPSConnectionPool(host='docker', port=2376): Read timed out. (read timeout=60)
ERROR: for support_dev_support-php-cli_1  HTTPSConnectionPool(host='docker', port=2376): Read timed out. (read timeout=60)
ERROR: for support-php-fpm  HTTPSConnectionPool(host='docker', port=2376): Read timed out. (read timeout=60)
ERROR: for support-php-cli  HTTPSConnectionPool(host='docker', port=2376): Read timed out. (read timeout=60)
An HTTP request took too long to complete. Retry with --verbose to obtain debug information.
If you encounter this issue regularly because of slow network conditions, consider setting COMPOSE_HTTP_TIMEOUT to a higher value (current value: 60).

Всё-таки, из-за чего это зависит и как это победить?

Ответить
Дмитрий Елисеев

Может сервисы отваливаются из-за недостатка памяти. Увеличьте размер виртуальной машины.

Ответить
slo_nik

Думал об этом, но проблема оказалась совершенно в другом.

Эти ошибки возникали при попытке dind-ом создать контейнеры. Но в самом коде были проблемы, на уровне php, поэтому dind не мог создать контейнер и не отвечал jenkins-у.

Плохо, что не очень информативные ошибки в логах jenkins. Пытался в лохаг dind смотреть, тоже, ничего не находил.

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

Ответить
slo_nik

ERROR: for mailer Get "registry-1.docker.io/v2/": net/http: TLS handshake timeout

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

Ответить
Дмитрий Елисеев

Одновременно на одной виртуалке лучше не запускать. Выставить максимальное число 1.

Ответить
slo_nik

Только что проверил, моя ошибка, было выставлено "2". Переустанавливал виртуалку и забыл изменить этот параметр.

Ответить
slo_nik

Добрый день.

Если выставить в настройка jenkins-a число 1 для обрабатываемых задач и при этом сделать подряд два commit-a, то всё равно запускается две задачи.

Можно это как-то победить?

Ответить
Дмитрий Елисеев

Если стоит 1, то вторая задача будет ждать в очереди.

Ответить
slo_nik

Опять же, возвращаясь к слабым виртуальным машинам.

Можно ли как-то освобождать swap по завершению сборки, то есть, дописать в Jenkinsfile соответствующую команду? И будет ли это эфективно для слабых машин?

Ответить
Дмитрий Елисеев

В этом нет смысла.

Ответить
Андрей

А у меня такая проблема. Jenkins сканирует github репозиторий раз в минуту и смотрит есть ли изменения. Но бывает, что он запускает пайплан даже если изменений никаких не было. И происходит это не систематизировано, может днем запустить, может ночью. Может неделю все тихо быть, а потом бац - билд пошел. Не сталкивались с таким?

Ответить
Дмитрий Елисеев

Сталкивался. Но проблемы в этом не вижу.

Ответить
Konstantin

добрый день! пушу в master, но почему то jenkins игнорирует эту стадию. в логах: Stage "Push" skipped due to when conditional. может надо прописать branch 'origin/master'?

stage('Push') {
    when {
         branch 'master'
    }
    steps {
        withCredentials([
            usernamePassword(
                credentialsId: 'REGISTRY_AUTH',
                usernameVariable: 'USER',
                passwordVariable: 'PASSWORD'
            )
        ]) {
            sh 'docker login -u=$USER -p=$PASSWORD $REGISTRY'
        }
        sh 'make push'
    }
}
Ответить
Konstantin

GIT_BRANCH=origin/master

Ответить
Konstantin

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

Ответить
slo_nik

Добрый вечер, Дмитрий.

Столкнулся с проблемой на самом первом этапе сборки в jenkins "Declarative: Checkout SCM".

Вываливалась ошибка

jenkins.util.io.CompositeIOException: Unable to delete '/var/jenkins_home/workspace/Project_dev'. Tried 3 times (of a maximum of 3) waiting 0.1 sec between attempts. (Discarded 41009 additional exceptions)

И далее в логе

Suppressed: java.nio.file.FileSystemException: /var/jenkins_home/workspace/Project_dev/pohoron/node_modules/color-name/package.json: Operation not permitted

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

Например, на директорию node_modules права drwxr-sr-x 738 root jenkins

И начались эти проблемы после простоя около двух месяцев, в этот промежуток времени ничего не делалось в проекте и не выгружалось.

Проблему решил путём очистки директории проекта в volume /var/jenkins_home/workspace/Project_dev

Но, как я понимаю, это временное решение.

Из-за чего могла случиться такая ситуация, как правильно решать подобные проблемы?

Ответить
Дмитрий Елисеев

Именно из-за того, что Jenkins запущен от пользователя jenkins, а контейнер node-cli внутри запускается от root. Поэтому Jenkins не может удалить папку node_modules.

Решается либо ручным удалением папки проекта из workspace в /var/lib/docker/volumes/jenkins_data/_data/workspace.

Либо в Dockerfile у node-cli можно переключить пользователя:

USER node

Либо не обращать внимания и удалять вручную, так как эта проблема встречается редко.

Ответить
slo_nik

Такая же ситуация с директориями var, vendor и файлом .ready.

До этой проблемы более 20-ти сборок прошли успешно, workspace проекта очищался успешно, а в последних вдруг перестало работать.

Остальные файлы проекта создаются от jenkins.

Добавить нужно не только USER node, как я понимаю?

Ответить
Дмитрий Елисеев

workspace проекта очищался успешно

А зачем его очищать перед каждой сборкой? Это только замедляет выполнение pipeline, не принося особой пользы.

Ответить
slo_nik

Вот это мне и непонятно)))

Пока вручную не очистил сборка завершалась ошибкой на самом начальном этапе.

Почему до этого всё работало, а потом вдруг перестало?

Ответить
Дмитрий Елисеев

Видимо он почему-то захотел удалить папку из workspace.

Чаще с ним бывает то, что рядом с workspace/project он создаёт project-2. Тогда тоже желательно workspace вычистить.

Ответить
slo_nik

Нет, второй директории не было.

Я вчера искал информацию, но то что я нашёл, всё было связано с ручным изменением прав на директорию workspace проекта.

Поэтому очистил директорию проекта, но саму директорию /var/jenkins_home/workspace/Project_dev оставил без изменений.

Ответить
slo_nik

В workspace проекта при новой сборке файле перезаписываются?

Значит до возникновения ошибки права были установлены такие же

drwxr-sr-x 738 root jenkins на var, vendor, node_modules, public/build.

Сейчас запустил ещё раз сборку, начальный этап "Declarative: Checkout SCM" прошёл успешно, права на указанные директории такие же drwxr-sr-x 738 root jenkins.

Может быть из-за того, что в течении двух месяцев не запускались сборки и что-то не так сохранилось в cache jenkins?

Ответить
Дмитрий Елисеев

Возможно, что после какого-то перерыва он вычищает workspace. У меня сборки идут каждый день без, поэтому такого не случалось.

Ответить
slo_nik

В том то и дело, что директория была с файлами от старых сборок.

Ответить
slo_nik

Вернее некоторые файлы были удалены из workspace проекта, а директории var, vendor, public/node_modules остались, jenkins не смог их удалить.

Ответить
slo_nik

Если создавать отдельного пользователя для node, то и для composer так же надо создать.

Или достаточно будет создать одного пользователя и для node и для composer?

Ответить
Дмитрий Елисеев

Для php-cli мы в 59-ом эпизоде создали пользователя app с тем же идентификатором 1000, как у node и jenkins. И команду ready тогда можно будет запускать из php-cli как:

docker-compose run --rm php-cli touch .ready

Но всё равно проблемы останутся с правами на файлы в var от php-fpm, у которого встроенный пользователь www-data создан с идентификатором 82 вместо 1000. Можно попробовать заменить его идентификатор через команды groupmod и usermod в Dockerfile.

Ответить
slo_nik

Этот момент я не реализовывал в своём проекте, пока не было необходимости.

Теперь пришло время.

Но всё-таки остаётся загадкой, почему вдруг jenkins решил, что ему не хватает прав.

Возможно из-за версии самого jenkins на сервере?

Ответить
Дмитрий Елисеев

Если за это время обновляли Jenkins, то может из-за этого он вдруг решил удалить старый workspace.

Ответить
slo_nik

Не обновлял.

Ответить
slo_nik

Сейчас использую jenkins/blueocean1.24.7

При обновлении не слетят настройки pipelines?

Ответить
Дмитрий Елисеев

Не слетят.

Ответить
slo_nik

Благодарю.

Ответить
slo_nik

Оказывается слетает всё)))

Обновил с enkinsci/blueocean:1.24.7 до enkinsci/blueocean:1.25.2 и в результате получил ошибку в браузере.

java.lang.OutOfMemoryError: Java heap space
Caused: hudson.util.HudsonFailedToLoad
at hudson.WebAppMain$3.run(WebAppMain.java:312)

В логах

WARNING h.ExpressionFactory2$JexlExpression#evaluate: Caught exception evaluating: h.isUserTimeZoneOverride() in /adjuncts/31d7f4b5/lib/layout/breadcrumbs.js. Reason: java.lang.IllegalStateException: Expected 1 instance of hudson.model.User$AllUsers but got 0

WARNING h.s.HttpSessionContextIntegrationFilter2#hasInvalidSessionSeed: Encountered IllegalStateException trying to get a user. System init may not have completed yet. Invalidating user session.
Ответить
slo_nik

Посмотрел внимательней лог ошибок контейнера jenkins.

Много ошибок при попытке загрузить плагины.

Пример

SEVERE	jenkins.InitReactorRunner$1#onTaskFailed: Failed Loading plugin Blue Ocean v1.25.2 (blueocean)
java.io.IOException: Failed to load: Blue Ocean (1.25.2)

Как можно решить подобную проблему?

В сети рекомендуют попробовать удалить содержимое директории /var/jenkins_home/plugins, но тогда, как я понимаю, слетят все настройки.

Перед обновлением образа jenkins делал обновление пакетов через ansible,

Ответить
slo_nik

В общем непонятная штука получилась.

Для проверки создал ещё одну виртуалку, где пытался воспроизвести проблему, ни так и не смог.

В общем удалил в контейнере директорию /var/jenkins_home/plugins и смог запустить jenkins c новой версией 1.25.2

Ответить
slo_nik

И ещё вопрос.

Когда заглянул в volume jenkins-a, то увидел директорию проекта, который давно удалён из pipeline, никаких images от него не осталось.

Должны ли из volume автоматически удаляться проекты из /var/jenkins_home при удалении проекта из pipeline?

Ответить
Дмитрий Елисеев

Вроде остаются.

Ответить
slo_nik

Можно ли как-то настроить автоматическое удаление ненужных директорий?

Ответить
Дмитрий Елисеев

Не смотрел.

Ответить
Pavel N

Добрый день, подскажите как бороться с переполнением диска в jenkins.

docker system prune -af --filter until=240h не помогает.

Запуск в ручную docker system prune -a - выдает Total reclaimed space: 0B. overlay2 - 57Gb.

Ответить
Дмитрий Елисеев

Чистить внутренний докер, а не только внешний:

docker-compose exec docker docker system prune -af --filter until=240h
Ответить
Pavel N

Благодарю.

Ответить
Сергей

Спасибо за урок! Не понимаю как запускать деплой повторно. Первый прошел успешно, все контейнеры поднялись, сайт запускается. При следующих деплоях вываливается ошибка, что порт уже занят и контейнер nginx не поднимается.

ERROR: for manager-nginx  Cannot start service manager-nginx: driver failed programming external connectivity on endpoint site_256_manager-nginx_1 (c95b2e8ad278d66fd16c****ed7b132c42accfb67754****7279b662b9****1****e5cae3****5a): Bind for ****.****.****.****:443 failed: port is already allocated 

Что я делаю не так? Это конечно решится добавлением docker-compose down перед build прошлой версии сборки, но у вас я этого не вижу. Или вы ручками на сервере запускаете docker-compose down а потом деплоите?

И еще второй вопрос как быть с volume для postgres? Каждый раз после деплоя создается новый volume с новой версией , и соответсвтенно пустой контент на сайте после деплоя. Например было название volume site_253_manager-postgres стал site_254_manager-postgres.

Ответить
Сергей

Сам разобрался. Для этого в папку, где лежит docker-compsoe.yml нужно положить env COMPOSE_PROJECT_NAME=name , где name указать алиас, который будет добавляться ко всем названиям контейнеров volume и тд. Тогда не важно как будет называться папка, со всех будет запускаться как один проект.

Ответить
Зарегистрируйтесь или войдите чтобы оставить комментарий

Или войти через:

Google
GitHub
Yandex
MailRu