Все уроки
DevOps и облакоСредний10 мин чтенияПолезно знать

Jenkins

Self-hosted сервер автоматизации: пайплайны как код в Jenkinsfile, которые выполняются на ваших агентах.

  • Pipelines as code
  • Agents
  • Plugins
  • Groovy
Шаг 1/6
Разработчик делает push. CI/CD pipeline просыпается, и первым делом linter проверяет стиль. Зелёный.

За 30 секунд

Jenkins — это open-source сервер автоматизации, который вы устанавливаете и эксплуатируете сами. Jenkinsfile в репозитории описывает пайплайн на языке на основе Groovy: стадии вроде build, test и deploy, которые выполняются на рабочих машинах — агентах. Огромная экосистема плагинов связывает его почти с любым инструментом, поэтому он до сих пор распространён в крупных компаниях, но обновления, безопасность и масштабирование — на вас.

Не в моём ежедневном стеке — я понимаю принципы и изучу детали, когда это понадобится проекту.

Почему это важно

  • Он целиком работает на вашей инфраструктуре, поэтому подходит для изолированных сетей, регулируемых отраслей и сборок, которым нужно особое железо.
  • Около двух тысяч плагинов интегрируют его почти с любой SCM, облаком, тестовым инструментом и чатом, так что он может оркестрировать необычные или legacy-процессы.
  • Многие давно существующие компании годами гоняют пайплайны на Jenkins; понимать его часто нужно, чтобы их поддерживать или мигрировать.
  • Поминутной оплаты нет: вы платите за машины, которые контролируете, и при очень тяжёлой постоянной нагрузке это может быть дешевле.

Как это работает

01Контроллер, агенты и executor-ы

У инсталляции Jenkins есть контроллер: веб-интерфейс, конфигурация, планирование job-ов и хост для плагинов. Работа должна выполняться на агентах — отдельных машинах или контейнерах, которые подключаются к контроллеру и запускают сборки. Каждый агент предоставляет несколько executor-ов — слотов, в каждом из которых может идти одна сборка. Метки вроде linux, docker или arm64 описывают агентов, и пайплайн запрашивает метку, а не конкретную машину. Агенты бывают постоянными (VM, подключённая по SSH или входящим соединением) или эфемерными: плагин Kubernetes запускает под на каждую сборку, а облачные плагины поднимают VM по запросу и потом удаляют их. Представьте контроллер как диспетчера в таксопарке, а агентов — как машины. Ключевое правило: выставьте число executor-ов на самом контроллере в ноль, чтобы недоверенный код сборки никогда не выполнялся на машине, где хранятся все credentials и настройки.

02Безопасная установка Jenkins

У Jenkins есть еженедельная линейка релизов и линейка Long-Term Support (LTS), которую раз в двенадцать недель выбирают из еженедельного релиза и патчат ради стабильности; в production должен работать LTS. Текущие LTS-релизы требуют Java 21 или Java 25. Официальный Docker-образ jenkins/jenkins — самый простой старт: зафиксируйте LTS-тег с суффиксом JDK, сохраните volume jenkins_home (там живут вся конфигурация и история) и поставьте его за reverse proxy с HTTPS. При первом запуске Jenkins печатает в лог начальный пароль администратора, а мастер настройки предлагает рекомендуемые плагины. Ставьте только то, что нужно: каждый плагин — это код со своей историей уязвимостей. Включите authentication realm (LDAP, SAML или OIDC через плагины) и matrix- или role-based авторизацию; никогда не оставляйте анонимным пользователям никаких прав на запись.

yaml
# compose.yaml - Jenkins controller, data in a named volume
services:
  jenkins:
    image: jenkins/jenkins:lts-jdk21
    restart: unless-stopped
    ports:
      - "127.0.0.1:8080:8080"   # expose via a reverse proxy with TLS
      - "50000:50000"           # inbound agents
    volumes:
      - jenkins_home:/var/jenkins_home
volumes:
  jenkins_home:

03Declarative pipeline: Jenkinsfile

