Replicate Data between Two Hazelcast Clusters with Hazelcast Platform Operator

Learn how to keep data in sync across two Hazelcast clusters.

Context

In this tutorial, you’ll do the following:

  • Deploy two Hazelcast clusters.

  • Create two Hazelcast map configurations on one of the clusters.

  • Synchronize map data between the two Hazelcast clusters.

Before you Begin

Before starting this tutorial, make sure that you have the following:

Step 1. Start the Hazelcast Cluster

  1. Clone the examples repository

    git clone https://github.com/hazelcast-guides/hazelcast-platform-operator-wan-replication.git
    cd hazelcast-platform-operator-wan-replication

    The sample code(excluding CLC) for this tutorial is in the clients directory.

  2. Create a secret with your Hazelcast Enterprise License.

    kubectl create secret generic hazelcast-license-key --from-literal=license-key=<hz-license-key>
  3. Create the Hazelcast clusters.

    1. Run the following command to create the first cluster.

      cat <<EOF | kubectl apply -f -
      apiVersion: hazelcast.com/v1alpha1
      kind: Hazelcast
      metadata:
        name: hazelcast-first
      spec:
        licenseKeySecret: hazelcast-license-key
        exposeExternally:
          type: Unisocket
          discoveryServiceType: LoadBalancer
      EOF
    2. Run the following command to create the second cluster.

      cat <<EOF | kubectl apply -f -
      apiVersion: hazelcast.com/v1alpha1
      kind: Hazelcast
      metadata:
        name: hazelcast-second
      spec:
        licenseKeySecret: hazelcast-license-key
        exposeExternally:
          type: Unisocket
          discoveryServiceType: LoadBalancer
      EOF
  4. Check the status of the clusters to make sure that both clusters are running.

    kubectl get hazelcast
    NAME               STATUS    MEMBERS
    hazelcast-first    Running   3/3
    hazelcast-second   Running   3/3
  5. Find the addresses of the clusters.

    kubectl get hazelcastendpoint --selector="app.kubernetes.io/instance in (hazelcast-first, hazelcast-second)"
    NAME                   TYPE        ADDRESS
    hazelcast-first        Discovery   34.123.9.149:5701
    hazelcast-first-wan    WAN         34.123.9.149:5710
    hazelcast-second       Discovery   34.16.0.16:5701
    hazelcast-second-wan   WAN         34.16.0.16:5710

    The ADDRESS column displays the external addresses of the Hazelcast clusters.

Step 2. Create a WAN Replication Configuration

  1. Create two maps on the first cluster. In this example, the following maps are created:

    • map-1

    • map-2.

      cat <<EOF | kubectl apply -f -
      apiVersion: hazelcast.com/v1alpha1
      kind: Map
      metadata:
        name: map-1
      spec:
        hazelcastResourceName: hazelcast-first
      ---
      apiVersion: hazelcast.com/v1alpha1
      kind: Map
      metadata:
        name: map-2
      spec:
        hazelcastResourceName: hazelcast-first
      EOF
  2. Create the configuration for WAN replication:

    • Use the first cluster as the source cluster by adding its name as a resource in the WAN Replication configuration. Adding the cluster name as a resource starts WAN replication for both the maps that you created earlier.

    • Add the second cluster as the target cluster to receive the WAN Replication events.

      Run the following command to apply the configuration.

      cat <<EOF | kubectl apply -f -
      apiVersion: hazelcast.com/v1alpha1
      kind: WanReplication
      metadata:
        name: wan-replication
      spec:
        resources:
          - name: hazelcast-first
            kind: Hazelcast
        targetClusterName: dev
        endpoints: "<SECOND-CLUSTER-EXTERNAL-IP>"
      EOF

Step 3. Put Entries to the Maps on the First Cluster

