open atlas
↑ К треку
CI/CD-пайплайны CICD · 09 · 01

Composite-экшены: упаковка шагов без накладных расходов рантайма

Composite-экшен упаковывает последовательность шагов в один action.yml без отдельного рантайма — он выполняется inline на раннере за миллисекунды, но наследует shell раннера и делит файловую систему вместо изоляции зависимостей.

CICD Senior ◷ 15 min
Уровень
ОсновыJuniorMiddleSenior

Один и тот же блок из одиннадцати шагов настройки — checkout, установка тулчейна, восстановление кэша, аутентификация в реестре, печать баннера версии — был скопирован в девятнадцать воркфлоу монорепы. Когда тулчейн поднял мажорную версию, платформенная команда правила его в девятнадцати файлах; три пропустили, и они неделю выкатывали сломанные ночные сборки. Решением был не JavaScript-экшен и не Docker-образ: это был composite-экшен — те одиннадцать шагов дословно подняли в единый action.yml, опубликовали как org/setup-toolchain@v2 и вызывали одной строкой uses:. Ни нового рантайма, ни шага бандлинга, ни pull контейнера. Первый прогон после миграции оказался быстрее inline-версии, которую он заменил, ведь единственное, что composite добавил, — слой косвенности, который раннер разрешает до выполнения любого шага. Хрупкость никогда не была в шагах — она была в наличии девятнадцати их копий.

К концу урока ты будешь точно знать, когда composite-экшен (действие GitHub Actions, упаковывающее шаги в один reusable-блок) — правильный инструмент, какие два правила авторинга ловят опытных инженеров врасплох и где его дизайн без изоляции превращается в проблему.

Что такое composite-экшен на самом деле

Зачем вообще использовать composite, а не reusable workflow или скрипт? Потому что composite — единственный тип, не добавляющий никаких накладных рантайма: когда одиннадцать скопированных шагов становятся бременем поддержки, composite схлопывает их в одну строку uses: без контейнера и без Node-процесса. Вот как это работает изнутри.

Composite-экшен — это метаданные, а не программа. Его action.yml объявляет runs.using: "composite" и список steps, а в момент вызова GitHub Actions вклеивает эти шаги в вызывающую джобу так, будто вы написали их inline — они выполняются на том же раннере, в том же рабочем каталоге, под тем же $GITHUB_WORKSPACE. Нет процесса Node.js для запуска и нет образа контейнера для pull, так что накладные расходы быть экшеном — по сути парсинг YAML: однозначные миллисекунды против джобы, которая уже стоит секунды.

# .github/actions/setup-toolchain/action.yml
name: "Setup Toolchain"
description: "Install the toolchain, restore cache, authenticate"
inputs:
  version:
    description: "Toolchain version"
    required: false
    default: "20"
outputs:
  cache-hit:
    description: "Whether the dependency cache was restored"
    value: ${{ steps.cache.outputs.cache-hit }}
runs:
  using: "composite"
  steps:
    - uses: actions/setup-node@v4
      with:
        node-version: ${{ inputs.version }}
    - id: cache
      uses: actions/cache@v4
      with:
        path: node_modules
        key: deps-${{ hashFiles('package-lock.json') }}
    - name: Install
      shell: bash
      run: npm ci

Два правила кусают здесь старших авторов. Первое: каждый шаг run: внутри composite обязан объявить shell: — нет дефолта уровня джобы для наследования, так что голый блок run: проваливает валидацию. Второе: inputs — это не переменные окружения: внутри composite вы читаете их как ${{ inputs.version }}, а не $VERSION, и чтобы поднять значение к вызывающему, прокидываете его через outputs[].value, ссылающийся на output шага, как показывает проводка cache-hit выше.

Викторина

В action.yml composite-экшена есть шаг с `run: npm ci` и без других ключей. Воркфлоу падает при загрузке экшена с ошибкой валидации. Чего не хватает?

Компромисс: скорость, купленная общим состоянием

