Следующий шаг после разработки веб-приложения — размещение его на сервере. Независимо от сложности проекта или используемой инфраструктуры, общий процесс остается одинаковым: нужно «упаковать» код в CI/CD-конвейер и отправить на сервер. В тексте рассмотрим, как это происходит на примере простого приложения to-do list на React. Подробности под катом!

Используйте навигацию, если не хотите читать текст полностью:

Описание проекта
Создание проекта в облаке
Настройка веб-сервера и ручной деплой
Настройка раннера
Автодеплой
Заключение

Описание проекта



Скриншот приложения.

To-do list — это приложение для управления задачами. Оно помогает пользователю быстро добавлять, удалять и помечать задачи как выполненные. Так как они хранятся в локальном хранилище браузера, нам не нужна внешняя база данных и API для работы. Сосредоточимся на деплое приложения и настройке веб-сервера. В качестве последнего используем Nginx, а для обеспечения доступности из глобальной сети — облачную инфраструктуру.


Создание проекта в облаке


1. Переходим в Панель управленияОблачная платформа. Нажимаем Создать проект.


2. Вводим название для нового проекта и нажимаем Создать проект.


3. В разделе Облачная платформа видим новый проект. Во вкладке Серверы нажимаем Создать сервер.


4. Выбираем произвольное имя (в нашем случае — todo-list), локацию, пул и источник.


В качестве источника выбрали Debian 12, но есть множество других вариантов. Вы можете выбрать подходящий дистрибутив Linux или уже готовый образ с приложениями.


5. Настраиваем конфигурацию. В нашем случае подойдет Standard с локальным SSD-диском на 8 ГБ, 1 vCPU и 1 ГБ ОЗУ. В блоке Сеть выбираем Новый публичный IP-адрес. Так наше приложение будет доступно из интернета.


6. В блоке Доступ добавляем SSH-ключ (публичную часть) для пользователя root.


7. После нажатия на кнопку Добавить SSH-ключ появляется окно ввода. Здесь есть шпаргалка для генерации пары SSH-ключей, а также ссылка на полную версию инструкции. Вводим публичный ключ в формате OpenSSH и имя для него, нажимаем Добавить.


8. Получаем пароль и сохраняем его в безопасное место.


9. Проверяем стоимость выбранной конфигурации и создаем сервер.


Отлично — сервер готов. Видим его в списке созданных.


После получения статуса ACTIVE можно заходить на сервер по выделенному белому IP-адресу:

ssh root@31.129.35.121


В примере мы сосредоточены на публикации нашего приложения и не рассматриваем вопросы первичной настройки и безопасности сервера. О необходимых мерах по защите сервера, настройке, подключении SSH и установку специализированного ПО для блокировки злоумышленников — рассказываем в пошаговой шпаргалке.

Настройка веб-сервера и ручной деплой


1. Обновляем информацию о пакетах:

apt update

2. Устанавливаем пакеты:

  • mc — для удобной графической навигации по файловой системе,
  • git — для клонирования репозитория с приложением,
  • nginx — для организации веб-сервера,
  • nodejs и npm — для установки пакетов и сборки веб-приложения на React.

apt install mc git nginx nodejs npm

3. После установки проверяем корректность работы Nginx. Для этого в браузере переходим по IP-адресу сервера 31.129.35.121, который ранее арендовали:


Приветственная страница Nginx. Если вы ее видите, значит сервер работает корректно.

4. Перемещаемся в директорию /var/www/ и клонируем репозиторий с веб-приложением:

git clone <a href="https://gitlab.com/Byurrer/todo-list.git">https://gitlab.com/Byurrer/todo-list.git</a>


5. Переходим в директорию todo-list и устанавливаем пакеты:

npm install

6. Производим сборку проекта:

npm run build

Теперь появилась директория build, в которой лежит вся статика нашего приложения:


7. Готовим конфигурацию сайта в Nginx. Для этого в директории /etc/nginx/sites-available/ создаем файл todo-list и записываем в него код:

server {
        listen 80;
        server_name 31.129.35.121;
        location / {
                root /var/www/todo-list/build/;
                try_files $uri $uri/ =404;
        }
}

