Представь: у тебя двадцать репозиториев, и в каждом лежит свой Jenkinsfile. В каждом - один и тот же блок деплоя на сотню строк, одинаковая отправка уведомлений в чат, одинаковая сборка Docker-образа. Кто-то поправил логику деплоя в одном проекте, в остальных девятнадцати она осталась старой. Через полгода у тебя двадцать слегка разных копий, и ни одна не считается эталоном. Это классическая копипаста, только размазанная по всему отделу.
Jenkins shared library (общие библиотеки Jenkins) - это механизм, который вытаскивает повторяющийся код конвейеров в отдельный Git-репозиторий. Один раз написал шаг deploy(), подключил его во всех Jenkinsfile, и правишь логику в одном месте. Это тот же принцип, что и обычные библиотеки в коде: DRY на уровне пайплайнов. По сути ты строишь внутренний DSL под свою компанию - набор готовых высокоуровневых шагов вроде buildApp(), runTests(), deployTo('prod'), за которыми спрятаны десятки строк рутины.
Идея не новая, она была и в книге Ластера, но за прошедшие годы вокруг неё устоялись практики, о которых в издании 2018 года не пишут. Их и разберём - на актуальном Jenkins. Для справки: на 2026 год LTS-линейка ушла далеко вперёд, ветка 2.541.x требует минимум Java 17, а более свежие сборки (2.555.x) уже хотят Java 21 или 25. Сам синтаксис shared library при этом стабилен много лет, что приятно.

