В этой статье я поделюсь своим опытом настройки CI/CD с использованием панели управления Plesk и Github Actions. Сегодня будем учиться деплоить простенький проект с незамысловатым названием «Helloworld». Он написан на Python-фреймворке Flask, с воркерами на Celery и фронтендом на Angular 8.

Ссылки на репозитории: бэкенд, фронтенд.

В первой части статьи мы посмотрим на наш проект и его части. Во второй — разберемся, как настроить Plesk и установить необходимые расширения и компоненты (БД, RabbitMQ, Redis, Docker и т.д.).

В третьей части мы, наконец, разберемся, как настроить пайплайн для деплоя нашего проекта на сервер в dev- и prod-среду. А потом запустим сайт на сервере.

И да, забыл представиться. Меня зовут Олег Борзов, я fullstack-разработчик в команде CRM для менеджеров ипотечного кредитования в Домклик.

Обзор проекта


Для начала рассмотрим два репозитория проекта — бэкенда и фронта — и пробежимся по коду.

Бэкенд-часть: Flask+Celery


Для бэк-части я взял достаточно популярную среди Python-разработчиков связку: фреймворк Flask (для API) и Celery (для очереди задач). В качестве ORM используется SQLAchemy. Для миграций применяется Alembic. Для валидации JSON в ручках — Marshmallow.

В репозитории есть файл Readme.md с подробным описанием структуры и инструкциями для запуска проекта.

API веб-части достаточно незамысловатый, состоит из 6 ручек:

  • /ping — для проверки доступности;
  • ручки для регистрации, авторизации, деавторизации и получения авторизованного пользователя;
  • ручка для отправки email, которая кладет задачку в очередь Celery.

Celery-часть еще проще, там всего одна задачка send_mail_task.

В папке /conf лежат две подпапки:

  • docker с двумя Docker-файлами (base.dockerfile для сборки редко меняющегося базового образа и Dockerfile для основных сборок);
  • .env_files — с файлами с переменными окружения для разных сред.

В корне проекта находятся четыре файла docker-compose:

  • docker-compose.local.db.yml для поднятия локальной БД для разработки;
  • docker-compose.local.workers.yml для локального поднятия воркера, БД, Redis и RabbitMQ;
  • docker-compose.test.yml для прогона тестов при развёртывания;
  • docker-compose.yml для деплоя.

И последняя интересная нам папка — .ci-cd. В ней лежат shell-скрипты для развёртывания:

  • deploy.sh — запуск миграции и деплоя. Запускается на сервере после сборки и прогона тестов в Github Actions;
  • rollback.sh — откат контейнеров на предыдущую версию сборки;
  • curl_tg.sh — отправка уведомлений о развёртывании в Telegram.

Фронтенд на Angular


Репозиторий с фронтом сильно проще бэковского. Фронт состоит из трёх страниц:

  • Главная страница с формой для отправки email и кнопкой выхода.
  • Страница входа.
  • Страница регистрации.

Главная страница выглядит аскетично:


В корне лежат два файла Dockerfile и docker-compose.yml, а также знакомая нам папка .ci-cd с чуть меньшим количеством скриптов, чем в бэковском репозитории (убраны скрипты для запуска тестов).

Заводим проект в Plesk


Начнем с настройки Plesk и создания подписки для нашего сайта.

Установка расширений


В Plesk нам понадобятся четыре расширения:

  • Docker для управления и визуального отображения состояния контейнеров в админке Plesk;
  • Git для настройки шага деплоя на сервере;
  • Let's Encrypt для генерации (и автопродления) бесплатных TLS-сертификатов;
  • Firewall для настройки фильтрации входящего трафика.

Установить их можно через админку Plesk в разделе Extensions:


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

Создание подписки и сайта


Далее нам нужно создать подписку для нашего сайта helloworld.ru и добавить туда поддомен dev.helloworld.ru.

  1. Создаем подписку для домена helloworld.ru и указываем логин-пароль для системного пользователя:


    Внизу страницы ставим галочку Secure the domain with Let's Encrypt, если хотим настроить HTTPS для сайта:

  2. Далее в этой подписке создаем поддомен dev.helloworld.ru (для которого также можно выпустить бесплатный TLS-сертификат):


Установка серверных компонентов


