Set up a multi-cluster mesh outside Google Cloud

This guide explains how to set up a multi-cluster mesh for the followingplatforms:

  • Google Distributed Cloud
  • Google Distributed Cloud
  • GKE on Azure
  • GKE on AWS
  • Attached clusters, including Amazon EKS clusters and Microsoft AKS clusters

This guide shows how to set up two clusters, but you can extend this process toincorporate any number of clusters into your mesh.

Platform note: Theasmcli create-mesh command that you use toenable endpoint discovery requires all the clusters to be on the same platform. Currently,asmcli create-mesh only supports hybrid mesh in preview.

Before you begin

This guide assumes you installed Cloud Service Mesh usingasmcli install. You needasmcli and the configuration package thatasmcli downloads to thedirectory that you specified in--output_dir when you ranasmcli install.If need to get set up, follow the steps inInstall dependent tools and validate clusterto:

You need access to the kubeconfig files for all the clusters that you aresetting up in the mesh.

Warning: Only use kubeconfig files from trusted sources. Using aspecially-crafted kubeconfig file could result in malicious code execution orfile exposure. If you must use an untrusted kubeconfig file, inspect itcarefully first, much as you would a shell script.

Set up environment variables and placeholders

You need the following environment variables when you install the east-westgateway.

  1. Create an environment variable for the project number. In the followingcommand, replaceFLEET_PROJECT_ID with thethe project ID of thefleet host project.

    exportPROJECT_NUMBER=$(gcloudprojectsdescribeFLEET_PROJECT_ID\--format="value(projectNumber)")
  2. Create an environment variable for the mesh identifier.

    exportMESH_ID="proj-${PROJECT_NUMBER}"
  3. Create environment variables for the cluster names in the format thatasmcli requires.

    exportCLUSTER_1="cn-FLEET_PROJECT_ID-global-CLUSTER_NAME_1"exportCLUSTER_2="cn-FLEET_PROJECT_ID-global-CLUSTER_NAME_2"
  4. Get the context name for the clusters by usingthe values under theNAME column in the output of this command:

    kubectl config get-contexts
  5. Set the environment variables to the cluster context names, which this guideuses in many steps later:

    export CTX_1=CLUSTER1_CONTEXT_NAMEexport CTX_2=CLUSTER2_CONTEXT_NAME

Install the east-west gateway

In the following commands:

  • ReplaceCLUSTER_NAME_1 andCLUSTER_NAME_2 with the names of your clusters.

  • ReplacePATH_TO_KUBECONFIG_1 andPATH_TO_KUBECONFIG_2 with the kubeconfig files foryour clusters.

Anthos Clusters

Mesh CA or CA Service

  1. Install a gateway in cluster1 that is dedicated toeast-west traffic to$CLUSTER_2. By default, this gateway will be public on the Internet.Production systems might require additional access restrictions, forexample firewall rules, to prevent external attacks.

    Note: If you installed Cloud Service Mesh using different values for--network_id, then you should pass the same values intogen-eastwest-gateway.sh with the--network flag.
    asm/istio/expansion/gen-eastwest-gateway.sh\--mesh${MESH_ID}\--cluster${CLUSTER_1}\--networkdefault\--revision asm-1282-4|\./istioctl--kubeconfig=PATH_TO_KUBECONFIG_1\install-y--setspec.values.global.pilotCertProvider=kubernetes-f-
  2. Install a gateway in$CLUSTER_2 that is dedicated to east-west trafficfor$CLUSTER_1.

    asm/istio/expansion/gen-eastwest-gateway.sh\--mesh${MESH_ID}\--cluster${CLUSTER_2}\--networkdefault\--revision asm-1282-4|\./istioctl--kubeconfig=PATH_TO_KUBECONFIG_2\install-y--setspec.values.global.pilotCertProvider=kubernetes-f-

Istio CA

  1. Install a gateway in cluster1 that is dedicated toeast-west traffic to$CLUSTER_2. By default, this gateway will be public on the Internet.Production systems might require additional access restrictions, forexample firewall rules, to prevent external attacks.

    Note: If you installed Cloud Service Mesh using different values for--network_id, then you should pass the same values intogen-eastwest-gateway.sh with the--network flag.
    asm/istio/expansion/gen-eastwest-gateway.sh\--mesh${MESH_ID}\--cluster${CLUSTER_1}\--networkdefault\--revision asm-1282-4|\./istioctl--kubeconfig=PATH_TO_KUBECONFIG_1\install-y--setspec.values.global.pilotCertProvider=istiod-f-
  2. Install a gateway in$CLUSTER_2 that is dedicated to east-west trafficfor$CLUSTER_1.

    asm/istio/expansion/gen-eastwest-gateway.sh\--mesh${MESH_ID}\--cluster${CLUSTER_2}\--networkdefault\--revision asm-1282-4|\./istioctl--kubeconfig=PATH_TO_KUBECONFIG_2\install-y--setspec.values.global.pilotCertProvider=istiod-f-

