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.

  1. Pods auflisten.
kubectl get pods -n test-ns
NAME                            READY   STATUS    RESTARTS   AGE
batchest-mysql-858cb8f7-l9tm4   1/1     Running   0          5m
  1. Mit Pod verbinden
kubectl exec -it batchest-mysql-858cb8f7-l9tm4 -n test-ns -- /bin/sh
sh-4.4$
  1. 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! 💙