Pipeline as code означает, что сборка описана в Jenkinsfile, который хранится вместе с кодом, проходит ревью в pull request-ах и версионируется как всё остальное. Declarative-синтаксис — рекомендуемый стиль: фиксированная структура из pipeline, agent, stages и steps плюс необязательные секции options, environment, when и post. Его проще читать и валидировать, чем свободный Groovy. Пример собирает и тестирует Node.js-приложение внутри Docker-контейнера на любом агенте с меткой docker, хранит только последние двадцать сборок, прерывается по таймауту через тридцать минут, публикует результаты тестов JUnit и всегда чистит workspace. Job типа Multibranch Pipeline сканирует репозиторий и создаёт пайплайн для каждой ветки и pull request-а с Jenkinsfile, так что новые ветки получают CI автоматически.

groovy
pipeline {
  agent { docker { image 'node:24-alpine'; label 'docker' } }
  options {
    timeout(time: 30, unit: 'MINUTES')
    buildDiscarder(logRotator(numToKeepStr: '20'))
    disableConcurrentBuilds()
  }
  stages {
    stage('Install') { steps { sh 'npm ci' } }
    stage('Test') {
      steps { sh 'npm test -- --reporter=junit --outputFile=reports/junit.xml' }
      post { always { junit 'reports/*.xml' } }
    }
    stage('Build') { steps { sh 'npm run build' } }
  }
  post { always { cleanWs() } }
}

04Credentials и стадии деплоя

Jenkins хранит секреты в своём хранилище credentials, зашифрованными на диске, с глобальной областью видимости или на уровне папки, чтобы команды видели только свои. Пайплайны получают к ним доступ через withCredentials или хелпер credentials() в блоке environment, и Jenkins маскирует значения в выводе консоли. Предпочитайте credentials уровня папки, а ещё лучше — внешний менеджер секретов, например HashiCorp Vault или облачный secret manager через плагин. Стадия деплоя обычно запускается только на ветке main, ждёт человека через input и использует lock или milestone, чтобы пересекающиеся сборки не могли задеплоиться не по порядку. Обратите внимание на одинарные кавычки вокруг shell-скрипта ниже: тогда Groovy не подставляет секрет в строку команды, а shell читает его из окружения — так он не попадает в логи и списки процессов.

groovy
stage('Deploy') {
  when { branch 'main' }
  steps {
    input message: 'Deploy to production?', ok: 'Deploy'
    withCredentials([sshUserPrivateKey(
        credentialsId: 'deploy-ssh-key',
        keyFileVariable: 'SSH_KEY',
        usernameVariable: 'SSH_USER')]) {
      sh '''
        ssh -i "$SSH_KEY" -o StrictHostKeyChecking=yes \
          "[email protected]" "./deploy.sh $GIT_COMMIT"
      '''
    }
  }
}

05Плагины и shared libraries

Большинство возможностей Jenkins — это плагины: Git, Pipeline, credentials, Docker, Kubernetes-агенты, отчёты тестов. Эта гибкость — и главный эксплуатационный риск. Плагины зависят друг от друга и от версий ядра, и устаревший плагин — частый источник security advisories. Держите список коротким, регулярно обновляйтесь — сначала на staging-Jenkins — и читайте страницу security advisories. Для переиспользования между множеством репозиториев Shared Libraries хранят Groovy-код в отдельном Git-репозитории: функции из vars/ становятся шагами пайплайна, так что десятки Jenkinsfile могут вызывать один стандартный buildNodeApp() вместо копирования логики. Загружайте библиотеки с зафиксированной версией: @Library('[email protected]') _. Configuration as Code (плагин JCasC) описывает сам контроллер — включая безопасность, агентов и ссылки на credentials — в YAML, так что сломанный Jenkins можно пересобрать из Git, а не по памяти.

Когда применять

  • Сборки должны полностью выполняться on-premises или в изолированных сетях без внешнего SaaS.
  • У вас уже большой парк Jenkins, и его нужно поддерживать, укреплять или постепенно модернизировать.
  • Необычная оркестрация: тесты hardware-in-the-loop, специальные сборочные машины или интеграции, которые есть только в плагинах Jenkins.
  • Очень большой постоянный объём сборок, при котором владеть машинами дешевле поминутной оплаты.

