Параллелизм и блокировки: parallel, lock, milestone

Рейтинг: 45.3% · 9 голосов
Подробный курс по Jenkins и CI/CD: установка и настройка, плагины и Configuration as Code, конвейеры (pipeline) и Jenkinsfile, declarative и scripted, агенты, Docker и Kubernetes, общие библиотеки на Groovy, безопасность и credentials, интеграции (SonarQube, артефакты), траблшутинг. Актуально на 2026.
Ответить
Аватара пользователя
Andrey_CICD
Сообщения: 47
Зарегистрирован: 11 май 2026, 05:31

Параллелизм и блокировки: parallel, lock, milestone

Сообщение Andrey_CICD »

Оглавление курса (47)
  1. Что такое Jenkins и зачем нужен CI/CD
  2. Jenkins в 2026: версии, экосистема и сравнение с GitLab CI и GitHub Actions
  3. Архитектура Jenkins: контроллер, агенты, узлы и исполнители
  4. Установка Jenkins: пакеты, Docker и Kubernetes
  5. Первичная настройка Jenkins: мастер, плагины, пользователи
  6. Плагины Jenkins: установка, обновление и ключевой набор
  7. Configuration as Code (JCasC): настройка Jenkins декларативно
  8. Конвейеры Jenkins и Jenkinsfile: pipeline as code
  9. Scripted против Declarative: два синтаксиса конвейера
  10. Структура конвейера: node, stage, steps и первый pipeline
  11. Генератор сниппетов, запуск конвейера и Replay
  12. Типы заданий Jenkins: Freestyle, Pipeline, Multibranch и Organization
  13. Декларативный конвейер: структура pipeline, agent, stages
  14. agent, environment и переменные в конвейере
  15. tools, options, parameters и triggers в декларативном конвейере
  16. stages, parallel и matrix: организация этапов
  17. Условное выполнение when и блок post
  18. Триггеры сборки: webhook от GitHub, SCM polling и расписание
  19. Параметры сборки и пользовательский ввод
  20. Управление потоком: timeout, retry, sleep и waitUntil
  21. Параллелизм и блокировки: parallel, lock, milestone (вы здесь)
  22. Постобработка и уведомления о результате сборки
  23. Groovy для Jenkins: основы скриптового конвейера
  24. Скриптовый конвейер: логика, циклы и функции
  25. Общие библиотеки Jenkins: Shared Libraries
  26. Подключение и загрузка общих библиотек
  27. Шаги оболочки: sh, bat, powershell и коды возврата
  28. Переменные среды, withEnv и рабочие пространства
  29. Работа с файлами, артефактами и отпечатками
  30. Сборка проектов в конвейере: Maven, Gradle, npm
  31. Качество кода: SonarQube и покрытие тестами
  32. Управление артефактами: публикация в Nexus и Artifactory
  33. Docker в Jenkins: образы как агенты сборки
  34. Сборка и публикация Docker-образов из конвейера
  35. Jenkins в Kubernetes: динамические pod-агенты
  36. Развёртывание в Kubernetes из конвейера
  37. Безопасность Jenkins: аутентификация и матрица прав
  38. Учётные данные Jenkins: credentials в конвейере
  39. Безопасность сценариев: Groovy sandbox и Vault
  40. Уведомления и отчёты: email, Slack, Telegram, HTML
  41. Blue Ocean и интерфейс Jenkins в 2026
  42. Автоматизация Jenkins: CLI, REST API и script console
  43. Поиск и устранение неисправностей: читаем логи конвейера
  44. Типичные ошибки конвейера: сериализация и неутверждённый код
  45. Эксплуатация Jenkins: бэкап, обновление, масштабирование
  46. Конвертация и миграция: от Freestyle к Jenkinsfile
  47. Итог: сквозной CI/CD конвейер и Jenkins против альтернатив в 2026