У нас в наличии сервер с OS Debian Stretch 9.12 и установленной панелью управления Plesk Obsidian 18.0.27.

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

  • PostgreSQL (в нашем случае будет один сервер с двумя БД для dev- и prod-среды).
  • RabbitMQ (то же самое, один инстанс с разными vhosts для сред).
  • Два инстанса Redis (для dev- и prod-среды).
  • Docker Registry (для локального хранения собранных Docker-образов).
  • UI-интерфейс для Docker registry.

PostgreSQL


В комплекте с Plesk уже идет СУБД PostgreSQL, однако не самой свежей версии (на момент написания статьи Plesk Obsidian поддерживал Postgres версий 8.4–10.8). Мы же хотим для своего приложения самую последнюю версию (12.3 на момент написания статьи), поэтому будем ставить ее вручную.

Подробных инструкций по установке Postgres на Debian в сети полно (пример), поэтому подробно описывать их не буду, просто приведу команды:

wget -q https://www.postgresql.org/media/keys/ACCC4CF8.asc -O - | sudo apt-key add -
sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt/ stretch-pgdg main" >> /etc/apt/sources.list.d/pgdg.list'

sudo apt-get update
sudo apt-get install postgresql postgresql-contrib

Учитывая, что в PostgreSQL достаточно посредственные настройки по умолчанию, нужно обязательно скорректировать конфигурацию. В этом нам поможет калькулятор: нужно вбить параметры своего сервера и заменить настройки в файле /etc/postgresql/12/main/postgresql.confна предложенные. Тут следует оговориться, что подобные калькуляторы — не волшебная пуля, и базу следует тюнить более точечно, исходя из вашего железа, приложения и сложности запросов. Но для старта этого достаточно.

Кроме предложенных калькулятором настроек также меняем в postgresql.confпрописанный по умолчанию порт 5432 на другой (в нашем примере — 53983).

После изменения конфигурационного файла перезагружаем postgresql-server командой:

service postgresql restart

Мы поставили и настроили PostgreSQL. Теперь создадим БД, пользователей для dev- и prod-сред, и выдадим пользователям права на управление БД:

$ su - postgres
postgres:~$ create database hw_dev_db_name;
CREATE DATABASE
postgres:~$ create user hw_dev_db_user with password 'hw_dev_db_password';
CREATE ROLE
postgres:~$ grant ALL privileges ON database hw_dev_db_name to hw_dev_db_user;
GRANT
postgres:~$ create database hw_prod_db_name;
CREATE DATABASE
postgres:~$ create user hw_prod_db_user with password 'hw_prod_db_password';
CREATE ROLE
postgres:~$ grant ALL privileges ON database hw_prod_db_name to hw_prod_db_user;
GRANT

RabbitMQ


Перейдем к установке RabbitMQ — брокера сообщений для Celery. Ставится он на Debian достаточно просто:

wget https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb
sudo dpkg -i erlang-solutions_1.0_all.deb

sudo apt-get update
sudo apt-get install erlang erlang-nox

sudo add-apt-repository 'deb http://www.rabbitmq.com/debian/ testing main'
wget -O- https://www.rabbitmq.com/rabbitmq-release-signing-key.asc | sudo apt-key add -

sudo apt-get update
sudo apt-get install rabbitmq-server

После установки нам нужно создать vhosts, пользователей и выдать нужные права:

sudo rabbitmqctl add_user hw_dev_amqp_user hw_dev_amqp_password 
sudo rabbitmqctl set_user_tags hw_dev_amqp_user administrator
sudo rabbitmqctl add_vhost hw_dev_vhost
sudo rabbitmqctl set_permissions -p hw_dev_vhost hw_dev_amqp_user ".*" ".*" ".*"

sudo rabbitmqctl add_user hw_prod_amqp_user hw_prod_amqp_password 
sudo rabbitmqctl set_user_tags hw_prod_amqp_user administrator
sudo rabbitmqctl add_vhost hw_prod_vhost
sudo rabbitmqctl set_permissions -p hw_prod_vhost hw_prod_amqp_user ".*" ".*" ".*"

Redis


Теперь установим и настроим последний компонент для нашего приложения — Redis. Он будет использоваться как бэкенд для хранения результатов задач Celery.

