Laptop with Kubernetes and MySQL logo on the screen

Let’s get MySQL up and running on Kubernetes

Make sure that you've taken a look at the following tutorial(s) before you continue:

Your one node cluster should be up and running. Persistent storage for MySQL should be created by now. Persistent disk should be formatted and detached from the instance.

In case of errors first of all check if it's not a badly formatted YAML file. Check the number of spaces in an indent. Count spaces in the line indicated if you have to.

error: yaml: line XX: did not find expected key

Next step is to consult last two articles in Useful links section. It's a two part series by Ross Kukulinski about solving issues with Kubernetes.

Commands presented in this tutorial should be executed from the root of tutorial's git repository.

You are going to learn

  • how to run something using Kubernetes inside your own node cluster deployed in the cloud
  • how to create a Namespace, Secret, Persistent Volume, Persistent Volume Claim, Deployment and Service

MySQL relational database on Kubernetes

Data source is usually the first thing one sets up on a new project. This tutorial will guide you through the process of running a MySQL docker container on Kubernetes. MySQL will keep it’s files on the formatted persistent storage prepared during the previous tutorial. Enough talk ! Let’s do it !

Clone git repository

Reliability: HighUpdated: 1 October 2017CopiedBookmarkedBookmark removed

Download all the code that you will need for this tutorial from GitHub. All the kubectl commands presented in this tutorial should be executed from the root of this git repository.

git clone

Add custom namespace to the cluster

Reliability: HighUpdated: 1 October 2017CopiedBookmarkedBookmark removed

Namespaces are virtual clusters inside your cluster. You use them whether you want or not. If you do not declare your own namespace then everything you create will live in a namespace called default. This might become problematic (Limit Range) so create your own. Newly created namespace will have no Limit Range set so you don't have to worry about it in this tutorial.

kubectl describe ns default
kubectl apply -f k8s/namespace.yaml

List all the namespaces in your cluster.

kubectl get namespaces –show-labels

Show details of the namespace that has just been created. Instead of using full name of the object in your commands, you can use abbreviated alias ns.

kubectl describe ns main

Get the name of your cluster and user

Reliability: HighUpdated: 1 October 2017CopiedBookmarkedBookmark removed

View configuration of kubectl

kubectl config view
cat ~/.kube/config

You should see a section that looks like this. cluster - name of your cluster, user - user who has access to the cluster, name - name of the context

– context:
    cluster: gke_projectname_europe-west1-d_clustername
    user: gke_projectname_europe-west1-d_clustername
  name: gke_projectname_europe-west1-d_clustername

Create a context for your namespace

Reliability: HighUpdated: 1 October 2017CopiedBookmarkedBookmark removed

Contexts are a way to send requests to specific cluster. Imagine that you manage multiple clusters. You can comfortably switch between the contexts using kubectl and underneath all the authentication required is done for you. Furthermore you can pass a namespace to the context.

kubectl config set-context main --namespace=main --cluster=CLUSTER_NAME --user=USER_NAME

You can switch between contexts with

kubectl config use-context main

View currently used context

kubectl config current-context

Check how the configuration has changed after your context was added

kubectl config view

Step notes

If the namespace is not declared inside .yaml file then Kubernetes object will be created in current context/namespace.
The only time I had to explicitly declare the namespace was with Persistent Volume containing a reference to a Persistent Volume Claim (claimRef).

Create a Secret

Reliability: HighUpdated: 1 October 2017CopiedBookmarkedBookmark removed

Secrets in Kubernetes hold data of sensitive nature such as passwords, usernames, API tokens etc.

Create MySQL Secret that will hold password of MySQL's root user. First you will need to base64 encode the data. Newlines are not valid withing base64 string.

Encode using base64 your desired MySQL root's password. If you worry about security then create a file and put the password inside it before encoding.

echo -n "PASSWORD_HERE" | base64 -w 0
echo -n "PASSWORD_HERE" | base64 | tr -d '\n'

Paste encoded string inside k8s/mysql/secret.yaml file which holds the MySQL secret. There should be no newlines present when you paste base64 encoded data into .yaml files.

Create and view a Secret.

kubectl apply -f k8s/mysql/secret.yaml
kubectl get secret
kubectl get secret --namespace=main
kubectl describe secret SECRET_NAME

Remember that you can double tap TAB key to autocomplete pretty much anything.

Create a Persistent Volume

Reliability: HighUpdated: 1 October 2017CopiedBookmarkedBookmark removed

First you need to let Persistent Volume know which persistent disk should be used. Modify k8s/mysql/volume.yaml and paste your MySQL disk's name into pdName. Persistent Volumes are not namespaced objects. When you choose their name, prefix it with project's or application's name for example. It will save you some trouble in the future.

 fsType: ext4

Create and view a Persistent Volume

kubectl apply -f k8s/mysql/volume.yaml
kubectl get pv
kubectl get pv --namespace=main
kubectl describe pv PV_NAME

Namespace main is mentioned explicitly inside k8s/mysql/volume.yaml but not in the rest of the files. This is required if you want to point to specific Persistent Volume Claim because their names are unique only within a namespace.

Create and view a Persistent Volume Claim

Reliability: HighUpdated: 1 October 2017CopiedBookmarkedBookmark removed

