+++
title = "Backup MySQL Databases in Kubernetes"
date = 2021-03-03
updated = 2021-03-03
description = "In this post, we will show you how to create a MySQL server backup using Kubernetes CronJobs."

[taxonomies]
tags = ["docker", "kubernetes", "cronjob", "bash", "backup"]

[extra]
toc = false
quick_navigation_buttons = true
+++
In this post, we will show you how to create a MySQL server backup using Kubernetes CronJobs.

In our case, we do not have a managed MySQL server. But we want to backup it to our NAS, so that we have a backup in case of emergency.
For this we first build a container that can execute our tasks, because we will certainly need several tasks to backup our cluster.

## CronJob Agent Container

First, we'll show you our Dockerfile so you know what we need.

```Dockerfile
FROM alpine:3.10

# Update
RUN apk --update add --no-cache bash nodejs-current yarn curl busybox-extras vim rsync git mysql-client openssh-client
RUN curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.18.0/bin/linux/amd64/kubectl && chmod +x ./kubectl && mv ./kubectl /usr/local/bin/kubectl

# Scripts
RUN mkdir /srv/jobs
COPY jobs/* /srv/jobs/

# Backup Folder
RUN mkdir /var/backup
RUN mkdir /var/backup/mysql
```

## Backup Script

And now our backup script which the container executes.

Our script is quite simple, we get all tables with the mysql client, export them as sql file, pack them in a zip file and send them in a 8 hours interval to our NAS.

```bash
#!/bin/bash

############# SET VARIABLES #############

# Env Variables
BACKUPSERVER="8.8.8.8" # Backup Server Ip
BACKUPDIR=/var/backup/mysql
BACKUPREMOTEDIR="/mnt/backup/kubernetes/"
HOST="mariadb.default"
NOW="$(date +"%Y-%m-%d")"
STARTTIME=$(date +"%s")
USER=mysqlUser
PASS=mysqlPassword


############# BUILD ENVIROMENT #############
# Check if temp Backup Directory is empty
mkdir $BACKUPDIR

if [ "$(ls -A $BACKUPDIR)" ]; then
    echo "Take action $BACKUPDIR is not Empty"
    rm -f $BACKUPDIR/*.gz
    rm -f $BACKUPDIR/*.mysql
else
    echo "$BACKUPDIR is Empty"
fi

############# BACKUP SQL DATABASES #############
for DB in $(mysql -u$USER -p$PASS -h $HOST -e 'show databases' -s --skip-column-names); do
    mysqldump -u$USER -p$PASS -h $HOST --lock-tables=false $DB > "$BACKUPDIR/$DB.sql";
done

############# ZIP BACKUP #############
cd $BACKUPDIR
tar -zcvf backup-${NOW}.tar.gz *.sql

############# MOVE BACKUP TO REMOTE #############
rsync -avz $BACKUPDIR/backup-${NOW}.tar.gz root@$BACKUPSERVER:$BACKUPREMOTEDIR

# done
```

## Kubernetes CronJob Deployment

Finally we show you the kubernetes deployment for our agent.

In the deployment, our agent is defined as a CronJob that runs every 8 hours.
In addition, we have added an SSH key as a Conifg map so that this can write to the NAS and a certain security is given.

```yaml
apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: backup-mariadb
  namespace: default
spec:
  schedule: "0 8 * * *"
  successfulJobsHistoryLimit: 1
  failedJobsHistoryLimit: 1
  jobTemplate:
    spec:
      template:
        spec:
          containers:
            - name: cronjob-agent
              image: xxx/cronjob-agent
              command: ["bash", "/srv/jobs/backup-mariadb.sh"]
              volumeMounts:
                - mountPath: /root/.ssh/id_rsa.pub
                  name: cronjob-default-config
                  subPath: id_rsa.pub
                - mountPath: /root/.ssh/id_rsa
                  name: cronjob-default-config
                  subPath: id_rsa
                  readOnly: true
                - mountPath: /root/.ssh/config
                  name: cronjob-default-config
                  subPath: config
          volumes:
            - name: cronjob-default-config
              configMap:
                name: cronjob-default-config
                defaultMode: 256
          restartPolicy: Never
```