Django on K8s (Part V: Running with MySQL)

mkalioby

Mohamed M El-Kalioby

Posted on January 12, 2022

Django on K8s (Part V: Running with MySQL)

Welcome back, in the last part, we discussed how to persist the storage between container restarts (assuming we have only one node), but we can't use SQLite on any environment, and we need a robust DBMS like MySQL to run our web-app. so In this part we will see how to use multiple containers per pod strategy to run Django with MySQL.

Warning: There is a better technique for production, where we have a MySQL service (which has replicas across several pods) and the master node is exposed to the other pods and Django apps can find it using ConfigMaps, but this is out-of-scope for this tutorial.

MySQL Container

The MySQL image on docker hub needs the databases folders '/var/lib/mysql' to be an external volume which makes sense as we need to persist the databases if the container crashes. also the MySQL image needs an env variable MYSQL_ROOT_PASSWORD to set the root password.

To configure the image, lets do the following steps

  1. Create a storage folder on minikube
  2. Start a MySQL container with the MYSQL_ROOT_PASSWORD environmental variable on the minikube docker
  3. Create a database called 'django-example' which will the data for the application
  4. Create an image of the created container,
  5. Move the settings file to MySQL instead of SQLite.
  6. Update the requirements file
  7. Create new docker image of the new code.
  8. Adjust the deployment.yaml to
    1. Add MySQL container based on our image to the containers,
    2. Add a command to the web container to migrate the database as the container starts

1. Create a storage folder on minikube

As minikube is our hosting we need to create a storage folder.



minikube ssh -- mkdir -p  /storage/mysql/django-example


Enter fullscreen mode Exit fullscreen mode

2. Start a MySQL

Move your environment to use Docker inside minikube



$ eval $(minikube docker-env)


Enter fullscreen mode Exit fullscreen mode

Start the container (with an external storage)



docker run -d -e MYSQL_ROOT_PASSWORD=pass1234 --name example-mysql  -v /storage/mysql/django-example:/var/lib/mysql mysql 


Enter fullscreen mode Exit fullscreen mode

The -v mount a folder from the host to the container.

After downloading all the image layers, you will get the container ID like the image below, next start a terminal and connect to mysql using the root and the password we set, as shown in the image below.

Creating and connecting to MySQL

3. Create a database called 'django-example' which will the data for the application

Connect to MySQL & Create Database



$ docker exec -it example-mysql /bin/bash

mysql -u root -ppass1234


Enter fullscreen mode Exit fullscreen mode


mysql> create database django_example;


Enter fullscreen mode Exit fullscreen mode

Exit twice by (pressing CTRL+D) to get back to your terminal.

4. Create an image of the created container

Create a new database image using docker commit, and check the images list as shown in the image below



$ docker commit example-mysql django-example-db:v1.0
$ docker images


Enter fullscreen mode Exit fullscreen mode

New Image

Verification Step

Start a new container from our image and let's check that the database is there.



docker run -d --name test-db -v /storage/mysql/django-example/:/var/lib/mysql django-example-db:v1.0
docker exec -it test-db /bin/bash
mysql -u root -ppass1234


Enter fullscreen mode Exit fullscreen mode

on the mysql terminal, write



show databases


Enter fullscreen mode Exit fullscreen mode

you shall find the database there as shown in the image below

Database Exists

Important: Clean what you done so we can continue, if you won't do so MySQL in the other containers won't start.



$ docker stop test-db 
test-db
$ docker rm test-db 
test-db


Enter fullscreen mode Exit fullscreen mode

5. Move the settings file to MySQL instead of SQLite.

Note: All the steps descibed below are available in the MySQL branch in the repo

Note:In Kubernetes, when multiple containers are in the same pod, they share the same IP and they communicate using the loopback interface (localhost) so we gonna to configure Django to connect to MySQL as follows:



DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'django_example',
        'USER': 'root',
        'PASSWORD': 'pass1234',
        'HOST': '127.0.0.1'
    }
}


Enter fullscreen mode Exit fullscreen mode

6. Update the 'requirements.txt' file to include 'mysqlclient' library



mysqlclient==2.1.0


