CI/CD и автоматизация
Обзор
Автоматизируй ворота качества так, чтобы ни одно изменение не доехало до продакшна, не пройдя тесты, линтер, проверку типов и сборку. CI/CD — это механизм принуждения для всех остальных скиллов: он ловит то, что упускают люди и агенты, и делает это одинаково на каждом изменении.
Сдвиг влево: лови проблемы как можно раньше в конвейере. Баг, пойманный линтером, стоит минуты; тот же баг, пойманный в продакшне, стоит часы. Двигай проверки вверх по потоку: статический анализ до тестов, тесты до стенда, стенд до продакшна.
Быстрее — значит безопаснее: меньшие порции и более частые релизы снижают риск, а не повышают его. Выкатку с 3 изменениями отлаживать проще, чем с 30. Частые релизы формируют доверие к самому процессу релиза.
Когда применять
- Настраиваешь конвейер CI нового проекта
- Добавляешь или меняешь автоматические проверки
- Настраиваешь конвейеры выкатки
- Когда изменение должно запускать автоматическую проверку
- Разбираешься с падениями CI
Конвейер ворот качества
Каждое изменение проходит эти ворота до вливания:
Открыт пулл-реквест
│
▼
┌──────────────────────┐
│ ЛИНТЕР │ eslint, prettier
│ ↓ прошло │
│ ПРОВЕРКА ТИПОВ │ tsc --noEmit
│ ↓ прошло │
│ ЮНИТ-ТЕСТЫ │ jest/vitest
│ ↓ прошло │
│ СБОРКА │ npm run build
│ ↓ прошло │
│ ИНТЕГРАЦИОННЫЕ │ тесты API/БД
│ ↓ прошло │
│ СКВОЗНЫЕ (опция) │ Playwright/Cypress
│ ↓ прошло │
│ АУДИТ БЕЗОПАСНОСТИ │ npm audit
│ ↓ прошло │
│ РАЗМЕР БАНДЛА │ проверка bundlesize
└──────────────────────┘
│
▼
Готово к ревью
Ни одни ворота нельзя пропустить. Если падает линтер — чини код, а не отключай правило. Если падает тест — чини код, а не пропускай тест.
Настройка GitHub Actions
Базовый конвейер CI
# .github/workflows/ci.yml
name: CI
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
- name: Type check
run: npx tsc --noEmit
- name: Test
run: npm test -- --coverage
- name: Build
run: npm run build
- name: Security audit
run: npm audit --audit-level=high
С интеграционными тестами на базе данных
integration:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_DB: testdb
POSTGRES_USER: ci_user
POSTGRES_PASSWORD: ${{ secrets.CI_DB_PASSWORD }}
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- run: npm ci
- name: Run migrations
run: npx prisma migrate deploy
env:
DATABASE_URL: postgresql://ci_user:${{ secrets.CI_DB_PASSWORD }}@localhost:5432/testdb
- name: Integration tests
run: npm run test:integration
env:
DATABASE_URL: postgresql://ci_user:${{ secrets.CI_DB_PASSWORD }}@localhost:5432/testdb
Замечание: даже для тестовых баз, живущих только в CI, храни учётные данные в GitHub Secrets, а не зашивай значения. Это формирует правильную привычку и предотвращает случайное переиспользование тестовых учётных данных в других местах.
Сквозные тесты
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- run: npm ci
- name: Install Playwright
run: npx playwright install --with-deps chromium
- name: Build
run: npm run build
- name: Run E2E tests
run: npx playwright test
- uses: actions/upload-artifact@v4
if: failure()
with:
name: playwright-report
path: playwright-report/
Возврат падений CI агентам
Сила CI в связке с AI-агентами — это петля обратной связи. Когда CI падает:
CI упал
│
▼
Скопировать вывод падения
│
▼
Отдать агенту:
«Конвейер CI упал с такой ошибкой:
[вставить конкретную ошибку]
Устрани проблему и проверь локально, прежде чем пушить снова.»
│
▼
Агент чинит → пушит → CI прогоняется снова
Ключевые паттерны:
Падение линтера → Агент запускает `npm run lint --fix` и коммитит
Ошибка типов → Агент читает место ошибки и правит тип
Падение теста → Агент следует скиллу debugging-and-error-recovery
Ошибка сборки → Агент проверяет конфигурацию и зависимости
Стратегии выкатки
Предварительные выкатки
Каждый пулл-реквест получает предварительную выкатку для ручной проверки:
# Предварительная выкатка на PR (Vercel/Netlify и подобные)
deploy-preview:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
- name: Deploy preview
run: npx vercel --token=${{ secrets.VERCEL_TOKEN }}
Фича-флаги
Фича-флаги отделяют выкатку от релиза. Выкатывай незаконченную или рискованную функциональность под флагами, чтобы иметь возможность:
- Выкатить код, не включая его. Влить в main рано, включить, когда готов.
- Откатиться без повторной выкатки. Выключить флаг вместо отката кода.
- Проверить на канарейке. Включить для 1% пользователей, потом 10%, потом 100%.
- Проводить A/B-тесты. Сравнить поведение с функциональностью и без неё.
// Простой паттерн фича-флага
if (featureFlags.isEnabled('new-checkout-flow', { userId })) {
return renderNewCheckout();
}
return renderLegacyCheckout();
Жизненный цикл флага: создать → включить для тестирования → канарейка → полная выкатка → удалить флаг и мёртвый код. Флаги, живущие вечно, становятся техническим долгом: назначай дату уборки при создании.
Поэтапные выкатки
PR влит в main
│
▼
Выкатка на стенд (автоматически)
│ Ручная проверка
▼
Выкатка в продакшн (по ручному запуску или автоматически после стенда)
│
▼
Наблюдение за ошибками (окно 15 минут)
│
├── Обнаружены ошибки → откат
└── Чисто → готово
План отката
Любая выкатка должна быть обратимой:
# Рабочий процесс ручного отката
name: Rollback
on:
workflow_dispatch:
inputs:
version:
description: 'Version to rollback to'
required: true
jobs:
rollback:
runs-on: ubuntu-latest
steps:
- name: Rollback deployment
run: |
# Выкатить указанную предыдущую версию
npx vercel rollback ${{ inputs.version }}
Управление окружениями
.env.example → Коммитится (шаблон для разработчиков)
.env → НЕ коммитится (локальная разработка)
.env.test → Коммитится (тестовое окружение, без настоящих секретов)
Секреты CI → Хранятся в GitHub Secrets / хранилище секретов
Продакшн-секреты → Хранятся в платформе выкатки / хранилище секретов
В CI никогда не должно быть продакшн-секретов. Используй отдельные секреты для тестирования в CI.
Автоматизация за пределами CI
Dependabot / Renovate
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: npm
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 5
Роль дежурного по сборке
Назначь ответственного за то, чтобы CI оставался зелёным. Когда сборка ломается, задача дежурного — починить или откатить, а не задача того, чьё изменение сломало. Это не даёт сломанным сборкам накапливаться, пока все считают, что починит кто-то другой.
Проверки на пулл-реквестах
- Обязательные ревью: минимум 1 одобрение до вливания
- Обязательные статусные проверки: CI должен пройти до вливания
- Защита ветки: никаких force-push в main
- Автослияние: если все проверки прошли и есть одобрение, вливать автоматически
Оптимизация CI
Когда конвейер превышает 10 минут, применяй эти стратегии в порядке отдачи:
Медленный конвейер CI?
├── Кешируй зависимости
│ └── Используй actions/cache или опцию cache в setup-node для node_modules
├── Запускай задания параллельно
│ └── Раздели линтер, проверку типов, тесты и сборку на отдельные параллельные задания
├── Гоняй только то, что изменилось
│ └── Используй фильтры по путям, чтобы пропускать несвязанные задания (например, сквозные тесты на PR только с документацией)
├── Используй матричные сборки
│ └── Разбей набор тестов на шарды по нескольким раннерам
├── Оптимизируй набор тестов
│ └── Убери медленные тесты с критичного пути, гоняй их по расписанию
└── Возьми раннеры помощнее
└── Более крупные раннеры GitHub или свои для сборок, тяжёлых по CPU
Пример: кеширование и параллельность
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '22', cache: 'npm' }
- run: npm ci
- run: npm run lint
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '22', cache: 'npm' }
- run: npm ci
- run: npx tsc --noEmit
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '22', cache: 'npm' }
- run: npm ci
- run: npm test -- --coverage
Типовые самооправдания
| Самооправдание | Как на самом деле |
|---|---|
| «CI слишком медленный» | Оптимизируй конвейер (см. «Оптимизация CI»), а не пропускай его. Пятиминутный конвейер предотвращает часы отладки. |
| «Изменение пустяковое, CI можно пропустить» | Пустяковые изменения ломают сборки. Для пустяковых изменений CI и так быстрый. |
| «Тест мигающий, просто перезапусти» | Мигающие тесты маскируют настоящие баги и тратят время всех. Устрани мигание. |
| «Добавим CI потом» | Проекты без CI накапливают сломанные состояния. Настрой в первый же день. |
| «Ручного тестирования достаточно» | Ручное тестирование не масштабируется и невоспроизводимо. Автоматизируй то, что можно. |
Тревожные признаки
- В проекте нет конвейера CI
- Падения CI игнорируются или заглушаются
- Тесты отключены в CI, чтобы конвейер прошёл
- Выкатки в продакшн без проверки на стенде
- Нет механизма отката
- Секреты хранятся в коде или конфигах CI (а не в менеджере секретов)
- Долгое время прогона CI без всяких попыток оптимизации
Проверка
После настройки или изменения CI:
- Все ворота качества на месте (линтер, типы, тесты, сборка, аудит)
- Конвейер запускается на каждом пулл-реквесте и пуше в main
- Падения блокируют вливание (защита ветки настроена)
- Результаты CI возвращаются в цикл разработки
- Секреты хранятся в менеджере секретов, а не в коде
- У выкатки есть механизм отката
- Конвейер укладывается в 10 минут для набора тестов