This article shows you how to install MySQL with an Azure Files Storage backend on Azure Kubernetes Service (AKS). Azure Files Storage is a highly available and scalable file storage that provides network shares in the cloud. A major benefit of Azure Files over Azure Managed Disk is that multiple containers can access the same storage.
This article will walk you through the deployment step-by-step so that you can ensure your installation of MySQL with Azure Files Storage and AKS runs smoothly. We will discuss how to create the needed resources in Kubernetes. I.e. how you start the MySQL Docker Image as a deployment in the pod, configure it, provide it as a service and test it at the end.
For the overview of these instructions, I use a separate
.yaml file for each Kubernetes resource. Of course you can also put them all in the same file if you separate them with three dashes
In principle, we need five different types of resources:
- Storage Class
Therefore, we first create the following five files:
deploy.yaml and ` ``service.yaml```. These will contain the complete resource description for k8s.
I use a new namespace
test-ns for the test files. If you don’t already have one, you can create it like this:
kubectl create namespace test-ns
AKS comes with a few predefined StorageClasses. Including the
azurefile-csi StorageClass. Normally it works perfectly. Unfortunately, we cannot use them with MySQL for the following reason.
The problem with azurefile-csi
MySQL tries to change the read and write permissions as well as the owner of the files. Also, MySQL tries to lure some files for InnoDB tables. The problem with this is that the AzureFiles file system does not store this information at all.
If you try with azurefile-csi, you might see errors like this in the logs:
2023-01-10T16:04:17.986024Z 0 [ERROR] [MY-012574] [InnoDB] Unable to lock ./#innodb_redo/#ib_redo0 error: 13 2023-01-10T16:04:17.991042Z 0 [ERROR] [MY-012894] [InnoDB] Unable to open './#innodb_redo/#ib_redo0' (error: 11).
It is also possible that the container does not start at all:
Back-off restarting failed container
As a workaround, we need to create our own StorageClass. To do this, we fill the corresponding file with the following content.
apiVersion: storage.k8s.io/v1 metadata: name: mysql-azurefile kind: StorageClass mountOptions: - dir_mode=0777 - file_mode=0777 - uid=999 - gid=999 - mfsymlinks - nobrl - cache=strict - nosharesock parameters: skuName: Standard_LRS provisioner: file.csi.azure.com reclaimPolicy: Delete volumeBindingMode: Immediate
Owner and group must be set to 999, then MySQL is happy and doesn’t try to change anything. Important: Some versions may behave differently. With MySQL container 5.7.16 and 8.0 this worked with 999.
Next we tell the Azure File Storage to set the permissions to 0777. This gives the containers that integrate this storage the illusion that they have full rights.
We can now roll out this file on our Kubernetes cluster.
kubectl apply -f storageclass.yaml
You should note that a storage class in Kubernetes is not tied to a namespace. If you already have “mysql-azurefile”, you have to change this name. You can check it with
kubectl get storageclass.
A ConfigMap is a Kubernetes resource that stores configuration data as key-value pairs. You can use them for configuring containers in a Kubernetes cluster. ConfigMaps allow you to inject environment variables, files, and other configurations into containers without packaging them directly into the image.
This allows us to later use the original MySQL image with our own configuration.
apiVersion: v1 kind: ConfigMap metadata: name: mysqld-cnf data: mysqld.cnf: | [mysqld] pid-file = /var/run/mysqld/mysqld.pid socket = /var/run/mysqld/mysqld.sock datadir = /var/lib/mysql # log-error = /var/log/mysql/error.log # By default we only accept connections from localhost #bind-address = 127.0.0.1 # Disabling symbolic-links is recommended to prevent assorted security risks symbolic-links=0 max_allowed_packet=500M
And roll out.
kubectl apply -f configmap.yaml -n test-ns
Persistent Volume Claim
A Persistent Volume Claim (PVC) is a request to the Kubernetes cluster to provision a Persistent Volume (PV). PVCs are used by Kubernetes pods to reserve memory for the data they need to keep even after they have restarted. They can also be used to make data accessible between different pods.
In our scenario we just have to make sure that we use the
mysql-azurefile storage class defined above.
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: azurefile-pvc spec: accessModes: - ReadWriteMany storageClassName: mysql-azurefile resources: requests: storage: 10Gi
Instead of “Storage: 10Gi” (~10 gigabytes) you can of course enter any other size. According to the documentation, the maximum limit is currently 5 TiB or 100 TiB with the feature for large file shares.
Thanks to the ReadWriteMany access mode, it is possible for several pods to connect to the storage at the same time.
kubectl apply -f pvc.yaml -n test-ns
A deployment serves to provision a desired number of pods that run a specific application or service. It is also possible to specify a deployment that provides a specific version of an application or service. You can also use deployments to update an existing application or service.
Here we define which container we want to use. I use the label “prj: mysqltest” here to be able to connect it to the service after.
apiVersion: apps/v1 kind: Deployment metadata: name: batchest-mysql labels: prj: mysqltest spec: selector: matchLabels: prj: mysqltest strategy: type: Recreate template: metadata: labels: prj: mysqltest spec: securityContext: runAsUser: 999 runAsGroup: 999 containers: - image: mysql:8.0 resources: requests: memory: "250Mi" cpu: "100m" limits: memory: "400Mi" cpu: "1000m" name: mysql # Fuer 5.7 willst du evtl diese args verwenden: #args: # - "--ignore-db-dir" # - "lost+found" env: - name: MYSQL_DATABASE value: mysql_db_name # creates a database called mysql_db_name # Fuer 5.7 willst du evtl MYSQL_USER auf root setzen: # - name: MYSQL_USER # value: root - name: MYSQL_ROOT_PASSWORD valueFrom: secretKeyRef: name: mysqlsecret key: MYSQL_DB_PASSWORD ports: - containerPort: 3306 name: mysql volumeMounts: - mountPath: /etc/mysql/conf.d readOnly: true name: mysqld-cnf - name: azurefile-pv mountPath: /var/lib/mysql subPath: sql volumes: - name: mysqld-cnf configMap: name: mysqld-cnf items: - key: mysqld.cnf path: meinsql.cnf - name: azurefile-pv persistentVolumeClaim: claimName: azurefile-pvc
Instead of the mysql:8.0 image you can of course also use the still widespread version 5.7. You also need to check and possibly increase the RAM/CPU limits.
Note that I include 2 volumes here: one for the ConfigMap mysqld-cnf and one for the persistent volume claim azurefile-pvc.
However, it doesn’t work that way yet. I didn’t want to write the password directly into the configuration. That’s why I say in deployment that the password is in a secret. Of course, you also have to generate this secret:
kubectl create secret generic mysqlsecret --from-literal=MYSQL_DB_PASSWORD=ABC123 -n test-ns
This configuration sets up the MySQL user
root with the password
The pod is started by the deployment after the following command.
kubectl apply -f deploy.yaml -n test-ns
A service in Kubernetes is a part of the network infrastructure that can be viewed as the entry and exit point for client requests. First, it allows the pods within the cluster to communicate with each other and provides a unified, logical address at which multiple pods can be reached. On the other hand, a service also allows access to pods from the external network if you configure it accordingly.
In this example, I’m using an internal service because I don’t want my MySQL server to be reachable from outside the cluster.
apiVersion: v1 kind: Service metadata: name: mysqlservice labels: prj: mysqltest spec: ports: - port: 3306 selector: prj: mysqltest clusterIP: None
kubectl apply -f service.yaml -n test-ns
So our container can be reached under the name
Here’s a quick test to see if everything works.
- List Pods
kubectl get pods -n test-ns
NAME READY STATUS RESTARTS AGE batchest-mysql-858cb8f7-l9tm4 1/1 Running 0 5m
- Connect to Pod
kubectl exec -it batchest-mysql-858cb8f7-l9tm4 -n test-ns -- /bin/sh
- Login to MySQL
mysql -u root -p
Enter password: Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 8 Server version: 8.0.31 MySQL Community Server - GPL Copyright (c) 2000, 2022, Oracle and/or its affiliates. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql>
Could I help? Buy me a drink ! 💙