CI/CD in Github Actions for a project on Flask+Angular

CI/CD in Github Actions for a project on Flask+Angular
In this article, I will share my experience of setting up CI/CD using the Plesk control panel and Github Actions. Today we will learn how to deploy a simple project with the straightforward name "Helloworld." It is written in the Python framework Flask, with workers using Celery and a frontend built with Angular 8.

Links to repositories: backend, frontend.

In the first part of the article, we will look at our project and its components. In the second part, we will figure out how to set up Plesk and install the necessary extensions and components (DB, RabbitMQ, Redis, Docker, etc.).

In the third part, we will finally figure out how to set up a pipeline to deploy our project on the server in both dev and prod environments. Then we will launch the site on the server.

Oh, and I forgot to introduce myself. My name is Oleg Borzov, and I am a full-stack developer on the CRM team for mortgage loan managers at Domclick.

Project Overview

First, let's take a look at the two project repositories β€” the backend and the frontend β€” and go through the code.

Backend part: Flask+Celery

For the backend, I chose a combination that is quite popular among Python developers: the Flask framework (for the API) and Celery (for the task queue). SQLAlchemy is used as the ORM. Alembic is used for migrations. Marshmallow is used for JSON validation in the endpoints.

In the repository There is a Readme.md file with detailed descriptions of the structure and instructions for running the project.

API for the web part is quite straightforward, consisting of 6 endpoints:

  • /ping β€” for availability checks;
  • endpoints for registration, authorization, deauthorization, and retrieving the authorized user;
  • an endpoint for sending emails, which places a task in the Celery queue.

Celery part is even simpler, with just one task send_mail_task.

In the folder /conf there are two subfolders:

  • docker with two Docker files (base.dockerfile for building a rarely changing base image and Dockerfile for main builds);
  • .env_files β€” with files containing environment variables for different environments.

In the root of the project, there are four docker-compose files:

  • docker-compose.local.db.yml for setting up a local database for development;
  • docker-compose.local.workers.yml for locally spinning up a worker, DB, Redis, and RabbitMQ;
  • docker-compose.test.yml for running tests during deployment;
  • docker-compose.yml for deployment.

And the last interesting folder for us β€” .ci-cdcontains shell scripts for deployment:

  • deploy.sh β€” starting migrations and deployment. It is run on the server after the build and test runs in Github Actions;
  • rollback.sh β€” rolls back containers to the previous version of the build;
  • curl_tg.sh β€” sending deployment notifications to Telegram.

Frontend on Angular

Repository with the frontend is much simpler than the backend. The frontend consists of three pages:

  • The main page with a form for sending email and a logout button.
  • Login page.
  • Registration page.

The main page looks minimalist:

CI/CD in Github Actions for a project on Flask+Angular
There are two files in the root Dockerfile and docker-compose.yml, as well as a familiar folder .ci-cd with slightly fewer scripts than in the backend repository (scripts for running tests have been removed).

Create a project in Plesk

Let's start by configuring Plesk and creating a subscription for our website.

Installing extensions

In Plesk, we will need four extensions:

  • Docker for managing and visually displaying the status of containers in the Plesk admin panel;
  • Git to configure the deployment step on the server;
  • Let's Encrypt for generating (and auto-renewing) free TLS certificates;
  • Firewall for setting up incoming traffic filtering.

You can install them through the Plesk admin panel in the Extensions section:

CI/CD in Github Actions for a project on Flask+Angular
We will not go into detailed configuration of the extensions; the default settings will suffice for our demo purposes.

Creating a subscription and site

Next, we need to create a subscription for our site helloworld.ru and add the subdomain dev.helloworld.ru.

  1. We create a subscription for the domain helloworld.ru and specify a username-password for the system user:

    CI/CD in Github Actions for a project on Flask+Angular
    At the bottom of the page, check the box Secure the domain with Let’s Encrypt, if we want to configure HTTPS for the site:

    CI/CD in Github Actions for a project on Flask+Angular

  2. Next, in this subscription, we create the subdomain dev.helloworld.ru (for which a free TLS certificate can also be issued):

    CI/CD in Github Actions for a project on Flask+Angular

Installing server components

We have a server with OS Debian Stretch 9.12 and installed control panel Plesk Obsidian 18.0.27.

We need to install and configure for our project:

  • PostgreSQL (in our case, there will be one server with two databases for dev and prod environments).
  • RabbitMQ (the same, one instance with different vhosts for environments).
  • Two instances of Redis (for dev and prod environments).
  • Docker Registry (for local storage of built Docker images).
  • UI interface for Docker registry.

PostgreSQL

