CI (Continuous Integration)
CI (Continuous Integration) — практика автоматической проверки изменений в репозитории. После push, Pull Request или Merge Request система CI может установить зависимости, запустить линтеры и тесты, проверить типы, собрать приложение и сохранить результаты.
CI делает проверку изменений воспроизводимой: одинаковые команды выполняются для каждого запуска на подготовленном runner.
commit → push → pipeline → lint → tests → build → artifact
└─ ошибка → исправление → новый запускСодержание
- Концепции CI/CD
- Структура pipeline
- GitHub Actions
- GitLab CI/CD
- Автоматический запуск тестов и линтеров
- Кеширование зависимостей
- Матрицы сборок
- Артефакты сборки
- Секреты и переменные
- Практические рекомендации
- Общий пример
- Краткая шпаргалка
Концепции CI/CD
CI
Continuous Integration, или непрерывная интеграция, означает регулярное объединение небольших изменений в общий репозиторий с автоматической проверкой.
Типичный CI-процесс:
- разработчик создаёт коммит;
- отправляет изменения в удалённый репозиторий;
- CI запускается по событию
pushили Pull/Merge Request; - runner получает исходный код и устанавливает зависимости;
- запускаются линтеры, тесты и сборка;
- результат отображается в интерфейсе репозитория.
CI не гарантирует отсутствие всех ошибок. Он проверяет только те условия, которые описаны в workflow или pipeline.
Continuous Delivery и Continuous Deployment
Continuous Delivery — практика, при которой проект постоянно находится в состоянии, пригодном для выпуска. Публикация обычно требует ручного подтверждения.
Continuous Deployment — автоматическая публикация каждого изменения, прошедшего все проверки.
CI: код → тесты → линтеры → сборка
Continuous Delivery: сборка → готовый релиз → ручное подтверждение
Continuous Deployment: сборка → публикация → автоматическое развёртывание| Практика | Что автоматизируется | Подтверждение |
|---|---|---|
| CI | Проверка изменений | Обычно не требуется |
| Continuous Delivery | Подготовка версии к выпуску | Часто требуется перед релизом |
| Continuous Deployment | Выпуск и развёртывание | Может не требоваться |
Pipeline, workflow, job и step
Термины зависят от системы, но обычно используются следующие понятия:
- pipeline/workflow — весь автоматизированный процесс;
- stage — логический этап, например
testилиbuild; - job — отдельное задание, выполняемое на runner;
- step — одна команда или действие внутри job;
- runner — машина или контейнер, на котором выполняется job;
- artifact — файл или набор файлов, сохранённый после job;
- cache — временно сохранённые данные для ускорения повторных запусков.
Структура pipeline
Обычно pipeline делят на этапы:
lint → typecheck → test → build → package → deployНе все проекты требуют каждого этапа. Библиотеке может быть достаточно линтеров, тестов и публикации пакета, а веб-приложению — ещё и production-сборки.
Pull Request или Merge Request
При создании запроса на слияние CI обычно должен:
- проверить форматирование и стиль кода;
- запустить быстрые unit-тесты;
- проверить типы;
- собрать проект;
- при необходимости выполнить интеграционные тесты.
Основная ветка
После слияния в main или master можно дополнительно запускать полный набор тестов, публикацию пакета или образа, создание релизного артефакта и развёртывание в тестовое окружение.
Независимые проверки лучше запускать параллельно. Дорогие проверки можно вынести в отдельные jobs или запускать только для Pull/Merge Request и основной ветки.
GitHub Actions
GitHub Actions хранит workflow в YAML-файлах каталога .github/workflows/.
Минимальный пример:
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Получить исходный код
uses: actions/checkout@v4
- name: Установить Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: pip
- name: Установить зависимости
run: pip install -r requirements.txt
- name: Запустить линтер
run: ruff check .
- name: Запустить тесты
run: pytestname
Задаёт название workflow:
name: Python CIon
Определяет события, запускающие workflow:
on:
push:
pull_request:Ограничение по веткам:
on:
push:
branches: [main, develop]
pull_request:
branches: [main]Запуск вручную:
on:
workflow_dispatch:Запуск по расписанию:
on:
schedule:
- cron: '30 2 * * 1'Время в расписании указывается в UTC.
jobs и runs-on
Раздел jobs содержит задания. Независимые jobs могут выполняться параллельно:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run lint
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm testruns-on задаёт runner:
runs-on: ubuntu-latestТакже могут использоваться windows-latest и macos-latest, если проект нужно проверять в нескольких ОС.
steps, uses и run
run выполняет команду оболочки:
- name: Запустить тесты
run: npm testМногострочная команда записывается через |:
- name: Сборка
run: |
npm ci
npm run build
npm run packageuses подключает готовое action:
- uses: actions/checkout@v4Версию action следует указывать явно и регулярно проверять обновления.
Зависимости между jobs
needs запускает job только после успешного завершения другого job:
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: npm test
build:
needs: test
runs-on: ubuntu-latest
steps:
- run: npm run buildНесколько зависимостей:
build:
needs: [lint, test]
runs-on: ubuntu-latest
steps:
- run: npm run buildПеременные и условия
Переменные можно объявлять на уровне workflow, job или шага:
env:
NODE_ENV: test
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: echo "Режим: $NODE_ENV"Выражения GitHub Actions записываются в ${{ }}:
- run: echo "Ветка: ${{ github.ref_name }}"Условие выполнения:
- name: Опубликовать результат
if: github.ref == 'refs/heads/main'
run: ./publish.shДля сохранения логов независимо от результата используется always():
- name: Сохранить логи
if: always()
uses: actions/upload-artifact@v4
with:
name: logs
path: logs/GitLab CI/CD
GitLab CI/CD обычно описывается в файле .gitlab-ci.yml в корне репозитория.
image: python:3.12
stages:
- lint
- test
- build
lint:
stage: lint
script:
- pip install ruff
- ruff check .
test:
stage: test
script:
- pip install -r requirements.txt
- pip install pytest
- pytest
build:
stage: build
script:
- pip install build
- python -m build
artifacts:
paths:
- dist/stages
stages задаёт порядок этапов:
stages:
- lint
- test
- buildJobs одного stage могут выполняться параллельно. Следующий stage обычно начинается после успешного завершения предыдущего.
Job и script
Job описывается именованным блоком:
unit_tests:
stage: test
script:
- pip install -r requirements.txt
- pytestscript содержит команды, выполняемые на runner.
image
Для Docker executor можно указать базовый образ:
image: node:22Для отдельного job:
test:
image: node:22-alpine
script:
- npm ci
- npm testbefore_script и after_script
before_script содержит общие подготовительные команды:
before_script:
- npm ciafter_script подходит для диагностических действий:
after_script:
- echo "Job завершён"rules
rules управляет условиями запуска:
build:
script:
- npm run build
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'needs
needs позволяет запускать job сразу после нужного задания, не дожидаясь всех jobs предыдущего stage:
build:
stage: build
needs: [test]
script:
- npm run buildАвтоматический запуск тестов и линтеров
Назначение проверок
- formatter проверяет или исправляет форматирование;
- linter ищет потенциальные ошибки и нарушения стиля;
- type checker проверяет типы;
- unit-тесты проверяют отдельные функции или модули;
- integration-тесты проверяют взаимодействие компонентов;
- end-to-end-тесты имитируют действия пользователя.
Часто проверки выполняют в таком порядке:
format/lint → typecheck → unit tests → integration tests → buildNode.js: GitHub Actions
name: Node CI
on:
push:
pull_request:
jobs:
check:
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
- run: npm run format:check
- run: npm run typecheck
- run: npm test -- --ci
- run: npm run buildnpm ci предназначена для чистой установки по lock-файлу. В CI она обычно предпочтительнее npm install.
Python: GitLab CI
image: python:3.12
stages:
- check
- test
lint:
stage: check
script:
- pip install ruff
- ruff check .
- ruff format --check .
test:
stage: test
script:
- pip install -r requirements.txt
- pip install pytest
- pytest -qОтчёт о тестах
pytest может создавать JUnit XML:
pytest --junitxml=reports/junit.xmlФайл можно сохранить как артефакт:
- name: Запустить тесты
run: pytest --junitxml=reports/junit.xml
- name: Загрузить отчёт
if: always()
uses: actions/upload-artifact@v4
with:
name: test-report
path: reports/junit.xmlЕсли тестовая команда завершилась с ненулевым кодом, job должен завершиться ошибкой. Не следует скрывать ошибку без явной причины.
Кеширование зависимостей
Кеш сохраняет данные между запусками и уменьшает время установки зависимостей. Job должен работать и при пустом кеше: cache — это оптимизация, а не источник истины.
Что кешируют
- каталог npm-кеша;
- каталог pip-кеша;
- кеш Gradle и Maven;
- кеш Cargo;
- промежуточные результаты сборки, если это безопасно.
Обычно не стоит без необходимости кешировать готовый node_modules или виртуальное окружение Python: такие каталоги могут быть большими и зависеть от ОС или версии runtime.
GitHub Actions: npm
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
cache-dependency-path: package-lock.json
- run: npm ciGitHub Actions: Python
- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: pip
cache-dependency-path: requirements.txt
- run: pip install -r requirements.txtЯвный cache action
- name: Кешировать pip
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: pip-${{ runner.os }}-${{ hashFiles('requirements.txt') }}
restore-keys: |
pip-${{ runner.os }}-В ключ обычно включают ОС, версию runtime и хеш lock-файла:
<инструмент>-<ОС>-<версия>-<хеш зависимостей>GitLab CI
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
cache:
key:
files:
- requirements.txt
paths:
- .cache/pipДля Node.js:
cache:
key:
files:
- package-lock.json
paths:
- .npm/
before_script:
- npm ci --cache .npm --prefer-offlineCache и artifacts
Cache предназначен для ускорения следующих запусков и может быть удалён или заменён.
Artifact — результат конкретного job: собранное приложение, отчёт, пакет или архив. Он передаётся другим jobs или скачивается пользователем.
Матрицы сборок
Матрица позволяет запустить одно и то же задание для нескольких комбинаций параметров: версий языка, операционных систем, баз данных или режимов проекта.
Матрица полезна для проверки совместимости, но увеличивает количество запусков. Включайте только действительно поддерживаемые комбинации.
GitHub Actions
name: Compatibility
on:
push:
pull_request:
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
python-version: ['3.11', '3.12']
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- run: pip install -r requirements.txt
- run: pytestЗдесь создаются четыре задания:
Ubuntu + Python 3.11
Ubuntu + Python 3.12
Windows + Python 3.11
Windows + Python 3.12fail-fast: false позволяет дождаться результатов остальных комбинаций, даже если одна завершилась ошибкой.
include и exclude
strategy:
matrix:
node-version: [20, 22]
os: [ubuntu-latest, windows-latest]
include:
- node-version: 22
experimental: true
exclude:
- node-version: 20
os: windows-latestinclude добавляет или расширяет комбинации, а exclude исключает ненужные варианты.
GitLab CI
В GitLab аналогичная задача описывается через parallel:matrix:
test:
image: python:$PYTHON_VERSION
parallel:
matrix:
- PYTHON_VERSION: ['3.11', '3.12']
DATABASE: ['sqlite', 'postgres']
script:
- pip install -r requirements.txt
- pytestПри использовании матриц важно, чтобы переменные действительно использовались в job. Иначе pipeline создаст несколько одинаковых заданий.
Артефакты сборки
Артефакт — файл или каталог, который job сохраняет после выполнения. Это может быть:
- каталог
dist/после frontend-сборки; - wheel или source archive Python-пакета;
- бинарный файл;
- Docker image archive;
- отчёт покрытия;
- логи падения тестов;
- документация.
GitHub Actions: загрузка
- name: Собрать приложение
run: npm run build
- name: Загрузить артефакт
uses: actions/upload-artifact@v4
with:
name: web-dist
path: dist/
retention-days: 7Артефакты диагностики можно загрузить даже при ошибке:
- name: Загрузить логи
if: always()
uses: actions/upload-artifact@v4
with:
name: test-logs
path: logs/GitHub Actions: использование
Job выполняется на отдельном runner, поэтому файлы из предыдущего job не появляются автоматически. Для передачи используйте artifact:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run build
- uses: actions/upload-artifact@v4
with:
name: web-dist
path: dist/
package:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
name: web-dist
path: dist/
- run: tar -czf web-dist.tar.gz dist/GitLab CI
build:
stage: build
script:
- npm ci
- npm run build
artifacts:
name: "web-dist-$CI_COMMIT_SHORT_SHA"
paths:
- dist/
expire_in: 7 daysПередача артефакта в следующий job:
package:
stage: package
needs:
- job: build
artifacts: true
script:
- tar -czf web-dist.tar.gz dist/Артефакты и публикация
Сохранить артефакт в CI и опубликовать его пользователям — разные операции. Артефакт может быть временным результатом pipeline, а публикация отправляет пакет в registry, release или объектное хранилище.
Не следует помещать в артефакты пароли, токены, приватные ключи, .env с секретами и диагностические дампы с конфиденциальными данными.
Секреты и переменные
Обычные настройки можно хранить в переменных pipeline:
env:
APP_ENV: testСекреты хранятся в GitHub Secrets, GitLab CI/CD Variables или внешнем secret manager.
GitHub Actions:
- name: Опубликовать пакет
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
run: ./publish.shСекреты нельзя выводить в лог:
# Плохо
- run: echo "Token: ${{ secrets.TOKEN }}"Даже если CI маскирует значения, не следует передавать секреты через команды, артефакты или диагностические файлы без необходимости.
Практические рекомендации
Фиксируйте версии
Используйте lock-файлы и фиксируйте версии runtime:
python-version: '3.12'
node-version: 22Для actions и внешних компонентов выбирайте контролируемые версии. Обновления выполняйте отдельно и проверяйте в review.
Делайте job воспроизводимыми
Job не должен зависеть от состояния предыдущего запуска. Он должен сам получить исходный код и установить зависимости либо получить явно переданный артефакт.
Проверяйте проект до сборки
Lint, typecheck и тесты должны завершаться до публикации артефакта. Не публикуйте результат, если обязательные проверки не пройдены.
Разделяйте быстрые и долгие проверки
Быстрые проверки дают обратную связь раньше. Долгие e2e-тесты и тесты совместимости можно запускать параллельно или отдельным job.
Сохраняйте диагностику
Отчёты и логи полезно загружать с условием always():
- name: Сохранить логи
if: always()
uses: actions/upload-artifact@v4
with:
name: logs
path: logs/Ограничивайте права workflow
GitHub Actions может явно ограничить разрешения:
permissions:
contents: readПрава расширяйте только для операций, которым они нужны.
Проверяйте изменения CI
Файлы CI являются исполняемой частью проекта. В review проверяйте внешние actions, команды публикации, права, условия запуска и работу с секретами.
Общий пример
Ниже workflow GitHub Actions для Node.js-проекта. Он запускает линтеры и тесты, использует кеш npm, проверяет проект на двух версиях Node.js, собирает приложение и сохраняет каталог dist как артефакт.
Файл .github/workflows/ci.yml:
name: Node CI
on:
push:
branches: [main, develop]
pull_request:
permissions:
contents: read
jobs:
check:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node-version: [20, 22]
steps:
- name: Получить исходный код
uses: actions/checkout@v4
- name: Установить Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: npm
- name: Установить зависимости
run: npm ci
- name: Проверить стиль
run: npm run lint
- name: Проверить типы
run: npm run typecheck
- name: Запустить тесты
run: npm test -- --ci
- name: Собрать приложение
run: npm run build
- name: Загрузить артефакт
if: matrix.node-version == 22
uses: actions/upload-artifact@v4
with:
name: web-dist
path: dist/
retention-days: 7В этом workflow:
- запуск происходит при
pushвmainилиdevelopи при Pull Request; - workflow имеет только право читать содержимое репозитория;
- проверки выполняются для Node.js 20 и 22;
fail-fast: falseпозволяет увидеть результаты обеих версий;- зависимости кешируются через
setup-node; - линтер, проверка типов, тесты и сборка выполняются последовательно;
- артефакт загружается только из варианта Node.js 22, чтобы не создавать дубликаты.
Краткая шпаргалка
GitHub Actions
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm teststrategy:
matrix:
node-version: [20, 22]- uses: actions/upload-artifact@v4
with:
name: build
path: dist/GitLab CI/CD
stages:
- test
- build
test:
stage: test
script:
- npm ci
- npm test
build:
stage: build
script:
- npm run build
artifacts:
paths:
- dist/Основные понятия
| Понятие | Назначение |
|---|---|
| Workflow/pipeline | Весь автоматизированный процесс |
| Job | Отдельное задание на runner |
| Step | Команда или действие внутри job |
| Runner | Среда выполнения job |
| Cache | Ускорение повторных запусков |
| Artifact | Результат, передаваемый между jobs или сохраняемый после запуска |
| Matrix | Запуск job для набора комбинаций |
| Secret | Защищённое значение, например токен |
Чек-лист CI
Перед использованием pipeline проверьте:
- workflow запускается на
pushи Pull/Merge Request; - зависимости устанавливаются по lock-файлу;
- запускаются линтеры и форматирование;
- запускаются unit-тесты;
- проверяются типы, если они используются;
- проект собирается в CI;
- кеш использует ключ, зависящий от lock-файла;
- артефакты сохраняются отдельно от кеша;
- логи и отчёты доступны при ошибке;
- секреты не выводятся в логах;
- права workflow ограничены необходимым минимумом;
- версии runner, runtime и actions контролируются;
- независимые jobs выполняются параллельно, если это сокращает время pipeline.