Привет, Хабр! В этом материале мы разберем деплой приложения на React, арендуем облачный сервер и настроим nginx. Здесь будет необходимый минимум для фронтенд-разработчика:

  • Заливка проекта на GitHub.
  • Аренда и настройка облачного сервера по SSH.
  • Настройка nginx для раздачи статических файлов.
  • Сжатие бандла.
  • Подключение домена.
  • Настройка HTTPS.
  • Настройка Docker.


Зальем проект на GitHub


У нас есть базовый роутинг и пара пустых страниц. Есть базовый пример со счетчиком и выводом значения по кнопке. Сборка идет на Vite.


Нас интересует только запуск в режиме dev и сборка самого приложения. Для разработчиков на Vue или Angular все будет работать аналогично. Исключение — приложения с серверным рендерингом. Алгоритм деплоя для них отличается.


В приложении есть пара кнопок, значения и навигация по страницам. Этого достаточно, чтобы рассмотреть процесс деплоя. Чтобы взаимодействовать с этим проектом, нам необходимо залить его на GitHub, GitLab, Bitbucket — любой удаленный репозиторий. Делаем git init, git add, проводим commit — теперь с кодом можно работать. Наиболее подробно про инициализацию репозитория можно прочитать по ссылке.

Арендуем и настроим облачный сервер по SSH


Теперь нужно арендовать облачный сервер. В первую очередь необходимо создать проект в разделе Облачная платформа. Выбрать регион, в котором хотите создать сервер, после чего нажать кнопку Создать. Откроется конфигуратор, в котором можно гибко настроить параметры сервера. Например, выбрать операционную систему.


Можно выбрать фиксированный конфиг или собрать собственный. Поставим минимальный размер HDD-диска — 10 ГБ. Можно выбрать дополнительный SSD, настроить сети и всю базовую (и не только) конфигурацию. После того как сервер создан, можно подключиться по публичному IP-адресу.

Проверьте в терминале, установлен ли у вас SSH-клиент. Команда SSH должна выдавать такой результат.


Теперь по SSH подключимся к серверу. Пароль копируем из панели управления и перейдем к терминалу. Необходимо указать логин. В нашем случае — root. После этого укажем IP-адрес через @. Нажимаем Enter и вводим пароль.

Поздравляю — вы успешно подключились к серверу, и вам полностью доступно взаимодействие с операционной и файловой системой.


Здесь можно вводить любые команды, которые доступны на Ubuntu, поскольку мы выбрали именно эту операционную систему.

Теперь обновим APT — инструмент для работы с пакетами. Это нужно для того, чтобы установить Node.js, Git, Docker, если потребуется.


Используем команду:

sudo install git-all

После установки Git перейдем в корень файловой системы и клонируем в нее репозиторий. Поскольку репозиторий открытый, можно легко клонировать его через git clone.

Теперь здесь есть index.html, исходный код, и можно установить зависимости. Node Version Manager необходимо поставить на чистую операционную систему облачного сервера. NVM нужен, чтобы гибко управлять версиями Node.js.

Чтобы все заработало, необходимо добавить переменную окружения. Копируем строчку:

export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

После ввода команды NVM должен заработать.


Сейчас актуальная LTS-версия Node.js — v18.16.1. Устанавливаем ее через NVM. Чтобы проверить установку, можно использовать команду:

node -v

Теперь время установить пакеты. Делаем:

npm i

Чтобы сделать сборку проекта в package.json, есть отдельный скрипт.


Удалим папку dist и попробуем его запустить:

npm run build

Появляется папка dist — там будет index.html, внутри которого подключен скрипт. Последним является наш бандл .js, который получился в результате компиляции Typescript-кода и счетчика на React.

Теперь эту же сборку сделаем уже на удаленном сервере. Для этого пишем npm run build и ждем успешной сборки. Если написать команду ls, то здесь появится папка dist со всем нужным внутри.

Теперь необходимо донести сборку с index.html до пользователя, чтобы он перешел на наш домен и сервер отдал ему эти статические файлы. Для этого используем nginx.