Записать код можно с помощью mcedit:

mcedit /etc/nginx/sites-available/todo-list

В этом конфиге приложение будет доступно на 80 порте (http) при обращении к хосту 31.129.35.121.

8. Указываем корень нашего сайта /var/www/todo-list/build/ и в директиве try_files обращаемся к файлам/директориям, переданным в строке запроса. Если их не удается найти — отдаем страницу 404.

9. Создаем символьную ссылку на нашу конфигурацию в директорию /etc/nginx/sites-enabled/ и проверяем ее на валидность. Если ошибок нет, отправляем Nginx команду на перезагрузку конфигурации:

ln -s /etc/nginx/sites-available/todo-list /etc/nginx/sites-enabled/
nginx -t
nginx -s reload


Вывод должен выглядеть примерно так.

10. Если ошибок не было, то при обращении к серверу через веб-браузер http://31.129.35.121/ вы получите страницу веб-приложения:


Настройка раннера


Когда проект небольшой и разрабатывается одним человеком, то ручной деплой, как мы это делали ранее, вполне уместен. Но если речь о командной работе над корпоративным или продуктовым приложением, то нужен автодеплой. Так при правках код будет сразу поставляться на сервере.

Пойдем простым путем. У нас есть сервер с развернутым веб-приложением, а код хостим на GitLab, где доступен удобный инструмент для организации CI/CD-конвейера — GitLab CI. С его помощью сможем поставлять приложение на веб-сервер. Рабочий механизм — GitLab Runner, а процессы описываются с помощью gitlab-ci.yml в корне репозитория.

1. Создадим раннер для проекта. Перейдем в Settings → CI/CD → Runners и нажмем New Project Runner.


2. Укажем произвольный тег, который мы дальше будем использовать при настройке CI/CD, а также описание раннера. Нажмем Create runner.


3. Следующая страница содержит инструкции по настройке раннера, а при клике по выделенной ссылке появится шпаргалка по установке раннера на сервер.


Выполнение шагов установки должно выглядеть примерно так:


Во время выполнения джобов CI/CD можно столкнуться с ошибкой:

ERROR: Job failed: prepare environment: exit status 1.  Check https://docs.gitlab.com/runner/shells/index.html#shell-profile-loading for more information


Для решения нужно использовать подсказку из документации.

4. Перейдем к регистрации раннера, как указано в Step 1.


Указываем источник и название по умолчанию, тип раннера — shell, так как исполнять команды будем на самом сервере:


5. После успешной регистрации на странице появится блок об успешном завершении.


6. Нажимаем кнопку View runners и видим страницу с раннерами для нашего проекта.


Важно! Нажимаем кнопку Enable for this project, чтобы раннер был включен для этого проекта.


Автодеплой


Созданный раннер будет выполнять все команды от пользователя gitlab-runner, но сперва попробуем сделать все вручную.

1. Создаем новую директорию, в котором будем размещать всю «статику» (статические файлы). Меняем владельца и группу на gitlab-runner.

mkdir /var/www/todo-list-build
chown gitlab-runner:gitlab-runner /var/www/todo-list-build

2. Меняем путь до корня сайта в конфиге Nginx.


3. При помощи nginx -t проверяем конфигурацию на ошибки, а далее перезагружаем конфиг командой nginx -s reload.

4. Заходим от имени пользователя gitlab-runner, собираем и деплоим проект.

su gitlab-runner
cd # переход в домашнюю директорию пользователя
git clone <a href="https://gitlab.com/Byurrer/todo-list.git">https://gitlab.com/Byurrer/todo-list.git</a>
npm install
npm run build
rsync -av --delete build/ /var/www/todo-list-build/

5. Проверяем в директории /var/www/todo-list-build/, что файлы скопированы.


6. Переходим в браузере по IP сервера. Веб-приложение также должно работать.

7. Теперь, когда мы поняли, какие команды будет выполнять раннер, можем реализовать автодеплой. Для его настройки нужно в корне репозитория создать файл .gitlab-ci.yml с таким содержимым:

stages:
  - build
  - deploy
build:
  stage: build
  script:
    - npm install
    - npm run build
  cache:
    paths:
      - node_modules/
  artifacts:
    paths:
      - build/
  tags:
    - todo-list
deploy:
  stage: deploy
  script:
    - rsync -av --delete $CI_PROJECT_DIR/build/ /var/www/todo-list-build/
  only:
    - main
  tags:
    - todo-list


8. Запушим в ветку main произвольные изменения и перейдем в Builds → Pipelines, где появился автоматически запущенный конвейер.


Внутри выполняются две джобы:


Можно перейти к каждому из них и наблюдать процесс выполнения.


Успешное исполнение джобы на стадии deploy выглядит примерно так.

После выполнения всего конвейера наши правки должны поступить в приложение на веб-сервере.

Заключение


Мы рассмотрели один из простых вариантов организации CI/CD-конвейера для веб-приложения. В реальных условиях процесс становится более сложным и гибким, однако понимание его основ — первый шаг. Далее вы можете масштабировать и адаптировать проект под задачи в рабочей среде.

Комментарии (5)


  1. egribanov
    31.10.2024 18:07

    Можно подобное, но только на gitea? Небольшие проекты тоже можно автодеплоить


    1. markelov69
      31.10.2024 18:07

      git webhook (применим к любому типу github, gitlab, gitea и т.д. и т.п.)
      1) поднимаете http server (например на node.js)
      2) настраивайте webhook чтобы слал инфу о пушах по адресу вашего http сервера
      3) Получаете инфу на сервере о пушах и делаете уже что нужно. Например git pull && npm i && npm run build и т.п.
      Всё что душе угодно, хоть тэги с версиями проставляйте, в JIRA или куда угодно по АПИ обращайтесь
      4) Запустить всё это чтобы работало автоматически например через pm2


      Вот пример:

      const http = require('http');
      const fs = require('fs');
      const { promisify } = require('util');
      const exec = promisify(require('child_process').exec);
      
      
      // Create a local server to receive data from
      const server = http.createServer(async (req, res) => {
          const body = [];
      
          console.log('hook');
      
          // Wait for request end and after that we will be ready to send response
          await (() => new Promise((resolve) => {
              let isEnd = false;
      
              req.on('data', async (chunk) => {
                  body.push(chunk);
                  if (isEnd) resolve(true);
              });
      
              req.on('end', () => {
                  isEnd = true;
                  resolve(true);
              });
          }))();
      
          console.log('hook receive data');
      
          res.writeHead(200, { 'Content-Type': 'application/json' });
          // Отвечаем что всё ок
          res.end(JSON.stringify({
              data: 'Hello World!',
          }));
      
          const json = JSON.parse(Buffer.concat(body).toString());
      
          // Чтобы изучить JSON можно посмотреть его в файле
          fs.writeFileSync(__dirname + '/hook.json', JSON.stringify(json), { encoding: 'utf-8' });
          // и/или в консоли (но не всегда это удобно)
          //console.log(json);
      
          // Ну и далее логика, например при пуше в master ветку
          if (json.repository.full_name === 'SomeRepo/name' && json.ref === 'refs/heads/main') {
            await exec(`cd /path/to/project_folder && git pull && npm i && npm run build`);
          }
      });
      
      server.listen(8181);
      

      В это примере на этом же сервере поднят nginx и он натравлен на папку dist которая появляется при сборке.
      По сравнению с остальными способами типа раннеров с докерами и т.п. этот работает сверх быстро, по сути время работы занимает только npm run build, сомневаюсь что при каждом пуше вы меняете зависимости чтобы их приходилось доустанавливать)

      Если планируется что пушить будете очень часто и возможна ситуация где условно в рамках нескольких секунд прилетит более 1 хука которые должно провоцировать билд, то лучше завернуть это в очередь, это достаточно легко


    1. Byurrer Автор
      31.10.2024 18:07

      Пока не доводилось работать с gitea, возможно позже рассмотрю.



  1. Coler95
    31.10.2024 18:07

    Очень классный и подробный гайд для новичков. Большое спасибо!