In diesem Artikel erfährst du, wie du MySQL mit einem Azure Files Storage Backend in Azure Kubernetes Service (AKS) installierst. Azure Files Storage ist ein hochverfügbarer und skalierbarer Dateispeicher, der Netzwerkfreigaben in der Cloud bereitstellt. Ein großer Vorteil von Azure Files im Gegensatz zu Azure Managed Disk ist, dass mehrere Container auf denselben Speicher zugreifen können.
Dieser Artikel wird dich Schritt für Schritt durch das Deployment leiten, damit du sicherstellen kannst, dass deine Installation von MySQL mit Azure Files Storage und AKS reibungslos verläuft. Wir werden besprechen, wie du die benötigten Ressourcen in Kubernetes erstellst. D.h. wie du das MySQL Docker Image als Deployment im Pod startest, es konfigurierst, als Service bereitstellst und es am Ende testest.
Viel Spaß!
Vorbereitung
Zur Übersicht dieser Anleitung verwende ich pro Kubernetes Ressource eine eigene .yaml
Datei. Natürlich kannst du auch alle in dieselbe Datei schreiben, wenn du sie mit drei Strichen ---
trennst.
Prinzipiell brauchen wir fünf verschiedene Ressourcentypen:
- StorageClass
- ConfigMap
- PersistentVolumeClaim
- Deployment
- Service
Deswegen erstellen wir als erstes folgende fünf Dateien: storageclass.yaml
, configmap.yaml
, pvc.yaml
, deploy.yaml
und service.yaml
. Diese werden die komplette Ressourcenbeschreibung für k8s enthalten.
Ich benutze für die Testdateien einen neuen Namespace test-ns
. Wenn du nicht schon einen hast kannst, du ihn so erstellen:
kubectl create namespace test-ns
Storage Class
AKS kommt mit ein paar vordefinierten Speicherklassen (engl: StorageClasses) daher. Darunter auch die azurefile-csi
StorageClass. Normalerweise funktioniert die einwandfrei. Leider können wir sie bei MySQL aus folgenden Grund nicht gebrauchen.
Das Problem mit azurefile-csi
MySQL versucht die Lese- und Schreib-Rechte sowie den Besitzer der Dateien zu verändern. Außerdem versucht MySQL manche Dateien für InnoDB Tabellen zu locken. Das Problem daran ist, dass das AzureFiles Dateisystem diese Informationen gar nicht speichert.
Wenn du es mit azurefile-csi probierst, könntest du Errors wie diese hier in den Logs sehen:
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).
Möglicherweise startet der Container auch gar nicht:
Back-off restarting failed container
Lösung
Als Workaround müssen wir unsere eigene StorageClass erzeugen. Dazu füllen wir die entsprechende Datei mit folgendem Inhalt.
storageclass.yaml
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
Besitzer und Gruppe müssen auf 999 gesetzt werden, dann ist MySQL zufrieden und versucht nichts zu ändern. Wichtig: Es kann sein, dass sich manche Versionen anders verhalten. Bei dem MySQL Container 5.7.16 und 8.0 hat das mit 999 funktioniert.
- uid=999
- gid=999
Als Nächstes sagen wir dem Azure File Storage, dass die Rechte auf 0777 gesetzt werden. Das gaukelt den Containern, die diesen Storage einbinden vor, dass sie volle Rechte haben.
- dir_mode=0777
- file_mode=0777
Diese Datei können wir jetzt auf unserem Kubernetes Cluster ausrollen.
kubectl apply -f storageclass.yaml
Du solltest beachten, dass eine StorageClass bei Kubernetes nicht an einen Namespace gebunden ist. Falls es “mysql-azurefile” bei dir schon gibt, musst du diesen Namen anpassen. Du kannst es mit kubectl get storageclass
überprüfen.
Config Map
Eine ConfigMap ist eine Kubernetes-Ressource, die Konfigurationsdaten als Schlüssel-Wert-Paare speichert. Du kannst sie für das Konfigurieren von Containern in einem Kubernetes-Cluster verwenden. ConfigMaps ermöglichen es dir, Umgebungsvariablen, Dateien und andere Konfigurationen in Container zu injizieren, ohne sie direkt in das Image zu packen.
Dadurch können wir später das originale MySQL Image mit unserer eigenen Konfiguration benutzen.
configmap.yaml
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
Und ausrollen.
kubectl apply -f configmap.yaml -n test-ns
Persistent Volume Claim
Ein Persistent Volume Claim (PVC) ist eine Anforderung an den Kubernetes-Cluster, ein Persistent Volume (PV) bereitzustellen. PVCs werden von Kubernetes-Pods verwendet, um Speicher für die Daten zu reservieren, die sie behalten müssen, auch nachdem sie neu gestartet wurden. Sie können auch dafür genutzt werden, dass Daten zwischen verschiedenen Pods zugänglich sind.
In unserem Szenario müssen wir nur darauf achten, dass wir die oben definierte mysql-azurefile
Speicherklasse verwenden.
pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: azurefile-pvc
spec:
accessModes:
- ReadWriteMany
storageClassName: mysql-azurefile
resources:
requests:
storage: 10Gi
Anstelle von “Storage: 10Gi” (~10 Gigabyte) kannst du natürlich jede andere Größe eintragen. Das maximale Limit ist laut der Dokumentation aktuell bei 5 TiB bzw. 100 TiB mit aktiviertem Feature für große Dateifreigaben.
Dank dem ReadWriteMany access mode ist es möglich, dass sich mehrere Pods gleichzeitig mit dem Storage verbinden können.
Aktivieren.
kubectl apply -f pvc.yaml -n test-ns
Deployment
Ein Deployment dient dazu, eine gewünschte Anzahl von Pods bereitzustellen, die eine bestimmte Anwendung oder einen Dienst ausführen. Es ist auch möglich, ein Deployment zu spezifizieren, das eine bestimmte Version einer Anwendung oder eines Dienstes bereitstellt. Deployments kannst du auch zum Aktualisieren einer bestehenden Anwendung oder eines Dienstes verwenden.
Hier definieren wir, welchen Container wir verwenden wollen. Ich benutze hier das Label “prj: mysqltest” um es nachher mit dem Service verbinden zu können.
deploy.yaml
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
Anstelle des mysql:8.0 Images kannst du natürlich auch die noch weit verbreitete Version 5.7 verwenden. Die RAM/CPU Limits musst du auch überprüfen und eventuell erhöhen.
Beachte, dass ich hier 2 Volumes einbinde: eines für die ConfigMap mysqld-cnf und eines für den persistent volume claim azurefile-pvc.
So funktioniert es allerdings noch nicht. Ich wollte das Passwort nicht direkt in die Konfiguration schreiben. Deswegen sage ich im Deployment, dass das Passwort in einem Secret liegt. Dieses Secret musst du natürlich auch erzeugen:
kubectl create secret generic mysqlsecret --from-literal=MYSQL_DB_PASSWORD=ABC123 -n test-ns
Mit dieser Konfiguration wird der MySQL User root
mit dem Passwort ABC123
eingerichtet.
Der Pod wird nach folgendem Befehl vom Deployment gestartet.
kubectl apply -f deploy.yaml -n test-ns
Service
Ein Service in Kubernetes ist ein Bestandteil der Netzwerk-Infrastruktur, der als Eintritts- und Ausgangspunkt für Client-Anfragen angesehen werden kann. Zum einen ermöglicht es den Pods innerhalb des Clusters, miteinander zu kommunizieren, und stellt eine einheitliche, logische Adresse bereit, unter der mehrere Pods erreicht werden können. Zum anderen ermöglicht ein Service auch den Zugriff auf Pods aus dem externen Netzwerk, wenn du ihn entsprechend konfigurierst.
In diesem Beispiel benutze ich einen internen Service, da ich nicht will, dass mein MySQL Server von außerhalb des Clusters erreichbar ist.
service.yaml
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
Somit ist unser Container unter dem Namen mysqlservice
erreichbar.
Finaler Test
Hier ein kurzer Test, ob alles funktioniert.
- Pods auflisten.
kubectl get pods -n test-ns
NAME READY STATUS RESTARTS AGE
batchest-mysql-858cb8f7-l9tm4 1/1 Running 0 5m
- Mit Pod verbinden
kubectl exec -it batchest-mysql-858cb8f7-l9tm4 -n test-ns -- /bin/sh
sh-4.4$
- In MySQL einloggen
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>
Konnte ich helfen? Ich freue mich über einen Drink!
💙