Scale your Flask Python Web Application with Docker and HAProxy

Fri 24 June 2016

For the last few months I was using Docker quite intensively for my projects and I really like it. In this post I will just describe the necessary steps to deploy a minimal Flask python application and scale it using docker-compose and HAProxy.

So, here is a diagram with what I plan to deploy.

ServicesDiagram

I am using a Vultr compute instance with Docker on CentOS 7. After the instance is up and running let's test if docker is working:

vultr-running

# docker run hello-world

docker-initial-test

  • it seems that is working, so let's install docker-compose
# yum install python-pip
# pip install docker-compose
  • if we try to run "docker-compose" command will give us an error

docker-compose-install-error

  • we need to install one more python module to have everything working
# pip install --upgrade backports.ssl_match_hostname
  • test the installation by running a simple hello world using docker-compose. We have to build a docker-compose.yml file with the following content:
my-test:
    image: hello-world
  • let's bring the container up and running
# docker-compose up

docker-compose-example-output

  • good, working perfectly. Time to prepare the files for our flask python application and haproxy.

  • Dockerfile - the file is needed to pull a minimal Flask application from my GitHub repository and run it as a docker container

FROM ubuntu:14.04
RUN apt-get update \
    && apt-get install -y python-pip \
    && apt-get install -y git
WORKDIR /myapp
RUN git clone https://github.com/vioan/minflask.git .
RUN pip install -r requirements.txt
CMD python app.py
EXPOSE 5000
  • let's try to see if our app is really running inside the docker container

  • for that we need to build first the image

# docker build .
  • list our available images and see if the new one was created
# docker images

docker-list-built-image

  • run the new created image by passing its "IMAGE ID" to our docker run command. We make sure that the port 5000 which is running inside our container, is mapped to port 80 on host
# docker run -it -p 80:5000 d73fa1b9c33

docker-run-image

  • open the browser and type the host ip address

docker-browser-output-test

  • is working. Let's move on ...

  • docker-compose.yml - the file which put together and link different services/containers (in our case python app and haproxy load balancer)

pyapp:
  build: .
loadbalancer:
  image: 'dockercloud/haproxy:latest'
  links:
    - pyapp
  volumes:
    - /var/run/docker.sock:/var/run/docker.sock
  ports:
    - 80:80
  • time to run our services together:
# docker-compose up -d
  • check if they are running
# docker-compose ps

docker-compose-test-noscalling2

  • yupiiiii, working. But wait, we have only one service corresponding to our python application. Let's scale our python application (e.g. run 5 instances)
# docker-compose scale pyapp=5

docker-scalling1

  • let's check if indeed we have 5 containers for our python app and one container for our load balancer
# docker-compose ps

docker-scalling2

  • yes, we have all services as we expected. But there is one more thing to do. After we scaled our python application we need to inform HAProxy about the new containers. You can do that in different ways but I did it like this:
# docker-compose stop loadbalancer
# docker-compose rm loadbalancer
# docker-compose up -d
  • so, basically, stopping, removing and restarting the load balancer container. Docker-compose knows that the other services don't have to be updated and will restart only our loadbalancer
microservices_pyapp_4 is up-to-date
microservices_pyapp_2 is up-to-date
microservices_pyapp_3 is up-to-date
microservices_pyapp_5 is up-to-date
microservices_pyapp_1 is up-to-date
Creating microservices_loadbalancer_1
  • now we can test to see if HAProxy is distributing the requests in Round-Robin mode. I used curl for that but of course you can use your browser as well and refresh the page to see that the hostname change which means that our python application is running in a different container.
[root@vioan microservices]# for request in `seq 1 5`; do curl http://45.32.187.37; done
Hello from FLASK. My Hostname is: 6f96511879bb
Hello from FLASK. My Hostname is: d860bfd42116
Hello from FLASK. My Hostname is: 1cd07155d7cd
Hello from FLASK. My Hostname is: e99bb7d73451
Hello from FLASK. My Hostname is: 5a9631644e16
[root@vioan microservices]#
  • that's all. Was not difficult but indeed this is just a minimal application without even a database. Perhaps in a following post I will describe how to build a more complete infrastructure using docker-compose

Note: Here is my repository with the files: GitHub