Управление потоком: timeout, retry, sleep и waitUntil

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

Управление потоком: timeout, retry, sleep и waitUntil

Сообщение 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
Конвейер, который зависает навсегда на одном шаге - классическая боль. Тест-раннер потерял соединение с базой и висит, исполнитель занят, очередь сборок растёт, дежурный ночью получает алерт и идёт убивать билд руками. Или наоборот: деплой падает раз в десять запусков из-за моргнувшей сети, а ты каждый раз перезапускаешь джобу вручную. Оба сценария лечатся встроенными шагами управления потоком. В этом уроке разберём, как jenkins flow control спасает реальные конвейеры: timeout режет зависания, retry глушит флапающие операции, sleep даёт паузу, а waitUntil ждёт готовности сервиса. Всё это - обычные шаги из плагина Pipeline: Basic Steps, доступные в любом declarative pipeline на актуальном Jenkins LTS (на момент урока это линия 2.541.x, Java 17/21).

Зачем нужен контроль выполнения шагов

По умолчанию шаг конвейера выполняется столько, сколько потребуется. Если процесс под капотом завис, jenkins pipeline будет терпеливо ждать - минуты, часы, до перезапуска контроллера. Агент при этом занят, слот исполнителя не освобождается. Поэтому первое правило надёжного конвейера: любой потенциально долгий или сетевой шаг должен иметь верхнюю границу по времени и, если операция нестабильна, политику повтора.

Все четыре героя урока - это step-функции, которые принимают замыкание (блок кода) и оборачивают его своим поведением. Их можно вкладывать друг в друга, и именно из комбинаций рождается надёжность.

Изображение

jenkins timeout: прервать зависший шаг

Шаг timeout задаёт лимит времени на выполнение вложенного блока. Если лимит превышен, Jenkins прерывает блок и сборка получает статус ABORTED. Параметры: time (число), unit (SECONDS, MINUTES, HOURS - по умолчанию минуты) и activity (если true, таймер считает не общее время, а время без вывода в лог - удобно для зависших, но молчащих процессов).

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

pipeline {
    agent any
    stages {
        stage('Integration tests') {
            steps {
                timeout(time: 10, unit: 'MINUTES') {
                    sh './gradlew integrationTest'
                }
            }
        }
    }
}
Тот же jenkins timeout есть и в виде директивы options - на уровне стейджа или всего конвейера. Это чище, когда лимит относится ко всему стейджу, а не к одному шагу:

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

stage('Deploy') {
    options {
        timeout(time: 15, unit: 'MINUTES')
    }
    steps {
        sh './deploy.sh production'
    }
}
Вариант с activity ловит именно зависание, а не легитимно долгую работу:

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

timeout(time: 5, unit: 'MINUTES', activity: true) {
    sh './run-noisy-task.sh'
}
Здесь таймер сбрасывается на каждой строке вывода. Пока скрипт что-то пишет в лог - он жив. Замолчал на 5 минут - значит завис, прерываем.

jenkins retry: повторить флапающий шаг

Шаг retry повторяет вложенный блок до N попыток, если внутри вылетело исключение. Первая попытка плюс повторы: retry(3) - это до трёх запусков всего. Между попытками есть небольшая стартовая пауза (по умолчанию около 250 мс). Это лекарство от моргающей сети, медленно поднимающегося контейнера, временно недоступного registry - всего, что падает не из-за бага, а из-за внешней нестабильности.

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

stage('Push image') {
    steps {
        retry(3) {
            sh 'docker push registry.example.com/app:$BUILD_NUMBER'
        }
    }
}
Важно: jenkins retry повторяет блок при ЛЮБОМ исключении. Не оборачивай в retry шаги с побочными эффектами, которые нельзя выполнять дважды (например, не идемпотентную миграцию БД без защиты), иначе три попытки наделают трёх бед. Повторяй только идемпотентные операции либо те, где повтор безопасен.

Связка timeout + retry для надёжного деплоя

Самое ценное в этом jenkins pipeline - комбинация. Нам нужно: повторить деплой при сбое, но чтобы каждая отдельная попытка не висла вечно. Порядок вложения решает всё.

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

stage('Deploy with safety net') {
    steps {
        retry(3) {
            timeout(time: 5, unit: 'MINUTES') {
                sh './deploy.sh staging'
            }
        }
    }
}
Здесь timeout ВНУТРИ retry. Логика: даём попытке 5 минут; если она зависла или упала - retry ловит это и запускает попытку заново, снова с пятиминутным лимитом. Итого до трёх честных попыток по 5 минут каждая. Это то, что нужно для деплоя.

А вот если поменять местами - timeout снаружи, retry внутри - смысл другой и почти всегда нежелательный: общий лимит времени накрывает все попытки разом. Сработал внешний timeout - и retry уже не получит шанса, потому что прерывание таймаута гасит весь блок. Запомни как правило: для надёжного деплоя timeout кладём внутрь retry, а не наоборот.

sleep и jenkins waitUntil: ждать готовности

Шаг sleep - тупая пауза на заданное время: sleep(time: 10, unit: 'SECONDS') или коротко sleep 10 (секунды по умолчанию). Использовать sleep как способ "дождаться, пока сервис поднимется" - антипаттерн: либо ждёшь слишком мало и ловишь гонку, либо слишком много и тормозишь конвейер. Для ожидания условия есть jenkins waitUntil.

Шаг waitUntil выполняет блок повторно, пока тот не вернёт true. Вернул false - подождал и попробовал снова. Пауза между проверками растёт от стартовой (initialRecurrencePeriod, по умолчанию 250 мс) до максимума в 15 секунд. Параметр quiet: true глушит лог на каждой итерации, чтобы не засорять вывод. Сам по себе waitUntil ждёт бесконечно - поэтому его ВСЕГДА оборачивают в timeout.

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