Когда не стоит

  • Новый проект на GitHub или GitLab со стандартными потребностями — встроенный CI проще эксплуатировать и защищать.
  • Маленькая команда, где некому отвечать за обновления контроллера, плагинов и бэкапы.
  • Вам нужна нулевая инфраструктура — Jenkins всегда что-то, что вы эксплуатируете сами.

Альтернативы

  • GitHub Actions

    Hosted CI, интегрированный с GitHub, без контроллера, который надо обслуживать.

  • GitLab CI/CD

    Встроен в GitLab, сильный self-managed вариант с раннерами в вашей сети.

  • Buildkite

    Hosted control plane с агентами на ваших машинах — частая замена Jenkins.

  • Tekton / Argo Workflows

    Kubernetes-native пайплайны для команд, которые строят собственную платформу.

Лучшие практики

  1. Используйте линейку LTS и проверяйте обновления ядра и плагинов сначала на staging-контроллере.
  2. Выставьте executor-ы контроллера в ноль и запускайте все сборки на агентах, лучше эфемерных.
  3. Используйте Declarative Pipeline в Jenkinsfile и Multibranch-job-ы вместо freestyle-job-ов, настроенных через UI.
  4. Управляйте конфигурацией контроллера через Configuration as Code и бэкапьте jenkins_home.
  5. Ограничивайте credentials папками, используйте shell-строки в одинарных кавычках для секретов и предпочитайте внешний менеджер секретов.
  6. Держите список плагинов минимальным и быстро реагируйте на security advisories Jenkins.
  7. Выносите переиспользуемую логику в версионированные Shared Libraries, оставляя Jenkinsfile короткими и декларативными.

Частые ошибки

  • Запускать сборки на контроллере, давая любому пайплайну доступ ко всем секретам и конфигурации.
  • Ставить десятки плагинов, о которых никто не помнит, а потом не иметь возможности обновить ядро из-за них.
  • Подставлять секреты в Groovy-строки в двойных кавычках, из-за чего они могут утечь в логи и списки процессов.
  • Выставлять Jenkins прямо в интернет без HTTPS, SSO и строгой авторизации.
  • Настраивать всё кликами в UI без бэкапа, так что отказ диска означает восстановление по памяти.

Глубже

06Как пайплайны переживают перезапуски: CPS и durability

Код пайплайна выполняется не как обычный Groovy. Jenkins преобразует его в continuation-passing style (CPS), чтобы состояние работающего пайплайна можно было сохранить на диск и продолжить после перезапуска контроллера. Поэтому длинные сборки могут пережить обновление — и поэтому же часть обычного Groovy ломается: несериализуемые объекты, некоторые замыкания и итераторы вызывают NotSerializableException. Методы с пометкой @NonCPS выполняются как обычный Groovy, но не могут вызывать шаги пайплайна. Помните также, что Groovy в Jenkinsfile выполняется на контроллере, а не на агенте; на агентах выполняются только шаги вроде sh. Тяжёлый парсинг или циклы в Groovy нагружают контроллер, поэтому выносите такую работу в shell-скрипты или инструменты, которые запускаются на агентах. Настройка durability (например, PERFORMANCE_OPTIMIZED) жертвует гарантиями возобновления ради меньшего дискового I/O на загруженных контроллерах.

07Эфемерные агенты в Kubernetes

Плагин Kubernetes запускает под на каждую сборку из pod template и подключает его как агента; под удаляется по окончании сборки. Каждый контейнер в поде может содержать свой тулчейн, а container() переключает между ними внутри стадии. Плюсы: каждый раз чистый workspace, никаких уникальных «снежинок» среди сборочных машин и масштабирование вместе с кластером. Следите за деталями: задавайте requests по CPU и памяти, чтобы сборки не отнимали ресурсы друг у друга, запускайте сборки не от root и не монтируйте Docker-сокет хоста — это даёт сборке root на ноде; вместо этого используйте rootless-сборщики в стиле Kaniko или BuildKit. Кэши зависимостей исчезают вместе с подом, поэтому используйте persistent volume, прокси-репозиторий вроде Nexus или Artifactory или build cache на базе registry, чтобы сборки оставались быстрыми.

