Пример создания Continuous Integration/Continuous Delivery процесса для проекта, написанного на Java, c использованием Docker
За основу был взят репозитарий, форкнутый с
https://github.com/nikhilpathania/hello-world-greeting
и его реализация в книге Learning Continuous Integration with Jenkins 2nd Edition by Nikhil Pathania
Здесь доступен оригинальный код автора книги
https://github.com/PacktPublishing/Learning-Continuous-Integration-with-Jenkins-Second-Edition
Все подготовительные работы(установка и интеграция Jenkins,Maven,SonarQube,Nexus) были выполнены в предыдущих статьях:
Установка и настройка Jenkins,Maven,SonarQube,Nexus на Centos 7
Установка и настройка SonarQube на Ubuntu 16
Установка и настройка Nexus на Ubuntu 16
Интеграция Jenkins,Maven,SonarQube,Nexus
Шаги Continuous Integration будут выполнены на master-ноде, а шаги Continuous Delivery – на slave/agent–ноде
Основное отличие от описанного ранее похожего примера
Jenkins: создание Continuous Integration/Continuous delivery процесса в Jenkins для проекта Java
в том, что в этой статье мы будем использовать Jenkins slave/agent-ноду(Docker контейнер) для запуска всех шагов Continuous Delivery.
В предыдущей статье такие шаги выполнялись на standalone Linux-сервере, на котором был развернут Tomcat
Continuous Integration будет состоять из следующих шагов, выполняемых на master-ноде
1 2 3 4 5 6 |
Получение кода из репозитария – stage Poll Cборка проекта средствами maven, запуска Junit-тестов, архивирование артефакта(war-файла) – stage Build & Unit test Анализа кода средствами SonarQube - stage Static Code Analysis Запуск интеграционных тестов, архивирование артефакта(war-файла) – stage Integration Test Копирования артефакта в Nexus-репозитарий – stage Copy artifacts to Nexus-repo Stash собранного артефакта для передачи его другой Jenkins ноде |
Jenkins pipeline использует т.н. stash, который позволяет передавать артефакты сборки между нодами Jenkins
Сохранение(stash) артефакта(war-файла) для передачи его другой ноде(Jenkins slave/agent-сервера), на которой Java-приложение будет запускаться с этого war-файла
В качестве slave/agent-сервера будет использоваться Docker-контейнер, который будет запускаться из подготовленного заранее нами Docker-образа, внутри контейнера будет запускаться Tomcat-сервер, для запуска Java-приложения.
Continuous Delivery будет состоять из следующих шагов, выполняемы на Jenkins slave/agent-ноде
1 2 3 |
Запуск Tomcat-сервера – stage Start Tomcat Un-stash-собранного на стадии Continuous Integration артефакта и деплой приложениия в Tomcat-сервере – stage Deploy Выполнение нагрузочного тестирования/тестирования производительности средствами Apache Jmeter, архивирование результатов тестирования, публикация отчета о результатах тестирования – stage Performance Testing |
Настройка Continuous Integration
1. Jenkinsfile для реализации Continuous Integration имеет вид
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
node('master') { stage('Poll') { git credentialsId: 'jenkins-user-ssh-key', url: 'git@bitbucket.org:mybitbucketuser/hello-world-greeting.git' } stage('Build & Unit test'){ // Perform only build and unit test with skip the integration testing sh 'mvn clean verify -DskipITs=true' // Publich JUnit unit tests reports junit '**/target/surefire-reports/TEST-*.xml' // Archive artifacts archiveArtifacts 'target/*.war' } stage('Static Code Analysis'){ // Sonarqube must be configured in the Jenkins: Configuration -> Add SonarQube withSonarQubeEnv('my-sonarqube-demo') { sh 'mvn clean verify sonar:sonar -Dsonar.projectVersion=$BUILD_NUMBER' } } stage ('Integration Test'){ // Perform integration testing and skip unit testing sh 'mvn clean verify -Dsurefire.skip=true' // Publich integration tests reports junit '**/target/failsafe-reports/TEST-*.xml' // Archive artifacts archiveArtifacts 'target/*.war' } stage('Copy artifacts to Nexus-repo') { sh "mvn -DskipITs=true -Dsurefire.skip=true deploy" } stash includes: 'target/hello-0.0.1.war,src/pt/hello-world.jmx', name: 'binary' } |
2.Pipeline-сборка в Jenkins
На хостовой ноде(в нашем случае Ubuntu16), на которой будет запускаться Docker-контейнер в качестве Jenkins slave/agent-ноды, устаналиваем Docker и разрешаем удаленное подключение к Docker-хосту c Jenkins master-ноды(это достигается с помощью firewall на Docker-хосте)
3.Установка Docker на хостовой Docker-ноде с Ubuntu16.04
1 |
# apt-get update && apt-get upgrade -y |
1 |
# apt-get install apt-transport-https ca-certificates curl software-properties-common |
1 |
# curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - |
1 |
# add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" |
1 |
# apt-get update && apt-get install docker-ce |
Verify that the default logging driver is used to json-file.
1 |
# docker info|grep "Logging Driver" |
1 |
Logging Driver: json-file |
Ensure that SELinux is not enabled for Docker
1 |
# docker info --format '{{json .SecurityOptions}}' |
1 |
["name=apparmor","name=seccomp,profile=default"] |
4.Настройка Docker-сервера на прослушивание удаленных запросов с Jenkins-сервера
1 |
# cp /lib/systemd/system/docker.service /etc/systemd/system/docker.service |
1 |
# nano /etc/systemd/system/docker.service |
1 |
ExecStart=/usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:4243 |
1 |
# systemctl daemon-reload |
1 |
# systemctl restart docker |
1 |
# netstat -nlptu | grep 4243 |
1 |
tcp6 0 0 :::4243 :::* LISTEN 29085/dockerd |
На уровне файерволла разрешаем подключение к порту 4243 только с Jenkins-сервера
Проверяем доступность Docker-сервера через API выполняя запрос с Jenkins-сервера
Проверка существующих образов на Docker-сервере
1 |
# curl -X GET http://mydockerhost:4243/images/json |
1 |
[{"Containers":-1,"Created":1536807419,"Id":"sha256:64b7082ceb84123df8436b34e68594ac595a69cc857d39e460fa5a022e20d1b4","Labels":null,"ParentId":"","RepoDigests":["jenkins/jenkins@sha256:df7ff8f7c2ab4293ab45a1fb538a6fe0509e82187f3f1bb60f8bfee27af3a87f"],"RepoTags":["jenkins/jenkins:lts"],"SharedSize":-1,"Size":702569839,"VirtualSize":702569839}] |
5.Установка Jenkins-плагина с именем Docker
6.Сборка Docker-образа для запуска Tomcat-сервера для выполнения тестирования производительности
Создадим и запустим контейнер на основе базового образа ubuntu
1 |
# docker run -it ubuntu /bin/bash |
Внутри контейнера выполним команды необходимые для
создания группы и пользователя jenkins, установим для него пароль(этот логин(jenkins) и пароль(установленный на этом этапе) будут использоваться при создании Credentials в Jenkins
Установки программного обеспечения, которое используется при запуске сборки, например java, а также ssh-сервер и утилиту curl
1 |
root@9eb3f8a20b4e:/# groupadd jenkins && useradd -m -r -s /bin/bash -g jenkins jenkins && passwd jenkins |
1 |
root@9eb3f8a20b4e:/# apt-get update && apt-get install openssh-server curl openjdk-8-jdk && mkdir /var/run/sshd |
Установка Tomcat-сервера
Загрузка Tomcat-сервера с сайта http://tomcat.apache.org/download-80.cgi
1 |
root@9eb3f8a20b4e:/# cd /tmp && wget http://apache.cp.if.ua/tomcat/tomcat-8/v8.5.34/bin/apache-tomcat-8.5.34.tar.gz |
Под пользователем Jenkins распаковываем архив в каталог /home/jenkins/tomcat
1 |
root@3f1731b3d3f0:/# su -l jenkins |
1 |
jenkins@9eb3f8a20b4e:~$ |
1 |
jenkins@9eb3f8a20b4e:~$ mkdir /home/jenkins/tomcat && tar -xvzf /tmp/apache-tomcat-8.5.34.tar.gz -C /home/jenkins/tomcat --strip-components=1 && rm /tmp/apache-tomcat-8.5.34.tar.gz && chown -R jenkins:jenkins /home/jenkins/tomcat |
Переключаемся на root-пользователя
1 |
jenkins@9eb3f8a20b4e:~$ exit |
1 |
root@9eb3f8a20b4e:/tmp# |
Установка Apache JMeter
Загрука Apache Jmeter с сайта http://jmeter.apache.org/download_jmeter.cgi
1 |
# mkdir /opt/jmeter && wget http://apache.cp.if.ua//jmeter/binaries/apache-jmeter-5.0.tgz && tar -xvzf apache-jmeter-5.0.tgz -C /opt/jmeter --strip-components=1 && chown -R jenkins:jenkins /opt/jmeter && chmod -R 777 /opt/jmeter && rm apache-jmeter-5.0.tgz |
Удаляем загруженные архивы пакетов для уменьшения размера Docker-образа
1 |
# apt-get clean |
Создание Docker-образа для сохранения сделанных изменений
Выходим из контейнера
1 |
root@9eb3f8a20b4e:/tmp# exit |
1 |
root@ubuntu ~ # |
Узнаем идентификатор контейнера и создаем Docker-образ с именем performance-test-agent-0.1
1 |
# docker ps -a |
1 2 |
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 9eb3f8a20b4e ubuntu "/bin/bash" 18 minutes ago Exited (0) 10 seconds ago infallible_clarke |
1 |
root@ubuntu ~ # docker commit 9eb3f8a20b4e performance-test-agent-0.1 |
1 |
sha256:69c908cbd007d8510bfe643721042e9756b2a9e21dbb66d1bd8637266448ecd5 |
1 |
root@ubuntu ~ # docker images |
1 2 3 4 |
REPOSITORY TAG IMAGE ID CREATED SIZE performance-test-agent-0.1 latest 69c908cbd007 11 seconds ago 673MB jenkins/jenkins lts 64b7082ceb84 4 weeks ago 703MB ubuntu latest cd6d8154f1e1 5 weeks ago 84.1MB |
7.Создание credentials для доступа к Docker в Jenkins
1 2 3 4 5 6 7 |
Jenkins->Credentials->System->Global credentials->Add credentials-> Kind: Username and password Scope: Global Username: jenkins Password: myjenkinspassword ID: docker-container-slave1 Description: credentials for docker container(Jenkins slave) |
8.Настройка Docker-настроек в Jenkins – добавление Docker-хоста и Docker-шаблона
1 |
Jenkins->Configure System->Сloud->Add a new cloud->Docker |
1 2 |
Name: My default docker host (произвольное имя) Docker Host URL: tcp://mydockerservername:4243 |
Нажимаем на кнопку Test Connection И проверяем корректный доступ к Docker-серверу с Jenkins благодаря настроенному ранее удаленному доступу.
Добавляем созданный ранее докер-образ
1 |
Add Docker Template |
1 2 3 4 |
Labels->docker-pt-slave (именно эта метка будет использоваться в pipeline) Enabled->Yes Name->docker Docker Image-> performance-test-agent-0.1(имя, под которым мы сохранили наш созданный образ) |
1 2 |
Remote File System Root->/home/jenkins Connect method->Connect with SSH |
1 2 3 4 5 |
SSH key->Use configured SSH credentials SSH Credentials->Jenkins)credentials for docker container(Jenkins slave)) (ранее созданные доступы в Jenkins) Host Key Verification Strategy->Non verifying Strategy Pull strategy->Never pull Apply->Save |
9.Создание плана тестирования в Apache Jmeter
Скачиваем Apache Jmeter с http://jmeter.apache.org/download_jmeter.cgi
Извлекаем файлы с архива и переходим в каталог bin
Выполняем файл jmeter.bat или jmeter.sh в зависимости от операционной системы
В результате чего открывается Apache Jmeter Console, в которой создаем наш план тестирования
Создание Thread Group
1 2 3 4 5 |
Правой кнопкой мыши на Test Plan->Add->Threads(Users)->Thread Group Name->User visiting Hello world page Number of Threads->2 Ramp-Up Period-> Loop Count->2 |
Создание Sampler
1 |
Правой кнопкой мыши на User visiting Hello world page->Add-Sampler->HTTP Request |
1 2 3 |
Servername or IP: localhost (т.к. Tomcat-сервер будет запускаться в этом же контейнере, где и Jmeter(тесты производительности) Port number: 8080 (дефолтный порт для Tomcat-сервера) Path: /hello-0.0.1/ (путь,по которому будет доступно приложение) |
Сохраняем созданный план тестирования
1 2 3 |
File->Save Test Plan as-> File name: hello-world.jmx Save in examples |
В репозитарии в каталоге src создаем каталог с именем pt и загружаем в него созданный план тестирования (файл hello-world.jmx),
1 |
# tree src/pt/ |
1 2 3 4 |
src/pt/ └── hello-world.jmx 0 directories, 1 file |
Коммитим изменения и заливаем на GIT-сервер
10. Создание Continiouse Delivery pipeline в Jenkinsfile
Изменяем ранее созданный Jenkins-файл добавляя в него после кода
1 |
# nano Jenkinsfile |
1 2 3 4 5 |
… stash includes: 'target/hello-0.0.1.war,src/pt/hello-world.jmx', name: 'binary' } |
добавляем следующий код
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
node('docker-pt-slave') { stage ('Start Tomcat'){ // Start Tomcat server sh '''cd /home/jenkins/tomcat/bin ./startup.sh''' } stage ('Deploy'){ // un-stash the binary package that we stashed from the previous node Docker block unstash 'binary' // deploy the un-stashed files into the webapps folder inside the Tomcat installation directory sh 'cp target/hello-0.0.1.war /home/jenkins/tomcat/webapps/' } stage ('Performance Testing'){ // Execute command to exec performance testing with the use of Apache JMeter // Waiting for 20 seconds before start test(until tomcat deploy war-file) sh '''cd /opt/jmeter/bin/ sleep 20 ./jmeter.sh -n -t $WORKSPACE/src/pt/hello-world.jmx -l $WORKSPACE/test_report.jtl''' // Archive result of performance testing archiveArtifacts '**/*.jtl' // Publish Apache JMeter results perfReport errorFailedThreshold: 5, errorUnstableThreshold: 3, percentiles: '0,50,90,100', sourceDataFiles: '**/*.jtl' } } |
Полный CI/CD pipeline в Jenkinsfile имеет вид
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
node('master') { stage('Poll') { git credentialsId: 'jenkins-user-ssh-key', url: 'git@bitbucket.org:mybitbucketuser/hello-world-greeting.git' } stage('Build & Unit test'){ // Perform only build and unit test with skip the integration testing sh 'mvn clean verify -DskipITs=true' // Publich JUnit unit tests reports junit '**/target/surefire-reports/TEST-*.xml' // Archive artifacts archiveArtifacts 'target/*.war' } stage('Static Code Analysis'){ // Sonarqube must be configured in the Jenkins: Configuration -> Add SonarQube withSonarQubeEnv('my-sonarqube-demo') { sh 'mvn clean verify sonar:sonar -Dsonar.projectVersion=$BUILD_NUMBER' } } stage ('Integration Test'){ // Perform integration testing and skip unit testing sh 'mvn clean verify -Dsurefire.skip=true' // Publich integration tests reports junit '**/target/failsafe-reports/TEST-*.xml' // Archive artifacts archiveArtifacts 'target/*.war' } stage('Copy artifacts to Nexus-repo') { sh "mvn -DskipITs=true -Dsurefire.skip=true deploy" } stash includes: 'target/hello-0.0.1.war,src/pt/hello-world.jmx', name: 'binary' } node('docker-pt-slave') { stage ('Start Tomcat'){ // Start Tomcat server sh '''cd /home/jenkins/tomcat/bin ./startup.sh''' } stage ('Deploy'){ // un-stash the binary package that we stashed from the previous node Docker block unstash 'binary' // deploy the un-stashed files into the webapps folder inside the Tomcat installation directory sh 'cp target/hello-0.0.1.war /home/jenkins/tomcat/webapps/' } stage ('Performance Testing'){ // Execute command to exec performance testing with the use of Apache JMeter // Waiting for 20 seconds before start test(until tomcat deploy war-file) sh '''cd /opt/jmeter/bin/ sleep 20 ./jmeter.sh -n -t $WORKSPACE/src/pt/hello-world.jmx -l $WORKSPACE/test_report.jtl''' // Archive result of performance testing archiveArtifacts '**/*.jtl' // Publish Apache JMeter results perfReport errorFailedThreshold: 5, errorUnstableThreshold: 3, percentiles: '0,50,90,100', sourceDataFiles: '**/*.jtl' } } |
11.Запускаем сборку и проверяем Pipeline,результаты тестов, наличие артефактов, результатов нагрузочного тестирования, публикацию отчетов по тестированию
Просмор сборки в Blue Ocean
Отчеты тестирования производительности
Источник: Книга Learning Continuous Integration with Jenkins 2nd Edition by Nikhil Pathania