Django on K8s (Part V: Running with MySQL)
Tags: django, kubernetes, docker, ubuntu
Published: 2022-01-12 07:52:16

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
1. Start a MySQL container with the `MYSQL_ROOT_PASSWORD` environmental variable on the minikube docker
1. Create a database called 'django-example' which will the data for the application
1. Create an image of the created container,
1. Move the settings file to MySQL instead of SQLite.
1. Update the requirements file
1. Create new docker image of the new code.
1. Adjust the deployment.yaml to  
       1. Add MySQL container based on our image to the containers, 
       1. 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
```
### 2. Start a MySQL
Move your environment to use Docker inside minikube
```
$ eval $(minikube docker-env)
```
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 
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wew8fzpfld99orxn9d81.png)

### 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
```

```sql
mysql> create database django_example;
```
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
```
![New Image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zfbhi2f6vm544z18veqh.png)

#### 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
```
on the mysql terminal, write
```sql
show databases
```
you shall find the database there as shown in the image below

![Database Exists](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0mx742lwxaibwfduqwr8.png)

**_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
```



### 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](https://github.com/mkalioby/django-on-k8s)

**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:
```python
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'django_example',
        'USER': 'root',
        'PASSWORD': 'pass1234',
        'HOST': '127.0.0.1'
    }
}
```

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

```
mysqlclient==2.1.0
```

### 7. Update Dockerfile to run the migrate command first.

```Docker
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
```
Run the docker build command

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

1. Adjust the deployment.yaml run the sql container with web container, as shown in `django-example_mysql.yaml`

```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/

```

Finally, Apply the configuration to Kubernetes

```
django-on-k8s$ kubectl apply -f django-example_mysql.yaml
```
Expose the service
```
$  kubectl expose deployment django-example-mysql
```
Start the minikube tunnel (in a new terminal window)
```
$ minikube tunnel  
```
Get the IP 
```
$ kubectl get svc
```
![Service IP](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lze68cxmvp1kuro4pihb.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ba1woxbw7vwwg0nhhzvw.png)

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
```

**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
```
The image below shows the steps
![Get Pod Name](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/22otc408icgoly3455pc.png)
 
 Now try to login again to the application.

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

![Login successfully](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/45gykulxvxaeqp51bynb.png)
 
## 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
```

![New pod is created](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a9vnfyrrm1or18ge2oon.png)

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.