Мы поднимем два Docker-контейнера с Redis под dev- и prod-среды с помощью расширения Docker для Plesk.

  1. Заходим в Plesk, переходим в раздел Расширения, ищем расширение Docker и устанавливаем его (нам нужна бесплатная версия):

  2. Переходим в установленное расширение, находим через поиск образ redis bitnami и ставим последнюю версию:

  3. Заходим в скачанный контейнер и корректируем конфигурацию: указываем порт, максимальный выделяемый размер ОЗУ, пароль в переменных окружения, и монтируем том:

  4. Выполняем шаги 2-3 для prod-контейнера, в настройках только меняем параметры: порт, пароль, размер ОЗУ и путь к папке volume на сервере:


Docker Registry


Помимо базовых сервисов было бы неплохо поставить на сервер собственный репозиторий Docker-образов. Благо место на серверах сейчас достаточно дешевое (уж точно дешевле подписки на DockerHub), да и процесс установки приватного репозитория очень прост.

Мы хотим, чтобы у нас были установлены:

  • защищенный паролем Docker-репозиторий, доступный по поддомену https://docker.helloworld.ru;
  • UI-интерфейс для просмотра образов в репозитории, доступный по адресу https://docker-ui.helloworld.ru.

Для этого:

  1. Создадим в Plesk два поддомена в нашей подписке: docker.helloworld.ru и docker-ui.helloworld.ru, и настроим для них сертификаты Let’s Encrypt.
  2. В папку поддомена docker.helloworld.ru добавим файл docker-compose.yml с таким содержимым:

    version: "3"
    
    services:
      docker-registry:
        image: "registry:2"
        restart: always
        ports:
          - "53985:5000"
        environment:
          REGISTRY_AUTH: htpasswd
          REGISTRY_AUTH_HTPASSWD_REALM: basic-realm
          REGISTRY_AUTH_HTPASSWD_PATH: /auth/.htpasswd
          REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data
        volumes:
          - ./.docker-registry.htpasswd:/auth/.htpasswd
          - ./data:/data
    
      docker-registry-ui:
        image: konradkleine/docker-registry-frontend:v2
        restart: always
        ports:
          - "53986:80"
        environment:
          VIRTUAL_HOST: '*, https://*'
          ENV_DOCKER_REGISTRY_HOST: 'docker-registry'
          ENV_DOCKER_REGISTRY_PORT: 5000
        links:
          - 'docker-registry'
    

  3. Под SSH сгенерируем файл .htpasswd для Basic-авторизации в Docker-репозитории:

    htpasswd -bBc .htpasswd hw_docker_admin hw_docker_password
  4. Соберем и поднимем контейнеры:

    docker-compose up -d
  5. И нужно перенаправить Nginx на наши контейнеры. Это можно сделать через Plesk.

Следующие действия нужно проделать для поддоменов docker.helloworld.ru и docker-ui.helloworld.ru:

В разделе Dev Tools нашего сайта заходим в Docker Proxy Rules:


И добавляем правило для проксирования входящего трафика в наш контейнер:


  1. Проверяем, что можем авторизоваться в нашем контейнере с локальной машины:

    $ docker login docker.helloworld.ru -u hw_docker_admin -p hw_docker_password
    WARNING! Using --password via the CLI is insecure. Use --password-stdin.
    Login Succeeded
  2. Также проверим работу поддомена docker-ui.helloworld.ru:


    При нажатии на Browse repositories браузер выдаст окошко для авторизации, куда нужно будет ввести логин и пароль для репозитория. После чего нас перекинет на страницу со списком репозиториев (у вас пока она будет пустой):


Открываем порты в Plesk Firewall


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

Посмотрим, как это делать, на примере установленного нами ранее расширения Firewall для Plesk.

  1. Переходим в Tools & Settings > Settings > Firewall:
  2. Переходим в Modify Plesk Firewall Rules > Add Custom Rule и открываем следующие TCP-порты для подсети Docker (172.0.0.0/8):
    RabbitMQ: 1883, 4369, 5671-5672, 25672, 61613-61614
    Redis: 32785, 32786

  3. Также добавим правило, которое откроет внешнему миру порты PostgreSQL и management-панели RabbitMQ:

  4. Применяем правила с помощью кнопки Apply Changes:


Настройка CI/CD в Github Actions


Приступим к самой интересной части — настройке пайплайн непрерывной интеграции и доставки нашего проекта до сервера.

Этот пайплайн будет состоять из двух частей:

  • сборка образа и прогон тестов (для бэкенда) — на стороне Github;
  • запуск миграций (для бэкенда) и деплой контейнеров — на сервере.

Деплой в Plesk


Разберемся сначала со вторым пунктом (т.к. от него зависит первый).