Представь типичную пятницу. Два разработчика почти одновременно мёржат в main. Jenkins честно запускает две сборки. Обе доходят до стадии деплоя на единственный staging-стенд - и начинают накатывать релиз параллельно. Миграции базы наезжают друг на друга, артефакты перетирают артефакты, на стенде оказывается франкенштейн из двух коммитов. А ещё бывает веселее: сборка #41 стартовала раньше, но застряла на тестах, сборка #42 пролетела быстрее и уже выкатилась в прод. Потом #41 оживает и спокойно деплоит поверх свежего релиза СТАРЫЙ код. Это классическая гонка деплоев, и руками её не поймать.

В этом уроке разбираем три инструмента, которые решают ровно эти боли в declarative pipeline: parallel - чтобы ускорять, lock - чтобы сериализовать доступ к общему ресурсу, milestone - чтобы старая сборка никогда не обгоняла новую. Плюс опции на уровне всего job, ограничивающие число одновременных прогонов. Материал основан на главе 3 книги Ластера, но синтаксис и практики приведены к реальности 2026 года.

parallel в Jenkins pipeline: запускаем ветки одновременно

Самый понятный способ ускорить конвейер - выполнять независимые задачи параллельно, а не цепочкой. В declarative pipeline для этого есть директива parallel внутри stage. Каждая вложенная ветка - это самостоятельный stage со своими шагами, и Jenkins разводит их по разным агентам (или исполнителям) одновременно.

Код: Выделить всё

pipeline {
    agent none
    stages {
        stage('Tests') {
            parallel {
                stage('Unit') {
                    agent { label 'linux' }
                    steps {
                        sh './gradlew test'
                    }
                }
                stage('Lint') {
                    agent { label 'linux' }
                    steps {
                        sh './gradlew checkstyleMain'
                    }
                }
                stage('Integration') {
                    agent { label 'docker' }
                    steps {
                        sh './gradlew integrationTest'
                    }
                }
            }
        }
    }
}
Тут юнит-тесты, линтер и интеграционные тесты бегут разом. Если на проекте честные 12 минут тестов разложить на три ветки, общее время стадии упадёт примерно до самой длинной из них. Полезные опции рядом с parallel: failFast true - как только падает одна ветка, остальные сразу прибиваются, не жгут ресурсы зря. Важно понимать ограничение: declarative parallel НЕ умеет вкладывать parallel внутрь parallel. Если нужна по-настоящему динамическая матрица (например, список агентов формируется на лету), переходи на scripted-конструкцию parallel branches: [-] это map, где ключ - имя ветки, значение - замыкание. Для статических случаев почти всегда хватает declarative-матрицы или директивы matrix.

Изображение

Jenkins lock: один деплой на стенд за раз

Параллелизм ускоряет, но порождает конфликты за общие ресурсы: один staging-стенд, одна тестовая БД, один порт, одна железка в лабе. Решение - шаг lock из плагина Lockable Resources. Он работает как мьютекс: пока одна сборка держит ресурс, остальные ждут в очереди, а не лезут параллельно.

Ресурсы заводятся двумя способами. Первый - объявить их глобально (Manage Jenkins, дальше конфигурация плагина), задав имя и метки (labels). Второй - сослаться на несуществующее имя прямо в pipeline, тогда создаётся эфемерный ресурс, живущий только пока его кто-то держит. Для именованных стендов лучше первый путь - так их видно в UI и понятно, кто чем занят.

Код: Выделить всё

pipeline {
    agent any
    stages {
        stage('Build') {
            steps { sh 'make build' }
        }
        stage('Deploy to staging') {
            steps {
                lock(resource: 'staging-env', reason: "deploy ${env.BUILD_TAG}") {
                    sh './deploy.sh staging'
                    sh './smoke-test.sh staging'
                }
            }
        }
    }
}
Пока этот блок держит staging-env, вторая сборка на стадии деплоя честно подождёт. Параметр reason попадает в UI плагина, и коллеги видят, какая именно сборка сейчас занимает стенд - очень помогает, когда в очереди затор. Есть и более мощные режимы. Заблокировать любой свободный стенд из пула по метке:

Код: Выделить всё

