Jobs, dependencies и conditions
Как проектировать граф jobs: `needs`, `if`, status functions, outputs, `continue-on-error`, `timeout-minutes`, параллельность по умолчанию и когда нужен последовательный pipeline.
Jobs, dependencies и conditions
Job в GitHub Actions — это единица выполнения на runner: отдельная машина или контейнерный runtime, свой набор steps, свои permissions, timeout, outputs и статус. Внутри одного workflow jobs по умолчанию не образуют последовательный pipeline. Если между ними не указана зависимость через needs, GitHub старается запускать их параллельно, насколько позволяют доступные runners и лимиты.
Минимальная ментальная модель такая: on решает, создавать ли workflow run; jobs описывает граф работ; needs задает ребра графа; if решает, должен ли конкретный job или step выполняться в уже созданном run. Это важное различие: filters в Events и filters могут вообще не создать run, а if создает run, но помечает часть графа как skipped.
name: CI
on:
pull_request:
push:
branches: [main]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run lint
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm testВ этом примере lint и test стартуют независимо. Это хорошая форма для CI: чем меньше искусственных зависимостей, тем быстрее обратная связь. Последовательность нужна не «для порядка», а когда один job реально потребляет результат другого, должен ждать его статуса или обязан идти после gate.
flowchart TD
A[lint] --> D[ci-ok]
B[test] --> D[ci-ok]
C[typecheck] --> D[ci-ok]
D --> E{ref == main?}
E -->|yes| F[deploy]
E -->|no| G[skip deploy]
B --> H[upload failure logs]
H -. if: failure() .-> Dneeds: явный граф, а не скрытый порядок
needs говорит: «этот job можно запускать только после успешного завершения указанных jobs». Значение может быть строкой или массивом.
jobs:
lint:
runs-on: ubuntu-latest
steps:
- run: npm run lint
test:
runs-on: ubuntu-latest
steps:
- run: npm test
build:
needs: [lint, test]
runs-on: ubuntu-latest
steps:
- run: npm run buildЕсли lint или test упадет, build будет skipped. Если skipped или failure случились в начале цепочки, downstream jobs тоже обычно skipped. Поэтому длинный линейный pipeline стоит строить только там, где он отражает реальную зависимость: например, build → publish image → deploy production.
Плохой запах — pipeline, где lint ждет test, test ждет build, хотя они проверяют один и тот же checkout. Такой граф медленнее и менее информативен: первая ошибка скрывает остальные. Лучше запускать независимые проверки параллельно, а затем собирать общий gate.
jobs:
lint:
runs-on: ubuntu-latest
steps:
- run: npm run lint
test:
runs-on: ubuntu-latest
steps:
- run: npm test
typecheck:
runs-on: ubuntu-latest
steps:
- run: npm run typecheck
ci-ok:
needs: [lint, test, typecheck]
runs-on: ubuntu-latest
steps:
- run: echo "CI gate passed"Такой ci-ok часто используют как один required check в branch protection. Но есть нюанс: если upstream job skipped из-за if, gate тоже может стать skipped. Для сложных required checks это уже тема Monitoring и troubleshooting.
if: condition на job и step
if можно ставить на job и на step. На job-уровне он решает, попадет ли весь job в исполнение; на step-уровне — выполнится ли конкретная команда или action.
jobs:
deploy:
if: github.ref == 'refs/heads/main'
needs: [build]
runs-on: ubuntu-latest
steps:
- run: ./scripts/deploy.shВ if на job GitHub автоматически воспринимает выражение как expression, поэтому часто пишут без ${{ }}. Но если выражение начинается с !, лучше явно обернуть его: YAML использует ! как специальную нотацию.
if: ${{ ! startsWith(github.ref, 'refs/tags/') }}Важно: jobs.<job_id>.if вычисляется до применения strategy.matrix. Поэтому нельзя на этом уровне обращаться к matrix.node так, будто matrix уже развернута. Условия, зависящие от конкретной комбинации matrix, обычно ставят внутри steps или выражают через саму matrix-конфигурацию. Подробная механика matrix вынесена в Matrix strategy, а синтаксис contexts и функций — в Contexts и expressions.
Status functions: success, failure, cancelled, always
Условия в Actions имеют скрытый default: если в if нет status function, GitHub фактически применяет success(). Поэтому step с if: steps.build.outcome == 'failure' может не сработать после ошибки, если не добавить failure().
steps:
- name: Build
id: build
run: npm run build
- name: Upload logs after build failure
if: ${{ failure() && steps.build.conclusion == 'failure' }}
uses: actions/upload-artifact@v4
with:
name: build-logs
path: logs/success() возвращает true, когда предыдущие steps успешны. failure() полезен для диагностики после падения; в цепочке зависимых jobs он также учитывает failure предков. cancelled() нужен для отдельных cleanup-сценариев. always() запускает step или job даже после failure/cancel, но его не стоит ставить на критичные операции вроде checkout или подготовки окружения: можно получить job, который висит до timeout. Для «сделать, если workflow не был отменен» часто безопаснее условие if: ${{ !cancelled() }}.
Job outputs: данные между jobs
Jobs изолированы. Переменная shell из build не появится в deploy. Чтобы передать небольшое структурное значение, используют step output → job output → needs.<job_id>.outputs.
jobs:
plan:
runs-on: ubuntu-latest
outputs:
deploy_target: ${{ steps.pick.outputs.target }}
steps:
- id: pick
run: |
if [ "${GITHUB_REF}" = "refs/heads/main" ]; then
echo "target=production" >> "$GITHUB_OUTPUT"
else
echo "target=staging" >> "$GITHUB_OUTPUT"
fi
deploy:
needs: plan
if: needs.plan.outputs.deploy_target == 'production'
runs-on: ubuntu-latest
steps:
- run: echo "Deploying to ${{ needs.plan.outputs.deploy_target }}"Outputs подходят для коротких строк, JSON-фрагментов и решений графа. Для файлов, отчетов, coverage и build outputs лучше использовать Artifacts и reports. Для зависимостей — Dependency cache, не outputs.
continue-on-error и timeout
continue-on-error меняет смысл падения. На step-уровне он позволяет job продолжить выполнение после неуспешного step. На job-уровне он может позволить workflow run считаться успешным, даже если этот job failed. Это удобно для experimental checks, nightly compatibility или non-blocking security audit, но опасно для обязательного CI: легко случайно сделать красный сигнал невидимым.
jobs:
test-experimental:
runs-on: ubuntu-latest
continue-on-error: true
steps:
- run: npm test -- --experimental-runtimeВ matrix это обычно делают точечно: stable-комбинации блокируют pipeline, experimental-комбинации дают сигнал, но не ломают весь run. С этим рядом живут strategy.fail-fast и strategy.max-parallel: они отвечают не за зависимости, а за поведение группы matrix jobs.
timeout-minutes задает верхнюю границу выполнения. У job default — 360 минут; у step тоже можно ставить timeout, максимум 360 минут. Практический смысл timeout — не только экономия минут. Он делает зависание явным failure, особенно для e2e-тестов, Docker build и deploy-команд.
jobs:
e2e:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- run: npm run test:e2e
timeout-minutes: 15Когда pipeline должен быть последовательным
Последовательный граф оправдан, когда есть материальный артефакт или gate: сначала собрать контейнер, потом просканировать image, потом задеплоить; сначала создать release, потом опубликовать package; сначала пройти environment approval, потом получить environment secrets. В Deployment workflows это становится особенно важно: деплой — не просто еще один test job, а изменение внешней системы.
Для CI чаще работает другой принцип: максимум независимых проверок параллельно, один небольшой aggregation job в конце, минимум side effects. Тогда workflow быстрее, failures легче читать, а граф в Actions UI действительно показывает архитектуру проверки, а не случайный порядок строк в YAML.
See also
- Workflow: файл, запуск, jobs и steps — базовая структура workflow-файла и то, где живет
jobs. - Events и filters — чем trigger filters отличаются от
ifвнутри workflow run. - Contexts и expressions — синтаксис
${{ }}, contexts, функции и expression-time evaluation. - Step outputs и job outputs — подробная передача значений между steps и jobs.
- Matrix strategy — параллельные варианты job и управление fail-fast/max-parallel.
- CI pipeline — практическая сборка CI из lint, test, build и reports.
- Deployment workflows — последовательные deploy-графы, environments и внешние side effects.
- Monitoring и troubleshooting — skipped jobs, pending checks, reruns и диагностика условий.
