Пишу про полезные материалы про IT, и собираю свой ламповый нетворкинг тут - https://t.me/+434aQiGpZtAyNTU6. Присоединяйтесь!
Оглавление.
Часть 3. Его величество, деплой!
Введение
На прошлых шагах, мы с вами разобрали базовые механизмы CI, которые позволили нам автоматизировать ряд рутинных операций. Самое время перейти к более сложному кейсу. Сказать откровенно – я несколько раз пытался подойти к освоению по части CD. Сложновато было найти руководство под мои нужны. А ознакомление с документацией не принесло пользы, в начале этого пути.
Поэтому, как и в прошлые разы, давайте попробуем взглянуть на задачу максимально просто. Определить – что мы хотим получить, что нам для этого нужно, и какие инструменты потребуются. 🫡
Постановка задачи.
CD – Continuous delivery (непрерывная доставка). Простыми словами – деплой, публикация нашего приложения в среду эксплуатации. Если совсем просто – мы разработали приложение, и оно готово для того, чтобы отдать его на растерзание конечному пользователю. Но пока мы работаем над ним – делаем это в локальной среде, на домашнем или рабочем компьютере.
Если брать в пример наше приложение на React – оно отлично запускается в режиме разработки на localhost:3000 (после выполнения стандартной для этого фреймворка команды npm run start). И мы видим результат в браузере. Но на этом этапе – никакой другой пользователь, не сможем открыть этот сайт.
PS. В материале не будет рассматриваться вариант – открыть локальную машину наружу – приобретением белого IP адреса у провайдера, настройкой локального WEB сервера и т.д. Как правило, такой подход не оправдывает ожиданий, но справедливости ради – задачу может выполнить. Даже локальный домашний компьютер можно настроить так, что ваш сайт будет доступен с него в сети интернет. Но, как говорится, это тема совсем другой статьи.
Таким образом, мы приходим к следующим требованиям. Наше приложение должно быть доступно в сети. Чтобы любой пользователь мог открыть браузер, набрать нужный адрес, нажать Enter и увидеть наше приложение. ✅
Что потребуется
Здесь в игру вступает серверное железо. В конечно счете, нам нужно иметь в доступе машину, с «белым» (доступным в сети) ip адресом. Файлы нашего приложения на его жестком диске, и веб сервер – который будет прослушивать входящие запросы по сети, и отдавать в ответ нашу index.html страницу. У нас ведь React приложение – и все что будет уходить на клиент – это одна html страница. Плюс пара файлов – js и css, что является статикой.
Мы пойдем одним из простых путей. Для таких объемов приложения, вполне хватит VPS/VDS сервера. Если вы не знакомы с этим – компании хостинг провайдеры, предоставляют сервера в аренду. По стоимости от нескольких рублей в день, до бесконечности. Забегая вперед – под это приложения (фронтенд, бекенд, админка + БД), отлично справляется тариф стоимость порядка 20-25 рублей в сутки. Не буду советовать конкретные компании, сам пользуюсь несколькими. В общим и целом – услуги серверов в целом похожи. Если Вас интересуют какие-то специфические составляющие, тут уже выбирайте под свои задачи.
На сервере, очевидно, нам понадобится какая-то операционная система – чтобы им можно было бы пользоваться. Из хороших новостей – все серверы, в разных компаниях, с которыми мне довелось поработать, предоставляют очень удобный интерфейс базовой настройки. После регистрации и выбора тарифа – операционную систему можно установить в пару кликов. Вот пример с текущего проекта.

