Jenkins. Начало работы.

Основы работы с Linux. -> Jenkins. Начало работы.

Jenkins. Начало работы.

Установка Doсker контейнера.

Создадим docker-compose.yaml.

version: '3.7'
services:
  jenkins:
    image: jenkins/jenkins:lts
    privileged: true
    user: root
    ports:
      - 8081:8080
      - 50000:50000
    container_name: jenkins
    volumes:
      - .data:/var/jenkins_home

Устанавливаем.

docker-compose up --build

start page

Заходим на станицу http://localhost:8081/

start page

Копируем и вставляем пароль из файла .data/secrets/initialAdminPassword

Далее попадаем на страницу установки плагинов.

start page

Вся работа в Jenkins сводится к созданию так называемых Конвееров (Pipeline).

Они представляют собой последовательность действий для деплоя и тестиррования вашего приложения.

Эта последовательность содержиться в файле Jenkinsfile и написана на языке Groovy.

Groovy — объектно-ориентированный язык программирования разработанный для платформы Java как альтернатива языку Java с возможностями Python, Ruby и Smalltalk.

Возможности Groovy (отличающие его от Java):

— Статическая и динамическая типизация

— Встроенный синтаксис для списков, ассоциативных массивов, массивов и регулярных выражений

— Замыкания

— Перегрузка операций

Более того, почти всегда java-код — это валидный groovy-код.

Установка groovy.

Прежде всего необходимо поставить java.

Затем запускаем следующие команды.

curl -s get.sdkman.io | bash

source "$HOME/.sdkman/bin/sdkman-init.sh"

Создание образа для jenkins с Docker.

Создаем файл Dockerfile c установкой всего необходимого для Docker в контейнер.

FROM jenkins/jenkins:lts

ARG DOCKER_COMPOSE_VERSION=1.25.0

USER root
RUN apt-get update && \
   apt-get upgrade -y && \
   apt-get -y install apt-transport-https \
      ca-certificates \
      curl \
      gnupg2 \
      git \
      software-properties-common && \
   curl -fsSL https://download.docker.com/linux/$(. /etc/os-release; echo "$ID")/gpg > /tmp/dkey; apt-key add /tmp/dkey && \
   add-apt-repository \
      "deb [arch=amd64] https://download.docker.com/linux/$(. /etc/os-release; echo "$ID") \
      $(lsb_release -cs) \
      stable" && \
   apt-get update && \
   apt-get -y install docker-ce && \
   apt-get clean autoclean && apt-get autoremove && rm -rf /var/lib/{apt,dpkg,cache,log}/

RUN curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose && chmod +x /usr/local/bin/docker-compose

RUN usermod -aG docker jenkins && gpasswd -a jenkins docker
USER jenkins

Собираем образ.

docker build .

Изменяем файл docker-compose.yaml

image: jenkins/jenkins:lts

Заменим на

build: .

Так же добавим два volumes в которым перенаправим бинарники докера изнутри контейнера на локальную машину.

volumes:
  - ./data:/var/jenkins_home
  - /var/run/docker.sock:/var/run/docker.sock
  - /usr/local/bin/docker:/usr/local/bin/docker

Перезапускаем контейнер.

docker-compose up --build

Получаем проблему в правах.

start page

Дело в том что пользователь внутри контейнера имеет отличные от локальных UID и GID и следовательно не может писать в папку data.

Существует вариант все команды пропускать через скрипт entry-point.sh

#!/bin/sh
set -e

# first arg is `-f` or `--some-option`
# or first arg is `something.conf`
if [ "${1#-}" != "$1" ] || [ "${1%.conf}" != "$1" ]; then
    set -- redis-server "$@"
fi

# allow the container to be started with `--user`
if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; then
    find . \! -user redis -exec chown redis '{}' +
    exec gosu redis "$0" "$@"
fi

exec "$@"

В котором перебивать права на нужного пользователя.

COPY docker-entrypoint.sh /usr/local/bin/
ENTRYPOINT ["docker-entrypoint.sh"]

источник

обсуждение в StackOverflow

Ручная установка.

Для работы необходима Java машина

java -version

Добавляем ключ репозитория в систему.

wget -q -O - https://pkg.jenkins.io/debian/jenkins.io.key | sudo apt-key add -

Затем добавьте в адрес репозитория пакетов Debian в sources.list сервера:

sudo sh -c 'echo deb http://pkg.jenkins.io/debian-stable binary/ > /etc/apt/sources.list.d/jenkins.list'

Обновляем список репозиториев.

sudo apt update

Установка

sudo apt install jenkins

Старт сервера.

sudo systemctl start jenkins

Проверка сервера.

sudo systemctl status jenkins

start page

Настраиваем виртуальный хост nginx.

server {
        listen 80;
        server_name jenkins.wezom.webmonstr.com;
        location / { 
                proxy_pass http://localhost:8080;
        }
}

Задача.

Предположим что у нас есть удаленная машина со средой разработки какого-нибудь проекта и командами для тестирования.

Мы хотим переодически запускать тесты на удаленной машине.

Для того, чтобы jenkins мог успешно установить своего агента на удаленном хосте должна стоять java.

Поставим вариант headless без всяких лишних графических “прибамбасов”.

sudo apt install openjdk-8-jre-headless

Добавление нового пользователя с правами захода на удаленный хост по ssh.

start page

start page

start page

start page

start page

Мы выбираем способ доступа по ssh ключу.

Поэтому в jenkins копируем приватный ключ из файла ~/.ssh/id_rsa

А на удаленной машине добавляем публичный ключ в файл .ssh/authorized_keys, который берем из ~/.ssh/id_rsa.pub

После добавления пользователя можно приступить к созданию новой среды сборки (сборщика) и привязать ее к удаленному хосту, на котором мы собираемся запускать тесты.

start page

start page

start page

При создании среды (Ноды) необходимо указать рабочий каталог, IP адрес и порт во вкладке Дополнительно если он отличается от стандартного 22-го.

Также необходимо выбрать пользователя, под которым будет выполнен вход на удаленную машину.

И указать метку для удобной привязке к нему задач.

Также можно указать количество вокеров в пуле сборщика на тот случай, если на удаленной машине есть доп. ресурсы в виде простаивающих ядер процессора.

start page

Наконец можно приступить к созданию новой задачи.

start page

Первым делом привязываем задачу к сборщику по его метке.

start page

Далее выбираем тип действия сборщика.

В простейшем случае выполнение команды BASH.

start page

start page

start page

В команде мы вначале прыгаем внутрь домашней директории для того, чтобы выйти за пределы рабочего каталога jenkins, который он создает для того, чтобы не “засерать” рабочую область проекта.

Запускаем сборку.

start page

Проверяем результат.

start page

start page

Основы работы с Linux. -> Jenkins. Работа с GIT.

Jenkins. Работа с GIT.

Для работы с репозиторием в начале необходимо добавить пользователя и его приватные ключи, используемые в git.

start page

Затем можно создать следующую задачу типа Pipeline.

node('pi') {
    stage('Git clone') {
        dir('/home/zdimon/project01') {
            git credentialsId: '0acbc9ab-ca88-4fbd-a13e-59c3c81c1722', url: 'git@github.com:zdimon/web-starter.git', branch: 'master'

            script {
                def stdOut = sh(script: "pwd && ls -alh",
                    returnStdout: true)
                echo stdOut
            }
        }
    }
}

node(‘pi’) - указываем в какой ноде (на каком сервере) работать.

stage(‘Git clone’) - обозначиваем статию.

dir(‘/home/zdimon/project01’) { … } - говорим в какой директории работать.

Если директорию не указывать, то jenkins для каждой задачи будет создавать отдельный каталог внутри папки workspace.

При формировании команды клона я воспользовался помошником.

start page

start page

Далее в блоке script мы выполням bash команду, заносим ее вывод в файл и выводим его в консоль.

    script {
        def stdOut = sh(script: "pwd && ls -alh",
            returnStdout: true)
        echo stdOut
    }

Результат.

start page

start page

Привязываем запрос на сборку к git хуку.

В папке .git есть каталог hooks c шаблонами bash скриптов, срабатывающих в разных ситуациях.

Создаем новый файл post-commit

#!/bin/sh
echo "Post commit"

Добавляем права на выполнение

chmod +x ./git/hooks/post-commit

Теперь этот скрипт будет выполнятся при каждом коммите.

Можно в него добавить запрос на сервер с jenkins.

curl http://yourserver/jenkins/git/notifyCommit?url=<URL of the Git repository>

Этот запрос просканирует все задачи, которые сконфигурированы с проверкой специфичного url.

