$ sudo teach|
    $ sudo teach|
    IT school
  • Telegram
  • Партнёрам
  • Все курсы
$ sudo teach IT
OOO "SALEPROFIT"Контакты и реквизитыIT-Park Logo

Школа

  • Блог
  • Проверить сертификат

Сотрудничество

  • Стать учителем
  • Партнёрская программа
  • О проекте

Право

  • Оферта
  • Политика конфиденциальности

© 2023–2026 $ sudo teach IT™. All Rights Reserved. Public user contributions licensed under CC BY-SA 4.0 license with attribution required
TelegramGitHubYouTube
ГлавнаяБлогКак устроен GitHub Actions: workflow, job, step и артефакты без путаницы

Как устроен GitHub Actions: workflow, job, step и артефакты без путаницы

$ sudo teach IT
·2 августа 2026 г.·9 мин·64
Как устроен GitHub Actions: workflow, job, step и артефакты без путаницы

Разберём архитектуру workflow и типовые сценарии: разделение по jobs, корректный кэш, сбор артефактов, повторное использование шагов и устойчивость к падениям. Будет много практических примеров для реальных проектов.

Содержание
Базовая модель исполнения: workflow → job → stepЧто такое workflowЧто такое jobЧто такое stepТиповые архитектурные паттерны workflowРазделяйте по смысловым этапам, а не “всё в один job”Используйте needs для явных зависимостейКэш зависимостей: что кэшировать и как не ошибитьсяПочему кэш может “не работать”Практический пример: pnpm (подходит многим monorepo)Как выбирать ключ кэшаЧто НЕ кэшироватьАртефакты: как переносить результаты между jobsЧто такое artifactsТиповой сценарий: build → test/report → deployНюансы передачи артефактовПовторное использование шагов: composite actions и локальная логикаПочему reusable workflows и composite actions полезныПример composite action: установка и тестирование (локальный)Подводные камни composite actionsУстойчивость к падениям: continue-on-error, условия и ранний выходРазделяйте “критические” и “некритические” проверкиУсловия if: always(), if: success(), if: failure()“Раннее падение” и экономия времениКомбинации: matrix, parallelism и общий смысл пайплайнаMatrix: тестирование на разных версияхАртефакты в matrixПример “почти реального” workflow: lint + test + build + deploy

GitHub Actions часто воспринимают как “набор кнопок для CI/CD”. На практике же это достаточно строгая модель исполнения, где маленькая ошибка в структуре (или в предположениях о порядке выполнения) приводит к трудноуловимым проблемам: кэши не срабатывают, артефакты “пропадают”, тесты выполняются не там, где вы думали, а падения маскируются.

Ниже разберём, как устроены ключевые сущности workflow, job, step и artifacts, как правильно структурировать пайплайн под реальные проекты и как сделать его устойчивым к типичным сбоям. Примеры будут максимально приближены к практике: Node/пакеты, тесты, сборки, кэш зависимостей и перенос результатов между стадиями.


Базовая модель исполнения: workflow → job → step

Что такое workflow

Workflow — это конфигурация процесса в файле .github/workflows/<name>.yml. В ней описываются:

  • когда запускать пайплайн (on)
  • какие окружения/версии использовать
  • наборы задач (jobs)

Один workflow может запускаться по нескольким триггерам и содержать несколько независимых jobs.

Ключевой момент: workflow — это шаблон, который GitHub исполняет. Он не является “пошаговым скриптом сверху вниз”. Внутри него есть DAG-подобная логика (зависимости между jobs через needs), а внутри job — последовательность шагов.

Что такое job

Job — это единица вычисления с собственным:

  • окружением (runner)
  • набором шагов (steps)
  • контекстом переменных и секретов
  • статусом (успешно/ошибка/пропуск)

Jobs могут выполняться параллельно (если нет needs) и не разделяют файловую систему между собой. То есть то, что вы собрали в одном job, напрямую в другом job не окажется — нужны механизмы передачи: artifacts (или более продвинутые варианты вроде caches или external storage).

Что такое step

Step — атомарная операция внутри job: запуск команды, вызов действия (action), установка зависимостей, публикация отчёта и т.д.

