CI/CD в Github Actions для проекта на Flask+Angular

CI/CD в Github Actions для проекта на Flask+Angular
В этой статье я поделюсь своим опытом настройки 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 и кнопкой выхода.
  • Страница входа.
  • Страница регистрации.

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

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

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

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

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

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

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

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

CI/CD в Github Actions для проекта на Flask+Angular
Детальную настройку расширений мы рассматривать не будем, для наших демо-целей подойдут настройки по умолчанию.

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

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

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

    CI/CD в Github Actions для проекта на Flask+Angular
    Внизу страницы ставим галочку Secure the domain with Let’s Encrypt, если хотим настроить HTTPS для сайта:

    CI/CD в Github Actions для проекта на Flask+Angular

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

    CI/CD в Github Actions для проекта на Flask+Angular

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

У нас в наличии сервер с 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 и устанавливаем его (нам нужна бесплатная версия):

    CI/CD в Github Actions для проекта на Flask+Angular

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

    CI/CD в Github Actions для проекта на Flask+Angular

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

    CI/CD в Github Actions для проекта на Flask+Angular

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

    CI/CD в Github Actions для проекта на Flask+Angular

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:

CI/CD в Github Actions для проекта на Flask+Angular
И добавляем правило для проксирования входящего трафика в наш контейнер:

CI/CD в Github Actions для проекта на Flask+Angular

  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:

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

    CI/CD в Github Actions для проекта на Flask+Angular

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

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

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

  1. Переходим в Tools & Settings > Settings > Firewall:
    CI/CD в Github Actions для проекта на Flask+Angular
  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

    CI/CD в Github Actions для проекта на Flask+Angular

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

    CI/CD в Github Actions для проекта на Flask+Angular

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

    CI/CD в Github Actions для проекта на Flask+Angular

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

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

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

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

Деплой в Plesk

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

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

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

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

    CI/CD в Github Actions для проекта на Flask+Angular

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

    CI/CD в Github Actions для проекта на Flask+Angular

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

    CI/CD в Github Actions для проекта на Flask+Angular

  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

CI/CD в Github Actions для проекта на Flask+Angular

Frontend

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

Итоги

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

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

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

Источник: habr.com