Monday 24 April 2017

Building a Docker Container To Sandbox Python WSGI Apps

Let's begin!

Creating a Base Docker Container From Ubuntu

Using docker's RUN command, we will begin with creating a new container based on the Ubuntu image. We are going to attach a terminal to it using the -t flag and will have bash as the running process.
We are going to expose port 80 so that our application will be accessible from the outside. In future, you might want to load-balance multiple instances and "link" containers to each other to access them using a reverse-proxy running container, for example.
sudo docker run -i -t -p 80:80 ubuntu /bin/bash
Note: After executing this command, docker might need to pull the Ubuntu image before creating a new container for you.
Remember: You will be attached to the container you create. In order to detach yourself and go back to your main terminal access point, run the escape sequence: CTRL+P followed by CTRL+Q. Being attached to a docker container is like being connected to a new droplet from inside another.
To attach yourself back to this container:
  1. List all running containers using "sudo docker ps"
  2. Find its ID
  3. Use "sudo docker attach [id]" to attach back to its terminal
Important: Please do not forget that since we are in a container, all the following commands will be executed there, without affecting the host it resides.

Preparing the Base Container for the Installation

In order to deploy Python WSGI web applications inside a container - and the tools we need for the process - the relevant application repositories must be available for the downloads. Unfortunately (and intentionally to keep things simple) this is not the case with the default Ubuntu image that comes with docker.
Let's append Ubuntu's universe repository to the default list of application sources list of the base image.
echo "deb http://archive.ubuntu.com/ubuntu/ $(lsb_release -sc) main universe" >> /etc/apt/sources.list
Update the list with the newly added source.
apt-get update
Before we proceed with setting up Python WSGI applications, there are some tools we should have such as nano, tar, curl, etc. - just in case.
Let's download some useful tools inside our container.
apt-get install -y tar \
                   git \
                   curl \
                   nano \
                   wget \
                   dialog \
                   net-tools
                   build-essential

Installing Common Python Tools for Deployment

For our tutorial (as an example), we are going to create a very basic Flask application. After following this article, you can use and deploy your favorite framework instead, the same way you would deploy it on a virtual server.
Remember: All the commands and instructions below still take place inside a container, which acts almost as if it is a brand new droplet of its own.
Let's begin our deployment process with installing Python and pip the Python package manager:
# Install pip's dependency: setuptools:
apt-get install -y python python-dev python-distribute python-pip

Installing The Web Application and Its Dependencies

Before we begin with creating a sample application, we better make sure that everything - i.e. all the dependencies - are there. First and foremost, you are likely to have your Web Application Framework (WAF) as your application's dependency (i.e. Flask).
As we have pip installed and ready to work, we can use it to pull all the dependencies and have them set up inside our container:
# Download and install Flask framework:
pip install flask
After installing pip, let's create a basic, sample Flask application inside a "my_application" folder which is to contain everything.
# Make a my_application folder
mkdir my_application

# Enter the folder
cd my_application 
Note: If you are interested in deploying your application instead of this simple-sample example, see the "Quick Tip" mid-section below.
Let's create a single, one page flask "Hello World!" application using nano.
# Create a sample (app.py) with nano:
nano app.py
And copy-and-paste the contents below for this small application we have just mentioned:
from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World!"

if __name__ == "__main__":
    app.run()
Press CTRL+X and approve with Y to save and close.
Alternatively, you can use a "requirements.txt" to contain your application's dependencies such as Flask.
To create a requirements.txt using nano text editor:
nano requirements.txt
And enter the following inside, alongside all your dependencies:
flask
cherrypy
Press CTRL+X and approve with Y to save and close.
Note: You can create your a list of your actual application's dependencies using pip. To see how, check out our tutorial Common Python Tools: Using virtualenv, Installing with Pip, and Managing Packages.
Our final application folder structure:
/my_application
    |
    |- requirements.txt  # File containing list of dependencies
    |- /app              # Application module (which should have your app)
    |- app.py            # WSGI file containing the "app" callable
    |- server.py         # Optional: To run the app servers (CherryPy)