Важно: внутри одного job шаги выполняются последовательно (за исключением специализированных конструкций), поэтому порядок steps критичен.

Пример минимальной структуры:

code
name: CI

on:
  push:
    branches: [ main ]
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Install
        run: npm ci

      - name: Test
        run: npm test

Здесь:

  • workflow — “CI”
  • job — “build”
  • steps — checkout, install, test

Типовые архитектурные паттерны workflow

Разделяйте по смысловым этапам, а не “всё в один job”

Одна из самых частых ошибок — класть сборку, тесты, упаковку и публикацию в один job. В результате:

  • нельзя гибко управлять зависимостями (needs)
  • параллелизм отсутствует
  • сложнее ускорять пайплайн кэшами и артефактами
  • при частичных сбоях теряется весь результат

Правильнее разделять на job по смыслу:

  • lint (статический анализ)
  • test (юнит/интеграционные тесты)
  • build (сборка артефактов)
  • package/publish (публикация)

Например:

code
jobs:
  lint:
    runs-on: ubuntu-latest
    steps: ...

  test:
    runs-on: ubuntu-latest
    needs: lint
    steps: ...

  build:
    runs-on: ubuntu-latest
    needs: test
    steps: ...

Даже если сейчас всё запускается последовательно, разбиение сохраняет структуру и даёт “пространство для оптимизации” без переписывания всего workflow.

Используйте needs для явных зависимостей

needs — это не просто “какой job после какого”. Это механизм управления выполнением: job с needs не стартует, пока нужный job не завершится.

Практический нюанс: needs также помогает передавать статус — вы можете понять, в каком месте пайплайн “сломался”, а не гадать по логам одного большого job.


Кэш зависимостей: что кэшировать и как не ошибиться

Почему кэш может “не работать”

Кэш в GitHub Actions — не магия. Он возвращает и сохраняет директории по ключу. Если ключ не совпал — кэш считается отсутствующим. Поэтому кэш часто “не срабатывает” из-за:

  1. Неправильно выбранной директории (кэшируется не то, что реально ускоряет)
  2. Нестабильного ключа (например, зависит от “воды” вроде времени, или от файлов, которые меняются каждую правку)
  3. Отсутствия fallback restore-ключей (или они подобраны неверно)
  4. Разных ОС/раннеров для ключа и восстановления
  5. Для языков/менеджеров пакетов — кэш “не тем способом”: npm/yarn/pnpm имеют собственные директории кэша

Практический пример: pnpm (подходит многим monorepo)

Допустим, вы используете pnpm. Типично стоит кэшировать ~/.pnpm-store и lock-файл для стабильности ключа.

code
name: CI

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'  # полезная встроенная опция (если поддерживается в вашем случае)

      # Если встроенный cache не подходит — можно явно через actions/cache:
      # - name: Cache pnpm store
      #   uses: actions/cache@v4
      #   with:
      #     path: ~/.pnpm-store
      #     key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
      #     restore-keys: |
      #       ${{ runner.os }}-pnpm-

      - name: Install
        run: pnpm install --frozen-lockfile

      - name: Test
        run: pnpm test

Замечание: actions/setup-node@v4 умеет кэшировать зависимости для распространённых менеджеров, но его поведение зависит от контекста проекта. Если ваш репозиторий нестандартный (например, отдельные workspace-структуры), явный кэш может быть надёжнее.

Как выбирать ключ кэша

Лучший ключ — тот, который обновляется при изменении “влияющих” факторов. Для зависимостей это обычно lock-файл:

  • npm: package-lock.json
  • yarn: yarn.lock
  • pnpm: pnpm-lock.yaml

Пример ключа:

code
key: ${{ runner.os }}-node20-${{ hashFiles('pnpm-lock.yaml') }}

Почему важно учитывать runner.os:

  • различия окружений могут приводить к несовместимости кэшей
  • вы снижаете шанс странных ошибок при переходе между linux/windows/mac

Что НЕ кэшировать

Не стоит пытаться кэшировать:

  • node_modules целиком (для разных версий node/архитектур это часто ломается)
  • результаты сборки, которые зависят от множества параметров (если только вы не выстроили единый детерминизм)
  • временные директории, которые живут только в текущем job