The PostgreSQL database is already included with Plesk, but it's not the latest version (as of the time of writing, Plesk Obsidian supported Postgres versions 8.4–10.8). We want the very latest version for our application (12.3 at the time of writing), so we will install it manually.

There are plenty of detailed instructions for installing Postgres on Debian online (an example), so I won’t describe them in detail, just list the commands:

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

Given that PostgreSQL has quite mediocre default settings, it is essential to adjust the configuration. This can be aided by calculator: you need to enter your server parameters and replace the settings in the file /etc/postgresql/12/main/postgresql.confwith the suggested ones. It should be noted that such calculators are not a silver bullet, and the database should be tuned more precisely based on your hardware, application, and query complexity. But this is enough to start.

In addition to the settings suggested by the calculator, we also change the postgresql.confdefault port 5432 to another one (in our case - 53983).

After changing the configuration file, restart the postgresql-server with the command:

service postgresql restart

We have installed and configured PostgreSQL. Now let's create the databases, users for the dev and prod environments, and grant the users permissions to manage the databases:

$ 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

Now let's move on to the installation of RabbitMQ β€” the message broker for Celery. It installs quite easily on 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

After installation, we need to create vhosts, users, and assign the necessary permissions:

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

Now let's install and configure the last component for our application β€” Redis. It will be used as a backend for storing Celery task results.

We will bring up two Docker containers with Redis for the dev and prod environments using the extension Docker for Plesk.

  1. We log into Plesk, navigate to the Extensions section, search for the Docker extension, and install it (we need the free version):

    CI/CD in Github Actions for a project on Flask+Angular

  2. We go to the installed extension, find the image via search redis bitnami and install the latest version:

    CI/CD in Github Actions for a project on Flask+Angular

  3. We enter the downloaded container and adjust the configuration: specify the port, maximum allocated RAM size, password in environment variables, and mount the volume:

    CI/CD in Github Actions for a project on Flask+Angular

  4. Perform steps 2-3 for the prod container, only changing settings like: port, password, RAM size, and the path to the server volume folder:

    CI/CD in Github Actions for a project on Flask+Angular

Docker Registry

In addition to basic services, it would be nice to set up our own Docker image repository on the server. Fortunately, server space is quite cheap now (certainly cheaper than a DockerHub subscription), and the process of installing a private repository is very straightforward.

We want to have the following installed:

For this:

  1. We will create two subdomains in Plesk within our subscription: docker.helloworld.ru and docker-ui.helloworld.ru, and configure Let's Encrypt certificates for them.
  2. In the folder of the subdomain docker.helloworld.ru, we will add the file docker-compose.yml with the following content:
    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. Via SSH, we will generate the .htpasswd file for Basic authentication in the Docker repository:
    htpasswd -bBc .htpasswd hw_docker_admin hw_docker_password
  4. We will build and start the containers:
    docker-compose up -d
  5. And we need to redirect Nginx to our containers. This can be done through Plesk.

The following actions need to be performed for the subdomains docker.helloworld.ru and docker-ui.helloworld.ru:

In the section Dev Tools of our website, we go to Docker Proxy Rules:

CI/CD in Github Actions for a project on Flask+Angular
And we add a rule for proxying incoming traffic to our container:

CI/CD in Github Actions for a project on Flask+Angular

  1. We check that we can authenticate in our container from the local machine:
    $ 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. We will also check the operation of the subdomain docker-ui.helloworld.ru:

    CI/CD in Github Actions for a project on Flask+Angular
    When you click on Browse repositories, the browser will display a login window where you need to enter the login and password for the repository. After that, you will be taken to a page with a list of repositories (it will be empty for you for now):

    CI/CD in Github Actions for a project on Flask+Angular

Opening Ports in Plesk Firewall

After installing and configuring the components, we need to open ports to make the components accessible from Docker containers and the external network.

Let's see how to do this using the example of the Firewall extension we installed earlier for Plesk.

  1. Navigate to Tools & Settings > Settings > Firewall:
    CI/CD in Github Actions for a project on Flask+Angular
  2. Navigate to Modify Plesk Firewall Rules > Add Custom Rule and open the following TCP ports for the Docker subnet (172.0.0.0/8):
    RabbitMQ: 1883, 4369, 5671-5672, 25672, 61613-61614
    Redis: 32785, 32786

    CI/CD in Github Actions for a project on Flask+Angular

  3. We will also add a rule to open the PostgreSQL ports and RabbitMQ management panel to the outside world:

    CI/CD in Github Actions for a project on Flask+Angular

  4. Apply the rules using the Apply Changes button:

    CI/CD in Github Actions for a project on Flask+Angular

