Jenkins Pipeline Shared Libraries

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

    Задача была следующей: есть определенный набор скриптов для развертывания сервисов, которые нужно запускать на каждом сервере каждого дата-центра. Скрипты выполняют серию операций: проверка статуса, вывод из-под load balancer’а, выпуск версии, развертывание, проверка статуса, отправка уведомлений через email и Slack и т.д. Это просто и удобно, но с ростом числа дата-центров и сервисов процесс выкатки новой версии может занять целый день. Кроме того, за некоторые действия отвечают отдельные команды, например, настройка load balancer’а. Также хотелось, чтобы управляющий процессом код хранился в общедоступном репозитории, дабы каждый член команды мог его поддерживать.

    Решить задачу удалось с помощью Jenkins Pipeline Shared Libraries: этапы процесса разделились визуально на логические части, код хранится в репозитории, а осуществить доставку на 20 серверов стало возможно в один клик. Ниже приведен пример подобного тестового проекта:

    image

    Сейчас я расскажу и покажу примеры как этого достичь. Надеюсь эта статья поможет сохранить время другим разработчикам, а также буду рад дельным комментариям.

    Создание библиотеки


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

    image
    Директория src используется для Groovy классов, которые добавляются в classpath при выполнении Pipeline.

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

    Ниже приведен пример Groovy класса

    @Grab(group = 'org.apache.commons', module = 'commons-lang3', version = '3.6')
    import org.apache.commons.lang3.StringUtils
    
    class Deployer {
        int tries = 0
        Script script
    
        def run() {
            while (tries < 10) {
                Thread.sleep(1000)
                tries++
                script.echo("tries is numeric: " + StringUtils.isAlphanumeric("" + tries))
            }
        }
    }
    

    В классах можно использовать любые возможности языка: создавать потоки, соединяться по FTP и т.д.

    Важно:
    — чтобы выводить в лог консоли из Pipeline, нужно передавать Script;
    — для импорта библиотек просто используем @Grab.

    Ниже пример скрипта:

    #!/usr/bin/env groovy
    
    def call(body) {
        echo "Start Deploy"
    
        new Deployer(script:this).run()
    
        echo "Deployed"
        currentBuild.result = 'SUCCESS' //FAILURE to fail
    
        return this
    }
    

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

    Важно:

    — Чтобы остановить выполнение достаточно установить значение currentBuild.result = 'FAILURE';
    — Получить параметры параметризованной сборки можно через переменную env. Например, env.param1.

    Вот пример репозитория с другими примерами.

    Подключение репозитория


    Следующий шаг — добавить наш репозиторий как глобальную библиотеку Pipeline.
    Для этого в Jenkins нужно перейти: Manage Jenkins → Configure System (Настроить Jenkins → Конфигурирование системы). В блоке Global Pipeline Libraries, добавить наш репозиторий, как на картинке ниже:

    image

    Создание Pipeline


    Последний шаг — создать Pipeline.

    Наш Pipeline будет выглядеть следующим образом:

    @Library('jenkins-pipeline-shared-lib-sample')_
    
    stage('Print Build Info') {
        printBuildinfo {
            name = "Sample Name"
        }
    } stage('Disable balancer') {
        disableBalancerUtils()
    } stage('Deploy') {
        deploy()
    } stage('Enable balancer') {
        enableBalancerUtils()
    } stage('Check Status') {
        checkStatus()
    }
    

    Т.е. мы просто добавляем @Library('jenkins-pipeline-shared-lib-sample')_ (не забываем добавить _ в конце) и вызываем наши функции по имени скриптов, например deploy.

    Наш Pipeline готов.

    В следующий раз я покажу, как я настраивал параметризованную сборку, чтобы элегантно получить зависимые параметры из REST сервиса.

    Спасибо за внимание!
    Поделиться публикацией
    Похожие публикации
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 17
    • +1

      У Pipeline Shared Libraries в таком исполнении есть один существенный недостаток — они ломают концепцию Pipeline-as-Code, когда код хранится и версионируется вместе с пайплайном.

      • 0
        Согласен, но все же зависит от решаемой задачи. В нашем случае используется именно библиотека, потому что очень много команд. Поддерживать очень большой один файл было бы слишком сложно.
        • 0
          Следует все таки различать декларацию, что хотим получить, и имплементацию декларации.
          Куча jenkins-плагинов, это как раз имплементация. Shared Library, из той же оперы.
        • 0
          Спасибо за статью, Андрей!

          1) Скажите, пожалуйста, у вас получилось как-то подружить Идею с скриптами для Дженкинса?

          У меня разразработка для Дженкинса в Идеи выглядит, как написание кода в IDE, в которой данный язык не поддерживается. Всё красное, никаких подсказок по классам. Оно и понятно, я не добавлял в класпас каких-либо jar-ников (которые я сходу не смог найти).

          2) Вы пишите, что можно и Джаву использовать. У вас есть опыт? Ведь в дженкинсе вроде бы только на груви можно писать пайплайны?
          • 0
            Привет,
            1. Я импортирую проект как новый модуль и добавляю groovy sdk. Мне этого достаточно
            2. Спасибо за замечание — исправил. Более правильно добавить, что можно использовать любые библиотеки с помощью Grab
            • +1
              Импортну вам любую либу, только позовите :)
              • 0

                C аннотацией grab тоже не все так просто. Например я пробовал подключить таким образом google guava и при обращении к классам библиотеки получил исключение MethodNotFoundException. Было несложно догадаться и проверить, что код pipeline-а загружается в classloader-е наследнике какого-то classloader-а в Jenkins куда уже загружены библиотеки самого Jenkins. Это может вести к очень неприятным последствиям.

              • 0

                Подсветка синтаксиса и автокомплит в Idea появляются после подключения библиотек Groovy Sdk И jenkins pipeline gdsl. Вот например: https://gist.github.com/arehmandev/736daba40a3e1ef1fbe939c6674d7da8

            • 0
              В скриптах можно использовать любые возможности языка

              Oчень смелое заявление. Важно понимать, что это не groovy, а jenkins dsl. Со своим кастомным интерпретатором и своими багами. И с учётом их фишки CPS это даёт незабываемые ощущения при разработке. Так, что тут чем проще, тем меньше шансов наступить на грабли.
              • 0
                Добрый день. Можете привести пример конструкции groovy, которую нельзя использовать внутри функции в модуле vars?
                • +1
                  (1..3).each

                  и надо еще не забывать, что в дженкинсе по умолчанию запрещено выполнение половины встроенных методов, и их надо разрешать отдельно.
                  • 0
                    Я обновил пример
                    И вот мой аутпут:
                    Enable balancer
                    [Pipeline] }
                    [Pipeline] // stage
                    [Pipeline] stage
                    [Pipeline] { (Check Status)
                    [Pipeline] echo
                    Check status
                    [Pipeline] echo
                    Number: 1
                    [Pipeline] echo
                    Number: 2
                    [Pipeline] echo
                    Number: 3
                    [Pipeline] }
                    [Pipeline] // stage
                    [Pipeline] End of Pipeline
                    Finished: SUCCESS

                    Можете привести другой пример?
                    • 0
                      я удивлен, но сейчас это действительно работает, когда пайплайн в дженкинсе только появился подобное работать отказывалсоь, и у меня до сих пор все старые библиотеки пестят конструкциями типа:
                      for (int i = 0; i < changeLogSets.size(); i++) {
                      или
                      @NonCPS
                      надо будет это переосмыслить
                      • 0
                        Это буквально недавно в новостях появилось. См. jenkins.io/blog/2017/09/08/enumerators-in-pipeline

                        Я правда пока не спешу обновлять сервера, есть недоверие что опять заработает только часть конструкций :)
                        • 0
                          это да, сначала пишешь все на груви, а потом еще день портируешь с груви на груви и без такой-то матери ниогда не обходится.
                  • 0
                    def collection = ['One Two Three'].collect{ it.split(' ') }
                    println "collection: " + collection
                    def result = ''
                    collection.each{ result += it }
                    println "result: " + result

                    Результат из Script Console:
                    collection: [[One, Two, Three]]
                    result: [One, Two, Three]

                    Результат из пайплайна:
                    [Pipeline] echo
                    collection: [[One, Two, Three]]
                    [Pipeline] echo
                    result: One

                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.