Если нет специфических требований, я обычно использую Ubuntu последней версии. Просто больше практического опыта с ней, и подходит для большинства задач. Опять же – вариант ОС нас здесь особо не интересует. Если у Вас появляется специфические задачи – выбираем ось под них.
Также вас попросят либо установить пароль администратора сервера (как правило root), либо задать свой. И где-то в настройках, либо в письме на почту – дадут данные для подключения – ip адрес сервера, имя учетной записи администратора и пароль. Это то, с чего мы начинаем в плане железа.🆗
PS. В случае с корпоративной средой – настройки, разумеется, будут отличаться. Но общая идея – что у нас должен быть в наличии боевой сервер, со всем установленным ПО, и файлами приложения – особо отличатся не будет. Да, он может запускать наш софт в рамках докер контейнера. Быть виртуальным или физическим. Но в общем и целом – это целевая машина (или кластер), до которой можно обратиться из вне по HTTP/HTTPS протоколу, и получить в ответ нужную информацию.
Касательно средств CD – здесь мы также будем использовать GitLab CI и дописывать наш pipeline, с прошлых частей. Из новых инструментов здесь добавиться работа с переменными окружения. Но об этом мы поговорим на соответствующем шаге.
И важный момент – нужно понять, что мы подразумеваем под процессом деплоя. Опять же – здесь все очень сильно будет разниться от приложения, языка и стека, на котором вы работаете. Вам нужно понять – что является фактом запущенного приложения. Это может быть запуск исполняемого файла. Или раздача собранных файлов веб сервером – как будет в нашем случае.
Здесь давайте остановимся чуть более подробнее. Чтобы наше приложение можно было бы открыть в браузере – нам нужен веб сервер. Возьмем популярный вариант Apache. С учетом того, что приложение маленькое – дев и продуктовую версию будем держать в рамках одной машины. Договоримся, что дев – который мы рассматриваем в нашем примере – повесим на 10001 порт. Общая структура url будет следующая
Мы не будем на этом этапе подключать https и доменное имя – в нашем случае для приложения в режиме разработки этого не требуется. Для продуктовой версии это будет несложно сделать – внеся нужные правки в конфигурацию Apache. Плюс часть в адресе приложение из примера – админ панель. Она будет доступа по этому префиксу.
/admin
И важный момент по самому приложению. Чего мы хотим добиться? CD – средство непрерывной доставки кода. Когда в develop ветке нашего приложения, а именно с нее мы будем собирать дев стенд – будут появляются изменения – мы хотим, чтобы на сервере они сразу были доступны. Таким образом – нам нужно, чтобы файлы с изменениями, которые хранятся на Gitlab (или в любом другом хранилище кода) – попадали на наш сервер. И приложение актуализировалось – проще говоря, происходила пересборка React приложения – и у нас появлялся актуальный билд. Который раздавался через Apache.✅
PS. Здесь хотелось бы отменить, реальную пользу автоматизации, которую я испытал на себе, настроив этот процесс впервые. Представьте себе ситуацию активной разработки, или исправлению багов. У себя на компьютере – открыть файл, исправить пару символов. Казалось бы – недолго. Сделать коммит и запушить. Тоже терпимо. А теперь нужно подключиться к удаленному серверу. Зайти в папку с проектом, сделать git clone. Запустить пересборку React. Кто делал – тот поймет.
Промежуточные результаты.
В абзаце выше, уже вполне сформировались шаги, которые мы сможем автоматизировать. Но часть настроек нужно будет сделать вручную. Давайте по пунктам
Заполучить себе в распоряжение боевой сервер, с установленной ОС (Ubuntu).
Установить на него NodeJS и npm.
Установить на него вебсервер Apache.
Выбрать место хранения файлов приложения. Давайте остановимся на директории
/var/www/development/admin
Это будет корневая папка нашего проекта (читай – корень репозитория)
Собрать билд React приложения в папку dist, из develop ветки.
Настроить Apache на раздачу файлов приложения из папки dist, по нашему айпишнику и префиксу /admin. Плюс позаботится, чтобы он также отдавал файлы js/css.
Эта первичная настройка нужна, во-первых, для проверки работоспособности, во-вторых – для подготовки основных инструментов.
Так что же мы автоматизируем? Все следующие изменения – читай, коммиты и пуллревесты в develop ветку, будут приводить к следующему. На сервере будет актуализировать develop ветка – мы будем пулить свежие изменения из гита. И пересобирать билд. Apache прекрасно будет работать без перезапуска – он просто будет брать свежие файлы из папки.
Таким образом – любой новый код в деве – это запуск всех проверок (мы настроили их на предыдущем шаге) – и доставка этих изменений до дев среды. Через минуту – у вас на сервере проверенный код, приложение работает – все правки в наличии.✅
PS – возможно, часть ручных действий также можно автоматизировать – установка ПО на сервер, настройка Apache и т.д. Я пока до такого не дошел. Если у вас есть практика автоматизации развертывания инфраструктуры – поделитесь в комментариях. Как это делать, и какие инструменты использовать.
Начинаем настраивать.
Давайте начнем с предварительной настройки нашего удаленного сервера. Обычно для этого к нему подключаются по SSH протоколу. В варианте с моим хостинг провайдером – у них есть терминал для подключения прямо из браузера. Но мне больше нравится работать с локальной консолью.
В версиях Unix – утилита для работы с SSH идет сразу из коробки. Тоже самое для Windows, начиная с 10-й версии.
Для подключения к серверу, наберите команду (подставив свои значения – логин и ip).
ssh <имяпользователя>@<ip.address>
Если команда ssh не отзывается, проверьте и при необходимости установите на свою машину open ssh client.
После попытки подключения – вас попросят ввести пароль. Сделайте это. Если все было сделано верно – мы увидим консоль удаленного сервера.

