Chapter 2 covered the basics of writing playbooks. But real life is always messier than the introductory chapters of programming books, so in this chapter we’re going to work through a complete example of deploying a nontrivial application.
Our example application is an open-source content management system (CMS) called Mezzanine (http://mezzanine.jupo.org), which is similar in spirit to WordPress. Mezzanine is built on top of Django, the free Python-based framework for writing web applications.
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 a provisioning script to get Mezzanine running on Ubuntu Focal/64.1
sudo apt-get install -y python3-venv python3 -m venv venv source venv/bin/activate pip3 install wheel pip3 install mezzanine mezzanine-project myproject cd myproject sed -i 's/ALLOWED_HOSTS = []/ALLOWED_HOSTS = ["*"]/' myproject/settings.py python manage.py migrate python manage.py runserver 0.0.0.0:8000
You should eventually see output on the terminal that looks like this:
..... _d^^^^^^^^^b_ .d'' ``b. .p' `q. .d' `b. .d' `b. * Mezzanine 4.3.1 :: :: * Django 1.11.29 :: M E Z Z A N I N E :: * Python 3.8.5 :: :: * SQLite 3.31.1 `p. .q' * Linux 5.4.0-74-generic `p. .q' `b. .d' `q.. ..p' ^q........p^ '''' Performing system checks... System check identified no issues (0 silenced). June 15, 2021 - 19:24:35 Django version 1.11.29, using settings 'myproject.settings' Starting development server at http://0.0.0.0: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.
Deploying this application to production is another matter. When you run the mezzanine-project
command, Mezzanine will generate a Fabric (http://www.fabfile.org) 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 almost 700 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 backend database and will create the database file if it doesn’t exist.
The development HTTP server serves up both the static content (images, .css files, JavaScript) as well as the dynamically generated HTML.
The development HTTP server uses the (insecure) HTTP, 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
).
Now, let’s look at what happens when you deploy to production.
SQLite is a serverless database. In production, you 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 you need to deploy a database management system, such as MySQL or PostgreSQL (aka Postgres). Setting up one of these database servers requires more work. You’ll need to do the following:
Install the database software.
Ensure the database service is running.
Create the database inside the database management system.
Create a database user who has the appropriate permissions for the database system.
Configure the Mezzanine application with the database user credentials and connection information.
Because Mezzanine is a Django-based application, you can run it using Django’s HTTP server, referred to as the development server in the Django documentation. Here’s what the Django 1.11 docs have to say about the development server:
Don’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. Also note that Mezzanine uses an insecure version of Django that is no longer supported.
Gunicorn will execute our Django application, just like the development server does. However, Gunicorn won’t serve any of the static assets associated with the application. Static assets are files such as images, .css files, and JavaScript files. They are called static because they never change, in contrast with the dynamically generated web pages that Gunicorn serves up.
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, Nginx will serve that file directly from the local filesystem. 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.
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 stop it and restart it easily. Numerous service managers 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 installs the Python packages into a virtualenv; the online example provisions a Vagrant VM automatically.
2 The WSGI protocol is documented in Python Enhancement Proposal (PEP) 3333 (https://www.python.org/dev/peps/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.
18.216.32.116