Еще один вариант запуска.

#!/bin/bash
/usr/bin/curl --user USERNAME:PASS -s \

http://jenkinsci/job/PROJECTNAME/build?token=1qaz2wsx

Для работы этого запроса необходимо сконфигурировать задачу удаленным триггером с токеном.

Запуск определенной задачи по ее имени.

curl http://localhost:8080/job/someJob/build?delay=0sec

curl -X POST http://:/job/job-name-in-jenkins/build?delay=0sec --user user:password
Основы работы с Linux. -> Jenkins. Скрипты на groovy.

Jenkins. Скрипты на groovy.

Последовательность операций сборки или тестирования может быть оформлен в виде pipeline (конвеера) в отдельном файле Jenkinsfile и положен в репозиторий.

Это позволит:

  • автоматически сгенерировать конвеер для всех бранчей и pull request-ов.

  • держать логику тестов в проекте для аудита

Jenkinsfile скрипт может быть написан в декларативном или императивном стиле.

Декларативный стиль предполагает использование высокоуровневых конструкций языка, которые решают комплексные задачи за раз.

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

Пример как может выглядеть процесс сборки, тестирования и деплоя проекта.

start page

Конвеер состоит ис следующих элементов.

нода (node) - сервер, на котором все исполняется

стадия (stage) - блок, определяющий набор операций для одной задачи (сборки тестирования, деплоя и т.д.). Эти стадии потом будут красиво оформлены интерфейсом Jenkins с указанием времени, статуса и пр.

шаг (step) - одна операция. Фактически указание что делать в определенный момент времени.

Рассмотрим варианты оформления Jenkinsfile.

Пример оформления конвеера в декларативном стиле.

pipeline {
    agent any 
    stages {
        stage('Build') { 
            steps {
                // 
            }
        }
        stage('Test') { 
            steps {
                // 
            }
        }
        stage('Deploy') { 
            steps {
                // 
            }
        }
    }
}

agent any - говорит о том что выполнять нужно на любом агенте

Для декларативных скриптов применяется агент, для императивных нода.

Пример императивного подхода.

node('node name') {  
    stage('Build') { 
        // 
    }
    stage('Test') { 
        // 
    }
    stage('Deploy') { 
        // 
    }
}

Рассмотрим еще один декларативный пример

pipeline { 
    agent any 
    options {
        skipStagesAfterUnstable()
    }
    stages {
        stage('Build') { 
            steps { 
                sh 'make' 
            }
        }
        stage('Test'){
            steps {
                sh 'make check'
                junit 'reports/**/*.xml' 
            }
        }
        stage('Deploy') {
            steps {
                sh 'make publish'
            }
        }
    }
}

pipeline - корневой блок всего конвеера

agent - сущность, которая будет выполнять код (нода, или компьютер, или докер образ)

sh - выполняет shell команду

junit - плагин, собирающий статистику и формирующий xml отчетссылка

Установка агента.

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

 agent any

выполнит на любом доступном агенте

 agent none

При этом мы заставляем прописывать агента для каждого шага, а для всего конвеера устанавливаем в none.

 agent { label 'my-defined-label' }

выберет агента по метке

 agent { node { label 'labelName' } }

Работает так же как и с меткой но позволяет использовать дополнительные настройки для ноды, например customWorkspace.

agent {
    node {
        label 'my-defined-label'
        customWorkspace '/some/other/path'
    }
}

customWorkspace может быть как относительным так и абсолютным путем и позволяет выйти за пределы дефолтного воркспейса (рабочего каталога)

Агент для докер образа.

agent {
    docker {
        image 'maven:3-alpine'
        label 'my-defined-label'
        args  '-v /tmp:/tmp'
    }
}

args - передает аргумены команде docker run

Образ можно собрать из файла Dockerfile

agent {
    dockerfile {
        filename 'Dockerfile.build'
        dir 'build'
        label 'my-defined-label'
        additionalBuildArgs  '--build-arg version=1.0.2'
        args '-v /tmp:/tmp'
    }
}

То же что запуск

"docker build -f Dockerfile.build --build-arg version=1.0.2 ./build/

Пример запуска команды внутри контейнера в декларативном стиле.

pipeline {
    agent { docker 'maven:3-alpine' } 
    stages {
        stage('Example Build') {
            steps {
                sh 'mvn -B clean verify'
            }
        }
    }
}

