Set up a multi-cluster mesh outside Google Cloud
Note: This guide only supports Cloud Service Mesh with Istio APIs and doesnot support Google Cloud APIs. For more information see,Cloud Service Mesh overview.This guide explains how to set up a multi-cluster mesh for the followingplatforms:
- Google Distributed Cloud (software only) for VMware
- Google Distributed Cloud (software only) for bare metal
- GKE on Azure (deprecated)
- GKE on AWS (deprecated)
- Attached clusters, including Amazon EKS clusters and Microsoft AKS clusters (deprecated)
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:
- Install required tools
- Download
asmcli - Grant cluster admin permissions
- Validate your project and cluster
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.
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)")Create an environment variable for the mesh identifier.
exportMESH_ID="proj-${PROJECT_NUMBER}"Create environment variables for the cluster names in the format that
asmclirequires.exportCLUSTER_1="cn-FLEET_PROJECT_ID-global-CLUSTER_NAME_1"exportCLUSTER_2="cn-FLEET_PROJECT_ID-global-CLUSTER_NAME_2"Get the context name for the clusters by usingthe values under the
NAMEcolumn in the output of this command:kubectl config get-contexts
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:
Replace
CLUSTER_NAME_1andCLUSTER_NAME_2with the names of your clusters.Replace
PATH_TO_KUBECONFIG_1andPATH_TO_KUBECONFIG_2with the kubeconfig files foryour clusters.
GKE Clusters
Mesh CA or CA Service
Install a gateway in cluster1 that is dedicated toeast-west traffic to
Note: If you installed Cloud Service Mesh using different values for$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.--network_id, then you should pass the same values intogen-eastwest-gateway.shwith the--networkflag.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-Install a gateway in
$CLUSTER_2that 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
Install a gateway in cluster1 that is dedicated toeast-west traffic to
Note: If you installed Cloud Service Mesh using different values for$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.--network_id, then you should pass the same values intogen-eastwest-gateway.shwith the--networkflag.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-Install a gateway in
$CLUSTER_2that 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
Caution:GKE on AzureandGKE on AWSare deprecated and will shut down on March 17, 2027.Cloud Service Mesh support forattached clusters, including Amazon EKS clusters and Microsoft AKS clusters,is deprecated and will shut down March 17, 2027.
Mesh CA
Install a gateway in cluster1 that is dedicated toeast-west traffic to
Note: If you installed Cloud Service Mesh using different values for$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.--network_id, then you should pass the same values intogen-eastwest-gateway.shwith the--networkflag.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-Install a gateway in
$CLUSTER_2that 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
Install a gateway in cluster1 that is dedicated toeast-west traffic to
Note: If you installed Cloud Service Mesh using different values for$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.--network_id, then you should pass the same values intogen-eastwest-gateway.shwith the--networkflag.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-Install a gateway in
$CLUSTER_2that 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-
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.
Expose services via the east-west gateway for
CLUSTER_NAME_1.kubectl --kubeconfig=PATH_TO_KUBECONFIG_1 apply -n istio-system -f \ asm/istio/expansion/expose-services.yamlExpose services via the east-west gateway for
CLUSTER_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_2Verify multicluster connectivity
This section explains how to deploy the sampleHelloWorld andSleep servicesto your multi-cluster environment to verify that cross-cluster loadbalancing works.
samples 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
Create the sample namespace in each cluster.
forCTXin${CTX_1}${CTX_2}dokubectlcreate--context=${CTX}namespacesampledoneEnable sidecar injection on created namespaces.
Recommended: Run the following command to apply the default injection label to the namespace:
forCTXin${CTX_1}${CTX_2}dokubectllabel--context=${CTX}namespacesample\istio.io/rev-istio-injection=enabled--overwritedoneWe recommend that you use default injection, but revision-based injection is supported:Use the following instructions:
Use the following command to locate the revision label on
istiod:kubectlgetdeploy-nistio-system-lapp=istiod-o\jsonpath={.items[*].metadata.labels.'istio\.io\/rev'}'{"\n"}'Apply the revision label to the namespace. In the following command,
REVISION_LABELis the value of theistiodrevisionlabel that you noted in the previous step.forCTXin${CTX_1}${CTX_2}dokubectllabel--context=${CTX}namespacesample\istio-injection-istio.io/rev=REVISION_LABEL--overwritedone
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.Create the HelloWorld service in both clusters:
kubectl create --context=${CTX_1} \ -f ${SAMPLES_DIR}/samples/helloworld/helloworld.yaml \ -l service=helloworld -n samplekubectl create --context=${CTX_2} \ -f ${SAMPLES_DIR}/samples/helloworld/helloworld.yaml \ -l service=helloworld -n sample
Deploy HelloWorld v1 and v2 to each cluster
Deploy
HelloWorld v1toCLUSTER_1andv2toCLUSTER_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 samplekubectl create --context=${CTX_2} \ -f ${SAMPLES_DIR}/samples/helloworld/helloworld.yaml \ -l version=v2 -n sampleConfirm
HelloWorld v1andv2are running using the following commands. Verify that the output is similar to that shown.:kubectl get pod --context=${CTX_1} -n sampleNAME READY STATUS RESTARTS AGEhelloworld-v1-86f77cd7bd-cpxhv 2/2 Running 0 40s
kubectl get pod --context=${CTX_2} -n sampleNAME READY STATUS RESTARTS AGEhelloworld-v2-758dd55874-6x4t8 2/2 Running 0 40s
Deploy the Sleep service
Deploy the
Sleepservice 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-nsampledoneWait for the
Sleepservice to start in each cluster. Verify that the output is similar to that shown:kubectl get pod --context=${CTX_1} -n sample -l app=sleepNAME READY STATUS RESTARTS AGEsleep-754684654f-n6bzf 2/2 Running 0 5s
kubectl get pod --context=${CTX_2} -n sample -l app=sleepNAME 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:
Call the
HelloWorldservice: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...
Call the
HelloWorldservice 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.