Команду sudo apt update уже выполнили. Теперь с помощью APT установим сам nginx. Копируем команду:

sudo apt install nginx


Настройка nginx


Nginx — это веб-сервер, который максимально прост в использовании. Очень гибкий, конфигурируемый, и который можно использовать для раздачи статических файлов. Его можно поднять за пару минут, и он будет раздавать IMG, HTML, CSS и JS-файлы. В качестве альтернативы можно привести Apache. Статический сервер можно поднять и на Node.js, но nginx, пожалуй, самое популярное и быстрое решение для таких задач сейчас.

Перейдем в папку etc и введем команду ls, чтобы посмотреть содержимое папки. Появится директория nginx. Здесь лежит много файлов, но нас интересует файл с конфигурациями.


С помощью Vim откроем его и посмотрим на содержимое. Если Vim по дефолту не установлен, то сначала займемся этим, чтобы более удобно работать с файлами. Повторяем команду:

vim nginx config

Сейчас нужно обратить внимание на два зеленых include, которые находятся в самом низу. В них подключаются дополнительные конфиги, которыми можно расширить главный.


При работе с Vim в первый раз будет трудно. Сначала нужно выйти, нажав Escape, далее :q! — так можно выйти из редактора без сохранения.

Теперь перейдем в папку sites enabled, на которую стоял include. Здесь будет файл default, откроем его с помощью Vim. Здесь множество различных комментариев, которые лучше удалить, чтобы не отвлекаться.

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

Первая строчка listen 80 говорит, что веб-сервер будет слушать 80 порт. Для HTTP он является основным по умолчанию. То есть здесь можно прописать любой порт, но тогда его каждый раз придется указывать в URL.

Далее указана строчка root /var/www/html — это путь к папке, в которой будут располагаться статические файлы: index.html, css-файлы, js-файлы, картинки и т. д. Далее указываются названия энтри-пойнтов. В нашем случае это index.html. Здесь можно задать разные названия. Затем указывается server_name — он пока не особо интересен. Следующая директива — location, в ней идет работа с URL. С помощью нее можно настраивать редиректы, проксирование, обрабатывать query-параметры.

Так выглядит минимальный конфиг для того, чтобы nginx раздавал статические файлы.


Теперь сохраним конфиг без комментариев. Для этого нажимаем Escape :wq.

Попробуем в браузере по IP-адресу, который предоставляет Selectel, открыть приложение. Копируем IP, вставляем его в строку запроса и получаем дефолтную страницу welcome nginx, которая раздается по умолчанию.

Еще раз поговорим, как nginx понимает, какие файлы ему надо раздавать. В пространстве var/www/html нужно указать путь до папки, в которой лежит проект. Давайте заменим его на var/www/dist. Dist — это папка, в которой собирается наше фронтенд-приложение.

Выходим из Vim с сохранением :wq, и теперь уйдем обратно в корень на несколько уровней вниз, чтобы перейти в папку var/www. Здесь внутри лежит html-папка, которую нужно удалить командой:

rm -rf html

Теперь сделаем сборку еще раз:

npm run build

Когда папка со сборкой появится, переместим туда var/www.


Указываем название папки, которую хотим переместить, и куда хотим переместить.

Теперь папке dist внутри лежит index.html. Если обновить страницу — получим not found. Обратите внимание на директиву location. Сейчас папку html удалили, поэтому найти ее не получилось, и вернулся 404.

Чтобы система начала читать уже не из папки html, а из папки dist, необходимо перезапустить nginx командой:

sudo service nginx restart

По этому IP-адресу приложение стало доступно. Счетчик и страницы работают и переключаются. Если в URL будет какой-то участок пути, он по нему начнет запрашивать файл. Файл он не найдет, потому что у нас только один файл — index.html.

Чтобы nginx всегда запрашивал index.html, необходимо настроить проксирование запросов. Нужно избавиться от этого 404 и сделать редирект. На любом участке пути он всегда будет отдавать пользователю ndex.html. Сейчас у нас в участке пути — welcome, если мы страницу обновляем — все начинает работать.