Процесс деплоя мы будем настраивать с помощью расширения Git для Plesk.

Рассмотрим пример с Prod окружением для Backend репозитория.

  1. Заходим в подписку нашего сайта Helloworld и переходим в подраздел Git:

  2. Вставляем в поле «Remote Git repository» ссылку на наш Github-репозиторий и меняем папку по умолчанию httpdocs на другую (например, /httpdocs/hw_back):

  3. Копируем SSH Public key с предыдущего этапа и добавляем его в настройках Github.
  4. Нажимаем ОК на экране в пункте 2, после чего нас перекидывает на страницу репозитория в Plesk. Теперь нам нужно настроить обновление репозитория при коммитах в ветку master. Для этого переходим в Repository Settings и сохраняем значение Webhook URL (оно нам понадобится позже при настройке Github Actions):

  5. В поле Actions на экране из предыдущего пункта вводим скрипт для запуска деплоя:

    cd {REPOSITORY_ABSOLUTE_PATH}
    .ci-cd/deploy.sh {ENV} {DOCKER_REGISTRY_HOST} {DOCKER_USER} {DOCKER_PASSWORD} {TG_BOT_TOKEN} {TG_CHAT_ID} 

    где:

    {REPOSITORY_ABSOLUTE_PATH} — путь к папке prod backend-репозитория на сервере;
    {ENV} — среда (dev/prod), в нашем случае prod;
    {DOCKER_REGISTRY_HOST} — хост нашего docker репозитория
    {TG_BOT_TOKEN} — токен Telegram-бота;
    {TG_CHAT_ID} — ID чата/канала для отправки уведомлений.

    Пример скрипта:

    cd /var/www/vhosts/helloworld.ru/httpdocs/hw_back/
    .ci-cd/deploy.sh dev docker.helloworld.ru docker_user docker_password 12345678:AAbcdEfghCH1vGbCasdfSAs0K5PALDsaw -1001234567890
  6. Добавляем пользователя из нашей подписки в группу Docker (чтобы он мог управлять контейнерами):

    sudo usermod -aG docker helloworld_admin

Dev-среда для backend-репозитория и frontend настраиваются аналогично.

Pipeline деплоя в Github Actions


Переходим к настройке первой части нашего CI/CD-пайплана в Github Actions.

Backend


Пайплайн описывается в файле deploy.yml.

Но перед его разбором заполним в Github нужные нам Secret-переменные. Для этого переходим в Settings -> Secrets:

  • DOCKER_REGISTRY — хост нашего Docker-репозитория (docker.helloworld.ru);
  • DOCKER_LOGIN — логин к Docker-репозиторию;
  • DOCKER_PASSWORD — пароль к нему;
  • DEPLOY_HOST — хост, на котором доступна админка Plesk (пример: helloworld.ru:8443 или 123.4.56.78:8443);
  • DEPLOY_BACK_PROD_TOKEN — токен для деплоя в prod-репозиторий на сервере (мы его получили в Развёртывание в Plesk п. 4);
  • DEPLOY_BACK_DEV_TOKEN — токен для деплоя в dev-репозиторий на сервере.

Процесс деплоя прост и состоит из трёх основных шагов:

  • сборка и публикация образа в нашем репозитории;
  • запуск тестов в контейнере на базе свежесобранного образа;
  • развёртывание в нужную среду в зависимости от ветки (dev/master).

Frontend


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

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


Проксирование трафика через Nginx


Ну что ж, мы подошли к концу. Осталось только настроить проксирование входящего и исходящего трафика в наш контейнер через Nginx. Этот процесс мы уже рассмотрели в пункте 5 настройки Docker Registry. То же самое нужно повторить для бэк- и фронт-части в dev- и prod-окружениях.

Приведу скрины настроек.

Backend



Frontend



Важное уточнение. Во фронтенд-контейнер будут проксироваться все URL, кроме начинающихся на /api/ — они будут проксированы в бэк-контейнер (поэтому в бэк-контейнере все обработчики должны начинаться с /api/).

Итоги


Теперь наш сайт должен быть доступен по адресам helloworld.ru и dev.helloworld.ru (prod- и dev-окружение соответственно).

Итого, мы узнали, как подготовить простое приложение на Flask и Angular и настроить в Github Actions пайплайн для его выкатки на сервер под управлением Plesk.

Продублирую сссылки на репозитории с кодом: бэкенд, фронтенд.