Azure, AWS, & Attached

Mesh CA

  1. Install a gateway in cluster1 that is dedicated toeast-west traffic to$CLUSTER_2. By default, this gateway will be public on the Internet.Production systems might require additional access restrictions, forexample firewall rules, to prevent external attacks.

    Note: If you installed Cloud Service Mesh using different values for--network_id, then you should pass the same values intogen-eastwest-gateway.sh with the--network flag.
    asm/istio/expansion/gen-eastwest-gateway.sh\--mesh${MESH_ID}\--cluster${CLUSTER_1}\--networkdefault\--revision asm-1282-4|\./istioctl--kubeconfig=PATH_TO_KUBECONFIG_1\install-y--setspec.values.global.pilotCertProvider=istiod-f-
  2. Install a gateway in$CLUSTER_2 that is dedicated to east-west trafficfor$CLUSTER_1.

    asm/istio/expansion/gen-eastwest-gateway.sh\--mesh${MESH_ID}\--cluster${CLUSTER_2}\--networkdefault\--revision asm-1282-4|\./istioctl--kubeconfig=PATH_TO_KUBECONFIG_2\install-y--setspec.values.global.pilotCertProvider=istiod-f-

Istio CA

  1. Install a gateway in cluster1 that is dedicated toeast-west traffic to$CLUSTER_2. By default, this gateway will be public on the Internet.Production systems might require additional access restrictions, forexample firewall rules, to prevent external attacks.

    Note: If you installed Cloud Service Mesh using different values for--network_id, then you should pass the same values intogen-eastwest-gateway.sh with the--network flag.
    asm/istio/expansion/gen-eastwest-gateway.sh\--mesh${MESH_ID}\--cluster${CLUSTER_1}\--networkdefault\--revision asm-1282-4|\./istioctl--kubeconfig=PATH_TO_KUBECONFIG_1\install-y--setspec.values.global.pilotCertProvider=istiod-f-
  2. Install a gateway in$CLUSTER_2 that is dedicated to east-west trafficfor$CLUSTER_1.

    asm/istio/expansion/gen-eastwest-gateway.sh\--mesh${MESH_ID}\--cluster${CLUSTER_2}\--networkdefault\--revision asm-1282-4|\./istioctl--kubeconfig=PATH_TO_KUBECONFIG_2\install-y--setspec.values.global.pilotCertProvider=istiod-f-
Platform note: For Google Distributed Cloud, an external IP addresswon't be assigned until later, when you complete the steps described inConfigureexternal IP addresses for Google Distributed Cloud.

Exposing services

Since the clusters are on separate networks, you need to expose all services(*.local) on the east-west gateway in both clusters. While this gateway ispublic on the Internet, services behind it can only be accessed by services witha trusted mTLS certificate and workload ID, just as if they were on the samenetwork.

  1. Expose services via the east-west gateway forCLUSTER_NAME_1.

    kubectl --kubeconfig=PATH_TO_KUBECONFIG_1 apply -n istio-system -f \    asm/istio/expansion/expose-services.yaml
  2. Expose services via the east-west gateway forCLUSTER_NAME_2.

    kubectl --kubeconfig=PATH_TO_KUBECONFIG_2 apply -n istio-system -f \    asm/istio/expansion/expose-services.yaml

Enable endpoint discovery

Note: For more information on endpoint discovery, refer toEndpoint discovery with multiple control planes.

Run theasmcli create-mesh command to enable endpoint discovery. Thisexample only shows two clusters, but you can run the command to enableendpoint discovery on additional clusters, subject to theGKE Hub service limit.

./asmclicreate-mesh\FLEET_PROJECT_ID\PATH_TO_KUBECONFIG_1\PATH_TO_KUBECONFIG_2

Verify multicluster connectivity

This section explains how to deploy the sampleHelloWorld andSleep servicesto your multi-cluster environment to verify that cross-cluster loadbalancing works.

Note: These sample services are located in theIstiosamples directory included in theistioctl tarball (located inOUTPUT_DIR/istio-${ASM_VERSION%+*}/samples) and not thesamples directory downloaded byasmcli (located inOUTPUT_DIR/samples).

Enable sidecar injection

Locate the revision label value, which you use in later steps.

Note: If your clusters each have different versions of Cloud Service Mesh you mustrun the following command for each cluster.

Use the following command to locate the revision label, which you willuse in later steps.

kubectl-nistio-systemgetpods-lapp=istiod--show-labels

The output looks similar to the following:

NAMEREADYSTATUSRESTARTSAGELABELSistiod-asm-173-3-5788d57586-bljj41/1Running023happ=istiod,istio.io/rev=asm-173-3,istio=istiod,pod-template-hash=5788d57586istiod-asm-173-3-5788d57586-vsklm1/1Running123happ=istiod,istio.io/rev=asm-173-3,istio=istiod,pod-template-hash=5788d57586

In the output, under theLABELS column, note the value of theistiodrevision label, which follows the prefixistio.io/rev=. In this example,the value isasm-173-3. Use the revision value in the steps in the next section.