Отлично. Из списка требуемых инструментов, у нас была NodeJS + npm. В предыдущих статьях, мы разбирали, как установить их. Можно воспользоваться этим решением.
Выполняем последовательно команды.
apt update
apt install -y curl
curl -fsSL https://deb.nodesource.com/setup_22.x | bash
apt install nodejs -y
Мы обновили данные по пакетам. Установили HTTP клиент curl, и с его помощью загрузили и установили ноду. Успешность установки можно проверить двумя командами.
node -v
npm -v
Если вы видите версии приложений, значит на этом этапе все хорошо.

Дальше устанавливаем Git.
apt install git
Проверяем версию.
git -v

Теперь установим вебсервер Apache.
apt install apache2
Чтобы проверить его установку, проверим – есть ли он в списке запущенных служб.
systemctl status apache2
Если мы видим информацию о службе – значит установка прошла успешно.

Теперь давайте займемся файлами проекта. Как показывал выше – я предлагаю разместить их в каталоге.
/var/www/development/admin
/var/www – стандартный путь, который часто используют для хранения данных веб сайтов. development/admin – Это уже внутренние папки. Как говорил, на этом сервере планируется храниться продакшн версия приложения, админ панель, фронт, бекенд. Поэтому эти папки – просто для структурирования информации.
В теории, вы можете выбирать любую файловую структуру.
Переходим в var/www.
cd var/www
Создаем здесь папку development.
mkdir development
Заходим в нее, и в ней создаем папку admin.
cd development
mkdir admin
cd admin
Итак, мы в корне нашего будущего репозитория. Давайте затащим сюда файлы нашего проекта с gitlab. Не забывайте подставить адрес своего репозитория.
git clone –branch develop <your_repo_clone_url> ./
Здесь мы вытаскиваем сразу нужную develop ветку, и размещаем файлы в текущей папке.

Делаем установку зависимостей, и сборку приложения.
npm install
npm run build
Если все было сделано верно, мы получим собранное приложение.

Теперь вернемся к настройке Apache. Для нашей задачи воспользуемся виртуальными хостами. Если в двух словах – это механизм, позволяющий на одном сервере размещать несколько сайтов (ресурсов). Для наших нужд подойдет – мы настроим виртуальный хост для дев стенда. А в последствии, когда будет готова продакшн версия – создадим ее, попутно настроив доменное имя.
Перейдем в папку с конфигурационными файлами Apache
cd /etc/apache2/sites/available
Здесь по умолчанию 2 конфигурационных файла. Удобно под каждый проект, создавать свой конфиг. Давайте сделаем это.
touch mimoza.conf
Название любое, расширение .conf.

