Seamlessly Migrating Applications Between Federated Kubernetes Clusters with LINSTOR

Matt Kereczman, Solutions Architect at LINBIT

Björn Kraus, Chief Technical Officer at PHOENIX MEDIA


When running stateful Kubernetes workloads, migrating applications and their persistent volumes between Kubernetes clusters isn’t trivial. Common methods used to overcome this challenge typically include some form of volume backup and restoration, a process which usually involves downtime while data is being restored in the target cluster. However, if you’re using LINSTOR® for persistent storage in Kubernetes, it’s possible to move workloads, including their persistent volumes, from one cluster to another with minimal downtime and without data loss.

PHOENIX MEDIA, a LINBIT® customer, accomplished this by connecting a second Kubernetes cluster to the LINSTOR controller running within the first cluster, allowing them to share the same storage back end. This clever technique enabled a clean migration of persistent volumes and workloads, using mostly standard Kubernetes tools and LINSTOR’s internal custom resource definitions (CRDs).

This blog outlines that migration process step-by-step, complete with YAML examples and the shell scripts that the PHOENIX MEDIA team used in their migration process. The entire procedure involves:

  • Connecting two clusters to the same LINSTOR controller
  • Exporting and importing PVCs and PVs between clusters
  • Handling workload failover gracefully
  • Optionally reassigning control to the new cluster

Environment details

The procedure begins with two separate Kubernetes clusters:

  • Cluster A is the original cluster with the Piraeus Operator installed and using LINSTOR to manage storage.
  • Cluster B is the new cluster that will take over workload responsibility.

In Cluster A:

  • Piraeus was deployed.
  • A PVC was created and used by a deployment.
  • LINSTOR satellites were running as expected.

In Cluster B:

  • Kubernetes was deployed.

📝 NOTE: The Piraeus Operator, part of the upstream open source Piraeus Datastore CNCF project, is functionally equivalent to the LINSTOR Operator. Both are open source tools for deploying and managing LINSTOR clusters in Kubernetes, and use the same CRDs and architecture. The key difference is that the Piraeus Operator uses community-maintained container images available to everyone, while the LINSTOR Operator deploys LINBIT enterprise images, which require a subscription to access and are officially supported by LINBIT.

Expose the LINSTOR controller in Cluster A

To allow Cluster B to communicate with the existing LINSTOR controller, expose it as a NodePort service in Cluster A by using the following configuration:

apiVersion: v1
kind: Service
metadata:
  name: linstor-controller-external
  namespace: linstor
spec:
  type: NodePort
  ports:
    - name: linstor-controller
      port: 3370
      targetPort: 3370
      nodePort: 30370
      protocol: TCP
  selector:
    app.kubernetes.io/component: linstor-controller
    app.kubernetes.io/instance: linstorcluster
    app.kubernetes.io/name: piraeus-datastore

Make sure firewall rules and network access between the clusters allow this port to be reached from Cluster B.

Connect Cluster B to the LINSTOR controller in Cluster A

In Cluster B, install Piraeus, referencing the controller in Cluster A. The LinstorCluster resource should include the externalController field:

apiVersion: piraeus.linbit.com/v1
kind: LinstorCluster
metadata:
  name: linstorcluster
spec:
  externalController:
    url: http://<cluster-a-node-ip>:30370

Ensure both clusters’ satellites use host networking, so that DRBD® can properly connect nodes across clusters.

Once the Piraeus Operator and the LINSTOR satellite pods have been deployed to Cluster B, they should be visible in the LINSTOR controller. To verify this, enter linstor node list in the LINSTOR controller in Cluster A and check the “online” status of the new nodes from Cluster B.

You can now create resources on the Cluster B nodes by using linstor resource create <cluster-b-node-name> <resource name>. This will create a DRBD replica on a node which is not part of the original cluster, Cluster A. The LINSTOR and DRBD software components are functioning properly at this point. Next, you need to ensure that the Kubernetes resources get migrated and are available in the new cluster.

Export and clean up PVCs and PVs

To make the storage resources available in Cluster B, you need to migrate the PVCs and PVs resources. This will allow access to the DRBD resources originally created in Cluster A.

On Cluster A, run this script to export and sanitize all in-use PVCs and PVs. It removes unnecessary metadata to prepare the objects for reapplication in Cluster B.

#!/bin/bash

BACKUP_DIR="./k8s-backup"
mkdir -p "$BACKUP_DIR/pvc"
mkdir -p "$BACKUP_DIR/pv"

echo "Export and cleanup PVCs..."

# All PVCs that link to PVs
kubectl get pv -o json | jq -r '
  .items[] | select(.spec.claimRef != null) |
  [.spec.claimRef.namespace, .spec.claimRef.name] | @tsv
' | sort | uniq | while IFS=$'\t' read -r namespace pvc_name; do
  output_file="$BACKUP_DIR/pvc/${namespace}-${pvc_name}.yaml"
  kubectl get pvc "$pvc_name" -n "$namespace" -o json | jq '
    {
      apiVersion,
      kind,
      metadata: {
        name: .metadata.name,
        namespace: .metadata.namespace
      },
      spec
    }
  ' > "$output_file"
  echo "  -> PVC $namespace/$pvc_name exported to $output_file"
done

echo "Export and cleanup PVs..."

