Capstone: чтение реального deploy.yml как кейс-стади по release engineering
Прочитай один реальный деплой-workflow от и до — concurrency cancel-in-progress, artifact-free build-deploy джоб, cache-keyed state, ~12-минутную сборку — и оцени против планки release engineering: версионирование, changelog, гейты, обратимость. Критика, а не recall.
Пайплайн, что ты сейчас прочитаешь, реален — это форма workflow, что деплоит большой статический учебный сайт, и он заработал свою текущую форму тяжёлым путём. Более ранняя версия шардила сборку по шести параллельным джобам и передавала ~420 МБ артефактов между ними на каждом ране; это разнесло квоту artifact-storage free-tier, и раз квота была достигнута, она блокировала каждый upload — даже файл 163 КБ — пока платформа не пересчитывала использование шесть-двенадцать часов спустя, заклинивая каждый деплой. Фикс был не большей квотой; а структурным переписыванием на единственный artifact-free джоб build-deploy, что держит state лишь в build-кэше. Эта история — суть этого capstone: релиз-пайплайн судится не по тому, зелён ли он сегодня, а по тому, как он падает, как восстанавливается и можешь ли ты ответить «что отгрузилось и как откатить» в 3 ночи. Прочитай этот workflow так, как senior ревьюит пайплайн коллеги — найди, что он делает хорошо, что оставляет на столе и что бы ты изменил, прежде чем он понесёт что-то, двигающее деньги.
Workflow на ревью
Вот форма пайплайна, сжатая до несущих частей:
name: build-deploy
on:
push:
branches: [main]
concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: true # новый push отменяет деплой в полёте
permissions:
contents: read
jobs:
build-deploy: # один джоб: plan → build → lint → deploy
runs-on: ubuntu-latest
timeout-minutes: 90
steps:
- uses: actions/checkout@v4
- uses: actions/cache@v4 # state живёт здесь, не в артефактах
with:
key: build-${{ hashFiles('content-hash') }}
path: dist/
- run: bun install --frozen-lockfile
- run: bun run build # полный рендер ~12 мин (инкрементальный короче)
- run: cat dist/lint-report.json # build-time linter-гейт
- run: ./deploy.sh dist/ # upload статики на CDN/Pages-хостЧто он делает хорошо, в словаре этого юнита:
concurrencyсcancel-in-progress: true, ключеванный по ref, означает, что новый push вmainотменяет более старый, ещё бегущий деплой той же ветки. Ты никогда не получаешь два деплоя, гоняющихся за публикацию, и никогда не тратишь 12-минутную сборку на коммит, что уже вытеснен. Это верный дефолт для деплоя статики на единую цель, где важно лишь последнее состояние.- Единственный artifact-free джоб — рубцовая ткань из Hook: plan, build, lint и deploy бегут в одном джобе и передают state через
actions/cache(отдельная от артефактов квота), поэтому нет передачи 420 МБ, что заклинит. Держание всего пайплайна в одном джобе также означает, что нет меж-джобного артефакта, что истечёт или повредится — цена в том, что шаги не могут масштабироваться вширь, что для ~12-минутной сборки приемлемый размен. permissions: contents: read— least privilege: токен джоба может читать репо и ничего больше, поэтому скомпрометированный шаг не может пушить коммиты или публиковать пакеты.- Build-time linter (
lint-report.json) — релиз-гейт внутри сборки: контент, нарушающий правила, валит ран до того, как что-либо отгрузится.
Почему `concurrency: { group: deploy-${{ github.ref }}, cancel-in-progress: true }` — верный выбор для этого деплоя статики на единую цель, и где он был бы опасен?
Оцени против планки release engineering
Читая любой пайплайн, попробуй разделить два вопроса: «это корректно деплоит?» и «это управляет релизом?» Большинство пайплайнов отвечают да на первый и нет на второй. Workflow операционно крепок, но прочитай его против четырёх столпов этого юнита, и дыры ясны — и поучительны, потому что они разница между «деплоит статику» и «управляет релизом».
- Версионирование и теги: этот пайплайн деплоит на каждый push в
mainбез версии, без тега и без неизменяемого ref. Для статики, что публикует latest-wins, это защитимый выбор — но он означает, что нет именованного релиза, на который указать, а «откатить на последнюю хорошую версию» нечего именовать. Митигация, что подходит этому дизайну — собственная неизменяемая история деплоев хоста (каждый upload — отдельный, адресуемый деплой, что можно мгновенно пере-промоутить), которая заменяет git-тег как якорь обратимости. - Changelog и релизная запись: нет генерируемого changelog и нет релизного объекта, поэтому «что изменилось в этом деплое» отвечаемо лишь чтением коммита, что его триггернул. Приемлемо для внутреннего сайта; дыра в момент, когда внешним потребителям или аудиторам нужна запись на релиз.
- Окружения и гейты: деплой бежит прямо в production на push без окружения, без required reviewer и без wait-таймера. Каждый смерженный коммит отгружается мгновенно без человека на момент релиза и без выдержки. Это самая большая дыра против юнита: для чего угодно сверх низко-ставочной статики ты бы поставил деплой за окружение
productionс required reviewer и коротким wait-таймером, и вероятно разделил build (на push) и deploy (гейтированный). - Обратимость: нет явного пути отката в workflow. Восстановление целиком зависит от способности хоста деплоя пере-промоутить предыдущий деплой. Это работает для статического CDN-хоста, но это неявно — senior сделал бы «передеплой предыдущий известно-хороший деплой» явным, протестированным, одно-командным путём, а не свойством, что он предполагает у платформы.
Ревьюер говорит, что у этого пайплайна 'нет истории отката'. Учитывая, что он деплоит статику на CDN-хост с неизменяемой историей деплоев на upload, что самая точная критика и фикс?
▸Почему это работает
Почему идеально зелёный, хорошо собранный пайплайн всё равно не дотягивает до планки юнита? Потому что «собирает и деплоит корректно» и «управляет релизом» — разные работы. Этот workflow прибивает операционный слой — конкурентность, least privilege, lint-гейт и закалённая-сбоем структура одного джоба, заработанная из реальной аварии. Но release engineering добавляет слой управления поверх: именованную, неизменяемую вещь, что каждый деплой производит (версию/тег или адресуемый деплой), человекочитаемую запись о том, что изменилось, намеренный человеческий гейт перед production и явный, протестированный путь назад. Контекст статики делает некоторые из них опциональными — публикация latest-wins по-настоящему не нуждается в semver — но в момент, когда тот же пайплайн несёт что-то, от чего зависит пользователь или что инспектирует аудитор, каждый опущенный столп становится дырой, что находит инцидент. Хорошо читать реальный пайплайн значит называть, какие упущения оправданы контекстом, а какие — латентные инциденты.
- 01Резюмируй, что этот пайплайн делает хорошо операционно и почему каждый выбор крепок для деплоя статики на единую цель.
- 02Пройди четыре столпа release engineering и оцени этот пайплайн по каждому, отличая оправданные контекстом упущения от латентных инцидентов.
Capstone читает один реальный деплой-workflow как кейс-стади и судит его так, как senior судит пайплайн коллеги. Операционно он крепок, и его форма заработана: ключеванный по ref concurrency с cancel-in-progress отменяет вытесненный деплой, чтобы два не гонялись и ни одна долгая сборка не тратилась; единственный artifact-free джоб build-deploy, передающий state через actions/cache — структурный фикс реальной аварии, где 420 МБ меж-джобных артефактов исчерпали квоту хранилища и заклинили каждый upload на часы; permissions: contents: read — least privilege; а build-time linter гейтит несоответствующий контент до отгрузки. Судимый против четырёх столпов этого юнита, однако, слой управления тонок. Нет версии, тега или неизменяемого ref — приемлемо для latest-wins публикации статики, с неизменяемой историей деплоев на upload хоста как якорем обратимости. Нет генерируемого changelog или релизного объекта, поэтому «что изменилось» — это лишь триггерящий коммит. Самая большая дыра — гейты: пайплайн отгружает прямо в production на push без окружения, required reviewer или wait-таймера, поэтому каждый merge релизит мгновенно без человека на момент релиза и без выдержки — для чего-то сверх низко-ставочного сайта ты бы загейтил деплой за окружение production и вероятно разделил build и deploy. А откат неявен, опираясь на фичу пере-промоута платформы, а не явный, протестированный одно-командный путь. Урок чтения реального пайплайна в том, что быть зелёным сегодня — не планка: релиз-пайплайн судится по тому, как он падает, как откатывается и можешь ли ты ответить «что отгрузилось и как откатить» под давлением — а честное ревью называет, какие упущения контекст оправдывает, а какие просто следующий ждущий инцидент. Теперь, когда откроешь любой deploy.yml, ты знаешь, какие два вопроса задать первыми — и как отличить оправданное упущение от латентного инцидента.
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.