stage('Wait for service health') {
    steps {
        timeout(time: 3, unit: 'MINUTES') {
            waitUntil(quiet: true) {
                script {
                    def code = sh(
                        script: 'curl -sf -o /dev/null -w "%{http_code}" http://staging:8080/health',
                        returnStdout: true
                    ).trim()
                    return code == '200'
                }
            }
        }
    }
}
Тот же приём для появления файла или готовности миграции:

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

timeout(time: 60, unit: 'SECONDS') {
    waitUntil {
        return fileExists('/shared/build.lock') == false
    }
}
Здесь блок возвращает true, когда lock-файл исчез. Возвращай из тела waitUntil именно булево; не оставляй внутри шаг, который сам бросает исключение при неудаче (иначе waitUntil не поймёт, что надо повторить, и блок просто упадёт - для этого случая есть параметр quiet/обработка, но проще писать тело так, чтобы оно отдавало true/false).

Мягкая обработка: catchError и warnError

Иногда сбой шага не должен ронять всю сборку. catchError выполняет блок и, если внутри ошибка, ставит результат сборки в указанный статус, но даёт конвейеру жить дальше. warnError - частный удобный случай: ловит ошибку и помечает сборку и стейдж как UNSTABLE, выводя твоё сообщение.

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

stage('Optional smoke test') {
    steps {
        warnError('Smoke-тест не прошёл, помечаю как нестабильно') {
            sh './smoke.sh'
        }
    }
    stage('Publish report') {
        steps {
            catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
                sh './upload-report.sh'
            }
        }
    }
}
Связка с timeout: если внутренний шаг прервался по таймауту, поймать это прерывание можно тем же catchError/warnError - и продолжить конвейер вместо аборта.

Типичные грабли
  • timeout снаружи retry вместо внутри - повторов фактически нет, внешний таймаут гасит всё.
  • retry вокруг не идемпотентного шага - три попытки = три побочных эффекта.
  • waitUntil без timeout - конвейер может ждать вечно, исполнитель занят навсегда.
  • sleep вместо waitUntil для ожидания сервиса - гонки или потерянное время.
  • Тело waitUntil бросает исключение вместо возврата false - блок падает на первой же неудаче.
  • Слишком жёсткий timeout на легитимно долгом шаге - ложные аборты; используй activity: true там, где работа долгая, но не молчаливая.
Для сравнения: в GitLab CI повтор настраивается ключом retry с max, а таймаут - директивой timeout на job; в GitHub Actions есть timeout-minutes у job/step, а повтор обычно тянут через сторонний action. У Jenkins же это полноценные программируемые шаги, которые свободно вкладываются друг в друга - в этом его сила для нетривиальной логики.

Мини-лаба
  • Создай pipeline-джобу. Оберни шаг sh 'sleep 120' в timeout(time: 5, unit: 'SECONDS') и убедись, что сборка завершилась как ABORTED через 5 секунд.
  • Сделай флапающий шаг: sh 'if [ $((RANDOM % 2)) -eq 0 ]; then exit 1; fi' и оберни в retry(5). Запусти несколько раз, посмотри в логе повторы.
  • Собери надёжный деплой: retry(3) { timeout(time: 30, unit: 'SECONDS') { ... } }. Поменяй местами вложение и сравни поведение при зависании.
  • Подними локально любой сервис с /health и дождись его через waitUntil под timeout. Затем погаси сервис и проверь, что таймаут срабатывает корректно.
Контрольные вопросы
  • Почему для надёжного деплоя timeout кладут внутрь retry, а не наоборот?
  • Какой статус получает сборка при срабатывании timeout и как этот статус смягчить?
  • Чем waitUntil лучше связки sleep + проверка, и почему его всегда оборачивают в timeout?
  • В чём разница между catchError и warnError по влиянию на результат сборки и стейджа?
Итог

timeout, retry, sleep и waitUntil - это базовый набор для управления потоком в конвейере. timeout защищает от зависаний, retry глушит флап нестабильных операций, waitUntil ждёт реальной готовности вместо слепых пауз, а catchError и warnError позволяют не ронять всю сборку из-за второстепенного сбоя. Ключевая идея - правильное вложение: timeout внутри retry для деплоя, waitUntil всегда под timeout. Освой эти комбинации - и ночные алерты про зависшие билды уйдут из твоей жизни.
👍2 ❤️4 🔥1 😄 🤔1
Аватара пользователя
solidity2003
Сообщения: 1
Зарегистрирован: 12 май 2026, 19:42

Re: Управление потоком: timeout, retry, sleep и waitUntil

Сообщение solidity2003 »

Поменял timeout и retry местами как в уроке - и правда, внешний таймаут резал все попытки разом. Теперь деплой переживает моргнувший registry, спасибо за объяснение порядка вложения.
👍2 ❤️1 🔥 😄 🤔1
Аватара пользователя
marabu
Сообщения: 1
Зарегистрирован: 11 май 2026, 02:23

Re: Управление потоком: timeout, retry, sleep и waitUntil

Сообщение marabu »

А подскажите, waitUntil реально ждёт с растущей паузой до 15 секунд? Боялся что он будет долбить curl каждые 250мс и положит сервис, но quiet:true и backoff закрыли вопрос.
👍2 ❤️2 🔥 😄 🤔
Ответить
← Предыдущая глава
Параметры сборки и пользовательский ввод
Следующая глава →
Параллелизм и блокировки: parallel, lock, milestone

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

Поделиться темой: ✈ Telegram VK

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

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

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