In a previous post we learned how to setup a LEMP guest machine with Vagrant and Ansible to test PHP applications. However, in this day and age, there are other programming languages that can be used for web development other than PHP.

Python is a very popular beginner-friendly programming language that will allow us to create web applications with ease due to its multiple third party modules and packages.

Unlike PHP, Python was not born as a server-side scripting language designed for web development and it’s not as intertwined with web servers (such as Apache). For this reason, it requires some kind of module or gateway to work.

For this post, we will be installing uWSGI, which is a Web Server Gateway Interface, inside our Vagrant guest machine and connect Nginx to it.

Creating a test application

We are going to create a very simple test application using Flask.

Flask is a micro web framework written in Python and based on the Werkzeug toolkit and Jinja2 template engine.

Wikipedia

A web framework is a great tool that makes developing Python web applications much easier.

We will save the configuration files for our application in the subdirectory vagrant/www/test:

vagrant/www/test
├── requirements.txt
├── test.ini
└── test.py

Requirements file

A virtual environment is a self-contained directory tree that contains a Python installation for a particular version of Python, plus a number of additional packages.

Python documentation

Virtual environments give us the possibility to isolate each Python application. This way, we can use PIP to install different versions of packages without conflicting with versions of the same package for other applications.

And how do we tell PIP which packages and which versions to install? Well, by using a requirements file for each application.

Since our test application only needs Flask, we will add it to requirements.txt:

Flask>=0.12

uWSGI’s configuration file

To tell uWSGI how to launch our application, we will set some parameters in the file test.ini that will be loaded by uWSGI on boot:

[uwsgi]
plugins = python3
socket = /tmp/test.sock
venv = /opt/virtualenvs/test
chdir = /vagrant/www/test
wsgi-file = test.py
callable = app

Here we specify the version of Python to use, the socket file, the path of the virtual environment, the directory for our application’s files, the file that contains the application, and what object to call.

Python application

The web application per se will be in test.py:

#!/usr/bin/env python3

from flask import Flask

app = Flask(__name__)


@app.route('/test')
def test():
    return '<p style="background: aliceblue;">Hello Wold</p>\n'


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8000)

This just will send the text Hello World (on a blue background) to the web browser when we connect to the server.

Configuration of Ansible

Now, we have to modify our previous configuration for Ansible so it installs all the necessary packages and loads the appropriate configuration files.

Global variables

First of all, since we will be using some variables that are common to more than one role, let’s create a file to store these global variables. In the subdirectory vagrant/cfg/group_vars we create a file called all.yml for all roles:

---
base_dir:   '/vagrant/www'
venv_dir:   '/opt/virtualenvs'

We specify the directory that Nginx will use as root and the directory where Python’s virtual environments will be created.

In this same file, we will include the name/directory of our applications:

apps:
  - name: test

Python role

Now, let’s add a new role to our configuration so all the appropriate Python dependencies are met. We will save the files in the subdirectory vagrant/cfg/roles/python:

vagrant/cfg/roles/python
├── handlers
│   └── main.yml
└── tasks
    └── main.yml

Tasks are saved in tasks/main.yml:

---
- name: Install Python
  package: name={{ item }} state=present
  with_items:
    - python3-pip
    - python3-venv
    - uwsgi
    - uwsgi-plugin-python3
  notify:
    - start uwsgi

- name: Install PIP packages
  pip:
    requirements: '{{ base_dir }}/{{ item.name }}/requirements.txt'
    virtualenv: '{{ venv_dir }}/{{ item.name }}'
    virtualenv_command: pyvenv
  with_items:
    - '{{ apps }}'

- name: Link uWSGI file
  file:
    src: '{{ base_dir }}/{{ item.name }}/{{ item.name }}.ini'
    dest: '/etc/uwsgi/apps-enabled/{{ item.name }}.ini'
    force: yes
    state: link
  with_items:
    - '{{ apps }}'
  notify:
    - restart uwsgi

The steps are:

  1. Using the package module, we install the Python 3 packages for PIP and venv along with uWSGI and its Python 3 plugin.
  2. Then, using the pip module, we read the file requirements.txt for each application and install its packages in a virtual environment.
  3. We then enable the application by adding a symbolic link to the application’s INI file in the directory /etc/uwsgi/apps-enabled using the file module.

And let’s not forget the handlers to enable and restart the service when needed. Add these to the file handlers/main.yml:

---
- name: start uwsgi
  service: name=uwsgi enabled=yes state=started

- name: restart uwsgi
  service: name=uwsgi state=restarted

Finally, we modify vagrant/cfg/site.yml to add this new role:

---
- name: Configure LEMP server
  hosts: lemp
  roles:
    - mariadb
    - php
    - python
    - nginx

Nginx role

We also need to modify the template for our Nginx role in vagrant/cfg/roles/nginx so it loads our applications.

For this, we edit templates/default and add the following inside the server directive:

{% for item in apps %}
  location /{{ item.name }} {
    include uwsgi_params;
    uwsgi_pass unix:/tmp/{{ item.name }}.sock;
  }
{% endfor %}

This will loop through each of our applications and configure Nginx to use uWSGI for its corresponding subpath.

Running the test application

We can now start the guest machine with Vagrant using the command vagrant up. After the machine has finished booting, we will be able to open our test application by pointing our web browser to http://172.28.128.10/test:

Test application

Conclusion

Using Python as the backend for our web applications is not as straightforward as throwing some PHP code in an index.php file but, with some tweaking, we can have a working application and have access to the power of Python packages.

Further reading