jenkins slave что такое
Балансируем нагрузку в Jenkins
В Arenadata мы используем Jenkins для CI. Почему? Как бы банально это ни звучало — так исторически сложилось. Мы хранили код в GitHub, когда там ещё не было Actions, и продолжаем хранить, потому что много работаем с Open Source. За три года работы с Jenkins мы неплохо разобрались в нём, в том числе научились быстро масштабироваться, чтобы удовлетворять запросы разработки. В этой статье хочу поделиться тем, что мы успели понять про разные способы балансировки нагрузки в Jenkins. Если вам это близко, добро пожаловать под кат.
Для тех, кто давно эксплуатирует Jenkins и кому проблемы, связанные с его эксплуатацией, набили оскомину, сразу напишу список того, о чём я не буду рассказывать в этой статье:
как быстро обновлять плагины Jenkins и не сломаться;
как следить за адом зависимостей плагинов;
что делать, если наш плагин перестали поддерживать (да и надо ли это обсуждать, речь ведь про Open Source).
Речь пойдёт о том, какими способами мы решали задачу балансировки нагрузки в Jenkins и что из этого получилось.
Небольшие вводные: Jenkins — это фреймворк автоматизации, написанный на языке Java. Понятно, что для успешного использования любого фреймворка неплохо бы владеть языком, на котором этот фреймворк написан, но где вы видели отделы DevOps, которые умеют писать на Java? Вот и наша DevOps-команда на Java не пишет. Однако пока нам удавалось успешно справляться со всеми вызовами, используя Jenkins.
Как в Jenkins балансируют нагрузку
Для каждого узла указаны:
метки (labels) — описывают задачи, которые могут запускаться на узле; название узла тоже является меткой;
исполнители (executors) — их количество определяет количество одновременно запущенных задач на узле Jenkins.
Все билды встают в очередь, зная label узла, на которой должны выполниться; как только на узле освобождается executor, запускается билд.
Итак, проблема понятна, давайте её решать.
инфраструктура в облаке;
множество команд разработки, потребности которых постоянно растут;
разнообразные требования по мощности к узлам Jenkins;
DevOps умеют писать на Python, но не на Java.
Проблемы
Управление состоянием кластера Jenkins.
Очереди на выполнение. «Слишком большие — дайте мощностей!»
Бережное использование ресурсов. «Очереди нет, надо убрать лишнее».
Когда узлов становится больше 10, становится сложно визуально понять, какие проекты используют выделенные мощности, а какие нет. Нужны мониторинг и аналитика.
Нужно постоянно помнить о необходимости убираться на узлах, чтобы они были в работоспособном состоянии.
Конкуренция на узлах за ресурсы.
Подход первый: Ansible
Начинали мы с самой простой задачи: нам надо было оперативно добавлять и удалять мощности из кластера. Конечно же, пишем ansible-playbook и сразу наслаждаемся бенефитами.
Набор софта на узле зафиксирован и версионируется.
Чтобы добавить в кластер новый узел, создаём виртуалку с заранее известным SSH-ключом, добавляем в Inventory и запускаем плейбук. Это действие, конечно же, легко автоматизируется, тут кому как будет удобней: Terraform + Ansible и динамический Inventory, а можно и на чистом Ansible. Получаем узел в кластере и данные узла в мониторинге.
Можно пытаться оперативно удалять и добавлять узлы, чтобы сэкономить деньги в ручном режиме. На самом деле это не работает, потому что узлы обычно добавляются там, где мало мощностей, а там, где их хватает, никто не жалуется. Поэтому сподвигнуть вас удалить лишние узлы могут только сбор метрик и аналитика.
Мы периодически дорабатывали плейбук, обычно меняя список пакетов по умолчанию или их версий. Держали всё, что нужно на всех узлах, поддерживали несколько операционных систем и архитектур.
Для балансировки нагрузки мы сделали небольшой пул узлов с меткой «docker», добавили побольше исполнителей и стали там запускать все незатратные процессы. А для проектов и стадий пайплайнов, которые требовали много ресурсов, сделали отдельные узлы с одним исполнителем.
В погоне за оптимизацией эти пулы узлов начинают пересекаться (некоторые узлы имеют более одной метки): выделенные мощные узлы продвигали очередь заданий по проектам, а общие узлы — это способ оптимизации использования ресурсов. Когда проектов всего два-три, всё выглядит просто. Но когда их становится пять, выбирать оптимальное минимальное количество выделенных и общих ресурсов становится сложно, все будет происходить интуитивно и соответственно не контролируемо. И с увеличением количества проектов ваш меточный ад будет всё страшнее. Кажется, что решить эту проблему получится только с помощью снятия метрик пайплайнов и написания алгоритма принятия решений, который будет выдавать нужные значения. И наверняка вам захочется интегрировать это всё в роль и гонять по расписанию.
Спойлер: мы так не сделали.
Да, управление кластером становится простым и лёгким, а ручные манипуляции сводятся к минимуму. Но я, если честно, спустя год начал ненавидеть постоянные добавления и удаления YAML.
С одной стороны, мы приблизились к решению проблем из пунктов 1, 2 и 3. Но я ещё ничего не сказал о мастере. Да и если хочется заботиться о ресурсах как следует, то есть ещё большой резерв по совершенствованию решений проблем из пунктов 2 и 3.
Подход второй: Slave Setup Plugin
В первом приближении стало понятно, как администрировать кластер. Правда, следить за метками было всё ещё неудобно, а идея, которая позволит улучшить ситуацию, выглядела сложной в реализации. Мониторинга и аналитики до сих пор не было. Задачи, которые запускались на docker-узлах, периодически «перерастали» их, это тоже было неприятно.
В следующей итерации мы нашли прекрасный Slave setup plugin, который позволяет выполнять произвольный скрипт при запуске slave-узла и в глобальных настройках на master-узле, чтобы включать slave по мере надобности и выключать его спустя какое-то время бездействия.
Пришло время сэкономить немного денег: будем включать/выключать узлы по требованию. Таким образом можно создать узлы с избытком, чтобы в определённых пределах нагрузки не возникло больших очередей. Переплачивать мы будем только за диски. Хотя и это не обязательно, ведь slave-узел можно не включать/выключать, а создавать/удалять, для master никакой разницы нет.
Тут ничего сложного:
переписываем наш Ansible, чтобы он умел распознавать узлы, умеющие выключаться/включаться по требованию;
пишем скрипт, который будет усыплять узел, а потом нежно будить его на работу по первому зову из очереди; вооружаем master этим скриптом.
Тут нужно кое-что пояснить. Для управления кластером Jenkins мы используем собственную платформу Arenadata Cluster Manager (ADCM). Она хранит в себе информацию об узлах и умеет их создавать, включать, отключать и удалять. Наш скрипт запрашивает эти операции, а вся логика содержится в ansible-плейбуках ADCM. В общем случае же достаточно, чтобы скрипт дожидался доступности узлов по SSH после их запуска, а после выключения дожидался, пока облако закончит операцию.
Создавать узлы можно напрямую через rest API Jenkins или выполнять groovy-скрипты на мастере, отправляя тело скрипта так же — через rest api.
Мы пошли вторым путём.
Обновленный кусок плейбука Ansible
Осталю ссылку на репозиторий Cloudbees со всякими скриптами для администрирования Jenkins, там можно черпать вдохновление по написанию скриптов как ансибле выше. Мы добавляем узлы с помощью groovy-скрипта, но у Jenkins много клиентов на различных языках, и вы сможете использовать в Ansible то, что вам ближе.
В итоге нашим разработчикам стало немного легче, а узлов стало гораздо больше. Хотя мы постарались снизить расходы, в работе иногда возникают проблемы, из-за которых затраты могут оказаться выше запланированных.
Например, при обилии узлов некоторые задачи зависали не из-за Jenkins, а из-за исполняемых в них процессов. DevOps не всегда предусматривает таймаут, а заметить зависшую задачу в пользовательском интерфейсе теперь уже сложно, ведь мониторинга метрик пайплайнов у нас ещё не было.
Если вы плохо следите за узлами, то на каких-нибудь из них может кончиться свободное место, Jenkins пометит такие узлы как «нездоровые» и забудет их выключить. Хотелось бы, чтобы он этого не забывал.
Ещё может зависнуть исполнение задачи на очистку. И вместо очистки и усыпления узел продолжит работать, а компания — платить. Да, уборка на jenkins-узлах обычно сводится именно к удалению всего, что осталось после контейнеров и образов.
Очистка jenkins slave узлов
С помощью Slave Setup Plugin мы стали экономнее, но, кажется, с метриками и аналитикой было бы лучше, не были решены и другие проблемы.
Подход третий: динамические узлы
Мы посмотрели на плагин aws и захотели нечто подобное. Динамическое выделение узлов по требованию решает все вышеописанные проблемы, и, как выяснилось, сделать это не так уж и сложно. С freestyle-проектами без знаний Java точно ничего не выйдет; казалось, что и с остальными тоже. Я даже начал забрасывать удочку к нашим Java-разработчикам, а потом нас осенило.
Что сделать, чтобы вас тоже осенило? Мы давно начали использовать pipeline-проекты и их производные: там уже можно делать вид, что ты программист на Groovy, и реализовывать вещи сложнее тех, которыми ограничиваются freestyle-проекты.
А если мы пишем код, то хочется его переиспользовать. Jenkins это умеет: сразу смотрите на shared libraries, хотя это и необязательно. Учить Groovy, скорей всего, будете по документации и по мануалам Jenkins и Stackoverflow. Я провёл за чтением доки и Stackoverflow не один и даже не два часа, а вот документацию Jenkins по shared libraries читал по диагонали, а оказалось, что самый интересный пример был именно там.
Давайте рассмотрим пример с определением собственных DSL-методов; в первую очередь обратите внимание на синтаксис.
If called with a block, the call method will receive a Closure. The type should be defined explicitly to clarify the intent of the step, for example:
def call(Closure body) <
The Pipeline can then use this variable like any built-in step which accepts a block:
Пример не исчерпывающий, ведь можно было бы сделать вот так:
Если честно, мне этот синтаксис до сих пор взрывает мозг, я не понимаю, как это работает. То, что closure — это объект и ссылку на него можно передавать в другие функции, вроде понятно, но почему-то из официальной документации эта картинка не образовалась. Один коллега сказал, что это похоже на каррирование. Может, кто-нибудь объяснит в комментариях, как работает этот пример, и поделится ссылкой на документацию языка, а не на документацию Jenkins?
А мы пока двинемся дальше и посмотрим на этот пример:
Scripted-pipeline синтаксис с парочкой самописных dsl-методов. WithJenkinsSlave и есть наш метод, который создаст узел, где запустится тело node, а по окончании удалит узел из Jenkins и облака. Наш slave будет иметь 10 ядер, 10 Гб памяти, диск на 186 Гб, а тип диска будет зависеть от облачного провайдера. В области видимости есть переменная DYNAMIC_SLAVE (имя нашего slave-узла), которую мы используем в качестве аргумента для DSL-метода node. Дальше уже более знакомые методы scripted pipeline.
С помощью метода withGHStatus отправим статус на GitHub о начале проверки, поймаем исключения от body, если такое будет, и отправим обратную связь на GitHub.
На всякий случай не забывайте про таймаут, чтобы не зависнуть. Его можно спрятать в withJenkinsSlave со значением по умолчанию.
Что такое jenkins-slave10? Мы будем поднимать, регистрировать и удалять узел, запуская bash. Для нас это единственный недостаток. Нам потребуется постоянный узел с достаточно большим количеством исполнителей, чтобы запускать легковесные скрипты. И такой узел уже есть — jenkins-slave10. На мастере это запускать не стоит. Он почти всегда онлайн, но ничто не мешает его выключать, когда узел простаивает.
В теле withJenkinsSlave у нас используется Bash и Terraform, у последнего есть провайдеры под большинство облаков и гипервизоров, так что подход практически универсален. Мы взяли packer, приготовили образ наших будущих динамических узлов. Манифест для packer не прикладываю, это мало кому будет интересно.
Получившийся образ мы используем в Terrafrom, упакованный в Docker. В образе у нас находится манифест для создания виртуальной машины и два плейбука для добавления и удаления машины в кластере Jenkins. Ansible, который добавляет узел в кластер, тот же самый, что и раньше.
Ну и, наконец, тело самой функции из нашей функции из shared-library:
Выбираем случайное название, вызываем Terraform, сохраняем состояние в стэш stash — всё, данные для удаления есть. Регистрируем узел в Jenkins через Ansible, выполняем тело closure body, а потом всё удаляем.
В best practices по Jenkins говорится, что злоупотреблять Groovy не стоит, поскольку этот код исполняется на master. Поэтому в теле функции используются sh, Ansible и Terraform, а не HTTP request plugin. К тому же эти инструменты хорошо перекликаются с задачами, которые обычно решает DevOps, если в его распоряжении есть облако.
В итоге мы имеем ряд следующих достоинств и недостатков.
Ресурсы нужные для выполнения задачи описаны прямо в пайплайне.
Мы получили единую точку входа в узел и можем реализовывать в одном месте все хорошие практики, которые нам покажутся таковыми.
На самом деле, так можно добавлять не только slave-узлы, но и любые окружения, которые нам могут понадобиться, например, сервер Selenoid для наших UI-тестов.
Всё ещё нужен узел, хоть и маломощный. В качестве альтернативы можно использовать Kubernetes-плагин и заменить узел с Docker на Kubernetes-под.
Это решение годится только для типов проекта pipeline и его производных. Нас это в целом устраивает.
Если PR синхронизируется новым пушем, то текущий билд обычно отменяют. Так вот, этот cancel может прилететь прямо в наш terraform destroy или ansible-t remove, что неприятно.
Рекомендую маркировать все создаваемые виртуальные машины меткой из переменной окружения, которая есть в каждой сборке Jenkins — BUILD_TAG из-за недостатка №3. Мы обзавелись ещё одной задачей которая проверяет Jenkins/облако на наличие таких остатков и удаляет их.
Помимо библиотеки Jenkins’а у наших QA есть ещё фреймворк pytest. Мы используем его для формирования тестовых окружений, pytest не всегда убираются из-за ошибки в коде или из-за того, что сборку неожиданно отменили. Поскольку нам известно правило формирования BUILD_TAG, сверившись с Jenkins, мы можем понять, какие виртуальные машины не имеют выполняемых родительских сборок, и их можно легко удалить, почистив облако, Jenkins и так далее.
Итого, чего нам удалось добиться:
Мы настраиваем кластер и управляем им через Ansible. Но, поскольку о master’е речи не шло, только о slave-узлах, вопрос решён лишь наполовину или меньше. Жизненный цикл master’а надо покрывать тестами; недостаточно написать плейбук, который будет обновлять master’а в надежде, что ничего не взорвётся. То есть задача весьма нетривиальная.
Что же касается Jenkins job, то их мы храним в виде YAML и накатываем с помощью Jenkins job builder. Утилита конвертирует YAML в XML описание job и грузит их через rest api, написана на Python, поэтому дорабатывать её мы можем самостоятельно, если на это есть необходимость.
Очередей в Jenkins больше нет, как и простаиващих ресурсов.
Получили единую точку входа для всех стадий пайплайнов в виде библиотеки, что позволяет нам в одном месте реализовывать все наши лучшие практики, но появилась задача по CI для самой библиотеки, которую мы пока не решали.
Нет задачи по поддержанию узлов в рабочем состоянии, но есть задача по приборке мусора. Из Jenkins мы получаем данные о запущенных сборках и всегда можем пойти и подчистить в облаке виртуалки, которые остались по каким либо причинам, например cancel самого билда о котором мы упоминули ранее.
Стейджи изолированы виртуалками, никакой конкуренции.
Нет меток — нет проблем.
Мы используем такой же подход на kvm-гипервизоре архитектуры PowerPC. Но там ресурсы ограничены: как ожидать готовности гипервизора обслуживать наши запросы нам еще предстоит исследовать, пока мы еще не упирались в его максимальный ресурс.
Итого, мы полностью решили задачи 2, 3, 5, 6 и 7, а над задачами 1 и 4 ещё предстоит поработать для достижения идеала. Из смешного то, что никакой балансировки в общем то и нет, все по требованию здесь и сейчас. Если сравнивать например с GitHub Actions, то есть подозрение, что так сделать не получится, те решения которые я видел в Open Source основывались на анализе очереди и добавлении/удалении раннеров.
В заключение хочу поблагодарить своего коллегу Дмитрия К. за проверку гипотез и исходный код.
linux-notes.org
Я ранее рассказывал как можно установить Jenkins на сервер. Сейчас, я хотел бы поделится своей заметкой по установке Jenkins-а и Jenkins-slave. Я для своего примера, буду использовать Docker + docker-compose чтобы поднять все необходимое. Конечно, это не самый хороший способ сделать отказоустойчевый сервер. Но тем не менее — у меня на маке все работает. Тем более, я настроил данное чудо не для ПРОД-а, а для локальной лабы. Чтобы более поробно изучить дженкинс. Хотелось бы сказать, ребят, если вы будете выбирать между CI/CD — не берите дженкинс (ИМХО). Есть ума других крутых тулов. Я хочу многие из них попробовать и конечно же — написать статейку в виде заметки.
Установка Jenkins в Unix/Linux
Как я говорил ранее, я буду использовать докер для установки дженкинса. ОС которую я использую — Mac OS X. Многие скажует, да какая разница, ты же запускаешь в докере. Но на самом деле — докер немного по разному работает на разных Unix/Linux ОС. Немного пришлось поплясать с бубном, чтобы зависти все это чудо на маке.
Мой docker-compose.yml файл выглядит следующим образом:
Кто работает с докер-компос, тот сможет прочитать данный файл и понять в чем дело. Но если кто-то не знает, я немного расскажу на что стоит заострить внимание. И так:
Я данным сервисом запускаю 3 контейнера, — gitlab, jenkins (master) и socat. Gitlab — система управления репозиториями кода для Git. данные конфиг делался универсальным и чтобы он работал в любом месте и на Unix/Linux системах. Если что-то не будет работать, то стоит рассмотреть поле DNS (в данном поле прописаны ДНС-ы которые служат резолвом в самих докер-контейнера. Иногда это уместно, когда на работе или дома используются свои ДНС, а остальные блокируются).
Можно заюзать статью чтобы проверить, какие ДНС-ы используются:
PS: Для данного поля стоит использовать, ТОЛЬКО 3 DNS ЗАПИСИ, не более! Иначе, они просто не будут работать и моугт сломать контейнер(ы).
Многие посмотрет на «socat» конейнер и спросят, а зачем он тут вообще упал? Так вот, он тут служит перенаправлением данных с порта (2375) на Unix сокет (/var/run/docker.sock). И сново могут полететь вопросы, а зачем?
Да дело в том, что докер-прогеры «не смогли» запилить «docker_opts»/»hosts» переменную в докер под Mac OS X. Данная переменная выполняет собственно аналогичные действия, но нативным спообом. Выглядит это вот так (на стороне Linux):
Т.е данную команду нужно прописать в конфиг докера, или можно запустить демон следующим образом:
Мне не совсем понятно почему нельзя было добавить такое в поддержку мака, но воркераунд найден и он работает. Вообще, такое чудо должно работать и на линуксе, но я не проверял. Если кому-то будет интересно, проверьте и напишите в комментариях результат.
На все это дело, я потратил около 7 часов времени и мне не очень было понятно почему не работает. Но в интернете нашелся пример моего бедствия. Я взял идею и опробовал ее — костыльненько, но а что поделать!
Собственно, gitlab + jenkins — готовы к использованию. Перейдем к настройке jenkins-slave.
Установка Jenkins-Slave в Unix/Linux
Стоит поставить: Docker Plugin плагин. Я еще ставил — Docker Slaves Plugin плагин, но не понял как он работает. Я покажу какие плагины у меня имеются в дженкинсе, возможно кому-то пригодится, но для начала, скачаем CLI для Jenkins:
Мои установленные плагины:
После того как плагины поставились, открываем дженкинс и переходим:
Кликаем по «Docker Cloud Details» чтобы ввести необходимые данные:
Задаем имя, у меня это — Docker. В поле «Docker Host URI» прописываем хост и порт который юзает докер. У меня — «tcp://172.6.6.2:2375». Почему-то, не прокатило использование хостнейма от соката в этом случае. Может исправлю попозже или на крайний случай — можно оставить как есть, т.к эта лаба служит в качестве примера. Стоит отметить, если у вас используется авторизация к докер хосту, то стоит заполнить «Server credentials». Если нажать на «Advanced», то выпадет список дополнительных параметров которые можно заполить тоже:
Нажмите на «Test connection» чтобы получить тестовое подключение (чтобы убедится что коннекшен работает как нужно).
Идем дальше, клацаем по «DOCKER AGENT TEMPLATES…» Я привел к виду:
Заполнил поле и лейблу как мне угодно. В поле «Docker image» я прописал Jenkins-Slave образ, который я взял с официального докер-регистра — «jenkinsci/slave». Так же, по необходимости заполните все необходимые поля (креденшелы, дополнительные опции).
Первый билд (джоба) на Jenkins-е
Создаем проект под свои нужды. Потом, создаем «Pipeline» проект, например:
Тестовый pipeline projec для Java
Нажимаем на «OK» и сейчас создадим все необходимое.
Находим «Run the build inside Docker containers» и ставим чекбокс. В поле «Docker Image» ставим наш образ, у меня — «jenkinsci/jnlp-slave:latest». Так же, можно прописать «Advanced settings» опции и выставить использовании по памяти. У меня все имеет вид:
Идем дальше, находим «Pipeline» вкладку и заполняем ее под свои нужды. У меня все приведено и имеет вид:
Т.е я заюзал свой гитлаб сервер. В нем есть репозиторий с проектом. Так же, добавил подключение к гитлабу. Собственно, все готово, можно нажимать на «SAVE»!
Слева вверху, нажимаем на «Build Now» и смотрим что получилось!
Видно что поднялся слейв и запустил джобу. Можно открыть ее и поглядеть статус выполнения:
Я думаю что на этом пока все, статья «Установка Jenkins и Jenkins-slave в Unix/Linux» завершена.
Учимся разворачивать микросервисы. Часть 4. Jenkins
Это четвертая и заключительная часть серии статей «Учимся разворачивать микросервисы», и сегодня мы настроим Jenkins и создадим пайплайн для микросервисов нашего учебного проекта. Jenkins будет получать файл конфигурации из отдельного репозитория, собирать и тестировать проект в Docker-контейнере, а затем собирать Docker-образ приложения и пушить его в реестр. Последней операцией Jenkins будет обновлять кластер, взаимодействуя с Helm (более подробно о нем в прошлой части).
Ключевые слова: Java 11, Spring Boot, Docker, image optimization
Ключевые слова: Kubernetes, GKE, resource management, autoscaling, secrets
Ключевые слова: Helm 3, chart deployment
Настройка Jenkins и пайплайна для автоматической доставки кода в кластер
Ключевые слова: Jenkins configuration, plugins, separate configs repository
Jenkins — это сервер непрерывной интеграции, написанный на Java. Он является чрезвычайно расширяемой системой из-за внушительной экосистемы разнообразных плагинов. Настройка пайплайна осуществляется в декларативном или императивном стиле на языке Groovy, а сам файл конфигурации (Jenkinsfile) располагается в системе контроля версий вместе с исходным кодом. Это удобно для небольших проектов, однако, часто более практично хранить конфигурации всех сервисов в отдельном репозитории.
Код проекта доступен на GitHub по ссылке.
Установка и настройка Jenkins
Установка
Существует несколько способов установить Jenkins:
War-архив с программой можно запустить из командной строки или же в контейнере сервлетов (№ Apache Tomcat). Этот вариант мы не будем рассматривать, так как он не обеспечивает достаточной изолированности системы.
Устранить этот недостаток можно, установив Jenkins в Docker. Из коробки Jenkins поддерживает использование докера в пайплайнах, что позволяет дополнительно изолировать билды друг от друга. Запуск докера в докере — плохая идея (здесь можно почитать почему), поэтому необходимо установить дополнительный контейнер ‘docker:dind’, который будет запускать новые контейнеры параллельно контейнеру Jenkins’а.
Также возможно развернуть Jenkins в кластере Kubernetes. В этом случае и Jenkins, и дочерние контейнеры будет работать как отдельные поды. Любой билд будет полностью выполняться в собственном контейнере, что максимально изолирует выполнения друг от друга. Из недостатков этот способ имеет довольно специфичную конфигурацию. Из приятных бонусов Jenkins в Google Kubernetes Engine может быть развернут одним кликом.
Хоть третий способ и кажется наиболее продвинутым, мы выберем прямолинейный путь и развернем Jenkins в Docker напрямую. Это упростит настройку, а также избавит нас от нюансов работы со stateful приложениями в Kubernetes. Хорошая статья для любопытствующих про Jenkins в Kubernetes.
Так как проект учебный, то мы установим Jenkins на локальной машине. В реальной обстановке можно посмотреть в сторону, например, Google Compute Engine. В дополнение замечу, что изначально я пробовал использовать Jenkins на Raspberry Pi, но из-за разной архитектуры «малинки» и машин кластера они не могут использовать одни и те же Docker-образы. Это делает невозможным применение Raspberry Pi для подобных вещей.
После установки Jenkins открываем http://localhost:8080/ и следуем инструкциям. Настроить Jenkins можно через UI, либо программно. Последний подход иногда называют «инфраструктура как код» (IaC). Он подразумевает описание конфигурации сервера с помощью хуков — скриптов на Groovy. Оставим этот способ настройки за рамками данной статьи. Для интересующихся ссылка.
Плагины
Для нашего проекта нам понадобится установить два плагина: Remote File Plugin и Kubernetes CLI. Remote File Plugin позволяет хранить Jenkinsfile в отдельном репозитории, а Kubernetes CLI предоставит доступ к kubectl внутри нашего пайплайна.
Установка Helm
Глобальные переменные среды
Так как у нас два сервиса, то мы создадим два пайплайна. Некоторые данные у них будут совпадать, поэтому логично вынести их в одно место. Это можно сделать, задав глобальные переменные среды, которые будут установлены перед выполнением любого билда на сервере Jenkins. Отмечу, что не стоит с помощью этого механизма передавать пароли — для этого существуют секреты.
Секреты
Имя секрета | Тип | Описание |
---|---|---|
github-creds | username with password | Логин/пароль от git-репозиториев |
dockerhub-creds | username with password | Логин/пароль от реестра Docker-образов |
kubernetes-creds | secret text | Токен сервисного аккаунта нашего кластера |
В предыдущей части в файле NOTES.txt нашего чарта мы описали последовательность команд для получения токена сервисного аккаунта. Вывести эти команды для кластера можно, запросив статус Helm-инсталляции ( helm status msvc-project ).
Конфигурация пайплайна
Как уже было упомянуто ранее, пайплайны описываются на Groovy, и могут быть написаны в декларативном и императивном стилях. Мы будем придерживаться декларативного подхода.
В Jenkins процесс выполнения пайплайна (билд) поделен на ряд шагов (stage). Каждый шаг выполняется определенным агентом (agent).
Наши пайплайны будут состоять из следующих шагов:
Структура файла конфигурации
Файл конфигурации будет иметь следующую структуру:
Агенты в Jenkins подчиняются типичным правилам «наследования» — если в шаге не определен агент, то будет использован агент родительского шага. Тег pipeline может рассматриваться как корневой шаг для всех остальных. Корневой шаг мы используем для объявления переменных среды и опций, которые по тому же правилу наследования будут иметь силу для всех вложенных шагов. Поэтому для тега pipeline установлен агент ‘none’. Использование этого агента подразумевает, что вложенные шаги обязаны переопределить этот агент для выполнения каких-либо полезных действий.
Агент в шагах Push Images и Trigger Kubernetes равен ‘any’. Это значит, что шаг может быть выполнен на любом доступном агенте. В простейшем случае это означает выполнение в контейнере Jenkins’а. Также эти шаги будут выполнены только для коммитов в мастер-ветку ( when < branch 'master' >).
Далее мы более пристально посмотрим на каждый из шагов, а также на блоки options и environment.
Опции
Блок опций может быть использован для настройки выполнения пайплайна или же отдельного шага. Мы используем следующие опции:
skipStagesAfterUnstable заставит Jenkins сразу прервать билд, eсли тесты были провалены. Поведение по умолчанию предусматривает установку статуса билда в UNSTABLE и продолжение выполнения. Это позволяет в сложных случаях более гибко обрабатывать подобные ситуации.
skipDefaultCheckout отключает автоматический чекаут репозитория проекта. Дефолтно Jenkins делает force чекаут репозитория для каждого шага с собственным агентом (в нашем случае Prepare Checkout, Push images и Trigger Kubernetes). То есть по сути затирает все изменения. Это может быть полезно при использовании пайплайна с несколькими различными образами. Однако нам нажно получить исходники с репозитория только единожды — на шаге Build. Применив опцию skipDefaultCheckout, мы получаем возможность произвести чекаут вручную. Также стоит заметить, что Jenkins будет автоматически переносить артефакты между шагами. Так, например, скомпилированные исходники из шага Build будут полностью доступны в шаге Test.
Переменные среды
Переменные среды могут быть также объявлены как для всего пайплайна, так и для отдельного шага. Их можно использовать, чтобы вынести какие-то редкоизменяемый свойства. В этом блоке мы определим имя файла докера и имена создаваемых Docker-образов.
В предыдущих статьях при работе с кластером мы всегда использовали наиболее свежие latest образы, но напомню, что это не лучший вариант из-за проблем при откатах к предыдущим версиям. Поэтому мы предполагаем, что в начальный момент времени кластер создается из самых свежих образов, а потом уже будет обновляться на конкретную версию. Тег IMAGE_TAG будет зависеть только от номера билда, который можно получить из предустановленной глобальной переменной среды BUILD_NUMBER. Таким образом наши версии будут составлять монотонную последовательность при условии того, что пайплайн не будет пересоздаваться (это приведет к сбросу номера билда). При неуспешных билдах BUILD_NUMBER также будет инкрементирован, следовательно последовательность версий образов может содержать «пробелы». Основное преимущество такого подхода к версионированию — его простота и удобство восприятия человеком. В качестве альтернативы можно подумать об использовании метки времени, чексуммы коммита или даже внешних сервисов.
Компиляция, тестирование, сборка
Чисто технически фазы компиляции, тестирования и сборки возможно объединить в один шаг, но для лучшей наглядности мы опишем их как отдельные шаги. С помощью плагинов Maven можно с легкостью добавлять дополнительные фазы, такие как статический анализ кода или интеграционное тестирование.
В фазе тестирования командой junit ‘**/target/surefire-reports/TEST-*.xml’ мы указываем Jenkins’у на файл с результатами тестов. Это позволит их отобразить прямо в веб-интерфейсе.
На последнем шаге мы генерируем jar-архив с нашим приложением.
Создание и отправка Docker-образа в реестр
На следующем шаге мы должны собрать Docker-образы и запушить их в реестр. Мы будем делать это средствами Jenkins, но в качестве альтернативы можно реализовать это и с помощью Maven-плагина.
Для начала нам надо доработать докер-файлы наших сервисов, которые мы разработали в первой статье цикла. Эти докер-файлы собирали приложение из исходников прямо в процессе создания образа. Сейчас же у нас имеется протестированный архив, следовательно один из шагов создания образа уже выполнен средствами Jenkins. Мы назовем этот новый файл Dockerfile-packaged.
Докер-файл для микросервиса шлюза аналогичен.
В конце шага происходит удаление установленных локально образов.
Обновление кластера Kubernetes
Финальным шагом осталось сообщить Kubernetes, что в реестре появился новый образ, и необходимо запустить процедуру обновления подов одного из микросервисов.
Итоговый Jenkinsfile
Jenkinsfile для микросервиса шлюза полностью аналогичен.
Подключение пайплайна
В Jenkins существует несколько видов пайплайнов. В данном проекте мы используем Multibranch pipeline. Как и следует из названия, билд будет запускаться для любого коммита в произвольной ветке.
Branch sources. Выбираем GitHub, в графе ‘Credentials’ выбираем секрет с логином/паролем от аккаунта и указываем URL репозитория с исходным кодом микросервиса.
Build Configuration. В Mode выбираем ‘by Remote File Plugin’, основное поле — Repository URL, в котором надо указать адрес репозитория с Jenkinsfile, а также Script Path с путем к этому файлу.
Scan Repository Triggers. В этом разделе настроим период, с которым Jenkins будет проверять, появились ли какие-то изменения в отслеживаемом репозитории. Недостаток этого подхода в том, что Jenkins будет генерировать трафик, даже когда в репозитории ничего не менялось. Более грамотный подход — настроить веб-хуки. При этом хранилище исходного кода само будет инициировать запуск билда. Очевидно, что сделать это можно, только если Jenkins доступен по сети для хранилища исходных кодов (они должны быть либо в одной сети, либо Jenkins должен иметь публичный IP-адрес). Подключение веб-хуков оставим за рамками данной статьи.
Запуск
После того как наш пайплайн настроен, можно его запустить. Наиболее удобно это сделать из меню Blue Ocean (Open Blue Ocean). Первый запуск может занять продолжительное время, так как потребуется время на загрузку Maven-зависимостей. После выполнения билда можно подключиться к кластеру и убедиться в том, что тег образа микросервиса был изменен ( helm get values msvc-project ).
В дальнейшем Jenkins будет отслеживать изменения в репозитории микросервиса и запускать билд автоматически.
Заключение
Jenkins — очень гибкая система, позволяющая реализовывать процесс непрерывной интеграции и развертывания любой сложности. В этой статье мы только коснулись этого инструмента, создав простенький пайплайн для микросервисов нашего проекта. В будущем этот пайплайн можно значительно дорабатывать и улучшать, например, добавив уведомления, дополнительные шаги, веб-хуки и многое-многое другое.