Install the HelloWorld service

Note: TheHelloWorld service example usesDocker Hub. In a privatecluster, the container runtime can pull container images fromArtifact Registryby default. The container runtime cannot pull images from any other containerimage registry on the internet. You can download the image and push to Artifact Registry or useCloud NAT to provide outbound internet access for certain private nodes. Formore information, seeMigrate external containersandCreating a private cluster.
  1. Create the sample namespace and the Service Definition in each cluster. Inthe following command, substituteREVISION with theistiodrevision label that you noted from the previous step.

    forCTXin${CTX_1}${CTX_2}dokubectlcreate--context=${CTX}namespacesamplekubectllabel--context=${CTX}namespacesample\istio-injection-istio.io/rev=REVISION--overwritedone

    whereREVISION is theistiod revision label that you previouslynoted.

    The output is:

    label "istio-injection" not found.namespace/sample labeled

    You can safely ignorelabel "istio-injection" not found.

  2. Create the HelloWorld service in both clusters:

    kubectl create --context=${CTX_1} \    -f ${SAMPLES_DIR}/samples/helloworld/helloworld.yaml \    -l service=helloworld -n sample
    kubectl create --context=${CTX_2} \    -f ${SAMPLES_DIR}/samples/helloworld/helloworld.yaml \    -l service=helloworld -n sample

Deploy HelloWorld v1 and v2 to each cluster

  1. DeployHelloWorld v1 toCLUSTER_1 andv2 toCLUSTER_2, which helps later to verify cross-cluster load balancing:

    kubectl create --context=${CTX_1} \  -f ${SAMPLES_DIR}/samples/helloworld/helloworld.yaml \  -l version=v1 -n sample
    kubectl create --context=${CTX_2} \  -f ${SAMPLES_DIR}/samples/helloworld/helloworld.yaml \  -l version=v2 -n sample
  2. ConfirmHelloWorld v1 andv2 are running using the following commands. Verify that the output is similar to that shown.:

    kubectl get pod --context=${CTX_1} -n sample
    NAME                            READY     STATUS    RESTARTS   AGEhelloworld-v1-86f77cd7bd-cpxhv  2/2       Running   0          40s
    kubectl get pod --context=${CTX_2} -n sample
    NAME                            READY     STATUS    RESTARTS   AGEhelloworld-v2-758dd55874-6x4t8  2/2       Running   0          40s

Deploy the Sleep service

  1. Deploy theSleep service to both clusters. This pod generates artificial network traffic for demonstration purposes:

    forCTXin${CTX_1}${CTX_2}dokubectlapply--context=${CTX}\-f${SAMPLES_DIR}/samples/sleep/sleep.yaml-nsampledone
  2. Wait for theSleep service to start in each cluster. Verify that the output is similar to that shown:

    kubectl get pod --context=${CTX_1} -n sample -l app=sleep
    NAME                             READY   STATUS    RESTARTS   AGEsleep-754684654f-n6bzf           2/2     Running   0          5s
    kubectl get pod --context=${CTX_2} -n sample -l app=sleep
    NAME                             READY   STATUS    RESTARTS   AGEsleep-754684654f-dzl9j           2/2     Running   0          5s

Verify cross-cluster load balancing

Call theHelloWorld service several times and check the output to verifyalternating replies from v1 and v2:

  1. Call theHelloWorld service:

    kubectl exec --context="${CTX_1}" -n sample -c sleep \    "$(kubectl get pod --context="${CTX_1}" -n sample -l \    app=sleep -o jsonpath='{.items[0].metadata.name}')" \    -- /bin/sh -c 'for i in $(seq 1 20); do curl -sS helloworld.sample:5000/hello; done'

    The output is similar to that shown:

    Hello version: v2, instance: helloworld-v2-758dd55874-6x4t8Hello version: v1, instance: helloworld-v1-86f77cd7bd-cpxhv...
  2. Call theHelloWorld service again:

    kubectl exec --context="${CTX_2}" -n sample -c sleep \    "$(kubectl get pod --context="${CTX_2}" -n sample -l \    app=sleep -o jsonpath='{.items[0].metadata.name}')" \    -- /bin/sh -c 'for i in $(seq 1 20); do curl -sS helloworld.sample:5000/hello; done'

    The output is similar to that shown:

    Hello version: v2, instance: helloworld-v2-758dd55874-6x4t8Hello version: v1, instance: helloworld-v1-86f77cd7bd-cpxhv...

Congratulations, you've verified your load-balanced, multi-cluster Cloud Service Mesh!

Clean up

When you finish verifying load balancing, remove theHelloWorld andSleepservice from your cluster.

kubectl delete ns sample --context ${CTX_1}kubectl delete ns sample --context ${CTX_2}

Except as otherwise noted, the content of this page is licensed under theCreative Commons Attribution 4.0 License, and code samples are licensed under theApache 2.0 License. For details, see theGoogle Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.

Last updated 2026-02-19 UTC.