Агента можно определять для разных стадий.

pipeline {
    agent none 
    stages {
        stage('Example Build') {
            agent { docker 'maven:3-alpine' } 
            steps {
                echo 'Hello, Maven'
                sh 'mvn --version'
            }
        }
        stage('Example Test') {
            agent { docker 'openjdk:8-jre' } 
            steps {
                echo 'Hello, JDK'
                sh 'java -version'
            }
        }
    }
}

Конструкция post

Позволяет выполнить код при определенных обстоятельствах, условиях или результатах выполнения стадий (в зависимосте где его всунуть).

Условия.

allways - выполнится всегда

failure - только когда провален

success - только когда успешно выполнен

changed - только если результаты отличаются от предыдущего запуска

fixed - если предыдущий запуск был провален а текущий нет

regression - наоборот

Пример.

pipeline {
    agent any
    stages {
        stage('Example') {
            steps {
                echo 'Hello World'
            }
        }
    }
    post { 
        always { 
            echo 'I will always say Hello again!'
        }
    }
}

Можно определять сразу несколько вариантов.

pipeline {
    agent any
    stages {
        stage('Test') {
            steps {
                sh 'echo "Fail!"; exit 1'
            }
        }
    }
    post {
        always {
            echo 'This will always run'
        }
        success {
            echo 'This will run only if successful'
        }
        failure {
            echo 'This will run only if failed'
        }
        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'
        }
    }
}

Переменные окружения. Директива environment.

Пары ключ - значение, которые передадуться внутрь каждого шага.

pipeline {
    agent any
    environment { 
        CC = 'clang'
    }
    stages {
        stage('Example') {
            environment { 
                AN_ACCESS_KEY = credentials('my-predefined-secret-text') 
            }
            steps {
                sh 'printenv'
            }
        }
    }
}

Как видим существует иерархия видимости переменных в зависимости куда их втыкать ко всему конвееру или конкретному шагу.

Еще пример вывода переменных окружения.

pipeline {
    agent {
        label '!windows'
    }

    environment {
        DISABLE_AUTH = 'true'
        DB_ENGINE    = 'sqlite'
    }

    stages {
        stage('Build') {
            steps {
                echo "Database engine is ${DB_ENGINE}"
                echo "DISABLE_AUTH is ${DISABLE_AUTH}"
                sh 'printenv'
            }
        }
    }
}

Директива options.

Позволяет конфигурировать некоторые полезные опции при выполнении конвеера.

buildDiscarder - управляет сохранением артифактов сборки и вывода в консоль

checkoutToSubdirectory - проверяет наличие произвольного каталога

options { checkoutToSubdirectory('foo') }

disableConcurrentBuilds - помогает исключить одновременное использование разделяемого ресурса

newContainerPerStage - будет создавать по новому контейнеру на каждую стадию

timeout - таймаут по которому нужно прервать конвеер

pipeline {
    agent any
    options {
        timeout(time: 1, unit: 'HOURS') 
    }
    stages {
        stage('Example') {
            steps {
                echo 'Hello World'
            }
        }
    }
}

retry - сколько раз повторить после провала

Опции можно устанавливать как ко всему конвееру так и к определенным стадиям.

Триггеры

Определяют условие при котором конвеер должен быть запущен.

cron - определяет расписание для запусков

triggers { cron('H */4 * * 1-5') }

pollSCM - определяет кроновый интервал для проверки изменений в исходниках. Если изменения обнаружены, то запускается конвеер.

triggers { pollSCM('H */4 * * 1-5') }

upstream - принимает строку с задачами через запятую и порог. Если хоть одна задача завершилась с указанным порогом, то конвеер перезапустится.

 triggers { upstream(upstreamProjects: 'job1,job2', threshold: hudson.model.Result.SUCCESS) }

Пример.

pipeline {
    agent any
    triggers {
        cron('H */4 * * 1-5')
    }
    stages {
        stage('Example') {
            steps {
                echo 'Hello World'
            }
        }
    }
}

triggers{ cron(‘H/15 * * * *’) } - каждые 15 мин.

triggers{ cron(‘H(0-29)/10 * * * *’) } - каждые 10 мин в первой половине часа

Директива tools.

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

pipeline {
    agent any
    tools {
        maven 'apache-maven-3.0.1' 
    }
    stages {
        stage('Example') {
            steps {
                sh 'mvn --version'
            }
        }
    }
}