Если вы делали не Single Page Application, а классическое многостраничное приложение, где много HTML-файлов, проксирование на index.html настраивать не надо.

Промежуточный итог: что успели сделать


  • Арендовали облачный сервер.
  • Подключились к нему по SSH.
  • Клонировали проект, установили Node.js, NVM.
  • Установили нод-модули, сделали сборку, установили nginx.
  • Настроили раздачу статических файлов и сборку приложения в отдельной директиве.

Концепция сжатия бандла


Проведем эксперимент. Откроем инструменты разработчика, вкладку Network и попробуем обновить страницу.


Смотрим на колонку Size и перезагружаем с очисткой кэша. И видим index bundle — это как раз файл index.js, который получился в результате сборки приложения.

Теперь попробуем уменьшить размер бандла. Если приложение большое и весит, например, 5-7 МБ, его лучше оптимизировать через код: разбить на чанки, использовать Lazy loading и т. д. Кроме этого, можно оптимизировать размер с помощью nginx-модуля gzip. Чтобы gzip работал, в корневой конфигурации nginx необходимо раскомментировать строки:


Эти строчки можно перенести в нашу конфигурацию, которая лежит в папке sites enabled.

Здесь важно обратить внимание на gzip_comp_level — это уровень грубости сжатия файлов. Максимальное сжатие — девять, минимальное — один. Важно соблюдать золотую середину: если переборщить с сжатием, можно потратить слишком много ресурсов. Чем сильнее сжатие, тем меньше трафика тратится, когда бандл перегоняется файлами по сети.

Нажимаем Escape, :wq, перезапускаем nginx:

sudo service nginx restart 

При любых изменениях в конфиге nginx лучше делать рестарт. Перезагрузим страницу с очисткой кэша еще раз. Как видим, бандл стал весить ровно в три раза меньше: весил 193 килобайта, сейчас — 63.


В заголовках можно увидеть content encoding gzip, то есть gzip сжал index.html и разархивировал его в браузере.

Подключение домена


Сам домен необходимо предварительно арендовать у хостера. Теперь сделаем так, чтобы к серверу можно было обращаться не только по IP-адресу, но и по какому-то красивому доменному имени.

Вводите любое уникальное название для вашего домена — в нашем случае пусть это будет vite-deploy-test. Для подключения необходимо указать DNS-серверы, которые будут обрабатывать домен. Поскольку хостимся в Selectel, будем использовать его DNS-серверы — здесь также есть небольшая инструкция, чтобы добавить домен.


Копируем название домена в панель управления Selectel и добавляем его. Открываем этот домен и видим адреса. Вернемся обратно в инструкцию и посмотрим, как делегировать домен.

Здесь необходимо передать управление на DNS-серверы Selectel. Они отличаются только цифрами в названии. Указав их, возвращаемся в инструкцию по делегированию доменов. Нажимаем Подключить к другому хостингу или сервису, указываем DNS-сервера — ns1, ns2, ns3 и ns4.

Осталось связать IP-адрес с доменом, который мы арендовали. Для этого существуют ресурсные записи типа A. Подробнее о настройке DNS читайте здесь.

В них указывается IP-адрес и домен, чтобы связать их, а делать это надо на стороне провайдера. Добавляется ресурсная запись типа А, в качестве значения указывается как раз IP-адрес. Имя записи vite-deploy-test.ru. В качестве значения необходимо указать наш IP-адрес, копируем его из личного кабинета.

Уточним, что это именно ресурсная запись типа А. Таких записей может быть много, но тип А отвечает именно за IP-адрес. Теперь остается ждать, пока DNS-сервер проснется. Когда домен начнет работать, необходимо будет какое-то время подождать.

Возвращаемся к домену, копируем название, вставляем в строку поиска и открываем приложение уже не по IP-адресу, а по доменному имени. Все должно работать, за исключением того, что браузер сообщает, что соединение небезопасно, потому что пытаемся получить доступ по HTTP. Если попробовать перейти на HTTPS, то будет ошибка, потому что сертификаты еще не настраивали.

Настройка сертификатов


