Git, CI/CD и деплой без простоя
От коммита на вашем ноутбуке до безопасного и обратимого релиза в продакшене.
За 30 секунд
Git записывает каждое изменение кода и позволяет команде параллельно работать в ветках. Continuous integration запускает проверки (линтер, тесты, сборку) на каждое изменение, так что проблемы ловятся за минуты, а не в продакшене. Continuous delivery затем выкатывает протестированную сборку на серверы так, что пользователи этого не замечают, а вы можете быстро всё откатить.
Почему я это использую
- Я использую Git как общую память команды: у каждого изменения есть автор, причина и путь назад.
- Я полагаюсь на автоматические проверки, потому что люди устают, а пайплайны — нет; красная сборка дешевле сломанной страницы оформления заказа.
- Мне важен деплой без простоя, потому что клиенты принимают заказы и платежи круглосуточно, и удачного момента для страницы «ведутся технические работы» просто нет.
- Маленькие, частые и обратимые релизы — самое действенное из известных мне средств от страха перед деплоем.
Как это работает
01Основы Git: коммиты, ветки, merge и rebase
Коммит — это снимок всего проекта плюс сообщение и указатель на родителя, так что история — это цепочка снимков. Ветка — просто подвижная метка, указывающая на коммит, поэтому ветки дешёвые. Когда две линии работы нужно свести вместе, вы делаете merge или rebase. Merge создаёт новый коммит с двумя родителями и сохраняет историю ровно такой, какой она была. Rebase переигрывает ваши коммиты поверх свежего main и даёт прямую линию, но переписывает эти коммиты с новыми хэшами. Правило: делайте rebase своей локальной или feature-ветки, чтобы навести порядок, но никогда не делайте rebase коммитов, которые другие уже стянули. Хорошие коммиты маленькие и сфокусированные, а их сообщение объясняет, зачем, а не только что.
git switch -c feature/order-export
git add -p # stage hunks, review as you go
git commit -m "Add CSV export for orders"
git fetch origin
git rebase origin/main # update your branch, resolve conflicts
git push --force-with-lease # safe force-push of your own branch
git log --oneline --graph -15
02Стратегии ветвления: trunk-based и GitFlow
Trunk-based разработка держит одну основную ветку, которая всегда готова к релизу. Разработчики работают в короткоживущих ветках, которые в идеале вливаются за день-два, а незаконченные фичи прячут за feature flag'ами. Это сводит к минимуму болезненные мерджи и естественно сочетается с continuous deployment. GitFlow использует долгоживущие ветки develop и main плюс ветки feature, release и hotfix. Он подходит продуктам с версионированными релизами по расписанию — например, мобильным приложениям, проходящим ревью в сторах, или on-premise софту, где одновременно поддерживается несколько версий. Для большинства веб-приложений выигрывает trunk-based с короткими pull request'ами. Что бы вы ни выбрали, защитите main: требуйте pull request'ы, зелёные проверки и хотя бы одно ревью, запретите force-push.
- Trunk-based: короткие ветки, feature flag'и, деплой из main.
- GitFlow: ветки develop + release, хорош для версионированных релизов.
- Релизные теги (v1.8.0) точно отмечают, что ушло в продакшен.
03Pull request'ы и код-ревью
Pull request — это предложение влить ветку плюс обсуждение вокруг него. Его задача — ловить баги, делиться знаниями и поддерживать единообразие кодовой базы, а не показывать, кто тут сеньор. Держите pull request'ы маленькими, в идеале до нескольких сотен строк: качество ревью резко падает с ростом размера. В описании должно быть, что изменилось, зачем и как это проверялось, а для UI — скриншоты. Стиль и форматирование пусть проверяет автоматика, чтобы люди могли сосредоточиться на логике, сохранности данных, безопасности, именовании и недостающих тестах. Ревьюеры должны быть конкретными и доброжелательными и отделять блокирующие замечания от предложений. Файлы CODEOWNERS направляют изменения людям, которые знают эту часть кода, а обязательные status check'и не дают ничего влить, пока CI красный.
04Continuous integration: пайплайн на каждое изменение
Continuous integration означает, что каждый push и pull request автоматически собирается и тестируется в чистом окружении. Типичный пайплайн ставит зависимости из lockfile, запускает линтер и проверку типов, гоняет unit- и feature-тесты и собирает продакшен-артефакт. Располагайте шаги от быстрых к медленным, чтобы большинство ошибок всплывало за минуту-две. Кэшируйте загрузку зависимостей между запусками. Пайплайн должен быть одинаковым шлагбаумом для всех: если он красный, изменение не вливается. Ниже — минимальный workflow GitHub Actions для Node- или Next.js-проекта; GitLab CI, Bitbucket Pipelines и другие используют очень похожие идеи с другим синтаксисом. Секреты живут в хранилище секретов CI-провайдера и никогда — в репозитории.
name: ci
on:
pull_request:
push:
branches: [main]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with: { node-version: 24, cache: npm }
- run: npm ci
- run: npm run lint
- run: npx tsc --noEmit
- run: npm test
- run: npm run build
05Стратегии деплоя: rolling, blue-green, canary
Continuous delivery означает, что любую зелёную сборку можно задеплоить одной кнопкой; continuous deployment — что она деплоится автоматически. Важно и то, как вы заменяете работающую версию. Rolling deploy заменяет инстансы по одному за load balancer'ом: мощность слегка проседает, но сервис продолжает работать. Blue-green держит два одинаковых окружения: вы деплоите в простаивающее (green), проверяете его, затем переключаете трафик на балансировщике или симлинком — и обратное переключение мгновенно. Canary отправляет на новую версию небольшую долю трафика, скажем пять процентов, следит за ошибками и задержками и постепенно расширяет долю. На одном сервере идею blue-green обычно реализуют через папки релизов и атомарное переключение симлинка или через запуск нового контейнера, который проходит health check, прежде чем на него переключат proxy.
- Rolling: дёшево, постепенно, две версии работают одновременно.
- Blue-green: мгновенное переключение и мгновенный откат, ненадолго нужна двойная мощность.
- Canary: ограничивает радиус поражения, но нужны хорошие метрики, чтобы судить.
06Деплой без простоя для Laravel и Next.js
Для Laravel на VPS проверенный паттерн — атомарные релизы: каждый деплой уходит в новую папку с таймстемпом, общие .env и storage подключаются симлинками, там же ставятся зависимости и собираются кэши, и только потом симлинк current переключается одним атомарным шагом. Reload PHP-FPM очищает OPcache, а queue:restart говорит воркерам доделать текущую задачу и стартовать уже на новом коде. Для Next.js тоже собирайте в новую папку релиза, а затем плавно перезагружайте Node-процесс (pm2 reload в cluster-режиме запускает новые воркеры до остановки старых) или запускайте новый контейнер и переключайте proxy после того, как он прошёл health check. Остерегайтесь рассинхрона версий: браузеры со старой открытой страницей могут запрашивать старые JavaScript-чанки, поэтому какое-то время держите предыдущие статические ассеты доступными.
#!/usr/bin/env bash
set -euo pipefail
APP=/var/www/app
RELEASE=$APP/releases/$(date +%Y%m%d%H%M%S)
git clone --depth 1 --branch main [email protected]:your_user/app.git "$RELEASE"
cd "$RELEASE"
rm -rf storage && ln -s $APP/shared/storage storage
ln -s $APP/shared/.env .env
composer install --no-dev --optimize-autoloader --no-interaction
npm ci && npm run build
php artisan migrate --force
php artisan optimize
ln -sfn "$RELEASE" $APP/current # atomic switch
sudo systemctl reload php8.3-fpm # match your PHP version
php artisan queue:restart
07Миграции базы данных и откаты
Код можно откатить за секунды, данные — нет. Во время любого деплоя старый и новый код какое-то мгновение работают одновременно, поэтому каждая миграция должна работать с обеими версиями. Паттерн expand and contract делает это безопасным. Чтобы переименовать колонку: сначала добавьте новую колонку и пишите в обе (expand), задеплойте, заполните старые строки пачками, переключите чтение на новую колонку и только в одном из следующих релизов удалите старую (contract). Избегайте долгих блокировок больших таблиц; добавляйте индексы конкурентно, если база это поддерживает. Откат — это повторный деплой предыдущей папки релиза или тега образа, поэтому держите несколько последних. Для базы данных лучше катиться вперёд исправляющей миграцией, чем запускать down-миграции в продакшене, и всегда имейте свежий бэкап перед рискованными изменениями.
- Сначала только добавление: новые колонки nullable или со значениями по умолчанию.
- Разрушающие изменения (удаление, переименование) — только когда старую структуру больше не использует никакой код.
- Держите на диске несколько предыдущих релизов для мгновенного отката.
Когда применять
- Любой проект, где больше одного разработчика или больше одного деплоя в месяц.
- Везде, где простой стоит денег: магазины, системы бронирования, платёжные сценарии, SaaS.
- Когда клиенту нужен аудит: что изменилось, когда и почему.
- И для мобильных приложений: пайплайны могут единообразно собирать, подписывать и загружать сборки в сторы.
Когда не стоит
- Полноценная canary-инфраструктура для маленького сайта-визитки — хватит простого атомарного деплоя.
- Релизные ветки GitFlow в веб-приложении, которое деплоится каждый день: они добавляют церемоний, а не безопасности.
- Автоматический деплой в продакшен без тестов — так вы просто быстрее отгружаете баги.
Альтернативы
- GitHub Actions
CI/CD, встроенный в GitHub, огромный маркетплейс action'ов, workflow на YAML.
- GitLab CI/CD
Пайплайны, окружения и container registry в одном продукте; можно хостить у себя.
- Deployer / Envoyer
Инструменты для PHP, реализующие атомарный деплой через папки релизов для Laravel.
- Kamal
Деплоит Docker-контейнеры на обычные серверы с переключением без простоя, оркестратор не нужен.
Лучшие практики
- Защитите main: pull request'ы, обязательные зелёные проверки и хотя бы одно ревью.
- Держите ветки короткоживущими, а pull request'ы — такими, чтобы их можно было отревьюить за один присест.
- Собирайте один раз и деплойте один и тот же артефакт везде; никогда не пересобирайте для продакшена.
- Делайте каждую миграцию обратно совместимой с кодом, который сейчас работает.
- Держите наготове несколько последних релизов и потренируйтесь откатываться до того, как это понадобится.
- Запускайте health check после каждого деплоя и роняйте пайплайн, если он не прошёл.
- Храните секреты в хранилище секретов CI или на сервере, никогда — в Git.
Частые ошибки
- Делать rebase и force-push ветки, которую коллеги уже стянули.
- Запускать git pull и composer install прямо в живой папке, оставляя сайт наполовину обновлённым на минуту.
- Удалять или переименовывать колонку в том же релизе, что и изменение кода.
- Забыть queue:restart, из-за чего воркеры часами крутят старый код.
- Огромные pull request'ы, которые одобряют после беглого взгляда, потому что по-настоящему их никто отревьюить не может.
Глубже
08Как Git хранит вашу работу и почему почти ничего не теряется
Git — это база данных с адресацией по содержимому. Каждая версия файла хранится как blob, каждый каталог — как tree, перечисляющий blob'ы и другие tree, а каждый коммит — как объект, который указывает на один корневой tree, родительские коммиты, автора и сообщение. Имя каждого объекта — хэш его содержимого, поэтому одинаковые файлы хранятся один раз, а любая подмена меняет хэш. Ветки и теги — это ref'ы: маленькие файлы с одним хэшем коммита. HEAD указывает на ref, на котором вы сейчас находитесь. Поскольку коммиты никогда не редактируются, а только создаются новые, rebase или reset не уничтожают старые коммиты — они просто теряют свою метку. Reflog записывает каждое положение HEAD и каждой ветки, обычно примерно за 90 дней, так что после неудачного rebase или reset можно найти старый хэш и снова направить на него ветку. Garbage collection удаляет недостижимые объекты только после этого срока.
git reflog -15 # where HEAD has been
git branch rescue HEAD@{3} # recover the state from 3 moves ago
git reset --hard ORIG_HEAD # undo the last rebase or merge
git fsck --lost-found # find dangling commits and blobs
git cat-file -p HEAD # see a commit object: tree, parent, author
09Защита самого пайплайна
CI-система держит ключи от продакшена, поэтому атакуют именно её. Важнее всего четыре меры. Первая — минимальные права для токена джоба: явно объявляйте permissions в начале каждого workflow, по умолчанию только чтение. Вторая — полностью откажитесь от долгоживущих облачных ключей: GitHub Actions, GitLab и другие умеют выдавать короткоживущий OIDC-токен, который AWS, Google Cloud или Azure обменивают на временные учётные данные, ограниченные одним репозиторием и веткой. Третья — фиксируйте сторонние actions на полный SHA коммита, а не на тег, потому что тег может передвинуть любой, кто контролирует тот репозиторий. Четвёртая — осторожнее с workflow, которые запускаются pull request'ами из форков: pull_request_target выполняется с секретами базового репозитория, поэтому он никогда не должен делать checkout и запускать недоверенный код. Деплой в продакшен должен идти через защищённое окружение с обязательными ревьюерами, чтобы скомпрометированная ветка не могла задеплоить сама.
permissions:
contents: read
id-token: write # allow OIDC token for cloud login
jobs:
deploy:
environment: production # required reviewers + env-scoped secrets
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: aws-actions/configure-aws-credentials@v6
with:
role-to-assume: arn:aws:iam::111122223333:role/deploy-web
aws-region: eu-central-1
10Собирать один раз, продвигать по digest и выстраивать деплои в очередь
Зрелый пайплайн собирает артефакт ровно один раз — при merge в main, помечает его SHA коммита и пушит в registry. Staging и продакшен затем деплоят тот же самый артефакт, определяемый неизменяемым digest, так что протестированное байт в байт совпадает с выкаченным. Пересборка для продакшена снова вносит риск: за это время могли поменяться зависимость или базовый образ. Два одновременных деплоя могут оставить сервер наполовину на каждой версии, поэтому выстраивайте их в очередь через concurrency group и не отменяйте уже идущий деплой: прерваться на середине хуже, чем довести до конца. Записывайте каждый деплой с коммитом, временем и автором, чтобы на вопрос «что сейчас крутится в продакшене» был ответ в одну строку. Откат тогда — это просто повторный деплой предыдущего digest.
concurrency:
group: deploy-production
cancel-in-progress: false # queue, never kill a running deploy
# build stage (once):
# docker build -t registry.example.com/web:$GITHUB_SHA .
# docker push registry.example.com/web:$GITHUB_SHA
# deploy stages reuse the same tag/digest, no rebuild
11Изменения схемы на больших таблицах без блокировки сайта
Expand and contract сохраняет совместимость кода и схемы, но каждый шаг всё равно может заблокировать таблицу. В PostgreSQL большинство форм ALTER TABLE берут блокировку ACCESS EXCLUSIVE. Даже быстрое изменение должно дождаться окончания любой долгой транзакции, а пока оно ждёт, все новые запросы к этой таблице встают в очередь за ним — и миграция на две миллисекунды может заморозить сайт на минуты. Защищайтесь коротким lock_timeout и повторами. Индексы стройте через CREATE INDEX CONCURRENTLY: он не блокирует запись, но не может выполняться внутри транзакции, поэтому во фреймворках, которые оборачивают миграции в транзакции, для такой миграции это нужно отключить (в Laravel — выставить свойство миграции withinTransaction в false). Добавлять NOT NULL на огромную таблицу безопаснее как CHECK-ограничение с NOT VALID и последующей валидацией. В MySQL предпочитайте ALGORITHM=INSTANT, где он поддерживается, или инструменты вроде gh-ost для больших перестроений.
SET lock_timeout = '3s';
ALTER TABLE orders ADD COLUMN export_status text; -- metadata-only, fast
CREATE INDEX CONCURRENTLY idx_orders_export_status
ON orders (export_status);
ALTER TABLE orders ADD CONSTRAINT orders_export_status_nn
CHECK (export_status IS NOT NULL) NOT VALID;
ALTER TABLE orders VALIDATE CONSTRAINT orders_export_status_nn;
Реальные ситуации
Секрет закоммитили и запушили
Проблема
Разработчик замечает, что коммит, запушенный час назад, содержит продакшен-ключ API в конфиг-файле. Репозиторий приватный, но несколько человек и CI-система уже его стянули.
Как решить
Сначала ротация, потом уборка. Немедленно отзовите ключ у провайдера и выпустите новый: всё, что было запушено, считается утёкшим, — и проверьте логи провайдера на неожиданное использование. Затем уберите файл из кода и перенесите значение в хранилище секретов. Переписывать историю через git filter-repo для приватного репозитория необязательно, и это вынуждает всех заново клонировать репозиторий, так что делайте это, только если того требует политика. Наконец, не допускайте повторения: включите secret scanning и push protection на хостинге, добавьте pre-commit hook вроде gitleaks и внесите шаблон конфиг-файла в .gitignore.
Баг появился где-то в последних 200 коммитах
Проблема
В редком случае суммы заказов считаются неверно. Последний точно корректный релиз был три недели назад, и никто не может сказать, какой из примерно 200 коммитов внёс проблему.
Как решить
Используйте git bisect — он делает бинарный поиск по истории, так что для 200 коммитов нужно всего около восьми проверок. Напишите небольшой скрипт или тест, который воспроизводит баг и завершается с ненулевым кодом при ошибке. Затем выполните git bisect start, git bisect bad HEAD, git bisect good v1.7.0 и git bisect run ./check-totals.sh. Git сам переключается на коммиты, запускает скрипт и сообщает первый плохой коммит. Прочитайте его diff, превратите скрипт в постоянный регрессионный тест, исправьте баг и закончите командой git bisect reset. Bisect лучше всего работает, когда каждый коммит собирается, — ещё одна причина делать маленькие самодостаточные коммиты.
Пайплайн идёт 25 минут, и его перестают ждать
Проблема
Разработчики мержат, не дождавшись CI, или перезапускают упавшие джобы, пока те не пройдут. Main краснеет дважды в неделю, и проверкам больше никто не доверяет.
Как решить
Сначала измерьте каждый шаг — обычно доминируют два-три. Кэшируйте загрузку зависимостей с ключом по lockfile и кэшируйте результаты сборки, где инструмент это поддерживает. Разделите независимую работу на параллельные джобы: линт и проверка типов, юнит-тесты и сборка могут идти бок о бок, а большие наборы тестов можно шардировать на несколько раннеров. В монорепозитории пропускайте нерелевантные джобы через фильтры по путям. Нестабильные тесты отправьте в отдельный неблокирующий джоб с владельцем и дедлайном вместо того, чтобы разрешать перезапуски. Затем сделайте проверки обязательными в branch protection или добавьте merge queue, чтобы ничего не попадало в main без зелёного прогона. Цель меньше десяти минут реалистична для большинства веб-приложений.
Шпаргалка
- Восстановить потерянный коммит или ветку
git reflog then git branch rescue <hash>- Безопасно отменить влитый pull request
git revert -m 1 <merge-commit-hash>- Найти коммит, который что-то сломал
git bisect start && git bisect bad && git bisect good v1.7.0 && git bisect run ./test.sh- Найти, когда строку добавили или удалили
git log -S "calculateTotal" --oneline- Поправить более ранний коммит до ревью
git commit --fixup <hash> && git rebase -i --autosquash origin/main- Работать над хотфиксом без stash
git worktree add ../app-hotfix origin/main- Force-push, только если никто больше не пушил
git push --force-with-lease- Выстроить деплои в очередь в GitHub Actions
concurrency: { group: deploy-production, cancel-in-progress: false }- Дать CI войти в облако без ключей
permissions: { id-token: write, contents: read }- Индекс в Postgres без блокировки записи
CREATE INDEX CONCURRENTLY idx_name ON orders (column);
В моих реальных проектах
- Примерно 7 100 коммитов в 19 продакшен-репозиториях — около половины всех коммитов в репозиториях команды.
- У приложений на React Native есть задокументированный процесс релиза, а force-update через Remote Config служит страховкой, когда старую версию нужно вывести из оборота.
- Офлайн-хаб филиала в POS SaaS использует версионированные миграции SQLite, закреплённые по контрольной сумме, так что изменённая миграция не может тихо выполниться.
- Изменения проходят через workflow агентов «планировщик — исполнитель — ревьюер» с хуками проверки типов после правок, прежде чем что-то будет влито.
Вопросы и ответы
Q1Чем merge отличается от rebase и когда что использовать?
Merge объединяет две ветки merge-коммитом с двумя родителями; история остаётся ровно такой, какой была, включая момент расхождения веток. Rebase берёт ваши коммиты и переигрывает их поверх другой базы, давая линейную историю, но создаёт новые коммиты с новыми хэшами. Я делаю rebase своей feature-ветки на свежий main перед открытием или обновлением pull request'а: так diff остаётся чистым, а конфликты разрешает тот, кто знает изменение. Я никогда не делаю rebase коммитов, которые другие уже стянули. Для вливания в main подойдёт merge или squash-merge — команде просто нужно выбрать одну договорённость.
Q2Trunk-based разработка или GitFlow?
Для веб-приложения, которое часто деплоится, — trunk-based. Короткоживущие ветки, влитые за день-два, избавляют от больших конфликтов, держат main всегда готовым к релизу и подходят для continuous deployment. Незаконченная работа прячется за feature flag'ами, а не живёт в ветке неделями. GitFlow по-прежнему имеет смысл, когда релизы версионированы и идут по расписанию — например, мобильные приложения, проходящие ревью в сторах, десктопный софт или продукты, где параллельно поддерживается несколько версий. Даже тогда я держу feature-ветки короткими. Стратегия важна меньше, чем дисциплина: маленькие изменения, защищённый main, обязательные проверки и ревью.
Q3Чем отличаются continuous integration, delivery и deployment?
Continuous integration означает, что каждое изменение часто вливается, автоматически собирается и тестируется, так что проблемы интеграции всплывают за минуты. Continuous delivery идёт дальше: каждое изменение, прошедшее пайплайн, даёт релиз-кандидат, который можно в любой момент задеплоить в продакшен, обычно по кнопке ручного подтверждения. Continuous deployment убирает эту кнопку, и каждое зелёное изменение уходит в продакшен автоматически. Для этого нужны сильные тесты, хороший мониторинг и быстрый откат, потому что ни один человек не проверяет каждый релиз. Многие команды делают continuous deployment на staging и continuous delivery в продакшен — разумная золотая середина.
Q4Как запускать миграции базы данных без простоя?
Я исхожу из того, что старый и новый код какое-то время будут работать одновременно, поэтому каждая миграция должна быть совместима с обоими. Это значит expand and contract. Expand: добавить новые таблицы или nullable-колонки, задеплоить код, который пишет в обе структуры, заполнить существующие строки пачками. Contract: когда старую структуру уже никто не читает, удалить её в одном из следующих релизов. Я избегаю операций, которые надолго блокируют большие таблицы, добавляю индексы конкурентно, где это возможно, и запускаю тяжёлые изменения данных фоновыми задачами, а не внутри деплоя. Перед рискованными миграциями я убеждаюсь, что есть свежий бэкап, и в продакшене предпочитаю исправлять вперёд, а не запускать down-миграции.
Q5Blue-green или canary деплой?
Blue-green держит два одинаковых окружения: вы деплоите в простаивающее, проверяете его и затем переключаете весь трафик разом. Откат — это просто обратное переключение, очень быстрое и понятное, но ненадолго нужна двойная мощность, и все пользователи переезжают одновременно. Canary направляет небольшой процент трафика на новую версию, сравнивает уровень ошибок и задержки со старой и шаг за шагом увеличивает долю. Он ограничивает радиус поражения неудачного релиза, но, чтобы он окупался, нужны хорошие метрики, разделение трафика и автоматизация. Маленьким командам обычно хватает большей части пользы от атомарных переключений в стиле blue-green.
Q6Деплой сломал продакшен. Что вы делаете?
Сначала восстанавливаю сервис, потом разбираюсь. Если вероятная причина — деплой, сразу откатываюсь на предыдущий релиз или тег образа; именно поэтому я держу наготове несколько релизов. Если миграция уже изменила данные, откаченный код всё равно должен работать с новой схемой — это гарантирует expand and contract. Если откат невозможен, выкатываю небольшое исправление вперёд. Параллельно сообщаю статус заинтересованным людям. Потом пишу короткий blameless postmortem: хронология, первопричина, почему тесты и мониторинг этого не поймали, и одно-два конкретных действия — новый тест, алерт или проверка при деплое.
Вопросы уровня senior
S1Как бы вы спроектировали CI/CD для монорепозитория с несколькими приложениями?
Цель — чтобы каждое изменение запускало только затронутую им работу, а изменения общего кода всё равно тестировали всё, что от него зависит. Инструмент сборки, понимающий граф зависимостей, например Turborepo, Nx или Bazel, умеет вычислять затронутые проекты и кэшировать результаты задач локально и удалённо, так что неизменённые пакеты не пересобираются. CI затем запускает линт, тесты и сборку для каждого затронутого приложения в параллельных джобах, а у каждого приложения свой пайплайн деплоя, версия и откат. Фильтры по путям — более простой старт, но они пропускают транзитивные зависимости. Компромиссы: монорепозиторий делает сквозные изменения атомарными и держит инструменты единообразными, но требует вложений в кэширование и правила владения, обычно CODEOWNERS по каталогам. Polyrepo упрощают пайплайны, но превращают обновление общих библиотек в рутину «выпусти и подними версию» во многих репозиториях.
S2Feature flags или долгоживущие ветки для больших фич?
В большинстве случаев флаги. Долгоживущая ветка уходит всё дальше от main, поэтому финальный merge получается большим, рискованным и тяжёлым для ревью, а фича тестируется на старой версии всего остального. С флагами код вливается маленькими частями за выключенным в продакшене переключателем, интеграция идёт непрерывно, и можно включить фичу для сотрудников, затем для одного процента пользователей, затем для всех — и мгновенно выключить без деплоя. Издержки реальны: оба пути кода должны работать и тестироваться, флаги добавляют условную сложность, а забытые флаги превращаются в технический долг. Снижайте это соглашением об именовании, владельцем и сроком жизни для каждого флага, а также задачей на очистку, которая заводится в тот день, когда флаг доходит до ста процентов.
S3Миграция необратима. Как всё равно сделать релиз безопасным?
Примите, что откат — это откат кода, а не данных, и проектируйте так, чтобы этого всегда хватало. Разбейте изменение так, чтобы рискованная разрушающая часть шла последней и отдельно: сначала добавляющая миграция, которую старый код игнорирует, затем новый код, который её использует, и только через релиз-два — разрушающий шаг, когда метрики покажут, что старую структуру никто не читает. Перед этим финальным шагом сделайте и проверьте свежий бэкап или убедитесь, что point-in-time recovery его покрывает, и прогоните миграцию на копии размером с продакшен, чтобы измерить длительность и блокировки. Большие перезаписи данных запускайте идемпотентными фоновыми задачами пачками, чтобы их можно было ставить на паузу и возобновлять. Если что-то всё же пойдёт не так, план — чинить вперёд новой миграцией, а бэкап нужен на случай катастрофы, а не для рутинного отката.
Глоссарий
- Ref
- Именованный указатель на коммит, например ветка или тег. Перемещение ветки — это просто перезапись её ref.
- HEAD
- Ref того, что у вас сейчас выбрано (checkout); обычно указывает на ветку, а она — на коммит.
- Reflog
- Локальный журнал всех положений HEAD и веток; с его помощью восстанавливают коммиты после reset или rebase.
- Fast-forward
- Merge, при котором целевая ветка просто сдвигается вперёд на новый коммит, потому что история не разошлась.
- Squash merge
- Объединение всех коммитов pull request'а в один коммит в main — история получается короткой и читаемой.
- Merge queue
- Система, которая перед merge тестирует каждый pull request поверх свежего main и изменений, стоящих в очереди.
- OIDC-токен
- Короткоживущий подписанный токен идентичности, который CI выдаёт джобу, а облако обменивает на временные учётные данные.
- Артефакт
- Результат сборки пайплайна, например образ контейнера или архив, который без изменений деплоится во все окружения.
- Feature flag
- Переключатель во время работы, который включает или выключает ветки кода без деплоя, отделяя релиз от деплоя.
- Version skew
- Старая и новая версии кода или клиент и сервер работают одновременно и должны уживаться друг с другом.
Что изучить дальше
Есть идея продукта?Давайте создадим его.
Расскажите о своей идее, продукте или команде. Обычно отвечаю в течение одного рабочего дня.