Автосборка Java проектов через Jenkins в Unix/Linux

Набрался немного опыта по установке и настройке Jenkins-а. Разобрался как писать свои собственные pipelin-ы и пришло время сделать билд и собрать что-то на Java. Для своего примера, я возьму код с Github-а и залью в гитлаб-сервер (локальный).

Полезное чтиво:

Установка Jenkins в Unix/Linux

Работа с Jenkins-CLI в Unix/Linux

Установка Docker в Debian’s

Установка Docker в RedHat’s

Установка 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 скрипт с SCM
Pipeline скрипт с SCM

Я выбрал «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» завершена.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте как обрабатываются ваши данные комментариев.