kubectl get pv -o json | jq -c '.items[]' | while IFS= read -r pv_json; do
  name=$(echo "$pv_json" | jq -r '.metadata.name')
  output_file="$BACKUP_DIR/pv/${name}.yaml"

  echo "$pv_json" | jq '
    {
      apiVersion,
      kind,
      metadata: {
        name: .metadata.name
      },
      spec: (
        .spec
        | if has("claimRef") then
            .claimRef |= {
              name: .name,
              namespace: .namespace
            }
          else .
        end
      )
    }
  ' > "$output_file"

  echo "  -> PV $name exported to $output_file"
done

Inspect the contents of the BACKUP_DIR to verify they are as expected.

Import PVCs and PVs in Cluster B

Apply the manifests that were backed up and sanitized in the previous step to the new cluster:

kubectl apply -f ./k8s-backup/pvc/
kubectl apply -f ./k8s-backup/pv/

The PVCs will bind to the imported PVs immediately.

💡 TIP: Be sure to use the correct kubeconfig when talking to Cluster A and B. For example, you can use an export KUBECONFIG=config-cluster-a command to set the kubeconfig before using kubectl.

Migrate workloads

Copy over the consuming deployments and statefulsets to Cluster B, and apply them. The pods will initially fail to start if the volume is still in-use on Cluster A. This is expected and a sign that DRBD is protecting you from split-brains.

Scale the deployments and statefulsets down to zero replicas on Cluster A:

kubectl scale deployment <your-app> --replicas=0
kubectl scale statefulsets <your-app> --replicas=0

Then, as Cluster A’s pods release or unmount each DRBD volume, pods will begin to start in Cluster B.

At this point, each pod will be accessing its data by using DRBD client (also known as diskless) mode. Use the following LINSTOR command to create a diskful replica on the node where the pods are running:

linstor resource toggle-disk <node> <resource>

This will synchronize the remote block device locally to a newly provisioned volume. Pods using the volume can continue to use the volume while it’s being synchronized. Once fully synchronized, the pod will seamlessly begin using the local replica of the volume without requiring a pod restart.

Migrate the LINSTOR controller

The last thing you need to do is to move the active LINSTOR controller to Cluster B. The LINSTOR “database” is stored within Custom Resource Definitions (CRDs) in Cluster A. So first, shutdown the LINSTOR controller in Cluster A to create a consistent backup:

kubectl scale deployment -n linstor linstor-controller --replicas=0

Then, export all LINSTOR CRDs from Cluster A:

mkdir -p linstor-crds; cd ./linstor-crds
kubectl api-resources --api-group=internal.linstor.linbit.com -oname | xargs --no-run-if-empty kubectl get crds -oyaml > crds.yaml
for CRD in $(kubectl api-resources --api-group=internal.linstor.linbit.com -oname); do
  kubectl get "${CRD}" -oyaml > "${CRD}.yaml"
done

Next, apply the exported CRDs into Cluster B:

kubectl apply -f ./linstor-crds/

Finally, remove the externalController field in Cluster B:

kubectl edit linstorclusters.piraeus.io

This will cause the LINSTOR controller to start in Cluster B, loading the imported CRDs as its state, completing the migration process.

Optionally, you could configure Cluster A to use Cluster B as its external LINSTOR controller now if necessary. That would essentially be the same process outlined here, but in reverse. I will leave that as an exercise for the reader.

Summary

With a bit of YAML wrangling and the abstractions LINSTOR provides, migrating workloads between Kubernetes clusters becomes a real and repeatable process. This method keeps your data intact, minimizes downtime to only a few minutes, allows gradual or full transitions between clusters, and doesn’t require custom operators or complex tooling.

The process and patterns used in this article are very close to what is needed to facilitate federated LINSTOR clusters in Kubernetes capable of “cluster failovers”.

LINBIT is extremely thankful, as an open source company, to have customers and users such as PHOENIX MEDIA that trust LINBIT software and support with their data. The LINBIT team is especially appreciative of those that collaborate with us when pioneering processes such as this, which in-turn feeds back into the community for others to use, learn from, or even expand upon.

Picture of Matt Kereczman

Matt Kereczman

Matt Kereczman is a Solutions Architect at LINBIT with a long history of Linux System Administration and Linux System Engineering. Matt is a cornerstone in LINBIT's technical team, and plays an important role in making LINBIT and LINBIT's customer's solutions great. Matt was President of the GNU/Linux Club at Northampton Area Community College prior to graduating with Honors from Pennsylvania College of Technology with a BS in Information Security. Open Source Software and Hardware are at the core of most of Matt's hobbies.

Talk to us

LINBIT is committed to protecting and respecting your privacy, and we’ll only use your personal information to administer your account and to provide the products and services you requested from us. From time to time, we would like to contact you about our products and services, as well as other content that may be of interest to you. If you consent to us contacting you for this purpose, please tick above to say how you would like us to contact you.

You can unsubscribe from these communications at any time. For more information on how to unsubscribe, our privacy practices, and how we are committed to protecting and respecting your privacy, please review our Privacy Policy.

By clicking submit below, you consent to allow LINBIT to store and process the personal information submitted above to provide you the content requested.

Talk to us

LINBIT is committed to protecting and respecting your privacy, and we’ll only use your personal information to administer your account and to provide the products and services you requested from us. From time to time, we would like to contact you about our products and services, as well as other content that may be of interest to you. If you consent to us contacting you for this purpose, please tick above to say how you would like us to contact you.

You can unsubscribe from these communications at any time. For more information on how to unsubscribe, our privacy practices, and how we are committed to protecting and respecting your privacy, please review our Privacy Policy.

By clicking submit below, you consent to allow LINBIT to store and process the personal information submitted above to provide you the content requested.