В компаниях с большим количеством проектов часто возникает ситуация, когда при разработке пайплайнов мы начинаем повторять себя, добавляя в разные сервисы одинаковые конструкции. Это противоречит основному принципу программирования DRY (Don’t Repeat Yourself), а ещё усложняет внесение изменений в код. Справиться с описанными проблемами помогает Jenkins Shared Library.

Мы пообщались с Кириллом Борисовым, Infrastructure Engineer технологического центра Deutsche Bank, и узнали, какие задачи решает Jenkins Shared Library и что её внедрение даёт компании. А ещё рассмотрели кейс разработки и тестирования с примерами кода.

Что такое Jenkins Shared Library

Jenkins Shared Library — это библиотека для многократного использования кода, которая позволяет описать методы один раз и затем применять их во всех пайплайнах. Она решает проблемы дублирования кода и унификации. Благодаря ней нам не нужно держать в каждом пайплайне огромное количество кода и постоянно повторять его. Мы можем написать код в одном месте, а потом инклюдить и вызывать его в рамках самого пайплайна.

Как это работает? Первым делом во всех пайплайнах мы выделяем общие части, которые можем объединить. Предположим, мы заметили, что везде используем нотификацию в Telegram, и везде она реализована по-разному. Мы создаем библиотеку, в ней делаем модуль нотификации и внедряем его во все проекты.

Затем смотрим, что ещё можем унифицировать. Например, обращение к внутренней системе, которое тоже везде реализовано по-разному. Мы заносим этот кусочек кода в библиотеку и далее распространяем на каждый микросервис. Так, выделяя общие части в каждом пайплайне, мы постепенно обогащаем нашу библиотеку и имплементируем её в наш пайплайн.

Польза Jenkins Shared Library для компании

Jenkins Shared Library уменьшает количество кода в пайплайне и повышает его читаемость. Для бизнеса это не столь важно, его больше заботит скорость доставки новых версий продукта до конечных пользователей. А здесь многое зависит от скорости разработки. Если каждая команда компании перестанет придумывать своё и начнёт использовать наработки других, вероятнее всего, разработка пайплайнов пойдёт быстрее. Соответственно, процесс доставки обновлений тоже ускорится.

С точки зрения самой команды, Jenkins Shared Library одновременно и усложняет, и упрощает работу. Например, проводить онбординг оказывается проще — вы показываете всё в одном месте. В то же время командам становится сложнее взаимодействовать. Сложность заключается в необходимости постоянной коммуникации. Когда решаем изменить что-то у себя в библиотеке, мы сначала должны синхронизироваться с другой командой, чтобы убедиться, что у неё ничего не сломается. Но обычно это решается регулярными синками, ревью кода и пул-реквестами.

Как команда понимает, что пора внедрять Jenkins Shared Library

На прошлом проекте у нас было 20 микросервисов, очень похожих между собой. Они были написаны по одному скелету на Java, а ещё в каждом из них лежал Jenkins file, где был описан пайплайн сборки проектов. Всякий раз, когда нужно было что-то поменять в пайплайне (а делать это приходилось часто, так как проект динамичный и быстро развивался), мы проходились по 20 репозиториям и поправляли всё вручную. Это довольно геморройный процесс, и мы подумали: «А почему бы нам не сделать что-то общее?».

Так мы перешли на Jenkins Shared Library. У нас появился базовый пайплайн в библиотеке, и, когда требовалось что-то изменить, мы работали с ним. Добавляли какие-то правки, и они автоматически имплементировались в каждый микросервис.

Ограничения Jenkins Shared Library

Любой инструмент призван решать какую-то проблему. Jenkins Shared Library решает проблему дублирования кода и унификации. Если вам эта унификация не нужна, смысла тащить библиотеку в проект нет. 

Если, скажем, у вас небольшой проект, где всего 5 микросервисов, писать для него библиотеку с нуля не стоит. Единственный вариант — переиспользовать, если она уже написана. Jenkins Shared Library — всё-таки решение для более крупных проектов с большим количеством микросервисов.

Кейс: разработка и тестирование Jenkins Shared Library

Для разработки и тестирования Jenkins Shared Library нам необходимо установить на свой компьютер gradle. Вот инструкция по установке — https://gradle.org/install/.

Далее в рабочем каталоге мы выполняем команду инициализации «gradle init», говорим установщику, что хотим настроить base каталог с groovy-файлами, и получаем готовый проект.

Следующий шаг — создание каталогов для JSL:

  1. var

  2. src

  3. resources

  4. test

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

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

Директория test содержит тесты для наших скриптом.

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

Затем заполянем build.gradle, добавляя в него описание наших каталогов:

–--
sourceSets {
  main {
        groovy {
            srcDirs = ['src','vars']
        }
        resources {
            srcDirs = ['resources']
        }
    }
    test {
        groovy {
             srcDirs = ['test']
        }
    }
}
---

И добавляем зависимости, которые понадобятся нам в работе. Полный файл можно посмотреть в репозитории: ссылка на репозиторий.

Пишем простой первый класс, который увеличивает возраст на указанное значение :)

package com.example

class SampleClass {
   String name
   Integer age

   def increaseAge(Integer years) {
      this.age += years
   }
}

Не забываем про тесты — создаем в директории test файл с именем SampleClassTest.groovy и содержимым.

