Getting started with Django Ansible Deployment
Deploying applications to production is a complex process requiring many configurations and testing. Imagine that you want to deploy a new version of your application to production. It would help if you made sure that the new version is stable and compatible with the existing version.
You would need to repeat the deployment steps for each application version. Manual deployment becomes tiresome if you want to deploy your application to production several times. This is where ansible comes into play. It gives us the ability to automate the deployment process. <!--more--> In this article, we will be deploying a Django application to production. Besides the Django framework and ansible, we will also use the Nginx webserver.
Table of contents
Prerequisites
Before you start this article, you need to have a basic understanding of the following:
Application setup
We will build a budget management application API that will allow us to create, update, and delete a budget.
- Create a new working directory for our project by executing the following command:
mkdir deployment
- Create a new virtual environment:
virtualenv deployment/venv
- Create a new Django project:
django-admin startproject budgetmanager
- Django organizes code into smaller units called apps. We will create a new app called
budget
:django-admin startapp budget
- Since we are going to create RESTful APIS, we will need to install the Django REST framework by:
pip install django-rest-framework
Models
Django models are objects that represent a database table. For example, we will create a budget model mapped to the budgets
table in the database.
In the budget
application, in models.py
, add the following code:
from django.db import models
class Budget(models.Model):
title = models.CharField(max_length=100)
description = models.TextField()
amount = models.PositiveIntegerField()
date_creates = models.DateTimeField(auto_created=True, auto_now_add=True)
last_modified = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
The code snippet above contains the Budget class and its attributes. The __str__
method returns a string representation of the budget object.
Serializers
Django serializers are used to convert a Django model object into a JSON object. Django REST framework provides a way to serialize and deserialize data. We will create a serializer that Django uses to serialize and deserialize the budget model.
- In the
budget
application, create a new file namedserializers.py
. - In the
budget
application, inserializers.py
, add the following code:
from rest_framework import serializers
from budget.models import Budget
class BudgetSerializer(serializers.ModelSerializer):
class Meta:
model = Budget
fields = "__all__"
"__all__"
is a special keyword that represents all the fields of the model.
Views
Django views are used to handle requests from the client. Therefore, we will create a view that will be used to handle the requests from the client.
- In the
budget
application, add the code snippet below in theviews.py
file.
from rest_framework.generics import ListAPIView
from rest_framework.generics import CreateAPIView
from rest_framework.generics import DestroyAPIView
from rest_framework.generics import UpdateAPIView
from budget.models import Budget
from budget.serializer import BudgetSerializer
class ListBudgetAPI(ListAPIView):
"""Retrieves all the budgets available in our database"""
queryset = Budget.objects.all()
serializer_class = BudgetSerializer
class CreateBudgetAPI(CreateAPIView):
"""Creates a new budget in our database"""
queryset = Budget.objects.all()
serializer_class = BudgetSerializer
class UpdateBudgetAPI(UpdateAPIView):
"""Update budget with the id sent through the request url path"""
queryset = Budget.objects.all()
serializer_class = BudgetSerializer
class DeleteBudgetAPI(DestroyAPIView):
"""Deletes a budget whose id has been sent through the url path"""
queryset = Budget.objects.all()
serializer_class = BudgetSerializer
URL configuration
Django views are mapped to URLs. We will create a url configuration file that will be used to map the views to URLs.
- In the
budget
application, create a new file namedurls.py
. - Add the following code snippet to the
urls.py
file.
from django.urls import path
from budget import views
urlpatterns = [
path("", views.ListBudgetAPI.as_view(), name="budget_list"),
path("create/", views.CreateBudgetAPI.as_view(), name="create_budget"),
path("update/<int:pk>/", views.UpdateBudgetAPI.as_view(), name="update_budget"),
path("delete/<int:pk>/", views.DeleteBudgetAPI.as_view(), name="delete_budget")
]
We must always add the url
configurations of the respective application in the main urls.py
file located in the project's root directory.
urlpatterns = [
path('admin/', admin.site.urls),
path('api/v1/budget/', include("budget.urls")),
]
Ansible
Ansible uses yml
files to configure your server. The ansible yml
files are also called playbooks
. Create a new directory named ansible
in the root project folder.
System packages
- In the
ansible
directory, create a new file namedpackages.yml
. - Add the following code snippet to the
packages.yml
file.
---
- hosts: all
gather_facts: no
tasks:
- name: Running apt update
apt: update_cache=yes
- name: Installing required packages
apt: name={{item}} state=present
with_items:
- fail2ban
- git
- python-pip
- rdiff-backup
- libpq-dev
- uwsgi
- uwsgi-plugin-python
- nginx
The above file contains all the system packages that we require for our application to run on the server.
Update
- In the
ansible
directory, create a new file namedupdate.yml
. - Add the following code to the
update.yml
file:
---
- hosts: all
gather_facts: no
tasks:
- name: Runing system update
apt: update_cache=yes
upgrade=safe
register: result
- debug: var=result.stdout_lines
The above playbook will run the apt update
command on the server and run the apt upgrade
command.
Generate SSH key
To make it possible for our server to connect with GitLab and pull the latest code, we need to generate an SSH key. Ansible will use this SSH key to establish a secure connection between our server and GitLab.
- In the
ansible
directory, create a new file namedgenerate_ssh_key.yml
. - Add the code snippet below to the
generate_ssh_key.yml
file created above.
---
- hosts: all
gather_facts: no
tasks:
- name: Create a new system user
user: name=ubuntu generate_ssh_key=yes ssh_key_bits=2048 ssh_key_file=.ssh/id_rsa #creates a ssh key in .ssh/id_rsa file
- name: Read SSH public key
slurp: src=/home/ubuntu/.ssh/id_rsa.pub
register: public_key
- debug: msg="{{ public_key['content'] | b64decode }}"
The above playbook generates an SSH key on our server and stores it in the .ssh/id_rsa.pub
file.
Deployment
- In the
ansible
directory, create a new file nameddeploy.yml
. - Add the code snippet below to the
deploy.yml
file.
---
- hosts: all
become: yes
become_user: ubuntu
gather_facts: no
tasks:
- name: pull branch master
git: repo={{ repo_url }}/{{ repo }}.git
dest={{ repo_dir }}
accept_hostkey=yes
- hosts: all
gather_facts: no
tasks:
- name: install python requirements
pip: requirements={{ repo_dir }}/requirements/production.txt extra_args=--upgrade
- hosts: all
become: yes
become_user: ubuntu
gather_facts: no
environment:
DJANGO_SETTINGS_MODULE: "{{ django_project }}.settings.production"
STATIC_ROOT: "{{ static_dir }}"
tasks:
- name: create static_root dir
file: path={{ static_dir }} state=directory mode=0755
- name: django collectstatic
shell: ./manage.py collectstatic --noinput chdir={{ django_dir }}
- name: django migrate
shell: ./manage.py migrate --noinput chdir={{ django_dir }}
- name: django loaddata
shell: ./manage.py loaddata user chdir={{ django_dir }}
- hosts: all
gather_facts: no
tasks:
- name: uwsgi restart
service: name=uwsgi state=restarted
The configuration above will pull the latest code from GitLab and install the required Python packages. It will also:
- Create the
static_root
directory and run thecollectstatic
command to collect all static files. - Run the
migrate
command to migrate the database. - Load the
user
fixture to populate the database. - Restart the
uwsgi
service.
Repository configuration
We need to configure our server to connect to our repository to pull the latest code.
- In the
ansible
directory, create a new file namedhosts
. - Add the code snippet below to the
hosts
file created above.
all:
hosts: yourserver
vars:
repo_url: ssh://[email protected]/user
repo: project
home_dir: /home/ubuntu
repo_dir: "{{ home_dir }}/{{ repo }}"
django_dir: "{{ repo_dir }}/django"
django_project: project
- Change the
yourserver
to your server name andrepo_url
to your repository url.
Ansible shell command
To pull the code from the repository to the server, we need a shell command that reads the configuration from the hosts
file and uses the provided information to pull the code from the repository to the server.
In the ansible
directory, create a new file named ansible.sh
. Add the following code:
#!/bin/bash
ansible-playbook -i ./hosts $1
The code snippet above will run the server's ansible-playbook
command.
Excecute the ansible.sh
file using the following command:
./ansible.sh generate_ssh_key.yaml
The command above generates an SSH key on the server and stores it in the .ssh/id_rsa.pub
file; the SSH key is also added to the Git repository.
Once the SSH key is added to the Git repository, we can pull the latest code to the server. Execute the commands below to pull the latest code from the repository and deploy it on the server.
./ansible.sh packages.yaml
./ansible.sh update.yaml
./ansible.sh deploy.yaml
Whenever we want to update our application, we only need to execute the command below to pull the latest code from the repository and deploy on the server.
./ansible.sh deploy.yaml
Conclusion
We have created a budget management application in this tutorial and deployed it to production using Ansible.
Try implementing automated deployments using Ansible in Django projects to reduce the time spent on manual Django application deployments. You can download the source code of this tutorial here.
Peer Review Contributions by: Okelo Violet