Jenkins: создание Continuous Integration/Continuous delivery процесса в Jenkins для проекта Java с использованием Docker, Docker Compose, Ansible

Пример создания Continuous Integration/Continuous Delivery процесса для проекта, написанного на Java, c использованием Gradle, в качестве инструмента сборки Java, Docker, Docker-compose в качестве контейнеризации приложения, Ansible в качестве системы управления/настройки staging-сервера и запуска docker-compose-файла
Реализация этой связки описана в книге Сontinuous delivery with Docker and Jenkins by Rafal Leszko

Алгоритм действий:

1.Создание Java-проекта, который будет собираться Gradle-ом
2.Создание Unit-теста
3.Анализ покрытия кода тестами и публикация результатов отчета
4.Создание Docker-образа c созданным jar-файлом и загрузка его на Docker-репозитарий(на основе Nexus Sonatype)
5.Создание простого acceptance-теста для тестирования приложения на staging-сервере
6.Добавление использования Docker-compose в Jenkins-pipeline для запуска нескольких контейнеров
a) выполнение/запуск acceptance-тестирования непосредственно с staging-сервера
b) выполнение/запуск acceptance-тестирования с отдельного docker-контейнера
7.Добавление использования Ansible в Jenkins-pipeline для автоматической установки docker,docker-compose и запуска docker-compose.yaml файла на staging-сервере
8.Версионирование собранных Docker-образов

 

1.Создание Java-проекта, который будет собираться Gradle-ом
https://start.spring.io/

Скачиваем полученный zip-архив и распаковываем его содержимое в репозитарий
https://bitbucket.org/mybitbucketuser/calculator/src/master/

Также сделаем копию каталога-репозитария и удалим оттуда .git и .gitignore для ручного выполнения gradle-команд с целью проверки корректности их выполнения

Проверяем корректность компилляции

 

2.Создание Unit-теста
А) добавление/создание бизнес-логики

Проверяем работу бизнес-логики

В браузере набираем

Либо

Страница должна отдать 3

Б) Создание Unit-теста

Копируем созданные нами файлы с тестового каталога(/root/git/calculator-cli) в каталог, который версионирован(под контролем) git(/root/git/calculator)

Коммитим изменения в репозитарий и запускам pipeline-сборку

 

3.Анализ покрытия тестами кода и публикация результатов отчета

Например, добавим требование на  минимальное покрытие тестами кода — не менее 20%, чтобы сборка не считалась проваленной.

Проверим уровень покрытия тестами кода

Генерация отчета о покрытия тестами

Отчеты генерируются в файл

Копируем созданный нами файл с тестового каталога(/root/git/calculator-cli) в каталог, который версионирован(под контролем) git(/root/git/calculator)

Добавляем покрытие кода в pipeline

Публикация результатов отчета покрытия кода(требуется предварительная установка плагина HTML Publisher)

Коммитим изменения в репозитарий и запускам pipeline-сборку

 

4.Создание Docker-образа c созданным jar-файлом и загрузка его на Docker-репозитарий(на основе Nexus Sonatype)

Создание Credentials с логином/паролем для доступа к удаленному Docker-репозитарию

Сборка Docker-образа и его загрузка в Docker-репозитарий(на основе Nexus Sonatype) будет производиться на основе Jenkins-slave/agent, в качестве которого выступает standalone Linux сервер(на нем предварительно необходимо установить Docker)
Для того,чтобы jenkins-пользователь, под которым Jenkins-мастер подключается к Jenkins-slave и выполняет весь цикл pipeline имел доступ к Docker-сокету, необходимо включить пользователя jenkins в группу docker на jenkins-slave сервере
Проверка прав и владельца/группы docker-сокета

Добавление пользователя jenkins в группу docker на Jenkins-slave сервере

Перед добавление команд в pipeline проверим их через командную строку вручную

Создние jar-пакета

Создание Docker-файла, с помощью которого собирается Docker-образ, в котором запускается созданный на предыдущем шаге jar-файл

Сборка Docker-образа

Запускаем docker-контейнер из созданного на предыдущем шаге образа и проверка в браузере корректного ответа http://myjenkinsslaveservername:8765/sum?a=1&b=2

Если в браузере получаем ответ 3, значит приложение корректно запустилось в контейнере

Копируем в основной репозитарий в корень файл Dockerfile

Коммитим изменения и загружаем их в удаленный репозитарий

Добавляем в pipeline команды по сборке jar-пакета(stage «Package») создания docker-образа(«Docker build»), загрузки собранного docker-образа на удаленный Docker-репозитарий(stage»Docker push»), запуск контейнера из собранного образа (stage «Deploy to staging»)

