Migrate MySQL w/ TLS-based rsync
This tutorial will show how to migrate a MySQL database using VolSync’s rsync-tls data mover.
Environment
This example will walk through deploying, setting up replication, then migrating MySQL between two OpenShift clusters in AWS.
Prerequisites:
Access to two OpenShift clusters (
east2
andwest2
). Optional: just use different Namespaces in the same clusterkubectl
access to the cluster(s).VolSync is installed in both clusters
Working installation of Helm
A CSI-based StorageClass for the database’s persistent data.
The persistent storage used for the database will come from EBS, using the ebs-csi driver:
$ kubectl --context east2 get storageclass/gp3-csi -oyaml allowVolumeExpansion: true apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: annotations: storageclass.kubernetes.io/is-default-class: "true" creationTimestamp: "2022-12-01T13:18:24Z" name: gp3-csi resourceVersion: "5233" uid: b7a8872e-f356-4498-8324-7d11061bc043 parameters: encrypted: "true" type: gp3 provisioner: ebs.csi.aws.com reclaimPolicy: Delete volumeBindingMode: WaitForFirstConsumer
Deploy MySQL
The Bitnami Helm chart for MySQL will be used to deploy the database. Start by adding the Bitnami Helm repo:
$ helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add bitnami https://charts.bitnami.com/bitnami
Then install the mysql chart into the mysql
namespace:
$ helm install --kube-context east2 --create-namespace --namespace mysql \
--set auth.rootPassword=mypassword \
--set primary.podSecurityContext.enabled=false \
--set primary.containerSecurityContext.enabled=false \
--set primary.persistence.storageClass=gp3-csi \
mysql bitnami/mysql
NAME: mysql
LAST DEPLOYED: Thu Dec 1 11:46:59 2022
NAMESPACE: mysql
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
CHART NAME: mysql
CHART VERSION: 9.4.4
APP VERSION: 8.0.31
** Please be patient while the chart is being deployed **
Tip:
Watch the deployment status using the command: kubectl get pods -w --namespace mysql
Services:
echo Primary: mysql.mysql.svc.cluster.local:3306
Execute the following to get the administrator credentials:
echo Username: root
MYSQL_ROOT_PASSWORD=$(kubectl get secret --namespace mysql mysql -o jsonpath="{.data.mysql-root-password}" | base64 -d)
To connect to your database:
1. Run a pod that you can use as a client:
kubectl run mysql-client --rm --tty -i --restart='Never' --image docker.io/bitnami/mysql:8.0.31-debian-11-r10 --namespace mysql --env MYSQL_ROOT_PASSWORD=$MYSQL_ROOT_PASSWORD --command -- bash
2. To connect to primary service (read/write):
mysql -h mysql.mysql.svc.cluster.local -uroot -p"$MYSQL_ROOT_PASSWORD"
Ensure that the database has successfully deployed:
$ kubectl --context east2 -n mysql get all,pvc
NAME READY STATUS RESTARTS AGE
pod/mysql-0 1/1 Running 0 3m32s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/mysql ClusterIP 172.30.81.8 <none> 3306/TCP 3m32s
service/mysql-headless ClusterIP None <none> 3306/TCP 3m32s
NAME READY AGE
statefulset.apps/mysql 1/1 3m32s
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/data-mysql-0 Bound pvc-e90001a9-8493-45cc-87af-f9b4aefa790d 8Gi RWO gp3-csi 3m32s
Create a destination Namespace
Create a corresponding Namespace on the destination (west2) cluster:
$ kubectl --context west2 create ns mysql
namespace/mysql created
Create a key to secure the replication
Generate a shared key for the replication relationship:
$ KEY=1:$(openssl rand -hex 32)
Add it as a Secret in the mysql
Namespace on both clusters:
$ kubectl --context east2 -n mysql create secret generic east-west --from-literal psk.txt=$KEY
secret/east-west created
$ kubectl --context west2 -n mysql create secret generic east-west --from-literal psk.txt=$KEY
secret/east-west created
Create the ReplicationDestination
This step creates the ReplicationDestination on the west cluster. Since there is no network tunnel between the clusters, you will use a LoadBalancer Service so that the source (east) cluster will be able to connect. You will use a volume size that matches the source (8Gi).
$ kubectl --context west2 -n mysql create -f - <<EOF
apiVersion: volsync.backube/v1alpha1
kind: ReplicationDestination
metadata:
name: mysql
spec:
rsyncTLS:
copyMethod: Snapshot
capacity: 8Gi
accessModes: ["ReadWriteOnce"]
storageClassName: gp3-csi
volumeSnapshotClassName: csi-aws-vsc
keySecret: east-west
serviceType: LoadBalancer
EOF
replicationdestination.volsync.backube/mysql created
Verify that the destination has been properly created by looking at the new ReplicationDestination object:
$ kubectl --context west2 -n mysql get replicationdestination/mysql -oyaml
apiVersion: volsync.backube/v1alpha1
kind: ReplicationDestination
metadata:
creationTimestamp: "2022-12-06T13:31:38Z"
generation: 1
name: mysql
namespace: mysql
resourceVersion: "302334"
uid: f3149aee-c399-462b-ac92-63ff756f4da8
spec:
rsyncTLS:
accessModes:
- ReadWriteOnce
capacity: 8Gi
copyMethod: Snapshot
keySecret: east-west
serviceType: LoadBalancer
storageClassName: gp3-csi
volumeSnapshotClassName: csi-aws-vsc
status:
conditions:
- lastTransitionTime: "2022-12-06T13:31:38Z"
message: Synchronization in-progress
reason: SyncInProgress
status: "True"
type: Synchronizing
lastSyncStartTime: "2022-12-06T13:31:38Z"
rsyncTLS:
address: abad73aa2ca4441ed8c9e13f1095c453-95258c1d3ff95327.elb.us-west-2.amazonaws.com
Above, the .status.conditions.Synchronizing
status is True
, and the
external address is available in .status.rsyncTLS.address
. This is the
address you will use to configure the source. It may take a few minutes for the
LoadBalancer address to be assigned and appear in the status.
Create the ReplicationSource
On the east cluster, you will create the corresponding ReplicationSource. Since
the EBS CSI driver does not support volume cloning, you will need to specify a
copyMethod
of Snapshot
.
Create the source:
$ kubectl --context east2 -n mysql create -f - <<EOF
apiVersion: volsync.backube/v1alpha1
kind: ReplicationSource
metadata:
name: mysql
spec:
sourcePVC: data-mysql-0
trigger:
schedule: "*/5 * * * *"
rsyncTLS:
keySecret: east-west
address: abad73aa2ca4441ed8c9e13f1095c453-95258c1d3ff95327.elb.us-west-2.amazonaws.com
copyMethod: Snapshot
volumeSnapshotClassName: csi-aws-vsc
EOF
replicationsource.volsync.backube/mysql created
Verify that the source has been created:
$ kubectl --context east2 -n mysql get replicationsource/mysql -oyaml
apiVersion: volsync.backube/v1alpha1
kind: ReplicationSource
metadata:
creationTimestamp: "2022-12-06T13:33:51Z"
generation: 1
name: mysql
namespace: mysql
resourceVersion: "438380"
uid: 5721c629-a1d9-447e-a009-92d752344b3f
spec:
rsyncTLS:
address: abad73aa2ca4441ed8c9e13f1095c453-95258c1d3ff95327.elb.us-west-2.amazonaws.com
copyMethod: Snapshot
keySecret: east-west
volumeSnapshotClassName: csi-aws-vsc
sourcePVC: data-mysql-0
trigger:
schedule: '*/5 * * * *'
status:
conditions:
- lastTransitionTime: "2022-12-06T13:50:21Z"
message: Waiting for next scheduled synchronization
reason: WaitingForSchedule
status: "False"
type: Synchronizing
lastSyncDuration: 21.447114717s
lastSyncTime: "2022-12-06T13:50:21Z"
nextSyncTime: "2022-12-06T13:55:00Z"
rsyncTLS: {}
Above, you can see that synchronization has completed (lastSyncTime
is set),
and the replication took 21 seconds (lastSyncDuration
).
Next, you will log into the database and make changes.
Log in to the database
Exec into the pod, using the password set during deployment (mypassword
):
$ kubectl --context east2 -n mysql exec -it sts/mysql -- mysql -u root -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 660
Server version: 8.0.31 Source distribution
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>
Add a new database
List the current databases with the show
command:
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| my_database |
| mysql |
| performance_schema |
| sys |
+--------------------+
5 rows in set (0.00 sec)
Use the create
command to add a new database, then log out:
mysql> create database newdb;
Query OK, 1 row affected (0.00 sec)
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| my_database |
| mysql |
| newdb |
| performance_schema |
| sys |
+--------------------+
6 rows in set (0.00 sec)
mysql> quit;
Bye
Wait for the changes to replicate
The system is configured to replicate every 5 minutes, so check the current time, then wait for the next synchronization interval to run:
$ date
Tue Dec 6 08:59:08 AM EST 2022
$ kubectl --context east2 -n mysql get replicationsource/mysql -oyaml
apiVersion: volsync.backube/v1alpha1
kind: ReplicationSource
metadata:
creationTimestamp: "2022-12-06T13:33:51Z"
generation: 1
name: mysql
namespace: mysql
resourceVersion: "442579"
uid: 5721c629-a1d9-447e-a009-92d752344b3f
spec:
rsyncTLS:
address: abad73aa2ca4441ed8c9e13f1095c453-95258c1d3ff95327.elb.us-west-2.amazonaws.com
copyMethod: Snapshot
keySecret: east-west
volumeSnapshotClassName: csi-aws-vsc
sourcePVC: data-mysql-0
trigger:
schedule: '*/5 * * * *'
status:
conditions:
- lastTransitionTime: "2022-12-06T14:00:22Z"
message: Waiting for next scheduled synchronization
reason: WaitingForSchedule
status: "False"
type: Synchronizing
lastSyncDuration: 22.094321374s
lastSyncTime: "2022-12-06T14:00:22Z"
nextSyncTime: "2022-12-06T14:05:00Z"
rsyncTLS: {}
Above, you can see that the lastSyncTime is after the time when the database changes were made, so these changes should now be available on the destination.
The replicated data is now held in a VolumeSnapshot on the destination (west cluster). This needs to be converted to a PVC for use by the database.
Pause replication
To make it easier to restore the latest replicated image, you should pause replication on the destination cluster.
Add paused: True
to the ReplicationDestination’s .spec
using kubectl
edit
:
$ kubectl --context west2 -n mysql edit replicationdestination/mysql
# ... add "paused: True", and save
replicationdestination.volsync.backube/mysql edited
Determine the latest image
Determine the name of the snapshot that holds the most recent copy of replicated data:
$ kubectl --context west2 -n mysql get replicationdestination/mysql -oyaml
apiVersion: volsync.backube/v1alpha1
kind: ReplicationDestination
metadata:
creationTimestamp: "2022-12-06T13:31:38Z"
generation: 2
name: mysql
namespace: mysql
resourceVersion: "321738"
uid: f3149aee-c399-462b-ac92-63ff756f4da8
spec:
paused: true
rsyncTLS:
accessModes:
- ReadWriteOnce
capacity: 8Gi
copyMethod: Snapshot
keySecret: east-west
serviceType: LoadBalancer
storageClassName: gp3-csi
volumeSnapshotClassName: csi-aws-vsc
status:
conditions:
- lastTransitionTime: "2022-12-06T14:15:21Z"
message: Synchronization in-progress
reason: SyncInProgress
status: "True"
type: Synchronizing
lastSyncDuration: 5m5.367038355s
lastSyncStartTime: "2022-12-06T14:15:21Z"
lastSyncTime: "2022-12-06T14:15:21Z"
latestImage:
apiGroup: snapshot.storage.k8s.io
kind: VolumeSnapshot
name: volsync-mysql-dst-20221206141521
rsyncTLS:
address: abad73aa2ca4441ed8c9e13f1095c453-95258c1d3ff95327.elb.us-west-2.amazonaws.com
Above, you can see that the latest Snapshot is
volsync-mysql-dst-20221206141521
.
Restore the Snapshot
Create a new PVC, using the latestImage
from the ReplicationDestination:
$ kubectl --context west2 -n mysql create -f - <<EOF
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: restored
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 8Gi
storageClassName: gp3-csi
dataSource:
apiGroup: snapshot.storage.k8s.io
kind: VolumeSnapshot
name: volsync-mysql-dst-20221206141521
EOF
persistentvolumeclaim/restored created
The PVC is now waiting for a Pod to attempt to use the data:
$ kubectl --context west2 -n mysql describe pvc/restored
Name: restored
Namespace: mysql
StorageClass: gp3-csi
Status: Pending
Volume:
Labels: <none>
Annotations: <none>
Finalizers: [kubernetes.io/pvc-protection]
Capacity:
Access Modes:
VolumeMode: Filesystem
DataSource:
APIGroup: snapshot.storage.k8s.io
Kind: VolumeSnapshot
Name: volsync-mysql-dst-20221206141521
Used By: <none>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal WaitForFirstConsumer 6s (x2 over 20s) persistentvolume-controller waiting for first consumer to be created before binding
Start the database on the destination
Deploy the MySQL Helm chart on the destination, but instead of provisioning a new PVC for the database, you will use the one you have just restored.
helm install --kube-context west2 --namespace mysql \
--set auth.rootPassword=mypassword \
--set primary.podSecurityContext.enabled=false \
--set primary.containerSecurityContext.enabled=false \
--set primary.persistence.existingClaim=restored \
mysql bitnami/mysql
NAME: mysql
LAST DEPLOYED: Tue Dec 6 09:26:39 2022
NAMESPACE: mysql
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
CHART NAME: mysql
CHART VERSION: 9.4.4
APP VERSION: 8.0.31
** Please be patient while the chart is being deployed **
Tip:
Watch the deployment status using the command: kubectl get pods -w --namespace mysql
Services:
echo Primary: mysql.mysql.svc.cluster.local:3306
Execute the following to get the administrator credentials:
echo Username: root
MYSQL_ROOT_PASSWORD=$(kubectl get secret --namespace mysql mysql -o jsonpath="{.data.mysql-root-password}" | base64 -d)
To connect to your database:
1. Run a pod that you can use as a client:
kubectl run mysql-client --rm --tty -i --restart='Never' --image docker.io/bitnami/mysql:8.0.31-debian-11-r10 --namespace mysql --env MYSQL_ROOT_PASSWORD=$MYSQL_ROOT_PASSWORD --command -- bash
2. To connect to primary service (read/write):
mysql -h mysql.mysql.svc.cluster.local -uroot -p"$MYSQL_ROOT_PASSWORD"
Verify the restored contents
Log into the database and verify that newdb
is present:
$ kubectl --context west2 -n mysql exec -it sts/mysql -- mysql -u root -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 34
Server version: 8.0.31 Source distribution
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> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| my_database |
| mysql |
| newdb |
| performance_schema |
| sys |
+--------------------+
6 rows in set (0.00 sec)
mysql> quit;
Bye
Congratulations, you have just migrated the database to the west cluster!