Ansible: Up and Running (2015)
Chapter 5. Introducing Mezzanine: Our Test Application
In Chapter 2, we covered the basics of writing playbooks. But real life is always messier than introductory chapters of programming books, so we’re going to work through a complete example of deploying a non-trivial application.
Our example application is an open source content management system (CMS) called Mezzanine, which is similar in spirit to WordPress. Mezzanine is built on top of Django, the free Python-based framework for writing web applications.
Why Deploying to Production Is Complicated
Let’s take a little detour and talk about the differences between running software in development mode on your laptop versus running the software in production.
Mezzanine is a great example of an application that is much easier to run in development mode than it is to deploy. Example 5-1 shows all you need to do to get Mezzanine running on your laptop.1
Example 5-1. Running Mezzanine in development mode
$ virtualenv venv
$ source venv/bin/activate
$ pip install mezzanine
$ mezzanine-project myproject
$ cd myproject
$ python manage.py createdb
$ python manage.py runserver
You’ll be prompted to answer several questions. I answered “yes” to each yes/no question, and accepted the default answer whenever one was available. This was what my interaction looked like:
You just installed Django's auth system, which means you don't have any
Would you like to create one now? (yes/no): yes
Username (leave blank to use 'lorinhochstein'):
Email address: firstname.lastname@example.org
Superuser created successfully.
A site record is required.
Please enter the domain and optional port in the format 'domain:port'.
For example 'localhost:8000' or 'www.example.com'.
Hit enter to use the default (127.0.0.1:8000):
Creating default site record: 127.0.0.1:8000 ...
Installed 2 object(s) from 1 fixture(s)
Would you like to install some initial demo pages?
Eg: About us, Contact form, Gallery. (yes/no): yes
You should eventually see output on the terminal that looks like this:
.d' `b. * Mezzanine 3.1.10
:: :: * Django 1.6.8
:: M E Z Z A N I N E :: * Python 2.7.6
:: :: * SQLite 3.8.5
`p. .q' * Darwin 14.0.0
0 errors found
December 01, 2014 - 02:54:40
Django version 1.6.8, using settings 'mezzanine-example.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
If you point your browser to http://127.0.0.1:8000/, you should see a web page that looks like Figure 5-1.
Figure 5-1. Mezzanine after a fresh install
Deploying this application to production is another matter. When you run the mezzanine-project command, Mezzanine will generate a Fabric deployment script at myproject/fabfile.py that you can use to deploy your project to a production server. (Fabric is a Python-based tool that helps automate running tasks via ssh.) The script is over 500 lines long, and that’s not counting the included configuration files that are also involved in deployment. Why is deploying to production so much more complex? I’m glad you asked.
When run in development, Mezzanine provides the following simplifications (see Figure 5-2):
§ The system uses SQLite as the back-end database, and will create the database file if it doesn’t exist.
§ The development HTTP server uses the (insecure) http protocol, not (secure) HTTPS.
§ The development HTTP server process runs in the foreground, taking over your terminal window.
§ The hostname for the HTTP server is always 127.0.0.1 (localhost).
Figure 5-2. Django app in development mode
Now, let’s look at what happens when you deploy to production.
PostgreSQL: The Database
SQLite is a serverless database. In production, we want to run a server-based database, because those have better support for multiple, concurrent requests, and server-based databases allow us to run multiple HTTP servers for load balancing. This means we need to deploy a database management system such as MySQL or PostgreSQL (aka simply “Postgres”). Setting up one of these database servers requires more work. We need to:
1. Install the database software.
2. Ensure the database service is running.
3. Create the database inside the database management system.
4. Create a database user who has the appropriate permissions for the database system.
5. Configure our Mezzanine application with the database user credentials and connection information.
Gunicorn: The Application Server
Because Mezzanine is a Django-based application, you can run Mezzanine using Django’s HTTP server, referred as the development server in the Django documentation. Here’s what the Django 1.7 docs have to say about the development server.
[D]on’t use this server in anything resembling a production environment. It’s intended only for use while developing. (We’re in the business of making Web frameworks, not Web servers.)
Django implements the standard Web Server Gateway Interface (WSGI),2 so any Python HTTP server that supports WSGI is suitable for running a Django application such as Mezzanine. We’ll use Gunicorn, one of the most popular HTTP WSGI servers, which is what the Mezzanine deploy script uses.
Nginx: The Web Server
Although Gunicorn can handle TLS encryption, it’s common to configure nginx to handle the encryption.3
We’re going to use nginx as our web server for serving static assets and for handling the TLS encryption, as shown in Figure 5-3. We need to configure nginx as a reverse proxy for Gunicorn. If the request is for a static asset, such as a css file, then nginx will serve that file directly from the local file system. Otherwise, nginx will proxy the request to Gunicorn, by making an http request against the Gunicorn service that is running on the local machine. Nginx uses the URL to determine whether to serve a local file or proxy the request to Gunicorn. Note that requests to nginx will be (encrypted) HTTPS, and all requests that nginx proxies to Gunicorn will be (unencrypted) HTTP.
Figure 5-3. Nginx as a reverse proxy
Supervisor: The Process Manager
When we run in development mode, we run the application server in the foreground of our terminal. If we were to close our terminal, the program would terminate. For a server application, we need it to run as a background process so it doesn’t terminate, even if we close the terminal session we used to start the process.
The colloquial terms for such a process are daemon or service. We need to run Gunicorn as a daemon, and we’d like to be able to easily stop it and restart it. There are a number of service managers that can do this job. We’re going to use Supervisor, because that’s what the Mezzanine deployment scripts use.
At this point, you should have a sense of the steps involved in deploying a web application to production. We’ll go over how to implement this deployment with Ansible in Chapter 6.
1 This will install the Python packages into a virtualenv. We’ll cover virtualenvs in “Installing Mezzanine and Other Packages into a virtualenv”.
2 The WSGI protocol is documented in Python Enhancement Proposal (PEP) 3333.
3 Gunicorn 0.17 added support for TLS encryption. Before that you had to use a separate application such as nginx to handle the encryption.