Пишу про полезные материалы про 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. С учетом того, что приложение маленькое, dev и продуктовую версию будем держать в рамках одной машины. Договоримся, что dev, который мы рассматриваем в нашем примере, повесим на 10001 порт. Общая структура URL будет следующая:
Мы не будем на этом этапе подключать https и доменное имя – в нашем случае для приложения в режиме разработки этого не требуется. Для продуктовой версии это будет несложно сделать – внеся нужные правки в конфигурацию Apache, плюс часть в адресе приложения из примера – админ-панель. Она будет доступа по этому префиксу:
/admin
И важный момент по самому приложению. Чего мы хотим добиться? CD – средство непрерывной доставки кода. Когда в develop-ветке нашего приложения, а именно с нее мы будем собирать dev-стенд – будут появляться изменения – мы хотим, чтобы на сервере они сразу были доступны. Таким образом, нам нужно, чтобы файлы с изменениями, которые хранятся на 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 прекрасно будет работать без перезапуска – он просто будет брать свежие файлы из папки.
Таким образом, любой новый код в dev – это запуск всех проверок (мы настроили их на предыдущем шаге) и доставка этих изменений до dev-среды. Через минуту у вас на сервере проверенный код, приложение работает – все правки в наличии.✅
PS – Возможно, часть ручных действий также можно автоматизировать: установка ПО на сервер, настройка Apache и т.д. Я пока до такого не дошел. Если у вас есть практика автоматизации развертывания инфраструктуры, поделитесь в комментариях. Как это делать и какие инструменты использовать.
Начинаем настраивать.
Давайте начнем с предварительной настройки нашего удаленного сервера. Обычно для этого к нему подключаются по SSH-протоколу. В моем варианте хостинг-провайдер предоставляет терминал для подключения прямо из браузера, но мне больше нравится работать с локальной консолью.
В Unix-подобных операционных системах утилита для работы с SSH доступна сразу "из коробки". То же самое и для Windows начиная с 10-й версии.
Для подключения к серверу наберите команду (подставив свои значения – логин и IP-адрес).
ssh <имя_пользователя>@<ip.address>
Если команда ssh не отзывается, проверьте и, при необходимости, установите на свою машину OpenSSH 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.js. Успешность установки можно проверить двумя командами:
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. Для нашей задачи воспользуемся виртуальными хостами. Если в двух словах, это механизм, позволяющий на одном сервере размещать несколько сайтов (ресурсов). Для наших нужд подойдет – мы настроим виртуальный хост для dev-стенда, а впоследствии, когда будет готова продакшн-версия, создадим ее, попутно настроив доменное имя.
Перейдем в папку с конфигурационными файлами 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-типа нужно для корректной работы других типов файлов.
Теперь зайдем в файл 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
Удачи!