Артефакты: как переносить результаты между jobs

Что такое artifacts

Артефакты — механизм хранения файлов, созданных на одном job, чтобы скачать их в другом job или потом посмотреть через интерфейс workflow.

Практически artifacts решают проблему “разделённых файловых систем” между jobs.

Типовой сценарий: build → test/report → deploy

  • job build собирает приложение и публикует артефакт (например, dist/)
  • job test запускает тесты, но может использовать build-результат (если нужно)
  • job deploy скачивает артефакт и разворачивает

Пример:

code
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20

      - run: npm ci
      - run: npm run build

      - name: Upload build artifact
        uses: actions/upload-artifact@v4
        with:
          name: app-dist
          path: dist/
          if-no-files-found: error
          retention-days: 7

  deploy:
    runs-on: ubuntu-latest
    needs: build
    steps:
      - name: Download artifact
        uses: actions/download-artifact@v4
        with:
          name: app-dist
          path: ./dist

      - run: ls -la dist
      - name: Deploy
        run: ./scripts/deploy.sh

Обратите внимание на if-no-files-found: error. Если dist/ не создался, пайплайн должен упасть с понятной причиной, а не “успешно завершиться” и потом сломаться в другом месте.

Нюансы передачи артефактов

  1. Не смешивайте имена артефактов: при нескольких build-матрицах следите за уникальностью name.
  2. Стабильный path: относительные пути чувствительны к working-directory. Для монорепозиториев лучше явно указывать path.
  3. Подумайте о “размере”: артефакты могут быть большими. Старайтесь публиковать то, что действительно нужно следующему этапу.

Повторное использование шагов: composite actions и локальная логика

Почему reusable workflows и composite actions полезны

Когда pipeline разрастается, вы сталкиваетесь с двумя проблемами:

  • копипаст одних и тех же шагов в разные workflow-файлы
  • необходимость поддерживать логику инсталляции/сборки в нескольких местах

GitHub предлагает два популярных подхода:

  1. Composite actions — набор шагов как “мини-действие”.
  2. Reusable workflows — переиспользование целого workflow с параметрами.

В контексте “повторного использования шагов” чаще удобнее composite action.

Пример composite action: установка и тестирование (локальный)

Допустим, вы хотите вынести шаги setup-node, npm ci, запуск тестов.

Файл /.github/actions/node-test/action.yml:

code
name: Node install and test
description: Install dependencies and run tests

inputs:
  node-version:
    required: true
  working-directory:
    required: false
    default: .

runs:
  using: composite
  steps:
    - uses: actions/setup-node@v4
      with:
        node-version: ${{ inputs.node-version }}

    - name: Install
      working-directory: ${{ inputs.working-directory }}
      run: npm ci

    - name: Test
      working-directory: ${{ inputs.working-directory }}
      run: npm test

Теперь в workflow:

code
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Run tests
        uses: ./.github/actions/node-test
        with:
          node-version: 20
          working-directory: .

Так вы уменьшаете шум в workflow и концентрируете логику в одном месте.

Подводные камни composite actions

  • Они не могут полностью заменить “настоящий” action по возможностям, но для шагов — идеальны.
  • Следите за версионированием: если composite action хранится в репозитории, при переносе может быть важно фиксировать ссылку по ref (для внешнего использования).

Устойчивость к падениям: continue-on-error, условия и ранний выход

Разделяйте “критические” и “некритические” проверки

Часто в реальных проектах:

  • линтер — важен, но иногда его можно настроить так, чтобы он давал отчёт, не блокируя весь пайплайн (зависит от политики команды)
  • отправка статуса в сторонние сервисы — может быть некритична
  • загрузка артефактов — лучше делать так, чтобы она не “скрывала” проблемы, но и не ломала отчётность без причины

continue-on-error позволяет продолжить выполнение шага даже при ошибке.

Например, сохранить junit-отчёт после падения тестов:

code
- name: Run tests
  id: tests
  run: npm test -- --reporter=junit || true