Create a Persistent Volume Claim that was referenced inside our Persistent Volume in claimRef. After creation, the Claim will "consume" Persistent Volume resource. Those two objects will become bound. Status of both resources will change to Bound at that moment.

kubectl apply -f k8s/mysql/volume-claim.yaml
kubectl get pvc
kubectl get pvc --namespace=main
kubectl describe pvc PVC_NAME

Deleting Persistent Volume or Persistent Volume Claim does not remove the files on the persistent disks unless Persistent Volume's persistentVolumeReclaimPolicy is set to Delete. Use Recycle policy for most cases.

Create a Deployment

Reliability: HighUpdated: 1 October 2017CopiedBookmarkedBookmark removed

Think of Deployment as a way to run docker images (containers) inside Kubernetes namespace. In the Kubernetes documentation you will find a lot of references toPods. For the sake of simplicity assume for now that a Deployment equals one Pod which equals one docker container. It could not be further from the truth but just use your imagination.

Look inside k8s/mysql/deployment.yaml file.
The Deployment declared there will contain one container which will be created using an image pulled from Docker Hub. The image used is an official MySQL docker image (

After pulling specified image, Kubernetes will try to run it. If everything is fine then the Deployment will become available.

Notice that the Deployment is using the Secret containing the MySQL root password. This secret will be available inside the running container as an environment variable called MYSQL_ROOT_PASSWORD. MySQL official docker image knows what to do with it.

Also a volume is mounted to the Deployment which references Persistent Volume Claim that has just been created. Effectively /var/lib/mysql directory where MySQL keeps it's data will reside on the persistent disk that was named inside the Perisitent Volume declaration. If you ever decide to move to a cloud platform supplier other than Google then all you have to do is transfer your data.

Label tier: mysql has been assigned to the Deployment.

Deployment details will be discussed in depth in the next part of this tutorial.

Create and view the Deployment

kubectl apply -f k8s/mysql/deployment.yaml
kubectl get deployment
kubectl get deployment --namespace=main
kubectl describe deployment DEPLOYMENT_NAME

Your Deployment should have no problem becoming available. If it is not so then check what is wrong using previously listed commands or in the status part of the output of following command

kubectl get deployment -o yaml

Examine Pod created by the Deployment

Reliability: HighUpdated: 1 October 2017CopiedBookmarkedBookmark removed

Creating the Deployment has also created a corresponding Pod in the process

kubectl get pod
kubectl describe pod POD_NAME

You can execute something on that Pod. Let's connect to it and start bash login shell.

kubectl exec -it POD_NAME -- bash -l

You can view the logs of a Pod.

kubectl logs POD_NAME
kubectl logs POD_NAME -f

You can copy files between your local machine and the Pod.

kubectl cp file.txt POD_NAME:/tmp/
kubectl cp POD_NAME:/tmp/file.txt ./file-copy.txt

Create a Service and connect to MySQL

Reliability: HighUpdated: 1 October 2017CopiedBookmarkedBookmark removed

You need to create a Service now. It is an abstraction that protects you against the case where something underneath it (eg. a Pod) dies and is not available. This Service exposes MySQL's standard port 3306 and points to Pods matching the selector tier: mysql. Deployment created before has that exact label. Get it ? In Kubernetes you can connect and group objects using labels. By adding another label in both declarations, you could for example target MySQL Deployments that are in production or development environment. You will learn all about it in next parts of this tutorial series.

kubectl apply -f k8s/mysql/service.yaml
kubectl get svc
kubectl get service
kubectl get service --namespace=main
kubectl describe service SERVICE_NAME

Now let's connect to our MySQL running Pod and run bash first. Then connect to the MySQL database and use the name of the Service as host name (wordpress-mysql). Give MySQL root's password when asked.

kubectl exec -it POD_NAME -- bash -l
mysql -hwordpress-mysql -uroot -p

As you can see Service name becomes a host name inside your namespace. Simple, right ? You can use that name to connect from anywhere within your namespace.

exit twice to leave MySQL prompt and then bash.

Good job. You deserve a bottle of Grog. Arrrr !
Success ! Let's open a case of Rum. Arrrr !
New skills gained. Beers all around! Arrrr !
Thanks for reading ! Please
  • 1

Leave a comment

Your email address will not be published. Required fields are marked *

One thought on “Let’s get MySQL up and running on Kubernetes

  • Chris McKenna

    Ok . I found a solution on line, it appears once i got pasted the selector phase there is an issue with persistent volumes have “lost+found” directory. So i fixed it following this link :

    They recommened using a subpath on the mountpoint. This cleared up the problem. Here is the yaml i used .. Its missing the namespace main.

    apiVersion: apps/v1 # for versions of Kubernetes <1.9.0 use apps/v1beta2
    kind: Deployment
    name: wordpress-mysql
    app: wordpress
    app: wordpress
    tier: mysql
    type: Recreate
    app: wordpress
    tier: mysql
    – image: mysql
    name: mysql
    name: mysql-secret
    – containerPort: 3306
    name: mysql
    – name: mysql-persistent-storage
    mountPath: /var/lib/mysql
    subPath: mysql
    – name: mysql-persistent-storage
    claimName: mysql-pvc