Building a Python scalable Flask application using docker-compose and Nginx load balancer
Itay Melamed
DevOps Cloud Consultant at Amazon Web Services (AWS) | 4x AWS Certified
Introduction
Docker is a tool designed to make it easier to create, deploy, and run applications by using containers. Containers allow a developer to package up an application with all of the parts it needs, such as libraries and other dependencies, and ship it all out as one package. By doing so, thanks to the container, the developer can rest assured that the application will run on any other Linux machine regardless of any customized settings that machine might have that could differ from the machine used for writing and testing the code.
Docker-Compose is a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application’s services. Then, with a single command, you create and start all the services from your configuration.
Flask is a lightweight WSGI web application framework. It is designed to make getting started quick and easy, with the ability to scale up to complex applications. It began as a simple wrapper around Werkzeug and Jinja and has become one of the most popular Python web application frameworks.
Nginx is an open-source HTTP Web server and reverses proxy server. Nginx can be configured as a simple yet powerful load balancer to improve your servers resource availability and efficiency. In a load balancing configuration, Nginx acts as a single entry point to a distributed web application working on multiple separate servers.
In this tutorial, you will create a Flask service and deploy it with docker-compose. in addition, you will create an Nginx load balancer and serve your flask application with it.
Step 1 — Creating the Flask Application
First, Let’s create our project directory by opening the terminal and run the following command:
$ mkdir flask_tutorial
Navigate to this directory by running:
$ cd flask_tutorial
Now let’s create our flask app directory:
$ mkdir app
Move into the newly created app directory:
$ cd app
Create a python file app.py, and add the following code snippet to it:
from flask import Flask app = Flask(__name__) @app.route('/ping') def ping(): return 'PONG', 200 if __name__ == '__main__': app.run('0.0.0.0', debug=True)
In the above code snippet, we created a basic flask server with the route /ping. The server will return a response with “PONG” for GET requests to /ping route.
Create a file named requirements.txt and add to it:
flask
Run the following command:
$ pip3 install -r requirements.txt
Now, let’s check our service, run the following command:
$ python3 app.py
Open another terminal window and execute the following command:
$ curl https://localhost:5000/ping
If everything is correct you should get “PONG” as a response.
Step 2 — Dockerizing our flask application
In order to create a docker container with our app running in it, we should build a docker image. We will do it by creating a Dockerfile. A Dockerfileis a text document that contains all the commands a user could call on the command line to assemble an image. Run the following command:
$ nano Dockerfile
Add the following code to it:
# using a python small basic image FROM python:alpine # exposing our app port in docker internal network EXPOSE 5000 # creates a dir for our application WORKDIR /app # copy our requirements.txt file and install dependencies COPY requirements.txt . RUN pip install -r requirements.txt # copy the rest of our application COPY . . # run the application CMD python app.py
Note that in our Dockerfile we first copied the requirements.txt and installs the dependencies and only then copy the rest of our code. We do that in order to make our builds faster thanks to Docker cache mechanism. The installation of our dependencies would be executed only when there is a change in our requirements.txt file.
now, let's create a .dockerignore file, this file function same as .gitignore file, it will prevent our venv folder to be added to our app image:
$ nano .dockerignore
Add to the .dockerignore file:
venv
Step 2 — Building NGINX Server
Now, we will create a docker image of our Nginx server.
Go to our project root directory:
$ cd ..
Create a directory named Nginx:
$ mkdir nginx
Go to Nginx directory:
$ cd nginx
Let's create our Nginx servers’ configuration file:
Create a new file named nginx.conf:
$ nano nginx.conf
Add to it the following configurations:
events {} # Define which servers to include in the load balancing scheme. http { upstream app { server app; server flask_tutorial_app_1:5000; server flask_tutorial_app_2:5000; } # This server accepts all traffic to port 80 and passes it to the upstream. server { listen 80; server_name app.com; location / { proxy_pass https://app; } } }
Load balancing with Nginx uses a round-robin algorithm by default if no other method is defined. With round-robin scheme each server is selected in turns according to the order we set them in the Nginx.conf file. This balances the number of requests equally between our to two app instances. We will discuss later in this tutorial how our Nginx server know to communicate with our application using the hostname: flask_tutorial_app_<: number>
After we have created the Nginx config file, let's create a docker image. Run the following command:
nano Dockerfile
Add to it the following code:
# using Nginx base image FROM nginx # delete nginx default .conf file RUN rm /etc/nginx/conf.d/default.conf # add the .conf file we have created COPY nginx.conf /etc/nginx/nginx.conf
Step 3— Configure our services containers using docker-compose
Go to our project root directory:
$ cd ..
Create a file named docker-compose.yml:
$ nano docker-compose.yml
Now we will add the configurations of our service and Nginx server containers by adding to the yml file the following code:
version: '3.7' services: app: build: app nginx: container_name: nginx build: nginx ports: - 80:80 depends_on: - app
In the above file, we tell docker to build our flask app using the dockerfile located in the ‘app’ directory. Then we tell him to build a container for our Nginx server and to expose it to port 80. This way the server could be accessible by the clients.
Docker-Compose API creates an internal network in which the containers can communicate with each other using the container’s name. For example, the Nginx container can interact with the flask app container by using the following hostname: https://flask_tutorial_app:5000/ping (The given name for each container by docker-compose is in this format: <project directory name>_<service name>). that's why in the above steps when we have configured our Nginx server we could set our app endpoint to https://flask_tutirlal_app_<instance number>:5000.
Step 4— Deploy and Scale our Flask application
Now, let's use docker-compose to deploy and scale our application. Run the following command:
$ docker-compose up --build -d --scale app=2
Docker-compose creates a container with our Nginx server and two instances of our flask application.
Let's check everything is running by running the following command:
$ docker ps --format '{{.Names}}'
We need to see the following running containers:
nginx flask_tutorial_app_2 flask_tutorial_app_1
Now, let's check our load balance works.
Open two separate terminal windows:
In the first terminal run:
$ docker logs flask_tutorial_app_1 -f
In the second:
$ docker logs flask_tutorial_app_2 -f
Now, open your browser and navigate to https://localhost/ping and look at the logs of the first app (flask_tutorial_app_1)
You will see that this request was redirected to it.
Refresh your browser and look at the logs of the second app instance (flask_tutorial_app_2). The request was redirected to it. The next request would be redirected again to the first app and etc.
Senior Engine/Gameplay Programmer – 4A Games
3 年Thank you. It was very helpful
Automation Infrastructure Team Leader @MyHeritage
5 年Excellent writing.