Для настройки сертификатов используем центр сертификации Let’s Encrypt. Нажимаем Get Started, выбираем HTTP-сервер — nginx и Ubuntu. В списке нет Ubuntu 22 (эта операционная система установлена на сервере), но Ubuntu 20 тоже устроит.

Далее по SSH подключаемся к серверу, необходимо установить snapd. Проверить, установлен ли snapd, можно с помощью команды:

snapd--version


Далее уже существующий certbot нужно удалить. На всякий случай выполним команду:

sudo apt get remove certbot

Следующим этапом идет его установка. Копируем команду:

sudo ln -s /snap/bin/certbot /usr/bin/certbot

Certbot успешно установился. Переходим в корень проекта и выполняем следующую команду. Теперь необходимо объединить certbot с nginx и настроить его. Вставляем команду, а далее будет небольшой опросник:

  1. Certbot спрашивает email-адрес, вводим свой.
  2. Дальше дважды он требует подтверждения, соглашаемся.
  3. После чего он попросит ввести вас доменное имя, для которого нужен сертификат.

Деплоим сертификат


Теперь перейдем в nginx sites enabled с нашим дефолтным конфигом. Здесь появятся дополнительные настройки. Теперь прослушивается 443-й порт — дефолтный порт для HTTPS. 80-й порт для классического HTTP у нас также прослушивается. Можно также увидеть строчки, которые подгружает certbot для работы с сертификатами. Остается в server namе передать доменное имя. Передаем его и через пробел передаем его с префиксом www. Почти конец.

Сохраняем, выходим из Vim и перезапускаем nginx. Еще есть команда, которая проверяет, что конфиг заполнен корректно — рекомендую ее выполнить:

nginx -t

Меняем HTTP на HTTPS, и замок становится закрытым — безопасное подключение, сертификаты валидны, HTTPS работает как нужно.

Промежуточный итог


Сертификаты настроили, домен настроили, облачный сервер настроили. Теперь посмотрим, как довозить изменения в коде. Обратите внимание, что в welcome page появились правки: вывод id из query-параметров и параметров строки запроса.


Переходим на welcome, вводим id в строку. Убедимся, что nginx сейчас корректно обрабатывает эти query-параметры.

Работаем с изменениями


Что сделать, чтобы новый код дотащить до сервера? Во-первых, необходимо это закоммитить на GitHub. Делаем git add, git commit, пишем commit message, git push origin master, отправляем все это в удаленный репозиторий на GitHub.

Во-вторых, переходим в папку, в которой расположен проект. Перед этим подключимся по SSH, потому что через какое-то время соединение отваливается. Открываем папку и делаем git pull. Таким образом, мы подтянем с GitHub, с удаленного репозитория, все изменения. Заново делаем сборку:

npm run build

Удалим старую сборку из папки var/www и переместим туда новую сборку: mv dist, и путь до папки www.

Попробуем открыть приложение vite-deploy-test. Сейчас уже с новыми правками сюда доехал id. Давайте попробуем добавить query-параметр:


Дело за малым: осталось сохранить конфигурацию nginx. Перейдем в папку, в которой она находится, с помощью команды:

cat default 

Выведем все содержимое файла в терминал и скопируем это.

Откроем Webstorm, сделаем папку .nginx, а внутрь поместим сам конфиг, nginx.conf. Сюда вставляем то, что скопировали из терминала. Если теперь будет изменен сервер, конфиг можно забрать с собой, скопировав его в нужную папку.

Для базового сценария и Single Page Application все готово. Изменения появляются быстро, какого-то сложного пайплайна не нужно. Если нужно автоматизировать и инкапсулировать все это в контейнер, то теперь настроим Docker.

Настройка Docker


Конфиг, который лежит в Docker-файле, необходимо поместить в нужную папку. Образ на основе Node.js указываем в work dir, устанавливаем зависимости, делаем сборку, потом делаем образ из nginx. Копируем сборку в папку, которую раздает nginx, то есть нашу сборку, dist перемещаем в папку, на которую смотрит nginx. Важно, чтобы данные в конфиге совпадали с теми, что вы указываете здесь.


