This section focuses on implementing the kubernetes hello-minikube tutorial adapted to a conventional Django application. The codebase for this tutorial can be cloned from my github repo and we will be working with the part_2-getting-started branch. The kubernetes version for this tutorial is assumed to be 1.9.
1. Requirements
OS
This tutorial assumes a Mac OS system, but has links on how to run it on a Linux/Ubuntu or Windows OS.
Minikube
Minikube is one of the easiest ways at the moment to run a single node Kubernetes cluster locally. In a Mac OS, the installation can be done by running.
$ brew cask install minikube
For a Linux or Windows OS, the installation instructions have been specified in the minikube github README page.
Virtualbox
Minikube supports several VM drivers but by default uses virtualbox which can be downloaded and installed from the virtualbox downloads page.
Docker
Docker is used for containerization and the installation can be found in the docker documentation page.
Kubectl
The Kubernetes command line tool is called kubectl and is used to deploy and manage applications. This is done by creating, updating and deleting components as well as inspecting cluster resources. To install it, simply run:
$ brew install kubectl
For detailed Windows and Linux installations, please refer to the kubernetes kubectl installation page.
Project files
In order to get the best of this tutorial, the project github repo should be cloned:
$ git clone https://github.com/gitumarkk/kubernetes_django.git
The branch this tutorial is based on is getting_started.
2. Minikube
To start the Kubernetes cluster using minikube, run the command:
$ minikube start
Several processes occur, which include:
- The creation and configuration of a VM which runs a single-node kubernetes cluster.
- Setting the default
kubectlcontext tominikubei.e.kubectl config use-context minikube, where a context is the configuration information used to communicate with each unique Kubernetes cluster.
The status of the minikube cluster can be determined by running:
$ minikube status
minikube: Running
cluster: Running
kubectl: Correctly Configured: pointing to minikube-vm at 192.168.99.100
And to confirm the kubectl context, the command is:
$ kubectl config current-context
minikube
The docker command line in the host machine can be configured to utilize the docker daemon within minikube by running:
$ eval $(minikube docker-env)
There are several reasons as to why it is useful to use the minikube docker daemon:
- Docker images to be deployed into the local cluster don’t have to be pushed to a container registry and pulled in by Kubernetes, they can be built inside the same docker daemon as minikube and be used directly.
- As a result, it’s good for running local experiments as has a much faster turn around time especially if an external registry is required.
- It applies when you have a single VM (node) docker cluster and want to use the docker daemon inside the VM.
To confirm that the docker cli is using the minikube docker daemon, run:
$ docker info | grep Name
Name: minikube
In order to revert back to the host docker daemon, simply run:
$ eval $(minikube docker-env -u)
For the rest of the tutorial, the kubectl context should be set to minikube and the minikube docker daemon should be used.
There are many management commands that are used by kubectl to view the state of the Kubernetes cluster. Fortunately, minikube provides a dashboard so we don’t have to worry about all the explicit commands. To view the dashboard, run the command:
$ minikube dashboard
This opens the default browser and displays the current state of the Kubernetes cluster.
3. Docker
As Kubernetes expects a containerized application, we will be using docker to get started. It’s assumed docker has already been installed and we are using the minikube docker daemon.
The Dockerfile
The following Dockerfile is in the root directory of the project file i.e. ./kubernetes_django/Dockerfile:
FROM python:3-slim
LABEL maintainer="mark.gituma@gmail.com"
ENV PROJECT_ROOT /app
WORKDIR $PROJECT_ROOT
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
COPY . .
CMD python manage.py runserver 0.0.0.0:8000
- The Dockerfile first defines the base image to build from, where in this case it’s the
python:3-slimimage. - The
LABELinstruction is then used to add metadata to the image. This is the recommended way to specify the package maintainer as theMAINTAINERinstruction has been deprecated. - The
ENV <VARIABLE> <var>directive sets the project root environmental variable, where the variable can be reused in several places as$<VARIABLE>. This allows for one point of modification in case some arbitrary variable needs to be changed. - The current working directory is then set using the
WORKDIRinstruction. The instruction resolves the$PROJECT_ROOTenvironmental variable previously set. The working directory will be the execution context of any subsequentRUN,COPY,ENTRYPOINTorCMDinstructions, unless explicitly stated. - The
COPYinstruction is then used to copy therequirements.txtfile from the current directory of the local file system and adds them to the file system of the container. Copying the individual file ensures that theRUN pip installinstruction’s build cache is only invalidated (forcing the step to be re-run) if specifically therequirements.txtfile changes, leading to an efficient build process. See the docker documentation for further details. It’s worth noting theCOPYas opposed to theADDinstruction is the recommended command for copying files from the local file system to the container file system. - The required python packages are then installed using the
RUN pip installinstruction. - The rest of the project files are then copied into the container file system. This should be one of the last steps as the files are constantly changing leading to more frequent cache invalidations resulting in more frequent image builds.
- The final instruction executed is
CMDwhich provides defaults for an executing container. In this case the default is to start the python web server.
Building Docker
The command used to build the required docker image based on the Dockerfile is:
$ docker build -t <IMAGE_NAME>:<TAG>.
The :<TAG> parameter though optional, is recommended in order to keep track of the version of the docker image to be run e.g. docker build -t gitumarkk/k8_django_minikube:1.0.0. The <IMAGE_NAME> can be any arbitrary string, but the recommended format is <REPO_NAME>/<APP_NAME>.
In order to see the built image within the minikube docker environment, run:
Building Docker
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
gitumarkk/k8_django_minikube 1.0.0 c459907decbb 5 minutes ago 197MB
python 3-slim dc41c0491c65 10 days ago 156MB
gcr.io/google_containers/kubernetes-dashboard-amd64 v1.8.0 55dbc28356f2 4 weeks ago 119MB
gcr.io/k8s-minikube/storage-provisioner v1.8.0 4689081edb10 7 weeks ago 80.8MB
.
.
As we are in the minikube docker daemon, it will display the image that we built as well as images used by minikube within the cluster.
4. Deployments
Kubernetes uses the concept of pods (i.e. a grouping of co-located and co-scheduled containers running in a shared context) to run applications. There are different controllers used to manage the lifecycle of pods in a Kubernetes cluster. However, a Deployment controller forms one of easiest ways to create, update and delete pods in the cluster.
Kubernetes commands can be executed by an imperative or declarative approach. Imperative commands specify how an operation needs to be performed, a declarative approach is done by using configuration files which can be stored in version control. The preferred method is the declarative approach as the steps can be tracked and audited. But for arguments sake we will look at both approaches
Imperative
To create a deployment imperatively, run the following command:
$ kubectl run <deployment-name> --image=<IMAGE-NAME> --port=8000
At it’s simplest, the command creates a Deployment controller, and the controller then creates pods consisting of containers based on the image defined by <IMAGE-NAME>. The pods are then deployed in the minikube Kubernetes cluster. The running deployments can be seen in the minikube dashboard under the Deployments and Pods side navigation bar. The new deployment can be viewed on the minikube dashboard, however to view it in the terminal, execute:
$ kubectl get deployments
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
kubernetes-django 1 1 1 1 20h
To delete a deployment, the command is:
$ kubectl delete deployment/<DEPLOYMENT_NAME>
Declarative
Using the declarative approach, the deployment can be conducted by applying the command:
$ kubectl apply -f deployments.yaml
deployment "<deployment-name>" created
To the following spec which is found in the ./kubernetes_django/deploy/kubernetes/django/deployments.yaml file in the repository:
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: <deployment_name>
labels:
<deployment_label_key>: <deployment_label_value>
spec:
replicas: 1
selector:
matchLabels:
<pod_label_key>: <pod_label_value>
template:
metadata:
labels:
<pod_label_key>: <pod_label_value>
spec:
containers:
- name: <pod_name>
image: <pod_image>
ports:
- containerPort: 8000
From the spec file:
- The
metadata: namefield describes the deployment name, whereas themetadata: labelsdescribes the labels for the deployment. - The
spec: replicasfield defines the number of pods to run. - The
spec: selector: matchLabelsfield describes what pods the deployment should apply to. - The
spec: template: metadata: labelsfield indicates what labels should be assigned to the running pod. This label is what is found by thematchLabelsfield in the deployment. - The
spec: template: specfield indicates the pod only has one container per pod as it only has one image definition. The<pod_name>can be any string but should ideally be descriptive enough. The<pod_image>is an image name that should be discoverable within the local context or from an external container registry. Since the local docker daemon is being used, the image will be used from the local context. - The deployment exposes port
8000within the pod as defined in thespec: template: spec: containers: portsfield.
Components created declaratively can be deleted by, running the command:
$ kubectl delete -f <file_path>.yaml
5. Services
When a deployment is created, each pod in the deployment has a unique IP address within the cluster. However, we need some kind of mechanism to allow the access of the pod IP address from outside the cluster. This is done by Services. Formally:
A Kubernetes Service is an abstraction which defines a logical set of Pods and a policy by which to access them - sometimes called a micro-service. A Service routes traffic across pods while allowing the specific pod IP addresses to be dynamic i.e. less stable. This means pods can die and be recreated and thus the IP address can change, and yet the traffic will always route to the right pods. This is abstracted away by the Service object and allows the user to focus on building the application.
As with Deployments, services can either be defined imperatively or declaratively.
Imperative
To create a service imperatively, the following shell command is to be executed:
$ kubectl expose deployment <deployment-name> --type=NodePort
In order to view the existing services, run:
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
django NodePort 10.111.73.57 <none> 8000:30098/TCP 16s
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 4d
This shows that our deployment has a NodePort type and exposes port 8000 on the container to port 30098 on the host machine. The latter port is called a nodePort and by default the range is 30000–32767. The deployed service can be viewed on the minikube dashboard, however, minikube also provides the useful cli command:
$ minikube service <service-name>
Where the <service-name> in this case is django. If everything goes well, the default browser should be opened with the django application running on the <minikube_ip>:<nodePort> url, showing the default Django 2 webpage.

To delete the service imperatively, the command is:
$ kubectl delete svc/<service-name>
Declarative
The following declarative declaration of the service can be found in the ./kubernetes_django/deploy/kubernetes/django/service.yaml file.
kind: Service
apiVersion: v1
metadata:
name: kubernetes-django-service
spec:
selector:
<pod_key>: <pod_value>
ports:
- protocol: TCP
port: 8000
targetPort: 8000
type: NodePort
- The
metadata: namefield describes the name of theServiceobject that will be created and can be identified by runningkubectl get svc. - The
spec: selectorfield specifies the<pod_label>and<pod_value>that the service applies to. This means that any pod matching<pod_key>=<pod_value>label will be exposed by the service - The
spec: portscontains a yaml array. Theprotocolin the first item in the array isTCPwhere the podport: 8000field is exposed to the Kubernetes cluster i.e. the cluster interacts with the pod on port8000. ThetargetPortis the port within the pod that it’s exposed through. Ifportis not defined, it will default to thetargetPort. TheNodePorttype instructs the service to expose the pod to the node/host machine on a random port in the default range30000–32767, however an explicitnodePortcan be set in theprotocolsarray to specify which port in the default range the host machine can communicate with thepod.
The deployed service can be viewed on the minikube dashboard or by running the command:
$ kubectl get svc
And the service can be viewed on the browser by running.
$ minikube service <deployment-name>
6. Summary
So far we have covered how to get a basic Django application up and running in Kubernetes cluster by:
- Installing and running
minikubewhich creates our local cluster. - Creating a
Dockerfilethat is used to build the image for the application. - Deploying the image as a
Podwithin our kubernetes cluster and seeing the result in theminikube dashboard.
This forms the foundation for the rest of the tutorial as it’s simply a matter of building on what we already have. The next tutorial will focus on how to deploy a Postgres backend with Celery that utilizes Redis as a message broker.
If you like this post, don’t forget to like and/or recommend it. You can find me on Twitter as @MarkGituma.
7. Credits
- https://kubernetes.io/docs/getting-started-guides/minikube/
- https://kubernetes.io/docs/tutorials/stateless-application/hello-minikube/
- https://medium.com/google-cloud/local-django-on-kubernetes-with-minikube-89f5ad100378
- http://www.bogotobogo.com/DevOps/DevOps-Kubernetes-1-Running-Kubernetes-Locally-via-Minikube.php
8. Terms and Definitions
- Node: A
nodeis a worker machine in Kubernetes and may consist of a virtual machine or a physical machine. - Cluster: A
clusteris a collection ofnodes. - Image: An
imageis a template that defines the packages and steps necessary to run an application. - Container: A
containeris the instance of animageand is a lightweight, stand-alone, executable package of a piece of software that includes everything needed to run it i.e. code, runtime, system tools, system libraries, settings. - Pod: A
podis a group of one or more containers (such as Docker containers), with shared storage/network, and a specification for how to run the containers.