Использование Master(главного и единственного сервера, на котором непосредственно установлен Jenkins) вместе с агентами(подчиненными серверами) позволяет выполнять сборки на этих агентах, уменьшая тем самым нагрузку на master-сервере, выполнять сборки на различном программном обеспечении/операционных системах, параллельно выполнять разные шаги одной и той же сборки на разных Jenkins-агентах(например, запуск параллельных тестов для различных WEB-браузеров(Chrome, Firefox, Opera и т.д.) или запуск большого количества интеграционных тестов можно разделить, например, на 4-х агентах, тем самым уменьшая общее время выполнения всех тестов теоретически в 4 раза).
Рассмотрим настройку двух типов Jenkins slave/agent, которые,например, будут собирать Java-проект с помощью maven.
В качестве slave-агентов будут выступать:
1 2 |
- Standalone(отдельный) Linux-сервер - Docker-контейнер, запущенный по запросу Jenkins-мастера |
Для определения конкретного slave/агента, на котором должна быть собрана сборка, будут использоваться метки, как на стороне slave/агентов(каждый такой агент будет иметь метку), так и в опциях самой сборки, которую нужно собрать.
Настройка Jenkins slave/agent на основе standalone Linux-сервера
Настройка slave/agent standalone Jenkins-сервера
1.Установить java,git,maven
1 |
# apt-get update && apt-get install openjdk-8-jdk git maven |
2.Создать группу и пользователя с именем jenkins
1 |
# groupadd jenkins |
1 |
# useradd -d /home/jenkins -m -r -s /bin/bash -g jenkins jenkins |
3.Переключиться на этого пользователя и
1 |
# su -l jenkins |
4.Проверить доступность Java
1 |
jenkins@ubuntu:~$ java -version |
1 2 3 |
openjdk version "1.8.0_181" OpenJDK Runtime Environment (build 1.8.0_181-8u181-b13-0ubuntu0.16.04.1-b13) OpenJDK 64-Bit Server VM (build 25.181-b13, mixed mode) |
5.Добавить публичный ключ пользователя jenkins с master сервера Jenkins в .ssh/authorized_keys пользователя jenkins на Slave-сервере
1 |
jenkins@ubuntu:~$nano .ssh/authorized_keys |
Настройка Master Jenkins-сервера
1.Переключиться под пользователя jenkins
1 |
root # su -l jenkins |
2.Подключиться по SSH к Slave-серверу(проверить корректность подключения по SSH-ключу)
1 |
jenkins$ ssh -p1234 myagentnode |
3.Настройка/добавление Slave-агента в настройках на Jenkins Master
1 |
Jenkins->Manage Jenkins->Manage Nodes->Add Node |
1 2 |
Name: standalone-linux-slave1 Permanent Agent->Yes |
В частности в этом примере будем собирать на Slave-сервере только те сборки, метки которых совпадают с меткой самого Slave-сервера(linux-slave1)
Что при этом происходит/как это работает?
Jenkins Master по SSH-подключается и копируется jar-файл на Slave в каталог, определенный параметром
«Корень удаленной файловой системы»( в нашем случае–это домашний каталог пользователя jenkins – каталог /home/jenkins)
После чего запускает этот jar-файл
1 |
cd "/home/jenkins" && java -jar remoting.jar -workDir /home/jenkins |
Лог подключения мастера к слейву и возможные ошибки,связанные с подключением,правами и т.д. доступны на вкладки Logs по пути
1 |
Jenkins->Configure Jenkins->Manage Nodes-><slave_node_name>->Logs |
Также полезна вкладка System Information (наряду с вкладкой Logs), которая предоставляет полную системную информацию о slave-сервере(свойства системы, переменные окружения и т.д.)
Для того, чтобы сборка выполнялась на slave-сервере в настройках сборки присвоем метку сборки такую же, как и метка slave-сервера (linux-slave1)
После чего запускаем сборку и проверяем ее логи на предмет выполнения сборки на Slave-сервере
При этом настройки сборки, логи ее выполнения, результаты тестов, артефакты и т.д. все хранится на master-сервере, на agent хранится только среда выполнения сборки(workspace), в которой непосредственно и собирается сборка
Настройка Jenkins slave/agent на основе Docker-контейнера
1.Настройка 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}] |
2.Установка Jenkins-плагина с именем Docker
3.Подготовка Docker-образа,который будет использоваться Jenkins-ом для создания Docker-контейнера( Jenkins-slave) на лету
В качестве родительского докер-образа будем использовать образ ubuntu
Загрузим образ ubuntu
1 |
# docker pull ubuntu |
1 |
# docker images |
1 2 |
REPOSITORY TAG IMAGE ID CREATED SIZE ubuntu latest cd6d8154f1e1 4 weeks ago 84.1MB |
Создадим и запустим контейнер
1 |
# docker run -it ubuntu /bin/bash |
Внутри контейнера выполним команды необходимые для
создания группы и пользователя пользователя jenkins, установим для него пароль
(этот логин(jenkins) и пароль(установленный на этом этапе) будут использоваться при создании Credentials в Jenkins.
Установим программного обеспечения, которое используется при выполнения сборки, например java,git,maven, а также ssh-сервер
1 |
root@3f1731b3d3f0:/# groupadd jenkins |
1 |
root@3f1731b3d3f0:/# useradd -m -r -s /bin/bash -g jenkins jenkins |
1 |
root@f1f90cbb2da7:/# passwd jenkins |
1 |
root@3f1731b3d3f0:/# apt-get update && apt-get install openssh-server git openjdk-8-jdk maven && mkdir /var/run/sshd |
Выходим из контейнер(выполняя команду exit)
Для этого узнаем идентификатор контейнера(в данном случае он равен f1f90cbb2da7) с помощью команды
1 |
# docker ps -a |
1 2 |
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES f1f90cbb2da7 ubuntu "/bin/bash" 14 minutes ago Exited (127) 35 seconds ago pedantic_kowalevski |
И сохраняем изменения, сделанные нами в контейнере путем создания нового Docker-образа c именем maven-build-slave-0.1
1 |
# docker commit f1f90cbb2da7 maven-build-slave-0.1 |
1 |
sha256:73acbc09abad884812264a61bbbf0049f7ef3fcaacc3b88c3ba873ebd38856a8 |
1 |
# docker images |
1 2 3 |
REPOSITORY TAG IMAGE ID CREATED SIZE maven-build-slave-0.1 latest 73acbc09abad 24 seconds ago 850MB ubuntu latest cd6d8154f1e1 4 weeks ago 84.1MB |
4.Создание credentials для доступа к Docker в Jenkins
1 |
Jenkins->Credentials->System->Global credentials->Add credentials-> |
1 2 3 4 5 6 |
Kind: Username and password Scope: Global Username: jenkins Password: myjenkinspassword ID: docker-container-slave1 Description: credentials for docker container(Jenkins slave) |
5.Настройка Docker-настроек в Jenkins
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 5 6 7 |
Add Docker Template Labels->docker-maven-build-slave (именно эта метка будет использоваться в pipeline) Enabled->Yes Name->docker Docker Image->maven-build-slave-0.1 (имя, под которым мы сохранили наш созданный образ) Remote File System Root->/home/jenkins Connect method->Connect with SSH |
1 2 3 4 |
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 |
1 |
Apply->Save |
Например, в Jenkins-джобе используем следующий pipeline
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
node('docker-maven-build-slave') { stage('Poll') { git credentialsId: 'jenkins-user-ssh-key', url: 'git@bitbucket.org:mybitbucketuser/hello-world-greeting.git' } stage('Build & Unit test'){ sh 'mvn clean verify -DskipITs=true'; junit '**/target/surefire-reports/TEST-*.xml' archiveArtifacts 'target/*.war' } stage ('Integration Test'){ sh 'mvn clean verify -Dsurefire.skip=true'; junit '**/target/failsafe-reports/TEST-*.xml' archiveArtifacts 'target/*.war' } } |
Согласно метке в поле node сборка будет запущена на Jenkins-slave с меткой docker-maven-build-slave, эта метка, которая соответствует нашему docker-контейнеру, настроенного на предыдущем шаге( в пункте 5)
Запускаем сборку с указанным выше pipeline-ом и проверяем,что в процессе выполнения сборки создался Docker-контейнер на основе Docker-образа maven-build-slave-0.1, в котором были выполненные указанные в pipeline шаги сборки.
Для запуска сборки (с анализом кода с использованием SonarQube, а также загрузки артефактов в maven-репозитарий, созданного на Nexus-сервере) на Docker-контейнере в качестве Jenkins-агента, а не на Jenkins-мастере необходимо выполнить несколько изменений:
SonarQube требует Java версии 8, по умолчанию с latest ubuntu Docker-образа ставится Java 11(при выполнении команды apt-get install java), поэтому небходимо доустановить Java версии 8 и сделать ее версией по-умолчанию.
После чего снова сохранить образ
Заходим внутрь docker-контейнера
1 |
# docker run -it maven-build-slave-0.1 /bin/bash |
1.Устанавливаем Java версии 8 и делаем ее версией по умолчанию
1 |
root@f9339fc220d8:/# apt-get update && apt-get install openjdk-8-jdk && apt-get clean |
1 |
root@f9339fc220d8:/# update-alternatives --config java |
1 2 3 4 5 6 7 |
There are 2 choices for the alternative java (providing /usr/bin/java). Selection Path Priority Status ------------------------------------------------------------ * 0 /usr/lib/jvm/java-11-openjdk-amd64/bin/java 1101 auto mode 1 /usr/lib/jvm/java-11-openjdk-amd64/bin/java 1101 manual mode 2 /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java 1081 manual mode |
1 |
Press <enter> to keep the current choice[*], or type selection number: 2 |
1 |
update-alternatives: using /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java to provide /usr/bin/java (java) in manual mode |
1 2 3 4 |
root@f9339fc220d8:/# java -version openjdk version "1.8.0_181" OpenJDK Runtime Environment (build 1.8.0_181-8u181-b13-0ubuntu0.18.04.1-b13) OpenJDK 64-Bit Server VM (build 25.181-b13, mixed mode) |
2.Добавляем/проверяем настройки maven в конфигурационный файл maven внутри контейнера
1 |
root@f9339fc220d8:/# nano /usr/share/maven/conf/settings.xml |
Логин/пароль,который был создан на Nexus-сервере с доступом ,к размещенному на Nexus maven-репозитарию.
1 2 3 4 5 |
<server> <id>my-nexus-maven-repo</id> <username>my-maven-user</username> <password>my-maven-password</password> </server> |
Настройки maven относительно SonarQube
1 2 3 |
<pluginGroups> <pluginGroup>org.sonarsource.scanner.maven</pluginGroup> </pluginGroups> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<profiles> <profile> <id>sonar</id> <activation> <activeByDefault>true</activeByDefault> </activation> <properties> <!-- Optional URL to server. Default value is http://localhost:9000 --> <sonar.host.url> https://mysonarqubeserver </sonar.host.url> </properties> </profile> </profiles> |
Выходим из контейнера и создаем новый Docker-образ с тем же именем(maven-build-slave-0.1) для сохранения сделанных изменений
1 |
# docker images |
1 2 3 |
REPOSITORY TAG IMAGE ID CREATED SIZE maven-build-slave-0.1 latest 73acbc09abad 2 weeks ago 850MB ubuntu latest cd6d8154f1e1 6 weeks ago 84.1MB |
1 |
# docker commit f9339fc220d8 maven-build-slave-0.1 |
1 |
sha256:4deb2c07397c05c38143a399e4117be740735250f72752bcf78b1917bf1c1d7e |
1 |
# docker images |
1 2 3 |
REPOSITORY TAG IMAGE ID CREATED SIZE maven-build-slave-0.1 latest 4deb2c07397c 3 seconds ago 856MB ubuntu latest cd6d8154f1e1 6 weeks ago 84.1MB |
В корне репозитария в файле pom.xml проверяем/добавляем(после чего не забываем закоммитить сделанные изменения)
1 |
# nano pom.xml |
Настройки свойств проекта
1 2 3 4 5 |
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <sonar.language>java</sonar.language> </properties> |
……
Настройки для Nexus-репозитария
1 2 3 4 5 6 7 8 9 10 11 |
<distributionManagement> <repository> <id>my-nexus-maven-repo</id> <url>https://mynexusserver/repository/my-maven-repo</url> </repository> <snapshotRepository> <id>my-nexus-maven-repo</id> <url>https://mynexusserver/repository/my-maven-repo</url> </snapshotRepository> </distributionManagement> |
3. Предоставить auth-токен для аутентификации на SonarQube-сервере(для загрузки результатов анализа кода)
Такой токен аутентификации можно передать через переменные окружения(чтобы не хардкодить его в Jenkins-file(при использовании pipeline script from scm) или непосредственно в самой сборке(при использовании pipeline script), например, выполняя команду
1 |
sh 'mvn clean verify sonar:sonar -Dsonar.projectVersion=$BUILD_NUMBER -Dsonar.login=myauthtoken' |
Что является не лучшим способом с точки зрения безопасности)
Для передачи токена через переменные окружения необходимо в настройках Docker agent template в Jenkins добавляем переменную окружения
После чего использовать эту переменную для аутентификации в SonarQube при вызове шага maven для проверки кода анализатором SonarQube
1 |
sh 'mvn clean verify sonar:sonar -Dsonar.projectVersion=$BUILD_NUMBER -Dsonar.login=${SONAR_AUTH_TOKEN}' |
Jenkins-pipeline имеет вид(теперь все шаги сборки будут выполнены на Docker-контейнере,созданного на базе Docker-образа maven-build-slave-0.1 )
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 |
/* node('master') { */ node('docker-maven-build-slave') { stage('Poll') { git credentialsId: 'jenkins-user-ssh-key', url: 'git@bitbucket.org:mybitbucketuser/hello-world-greeting.git', branch: 'master' } 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'){ sh 'mvn clean verify sonar:sonar -Dsonar.projectVersion=$BUILD_NUMBER -Dsonar.login=${SONAR_AUTH_TOKEN}' } 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" } } |
Использование volume(томов) смонтированный с Docker-хоста в Docker-контейнер для уменьшения времени sполнения сборки
При сборке могут загружаться много зависимостей проекта(например, при использовании Maven/Gradle), что увеличивает время выполнения сборки.
Например, смонтируем каталог с ноды /opt/docker/jenkins/volume-maven-build-slave-0.1 внутрь контейнера в каталог .m2 в домашнем каталоге пользователя jenkins (/home/jenkins/.m2) для того, чтобы при запуске сборки в контейнере Maven каждый раз не загружал зависимости проекта
В настройках Jenkins в разделе Configure Jenkins->Cloud->Docker->Docker Agent Templates для докер образа maven-build-slave-0.1 нажимаем Container Settings и добавляем
1 |
Volumes->/opt/docker/jenkins/volume-maven-build-slave-0.1:/home/jenkins/.m2/:rw |
Необходимо отметить, что UID/GID-пользователя jenkins, под которым внутри контейнера выполняется сборка должен соответствовать такому же значению UID/GID для владельца/группы на каталог ноды /opt/docker/jenkins/volume-maven-build-slave-0.1
(чтобы пользователь jenkins внутри контейнера имел право записи в каталог /home/jenkins/.m2/)
Например, внутри контейнера UID/GID равен 999/1000
1 |
root@76e926536d0e:/# grep jenkins /etc/{passwd,group} |
1 2 |
/etc/passwd:jenkins:x:999:1000::/home/jenkins:/bin/bash /etc/group:jenkins:x:1000: |
На ноде выставляем рекурсивно соответствующие UID:GID на каталог, который монтируется внутрь контейнера
1 |
# chown -R 999:1000 /opt/docker/jenkins/volume-maven-build-slave-0.1/ |
Источник: Книга Learning Continuous Integration with Jenkins 2nd Edition by Nikhil Pathania