lock(label: 'staging-pool', quantity: 1, variable: 'STAND') {
    sh "./deploy.sh ${env.STAND}"
}
В переменную STAND попадёт имя конкретного захваченного ресурса - удобно, когда стендов несколько и всё равно какой. Lock можно ставить и на уровне опций стадии через options { lock('staging-env') } - тогда блокировка охватывает весь stage, включая его post-секцию. И ещё важная связка: lock внутри parallel-веток работает корректно, если две ветки просят один ресурс, одна займёт, другая дождётся.

milestone и concurrent builds: защита от гонки деплоев

Lock сериализует доступ, но не решает проблему обгона. Вернёмся к сценарию "старая сборка деплоит поверх новой". Здесь нужен шаг milestone. Идея простая: milestone - это пронумерованный рубеж в конвейере. Правило железное - когда какая-то сборка проходит milestone с номером N, Jenkins автоматически отменяет (abort) все более ранние сборки этого job, которые ещё не дошли до N. То есть старый прогон физически не сможет добраться до деплоя, если его уже обогнал свежий.

Код: Выделить всё

pipeline {
    agent any
    stages {
        stage('Build') {
            steps {
                milestone(ordinal: 1, label: 'built')
                sh 'make build'
            }
        }
        stage('Deploy prod') {
            steps {
                milestone(ordinal: 2, label: 'before-deploy')
                lock('prod-env') {
                    sh './deploy.sh prod'
                }
            }
        }
    }
}
Связка milestone + lock - это и есть рецепт безопасной выкатки. Lock гарантирует, что в прод деплоит ровно одна сборка за раз. Milestone перед lock гарантирует, что в очереди за блокировкой не стоит устаревший прогон: как только новая сборка переступит рубеж before-deploy, все более старые, ждущие этот же milestone, будут отменены. Гонка закрыта с обеих сторон.

Третий инструмент - ограничение числа одновременных прогонов всего job. Если конкурентность тебе вообще не нужна, ставь в options:

Код: Выделить всё

options {
    disableConcurrentBuilds()
}
Тогда новая сборка встаёт в очередь и ждёт, пока завершится текущая. Часто полезнее агрессивный вариант - не ждать, а убить предыдущую: disableConcurrentBuilds(abortPrevious: true). Это идеально для веток разработки, где важна только последняя версия, а промежуточные сборки на каждый пуш просто жгут исполнителей. Когда нужно тоньше - например, не более двух деплоев одного типа на весь Jenkins сразу, - подключают плагин Throttle Concurrent Builds и категории, но для большинства команд хватает связки disableConcurrentBuilds + lock + milestone.

Типичные грабли
  • lock без milestone. Lock честно сериализует деплои, но в очереди может стоять устаревшая сборка, и она своё отдеплоит, просто позже. Lock без milestone не спасает от обгона - нужны оба.
  • milestone в неправильном порядке. Номера ordinal должны строго возрастать по ходу конвейера. Если поставить milestone(ordinal: 2) раньше, чем ordinal: 1, поведение станет нелогичным. Проще не указывать ordinal вручную - тогда Jenkins нумерует рубежи автоматически в порядке появления.
  • Эфемерный ресурс вместо именованного. Опечатка в имени ресурса в lock не вызовет ошибку - создастся новый эфемерный ресурс, и сериализация просто не сработает. Для критичных стендов объявляй ресурсы глобально и проверяй, что имена совпадают.
  • agent any при parallel. Если на верхнем уровне стоит agent any, весь job занимает один исполнитель, и ветки parallel могут не разъехаться по узлам. Для настоящей параллельности используй agent none сверху и назначай agent внутри каждой ветки.
  • disableConcurrentBuilds не отменяет уже идущую. По умолчанию он ставит в очередь, а не убивает. Если ждёшь, что старая сборка прервётся, явно добавляй abortPrevious: true.
Сравнение с GitLab CI и GitHub Actions