Открываем файл на редактирование.
nano mimoza.conf
Ниже приведен листинг настройки.
<VirtualHost *:10001>
DocumentRoot /var/www/development/admin/dist
<Directory /var/www/development/admin/dist>
Options Indexes FollowSymLinks
Require all granted
RewriteEngine On
RewriteCond %{REQUEST_URI} ^/admin
RewriteRule ^admin/.*$ /index.html [L]
</Directory>
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
<FilesMatch "\.js$">
Header set Content-Type "application/javascript"
</FilesMatch>
<FilesMatch "\.css$">
Header set Content-Type "text/css"
</FilesMatch>
</VirtualHost>
Здесь 5 важных блоков настроек
Мы создаем виртуальный хост на текущем ip адресе, и прослушиваем порт :10001. Теперь все подключения вида <ip>:10001 будут обслуживаться Apache. И нам нужно, чтобы он в ответ отдавал нашу index.html страницу из папки dist
DocumentRoot – здесь указываем корневую папку проекта, на которую прицеливается Apache.
<Directory> - С учетом того, что все пути в рамках <ip>:10001/admin/* должны приводить на index.html страницу, здесь мы добавляем редиректы. Это связано с особенностью SPA на React. Любой путь на сайте – будет обслуживаться средствами JS, в рамках index.html страницы. Поэтому мы редиректим все запросы на нашу корневую html страницу. Клиент получает ее, загружает – а дальше начинает работать React.
ErrorLog – здесь стандартные настройки логирования Apache
2 директивы FilesMatch – здесь мы добавляем http заголовок с нужным типом содержимого. По умолчанию Apache отдает файлы в виде text. Указание нужного Mime type нужно для корректной работы других типов файлов.
Теперь зайдем в файл ports.conf, и добавим правило для прослушивание 10001 порта. Он находится в директории выше - /etc/apache2.

Активируем 2 модуля – rewrite и mime.
a2emod rewrite
a2emod mime
И перезапустим Apache, чтобы наши изменения вступили в силу.
systemctl restart apache2

Теперь если попробовать сделать запрос, например на адрес <ip>:10001/admin/login – будет отзываться наше приложение. Значит первый деплой мы сделали верно. Приложение работает, Apache отдает нашу статику.

Можно переходить к автоматизации. 🫡
Gitlab CD
Возвращаемся к нашему Pipeline. В интерфейсе Gitlab идем в Pipeline editor, выбираем ветку develop. Добавляем еще одну стадию – dev deploy.

Добавим новую Job.
dev deploy:
stage: dev deploy
image: ubuntu:22.04
before_script:
- apt update
- apt install openssh-client -y
Указываем название и стадию. Образ выбираем Ubuntu 22. И устанавливаем openssh-client, чтобы подключаться к удаленному серверу. В моих тестах на чистой Ubuntu, клиента не было, поэтому я добавил вариант его установки.
Идея будет заключатся в том – что наш runner, будет подключаться к удаленному серверу. И на нем уже выполнять все требуемые команды. Матрица среди нас – сервера управляют серверами. 😱
Теперь нужно понять, каким образом сервера будут общаться друг с другом. Здесь мы пойдем таким же путем, как это делали локально – через ssh подключение. Но вместо пароля, будем использовать SSH ключи доступа. Чуть более надежно, и удобно, с точки зрения настройки.
Если Вы ранее не работали с парами ssh ключей, краткая теория. Мы генерируем пару ключей – открытый и закрытый ключ. Открытый хранится на сервере – закрытый на клиенте. При попытке подключиться к серверу по SSH – мы передаем закрытый ключ. Сервер проверяет его – сверяясь с открытым. Если пара корректная – значит авторизацию можно разрешить.
Давайте настроим этот процесс.
Первым делом нужно сгенерировать пару ключей. Наверняка вы локально используйте GIT. В его терминале работает команда ssh-keygen.
Запустите git cmd

В появившемся терминале выполните
ssh-keygen -t rsa
Вас попросят указать место хранения ключей. Можно оставить по умолчанию – нажимаем Enter
Парольную фразу можно добавить по желанию. Можно пропустить. Дважды прожимаем Enter. Получаем сгенерированную пару.

Теперь нам нужно перенести открытый ключ на клиент. На своей локальной машине, вы найдете 2 пары ключей.

Откройте в блокноте файл с расширением .pub. Информацию скопируйте. Теперь нам нужно перенести его на наш удаленный сервер.
Вернитесь в терминал, где мы подключены к нашему серверу.
Перейдите в домашнюю директорию текущего пользователя, в папку .ssh. Если вы настраиваете сервер с нуля, здесь нужно создать файл authorized_keys. Если вы уже добавляли пары ключей, то этот файл уже существует. У нас первый вариант. Давайте создадим файл, и вставим сюда скопированный ранее открытый ключ
nano authorized_keys
Ctrl + C наш ключ
Сохраняем файл.

Теперь мы можем подключаться к серверу используя команду SSH, и указывая наш ключ.
ssh -I <путь до закрытого локального ключа> <имя_пользователя>@<ip_сервера>
Налаживаем связь между серверами
Локально мы научились подключаться к удаленному серверу. Давайте сделаем тоже самое в нашем runner.
Для этого нам нужно перенести наш приватный SSH ключ в Gitlab. Идем в Settings => CI/CD => Variables. Здесь нажимаем на кнопку Add Variable.

Заполняем появившуюся форму. Выбираем тип “File”, Environments оставляем по умолчанию. Ставим флаг “Expand variable reference”. Даем любое имя (у меня это SSH_KEY). И в поле значение – копируем содержимое нашего приватного ключа. Сохраняем.

Теперь наш файл с ключом хранится внутри CI/CD инфраструктуры. И мы можем им пользоваться в рамках наших pipeline.
Мы полностью готовы.
Возвращаемся к в редактор.
Здесь добавляем строку в подготовительный скрипт
chmod 400 $SSH_KEY
Где SSH_KEY это имя нашей переменной окружения с ключей. Мы задаем ему требуемый уровень разрешений, чтобы им можно было бы воспользоваться

С учетом того, что файл со всеми jobs, у нас будет находится в одном месте, давайте выделим текущую стадию, только для dev ветки.
Это можно сделать, добавив правило следующее правило
rules:
- if: $CI_COMMIT_BRANCH == ‘develop’
Если коммит был в develop ветку, делаем текущую job.
А дальше непосредственно процесс автоматизации.
Мы подключаемся из консоли нашего runner к удаленному серверу, и выполняем все необходимые команды. Обратите внимание – в кавычках будут перечисляться команды, которые будут выполнятся уже в контексте удаленного сервера.
Чтобы легче это понять – попробуйте сделать локально. Мы подключаемся через SSH в терминале, и затем в терминале передаем команды – уже удаленной машине. Здесь суть да же.
Чтобы не передавать каждую команду по отдельности, и оставаться в контекcте подключения, мы группируем их через &&. Вот листинг.
- ssh -i $SSH_KEY root@<ip_address>" // подключаем, указывая наш приватный ключ
cd /var/www/development/admin/ && // переходим в директорию с проектом
rm tsconfig.tsbuildinfo -f && // убираем файл, который TS генерирует при сборке
git checkout develop && убеждаемся, что стоим на ветве develop
git pull --force && // забираем последние изменения с репозитория
source ~/.nvm/
nvm.sh
&& // явно активируем внутреннюю консоль
npm install && // запускаем установку зависимостей
npm run build" // запускаем сборку
Полная версия выглядит следующим образом.

Все команды довольно понятны, кроме двух пунктов.
rm tsconfig.tsbuildinfo -f
При сборке React приложения, генерируется новый файл, с мета информацией. Полезной нагрузки в нашем случае он не несет. Но его наличие ломает команду git pull – мы не сможем забрать измененения, если есть незакомиченный файл. Поэтому здесь я добавил его явное удаление.
source ~/.nvm/
nvm.sh
&& явный запуск консоли
На этапе тестов этого кода в раннере, я получал ошибки при выполнении команды npm install. Решение было найдено – посоветовали явно активировать консоль. Команда выше.
Давайте сохраним конфиг, и попробуем сборку в действии.

Все прекрасно собралось. На сервере появился билд с последними правками. И Apache начинает раздавать свежую версию приложения.
Теперь любые изменения в деве через пару минут будут появляться на стенде – Profit. ✅
Итого.
Мы неплохо поработали. Настроили CI/CD – теперь все наши коммиты автоматически проверяются на соответствие правилам кода. Мы убеждаемся, что в приложении нет критических ошибок, препятствующих сборке. И происходит автоматический деплой – новая версия приложения доступна для работы, через пару минут после слияния нашего PR с dev веткой.
Я попытался рассмотреть процесс максимально просто – так, как это помогло мне. Пробуйте сформилировать – какую цель мы преследуем?. Какие инструменты у нас должны быть для этого. Какие шаги и команды мы выполняем, для достижения результата. Что нужно настроить, какие файлы добавить и т.д. чтобы процесс заработал.
Вы всегда можете тестировать шаги автоматизации локально – выполняя нужные шаги, проверяя результат. А уже потом перенося рабочие примеры в код pipeline.
Можно изменять подход и инструменты. Не получается сгенировать SSH ключ через GIT cmd - используйте другие инструменты. Не походит Ubuntu, как операционная система runner? Пробуйте альтернативы.
Данное руководство будет полезно для разработчиков на React. Но общие идеи и мысли в целом, могут помочь погрузится в процесс CI/CD.
Вам был полезен материал? Хотите что-нибудь дополнить или посоветовать? Оставляйте комментарии или присоединяйтесь ко мне в телеграм - https://t.me/+434aQiGpZtAyNTU6
Удачи!