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
Заходим на станицу http://localhost:8081/
Копируем и вставляем пароль из файла .data/secrets/initialAdminPassword
Далее попадаем на страницу установки плагинов.
Вся работа в 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
Получаем проблему в правах.
Дело в том что пользователь внутри контейнера имеет отличные от локальных 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"]
Ручная установка.
Для работы необходима 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
Настраиваем виртуальный хост 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.
Мы выбираем способ доступа по ssh ключу.
Поэтому в jenkins копируем приватный ключ из файла ~/.ssh/id_rsa
А на удаленной машине добавляем публичный ключ в файл .ssh/authorized_keys, который берем из ~/.ssh/id_rsa.pub
После добавления пользователя можно приступить к созданию новой среды сборки (сборщика) и привязать ее к удаленному хосту, на котором мы собираемся запускать тесты.
При создании среды (Ноды) необходимо указать рабочий каталог, IP адрес и порт во вкладке Дополнительно если он отличается от стандартного 22-го.
Также необходимо выбрать пользователя, под которым будет выполнен вход на удаленную машину.
И указать метку для удобной привязке к нему задач.
Также можно указать количество вокеров в пуле сборщика на тот случай, если на удаленной машине есть доп. ресурсы в виде простаивающих ядер процессора.
Наконец можно приступить к созданию новой задачи.
Первым делом привязываем задачу к сборщику по его метке.
Далее выбираем тип действия сборщика.
В простейшем случае выполнение команды BASH.
В команде мы вначале прыгаем внутрь домашней директории для того, чтобы выйти за пределы рабочего каталога jenkins, который он создает для того, чтобы не “засерать” рабочую область проекта.
Запускаем сборку.
Проверяем результат.
Основы работы с Linux. -> Jenkins. Работа с GIT.
Jenkins. Работа с GIT.
Для работы с репозиторием в начале необходимо добавить пользователя и его приватные ключи, используемые в git.
Затем можно создать следующую задачу типа 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.
При формировании команды клона я воспользовался помошником.
Далее в блоке script мы выполням bash команду, заносим ее вывод в файл и выводим его в консоль.
script {
def stdOut = sh(script: "pwd && ls -alh",
returnStdout: true)
echo stdOut
}
Результат.
Привязываем запрос на сборку к 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 скрипт может быть написан в декларативном или императивном стиле.
Декларативный стиль предполагает использование высокоуровневых конструкций языка, которые решают комплексные задачи за раз.
При императивном подходе каждую задачу необходимо более подробно описывать.
Пример как может выглядеть процесс сборки, тестирования и деплоя проекта.
Конвеер состоит ис следующих элементов.
нода (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
}
}
}