
Набрался немного опыта по установке и настройке Jenkins-а. Разобрался как писать свои собственные pipelin-ы и пришло время сделать билд и собрать что-то на Java. Для своего примера, я возьму код с Github-а и залью в гитлаб-сервер (локальный).
Полезное чтиво:
Установка Jenkins в Unix/Linux
Работа с Jenkins-CLI в Unix/Linux
Установка Docker-compose в Unix/Linux
Установка Jenkins и Jenkins-slave в Unix/Linux
Установка Jenkins в Unix/Linux
Буду использовать докер для установки дженкинса. ОС которую я использую — Mac OS X. Мой docker-compose.yml файл выглядит следующим образом:
---
version: '3.5'
services:
gitlab:
image: gitlab/gitlab-ce:latest
container_name: gitlab
hostname: gitlab.local
labels:
local.gitlab.description: "Gitlab server"
ports:
- "443:443"
- "80:80"
- "2222:22"
dns:
- 10.17.0.3
- 1.1.1.1
- 74.82.42.42
volumes:
- "/usr/local/gitlab/config:/etc/gitlab:rw"
- "/usr/local/gitlab/logs:/var/log/gitlab:rw"
- "/usr/local/gitlab/data:/var/opt/gitlab:rw"
extra_hosts:
jenkins_local_docker: 172.6.6.20
socat_container: 172.6.6.2
restart: always
environment:
- GITLAB_OMNIBUS_CONFIG="external_url 'http://gitlab.local:80'; gitlab_rails['gitlab_shell_ssh_port']=2222; gitlab_rails['lfs_enabled'] = true;"
networks:
network0:
ipv4_address: 172.6.6.10
healthcheck:
test: ["CMD", "curl", "-f", "http://gitlab.local"]
interval: 5m
timeout: 30s
retries: 3
start_period: 5m
jenkins:
image: jenkins/jenkins:latest
container_name: jenkins
hostname: jenkins.local
ports:
- "8080:8080"
- "50000:50000"
dns:
- 10.17.0.3
- 1.1.1.1
- 74.82.42.42
volumes:
- "/usr/local/jenkins/data:/var/jenkins_home:rw"
- "/usr/local/jenkins/backups:/backups:rw"
- "/var/run/docker.sock:/var/run/docker.sock:rw"
- "/usr/local/bin/docker:/bin/docker"
extra_hosts:
gitlab_local_docker: 172.6.6.10
socat_container: 172.6.6.2
restart: always
privileged: true
environment:
- DOCKER_HOST=tcp://socat:2375
- JAVA_OPTS="-Xmx2048M"
#- JAVA_OPTS="-Xms512M -Xmn512m -Xmx1024m -Duser.timezone=Europe/Kiev -Dfile.encoding=UTF-8"
#- JENKINS_OPTS=""
links:
- socat
depends_on:
- gitlab
networks:
network0:
ipv4_address: 172.6.6.20
pid: host
socat:
image: bpack/socat
container_name: socat
hostname: socat_container
restart: "always"
privileged: true
ports:
- "2375:2375"
dns:
- 10.17.0.3
- 1.1.1.1
- 74.82.42.42
command: "TCP4-LISTEN:2375,fork,reuseaddr unix-connect:/var/run/docker.sock"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
networks:
network0:
ipv4_address: 172.6.6.2
#volumes:
#docker_socket:
#driver_opts:
# type: none
# device: "/var/run/docker.sock"
# o: bind
networks:
network0:
ipam:
driver: default
config:
- subnet: 172.6.6.0/24
Запустить стек можно следующим образом:
$ docker-compose -f /Users/captain/Projects/docker/jenkins_gitlab/docker-compose.yml up -d
Creating network "jenkins_gitlab_network0" with the default driver
Creating gitlab ... done
Creating socat ... done
Creating jenkins ... done
Стек запущен и через несколько минут будет готов к использованию.
Работоспособность стека можно поглядеть:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0cde5390a8d1 jenkins/jenkins:latest "/sbin/tini -- /usr/…" 51 seconds ago Up 50 seconds 0.0.0.0:8080->8080/tcp, 0.0.0.0:50000->50000/tcp jenkins
a89eaa4bd873 bpack/socat "socat TCP4-LISTEN:2…" 52 seconds ago Up 50 seconds 0.0.0.0:2375->2375/tcp socat
447dcb1faa0a gitlab/gitlab-ce:latest "/assets/wrapper" 52 seconds ago Up 50 seconds (health: starting) 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp, 0.0.0.0:2222->22/tcp gitlab
Как видно с вывода, gitlab все еще стартует. Ему потребуется 1-3 минуты (а поменяет свое состояние через 5 минут, т.к я создал свой хелз_чек) на то, чтобы запустится (healthy) чтобы запустится и работать.
Настройка Gitlab-сервера в Unix/Linux
Под настройкой гитлаб-сервера, я подразумеваю некоторые моменты, — например создания вебхука для дженкинса + добавления открытого ключа для пользователя git. Открываем УРЛ и переходим в админ-панель (http://gitlab.local/admin):

Нажимаем на «New user» (Это сразу под «Users») чтобы создать пользователя git. В этом нет ничего сложного. После того как создали, клацаем на «USERS:» (как на картинке, у меня всего 3 пользователя). Находим «git» пользователя которого создали и нажемаем по нему (Можно не логинится в данного юзера, а просто нажать на «impersonate» чтобы «стать им»). Затем, открываем профиль самого пользователя и переходим в поле «SSH Keys». Собственно в поле вставляем публичный\открытый ключ для подключения к гитлаб-серверу.
PS: я генерировал в 2048 битный ключ (если использовать 4096-битный, будет ошибка):
$ ssh-keygen -t rsa -C "The key for gitlab server" -f gitlab
Т.е вот так не сработает:
$ ssh-keygen -t rsa -b 4096 -C "The key for gitlab server" -f ~/.ssh/gitlab
PS: Конечно странно что нельзя использовать 4096-битный ключ. Возможно кто-то знает причину и поделится знаниями в коментариях.
Я создавал еще «Personal Access Tokens» не помню уже зачем, и нужно ли это вообще. Но оставил чтобы не забыть в случае надобности… Все это были настройки для git-юзера в его юзер-спейсе. Можно закрыть и перейти в админ-спейс, а именно в меню «System Hooks».
В поле URL, я вставил:
http://jenkins_local_docker:8080/generic-webhook-trigger/invoke?token=jenkins_token_to_gitlab
Где:
- http://jenkins_local_docker:8080 — Ссылка на дженкинс-сервер.
- generic-webhook-trigger/invoke — Плагин который устанавливали недавно (в дженкинсе).
- token=jenkins_token_to_gitlab — собственно сам токен, по которому будет происходить веб-хук. Выбрал простое название для токена (конечно не очень безопасное, но сойдет для локальной лабы).
В поле «Secret Token» собственно прописал токен, например у меня это:
jenkins_token_to_gitlab
В поле «Trigger» стоит отметить на что нужно реагировать. Я отметил все (не знаю зачем, но пусть будет), но можно было только «Repository update events», «Push events» и «Enable SSL verification», так же, если используете теги, то включаем «Tag push events», для мерджей стоит включить «Merge request events». Если нажать на «TEST», то можно проверить работоспособность вашего созданного веб-хука. У меня все огонь и я могу использовать его.
И да, нужно для юзера от которого будут идти коммиты в гитлаб-сервер, добавить открытый ключ. Собственно кто работает с гит-ом не один раз, помнит об этом…
В качестве примера сборки, я зашел на гитхаб и нашел «simple-java-maven-app» проект для моей сборки. Код содержит обычный «Hello world» и тесты для него + Pipelinefile + сборочный файл для maven-а (pom.xml). Код скачал на свою машину и выполнил пуш в свой гитлаб-сервер.
И так, теперь все готово к сборке!
Автосборка Java проектов через Jenkins в Unix/Linux
Если делали по моему docker-compose файлу, то я в /etc/hosts добавил (привел к виду):
#
#
#
127.0.0.1 localhost gitlab.local jenkins.local
255.255.255.255 broadcasthost
::1 localhost
#
#
#
Можно конечно и не делать и юзать localhost или 127.0.0.1 для входа на ресурсы, но так нагляднее и удобнее (как по мне).
Тогда гитлаб будет доступен по — http://gitlab.local:80/ , а дженкинс по — http://jenkins.local:8080. Это так, короткое отступление, если кто-то не знает как сделать или запустить. Сейчас тосит установить пару плагинов которые нам понадобяться. Чтобы это сделать, открываем «Manage Jenkins» -> «Manage Plugins» и переходим во «Available» вкладку. Потом можно использовать «Filter:» для поиска нужного плагина. Стоит установить — Gitlab Authentication plugin, GitLab Plugin, Generic Webhook Trigger Plugin. Почитать про них можно в интернете, моя статья заключается не в этом, по этому идем дельше.
Переходим на главную страницу и нажимаем на «New Item» чтобы создать проекты под себя. Я создал структуру которая имеет вид:
Projects->Java
Т.е в проектах будут лежать разные проекты для сборок (Java, Python, Go, PHP и может что-то еще). В папке с Java, создадим «Pipeline», названия могут быть любые, например — «simple-java-maven-app». Стоит перейти во вкладку «Pipeline» и заполнить все необходимые поля, у меня это выглядит вот так:

Я выбрал «Pipeline script from SCM», затем в поле SCM выбрал «Git». В поля репозиторий — я добавил урл на мой локальный гитлаб репозиторий с кодом, выглядит он вот так — git@gitlab_local_docker:java/simple-java-maven-app.git. В поле «Credentials» я добавил закрытый ключь от Gitlab-а (На гитлабе собственно положил открытый, для пользователя git). Конечно можно заполнять и другие вкладки руками, но я все необходимое вшил в свой пайплайн (Тем самым автоматизировал процесс) и данное действие необходимо только для 1-го запуска.
Гитлаб репозиторий с файлами выглядит следующим образом:
┌(captain@Macbook)─(✓)─(10:46 PM Sat Feb 09)
└─(~/Projects/gitlab/repos/simple-java-maven-app)─(6 files, 16b)─> tree
.
├── CHANGELOG
├── CONTRIBUTING.md
├── README.md
├── jenkins
│ ├── Jenkinsfile
│ ├── Jenkinsfile2
│ └── scripts
│ └── deliver.sh
├── pom.xml
└── src
├── main
│ └── java
│ └── com
│ └── mycompany
│ └── app
│ └── App.java
└── test
└── java
└── com
└── mycompany
└── app
└── AppTest.java
13 directories, 9 files
В «jenkins» папке лежат два Jenkinsfil-а (Jenkinsfile, Jenkinsfile2). Первый pipeline — это не измененный файл, т.е склонировал с проекта (все что шло в комплекте). А Jenkinsfile2 — это мой груви-код, который выглядит следующим образом:
#!/usr/bin/env groovy
properties([
parameters([
choice(name: 'GITLAB_PROTOCOL', choices: ['SSH', 'HTTP', 'HTTPS'],
description: 'Set protocol for Gitlab usage (SSH, HTTP, HTTPS).'),
string(name: 'GITLAB_SERVER', defaultValue: 'gitlab_local_docker'.toLowerCase(),
description: 'Set GITLAB server or URL. I use hostname of my gilab docker container which linked to jenkins'),
string(name: 'GITLAB_PROJECT', defaultValue: 'java'.toLowerCase(),
description: 'Set GITLAB project.'),
string(name: 'BRANCH_NAME', defaultValue: 'master',
description: 'Service branch. Modify the build job config and change the Pipeline SCM branch to build using branch scripts.'),
string(name: 'REPO_NAME', defaultValue: 'simple-java-maven-app'.toLowerCase(),
description: 'Repo name')
]),
//gitLabConnection('gitlab-private-key'),
pipelineTriggers([
[
$class: 'GitLabPushTrigger',
branchFilterType: 'All',
triggerOnPush: true,
triggerOnMergeRequest: false,
triggerOpenMergeRequestOnPush: "never",
triggerOnNoteRequest: true,
noteRegex: "Jenkins please retry a build",
skipWorkInProgressMergeRequest: true,
secretToken: "jenkins_token_to_gitlab",
ciSkip: false,
setBuildDescription: true,
addNoteOnMergeRequest: true,
addCiMessage: true,
addVoteOnMergeRequest: true,
acceptMergeRequestOnSuccess: false,
branchFilterType: "NameBasedFilter",
includeBranchesSpec: "release/qat",
excludeBranchesSpec: "",
],
[
$class: 'GenericTrigger',
genericVariables: [
[key: 'ref', value: '$.ref'],
[key: 'before', value: '$.before',
expressionType: 'JSONPath', //Optional, defaults to JSONPath
regexpFilter: '', //Optional, defaults to empty string
defaultValue: '' //Optional, defaults to empty string
]
],
genericRequestVariables: [
[key: 'requestWithNumber', regexpFilter: '[^0-9]'],
[key: 'requestWithString', regexpFilter: '']
],
genericHeaderVariables: [
[key: 'headerWithNumber', regexpFilter: '[^0-9]'],
[key: 'headerWithString', regexpFilter: '']
],
causeString: 'Triggered on $ref',
token: 'jenkins_token_to_gitlab',
printContributedVariables: true,
printPostContent: true,
silentResponse: false,
regexpFilterText: '$ref',
regexpFilterExpression: 'refs/heads/' + env.BRANCH_NAME
]
])
])
pipeline {
//tools {
// maven 'apache-maven-3.0.1'
// jdk "default"
//}
options {
buildDiscarder(logRotator(artifactDaysToKeepStr: '', artifactNumToKeepStr: '5', daysToKeepStr: '', numToKeepStr: '5'))
timeout(time: 60, unit: 'MINUTES')
ansiColor('xterm')
retry(1)
timestamps()
skipDefaultCheckout(true)
parallelsAlwaysFailFast()
disableConcurrentBuilds()
}
environment {
SCM_URL = ""
CREDS_ID = ""
}
agent {
docker {
image 'maven:3-alpine'
args '-u root -v /tmp:/tmp -v $HOME/.m2:/root/.m2'
reuseNode true
// executes on an executor with the label 'some-label' or 'docker'
//label "some-label || docker"
}
}
stages {
stage("Check_java_version") {
steps {
script {
try {
sh "java -version"
currentBuild.result = 'SUCCESS'
} catch(Exception err) {
currentBuild.result = 'FAILURE'
ansiColor('xterm') {
echo "\033[1;31mCaught exception: ${err}\033[0m"
}
throw err
}
echo "\033[0;31m RESULT: ${currentBuild.result} \033[0m"
}
}
}
stage("Check_mvn_version") {
when {
expression {
currentBuild.result != 'FAILURE'
}
}
steps {
sh "mvn -version"
}
}
stage('Gitlab_get_repo') {
steps {
script {
if ("${env.GITLAB_PROTOCOL}".toLowerCase() == null) {
env.GITLAB_PROTOCOL = 'ssh'
}
if ("${env.GITLAB_SERVER}".toLowerCase() == null) {
env.GITLAB_SERVER = 'gitlab_local_docker'
}
if ("${env.GITLAB_PROJECT}".toLowerCase() == null) {
env.GITLAB_PROJECT = 'java'
}
if ("${env.REPO_NAME}".toLowerCase() == null) {
env.REPO_NAME = 'simple-java-maven-app'
}
if ("${env.BRANCH_NAME}".toLowerCase() == null) {
env.BRANCH_NAME = 'master'
}
if ("${env.GITLAB_PROTOCOL}".toLowerCase() == 'ssh') {
SCM_URL = "git@${env.GITLAB_SERVER}:${env.GITLAB_PROJECT}/${env.REPO_NAME}.git"
CREDS_ID = 'gitlab-private-key'
}else if ("${env.GITLAB_PROTOCOL}".toLowerCase() == 'http') {
SCM_URL = "http://gitlab_local_docker/${env.GITLAB_PROJECT}/${env.REPO_NAME}"
CREDS_ID = 'gitlab-login'
}else {
SCM_URL = "https://${env.GITLAB_SERVER}/${env.GITLAB_PROJECT}/${env.REPO_NAME}"
CREDS_ID = 'gitlab-login'
}
}
checkout([$class: 'GitSCM', branches: [[name: "*/${env.BRANCH_NAME}"]],
doGenerateSubmoduleConfigurations: false,
extensions: [[$class: 'CloneOption', noTags: false, reference: '', shallow: true]],
submoduleCfg: [],
userRemoteConfigs: [[credentialsId: CREDS_ID, url: SCM_URL]]])
}
}
stage('Build') {
steps {
sh 'mvn -B -DskipTests clean package'
}
}
stage('Test') {
steps {
sh 'mvn test'
}
post {
always {
junit 'target/surefire-reports/*.xml'
}
}
}
stage('Deliver') {
steps {
sh './jenkins/scripts/deliver.sh'
}
}
}
post {
always {
echo 'This will always run'
}
success {
echo "success"
// notify users when the Pipeline success
//mail to: 'team@example.com',
// subject: "The Pipeline: ${currentBuild.fullDisplayName} has been finished successfully",
// body: "The job ${env.BUILD_URL}"
}
failure {
echo 'failure'
// notify users when the Pipeline fails
//mail to: 'team@example.com',
// subject: "Failed Pipeline: ${currentBuild.fullDisplayName}",
// body: "Something is wrong with ${env.BUILD_URL}",
// from: 'xxxx@yyyy.com',
// replyTo: 'yyyy@yyyy.com',
}
unstable {
echo 'This will run only if the run was marked as unstable'
}
changed {
echo 'This will run only if the state of the Pipeline has changed'
echo 'For example, if the Pipeline was previously failing but is now successful'
}
}
}
Т.е в данном файле предусмотрена вся логика для создания/сборки проекта. Я предусмотрел сборку проектов, по двум критериям:
- Автосборка проекта по пушу кода в гитлаб репозиторий (Используется master бранч).
- Если нужно собрать проект с другими параметрами, можно выполнить сборку «Build with Parameters».
И так, если кто-то запушит свой код в master-branch, то выполнится автосборка проекта по заданному пайплайну. Как по мне, — это очень логично выполнять все автоматом именно с мастер-ветки. Для остальных сборок (кастомных), можно собрать с некоторыми параметрами, выглядит это вот так:

Вот собственно и все, статья «Автосборка Java проектов через Jenkins в Unix/Linux» завершена.