Антон Ларичев

Введение
CI/CD пайплайн — это автоматизированная цепочка шагов, которая проверяет, собирает и доставляет код в продакшн без ручного вмешательства. Вместо того чтобы вручную гонять тесты и заливать билд на сервер, разработчик просто пушит код, а дальше всё происходит само: линтер, тесты, сборка образа, деплой.
В этой статье разберём, как построить CI/CD пайплайн с нуля на примере GitHub Actions — самого доступного инструмента, который не требует отдельной инфраструктуры. Логика переносится на GitLab CI, Jenkins или CircleCI почти без изменений, меняется только синтаксис конфигурации.
Шаг 1: Структура репозитория и первый workflow
GitHub Actions ищет конфигурации в папке .github/workflows. Начнём с простейшего файла, который запускается при пуше в ветку main.
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Клонируем репозиторий
uses: actions/checkout@v4
- name: Устанавливаем Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Устанавливаем зависимости
run: npm ci
Триггер on описывает, когда запускать пайплайн. Важно указывать и push, и pull_request — так проверка кода идёт ещё до слияния в основную ветку, а не после.
Шаг 2: Добавляем линтер и тесты
Пайплайн без тестов — это просто автоматическая сборка, а не полноценный CI. Добавим шаги проверки.
- name: Линтер
run: npm run lint
- name: Юнит-тесты
run: npm run test -- --coverage
- name: Сборка проекта
run: npm run build
Если любой из шагов завершится с ошибкой, GitHub Actions остановит выполнение и пометит коммит крестиком. Это тот самый сигнал, который не даёт сломанному коду попасть в main.
Для больших проектов стоит разделить джобы, чтобы линтер и тесты выполнялись параллельно, а не последовательно:
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 run test
Параллельные джобы сокращают время прогона пайплайна почти вдвое на средних проектах.
Шаг 3: Сборка и публикация Docker-образа
Когда тесты зелёные, можно собирать образ и пушить его в registry. Добавим джобу, которая зависит от успешного прохождения тестов через needs.
docker:
needs: [lint, test]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Логинимся в Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Собираем и пушим образ
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: myorg/myapp:${{ github.sha }}
Тег с github.sha даёт уникальную версию образа под каждый коммит — это упрощает откат на предыдущую версию, если что-то пойдёт не так после деплоя.
Шаг 4: Автоматический деплой
Финальный шаг — доставка образа на сервер. Самый простой вариант для небольшого проекта — подключиться по SSH и перезапустить контейнер.
deploy:
needs: docker
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Деплой на сервер по SSH
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SERVER_SSH_KEY }}
script: |
docker pull myorg/myapp:${{ github.sha }}
docker stop myapp || true
docker rm myapp || true
docker run -d --name myapp -p 3000:3000 myorg/myapp:${{ github.sha }}
Условие if: github.ref == 'refs/heads/main' защищает от случайного деплоя из feature-веток. Для продакшн-сценариев вместо прямого SSH лучше использовать Kubernetes, но для старта такого подхода достаточно.
Шаг 5: Секреты и переменные окружения
Никогда не храните пароли, токены и ключи прямо в yaml-файле. GitHub Actions хранит их в разделе Settings → Secrets and variables → Actions, а в конфигурации они доступны через secrets.ИМЯ.
Для переменных, которые не являются секретными (например, окружение staging или production), используйте vars вместо secrets — это упрощает чтение логов при отладке.
Частые ошибки
Отсутствие кеширования зависимостей. Без cache: 'npm' в setup-node каждый запуск заново скачивает все пакеты, что добавляет минуты к каждому пайплайну.
Одна гигантская джоба вместо нескольких параллельных. Это увеличивает время обратной связи и усложняет диагностику — непонятно, что именно упало: линтер, тесты или сборка.
Деплой без проверки тестов. Джоба deploy обязательно должна зависеть через needs от джобы с тестами, иначе сломанный код может уехать в прод при зелёной сборке Docker-образа.
Хранение секретов в коде или в yaml-файле пайплайна. Даже в приватном репозитории это риск — токены попадают в историю git и остаются там навсегда.
Игнорирование уведомлений о падении пайплайна. Если команда привыкла игнорировать красные билды, CI/CD теряет смысл — стоит настроить уведомления в Slack или Telegram через дополнительный шаг.
Отсутствие стратегии отката. Если у образов нет версионирования по тегам, откат на предыдущую рабочую версию превращается в ручной поиск по истории сборок.
Заключение
CI/CD пайплайн с нуля строится по одной и той же схеме: проверка кода, тесты, сборка, деплой. Начать стоит с малого — линтера и тестов на каждый пуш, а затем постепенно добавлять сборку Docker-образа и автоматический деплой.
Главное правило — каждый шаг должен быть обратимым и прозрачным: понятные логи, версионированные образы и секреты вне кода. Тогда пайплайн не просто ускоряет разработку, а реально снижает число ошибок, которые долетают до продакшена.





Комментарии
0