Run Django applications on the Canonical Distribution of Kubernetes

Jacek Nykis

Jacek Nykis

on 20 July 2017

Introduction

Canonical’s IS department is responsible for running most of the company’s internal and external services. This includes infrastructure like Landscape and Launchpad, as well as third party software such as internal and external wikis, WordPress blogs and Django sites.

One of the most important functions of our department is to help improve our products by using them in production, providing feedback and submitting patches. We are also constantly looking for ways to help our development teams to run their software in easier and more efficient ways. Kubernetes offers self-healing, easy rollouts/rollbacks and scale-out/scale-back, so we decided to take the Canonical Distribution of Kubernetes for a spin.

Deploying the Canonical Distribution of Kubernetes

Inside Canonical, all new services are deployed with Juju and Mojo on top of our OpenStack clouds. The Canonical Distribution of Kubernetes is distributed as a Juju bundle, which made it a great starting point. We turned the bundle into a Mojo spec and added additional charms like the canonical-livepatch subordinate for live kernel updates and nrpe support for monitoring. Once the spec was ready we deployed it into a clean Juju 2 model with a single command:

mojo run

To make sure we can recover quickly from disasters we also set up a Jenkins job that periodically deploys the entire Kubernetes stack in a Continuous Integration environment. This gives us confidence that the spec stays in a deployable state all of the time. Once we had Kubernetes up and running it was time to pick an application to migrate. We wanted to start with an application that we use every day so that we would discover problems quickly. We also wanted to exercise multiple Kubernetes concepts, for example CronJob resources and ExternalName services. Our choice was to start with an internal Django site which we use to help manage ticket from customers.

Application migration

The application we picked is fairly standard Django code with an Apache frontend. For this first pass we decided not to migrate the database, allowing us to avoid need for stateful components in Kubernetes. The first step was to turn the Django application into a Docker container. To make this possible we had to update settings.py to support Kubernetes secrets. For simplicity, we chose to use environment variables and created stanzas similar to the following:

	DATABASES = {
    	"default": {
        	"ENGINE": "django.db.backends.postgresql_psycopg2",
        	"NAME": os.environ['DB_NAME'],
        	"USER": os.environ['DB_USERNAME'],
        	"PASSWORD": os.environ['DB_PASSWORD'],
        	"HOST": "db",
        	"PORT": int(os.environ['DB_PORT']),
        	}
    	}

Next we needed to find a way for the container to talk to the external database – this was easily achieved using “ExternalName” service:

	kind: Service
	apiVersion: v1
	metadata:
  	  name: database
  	  namespace: default
	spec:
  	  type: ExternalName
  	  externalName: external-db.example.com

Then we simply copied our uwsgi configuration and ran the application like this:

CMD ["/usr/bin/uwsgi-core", "--emperor", "/path/to/config/"]

The next step was to create a Dockerfile for the Apache frontend. This one was slightly more tricky because we wanted to use a single image for the development, staging and production deployments, however there are small configuration differences between each one. Kubernetes documentation suggested that ConfigMaps are normally the best way to solve such problems and sure enough it worked! We added a new “volume” to each of the deployments:

	volumes:
  	- name: config-volume
    	configMap:
      	name: config-dev

Which we then mounted inside the container:

	volumeMounts:
	- name: config-volume
  	mountPath: /etc/apache2/conf-k8s

And finally we included this in the main apache configuration:

Include /etc/apache2/conf-k8s/*.conf

The ConfigMap contains ACLs and also ProxyPass rules appropriate for the deployment. For example, in development we point at the “app-dev” backend like this:

ProxyPass / uwsgi://app-dev:32001/ 
ProxyPassReverse / uwsgi://app-dev:32001/ 

With all of above changes completed, we had the development environment running successfully on Kubernetes!

Further improvements

Of course we wanted to make code updates easier, so we did not stop there. We decided to use Jenkins for Continuous Integration and created two jobs. The first one takes a branch as an argument, runs tests and if they are successful, builds a Docker image and deploys it to our development environment. This allows us to quickly verify changes in an environment that’s set up in exactly the same way as production. Once a developer is happy with changes they submit a merge proposal in Launchpad, which gets reviewed and merged as normal. This is where the second Jenkins job comes in – it starts automatically on trunk change, runs tests, builds a Docker image and deploys it to our staging environment. Due to the nature of the application, we still want a final human sign off before we push to production, but once that’s done it’s a quick push-button process to go live.

What’s next?

We are investigating Kubernetes liveness probes to see if they can improve Django container’s failure detection. We also want to get more familiar with Kubernetes concepts and operation because we would like to offer it to the internal development teams and migrate more applications run by our department to Kubernetes.

Was it worth it?

Absolutely! Some of the biggest wins we captured:

  • We provided lots of feedback to the Kubernetes charm developers which helped them make improvements
  • We submitted multiple charm patches, including juju `actions`, sharing our operational experience with the community
  • We are in a very good position to start offering Kubernetes to Canonical internal development teams
  • We gained experience in migrating application to Kubernetes, which we will use as we move more services to Kubernetes

Ubuntu cloud

Ubuntu offers all the training, software infrastructure, tools, services and support you need for your public and private clouds.

Newsletter signup

Select topics you’re interested in

In submitting this form, I confirm that I have read and agree to Canonical’s Privacy Notice and Privacy Policy.

Related posts

Infographic: Ubuntu connects everything

As highlighted in the Ubuntu is Everywhere infographic to coincide with the 16.04 LTS, Ubuntu is used by millions across every sector and technology imaginable. Two years on, and with 18.04 LTS now released, we take a new look at how…

A unified OpenStack for a scalable open infrastructure

Stu Miniman and John Boyer of theCUBE interviewed Mark Baker, Field Product Manager, Canonical at the OpenStack Summit in Vancouver. Read on to to find out about OpenStack’s increasing maturity. The Kubernetes and OpenStack story isn’t…

Kubernetes and OpenStack solving AI complexities at scale

Stu Miniman and John Boyer of theCUBE interviewed Stephan Fabel, Director of Ubuntu Product and Development at the OpenStack Summit in Vancouver. Read on for the full interview, and to hear more on Kubernetes, Kubeflow and MicroK8s.…