groovy
pipeline {
  agent {
    kubernetes {
      yaml '''
        apiVersion: v1
        kind: Pod
        spec:
          containers:
            - name: node
              image: node:24-alpine
              command: [sleep]
              args: [infinity]
              resources:
                requests: { cpu: "500m", memory: 1Gi }
      '''
      defaultContainer 'node'
    }
  }
  stages { stage('Test') { steps { sh 'npm ci && npm test' } } }
}

08Защита контроллера Jenkins

У Jenkins длинный список исторических уязвимостей, в основном в плагинах, и атакующие активно сканируют открытые инстансы. Защита в несколько слоёв: обновляйте ядро и плагины по опубликованным security advisories; ставьте контроллер за reverse proxy с TLS и желательно за VPN или SSO; используйте matrix- или role-based авторизацию без анонимного доступа; отключите CLI через remoting и неиспользуемые протоколы; оставьте script console только администраторам — она выполняет произвольный Groovy с полными правами контроллера. Плагин Script Security изолирует Groovy пайплайнов в песочнице и требует одобрения администратора для опасных методов; не одобряйте сигнатуры бездумно. Контролируйте, какие репозитории могут определять пайплайны и какие credentials доступны каждой папке: вредоносное изменение Jenkinsfile в pull request может напечатать любой credential, доступный job-у. Настройте trusted sources, чтобы pull request-ы из форков не могли менять Jenkinsfile, используемый для их сборки.

Реальные ситуации

01

Обновление Jenkins блокируют старые плагины

Проблема

Контроллер на два года отстаёт по ядру и содержит 140 плагинов. Обновление ядра ломает несколько плагинов, и никто не знает, какие из них используются.

Как решить

Сделайте бэкап jenkins_home и разверните его копию как staging-контроллер. Выгрузите список плагинов и по данным об использовании (плагин Plugin Usage или grep по конфигам job-ов) найдите неиспользуемые; удалите их. Сначала обновите Java до 21, затем ядро — шагами по LTS-релизам, на каждом шаге обновляя плагины через plugin installation manager tool. Почините или замените заброшенные плагины. Зафиксируйте результат через Configuration as Code и plugins.txt в Git, затем соберите production-контроллер из этого образа. После этого запланируйте ежемесячные обновления LTS, чтобы отставание больше не росло.

02

Сборки случайно падают на общих агентах

Проблема

На одном и том же коммите сборки то проходят, то падают — в зависимости от того, какой постоянный агент их запускает. У агентов разные версии инструментов и оставшиеся файлы.

Как решить

Перенесите сборки в контейнеры: используйте agent { docker { image 'node:24-alpine' } } или pod templates Kubernetes, чтобы тулчейны были описаны в Jenkinsfile и везде одинаковы. Собирайте образы агентов в CI, с версиями и сканированием. Чистите workspace через cleanWs() в post и перестаньте полагаться на файлы вне workspace. Замените постоянных агентов эфемерными из плагина Kubernetes или облачного плагина. Оставьте один постоянный агент только там, где нужно особое железо, и управляйте его конфигурацией через Ansible, чтобы его можно было пересобрать.

03

Миграция с Jenkins на GitHub Actions

Проблема

Компания переносит код на GitHub и хочет отказаться от Jenkins, но у неё 80 пайплайнов с Shared Libraries и кастомными плагинами.

Как решить

Проведите инвентаризацию пайплайнов и сгруппируйте их по паттернам — большинство сведутся к нескольким формам. Переведите функции Shared Library в reusable workflows и composite actions. GitHub Actions Importer может сделать черновые воркфлоу из Jenkinsfile и ускорить механическую часть. Пересоздайте credentials как секреты environment или, лучше, как доверие через OIDC. Сначала мигрируйте пилотную группу и несколько недель держите обе системы параллельно, сравнивая результаты. Оставьте Jenkins только для job-ов, которым нужно on-premises железо, или подключите их через self-hosted раннеры, а затем выключите контроллер после финального бэкапа.

Шпаргалка