В нашем примере staging-сервер(на котором выполняются стадии Continuous Delivery) и сервер, на котором собирается проект и создается контейнер(т.е. выполняются стадии Continuous Integration) один и тот же(например, это standalone Linux-сервер на котором установлен Docker, этот сервер выступает в качестве Jenkins-slave ноды/агента
Если CI выполняется на одном сервере, а CD – на другом(как показано на изображении ниже, то перед стадией stage(«Deploy to staging») добавляем ноду/агента, на котором необходимо выполнять стадии CD
Например, где ‘jenkins-slave-linux’ – метка Jenkins-slave-сервера, на котором необходимо выполнить все последующие стадии CD начиная со стадии stage(«Deploy to staging»)

Запускаем pipeline-сборку и проверяем доступность раздеплоенного приложения

 

5.Создание простого acceptance-теста для тестирования приложения на staging-сервере

В корне репозитария создаем файл acceptance_test.sh, который будет выполнять acceptance-тест
Например, проверка вывода команды сложения двух чисел(1 и 2)

Выставляем бит исполнения на скрипт

Коммитим изменения и загружаем их в удаленный репозитарий

Добавляем в pipeline команды, которые выполняют простой acceptance test(stage «Acceptance test»), и очищают stage-окружение путем остановки докер-контейнера, который использовался для acceptance-тестов (стадия post)
Т.к. на стадии Deploy to staging Docker-контейнер был запущен с опцией —rm, то при остановке Docker-контейнера, он будет автоматически удален.
Это позволит не хранить остановленые докер-контейнеры на staging-окружении и тем самым не занимать дисковое пространство staging-сервера
Т.к. в секции post используется опция always, то команда(по остановке docker-контейнера) будет выполнена всегда, независимо от того, успешно ли завершилась ранее запущенный stage
На стадии Acceptance test выдерживается пауза в 15 секунд(в виде опции sleep), необходимая для запуска контейнера, на котором будет запущен acceptance-тест

Общий pipeline имеет вид

Выполняем сборку и проверяем успешность выполнения всех стадий pipeline

 

6.Добавление Docker-compose в Jenkins-pipeline

В реальной жизни приложения часто использует несколько зависимостей для своей работы(базы данных, кеширование, сервера очередей и т.д.)
Здесь на помощь приходит Docker Compose, управляющий много-контейнерными приложениями.
Docker Compose обеспечивает зависимости между контейнерам т.е. он связывает
один контейнер с другим благодаря тому, что контейнеры находятся в одной и той же сети и видны друг для друга по сети.
Установим docker-compose на staging-сервер( на котором будут запускаться несколько контейнеров и производиться acceptance-тестирование)
В данном случае в качестве staging-сервера используется все тот же Jenkins-slave-сервер, на котором и происходит все стадии CI/CD

Установка docker-compose
Последняя версия доступна здесь
https://github.com/docker/compose/releases

Установка docker-compose автодополнения команд

https://docs.docker.com/compose/completion/#install-command-completion

Например, приложения calculator использует Redis для кеширования, Redis — отдельный контейнер который будет запускаться через docker-compose

a) выполнение/запуск acceptance-тестирования непосредственно с staging-сервера
Acceptance-тестирование можно провести двумя способами
Bash-скрипт, с помощью которого выполняется acceptance-тестирование можно запускать непосредственно с сервера, на котором запускаются контейнеры с приложением(например, это может быть Jenkins-slave(или Jenkins-master,если не используются Jenkins slave/agent))
Аналогично тому, как мы запускали acceptance-тестирования, когда использовали только один контейнер приложением (без зависимостей типа Redis), только вместо doсker-команд для работы с docker-контейнерами будет использоваться docker-compose

Создадим docker-compose.yaml файл в корне репозитария

Чтобы иметь возможность масштабировать приложения путем запуска несколько экземпляров docker-контейнеров с приложением мы преднамеренно не указывем порт открываемый на хосте, который должен выставляться/прослушиваться для проброса на порт в контейнере(8080)
Docker самостоятельно выберет случайный свободный порт на хосте для этого и при необходимости запуска более одного экземпляра Docker-контейнера с приложением мы не получим ошибку, что порт на хосту уже занят(первым контейнером)

Поэтому отредактируем скрипт, в котором происходит acceptance-тестирование
Определим номер динамически выбранного Docker-ом порта для приложения и используем этот порт в проверке curl

Коммитим изменения в репозитарий

Сделаем изменения в pipeline
Используем docker-compose вместо docker в двух местах

При деплои приложения на stage-сервер вместо

Для остановки и удаления всех контейнеров, запущенных для acceptance-тестирования

и запускаем pipeline-сборку и проверяем корректность отработки команд касательно docker-compose и успешного acceptance-тестирования

b) выполнение/запуск acceptance-тестирования с отдельного docker-контейнера

Вторым способом запуска acceptance-тестирования может быть запуск bash-скрипта, с помощью которого выполняется acceptance-тестирование, ВНТУРИ ОТДЕЛЬНОГО docker-контейнера
Т.е. на Docker-хосте создается отдельный docker-контейнер, на котором будет запущен bash-скрипт для acceptance-тестирования приложения

 

При таком подходе не требуется вычисление/определение порта, на котором будет запущено приложение т.к. этот порт внутри контейнера постоянный (в нашем случае 8080), а контейнер со скриптом тестирования и контейнер с приложением будут запущены в одной подсети и внутренним механизмом docker-compose доступны один одному. В скрипте с тестом в качестве имени сервера/ip-адреса сервера будет использоваться имя сервиса, указанным в корневом docker-compose.yaml файле ( в нашем случае calculator)