Enter fullscreen mode Exit fullscreen mode

7. Update Dockerfile to run the migrate command first.



FROM python:3.8-slim-bullseye

RUN apt update && apt install -y gcc libmariadb-dev-compat #<---New

RUN pip install gunicorn

WORKDIR /app

COPY django_app/requirements.txt .

RUN pip install -r /app/requirements.txt

COPY django_app/ /app/

CMD sleep 5                       #<---New
CMD python manage.py migrate      #<---New

EXPOSE 80/tcp

ENTRYPOINT gunicorn -w 4 k8s.wsgi -b 0.0.0.0:80


Enter fullscreen mode Exit fullscreen mode

Run the docker build command



docker build -t django-example-mysql:v1.0 .


Enter fullscreen mode Exit fullscreen mode
  1. Adjust the deployment.yaml run the sql container with web container, as shown in django-example_mysql.yaml


apiVersion: apps/v1
kind: Deployment
metadata:
  name: django-example-mysql
  labels:
    app: django-example-mysql
spec:
  replicas: 1
  selector:
    matchLabels:
      app: django-example-mysql
  template:
    metadata:
      labels:
        app: django-example-mysql
    spec:
      volumes:                   # <-- Add MySQL Storage
        - name: mysql-volume
          hostPath:
            path: /storage/mysql/django-example/
            type: Directory
      containers:
      - name: web-app
        image: django-example-mysql:v1.0 #<--Use New Image
        command: ['/bin/bash']           #<-- Add Command to run at start-up
        args: ['-c','python manage.py migrate; gunicorn -w 4 k8s.wsgi -b 0.0.0.0:80']
        ports:
        - containerPort: 80
      - name: mysql                   #<-- Add Second Container
        image: django-example-db:v1.0
        volumeMounts:
          - name: mysql-volume
            mountPath: /var/lib/mysql/



Enter fullscreen mode Exit fullscreen mode

Finally, Apply the configuration to Kubernetes



django-on-k8s$ kubectl apply -f django-example_mysql.yaml


Enter fullscreen mode Exit fullscreen mode

Expose the service



$  kubectl expose deployment django-example-mysql


Enter fullscreen mode Exit fullscreen mode

Start the minikube tunnel (in a new terminal window)



$ minikube tunnel  


Enter fullscreen mode Exit fullscreen mode

Get the IP



$ kubectl get svc


Enter fullscreen mode Exit fullscreen mode

Service IP

Now open a new browser window, and go to 'http://IP/' and try to login by 'admin','admin', you get 'Invalid Username or Password' as shown below.

Invalid Credentials

I think, it is clear why the username and password are invalid, because the database doesn't have any users and it is just migrated by web container, so next we connect to the pod and run createsuperuser command to create the first user that is done as follows

Get the pod name through



$ kubectl get po -l  app=django-example-mysql


Enter fullscreen mode Exit fullscreen mode

Note: This filters the pods by the app name
Then connect to the pod by



$ kubectl exec -it django-example-mysql-58459c6f4-q8zg8 -c web-app -- /bin/bash


Enter fullscreen mode Exit fullscreen mode

The image below shows the steps
Get Pod Name

Now try to login again to the application.

You shall be able to login successfully, as shown below.

Login successfully

Verification

So, we logged-in successfully, but we need to make sure that the database will hold against containers failure so we can are going to delete the deployment and create it again and try to refresh and we shall be still login and we can logout and login again

To simulate pod failure, we will delete the pod automatically, which will force Kubernetes to start a new pod to match the deployment's requirements as shown in the image below



$ kubectl delete po/django-example-mysql-58459c6f4-q8zg8


Enter fullscreen mode Exit fullscreen mode

New pod is created

Go to the browser window which is showing the application and refresh, you will find everything working and you logout and login again.

Wrap-up

In this part, we discussed how to start a MySQL container with our web-app container and make them talk together and now we have most of things to go production. Next part will show some advanced topics like rediness & liveness checks, rolling updates and rolling back and secrets, so stay tuned.

💖 💪 🙅 🚩
mkalioby
Mohamed M El-Kalioby

Posted on January 12, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related