Чтобы видеть Jenkins в контексте 2026 года: в GitLab CI аналог lock - это resource_group (сериализация job в рамках окружения) плюс interruptible: true для отмены устаревших пайплайнов. В GitHub Actions роль milestone и disableConcurrentBuilds играет блок concurrency с group и cancel-in-progress: true. Идея везде одна и та же - не пускать два деплоя в один контур и гасить обогнавшие друг друга прогоны. Разница в том, что у Jenkins это набор плагинов (Lockable Resources, Throttle) с очень гибкими пулами по меткам и переменными, тогда как в облачных системах механизм встроен, но проще. На современном Jenkins LTS (Java 17 в начале года, переход на Java 21 как базовый в апрельской ветке 2026) весь этот арсенал - часть стандартного declarative pipeline. Blue Ocean как UI давно заброшен и держится лишь как наследие, так что строй визуализацию на штатном Pipeline-графе и Stage View, а логику безопасности - на lock и milestone.

Мини-лаба
  • Собери pipeline с тремя параллельными ветками тестов (parallel) и добавь failFast true. Завали одну ветку нарочно и убедись, что остальные прибиваются.
  • Заведи глобальный lockable resource staging-env. Оберни стадию деплоя в lock('staging-env'). Запусти две сборки подряд и посмотри в UI плагина, как вторая ждёт.
  • Добавь milestone перед lock на стадии деплоя. Запусти три сборки почти одновременно (например, тремя быстрыми пушами) и убедись, что до деплоя дойдёт только самая свежая, а ранние отменятся.
  • Поменяй options на disableConcurrentBuilds(abortPrevious: true) и сравни поведение с обычным disableConcurrentBuilds().
Контрольные вопросы
  • Чем отличается роль lock от роли milestone при защите деплоя, и почему для безопасной выкатки нужны оба?
  • Что произойдёт, если в lock указать имя ресурса с опечаткой, не объявив его глобально?
  • В чём разница между disableConcurrentBuilds() и disableConcurrentBuilds(abortPrevious: true)?
  • Почему для настоящего параллелизма веток parallel лучше ставить agent none на верхнем уровне, а agent назначать внутри веток?
Итог

parallel ускоряет конвейер, разводя независимые задачи по агентам. lock сериализует доступ к общему ресурсу - один деплой на стенд за раз. milestone не даёт старой сборке обогнать новую и отменяет устаревшие прогоны на подходе к деплою. А disableConcurrentBuilds регулирует, сколько копий job вообще крутится одновременно. Связка milestone + lock на стадии выката - это и есть рабочий стандарт безопасного jenkins pipeline в 2026 году, который закрывает гонку деплоев и руками, и автоматически.
👍3 ❤️3 🔥2 😄 🤔
Аватара пользователя
wasm2003
Сообщения: 1
Зарегистрирован: 15 май 2026, 11:35

Re: Параллелизм и блокировки: parallel, lock, milestone

Сообщение wasm2003 »

Наконец-то дошло, зачем milestone отдельно от lock. Думал хватит одного lock на прод, а оно старую сборку в очереди не отменяет - спасибо, переделал.
👍1 ❤️2 🔥 😄 🤔
Аватара пользователя
matiah77
Сообщения: 1
Зарегистрирован: 29 май 2026, 04:49

Re: Параллелизм и блокировки: parallel, lock, milestone

Сообщение matiah77 »

А подскажите, lock с label и quantity работает внутри parallel веток? Хочу пул из трёх стендов раздавать на параллельные интеграционные тесты, чтобы каждая ветка свой стенд хватала.
👍1 ❤️1 🔥1 😄 🤔
Ответить
← Предыдущая глава
Управление потоком: timeout, retry, sleep и waitUntil
Следующая глава →
Постобработка и уведомления о результате сборки

Все главы курса «Jenkins: конвейеры CI/CD от основ до продакшена»

Поделиться темой: ✈ Telegram VK
Похожие запросы: параметры и параллельные стейджи в jenkins pipeline

Вернуться в «Jenkins: конвейеры CI/CD от основ до продакшена»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость