Hello Kubernetes with node, flask and django at the same time.

Over the past year I have developed an aversion to overly complex software tutorials. I understand that using specialized tools necessitates the knowledge of supporting tools, but far too many software introductions are convoluted by the inclusion of extraneous libraries. I will do my best to keep the number of libraries in this tutorial to a minimum. I will also refrain from giving long in depth explanations of anything at all. When I go through tutorials I tend to jump between code blocks and ignore the writing. The minutiae do not concern me and neither do the author’s knowledge or opinions. I care about the result.

To take full advantage of this tutorial you should know about html, node, python, flask, django, docker and kubernetes. To take partial advantage of this tutorial you should know about html, docker, and node or flask or django. No databases (postgresql, mysql, mongo, redis, etc.) are required. I am using a mac and will be writing this from a mac user’s perspective. Things will most likely work in a similar way on a Linux operating system, and I have no idea how things will work on a Windows operating system. If you do not want to deal with your computer’s operating system then I suggest you use the Google Cloud platform https://cloud.google.com/container-engine/. I found the Google Cloud Platform frustrating to use and wanted something that would not eventually start charging my credit card.

Disclaimer: my failure to remove gunicorn from this tutorial completely goes against what I have written in the first paragraph, my own inabilities as a programmer are forcing me to use it. If some smart programmer has a good suggestion, don’t be afraid to put it in the comments.

This tutorial takes directly from the official “Hello Minikube” kubernetes tutorial, https://kubernetes.io/docs/tutorials/stateless-application/hello-minikube/ and the “Scalable and resilient Django with Kubernetes” tutorial, by Harish Narayanan, https://harishnarayanan.org/writing/kubernetes-django/.

The source code for this post is here: https://github.com/patcurry/hellokubernetes.

Let’s go!

Install Docker: https://docs.docker.com/engine/installation/

Install Minikube and kubectl: https://kubernetes.io/docs/tasks/tools/install-minikube/

The majority of this first section is almost exactly the same as the kubernetes offical intro to Minikube, called “hello-minikube”: https://kubernetes.io/docs/tutorials/stateless-application/hello-minikube/. In fact, I believe that they do a much better job of explaining things than I do, so I would suggest that you go to the link and follow their directions.

In the base directory create two subdirectories, “containers” and “kubernetes”. In the containers subdirectory make another directory called “hello-node”. In the hello-node directory put a Dockerfile and a javascript file called server.js.

Dockerfile contents:

FROM node:6.9.2
EXPOSE 8080
COPY server.js .
CMD node server.js

server.js contents:

var http = require(‘http’);
var handleRequest = function(request, response) {
console.log(‘Received request for URL: ‘ + request.url);
response.writeHead(200);
response.end(‘This is Node!’);
};
var www = http.createServer(handleRequest);
www.listen(8080);

Current directory structure:

.
├── containers
│ └── hellonode
│ ├── Dockerfile
│ └── server.js
└── kubernetes

Now make sure to build the docker image on the minikube virtual machine. To make sure you are using the repo on the minikube virtual machine execute this command:

eval $(minikube docker-env)

Go into the hellonode directory and build the docker image:

cd containers/hellonode
docker build -t hello-node:1.0 .

Create a deployment on minikube on port 8080:

kubectl run hello-node --image=hello-node:1.0 --port=8080

Take a look at your deployment:

kubectl get deployments

Now the pod:

kubectl get pods

Events:

kubectl get events

Make your deployment available:

kubectl expose deployment hello-node --type=LoadBalancer

Now get your services:

kubectl get services

View the service (hello-node of course) in your browser:

minikube service hello-node

Presumably you’ve pulled up a webpage that has the text ‘This is Node!’

Go ahead and kill the service and the deployment now.

kubectl delete service hello-node
kubectl delete deployment hello-node

Check if you have still got pods, services and deployments. You should not have any that are running.

kubectl get deployments
kubectl get services
kubectl get deployments

