Заливаем приложение на облачный сервер

Деплой приложения с nginx

Деплоим приложение на арендованный сервер, настраиваем nginx, доставку кода и Docker.

Введение

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

$ sudo dnf 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 и настроить его. Вставляем команду, а далее будет небольшой опросник:

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

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

Теперь перейдем в 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 есть подходящая конфигурация