При настройке CI/CD процессов в GitHub Actions разработчики часто сталкиваются с ситуацией, когда несколько пушей в репозиторий или частые ручные запуски создают очередь из одинаковых рабочих процессов (workflows). Запуск нескольких параллельных задач на сборку или деплой не только неэффективно расходует ресурсы (минуты GitHub Actions), но и может привести к конфликтам при развертывании приложения.
В этой статье мы разберем, как использовать параметр concurrency, чтобы гарантировать выполнение только одного воркфлоу в конкретный момент времени, и как автоматически отменять устаревшие задачи.
Проблема параллельных запусков
Представьте ситуацию: вы правите баг и делаете три коммита подряд. GitHub Actions послушно запускает три идентичных процесса сборки. Если это процесс деплоя на сервер, то три параллельных потока могут начать перезаписывать файлы друг друга, что приведет к непредсказуемым ошибкам.
По умолчанию GitHub Actions запускает столько экземпляров workflow, сколько триггеров было активировано. Чтобы этого избежать, в функционале GitHub предусмотрен механизм управления очередностью — concurrency.
Использование ключа concurrency
Параметр concurrency позволяет объединять запуски в группы. Если в одной группе оказывается несколько задач, GitHub Actions будет следить за тем, чтобы активной была только одна.
Базовая настройка по ID группы
Вы можете задать concurrency как на уровне всего workflow, так и для конкретной задачи (job). Самый простой вариант — использование идентификатора группы:
concurrency: my-app-deploy
В данном случае, если запущен один воркфлоу с таким ID, второй не начнется, пока первый не завершится. Однако у такого подхода есть нюанс: новые задачи просто встают в очередь (pending), и вы все равно тратите время на их последующее выполнение.
Оптимизация: Параметр cancel-in-progress
Чтобы сделать процесс максимально эффективным, особенно для задач CI (непрерывной интеграции), логично отменять выполнение старых проверок, если появилась новая. Зачем дожидаться окончания сборки кода, который уже не актуален?
Для этого используется расширенный синтаксис с параметром cancel-in-progress:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
Как это работает:
- group: Мы задаем уникальное имя группы. Использование переменных
${{ github.workflow }}и${{ github.ref }}позволяет изолировать ограничения для разных веток. - cancel-in-progress: true: Это ключевая настройка. Если новый запуск попадает в ту же группу, где уже выполняется процесс, GitHub автоматически прерывает текущий (старый) процесс и начинает новый.
Практический пример конфигурации
Ниже приведен пример стандартного YAML-конфигурации для GitHub Actions, где реализовано ограничение на одновременные запуски:
name: Production Deployment
on:
push:
branches:
- main
concurrency:
group: production_environment
cancel-in-progress: true
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Deploy to Server
run: |
echo "Выполняется деплой..."
sleep 60 # Имитация долгого процесса
С такой конфигурацией, сколько бы раз вы ни пушили код в ветку main, на сервере всегда будет выполняться только самый актуальный деплой. Все промежуточные запуски будут мгновенно остановлены.
Заключение
Настройка concurrency в GitHub Actions — это признак профессионального подхода к организации CI/CD. Это простой, но мощный инструмент, который делает автоматизацию стабильной и предсказуемой.