Now it is time to add a replication controller and a service. The replication controller will be specific to the app (in this case it will be specific to the node app you just made) and the service will be able to handle all the apps. To do this we will set every app to run on port 8080. The service will then treat each app equally. They could be completely different web apps, but they would be accessed through the same port. The next couple sections off this tutorial pull from a tutorial by Harish Narayanan https://harishnarayanan.org/writing/kubernetes-django/.

In the base directory create another folder called “kubernetes”. In the kubernetes folder add a file called “replication-controller-node.yaml” and a file called “service.yaml”.

replication-controller-node.yaml contents:

apiVersion: v1
kind: ReplicationController
metadata:
name: hello-node
labels:
name: hello
spec:
replicas: 1
selector:
app: hello
framework: node
template:
metadata:
labels:
app: hello
framework: node
spec:
containers:
- name: hello
image: hello-node:1.0
ports:
- containerPort: 8080

service.yaml contents:

apiVersion: v1
kind: Service
metadata:
name: hello
labels:
name: hello
spec:
selector:
app: hello
ports:
- protocol: TCP
port: 8080
targetPort: 8080
type: LoadBalancer

Current directory structure:

.
├── containers
│ └── hellonode
│ ├── Dockerfile
│ └── server.js
└── kubernetes
├── replication-controller-node.yaml
└── service.yaml

Now run the same kubectl command you did in the first part of this tutorial to get a node image going.

kubectl run hello-node --image=hello-node:1.0 --port=8080

Now, just like in Harish Narayanan’s tutorial, go into the kubernetes directory and create the replication controller.

cd kubernetes
kubectl create -f replication-controller-node.yaml
kubectl get rc
kubectl get pods
kubectl describe pod <pod-id>
kubectl logs <pod-id>

Create the service and view it:

kubectl create -f service.yaml
kubectl get services
kubectl describe service hello
minikube service hello

You should now see the same node page as before, but you created it using a file instead of the command line utility (mostly). We will now add a Flask app that is accessed by the same service.

In the containers directory build another directory folder called “helloflask” and put a Dockerfile, a hello.py file, and a requirements.txt file into it.

Dockerfile contents:

FROM python:3.6
WORKDIR /app
ADD . /app
RUN pip install -r requirements.txt
EXPOSE 8080
ENV NAME world
CMD ["python", "hello.py"]

hello.py contents:

from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "This is Flask!"
if __name__ == "__main__":
app.run(host='0.0.0.0', port=8080)

requirements.txt contents:

Flask

Back in the kubernetes directory create a file called replication-controller-flask.yaml.

replication-controller-flask.yaml contents:

apiVersion: v1
kind: ReplicationController
metadata:
name: hello-flask
labels:
name: hello
spec:
replicas: 1
selector:
app: hello
framework: flask
template:
metadata:
labels:
app: hello
framework: flask
spec:
containers:
- name: hello
image: hello-flask:1.0
ports:
- containerPort: 8080

Your project structure will now look like this:

.
├── containers
│ ├── helloflask
│ │ ├── Dockerfile
│ │ ├── hello.py
│ │ └── requirements.txt
│ └── hellonode
│ ├── Dockerfile
│ └── server.js
└── kubernetes
├── replication-controller-flask.yaml
├── replication-controller-node.yaml
└── service.yaml

Go into the containers/helloflask directory and build the docker image for the flask app:

cd containers/helloflask
docker build -t hello-flask:1.0 .

Change into the kubernetes directory and create the replication controller for the flask app.

cd kubernetes
kubectl create -f replication-controller-flask.yaml
kubectl get rc
kubectl get pods
kubectl get services

You should see that you now have a pod for hello-node and a pod for hello-flask, but only one service; the hello service. View the service in your browser.

minikube service hello

Refresh the page a few times. Part of the time you should see a page that says “This is Node!” and rest of the time you should see “This is Flask!” If you are unable to pull up the “This is Flask!” page go ahead and scale the service to favor the flask app. Make 3 flask app replicas.

kubectl scale rc hello-flask --replicas=3
kubectl get pods

If that doesn’t work scale the hello-node app to zero.

kubectl scale rc hello-node --replicas=0
kubectl get pods

To get back to normal just use the kubectl scale rc command again.