- name: Upload test report
  if: always()
  uses: actions/upload-artifact@v4
  with:
    name: junit-report
    path: test-results/
    if-no-files-found: warn

Тут:

  • тесты не останавливают job (|| true)
  • артефакты пытаемся загрузить всегда (if: always())
  • если отчёта нет — предупреждаем, но не делаем из этого “основную” причину отказа

Важный нюанс: если вам принципиально нужно, чтобы job падал при ошибках тестов, используйте другой механизм: сохраняйте отчёт в любом случае, но возвращайте корректный код. Например, можно запомнить результат и в конце завершить job с ошибкой.

Условия if: always(), if: success(), if: failure()

Это базовые инструменты устойчивости:

  • if: always() — выполнится независимо от статуса предыдущих шагов
  • if: success() — только при успехе
  • if: failure() — только при провале

Классический пример — загрузка отчётов при падении линтера или тестов.

code
- name: Lint
  run: npm run lint

- name: Upload lint report
  if: always()
  uses: actions/upload-artifact@v4
  with:
    name: lint-report
    path: reports/lint/
    if-no-files-found: warn

“Раннее падение” и экономия времени

Иногда лучший способ устойчивости — не скрывать проблемы, а обнаруживать их раньше:

  • делайте линт/форматирование до дорогих шагов
  • проверяйте зависимости и версии инструментов перед сборкой
  • в матрицах (например, тесты под разные версии node) используйте разумный порядок

Если job очевидно упадёт из-за несовместимости, нет смысла собирать огромный билд.


Комбинации: matrix, parallelism и общий смысл пайплайна

Matrix: тестирование на разных версиях

GitHub Actions поддерживает стратегию матрицы, где каждый элемент создаёт отдельную “вариацию” job.

Пример:

code
jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        node: [18, 20, 22]

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}

      - run: npm ci
      - run: npm test

fail-fast: false полезен, когда вы хотите увидеть все несовместимости, а не остановиться на первой ошибке.

Артефакты в matrix

Если каждое значение матрицы производит отчёт, обязательно именуйте артефакты с включением matrix.node, иначе они будут перетираться или вам будет сложно разобраться.

code
- name: Upload report
  if: always()
  uses: actions/upload-artifact@v4
  with:
    name: junit-${{ matrix.node }}
    path: test-results/
    if-no-files-found: warn

Пример “почти реального” workflow: lint + test + build + deploy

Ниже — цельный пример, где отражены принципы:

  • разбиение на jobs
  • кэширование зависимостей
  • публикация build-артефакта
  • загрузка отчётов при падении
  • устойчивость и понятные условия
code
name: CI/CD

on:
  push:
    branches: [ main ]
  pull_request:

permissions:
  contents: read

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'

      - name: Install
        run: npm ci

      - name: Lint
        run: npm run lint

  test:
    runs-on: ubuntu-latest
    needs: lint
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'

      - name: Install
        run: npm ci

      - name: Run tests
        run: npm test -- --reporter=junit

      - name: Upload test report
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: junit
          path: test-results/
          if-no-files-found: warn

  build:
    runs-on: ubuntu-latest
    needs: test
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'

      - name: Install
        run: npm ci

      - name: Build
        run: npm run build

      - name: Upload dist
        uses: actions/upload-artifact@v4

Войдите, чтобы поставить лайк и оставить комментарий.

Автор

$ sudo teach IT

Продолжите обучение

Все курсы
Python – для начинающих!

Python – для начинающих!

С нуля до профессионального уровня. Подходит для всех. Учитесь каждый день и овладейте самым популярным языком программирования.

Перейти к курсу
Solidity DLC - поймай, если сможешь

Solidity DLC - поймай, если сможешь

Аудит смарт-контрактов на реальных взломах. The DAO, Parity, Wormhole, Ronin, Euler и ещё 7 разборов: что пошло не так, сколько потеряли и что бы спасло. Атаки и защиты на Foundry, план входа в профессию аудитора.

Перейти к курсу
Ботостроение Telegram

Ботостроение Telegram

Лёгкий, быстрый и доступный способ познакомиться с миром ботостроения в Telegram. Видео, конспекты, практика и помощь – всё у нас на курсе.