В секции Before описываем действия, которые необходимо выполнить перед тестом — в нашем случае это объявление класса. Далее в секции Test описываем сам тест. Объявляем age = 7 и выполняем функцию increaseAge с параметром 3. В случае правильного выполнения ожидаем получить 10.

class SampleClassTest {
  def sampleClass

  @Before
  void setUp() {
    sampleClass = new SampleClass()
  }

  @Test
  void testIncrease() {
    sampleClass.age = 7  
    def expect = 10
    assertEquals expect, sampleClass.increaseAge(3)
  }
}

 Тест готов, запускаем командой gradle test. Результат будет такой: 

Но это синтетические примеры, давайте рассмотрим реальный

Каждый раз для вызова GET или POST запроса в пайплайн нам нужно вызывать класс HttpsURLConnection, передавать в него правильные параметры и проверять валидность сертификата.

Последуем главному принципу программирования DRY и подготовим класс, который позволит нам вызывать get и post запросы в пайплайне.

Создаем в директории src/com/example HttpsRequest.groovy, в нём создаем два метода get и post. В параметрах методов передаём URL-запроса и в случае POST еще и body-запроса.

Корневым методом нашего класса будет метод httpInternal. В нём закладываем логику, добавляем параметры к HttpsURLConnection, отлавливаем ошибку и в результате возвращаем тело ответа и ошибку:

package com.example

import groovy.json.JsonSlurper
import javax.net.ssl.HttpsURLConnection
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManager
import javax.net.ssl.X509TrustManager
import java.security.cert.CertificateException
import java.security.cert.X509Certificate

class HttpsRequest {

  def get(String uri) {
    httpInternal(uri, null, false)
  }

  def post(String uri, String body) {
    httpInternal(uri, body, true)
  }

  def httpInternal(String uri, String body, boolean isPost) {
    def response = [:]
    def error
    try {
      
      def http = new URL(uri).openConnection() as HttpsURLConnection
      if (isPost) {
        http.setRequestMethod('POST')
        http.setDoOutput(true)
        if (body) {
          http.outputStream.write(body.getBytes("UTF-8"))
        }
      }

      http.setRequestProperty("Accept", 'application/json')
      http.setRequestProperty("Content-Type", 'application/json')
      http.connect()

      if (http.responseCode == 200) {
        response = new JsonSlurper().parseText(http.inputStream.getText('UTF-8'))
      } else {
        response = -1
      }
    } catch (Exception e) {
      println(e)
      error = e
    }
    return [response, error]
  }
}

Покрываем тестами и создаем файл в директории test/HttpsRequestTest.groovy.

Для примера делаем get запрос, получаем ответ и проверяем, что он соответствует нашим ожиданиям:

  void testGet() {
    def expect = json.parseText('{"hello":"slurm"}')
    def (result, error) = http.get("https://run.mocky.io/v3/0bd64f74-1861-4833-ad9d-80110c9b5f25")
    if (error != null) {
      println(error)
    }
    assertEquals "result:", expect, result
  }

 Запускаем тесты:

Осталось дело за малым — подключить библиотеку в Jenkins.

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

 Последний шаг — создаем наш пайплайн:

@Library('jenkins-shared-library') _

import com.example.HttpsRequest

pipeline {
    agent any
    stages {
        stage('Demo') {
            steps {
                script {
                    def http = new HttpsRequest()
                    def (result, error) = http.get("https://run.mocky.io/v3/0bd64f74-1861-4833-ad9d-80110c9b5f25")
                    if (error != null) {
                        println(error)
                    } else {
                        println result
                    }
                    
                }
            }
        }
    }
}

Выполняем :)

Весь код можно найти в репозитории: ссылка на репозиторий.

Вместо заключения: о чём нужно помнить при работе с Jenkins Shared Library

Jenkins Shared Library — больше инструмент разработки в том плане, что нужно программировать. Чаще всего когда вы первый раз работаете с ней, если вы не разработчик, а администратор или DevOps, вы делаете это по наитию с точки зрения администратора. Это рабочий подход, но у него есть свои недостатки. Например, расширять проект дальше сложнее. 

Лучше сразу начинать мыслить как разработчик: выделять классы и методы, правильно их имплементировать в пайплайн и т.д. Если вы не из мира разработки, это осознание приходит с запозданием. И чем позже оно придёт, тем больше времени и сил вы потратите на то, чтобы переписать то, что уже сделали.

Для тех, кто хочет углубиться в тонкости работы с Jenkins Shared Library

6 сентября у нас стартует курс по Jenkins, автором которого выступил Кирилл Борисов, Infrastructure Engineer технологического центра Deutsche Bank. В курсе будет много кейсов и примеров из практики спикера.

Вы научитесь:

  • автоматизировать процесс интеграции и поставки;

  • ускорять цикл разработки и внедрять полезные инструменты;

  • настраивать плагины и создавать пайплайны Jenkins as a code;

  • работать с Jenkins Shared Library.

Ознакомиться с программой и записаться: https://slurm.club/3a6OEs8

Комментарии (1)


  1. stopper79
    01.07.2022 17:43

    Мое мнение, зашивать def (result, error) = http.get("https://run.mocky.io/v3/0bd64f74-1861-4833-ad9d-80110c9b5f25") не есть гуд. Лучше использовать переменные, т.к. я думаю, урл может со временем поменяться. ИМХО