CI/CD для новичков: деплой «по накатанной» на GitHub Actions и проверка артефактов
Создадим понятный пайплайн: тесты, линтинг, сборка артефакта и деплой с проверкой, что версия действительно та. Рассмотрим частые проблемы новичков и как их избежать.
Содержание
CI/CD для новичков: деплой «по накатанной» на GitHub Actions и проверка артефактов
CI/CD — это не “автоматизация ради автоматизации”, а способ сделать доставку изменений предсказуемой. Для новичка ключевая проблема обычно не в том, что “сложно написать YAML”, а в том, что непонятно, что именно должно происходить в пайплайне и как доказать, что деплой выполнен из правильной версии.
В этой статье мы построим понятный CI/CD-пайплайн на GitHub Actions:
- запуск тестов и линтинга,
- сборка артефакта,
- деплой,
- проверка, что версия в деплое соответствует версии артефакта, а не “чего-то похожего”.
Материал будет полезен, даже если вы не планируете немедленно деплоить в прод: структура и принципы остаются теми же. В конце я мягко упомяну курс CI-CD от теории до практики как один из способов углубиться, если хочется системно разобрать детали.
Как мыслить CI/CD: от коммитов к артефактам
Большинство “кривых” пайплайнов новичков имеют одну общую черту: деплой делается не из артефакта, а из исходников “как получится”. Например:
- сначала собирают на одной машине,
- потом деплоят “просто по тегу” или “пере-собирают на сервере”,
- в итоге непонятно, что именно было смонтировано на сервере и совпадает ли оно с тем, что прошло тесты.
Правильная модель (упрощённо):
- CI: тесты → линтинг → сборка → создание артефакта.
- Проверки: артефакт имеет уникальную идентичность (версию/хеш).
- CD: деплой использует тот же артефакт и тот же идентификатор.
Это можно сделать даже без сложной инфраструктуры — достаточно научиться:
- вычислять и сохранять версию сборки (например, по
GITHUB_SHA), - сохранять метаданные вместе с артефактом,
- в деплое проверять, что вы ставите артефакт с ожидаемым SHA/версией.
Подготовка: пример проекта и что будет деплоиться
Для конкретики предположим, что у нас есть простой Node.js-проект, который собирается в папку dist/, а деплой — это копирование сборки на “приёмник” (например, тестовый сервер или статический хостинг). Если вы работаете с другим языком, логика остаётся: тесты/линтинг → сборка → артефакт → деплой.
Пример структуры
.
├── package.json
├── .eslintrc.cjs
├── src/
└── dist/ (создаётся при сборке)
В package.json допустимы скрипты:
lint: линтингtest: тестыbuild: сборка
Пайплайн на GitHub Actions: базовая схема
На GitHub Actions пайплайн задаётся в .github/workflows/<name>.yml. Мы сделаем несколько шагов и (важно) разделим задачу на CI и CD. Это повышает прозрачность и помогает новичкам видеть границы ответственности.
Общая идея workflow
- Триггеры:
- на
pushвmain(для автоматических релизов в тест), - на
workflow_dispatch(для ручного запуска, полезно при отладке).
- на
- CI job:
- чек-аут кода,
- установка зависимостей,
- тесты,
- линтинг,
- сборка,
- генерация артефакта,
- вычисление метаданных версии,
- загрузка артефакта как artifact.
- CD job:
- скачивание artifact,
- проверка метаданных (что SHA совпадает с ожидаемым),
- деплой.
Важный принцип: версия сборки ≠ версия пакета
Чтобы корректно проверить “версию в деплое”, нужно определить, что именно считаем версией.
Практичный выбор для старта:
BUILD_SHA=GITHUB_SHA(хеш конкретного коммита),BUILD_NUMBER=GITHUB_RUN_NUMBER(номер запуска),- при желании — можно дополнить семантической версией из
package.json.
Для доказательства соответствия достаточно SHA: он уникален для кода.
Подводный камень: новички часто пытаются сравнивать package.json версия/тег, но забывают, что:
- версия может не обновляться,
- релиз может создаваться из не той ветки,
- теги могут “переезжать” (особенно в ручных сценариях).
Коммитный SHA надёжнее как идентификатор сборки.
Реализация: workflow с проверкой артефакта
Создадим файл .github/workflows/ci-cd.yml.
Пример workflow
name: CI/CD
on:
push:
branches: [ "main" ]
workflow_dispatch:
jobs:
ci:
runs-on: ubuntu-latest
outputs:
build_sha: ${{ steps.meta.outputs.build_sha }}
build_id: ${{ steps.meta.outputs.build_id }}
artifact_name: ${{ steps.meta.outputs.artifact_name }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
- name: Test
run: npm test
- name: Build
run: npm run build
- name: Create build metadata
id: meta
shell: bash
run: |
BUILD_SHA="${GITHUB_SHA}"
BUILD_NUMBER="${GITHUB_RUN_NUMBER}"
ARTIFACT_NAME="web-${BUILD_SHA}.tar.gz"
BUILD_ID="${BUILD_SHA}-${BUILD_NUMBER}"
echo "build_sha=${BUILD_SHA}" >> "$GITHUB_OUTPUT"
echo "build_id=${BUILD_ID}" >> "$GITHUB_OUTPUT"
echo "artifact_name=${ARTIFACT_NAME}" >> "$GITHUB_OUTPUT"
# пишем метаданные рядом с артефактом
cat > dist/build-metadata.json <<EOF
{
"build_sha": "${BUILD_SHA}",
"build_id": "${BUILD_ID}",
"git_ref": "${GITHUB_REF}",
"built_at": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
}
EOF
- name: Package artifact
shell: bash
run: |
ARTIFACT_NAME="web-${GITHUB_SHA}.tar.gz"
tar -czf "$ARTIFACT_NAME" -C dist .
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ steps.meta.outputs.artifact_name }}
path: web-${{ github.sha }}.tar.gz
if-no-files-found: error
cd:
needs: ci
runs-on: ubuntu-latest
env:
EXPECTED_BUILD_SHA: ${{ needs.ci.outputs.build_sha }}
EXPECTED_ARTIFACT: ${{ needs.ci.outputs.artifact_name }}
steps:
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: ${{ env.EXPECTED_ARTIFACT }}
path: ./artifact
- name: Verify artifact metadata (before deploy)
shell: bash
run: |
set -euo pipefail
cd artifact
tar -xzf ./*.tar.gz
# dist/build-metadata.json должен быть внутри
ACTUAL_SHA="$(jq -r '.build_sha' dist/build-metadata.json)"
echo "Expected build_sha: ${EXPECTED_BUILD_SHA}"
echo "Actual build_sha: ${ACTUAL_SHA}"
if [ "${ACTUAL_SHA}" != "${EXPECTED_BUILD_SHA}" ]; then
echo "ERROR: Build SHA mismatch. Refusing deploy."
exit 1
fi
echo "Artifact verification passed."
- name: Deploy (example: copy to server)
shell: bash
run: |
set -euo pipefail
# Пример: имитируем деплой
# В реальном проекте тут может быть scp/rsync, kubectl set image и т.д.
echo "Deploying build for ${EXPECTED_BUILD_SHA}..."
ls -la artifact/dist
# TODO: заменить на реальные шаги деплоя
# Дополнительно: можно записать build_sha на сервере,
# чтобы потом проверять, что именно установлено.
Что здесь важно
-
CI job:
- после сборки создаёт
dist/build-metadata.jsonсbuild_sha, - упаковывает
distв.tar.gz, - загружает архив в artifacts.
- после сборки создаёт
-
CD job:
- скачивает тот же artifact по имени,
- распаковывает,
- читает
dist/build-metadata.json, - сравнивает
actual build_shaсexpected build_sha, - если не совпало — деплой прерывается.
Это и есть “проверка, что версия действительно та”: мы связываем деплой с конкретным артефактом, который прошёл CI.
Где часто ошибаются новички
1) Путают “проверку” с “просто запуском деплоя”
Если вы деплоите прямо из исходников (например, git pull && npm run build на сервере), то “проверка” тестами в CI теряет смысл: сервер собрал своё, не CI-шное.
Решение: деплой должен использовать artifact, а не свежескачанный код.
2) Доверяют только тегам или версии из package.json
Тег может не соответствовать реальности, версия в package.json может не обновляться, а “последний” коммит на ветке может отличаться от того, что вы думали.
Решение: использовать идентификатор сборки на базе GITHUB_SHA (или вычисленного хеша артефакта).
3) Нет метаданных у артефакта
Даже если вы деплоите artifact, без метаданных вы не докажете соответствие. Проверять “какое-то содержимое” ненадёжно.
Решение: положить рядом с артефактом build-metadata.json и проверять поля.
4) Отсутствие fail-fast логики
Если проверка проходит после распаковки/деплоя или где-то “мягко” (например, || true), инциденты превращаются в расследования.
Решение: при несовпадении версии — немедленный exit 1 в CD job.
5) Забывают о jq
В примере выше используется jq. На ubuntu-latest он обычно доступен, но если у вас свой runtime/образ — добавьте установку.
Например:
sudo apt-get update
sudo apt-get install -y jq
Усиление: контроль целостности артефакта хешем
SHA коммита — отличный идентификатор, но можно добавить ещё и контроль целостности самого архива.
Идея:
- на CI считаем
sha256sumот.tar.gz, - сохраняем это значение в метаданных,
- на CD пересчитываем хеш архива и сверяем.
Это особенно полезно при сетевых проблемах, если artifact “переупаковывают”, или при усложнении цепочки.
Пример: добавить хеш в build-metadata.json можно так (в CI):
HASH="$(sha256sum "$ARTIFACT_NAME" | awk '{print $1}')"
cat > dist/build-metadata.json <<EOF
{
"build_sha": "${BUILD_SHA}",
"build_id": "${BUILD_ID}",
"artifact_sha256": "${HASH}",
"built_at": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
}
EOF
А на CD:
- вычисляете
actual_artifact_sha256от скачанного архива, - сравниваете с
dist/build-metadata.json.
Реальный деплой: как заменить “Deploy (example)” на рабочий сценарий
В примере мы имитировали деплой. На практике есть два базовых пути для новичка:
Вариант A: копирование статической сборки по SSH (scp/rsync)
Если вы деплоите статический сайт на сервер с папкой, то CD job может выглядеть так:
- name: Deploy via rsync
shell: bash
run: |
set -euo pipefail
ARTIFACT_DIR="artifact/dist"
# Замените на ваши значения
SSH_HOST="${{ secrets.SSH_HOST }}"
SSH_USER="${{ secrets.SSH_USER }}"
SSH_PORT="${{ secrets.SSH_PORT || 22 }}"
SSH_DEST="${{ secrets.SSH_DEST }}"
rsync -avz --delete -e "ssh -p ${SSH_PORT}" \
"${ARTIFACT_DIR}/" \
"${SSH_USER}@${SSH_HOST}:${SSH_DEST}/"
Вариант B: деплой в Kubernetes с фиксированным образом/артефактом
Если вы упаковываете в контейнер, то вместо “tar.gz” будет Docker image. Принцип тот же: деплой должен ссылаться на тот образ, который был собран и протестирован, а метаданные/labels должны позволять проверить соответствие.
Если вы ещё не дошли до контейнеров — не усложняйте. Начните с artifact.
Выбор артефакта и “где хранить сборку”
GitHub Actions artifacts — удобный способ для связки CI и CD внутри одного workflow. Но важно понимать ограничения:
- artifacts хранятся ограниченное время (по умолчанию недолго, можно настроить),
- это не универсальная “база релизов”,
- в больших системах обычно используют registry (Docker), S3-like хранилище, пакеты и т.п.
Для учебных целей и стартовых команд artifacts хватает: вы сохраняете “что деплоилось” в рамках конкретного прогона.
Если же вы планируете промышленные релизы, добавляйте внешний storage релизов и подписывайте артефакты.
Параллелизация и время выполнения: что можно улучшить
Новичкам часто кажется, что “чем больше шагов — тем медленнее”, но на деле правильная параллелизация уменьшает ожидание.
Например:
- линтинг и тесты можно делать параллельно,
- сборку запускать только после успешных линтинга/тестов.
Однако при использовании artifact и проверок лучше сохранять простоту на старте. Сложные матрицы (matrix) и параллели могут скрыть причинно-следственные связи.
Отладка пайплайна: как не терять время
Когда что-то ломается, обычно причина одна из трёх:
- Сломались тесты/линтинг — логично, надо чинить код.
- Не создаётся artifact или не совпадает имя — типичная ошибка с
name: ...иpath: .... - CD не находит метаданные — забыли положить
build-metadata.jsonв нужное место или распаковали не туда.
Практический совет: добавляйте временную диагностику в CD:
ls -R
find . -maxdepth 4 -type f
Потом удалите, когда всё стабилизируется.
Мини-чеклист: “деплой по накатанной” без сюрпризов
Перед тем как считать пайплайн “готовым”, проверьте:
- CI запускает тесты и линтинг перед сборкой.
- CD деплоит не исходники, а артефакт.
- Артефакт содержит метаданные сборки (
build_sha). - CD проверяет
build_shaдо начала деплоя. - При несовпадении версии CD падает с ошибкой (
exit 1). - Артефакт однозначно идентифицирован (например, имя содержит
GITHUB_SHA).
Этот набор даёт реальную воспроизводимость. И главное — новичку становится понятно, почему пайплайн доверять можно.
Вывод
GitHub Actions позволяет собрать CI/CD-пайплайн “по накатанной” даже без сложных платформ. Главное — мыслить не как “написать workflow”, а как “связать доказательство”: тесты и линтинг должны относиться к той же сборке, которую вы деплоите.
Самая полезная практика для новичка — сделать артефакт носителем идентичности (например, build_sha) и проверять эту идентичность в CD перед изменением окружения. Тогда “деплой прошёл” означает не “что-то обновилось”, а “обновилось то самое, что собрано и проверено”.
Если хотите дальше разобраться глубже (роль релизов, управление окружениями, правильная стратегия версий, шаблоны пайплайнов и типовые архитектурные решения), можно начать с материала CI-CD от теории до практики — как структурированный путь от концепций к рабочим примерам.
Комментарии
Пока нет комментариев