То, что composite даёт бесплатно, — это же и то, что может ранить: отсутствие изоляции. Он выполняется в окружении вызывающего, видит его файлы и мутирует его файловую систему. Если ваш composite запускает npm install -g some-cli, этот бинарь теперь в PATH для каждого последующего шага джобы — удобно, пока два composite не поставят конфликтующие глобальные версии. Нет песочницы node_modules, нет вендоринга зависимостей; composite, зависящий от jq, предполагает, что jq уже есть на раннере. Это нормально для оркестрации — выстраивания инструментов, что у раннера уже есть, — и неверно для логики, нуждающейся в своих запиненных зависимостях. Этот последний случай — ровно то, для чего существуют JavaScript- и Docker-экшены.

Прежде чем ставить глобальный CLI внутри composite, спроси себя: ожидает ли следующий composite в этой джобе чистый PATH — или ему всё равно, что твой бинарь уже там? Этот вопрос вскрывает настоящую опасность до того, как она выстрелит в продакшне.

Ещё два острых края. Composite могут вкладываться и вызывать другие экшены, но обработка ошибок грубая: упавший шаг прерывает composite так же, как упавший шаг джобы прерывает джобу, и до 1.0 нельзя было даже использовать continue-on-error пошагово (теперь поддерживается, но условия if: на шагах composite всё ещё вычисляются иначе, чем на шагах воркфлоу — они не видят состояние success()/failure() уровня джобы так же). И пиннинг безопасности применяется рекурсивно: когда ваш composite делает uses: actions/cache@v4, этот тег изменяем. Для всего, что касается креденшелов, пиньте вложенные экшены полным commit SHA — uses: actions/cache@<40-символьный-sha> — чтобы скомпрометированный тег в зависимости не мог внедрить код в каждый воркфлоу, что вызывает ваш composite.

Викторина

Команде нужен переиспользуемый экшен, выполняющий нетривиальную логику со своими запиненными сторонними зависимостями, изолированными от того, что окажется на раннере. Почему composite-экшен плохо подходит?

Вспомните перед уходом
  1. 01
    Какой рантайм использует composite-экшен и какие накладные расходы он добавляет к джобе по сравнению с JavaScript- или Docker-экшеном?
  2. 02
    Назови три вещи, что специфично кусают авторов composite-экшенов, помимо того, что требует обычный шаг воркфлоу.
Итог

Composite-экшен — легчайший способ сделать шаги GitHub Actions переиспользуемыми: его action.yml ставит runs.using: "composite" и перечисляет steps, а в момент вызова эти шаги вклеиваются в вызывающую джобу и выполняются inline на том же раннере. Отдельного рантайма нет — ни процесса Node, ни контейнера — так что накладные быть экшеном это парсинг YAML, однозначные миллисекунды против джобы, измеряемой секундами; миграция со скопированных inline-шагов на composite обычно даёт чистый выигрыш в скорости плюс устранение N разошедшихся копий. Цена — отсутствие изоляции: экшен делит shell, PATH и $GITHUB_WORKSPACE раннера, так что глобальные установки протекают в последующие шаги, а экшен полагает, что его инструменты уже есть на раннере, а не вендорит своё дерево зависимостей. Это делает composite идеальным для оркестрации имеющихся у раннера инструментов и неверным для самодостаточной логики с запиненными сторонними библиотеками — работы для JavaScript- и Docker-экшенов. Грабли авторинга, ловящие старших: каждому шагу run: нужен явный shell:, ведь нет унаследованного дефолта; inputs читаются как ${{ inputs.name }}, а не env-переменные, а outputs поднимаются через outputs[].value, привязанный к output шага; вложенные ссылки uses: — изменяемые теги, что стоит пинить по полному 40-символьному commit SHA всякий раз, когда креденшелы где-то рядом с воркфлоу, чтобы скомпрометированный тег зависимости не мог внедрить код в каждого вызывающего. Теперь, когда встретишь скопированные блоки настройки в нескольких воркфлоу, ты знаешь: тяни к composite — и пиннинг по SHA ставь ещё до того, как первый потребитель прогонит через него креденшелы.

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 5 завершено

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.