Note: Please see the following section regarding the "server.py" - Configuring your Python WSGI Application .
Remember: This application folder will be created inside the container. When you are automatically building images (see the following section on Dockerfiles), you will need to make sure to have this structure on the host, alongside the Dockerfile.
__ * Quick tip for actual deployments * __

How to get your application repository and its requirements inside a container

In the above example, we created the application directory inside the container. However, you will not be doing that to deploy your application. You are rather likely to pull its source from a repository.
There are several ways to copy your repository inside a container.
Below explained are two of them:
# Example [1]
# Download the application using git:
# Usage: git clone [application repository URL]
# Example:
git clone https://github.com/mitsuhiko/flask/tree/master/examples/flaskr

# Example [2]
# Download the application tarball:
# Usage: wget [application repository tarball URL]
# Example: (make sure to use an actual, working URL)
wget http://www.github.com/example_usr/application/tarball/v.v.x

# Expand the tarball and extract its contents:
# Usage: tar vxzf [tarball filename .tar (.gz)]
# Example: (make sure to use an actual, working URL)
tar vxzf application.tar.gz

# Download and install your application dependencies with pip.
# Download the requirements.txt (pip freeze output) and use pip to install them all:
# Usage: curl [URL for requirements.txt] | pip install -r -
# Example: (make sure to use an actual, working URL)
curl http://www.github.com/example_usr/application/requirements.txt | pip install -r -

Configuring your Python WSGI Application

To serve this application, you will need a web server. The web server, which powers the WSGI app, needs to be installed in the same container as the application's other resources. In fact, it will be the process that docker runs.
Note: In this example, we will use CherryPy's built-in production ready HTTP web server due to its simplicity. You can use Gunicorn, CherryPy or even uWSGI (and set them up behind Nginx) by following our tutorials on the subject.
Download and install CherryPy with pip:
pip install cherrypy
Create a "server.py" to serve the web application from "app.py":
nano server.py
Copy and paste the contents from below for the server to import your application and start serving it:
# Import your application as:
# from app import application
# Example:

from app import app

# Import CherryPy
import cherrypy

if __name__ == '__main__':

    # Mount the application
    cherrypy.tree.graft(app, "/")

    # Unsubscribe the default server
    cherrypy.server.unsubscribe()

    # Instantiate a new server object
    server = cherrypy._cpserver.Server()

    # Configure the server object
    server.socket_host = "0.0.0.0"
    server.socket_port = 80
    server.thread_pool = 30

    # For SSL Support
    # server.ssl_module            = 'pyopenssl'
    # server.ssl_certificate       = 'ssl/certificate.crt'
    # server.ssl_private_key       = 'ssl/private.key'
    # server.ssl_certificate_chain = 'ssl/bundle.crt'

    # Subscribe this server
    server.subscribe()

    # Start the server engine (Option 1 *and* 2)

    cherrypy.engine.start()
    cherrypy.engine.block()
And that's it! Now you can have a "dockerized" Python web application securely kept in its sandbox, ready to serve thousands and thousands of client requests by simply running:
python server.py
This will run the server on the foreground. If you would like to stop it, press CTRL+C.
To run the server in the background, run the following:
python server.py &
When you run an application in the background, you will need to use a process manager (e.g. htop) to kill (or stop) it.
Note: To learn more about configuring Python WSGI applications for deployment with CherryPy, check out our tutorial: How to deploy Python WSGI apps Using CherryPy Web Server
To test that everything is running smoothly, which they should given that all the port allocations are already taken care of, you can visit http://[your droplet's IP] with your browser to see the "Hello World!" message.

No comments:

Post a Comment

User Interface(UI) for Docker, Portainer

Portainer gives you access to a central overview of your Docker host or Swarm cluster. From the dashboard, you can easily access any manag...