Запустить Jenkins LTS в Docker
docker run -d -p 127.0.0.1:8080:8080 -v jenkins_home:/var/jenkins_home jenkins/jenkins:lts-jdk21
Начальный пароль администратора
docker exec <container> cat /var/jenkins_home/secrets/initialAdminPassword
Запуск только в main
when { branch 'main' }
Таймаут сборки
options { timeout(time: 30, unit: 'MINUTES') }
Хранить последние 20 сборок
options { buildDiscarder(logRotator(numToKeepStr: '20')) }
Использовать secret text
environment { API_TOKEN = credentials('api-token') }
Загрузить shared library
@Library('[email protected]') _
Параллельные стадии
parallel { stage('Unit') {...} stage('Lint') {...} }
Установить плагины в образ
jenkins-plugin-cli --plugin-file plugins.txt
Проверить Jenkinsfile
Pipeline Linter: POST $JENKINS_URL/pipeline-model-converter/validate

Вопросы и ответы

Q1Чем контроллер отличается от агента?

Контроллер — это мозг: он обслуживает веб-интерфейс, хранит конфигурацию, credentials и историю сборок, планирует сборки и запускает плагины. Агенты — это рабочие, которые подключаются к контроллеру и реально выполняют шаги сборки. У агента несколько executor-ов, каждый может выполнять одну сборку. Сборки выбирают агентов по метке, поэтому job, которому нужен Docker, просит docker, а не имя хоста. Сам контроллер не должен запускать сборки, потому что скрипты сборки — это код из репозиториев, и он может прочитать секреты контроллера или повредить его конфигурацию. Современные установки используют эфемерных агентов — под Kubernetes или облачную VM на каждую сборку, — которые каждый раз чистые и масштабируются по спросу.

Q2Declarative или scripted pipeline?

У declarative pipeline фиксированная структура: pipeline, agent, stages, steps, post, when, options. Jenkins может провалидировать его до запуска, представления в стиле Blue Ocean его понимают, и всей команде его проще читать. Scripted pipeline — это обычный Groovy внутри блоков node: максимум гибкости, но его легко превратить в нечитаемую программу с тонкими проблемами CPS-сериализации. Мой выбор по умолчанию — Declarative для каждого Jenkinsfile и блок script только для небольшой неизбежной логики. Когда нужна настоящая логика, ей место в Shared Library с тестами, которую вызывает короткий Declarative Jenkinsfile, а не во встроенном Groovy в каждом репозитории.

Q3Как работать с секретами в пайплайнах Jenkins?

Храните их в хранилище credentials Jenkins с областью видимости папки той команды, которой они нужны, или получайте из внешнего менеджера вроде Vault через плагин. В пайплайнах привязывайте их только там, где нужно, через withCredentials или credentials() в блоке environment — это заодно маскирует их в логах. Используйте sh-строки в одинарных кавычках, чтобы переменную раскрывал shell, а не Groovy; Jenkins даже предупреждает о небезопасной интерполяции. Никогда не выводите секреты через echo и не передавайте их аргументами команд, где они видны в списке процессов. Ограничьте, кто может настраивать job-ы: любой, кто может редактировать пайплайн, может напечатать доступный ему credential. И периодически проводите аудит использования credentials.

Q4Как годами держать инстанс Jenkins в поддерживаемом состоянии?

Относитесь к нему как к любому production-сервису. Используйте LTS и обновляйтесь по графику, репетируя на staging-контроллере, восстановленном из свежего бэкапа. Плагинов должно быть мало, и они должны быть актуальными; неиспользуемые удаляйте. Описывайте контроллер через Configuration as Code в Git, чтобы его можно было пересобрать; job-ы берутся из Jenkinsfile через Multibranch или organization folders, а не из UI. Бэкапьте jenkins_home или хотя бы те части, которые нельзя воспроизвести из Git. Используйте эфемерных агентов из образов, собранных в CI, чтобы инструменты агентов тоже были версионированы. Мониторьте место на диске, длину очереди и загрузку executor-ов и удаляйте старые сборки и артефакты через logRotator, чтобы диск не заполнился незаметно.

Q5Что такое Multibranch Pipeline?

Multibranch Pipeline — это тип job-а, который указывает на репозиторий и автоматически создаёт под-job для каждой ветки, а при желании и каждого pull request-а, в которых есть Jenkinsfile. Когда создаётся ветка, Jenkins обнаруживает её через webhook или периодическое сканирование и начинает сборку; когда ветку удаляют, под-job тоже удаляется. С условиями when { branch 'main' } или changeRequest() один Jenkinsfile может вести себя по-разному для feature-веток, pull request-ов и main. Organization Folder поднимается на уровень выше и сканирует целую организацию GitHub или группу GitLab, создавая Multibranch-job-ы для каждого репозитория с Jenkinsfile.