kubectl scale rc hello-node --replicas=1
kubectl scale rc hello-flask --replicas=1
kubectl get pods

Goof around with scaling and page refreshing for a while to get a handle on the commands and the whole concept. Don’t forget to view the pods, services, replication-controllers, etc.

One thing that you may notice, besides the text of the two apps, is a difference in the Node app and the Flask app’s styling. The Flask app is pure html, and the Node app has some default css attached to it.

Create another folder in the containers directory called “hellodjango” and put a Dockerfile and a requirements.txt file into it. Use django’s standard machinery to create a django app in that directory. Make a templates directory in the main django app project directory and make a home.html file in that directory. Put a file called replication-controller-django.yaml into the kubernetes directory.

django-admin.py startproject main .

Opinion: I usually just call my projects main, and build them directly into the base directory. It does not make sense to me to have so many nested folders with the same name.

Dockerfile contents (I have really been struggling with django on docker. If somebody has a simpler Dockerfile that works, please let me know so I can modify this):

FROM python:3.6
RUN apt-get -q update && apt-get install -y -q \
sqlite3 --no-install-recommends \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
ENV LANG C.UTF-8
RUN mkdir /app
WORKDIR /app
ADD requirements.txt /app/requirements.txt
RUN pip install -r /app/requirements.txt
ADD . /app
CMD gunicorn -b :8080 main.wsgi

requirements.txt contents:

DJANGO==1.11.4
gunicorn==19.7.1

main/urls.py contents :

from django.conf.urls import url
from django.views.generic import TemplateView
urlpatterns = [
url(r'^$', TemplateView.as_view(template_name='home.html')),
]

Instead of telling you the full main/settings.py contents, I will just tell you what to change. Make the allowed hosts universal:

ALLOWED_HOSTS = ['*']

Make the templates directory equal to the templates folder you put into the main django app directory. This is my entire templates configuration, with the directory part in bold:

TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': False,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]

templates/home.html contents:

This is Django!

replication-controller-flask.yaml contents:

apiVersion: v1
kind: ReplicationController
metadata:
name: hello-django
labels:
name: hello
spec:
replicas: 1
selector:
app: hello
framework: django
template:
metadata:
labels:
app: hello
framework: django
spec:
containers:
- name: hello
image: hello-django:1.0
ports:
- containerPort: 8080

Current directory structure:

.
├── containers
│ ├── hellodjango
│ │ ├── Dockerfile
│ │ ├── db.sqlite3
│ │ ├── main
│ │ │ ├── __init__.py
│ │ │ ├── settings.py
│ │ │ ├── urls.py
│ │ │ └── wsgi.py
│ │ ├── manage.py
│ │ ├── requirements.txt
│ │ └── templates
│ │ └── home.html
│ ├── helloflask
│ │ ├── Dockerfile
│ │ ├── hello.py
│ │ └── requirements.txt
│ └── hellonode
│ ├── Dockerfile
│ └── server.js
└── kubernetes
├── replication-controller-django.yaml
├── replication-controller-flask.yaml
├── replication-controller-node.yaml
└── service.yaml

Just like in the last couple sections you will need to build a docker image of the django app and create a replication controller for the django app.

Go back to the service page and refresh it a bunch of times. You should see a one page that displays “This is Node!”, one that displays “This is Flask!” and one that displays “This is Django!”

To kill everything you will need to delete not only the service but each deployment.

kubectl delete service hello
kubectl delete deployment hello-node
kubectl delete deployment hello-flask
kubectl delete deployment hello-django

And stop minikube (if you like):

minikube stop

You have now created three simple dockerized web apps that are available from the same service and you are able to scale the web apps with a replication controller.

I did not show you how to deal with databases and I did not show you how to update a deployment to the next version (ie hello-node:1.0 to hello-node:1.1), but both of the tutorials I took from do exactly that, so take a look:

Finally, my intention is to create a follow up to this post in which I make node, flask, and django webapps that connect to the same simple postgresql book database (1 table with 3 columns, book title, book author(s), and whether I have read it or not).

Let me know what you think, and let me know if I made any mistakes so that I can remedy them.