Завершающие штрихи


Повторяем действия для самого конфига: перемещаем в папку nginx sites enabled. Запускаем nginx, и теперь из коробки все будет работать. Может быть, на этом этапе еще потребуется поработать с портами и настройкой доступа по HTTPS, но нет ничего такого, что мы не рассматривали.

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

Заключение


В этой инструкции мы прошли по всем основным этапам деплоя простого фронтенд-приложения и даже попробовали работать с Docker. Вне зависимости от того, какой сложности вы хотели бы развернуть проект, — в Selectel есть подходящая конфигурация.

Полезные материалы по теме


   

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


  1. baldr
    18.08.2023 14:33
    +3

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

    Важно соблюдать золотую середину: если переборщить с сжатием, можно повредить данные или процессы.

    WUT? Это что за жесть? Вы там гидравлическим прессом сжимаете что ли?

    Собирать статику напрямую на сервере - антисовет. А если у вас 5 (20) серверов - на каждом будете собирать? И имена ресурсов будут на них разные, то есть кэширования можно не ждать. Да и вообще ставить средства сборки на сервер - антисовет.

    Certbot я бы, как раз, ставил внутрь докера. Тем более что у них и образ есть готовый.


    1. YaMisha
      18.08.2023 14:33

      Я не так давно изучаю мир JavaScript, но если не ошибаюсь, браузеру необязательно, чтобы совпадали названия статик-файлов, чтобы их кэшировать в себе. А насчёт нагрузки на сбор статики можно не согласиться, ведь в том же django она собирается на стороне сервера единожды. Возможно, я не так понял. За пределами collectstatic в django не видел мира


      1. baldr
        18.08.2023 14:33
        +2

        Браузер кэширует по имени файла. Вернее, по URL ресурса.

        Если у вас есть, например, 5 серверов, стоящих за Load Balancer'ом и на каждом статика собиралась отдельно, то в итоге мы можем получить файлы вида mywidget34567.js - на каждом сервере свое имя. И после обновления страницы вполне можем попасть на другой сервер с другими именами файлов, то есть браузер будет их грузить заново.

        А насчёт нагрузки на сбор статики можно не согласиться, ведь в том же django она собирается на стороне сервера единожды.

        Дело даже не в нагрузке, а в лишних инструментах, которые ставятся на веб-сервер. Это угроза, в первую очередь, безопасности. Если на сервер попадет злоумышленник и найдет там, например, компилятор gcc - то он сможет быстро собрать из исходников какой-нибудь зловред. По этой причине на продакшен-сервера ставится абсолютный минимум утилит. Частично сейчас это решается контейнерами, но, опять же - процедура деплоймента должна быть как можно более простой - закинуть известные файлы по известному пути - и все. А какое количество библиотек ставится в node_modules - про это даже анекдоты ходят..


  1. 13werwolf13
    18.08.2023 14:33
    +1

    что за рандомный набор слов без общего смысла.

    огромное кол-во опечаток, антипаттерны подаются как хороший совет..


  1. Suvitruf
    18.08.2023 14:33
    +1

    В блоге Selectel'а не ожидал увидеть статью, в которой описано, как нужно заходить по ssh и ручками пулить изменения из гита, чтоб потом на хосте это собирать.

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

    Нет, Докер очень удобная штука для любого проекта для упрощения. Github + Actions, который по пушам собирает образ и кладёт в Registry, а на серваке что-то, что будет проверить обновления образа и пулить.


    1. AlexGluck
      18.08.2023 14:33
      +1

      Например вместо богомерзкого докера подман, у которого есть даже процедура автообновления контейнеров)


  1. pauelstv
    18.08.2023 14:33
    -1

    Хабр стремительно деградирует.


  1. warus
    18.08.2023 14:33

    на сервере поставить snap, чтобы запустить certbot, чем родной certbot не устраивает?
    ещё тормозов добавить?
    понимаю что все как обезьяны у друг друга копируют,
    уже 3 раз это встречаю, даже на дебиан snap ставят