Директива input.

Позволяет забирать данные, вводимые пользователем.

pipeline {
    agent any
    stages {
        stage('Example') {
            input {
                message "Should we continue?"
                ok "Yes, we should."
                submitter "alice,bob"
                parameters {
                    string(name: 'PERSON', defaultValue: 'Mr Jenkins', description: 'Who should I say hello to?')
                }
            }
            steps {
                echo "Hello, ${PERSON}, nice to meet you."
            }
        }
    }
}

К примеру, мы можем это использовать для получения разрешения на деплой кода на продакшин сервер после успешных тестов.

Директива when

Определяет условие, при котором нужно выполнять стадию.

Например если бранч соответствует маске.

when { branch pattern: "release-\\d+", comparator: "REGEXP"}

Когда билд содержит тег.

when { buildingTag() }

Когда в логе содержится искомая строка.

 when { changelog '.*^\\[DEPENDENCY\\] .+$' }

Если исходники содержат интересующие файлы

when { changeset "**/*.js" }

При запросах Pull Request

when { changeRequest() }.

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

when { environment name: 'DEPLOY_TO', value: 'production' }

Если присутствует тег

when { tag "release-*" }.

Отрицание

 when { not { branch 'master' } }

Если сработал определенный триггер

when { triggeredBy 'TimerTrigger' }

Пример.

pipeline {
    agent any
    stages {
        stage('Example Build') {
            steps {
                echo 'Hello World'
            }
        }
        stage('Example Deploy') {
            when {
                branch 'production'
            }
            steps {
                echo 'Deploying'
            }
        }
    }
}

Условия можно комбинировать.

when { branch ‘production’ environment name: ‘DEPLOY_TO’, value: ‘production’ }

Либо влаживать друг в друга.

when {
    allOf {
        branch 'production'
        environment name: 'DEPLOY_TO', value: 'production'
    }
}

Последовательные стадии

Стадии могут быть не только вложенными, но и определять последовательность при помощи директив steps, stages, parallel или matrix.

Паралельное выполнение (paralel).

    stage('Parallel In Sequential') {
        parallel {
            stage('In Parallel 1') {
                steps {
                    echo "In Parallel 1"
                }
            }
            stage('In Parallel 2') {
                steps {
                    echo "In Parallel 2"
                }
            }
        }
    }

stage может содержать только один блок paralel.

Установка параметра failFast true позволяет прервать выполнение в случае провала онтой из стадий.

    stage('Parallel Stage') {
        when {
            branch 'master'
        }
        failFast true
        parallel {
            stage('Branch A') {
                agent {
                    label "for-branch-a"
                }
                steps {
                    echo "On Branch A"
                }
            }
          ....

Matrix

Позволяет конструировать матрицу из ячеек.

Например так можно создать матрицу 4 на 3 из 12 значений.

matrix {
    axes {
        axis {
            name 'PLATFORM'
            values 'linux', 'mac', 'windows'
        }
        axis {
            name 'BROWSER'
            values 'chrome', 'edge', 'firefox', 'safari'
        }
    }
    // ...
}

Если теперь вложить внутрь стадии, то они выполнятся для каждой ячейки последовательно.

matrix {
    axes {
        axis {
            name 'PLATFORM'
            values 'linux', 'mac', 'windows'
        }
    }
    stages {
        stage('build') {
            // ...
        }
        stage('test') {
            // ...
        }
        stage('deploy') {
            // ...
        }
    }
}

Директива script

Внутрь этой директивы может быть вложен код groovy и выполнен на уровне одного шага.

pipeline {
    agent any
    stages {
        stage('Example') {
            steps {
                echo 'Hello World'

                script {
                    def browsers = ['chrome', 'firefox']
                    for (int i = 0; i < browsers.size(); ++i) {
                        echo "Testing the ${browsers[i]} browser"
                    }
                }
            }
        }
    }
}

Условия

node {
    stage('Example') {
        if (env.BRANCH_NAME == 'master') {
            echo 'I only execute on the master branch'
        } else {
            echo 'I execute elsewhere'
        }
    }
}

Императивные скрипты.

Исключения

node {
    stage('Example') {
        try {
            sh 'exit 1'
        }
        catch (exc) {
            echo 'Something failed, I should sound the klaxons!'
            throw
        }
    }
}