Вопросы уровня senior

S1Когда вы бы сегодня всё ещё выбрали Jenkins для новой платформы?

По умолчанию — редко, но честные случаи есть. Изолированные или строго регулируемые среды, где SaaS control plane не разрешён, а организация уже хорошо эксплуатирует Jenkins. Сложные процессы с железом, например embedded- или device-тестирование, которые зависят от конкретных плагинов или настроек агентов. Очень большие сборочные фермы, где опытная команда эксплуатирует Jenkins с Kubernetes-агентами, JCasC и Shared Libraries дешевле, чем hosted-минуты. Против: эксплуатационная нагрузка, риски безопасности плагинов, специфичные навыки Groovy и более слабый developer experience, чем у встроенного CI. Если код в GitLab или GitHub Enterprise Server, их CI с self-hosted раннерами обычно даёт тот же on-premises контроль при меньшей поддержке, так что Jenkins я бы выбрал только с ясной причиной и командой, которая будет им владеть.

S2Как масштабировать Jenkins на сотни команд?

Один гигантский контроллер становится единой точкой отказа и узким местом по производительности, потому что логика Groovy-пайплайнов, UI и плагины работают там. Распространённый паттерн — несколько контроллеров, разделённых по бизнес-подразделениям или уровню доверия, каждый управляется одинаково через Configuration as Code и выверенный образ с плагинами, возможно с CloudBees CI или похожими инструментами для централизованного управления. Агенты — эфемерные поды или VM с автомасштабированием. Shared Libraries дают стандартные шаги пайплайна, версионированные и протестированные. Команды получают папки с ограниченными credentials и правами. Мониторинг покрывает время в очереди, загрузку executor-ов, heap контроллера и диск. Компромисс — накладные расходы на управление парком против радиуса поражения: больше контроллеров — больше обновлений, которые надо оркестрировать, зато плохой плагин или вышедший из-под контроля job затрагивает только часть организации.

S3Pull request меняет Jenkinsfile так, чтобы напечатать credentials. Как это предотвратить?

Нужно сочетание нескольких мер. В конфигурации Multibranch настройте доверие так, чтобы pull request-ы из форков или от участников без прав на запись собирались с Jenkinsfile из целевой ветки, а не со своим изменённым. Ограничьте credentials папками и вообще не давайте сборкам pull request-ов production credentials; deploy credentials привязываются только в стадиях, которые выполняются в main. Маскирование помогает, но легко обходится кодированием, так что мерой контроля оно не является. Держите Groovy-песочницу включённой и не одобряйте рискованные сигнатуры скриптов. Требуйте код-ревью для изменений Jenkinsfile и Shared Library через branch protection. Для самых ценных секретов используйте короткоживущие credentials из Vault, чтобы даже утечка быстро истекала. Базовый принцип: любой, кто может менять код пайплайна, может использовать любой секрет, до которого этот пайплайн дотягивается.

Глоссарий

Controller
Центральный сервер Jenkins: UI, конфигурация, планирование и плагины. Раньше назывался master.
Agent
Машина или контейнер, который подключается к контроллеру и выполняет шаги сборки.
Executor
Слот на агенте, в котором одновременно выполняется одна сборка.
Jenkinsfile
Файл в репозитории, описывающий пайплайн как код.
Declarative Pipeline
Структурированный рекомендуемый синтаксис Jenkinsfile с блоками pipeline, agent, stages и steps.
Shared Library
Git-репозиторий переиспользуемых Groovy-шагов, который подгружают многие Jenkinsfile.
JCasC
Configuration as Code: описывает настройки контроллера в YAML, чтобы его можно было пересобрать из Git.
LTS
Линейка релизов Long-Term Support, выбирается раз в двенадцать недель и патчится ради стабильности.
Multibranch Pipeline
Job, создающий пайплайн для каждой ветки или pull request-а с Jenkinsfile.

Есть идея продукта?Давайте создадим его.

Расскажите о своей идее, продукте или команде. Обычно отвечаю в течение одного рабочего дня.