In this step, you’ll fill the maps on the first, source cluster.

  1. Configure the Hazelcast client to connect to the first cluster, using its address.

    • CLC

    • Java

    • NodeJS

    • Go

    • Python

    • .NET

    Before using CLC, it should be installed in your system. Check the installation instructions for CLC: Installing the Hazelcast CLC.

    Run the following command for adding the first cluster config to the CLC.

    clc config add hz-1 cluster.name=dev cluster.address=<FIRST-CLUSTER-EXTERNAL-IP>
    ClientConfig config = new ClientConfig();
    config.getNetworkConfig().addAddress("<FIRST-CLUSTER-EXTERNAL-IP>");
    const { Client } = require('hazelcast-client');
    
    const clientConfig = {
        network: {
            clusterMembers: [
                '<FIRST-CLUSTER-EXTERNAL-IP>'
            ]
        }
    };
    const client = await Client.newHazelcastClient(clientConfig);
    import (
    	"log"
    
    	"github.com/hazelcast/hazelcast-go-client"
    )
    
    func main() {
    	config := hazelcast.Config{}
    	cc := &config.Cluster
    	cc.Network.SetAddresses("<FIRST-CLUSTER-EXTERNAL-IP>")
    	ctx := context.TODO()
    	client, err := hazelcast.StartNewClientWithConfig(ctx, config)
    	if err != nil {
    		panic(err)
    	}
    }
    import logging
    import hazelcast
    
    logging.basicConfig(level=logging.INFO)
    
    client = hazelcast.HazelcastClient(
        cluster_members=["<FIRST-CLUSTER-EXTERNAL-IP>"],
        use_public_ip=True,
    )
    var options = new HazelcastOptionsBuilder()
        .With(args)
        .With((configuration, options) =>
        {
            options.Networking.UsePublicAddresses = true;
            options.Networking.SmartRouting = false;
            options.Networking.Addresses.Add("<FIRST-CLUSTER-EXTERNAL-IP>");
        })
        .Build();
  2. Start to fill the maps.

    • CLC

    • Java

    • NodeJS

    • Go

    • Python

    • .NET

    Run the following command for each map, using the map name as an argument to fill each map with entries. Use the map names map-1 and map-2.

    for i in {1..10};
    do
       clc -c hz-1 map set --name <MAP-NAME> key-$i value-$i;
    done

    Run the following command for each map to check if the sizes are expected.

    clc -c hz-1 map size --name <MAP-NAME>

    Start the application for each map, using the map name as an argument to fill each map with random entries. Use the map names map-1 and map-2.

    cd clients/java
    mvn package
    java -jar target/*jar-with-dependencies*.jar fill <MAP-NAME>

    You should see the following output.

    Successful connection!
    Starting to fill the map (<MAP-NAME>) with random entries.
    Current map size: 2
    Current map size: 3
    Current map size: 4
    ....
    ....

    Start the application for each map, using the map name as an argument to fill each map with random entries. Use the map names map-1 and map-2.

    cd clients/nodejs
    npm install
    npm start fill <MAP-NAME>

    You should see the following output.

    Successful connection!
    Starting to fill the map (<MAP-NAME>) with random entries.
    Current map size: 2
    Current map size: 3
    Current map size: 4
    ....
    ....

    Start the application for each map, using the map name as an argument to fill each map with random entries. Use the map names map-1 and map-2.

    cd clients/go
    go run main.go fill <MAP-NAME>

    You should see the following output.

    Successful connection!
    Starting to fill the map (<MAP-NAME>) with random entries.
    Current map size: 2
    Current map size: 3
    Current map size: 4
    ....
    ....

    Start the application for each map, using the map name as an argument to fill each map with random entries. Use the map names map-1 and map-2.

    cd clients/python
    pip install -r requirements.txt
    python main.py fill <MAP-NAME>

    You should see the following output.

    Successful connection!
    Starting to fill the map (<MAP-NAME>) with random entries.
    Current map size: 2
    Current map size: 3
    Current map size: 4
    ....
    ....

    Start the application for each map, using the map name as an argument to fill each map with random entries. Use the map names map-1 and map-2.

    cd clients/dotnet
    dotnet build
    dotnet run fill <MAP-NAME>

    You should see the following output.

    Successful connection!
    Starting to fill the map (<MAP-NAME>) with random entries.
    Current map size: 2
    Current map size: 3
    Current map size: 4
    ....
    ....

Step 3. Verify the Replication of Map Entries

In this step, you’ll check the sizes of the maps on the second, target cluster to make sure that WAN replication events have been received.

  1. Configure the Hazelcast client to connect to the second cluster, as you did in Configure the Hazelcast Client.

    • CLC

    • Java

    • NodeJS

    • Go

    • Python

    • .NET

    Run the following command for adding the second cluster config to the CLC.

    clc config add hz-2 cluster.name=dev cluster.address=<SECOND-CLUSTER-EXTERNAL-IP>
    ClientConfig config = new ClientConfig();
    config.getNetworkConfig().addAddress("<SECOND-CLUSTER-EXTERNAL-IP>");
    const { Client } = require('hazelcast-client');
    
    const clientConfig = {
        network: {
            clusterMembers: [
                '<SECOND-CLUSTER-EXTERNAL-IP>'
            ]
        }
    };
    const client = await Client.newHazelcastClient(clientConfig);
    import (
    	"log"
    
    	"github.com/hazelcast/hazelcast-go-client"
    )
    
    func main() {
    	config := hazelcast.Config{}
    	cc := &config.Cluster
    	cc.Network.SetAddresses("<SECOND-CLUSTER-EXTERNAL-IP>")
    	ctx := context.TODO()
    	client, err := hazelcast.StartNewClientWithConfig(ctx, config)
    	if err != nil {
    		panic(err)
    	}
    }
    import logging
    import hazelcast
    
    logging.basicConfig(level=logging.INFO)
    
    client = hazelcast.HazelcastClient(
        cluster_members=["<SECOND-CLUSTER-EXTERNAL-IP>"],
        use_public_ip=True,
    )
    var options = new HazelcastOptionsBuilder()
        .With(args)
        .With((configuration, options) =>
        {
            options.Networking.UsePublicAddresses = true;
            options.Networking.SmartRouting = false;
            options.Networking.Addresses.Add("<SECOND-CLUSTER-EXTERNAL-IP>");
    
        })
        .Build();
  2. Start the application for each map, using the map name as an argument to check the map size, and to check that WAN replication was successful. Use the map names map-1 and map-2.

    • CLC

    • Java

    • NodeJS

    • Go

    • Python

    • .NET

    clc -c hz-2 map size --name <MAP-NAME>
    cd clients/java
    mvn package
    java -jar target/*jar-with-dependencies*.jar size <MAP-NAME>

    You should see the following output:

    Successful connection!
    Current map (<MAP-NAME>) size: 12
    cd clients/nodejs
    npm install
    npm start size <MAP-NAME>

    You should see the following output:

    Successful connection!
    Current map (<MAP-NAME>) size: 12
    cd clients/go
    go run main.go size <MAP-NAME>

    You should see the following output:

    Successful connection!
    Current map (<MAP-NAME>) size: 12
    cd clients/python
    pip install -r requirements.txt
    python main.py size <MAP-NAME>

    You should see the following output:

    Successful connection!
    Current map (<MAP-NAME>) size: 12
    cd clients/dotnet
    dotnet build
    dotnet run size <MAP-NAME>

    You should see the following output:

    Successful connection!
    Current map (<MAP-NAME>) size: 12

Clean Up

To remove all custom resources, run the following commands:

kubectl delete secret hazelcast-license-key
kubectl delete $(kubectl get wanreplications,map,hazelcast -o name)