Setting Up CI/CD in Github Actions

Let's move on to the most interesting part β€” configuring the continuous integration and delivery pipeline for our project to the server.

This pipeline will consist of two parts:

  • building the image and running tests (for the backend) β€” on the Github side;
  • running migrations (for the backend) and deploying containers β€” on the server.

Deployment in Plesk

Let's first address the second point (since the first depends on it).

We will configure the deployment process using the Git extension for Plesk.

Let's consider an example with the Prod environment for the Backend repository.

  1. Go to the subscription for our Helloworld site and navigate to the Git section:

    CI/CD in Github Actions for a project on Flask+Angular

  2. In the 'Remote Git repository' field, paste the link to our Github repository and change the default folder httpdocs to another one (for example, /httpdocs/hw_back):

    CI/CD in Github Actions for a project on Flask+Angular

  3. Copy the SSH Public key from the previous step and add add it in the Github settings.
  4. Click OK on the screen in step 2, after which you will be redirected to the repository page in Plesk. Now we need to set up the repository update on commits to the master branch. For this, go to Repository Settings and save the value Webhook URL (we will need it later when setting up Github Actions):

    CI/CD in Github Actions for a project on Flask+Angular

  5. In the Actions field on the screen from the previous step, enter the script to trigger the deployment:
    cd {REPOSITORY_ABSOLUTE_PATH}
    .ci-cd/deploy.sh {ENV} {DOCKER_REGISTRY_HOST} {DOCKER_USER} {DOCKER_PASSWORD} {TG_BOT_TOKEN} {TG_CHAT_ID} 

    where:

    {REPOSITORY_ABSOLUTE_PATH} β€” path to the prod backend repository folder on the server;
    {ENV} β€” environment (dev/prod), in our case prod;
    {DOCKER_REGISTRY_HOST} β€” host of our docker repository
    {TG_BOT_TOKEN} β€” Telegram bot token;
    {TG_CHAT_ID} β€” chat/channel ID for sending notifications.

    Example script:

    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. Adding a user from our subscription to the Docker group (so they can manage containers):
    sudo usermod -aG docker helloworld_admin

The Dev environment for the backend repository and frontend is configured similarly.

Deployment pipeline in Github Actions

Let's move on to setting up the first part of our CI/CD pipeline in Github Actions.

Backend

The pipeline is described in the deploy.yml file.

But before we dissect it, let's fill in the necessary Secret variables in Github. For this, go to Settings -> Secrets:

  • DOCKER_REGISTRY β€” the host of our Docker repository (docker.helloworld.ru);
  • DOCKER_LOGIN β€” the login to the Docker repository;
  • DOCKER_PASSWORD β€” the password for it;
  • DEPLOY_HOST β€” the host where the Plesk admin panel is available (example: helloworld.ru:8443 or 123.4.56.78:8443);
  • DEPLOY_BACK_PROD_TOKEN β€” the token for deploying to the prod repository on the server (we obtained it in Deployment in Plesk paragraph 4);
  • DEPLOY_BACK_DEV_TOKEN β€” the token for deploying to the dev repository on the server.

The deployment process is simple and consists of three main steps:

  • building and publishing the image in our repository;
  • running tests in a container based on the freshly built image;
  • deploying to the necessary environment depending on the branch (dev/master).

Frontend

The deploy.yml file for the frontend repository doesn't differ much from the backend one. It lacks the step for running tests and the names of the tokens for deployment are changed. By the way, secrets for the frontend repository need to be filled in separately.

Configuring the site

Proxying traffic through Nginx

Well, we have reached the end. Now we just need to set up proxying of incoming and outgoing traffic to our container through Nginx. We have already covered this process in section 5 of the Docker Registry setup. The same needs to be repeated for the backend and frontend in both dev and prod environments.

I will provide screenshots of the settings.

Backend

CI/CD in Github Actions for a project on Flask+Angular

Frontend

CI/CD in Github Actions for a project on Flask+Angular
Important clarification. In the frontend container, all URLs will be proxied except those starting with /api/ β€” they will be proxied to the backend container (therefore, in the backend container, all handlers must start with /api/).

Summary

Now our site should be accessible at helloworld.ru and dev.helloworld.ru (prod and dev environments respectively).

In summary, we learned how to prepare a simple application using Flask and Angular and set up a pipeline in Github Actions for deploying it to a server managed by Plesk.

I will duplicate the links to the repositories with the code: backend, frontend.

Source: habr.com

Buy reliable website hosting with DDoS protection, VPS VDS servers πŸ”₯ Buy reliable website hosting with DDoS protection, VPS VDS servers | ProHoster