Структура: vars, src и resources
Библиотека - это обычный Git-репозиторий с тремя каталогами верхнего уровня. Каждый отвечает за свой тип кода.
Код: Выделить всё
my-shared-library/
vars/
deployApp.groovy # глобальный шаг, вызывается как deployApp()
deployApp.txt # документация шага (HTML/Markdown)
notifySlack.groovy
src/
org/cyberlake/
Docker.groovy # обычный Groovy-класс, пакет org.cyberlake
resources/
org/cyberlake/
deploy-template.yaml # статичные файлы, грузятся libraryResource
src/ - стандартное дерево исходников в стиле Java: пакеты, классы, нормальное ООП. Сюда выносят сложную логику, которую неудобно держать в плоских скриптах vars: парсинг, работу с моделями данных, классы-обёртки. Каталог src добавляется в classpath при выполнении пайплайна, так что классы из него видны и в vars, и в самом Jenkinsfile через import.
resources/ - не-Groovy файлы: шаблоны конфигов, JSON, скрипты. Их читают шагом libraryResource внутри библиотеки. Удобно, когда у шага есть, например, шаблон манифеста, и его не хочется зашивать в строку Groovy.
Пример переиспользуемого шага: выносим deploy
Сделаем то, ради чего всё затевалось - стандартный деплой как один шаг. Сначала простой вариант в vars. Метод call() - это то, что вызывается, когда в пайплайне ты пишешь имя файла как функцию.
Код: Выделить всё
// vars/deployApp.groovy
def call(Map config = [:]) {
String env = config.environment ?: 'staging'
String image = config.image ?: error('Не передан image для деплоя')
String ns = config.namespace ?: env
echo "Деплой ${image} в окружение ${env} (namespace ${ns})"
// шаблон манифеста лежит в resources/
String manifest = libraryResource 'org/cyberlake/deploy-template.yaml'
manifest = manifest.replace('__IMAGE__', image).replace('__NS__', ns)
writeFile file: 'deploy.yaml', text: manifest
sh "kubectl apply -f deploy.yaml -n ${ns}"
sh "kubectl rollout status deployment/app -n ${ns} --timeout=120s"
}
Код: Выделить всё
@Library('cyberlake-shared') _
pipeline {
agent any
stages {
stage('Deploy') {
steps {
deployApp(
environment: 'prod',
image: 'registry.cyberlake.ru/app:1.4.2',
namespace: 'production'
)
}
}
}
}
Если логика разрастается, переноси её в класс в src. Например, обёртка над Docker:
Код: Выделить всё
// src/org/cyberlake/Docker.groovy
package org.cyberlake
class Docker implements Serializable {
def steps
Docker(steps) { this.steps = steps }
String build(String name, String tag) {
steps.sh "docker build -t ${name}:${tag} ."
return "${name}:${tag}"
}
}
Код: Выделить всё
// vars/buildImage.groovy
import org.cyberlake.Docker
def call(String name, String tag) {
def docker = new Docker(this) // this - текущий пайплайн со всеми шагами
return docker.build(name, tag)
}
Документация шага. Положи рядом с vars/deployApp.groovy файл vars/deployApp.txt - и Jenkins покажет его на странице Pipeline Syntax > Global Variables Reference. Внутри можно HTML или Markdown. Для общей jenkins library, которой пользуется весь отдел, это не формальность: без описания параметров коллеги будут читать исходник или дёргать тебя в личку.
Версионирование. Библиотека - это Git, поэтому версия - это ref: ветка, тег или коммит. Управляется прямо в аннотации через @:
Код: Выделить всё
@Library('cyberlake-shared@main') _ // всегда свежая ветка main
@Library('cyberlake-shared@v2.3.0') _ // зафиксировали на теге
@Library('cyberlake-shared@4f1c9ab') _ // конкретный коммит
@Library(['cyberlake-shared@v2.3.0', 'infra-lib@main']) _ // сразу несколько
Доверенные и недоверенные библиотеки. Тут важный момент безопасности. Глобальные библиотеки, настроенные в Manage Jenkins > System > Global Trusted Pipeline Libraries, считаются доверенными: их код выполняется без Groovy-песочницы и может звать любые методы Java, внутренние API Jenkins, плагины. Это мощно и опасно - доступ к такой библиотеке должен быть только у тех, кому ты доверяешь сам контроллер. А вот библиотеки, заданные на уровне папки (folder-level), запускаются в песочнице наравне с обычными пайплайнами: им разрешён лишь список безопасных методов. Логика здравая: общий код, который видят все проекты, надёжно изолирован, а привилегированные обёртки лежат там, куда не дотянется случайный разработчик. Если в shared library нужен потенциально небезопасный вызов, прячь его в доверенной библиотеке за безопасным интерфейсом, а не раздавай сырой доступ всем.
Типичные грабли
- Забыли _ после @Library('name') - компиляция падает с невнятной ошибкой. Лечится одним символом.
- Назвали файл в vars через дефис или с заглавной (My-Step.groovy) - шаг не подхватится. Только camelCase: myStep.groovy.
- Класс в src не реализует Serializable - при сохранении состояния пайплайн ломается на ровном месте. Добавляй implements Serializable почти всегда.
- Зовёте sh или echo напрямую из класса в src - там их нет. Прокидывай steps (или this) в конструктор.
- Тянете @Library('lib@main') в прод и удивляетесь, что сборка сегодня собралась иначе, чем вчера. Фиксируй тег.
- Думаете, что библиотека всемогуща, а она в песочнице (folder-level) - и падает на запрещённом методе. Проверь, доверенная она или нет.
Мини-лаба
- Создай Git-репозиторий с каталогами vars/, src/, resources/.
- Напиши vars/hello.groovy с методом call(String name), который делает echo "Привет, ${name}". Рядом положи hello.txt с описанием.
- Подключи библиотеку в Manage Jenkins > System как Global Trusted Pipeline Library, источник - твой Git.
- В тестовом проекте напиши Jenkinsfile с @Library('твоё-имя@main') _ и вызови hello('cyberlake').
- Вынеси логику в класс src/org/example/Greeter.groovy (с implements Serializable и steps в конструкторе), вызови его из vars.
- Поставь тег v1.0.0, переключи Jenkinsfile на @Library('...@v1.0.0') и убедись, что сборка берёт именно его.
- Чем отличается код в каталоге vars/ от кода в src/ и когда что выбирать?
- Зачем в классе из src прокидывать объект steps через конструктор и причём тут Serializable?
- Как зафиксировать конкретную версию общей библиотеки в Jenkinsfile и почему для прода это важно?
- В чём разница между доверенной и недоверенной библиотекой с точки зрения Groovy-песочницы?
Shared library убирает копипасту Jenkinsfile и превращает разрозненные конвейеры в управляемый набор готовых шагов. vars - плоские глобальные шаги вроде deployApp(), src - классы для сложной логики, resources - статичные файлы. Версию фиксируй через @ref, документируй шаги в .txt, а к доверию относись серьёзно: доверенные библиотеки бегают без песочницы. Освоив этот механизм, ты перестаёшь чинить деплой в двадцати местах сразу - правишь в одном.