Перейти к курсу
Solidity pro - всё или ничего

Solidity pro - всё или ничего

Как пишут контракты, которые работают в основной сети. Наследование и OpenZeppelin, паттерны продакшена, оптимизация газа, ABI, delegatecall и подписи. 7 модулей, 33 урока, а в финале собственное dApp: мини-DAO с токеном, голосованием и казной.

Перейти к курсу

Другие статьи

Как устроен GIL в Python: что он ограничивает и как это влияет на реальный код
устроен

Как устроен GIL в Python: что он ограничивает и как это влияет на реальный код

Разберём, почему GIL мешает CPU-bound потокам, а для IO-bound иногда всё “выглядит как будто работает”. Поймём, когда выбирать потоки, процессы и asyncio, и как измерить эффект на вашей задаче.

16 сентября 2026 г.
1570
GitHub Actions на практике: автоматизируем тесты, линтинг и деплой за один вечер
github

GitHub Actions на практике: автоматизируем тесты, линтинг и деплой за один вечер

Пишем реальный workflow с нуля: запуск pytest, проверка кода через Ruff и автоматический деплой на сервер при пуше в main. Разбираем структуру YAML-конфига и типичные ошибки.

18 июля 2026 г.
1200
Как выучить программирование самому и бесплатно: с чего начать и в каком порядке
выучить

Как выучить программирование самому и бесплатно: с чего начать и в каком порядке

Разберём понятный маршрут для новичка: как выбрать первый язык, что учить в первую очередь, какие мини-проекты делать и как не забросить занятия. Дам бесплатные ориентиры и план, чтобы знания складывались в портфолио.

21 сентября 2026 г.
1960
Как начать с HTML и сделать первую страницу: пошагово и без сложных слов
html

Как начать с HTML и сделать первую страницу: пошагово и без сложных слов

Разберём, что нужно новичку, чтобы за 1–2 вечера собрать свою первую веб-страницу: структура HTML, базовые теги, ссылки и картинки, плюс простой чек-лист, как понять, что страница работает правильно.

2 ноября 2026 г.
20
Cargo для продакшена: workspaces, фичи и стратегия версий зависимостей
cargo

Cargo для продакшена: workspaces, фичи и стратегия версий зависимостей

Разберём, как организовать Rust workspace, управлять optional features, фиксировать версии библиотек и уменьшать “дрейф” зависимостей при командной разработке и CI.

18 сентября 2026 г.
1960
Что такое HTML и чем он отличается от CSS — объясню простыми словами и на примерах
такое

Что такое HTML и чем он отличается от CSS — объясню простыми словами и на примерах

Разберём, из чего состоит веб-страница: что делает HTML (смысл и структура) и что делает CSS (оформление). В конце соберёте мини-страницу и поймёте, что нужно учить в каком порядке.

30 октября 2026 г.
130

Комментарии

Станьте первым в обсуждении

Поделитесь мнением о статье или задайте вопрос автору

Содержание

Базовая модель исполнения: workflow → job → stepЧто такое workflowЧто такое jobЧто такое stepТиповые архитектурные паттерны workflowРазделяйте по смысловым этапам, а не “всё в один job”Используйте needs для явных зависимостейКэш зависимостей: что кэшировать и как не ошибитьсяПочему кэш может “не работать”Практический пример: pnpm (подходит многим monorepo)Как выбирать ключ кэшаЧто НЕ кэшироватьАртефакты: как переносить результаты между jobsЧто такое artifactsТиповой сценарий: build → test/report → deployНюансы передачи артефактовПовторное использование шагов: composite actions и локальная логикаПочему reusable workflows и composite actions полезныПример composite action: установка и тестирование (локальный)Подводные камни composite actionsУстойчивость к падениям: continue-on-error, условия и ранний выходРазделяйте “критические” и “некритические” проверкиУсловия if: always(), if: success(), if: failure()“Раннее падение” и экономия времениКомбинации: matrix, parallelism и общий смысл пайплайнаMatrix: тестирование на разных версияхАртефакты в matrixПример “почти реального” workflow: lint + test + build + deploy