Корневые Dockerfile и docker-compose.yaml файлы остаются неизменными

Для создания отдельного контейнера с acceptance-тест создадим отдельный каталог acceptance внутри котрого создадим файлы Dockerfile и docker-compose-acceptance.yaml

Для начала выполним это в в тестовом окружении (/root/git/calculator-cli)(который не под контролем git), после успешного запуска перенесем созданные файлы/каталоги в локальную рабочую копию репозитария(/root/git/calculator).

Создание Dockerfile для  образа использумого в acceptance-тесте
Сборка образа на основе ubuntu, установка curl, копирование и запуск тестового скрипта

Создание docker-compose.yml файла для acceptance-теста
Сборка и запуск контейнера с именем test, созданного из образа, собранного из Dockerfile в каталоге acceptance

Создание скрипта для acceptance-тест

Запуск acceptance-теста

Проверка запущенных контейнеров

Нас интересует контейнер с тестами, в данном случае он имеет имя
acceptance_test_1_b4dbf9cf6f60

Лог контейнера сообщает о успешном вызове команды curl

Для просмотра результата выполнения команды curl определяем код выхода контейнера

Код выхода контейнера 0(ноль) означает, что тест прошел успешно

Также с Docker-хоста/ноды можно проверить корректность работы приложения, запросив порт, который используется на Docker-хосте для проброса запроса на контейнер с приложением

Удаляем созданные docker-compose-ом контейнеры

Копируем созданный в тестовом окружении (/root/git/calculator-cli) в локальную рабочую копию репозитария(/root/git/calculator).

Коммитим изменения в репозитарий

Изменяем pipeline

Вместо стадий

используем одну новую стадию

Сначало мы собирем образ для тестового контейнера (служба test). Затем запускаем все три контейнера (calculator,redis,test), после чего проверяем код выхода контейнера с тестом acceptance_test_1_*)

А также изменим опции post, в которой остановим и удалим все запущенные на предыдущем шаге контейнеры

Общий итоговый pipeline имеет вид

Запускаем pipeline и проверяем корректность отработки шагов на stage(«Acceptance test»)

 

7.Добавление использования Ansible для установки docker,docker-compose и выполнения docker-compose.yaml файла на staging-сервере

Установка ansible на Jenkins-slave, на котором выполняется сборка

На staging-сервере(у нас это Jenkins-slave) желательно предварительно проверить уже установленные модули python относительно docker

Удаляем 3 указанных модуля(docker, docker-compose, docker-py),если они уже установлены в системе(ansible сам установить нужные модули соглаcно своего playbook-а)

Проверяем, что указанные модули удалились

Создадим playbook, который будет устанавливать на staging-сервере docker,docker-compose, копировать файл docker-compose.yaml на staging-сервер и выполнять его там(полагается,что staging-сервер работает под Ubuntu16.04)

Inventory-файл имеет вид

Также нужно предоставить беспарольный(по SSH-ключам) доступ под пользователем jenkins на staging-сервер, на котором ансиблом запускается docker-compose( в даном случае jenkins-slave выполняет роль также staging-сервера) + добавить пользователя jenkins в /etc/sudoers

Чтобы после подключения под пользователем jenkins повысить привиллегии до пользователя root без запроса пароля при отработки ansible-плейбука
После чего проверить с командной строки Jenkins-slave-сервера
А) переключиться на jenkins-пользователя и проверить подключение под ним на staging-сервер по SSH-ключам

Отключение в Ansible предложения по добавлению ключей SSH в файл known_hosts

Альтернативным вариантом является установка переменной

Docker-compose.yaml имеет вид

Проверяем синтаксис playbook-а

Выполняем playbook

Проверяем наличие запущенных контейнеров

Проверяем с Jenkins-slave-сервера, на котором выполнили playbook

Список установленных ansible-ом python модулей

Pipeline по разворачивание приложения на staging-сервере будет иметь вид

Скрипт по тестированию доступности приложения имеет вид

Общий pipeline будет иметь вид

 

8.Версионирование собранных Docker-образов
Добавим временную метку, например в формате yyyy-MM-dd-HH-mm, в качестве тега Docker-образа

Установим плагин Build Timestamp Plugin

Установим формат даты в Jenkins-настройках
Например, в формате

Во всех местах,где используется Docker-образ добавим соответствующий тег в виде переменной ${BUILD_TIMESTAMP}

 

Docker-compose.yaml файл приводим к виду:

Скрипт для проверки доступности приложения остается неизменным

Ansible плейбук приводим к виду:

Коммитим изменения в репозитарий и запускаем pipeline-сборку


Проверяем наличие запущенных контейнеров

 

Источник: Книга Сontinuous delivery with Docker and Jenkins by Rafal Leszko

Комментирование и размещение ссылок запрещено.

Комментарии закрыты.

Яндекс.Метрика