Helm
Use Helm to link ambient service meshes across multiple clusters.
Overview
In this guide, you deploy an ambient mesh to each workload cluster, create an east-west gateway in each cluster, and link the istiod control planes across cluster networks by using peering gateways. In the next guide, you can deploy the Bookinfo sample app to the ambient mesh in each cluster, and make select services available across the multicluster mesh. Incoming requests can then be routed from an ingress gateway, such as Solo Enterprise for kgateway, to services in your mesh across all clusters.
The following diagram demonstrates an ambient mesh setup across multiple clusters. For more information about the components that are installed in these steps, see the ambient components overview.
Considerations
Before you set up a multicluster ambient mesh, review the following considerations and requirements.
License requirements
Version requirements
Review the following known Istio version requirements and restrictions.
- If you use Istio versions 1.27.7, 1.28.4, 1.29.0 or later, and you install the Solo UI into a namespace other than
gloo-mesh, you must allow that namespace by listing it in theDEBUG_ENDPOINT_AUTH_ALLOWED_NAMESPACESenvironment variable of your istiod installation. For more information, see the release notes.
Platform requirements
The steps in the following sections have options for deploying an ambient mesh to either Kubernetes or OpenShift clusters.
If you use OpenShift clusters, complete the following steps before you begin:
- Set
routingViaHost: truein the Cluster Network Operator to enable OVN-Kubernetes local gateway mode. For more information, see the Platform-specific prerequisites in the ambient mesh docs.
The commands for OpenShift in the following steps contain these required settings:
- Your Helm settings must include
global.platform=openshiftfor Istio 1.24 and later. If you instead install Istio 1.23 or earlier, you must useprofile=openshiftinstead of theglobal.platformsetting. - Install the
istio-cniandztunnelHelm releases in thekube-systemnamespace, instead of theistio-systemnamespace.
Revision and canary upgrade limitations
The upgrade guides in this documentation show you how to perform in-place upgrades for your Istio components, which is the recommended upgrade strategy.
Cross-cluster traffic addresses
In each cluster, you create an east-west gateway, which is implemented as a ztunnel that facilitates traffic between services across clusters in your multicluster mesh. In the Solo distribution of Istio 1.28 and later, you can use either LoadBalancer or NodePort addresses to resolve cross-cluster traffic requests through this gateway. Note that the NodePort method is considered beta in the Solo distribution of Istio version 1.29.
LoadBalancer: In the standard LoadBalancer peering method, cross-cluster traffic through the east-west gateway resolves to its LoadBalancer address.
NodePort (beta): If you prefer to use direct pod-to-pod traffic across clusters, you can annotate the east-west and peering gateways so that cross-cluster traffic resolves to NodePort addresses. This method allows you to avoid LoadBalancer services to reduce cross-cluster traffic costs. Before you use this feature, be sure to review its limitations, considerations, and best practices.
The steps in the following guide include options for either the LoadBalancer or NodePort method. A status condition on each east-west and remote peer gateway indicates which dataplane service type is in use.
Migrating from multicluster community Istio
If you previously used the multicluster feature in community Istio, and want to now migrate to multicluster peering in the Solo distribution of Istio, the DISABLE_LEGACY_MULTICLUSTER environment variable is introduced in the Solo distribution of Istio version 1.28 to disable the community multicluster mechanisms. Multicluster in community Istio uses remote secrets that contain kubeconfigs to watch resources on remote clusters. This system is incompatible with the decentralized, push-based model for peering in the Solo distribution of Istio. This variable causes istiod to ignore remote secrets so that it does not attempt to set up Kubernetes clients to connect to them.
- For fresh multicluster mesh installations with the Solo distribution of Istio, use this environment variable in your istiod settings. This setting serves as a recommended safety measure to prevent any use of remote secrets.
- If you want to initiate a multicluster migration from community Istio, contact a Solo account representative. An account representative can help you set up two revisions of Istio that each select a different set of namespaces, and set the
DISABLE_LEGACY_MULTICLUSTERvariable on the revision that uses the Solo distribution of Istio for multicluster peering.
Multicluster setup method
To get started, choose one of following methods for creating a multicluster mesh.
- Option 1: Install and link new ambient meshes to form a multicluster mesh.
- Option 2: Upgrade and link existing ambient meshes that you already installed in individual clusters.
Option 1: Install and link new ambient meshes
In each cluster, use Helm to create the ambient mesh components. Then, create an east-west gateway so that traffic requests can be routed cross-cluster, and link clusters to enable cross-cluster service discovery.
Set up tools
Set your Enterprise level license for Solo Enterprise for Istio as an environment variable. If you do not have one, contact an account representative. If you prefer to specify license keys in a secret instead, see Licensing. Note that you might have previously saved this key in another variable, such as
${SOLO_LICENSE_KEY}or${GLOO_MESH_LICENSE_KEY}.export SOLO_ISTIO_LICENSE_KEY=<enterprise_license_key>Choose the version of Istio that you want to install or upgrade to by reviewing the supported versions.
Save the Solo distribution of Istio version.
export ISTIO_VERSION=1.30.1 export ISTIO_IMAGE=${ISTIO_VERSION}-soloSave the image and Helm repository information for the Solo distribution of Istio.
Istio 1.29 and later:
export REPO=us-docker.pkg.dev/soloio-img/istio export HELM_REPO=us-docker.pkg.dev/soloio-img/istio-helmIstio 1.28 and earlier: You must provide a repo key for the minor version of the Solo distribution of Istio that you want to install. This is the 12-character hash at the end of the repo URL
us-docker.pkg.dev/gloo-mesh/istio-<repo-key>, which you can find in the Istio images built by Solo.io support article.# 12-character hash at the end of the repo URL export REPO_KEY=<repo_key> export REPO=us-docker.pkg.dev/gloo-mesh/istio-${REPO_KEY} export HELM_REPO=us-docker.pkg.dev/gloo-mesh/istio-helm-${REPO_KEY}
Get the Solo distribution of Istio binary and install
istioctl, which you use for multicluster linking and gateway commands. This script automatically detects your OS and architecture, downloads the appropriate Solo distribution of Istio binary, and verifies the installation.bash <(curl -sSfL https://raw.githubusercontent.com/solo-io/doc-examples/main/istio/install-istioctl.sh) export PATH=${HOME}/.istioctl/bin:${PATH}Save the names and kubeconfig contexts of each cluster. This guide uses two clusters as an example. To add more clusters to the multicluster setup, include them in the arrays.
export cluster1=<cluster1_name> export context1=<cluster1_context> export cluster2=<cluster2_name> export context2=<cluster2_context>
Create a shared root of trust
Each cluster in the multicluster setup must have a shared root of trust. This can be achieved by providing a root certificate signed by a PKI provider, or a custom root certificate created for this purpose. The root certificate signs a unique intermediate CA certificate for each cluster.
By default, the Istio CA generates a self-signed root certificate and key, and uses them to sign the workload certificates. For more information, see the Plug in CA Certificates guide in the community Istio documentation.
For demo installations, you can run the following function to quickly generate and plug in the certificates and key for the Istio CA:
curl -L https://istio.io/downloadIstio | ISTIO_VERSION=${ISTIO_VERSION} sh -
cd istio-${ISTIO_VERSION}
mkdir -p certs
pushd certs
make -f ../tools/certs/Makefile.selfsigned.mk root-ca
function create_cacerts_secret() {
context=${1:?context}
cluster=${2:?cluster}
make -f ../tools/certs/Makefile.selfsigned.mk ${cluster}-cacerts
kubectl --context=${context} create ns istio-system || true
kubectl --context=${context} create secret generic cacerts -n istio-system \
--from-file=${cluster}/ca-cert.pem \
--from-file=${cluster}/ca-key.pem \
--from-file=${cluster}/root-cert.pem \
--from-file=${cluster}/cert-chain.pem
}
create_cacerts_secret ${context1} ${cluster1}
create_cacerts_secret ${context2} ${cluster2}
cd ../..To enhance the security of your setup even further and have full control over the Istio CA lifecycle, you can generate and store the root and intermediate CA certificates and keys with your own PKI provider. You can then use tools such as cert-manager to send certificate signing requests on behalf of istiod to your PKI provider. Cert-manager stores the signed intermediate certificates and keys in the cacerts Kubernetes secret so that istiod can use these credentials to issue leaf certificates for the workloads in the service mesh. You can set up cert-manager to also check the certificates and renew them before they expire.
AWS Private CA issuer and cert-manager: For an architectural overview of this certificate setup, see Bring your own Istio CAs with AWS. For steps on how to deploy this certificate setup, check out this Solo.io blog post. Be sure to repeat the steps so that a cacerts secret exists in each cluster.
Deploy ambient components
Apply the CRDs for the Kubernetes Gateway API to your cluster, which are required to create components such as waypoint proxies for L7 traffic policies, gateways with the
Gatewayresource, and more.kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.5.0/standard-install.yaml --context ${context1} kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.5.0/standard-install.yaml --context ${context2}Install the
basechart, which contains the CRDs and cluster roles required to set up Istio, in both clusters.function install_base() { context=${1:?context} helm upgrade --install istio-base oci://${HELM_REPO}/base \ --namespace istio-system \ --create-namespace \ --kube-context ${context} \ --version ${ISTIO_IMAGE} \ -f - <<EOF defaultRevision: "" profile: ambient EOF } install_base ${context1} install_base ${context2}function install_base() { context=${1:?context} helm upgrade --install istio-base oci://${HELM_REPO}/base \ --namespace istio-system \ --create-namespace \ --kube-context ${context} \ --version ${ISTIO_IMAGE} \ -f - <<EOF defaultRevision: "" profile: ambient global: platform: openshift EOF } install_base ${context1} install_base ${context2}You can optionally verify that the CRDs are successfully installed in both clusters.
kubectl get crds -l app.kubernetes.io/instance=istio-base --kube-context ${context1} kubectl get crds -l app.kubernetes.io/instance=istio-base --kube-context ${context2}Example output:
NAME CREATED AT authorizationpolicies.security.istio.io 2025-12-16T22:56:00Z destinationrules.networking.istio.io 2025-12-16T22:56:00Z envoyfilters.networking.istio.io 2025-12-16T22:56:00Z gateways.networking.istio.io 2025-12-16T22:56:00Z peerauthentications.security.istio.io 2025-12-16T22:56:00Z proxyconfigs.networking.istio.io 2025-12-16T22:56:00Z requestauthentications.security.istio.io 2025-12-16T22:56:00Z segments.admin.solo.io 2025-12-16T22:56:00Z serviceentries.networking.istio.io 2025-12-16T22:56:00Z sidecars.networking.istio.io 2025-12-16T22:56:00Z telemetries.telemetry.istio.io 2025-12-16T22:56:00Z virtualservices.networking.istio.io 2025-12-16T22:56:00Z wasmplugins.extensions.istio.io 2025-12-16T22:56:00Z workloadentries.networking.istio.io 2025-12-16T22:56:00Z workloadgroups.networking.istio.io 2025-12-16T22:56:00ZCreate the
istiodcontrol plane in both clusters.function install_istiod() { context=${1:?context} cluster=${2:?cluster} helm upgrade --install istiod oci://${HELM_REPO}/istiod \ --namespace istio-system \ --kube-context ${context} \ --version ${ISTIO_IMAGE} \ -f - <<EOF env: # Assigns IP addresses to multicluster services PILOT_ENABLE_IP_AUTOALLOCATE: "true" # Required when meshConfig.trustDomain is set PILOT_SKIP_VALIDATE_TRUST_DOMAIN: "true" # Disable community Istio multicluster mechanisms DISABLE_LEGACY_MULTICLUSTER: "true" global: hub: ${REPO} multiCluster: clusterName: ${cluster} network: ${cluster} proxy: clusterDomain: cluster.local tag: ${ISTIO_IMAGE} meshConfig: accessLogFile: /dev/stdout defaultConfig: proxyMetadata: ISTIO_META_DNS_CAPTURE: "true" # Assign each cluster a unique trust domain to apply policies to specific clusters trustDomain: "${cluster}.local" pilot: cni: namespace: istio-system enabled: true # Required to enable multicluster support platforms: peering: enabled: true profile: ambient license: value: ${SOLO_ISTIO_LICENSE_KEY} # Uncomment if you prefer to specify your license secret # instead of an inline value. # secretRef: # name: # namespace: EOF } install_istiod ${context1} ${cluster1} install_istiod ${context2} ${cluster2}function install_istiod() { context=${1:?context} cluster=${2:?cluster} helm upgrade --install istiod oci://${HELM_REPO}/istiod \ --namespace istio-system \ --kube-context ${context} \ --version ${ISTIO_IMAGE} \ -f - <<EOF env: # Assigns IP addresses to multicluster services PILOT_ENABLE_IP_AUTOALLOCATE: "true" # Required when meshConfig.trustDomain is set PILOT_SKIP_VALIDATE_TRUST_DOMAIN: "true" # Disable community Istio multicluster mechanisms DISABLE_LEGACY_MULTICLUSTER: "true" global: hub: ${REPO} multiCluster: clusterName: ${cluster} network: ${cluster} platform: openshift proxy: clusterDomain: cluster.local tag: ${ISTIO_IMAGE} meshConfig: accessLogFile: /dev/stdout defaultConfig: proxyMetadata: ISTIO_META_DNS_CAPTURE: "true" # Assign each cluster a unique trust domain to apply policies to specific clusters trustDomain: "${cluster}.local" pilot: cni: namespace: kube-system enabled: true # Required to enable multicluster support platforms: peering: enabled: true profile: ambient license: value: ${SOLO_ISTIO_LICENSE_KEY} # Uncomment if you prefer to specify your license secret # instead of an inline value. # secretRef: # name: # namespace: EOF } install_istiod ${context1} ${cluster1} install_istiod ${context2} ${cluster2}Install the Istio CNI node agent daemonset in both clusters.
function install_cni() { context=${1:?context} helm upgrade --install istio-cni oci://${HELM_REPO}/cni \ --namespace istio-system \ --kube-context ${context} \ --version ${ISTIO_IMAGE} \ -f - <<EOF # Assigns IP addresses to multicluster services ambient: dnsCapture: true excludeNamespaces: - istio-system - kube-system global: hub: ${REPO} tag: ${ISTIO_IMAGE} profile: ambient EOF } install_cni ${context1} install_cni ${context2}function install_cni() { context=${1:?context} helm upgrade --install istio-cni oci://${HELM_REPO}/cni \ --namespace kube-system \ --kube-context ${context} \ --version ${ISTIO_IMAGE} \ -f - <<EOF # Assigns IP addresses to multicluster services ambient: dnsCapture: true excludeNamespaces: - istio-system - kube-system global: hub: ${REPO} platform: openshift tag: ${ISTIO_IMAGE} profile: ambient EOF } install_cni ${context1} install_cni ${context2}Install the ztunnel daemonset in both clusters.
function install_ztunnel() { context=${1:?context} cluster=${2:?cluster} helm upgrade --install ztunnel oci://${HELM_REPO}/ztunnel \ --namespace istio-system \ --kube-context ${context} \ --version ${ISTIO_IMAGE} \ -f - <<EOF configValidation: true enabled: true env: L7_ENABLED: "true" # Required when a unique trust domain is set for each cluster SKIP_VALIDATE_TRUST_DOMAIN: "true" hub: ${REPO} istioNamespace: istio-system multiCluster: clusterName: ${cluster} namespace: istio-system network: ${cluster} profile: ambient proxy: clusterDomain: cluster.local tag: ${ISTIO_IMAGE} terminationGracePeriodSeconds: 29 variant: distroless EOF } install_ztunnel ${context1} ${cluster1} install_ztunnel ${context2} ${cluster2}function install_ztunnel() { context=${1:?context} cluster=${2:?cluster} helm upgrade --install ztunnel oci://${HELM_REPO}/ztunnel \ --namespace kube-system \ --kube-context ${context} \ --version ${ISTIO_IMAGE} \ -f - <<EOF configValidation: true enabled: true env: L7_ENABLED: "true" # Required when a unique trust domain is set for each cluster SKIP_VALIDATE_TRUST_DOMAIN: "true" global: platform: openshift hub: ${REPO} istioNamespace: istio-system multiCluster: clusterName: ${cluster} namespace: kube-system network: ${cluster} profile: ambient proxy: clusterDomain: cluster.local tag: ${ISTIO_IMAGE} terminationGracePeriodSeconds: 29 variant: distroless EOF } install_ztunnel ${context1} ${cluster1} install_ztunnel ${context2} ${cluster2}Verify that the components of the Istio ambient control and data plane are successfully installed in both clusters. Because the Istio CNI and ztunnel are deployed as daemon sets, the number of CNI and ztunnel pods equals the number of nodes in your cluster. Note that it might take a few seconds for the pods to become available.
kubectl get pods -A --context ${context1} | grep -E 'istio|ztunnel' kubectl get pods -A --context ${context2} | grep -E 'istio|ztunnel'Example output:
istiod-85c4dfd97f-mncj5 1/1 Running 0 40s istio-cni-node-pr5rl 1/1 Running 0 9s istio-cni-node-pvmx2 1/1 Running 0 9s istio-cni-node-6q26l 1/1 Running 0 9s ztunnel-tvtzn 1/1 Running 0 7s ztunnel-vtpjm 1/1 Running 0 4s ztunnel-hllxg 1/1 Running 0 4sOptional: Check the istiod logs in both clusters to verify that the certificate you generated earlier is picked up by istiod.
kubectl logs deploy/istiod -n istio-system --context ${context1} | grep x509 kubectl logs deploy/istiod -n istio-system --context ${context2} | grep x509Example output:
2025-12-16T22:59:06.783901Z info x509 cert - Issuer: "CN=Intermediate CA,O=Istio,L=cluster-1", Subject: "", SN: def320623729b8370172413749143836, NotBefore: "2025-12-16T22:57:06Z", NotAfter: "2035-12-14T22:59:06Z" 2025-12-16T22:59:06.783937Z info x509 cert - Issuer: "CN=Root CA,O=Istio", Subject: "CN=Intermediate CA,O=Istio,L=cluster-1", SN: 452d45254328667ccf8434c64c79fe789612bb5a, NotBefore: "2025-12-16T22:54:30Z", NotAfter: "2035-12-14T22:54:30Z" 2025-12-16T22:59:06.783966Z info x509 cert - Issuer: "CN=Root CA,O=Istio", Subject: "CN=Root CA,O=Istio", SN: 38dad68e56ab8c506ae07454801f65b134bd9580, NotBefore: "2025-12-16T22:54:30Z", NotAfter: "2035-12-14T22:54:30Z"Label the
istio-systemnamespace with the clusters’ network names, which you previously set to each cluster name in theglobal.networkfield of theistiodinstallations. The ambient control plane uses this label internally to group pods that exist in the same L3 network.kubectl label namespace istio-system --context ${context1} topology.istio.io/network=${cluster1} kubectl label namespace istio-system --context ${context2} topology.istio.io/network=${cluster2}Create an east-west gateway in the
istio-eastwestnamespace. In each cluster, the east-west gateway is implemented as a ztunnel that facilitates traffic between services across clusters in your multicluster mesh.You can use either LoadBalancer or NodePort addresses for cross-cluster traffic.
Create the east-west gateway in both clusters. Cross-cluster traffic though this gateway resolves to the LoadBalancer address. For customization options, see the gateway guide in the Istio docs.
- Solo distribution of
istioctl: For more information about this command, see the CLI reference.function create_ew_gateway_lb() { context=${1:?context} cluster=${2:?cluster} kubectl create namespace istio-eastwest --context ${context} istioctl multicluster expose --namespace istio-eastwest --context ${context} --generate > ew-gateway-${cluster}.yaml kubectl apply -f ew-gateway-${cluster}.yaml --context ${context} } create_ew_gateway_lb ${context1} ${cluster1} create_ew_gateway_lb ${context2} ${cluster2}
- Helm: For more information about the
peeringchart, see the Helm values reference. For recommendations on customizing the east-west gateway for resiliency and availability with the Helm chart, see the best practices for multicluster peering.function create_ew_gateway_lb_helm() { context=${1:?context} cluster=${2:?cluster} helm upgrade -i peering-eastwest oci://${HELM_REPO}/peering \ --version ${ISTIO_IMAGE} \ --namespace istio-eastwest \ --kube-context ${context} \ -f - <<EOF eastwest: create: true cluster: ${cluster} # The network that the istio-system namespace is labeled with. # In prod environments, network and cluster are likely not the same value. network: ${cluster} deployment: {} EOF } create_ew_gateway_lb_helm ${context1} ${cluster1} create_ew_gateway_lb_helm ${context2} ${cluster2}
Create the east-west gateway in both clusters. Note that the gateway must still be created with a stable internal IP address, which is required for xDS communication with the istiod control plane in each cluster. NodePort peering is used for data plane communication, in that requests to services resolve to the NodePort instead of the gateway’s stable IP address. For more information about NodePort peering considerations and requirements, see Cross-cluster traffic addresses.
- Solo distribution of
istioctl: For more information about this command, see the CLI reference.function create_ew_gateway_nodeport() { context=${1:?context} cluster=${2:?cluster} kubectl create namespace istio-eastwest --context ${context} istioctl multicluster expose --namespace istio-eastwest --context ${context} --generate > ew-gateway-${cluster}.yaml kubectl apply -f ew-gateway-${cluster}.yaml --context ${context} kubectl annotate gateway istio-eastwest -n istio-eastwest peering.solo.io/data-plane-service-type=NodePort --context ${context} } create_ew_gateway_nodeport ${context1} ${cluster1} create_ew_gateway_nodeport ${context2} ${cluster2}
- Helm: For more information about the
peeringchart, see the Helm values reference. For recommendations on customizing the east-west gateway for resiliency and availability with the Helm chart, see the best practices for multicluster peering.function create_ew_gateway_nodeport_helm() { context=${1:?context} cluster=${2:?cluster} helm upgrade -i peering-eastwest oci://${HELM_REPO}/peering \ --version ${ISTIO_IMAGE} \ --namespace istio-eastwest \ --kube-context ${context} \ -f - <<EOF eastwest: create: true cluster: ${cluster} # The network that the istio-system namespace is labeled with. # In prod environments, network and cluster are likely not the same value. network: ${cluster} dataplaneServiceTypes: nodeport deployment: {} EOF } create_ew_gateway_nodeport_helm ${context1} ${cluster1} create_ew_gateway_nodeport_helm ${context2} ${cluster2}
- Solo distribution of
Verify that the east-west gateway is successfully deployed in both clusters.
kubectl get svc -n istio-eastwest --context ${context1} kubectl get svc -n istio-eastwest --context ${context2}Example output:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE istio-eastwest LoadBalancer 172.20.205.104 <external_address> 15021:31655/TCP,15008:32699/TCP,15012:32166/TCP 55s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE istio-eastwest LoadBalancer 172.20.21.117 <external_address> 15021:30324/TCP,15008:31875/TCP,15012:31050/TCP 77s
Link clusters
Link clusters to enable cross-cluster service discovery and allow traffic to be routed through east-west gateways across clusters.
Optional: Before you link clusters, you can check the individual readiness of each cluster for linking by running the
istioctl multicluster check --precheckcommand. For more information about this command, see the CLI reference. If any checks fail, run the command with--verbose, and see Validate your multicluster setup.istioctl multicluster check --precheck --contexts="$context1,$context2"Before continuing to the next step, make sure that the following checks pass as expected:✅ Relevant environment variables on istiod are supported.✅ The license in use by istiod supports multicluster.✅ All istiod, ztunnel, and east-west gateway pods are healthy.✅ The east-west gateway is programmed.
Link clusters to enable cross-cluster service discovery and allow traffic to be routed through east-west gateways across clusters. Note that you can either link the clusters bi-directionally or asymmetrically. In a standard bi-directional setup, services in any of the linked clusters can send requests to and receive requests from the services in any of the other linked clusters. In an asymmetrical setup, you allow one cluster to send requests to another cluster, but the other cluster cannot send requests back to the first cluster.
In the Solo distribution of Istio 1.29 or later, you can use the
peeringHelm chart to link your clusters.For more information about thepeeringchart, see the Helm values reference.Get the addresses of the east-west gateway in each cluster. The following commands show examples for two clusters.
export CLUSTER1_EW_ADDRESS=$(kubectl get svc -n istio-eastwest istio-eastwest --context ${context1} -o jsonpath="{.status.loadBalancer.ingress[0]['hostname','ip']}") export CLUSTER2_EW_ADDRESS=$(kubectl get svc -n istio-eastwest istio-eastwest --context ${context2} -o jsonpath="{.status.loadBalancer.ingress[0]['hostname','ip']}") echo "Cluster1 east-west gateway: $CLUSTER1_EW_ADDRESS" echo "Cluster2 east-west gateway: $CLUSTER2_EW_ADDRESS"Link the clusters by creating Helm releases in each cluster to represent the other clusters. In each cluster where you create Helm releases, a Gateway resource is created that uses the
istio-remoteGatewayClass. This class allows the gateway to connect to other clusters by using the addresses of the east-west gateways.- Be sure to update the region, and optionally zone, of each cluster.
- If you want to use NodePorts instead of the gateway LoadBalancer IP address for cross-cluster traffic, change the
preferredDataplaneServiceTypetonodeport. This automatically configures the peering gateways for NodePort-based cross-cluster traffic. For more information about NodePort peering considerations and requirements, see Cross-cluster traffic addresses. - The following example depicts a bi-directional setup. In an asymmetrical setup, you create Helm releases to only represent the directionality you want to allow.
helm upgrade -i peering-remote oci://${HELM_REPO}/peering \ --version ${ISTIO_IMAGE} \ --namespace istio-eastwest \ --kube-context ${context1} \ -f - <<EOF remote: create: true items: # Remote peer configuration for cluster2 - name: istio-remote-peer-${cluster2} cluster: ${cluster2} network: ${cluster2} # Change to "Hostname" if using hostname addressType: IPAddress address: ${CLUSTER2_EW_ADDRESS} # Change to "nodeport" if using NodePorts preferredDataplaneServiceType: loadbalancer trustDomain: cluster.local region: "<cluster2_region>" # Uncomment as needed # zone: "<cluster2_zone>" EOF helm upgrade -i peering-remote oci://${HELM_REPO}/peering \ --version ${ISTIO_IMAGE} \ --namespace istio-eastwest \ --kube-context ${context2} \ -f - <<EOF remote: create: true items: # Remote peer configuration for cluster1 - name: istio-remote-peer-${cluster1} cluster: ${cluster1} network: ${cluster1} # Change to "Hostname" if using hostname addressType: IPAddress address: ${CLUSTER1_EW_ADDRESS} # Change to "nodeport" if using NodePorts preferredDataplaneServiceType: loadbalancer trustDomain: cluster.local region: "<cluster1_region>" # Uncomment as needed # zone: "<cluster1_zone>" EOF
Use the
istioctl multicluster linkcommand to quickly link clusters.Verify that the contexts for the clusters that you want to include in the multicluster mesh are listed in your kubeconfig file, which is required for the
istioctl multicluster linkcommand. If you do not have access to the kubeconfig files, use the Helm or declarative resources tabs.kubectl config get-contexts- If you have multiple kubeconfig files, you can generate a merged kubeconfig file by running the following command.
KUBECONFIG=<kubeconfig_file1>.yaml:<file2>.yaml kubectl config view --flatten
- If you have multiple kubeconfig files, you can generate a merged kubeconfig file by running the following command.
Using the names of the cluster contexts, link the clusters so that they can communicate. To take a look at the Gateway resources that this command creates, you can include the
--generateflag in the command. For more information about this command, see the CLI reference.Bi-directional: You can use the following
istioctlcommand to quickly link the clusters bi-directionally. In each cluster, Gateway resources are created that use theistio-remoteGatewayClass. This class allows the gateways to connect to other clusters by using the addresses of the east-west gateways.istioctl multicluster link --namespace istio-eastwest --contexts="$context1,$context2"If you want to use NodePorts instead of the gateway LoadBalancer IP address for cross-cluster traffic, use the
–preferred-data-plane-service-type nodeportflag when linking clusters. This enablement automatically configures the peering gateways for NodePort-based cross-cluster traffic. For more information about NodePort peering considerations and requirements, see Cross-cluster traffic addresses.Example output for two clusters:
Gateway istio-eastwest/istio-remote-peer-cluster1 applied to cluster "cluster2" pointing to cluster "cluster1" (network "cluster1") Gateway istio-eastwest/istio-remote-peer-cluster2 applied to cluster "cluster1" pointing to cluster "cluster2" (network "cluster2")Asymmetrical: You can use the following
istioctlcommand to quickly link the clusters asymmetrically. The services in the cluster in the--fromflag can send requests to services in the cluster in the--toflag, but sending requests in the reverse direction is not permitted.For example, this command allows services in
cluster1’s mesh to send requests to services incluster2’s mesh throughcluster2’s east-west gateway. However, the reverse is not permitted: services incluster2’s mesh cannot send requests throughcluster1’s east-west gateway to services incluster1.istioctl multicluster link --namespace istio-eastwest --from ${context1} --to ${context2}If you want to use NodePorts instead of the gateway LoadBalancer IP address for cross-cluster traffic, use the
–preferred-data-plane-service-type nodeportflag when linking clusters. This automatically configures the peering gateways for NodePort-based cross-cluster traffic. For more information about NodePort peering considerations and requirements, see Cross-cluster traffic addresses.Example output:
Gateway istio-eastwest/istio-remote-peer-cluster2 applied to cluster "cluster1" pointing to cluster "cluster2" (network "cluster2")
Link the clusters by declaratively creating
istio-remotepeer gateways.Bi-directional: Use the following Gateway resources to create an
istio-remotepeer gateway in each cluster. Theistio-remoteGatewayClass allows the gateways to connect to other clusters by using the addresses of the east-west gateways.Get the addresses of the east-west gateway in each cluster. The following commands show examples for two clusters.
export CLUSTER1_EW_ADDRESS=$(kubectl get svc -n istio-eastwest istio-eastwest --context ${context1} -o jsonpath="{.status.loadBalancer.ingress[0]['hostname','ip']}") export CLUSTER2_EW_ADDRESS=$(kubectl get svc -n istio-eastwest istio-eastwest --context ${context2} -o jsonpath="{.status.loadBalancer.ingress[0]['hostname','ip']}") echo "Cluster1 east-west gateway: $CLUSTER1_EW_ADDRESS" echo "Cluster2 east-west gateway: $CLUSTER2_EW_ADDRESS"Using the east-west gateway addresses, create a Gateway resource in each cluster to represent the other cluster.
- In the
labelssection, be sure to update the locality labels according to the region, and optionally zone, of each cluster. For more information about locality support, see the release notes. - If you want to use NodePorts instead of the gateway LoadBalancer IP address for cross-cluster traffic, uncomment the
peering.solo.io/preferred-data-plane-service-type: NodePortannotation from each Gateway resource. Additionally, you can comment out the HBONE listener in each gateway, because traffic is routed through the NodePort directly. For more information about NodePort peering considerations and requirements, see Cross-cluster traffic addresses.
kubectl apply --context ${context1} -f- <<EOF apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: annotations: gateway.istio.io/service-account: istio-eastwest gateway.istio.io/trust-domain: ${cluster2} # Uncomment for NodePort-based cross-cluster traffic # peering.solo.io/preferred-data-plane-service-type: NodePort labels: topology.istio.io/network: ${cluster2} topology.kubernetes.io/region: "<cluster2_region>" # Uncomment as needed # topology.kubernetes.io/zone: "<cluster2_zone>" name: istio-remote-peer-${cluster2} namespace: istio-eastwest spec: addresses: # Change to 'type: Hostname' as needed, such as for AWS hostnames - type: IPAddress value: $CLUSTER2_EW_ADDRESS gatewayClassName: istio-remote listeners: # Comment HBONE out for NodePort-based cross-cluster traffic - name: cross-network port: 15008 protocol: HBONE tls: mode: Passthrough - name: xds-tls port: 15012 protocol: TLS tls: mode: Passthrough EOF kubectl apply --context ${context2} -f- <<EOF apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: annotations: gateway.istio.io/service-account: istio-eastwest gateway.istio.io/trust-domain: ${cluster1} # Uncomment for NodePort-based cross-cluster traffic # peering.solo.io/preferred-data-plane-service-type: NodePort labels: topology.istio.io/network: ${cluster1} topology.kubernetes.io/region: "<cluster1_region>" # Uncomment as needed # topology.kubernetes.io/zone: "<cluster1_zone>" name: istio-remote-peer-${cluster1} namespace: istio-eastwest spec: addresses: # Change to 'type: Hostname' as needed, such as for AWS hostnames - type: IPAddress value: $CLUSTER1_EW_ADDRESS gatewayClassName: istio-remote listeners: # Comment HBONE out for NodePort-based cross-cluster traffic - name: cross-network port: 15008 protocol: HBONE tls: mode: Passthrough - name: xds-tls port: 15012 protocol: TLS tls: mode: Passthrough EOF- In the
Asymmetrical: Use the following Gateway resources to create an
istio-remotepeer gateway in only some clusters. Theistio-remoteGatewayClass allows the gateway in one cluster to connect to another cluster by using the address of the east-west gateway, but sending requests in the reverse direction is not permitted.Get the address of the east-west gateway in the cluster that you want to send traffic to. The following command shows an example for
cluster2.export CLUSTER2_EW_ADDRESS=$(kubectl get svc -n istio-eastwest istio-eastwest --context ${context2} -o jsonpath="{.status.loadBalancer.ingress[0]['hostname','ip']}") echo "Cluster2 east-west gateway: $CLUSTER2_EW_ADDRESS"Using the east-west gateway address, create a Gateway resource in the cluster that you want to send requests from. For example, this Gateway resource allows services in
cluster1’s mesh to send requests to services incluster2’s mesh throughcluster2’s east-west gateway. However, the reverse is not permitted: services incluster2’s mesh cannot send requests throughcluster1’s east-west gateway to services incluster1.- In the
labelssection, be sure to update the locality labels according to the region, and optionally zone, of each cluster. For more information about locality support, see the release notes. - If you want to use NodePorts instead of the gateway LoadBalancer IP address for cross-cluster traffic, uncomment the
peering.solo.io/preferred-data-plane-service-type: NodePortannotation from each Gateway resource. Additionally, you can comment out the HBONE listener in each gateway, because traffic is routed through the NodePort directly. For more information about NodePort peering considerations and requirements, see Cross-cluster traffic addresses.
kubectl apply --context ${context1} -f- <<EOF apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: annotations: gateway.istio.io/service-account: istio-eastwest gateway.istio.io/trust-domain: ${cluster2} # Uncomment for NodePort-based cross-cluster traffic # peering.solo.io/preferred-data-plane-service-type: NodePort labels: topology.istio.io/network: ${cluster2} topology.kubernetes.io/region: "<cluster2_region>" # Uncomment as needed # topology.kubernetes.io/zone: "<cluster2_zone>" name: istio-remote-peer-${cluster2} namespace: istio-eastwest spec: addresses: # Change to 'type: Hostname' as needed, such as for AWS hostnames - type: IPAddress value: $CLUSTER2_EW_ADDRESS gatewayClassName: istio-remote listeners: # Comment HBONE out for NodePort-based cross-cluster traffic - name: cross-network port: 15008 protocol: HBONE tls: mode: Passthrough - name: xds-tls port: 15012 protocol: TLS tls: mode: Passthrough EOF- In the
Verify that peer linking was successful by running the
istioctl multicluster checkcommand. If any checks fail, run the command with--verbose, and see Validate your multicluster setup.istioctl multicluster check --contexts="$context1,$context2"In this example output, the remote peer gateways are successfully connected, and all other checks passed successfully. No global services exist because no app services are exposed across clusters in the multicluster mesh yet.
=== Cluster: arn:aws:eks:us-east-1:111122223333:cluster/cluster1 === ✅ Incompatible Environment Variable Check: all relevant environment variables are valid ✅ License Check: license is valid for multicluster ✅ CNI DNS Capture Check: AMBIENT_DNS_CAPTURE is enabled ✅ Pod Check (istiod): all pods healthy ✅ Pod Check (ztunnel): all pods healthy ✅ Pod Check (eastwest gateway istio-eastwest/istio-eastwest): all pods healthy ✅ Gateway Check: all eastwest gateways programmed ✅ istio-eastwest/istio-eastwest available at aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-1111111111.us-east-1.elb.amazonaws.com ✅ Peers Check: all clusters connected ✅ Connected to cluster2 via bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb-2222222222.us-east-2.elb.amazonaws.com ✅ Remote Gateway Service Account Check: all remote gateway service accounts match ℹ️ Shared Services Check: no globally shared services found ✅ Service Check: no conflicting service aliases or hostnames found ✅ Peer Segments Check: no rejected peer segments found ✅ Missing Local Segment Check: all peer Segments have matching local Segment resources ✅ Hostname Conflict Check: no hostname conflicts detected in auto-generated ServiceEntry resources ====== === Cluster: arn:aws:eks:us-east-2:111122223333:cluster/cluster2 === ✅ Incompatible Environment Variable Check: all relevant environment variables are valid ✅ License Check: license is valid for multicluster ✅ CNI DNS Capture Check: AMBIENT_DNS_CAPTURE is enabled ✅ Pod Check (istiod): all pods healthy ✅ Pod Check (ztunnel): all pods healthy ✅ Pod Check (eastwest gateway istio-eastwest/istio-eastwest): all pods healthy ✅ Gateway Check: all eastwest gateways programmed ✅ istio-eastwest/istio-eastwest available at bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb-2222222222.us-east-2.elb.amazonaws.com ✅ Peers Check: all clusters connected ✅ Connected to cluster1 via aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-1111111111.us-east-1.elb.amazonaws.com ✅ Remote Gateway Service Account Check: all remote gateway service accounts match ℹ️ Shared Services Check: no globally shared services found ✅ Service Check: no conflicting service aliases or hostnames found ✅ Peer Segments Check: no rejected peer segments found ✅ Missing Local Segment Check: all peer Segments have matching local Segment resources ✅ Hostname Conflict Check: no hostname conflicts detected in auto-generated ServiceEntry resources ====== ✅ Intermediate Certs Compatibility Check: all clusters have compatible intermediate certificates ✅ Network Configuration Check: all network configurations are valid ✅ ISTIO_META_NETWORK Check: all ISTIO_META_NETWORK values match network label ✅ Stale Workloads Check: skipped (flat network not detected)Optional: Verify that the istiod control plane for each peered cluster is included in each cluster’s proxy status list.
istioctl proxy-status --context ${context1} istioctl proxy-status --context ${context2}Example output for
cluster1, in which you can verify that the istiod control plane forcluster2is listed:NAME CLUSTER ISTIOD VERSION SUBSCRIBED TYPES istio-eastwest-67fd5679dc-fhsxs.istio-eastwest cluster1 istiod-7b7c9cc4c6-bdm9c 1.30.1-solo-fips 2 (WADS,WDS) istiod-6bc6765484-5bbhd.istio-system cluster2 istiod-7b7c9cc4c6-bdm9c 1.30.1-solo-fips 3 (FSDS,SGDS,WDS) ztunnel-5f8rb.kube-system cluster1 istiod-7b7c9cc4c6-bdm9c 1.30.1-solo-fips 2 (WADS,WDS) ztunnel-f96kh.kube-system cluster1 istiod-7b7c9cc4c6-bdm9c 1.30.1-solo-fips 2 (WADS,WDS) ztunnel-vtj4f.kube-system cluster1 istiod-7b7c9cc4c6-bdm9c 1.30.1-solo-fips 2 (WADS,WDS)
Next: Add apps to the ambient mesh. For multicluster setups, this includes making specific services available across your linked cluster setup.
Option 2: Upgrade and link existing ambient meshes
Upgrade your existing ambient meshes installed with Helm and link them to create a multicluster ambient mesh.
Set up tools
Set your Enterprise level license for Solo Enterprise for Istio as an environment variable. If you do not have one, contact an account representative. If you prefer to specify license keys in a secret instead, see Licensing. Note that you might have previously saved this key in another variable, such as
${SOLO_LICENSE_KEY}or${GLOO_MESH_LICENSE_KEY}.export SOLO_ISTIO_LICENSE_KEY=<enterprise_license_key>Choose the version of Istio that you want to install or upgrade to by reviewing the supported versions.
Save the Solo distribution of Istio version.
export ISTIO_VERSION=1.30.1 export ISTIO_IMAGE=${ISTIO_VERSION}-soloSave the image and Helm repository information for the Solo distribution of Istio.
Istio 1.29 and later:
export REPO=us-docker.pkg.dev/soloio-img/istio export HELM_REPO=us-docker.pkg.dev/soloio-img/istio-helmIstio 1.28 and earlier: You must provide a repo key for the minor version of the Solo distribution of Istio that you want to install. This is the 12-character hash at the end of the repo URL
us-docker.pkg.dev/gloo-mesh/istio-<repo-key>, which you can find in the Istio images built by Solo.io support article.# 12-character hash at the end of the repo URL export REPO_KEY=<repo_key> export REPO=us-docker.pkg.dev/gloo-mesh/istio-${REPO_KEY} export HELM_REPO=us-docker.pkg.dev/gloo-mesh/istio-helm-${REPO_KEY}
Get the Solo distribution of Istio binary and install
istioctl, which you use for multicluster linking and gateway commands. This script automatically detects your OS and architecture, downloads the appropriate Solo distribution of Istio binary, and verifies the installation.bash <(curl -sSfL https://raw.githubusercontent.com/solo-io/doc-examples/main/istio/install-istioctl.sh) export PATH=${HOME}/.istioctl/bin:${PATH}Save the names and kubeconfig contexts of each cluster. This guide uses two clusters as an example. To add more clusters to the multicluster setup, include them in the arrays.
export cluster1=<cluster1_name> export context1=<cluster1_context> export cluster2=<cluster2_name> export context2=<cluster2_context>
Create a shared root of trust
Each cluster in the multicluster setup must have a shared root of trust. This can be achieved by providing a root certificate signed by a PKI provider, or a custom root certificate created for this purpose. The root certificate signs a unique intermediate CA certificate for each cluster.
By default, the Istio CA generates a self-signed root certificate and key, and uses them to sign the workload certificates. For more information, see the Plug in CA Certificates guide in the community Istio documentation.
For demo installations, you can run the following function to quickly generate and plug in the certificates and key for the Istio CA:
curl -L https://istio.io/downloadIstio | ISTIO_VERSION=${ISTIO_VERSION} sh -
cd istio-${ISTIO_VERSION}
mkdir -p certs
pushd certs
make -f ../tools/certs/Makefile.selfsigned.mk root-ca
function create_cacerts_secret() {
context=${1:?context}
cluster=${2:?cluster}
make -f ../tools/certs/Makefile.selfsigned.mk ${cluster}-cacerts
kubectl --context=${context} create ns istio-system || true
kubectl --context=${context} create secret generic cacerts -n istio-system \
--from-file=${cluster}/ca-cert.pem \
--from-file=${cluster}/ca-key.pem \
--from-file=${cluster}/root-cert.pem \
--from-file=${cluster}/cert-chain.pem
}
create_cacerts_secret ${context1} ${cluster1}
create_cacerts_secret ${context2} ${cluster2}
cd ../..To enhance the security of your setup even further and have full control over the Istio CA lifecycle, you can generate and store the root and intermediate CA certificates and keys with your own PKI provider. You can then use tools such as cert-manager to send certificate signing requests on behalf of istiod to your PKI provider. Cert-manager stores the signed intermediate certificates and keys in the cacerts Kubernetes secret so that istiod can use these credentials to issue leaf certificates for the workloads in the service mesh. You can set up cert-manager to also check the certificates and renew them before they expire.
AWS Private CA issuer and cert-manager: For an architectural overview of this certificate setup, see Bring your own Istio CAs with AWS. For steps on how to deploy this certificate setup, check out this Solo.io blog post. Be sure to repeat the steps so that a cacerts secret exists in each cluster.
Upgrade settings
In each cluster, update the ambient mesh components for multicluster, and create an east-west gateway so that traffic requests can be routed cross-cluster.
Get the current values for the istiod Helm release in both clusters.
function get_istiod_values() { context=${1:?context} cluster=${2:?cluster} helm get values --kube-context ${context} istiod -n istio-system -o yaml > istiod-${cluster}.yaml } get_istiod_values ${context1} ${cluster1} get_istiod_values ${context2} ${cluster2}Update your Helm release with the following multicluster values in both clusters. If you must update the Istio minor version, include the
--set global.tag=${ISTIO_IMAGE}and--set global.hub=${REPO}flags too.If you prefer to specify your license secret instead of an inline value, you can include
--set license.secretRef.name=<name>and--set license.secretRef.namespace=<namespace>.function upgrade_istiod() { context=${1:?context} cluster=${2:?cluster} helm upgrade istiod oci://${HELM_REPO}/istiod \ --namespace istio-system \ --kube-context ${context} \ --version ${ISTIO_IMAGE} \ -f istiod-${cluster}.yaml \ --set env.PILOT_ENABLE_IP_AUTOALLOCATE="true" \ --set env.DISABLE_LEGACY_MULTICLUSTER="true" \ --set env.PILOT_SKIP_VALIDATE_TRUST_DOMAIN="true" \ --set global.multiCluster.clusterName=${cluster} \ --set global.network=${cluster} \ --set global.trustDomain="${cluster}.local" \ --set platforms.peering.enabled=true \ --set license.value=${SOLO_ISTIO_LICENSE_KEY} } upgrade_istiod ${context1} ${cluster1} upgrade_istiod ${context2} ${cluster2}If you prefer to specify your license secret instead of an inline value, you can include
--set license.secretRef.name=<name>and--set license.secretRef.namespace=<namespace>.function upgrade_istiod() { context=${1:?context} cluster=${2:?cluster} helm upgrade istiod oci://${HELM_REPO}/istiod \ --namespace istio-system \ --kube-context ${context} \ --version ${ISTIO_IMAGE} \ -f istiod-${cluster}.yaml \ --set env.PILOT_ENABLE_IP_AUTOALLOCATE="true" \ --set env.DISABLE_LEGACY_MULTICLUSTER="true" \ --set env.PILOT_SKIP_VALIDATE_TRUST_DOMAIN="true" \ --set global.multiCluster.clusterName=${cluster} \ --set global.network=${cluster} \ --set global.platform=openshift \ --set global.trustDomain="${cluster}.local" \ --set platforms.peering.enabled=true \ --set license.value=${SOLO_ISTIO_LICENSE_KEY} } upgrade_istiod ${context1} ${cluster1} upgrade_istiod ${context2} ${cluster2}Verify that the istiod pods are successfully restarted in both clusters. Note that it might take a few seconds for the pods to become available.
kubectl get pods --context ${context1} -n istio-system | grep istiod kubectl get pods --context ${context2} -n istio-system | grep istiodExample output:
istiod-b84c55cff-tllfr 1/1 Running 0 58sOptional: Check the istiod logs in both clusters to verify that the certificate you generated earlier is picked up by istiod.
kubectl logs deploy/istiod -n istio-system --context ${context1} | grep x509 kubectl logs deploy/istiod -n istio-system --context ${context2} | grep x509Example output:
2025-12-16T22:59:06.783901Z info x509 cert - Issuer: "CN=Intermediate CA,O=Istio,L=cluster-1", Subject: "", SN: def320623729b8370172413749143836, NotBefore: "2025-12-16T22:57:06Z", NotAfter: "2035-12-14T22:59:06Z" 2025-12-16T22:59:06.783937Z info x509 cert - Issuer: "CN=Root CA,O=Istio", Subject: "CN=Intermediate CA,O=Istio,L=cluster-1", SN: 452d45254328667ccf8434c64c79fe789612bb5a, NotBefore: "2025-12-16T22:54:30Z", NotAfter: "2035-12-14T22:54:30Z" 2025-12-16T22:59:06.783966Z info x509 cert - Issuer: "CN=Root CA,O=Istio", Subject: "CN=Root CA,O=Istio", SN: 38dad68e56ab8c506ae07454801f65b134bd9580, NotBefore: "2025-12-16T22:54:30Z", NotAfter: "2035-12-14T22:54:30Z"Get the current values for the ztunnel Helm release in both clusters.
function get_ztunnel_values() { context=${1:?context} cluster=${2:?cluster} helm get values ztunnel --kube-context ${context} -n istio-system -o yaml > ztunnel-${cluster}.yaml } get_ztunnel_values ${context1} ${cluster1} get_ztunnel_values ${context2} ${cluster2}function get_ztunnel_values() { context=${1:?context} cluster=${2:?cluster} helm get values ztunnel --kube-context ${context} -n kube-system -o yaml > ztunnel-${cluster}.yaml } get_ztunnel_values ${context1} ${cluster1} get_ztunnel_values ${context2} ${cluster2}Update your Helm release with the following multicluster values in both clusters. If you must update the Istio minor version, include the
--set tag=${ISTIO_IMAGE}and--set hub=${REPO}flags too.function upgrade_ztunnel() { context=${1:?context} cluster=${2:?cluster} helm upgrade ztunnel oci://${HELM_REPO}/ztunnel \ -n istio-system \ --version ${ISTIO_IMAGE} \ --kube-context ${context} \ -f ztunnel-${cluster}.yaml \ --set env.SKIP_VALIDATE_TRUST_DOMAIN="true" \ --set multiCluster.clusterName=${cluster} \ --set network=${cluster} } upgrade_ztunnel ${context1} ${cluster1} upgrade_ztunnel ${context2} ${cluster2}function upgrade_ztunnel() { context=${1:?context} cluster=${2:?cluster} helm upgrade ztunnel oci://${HELM_REPO}/ztunnel \ -n kube-system \ --version ${ISTIO_IMAGE} \ --kube-context ${context} \ -f ztunnel-${cluster}.yaml \ --set env.SKIP_VALIDATE_TRUST_DOMAIN="true" \ --set multiCluster.clusterName=${cluster} \ --set network=${cluster} } upgrade_ztunnel ${context1} ${cluster1} upgrade_ztunnel ${context2} ${cluster2}Verify that the ztunnel pods are successfully installed in both clusters. Because the ztunnel is deployed as a daemon set, the number of pods equals the number of nodes in your cluster. Note that it might take a few seconds for the pods to become available.
kubectl get pods -A --context ${context1} | grep ztunnel kubectl get pods -A --context ${context2} | grep ztunnelExample output for one cluster:
ztunnel-tvtzn 1/1 Running 0 7s ztunnel-vtpjm 1/1 Running 0 4s ztunnel-hllxg 1/1 Running 0 4sLabel the
istio-systemnamespace with the clusters’ network names, which you previously set to each cluster name in theglobal.networkfield of theistiodinstallations. The ambient control plane uses this label internally to group pods that exist in the same L3 network.kubectl label namespace istio-system --context ${context1} topology.istio.io/network=${cluster1} kubectl label namespace istio-system --context ${context2} topology.istio.io/network=${cluster2}Create an east-west gateway in the
istio-eastwestnamespace. In each cluster, the east-west gateway is implemented as a ztunnel that facilitates traffic between services across clusters in your multicluster mesh.You can use either LoadBalancer or NodePort addresses for cross-cluster traffic.
Create the east-west gateway in both clusters. Cross-cluster traffic though this gateway resolves to the LoadBalancer address. For customization options, see the gateway guide in the Istio docs.
- Solo distribution of
istioctl: For more information about this command, see the CLI reference.function create_ew_gateway_lb() { context=${1:?context} cluster=${2:?cluster} kubectl create namespace istio-eastwest --context ${context} istioctl multicluster expose --namespace istio-eastwest --context ${context} --generate > ew-gateway-${cluster}.yaml kubectl apply -f ew-gateway-${cluster}.yaml --context ${context} } create_ew_gateway_lb ${context1} ${cluster1} create_ew_gateway_lb ${context2} ${cluster2}
- Helm: For more information about the
peeringchart, see the Helm values reference. For recommendations on customizing the east-west gateway for resiliency and availability with the Helm chart, see the best practices for multicluster peering.function create_ew_gateway_lb_helm() { context=${1:?context} cluster=${2:?cluster} helm upgrade -i peering-eastwest oci://${HELM_REPO}/peering \ --version ${ISTIO_IMAGE} \ --namespace istio-eastwest \ --kube-context ${context} \ -f - <<EOF eastwest: create: true cluster: ${cluster} # The network that the istio-system namespace is labeled with. # In prod environments, network and cluster are likely not the same value. network: ${cluster} deployment: {} EOF } create_ew_gateway_lb_helm ${context1} ${cluster1} create_ew_gateway_lb_helm ${context2} ${cluster2}
Create the east-west gateway in both clusters. Note that the gateway must still be created with a stable internal IP address, which is required for xDS communication with the istiod control plane in each cluster. NodePort peering is used for data plane communication, in that requests to services resolve to the NodePort instead of the gateway’s stable IP address. For more information about NodePort peering considerations and requirements, see Cross-cluster traffic addresses.
- Solo distribution of
istioctl: For more information about this command, see the CLI reference.function create_ew_gateway_nodeport() { context=${1:?context} cluster=${2:?cluster} kubectl create namespace istio-eastwest --context ${context} istioctl multicluster expose --namespace istio-eastwest --context ${context} --generate > ew-gateway-${cluster}.yaml kubectl apply -f ew-gateway-${cluster}.yaml --context ${context} kubectl annotate gateway istio-eastwest -n istio-eastwest peering.solo.io/data-plane-service-type=NodePort --context ${context} } create_ew_gateway_nodeport ${context1} ${cluster1} create_ew_gateway_nodeport ${context2} ${cluster2}
- Helm: For more information about the
peeringchart, see the Helm values reference. For recommendations on customizing the east-west gateway for resiliency and availability with the Helm chart, see the best practices for multicluster peering.function create_ew_gateway_nodeport_helm() { context=${1:?context} cluster=${2:?cluster} helm upgrade -i peering-eastwest oci://${HELM_REPO}/peering \ --version ${ISTIO_IMAGE} \ --namespace istio-eastwest \ --kube-context ${context} \ -f - <<EOF eastwest: create: true cluster: ${cluster} # The network that the istio-system namespace is labeled with. # In prod environments, network and cluster are likely not the same value. network: ${cluster} dataplaneServiceTypes: nodeport deployment: {} EOF } create_ew_gateway_nodeport_helm ${context1} ${cluster1} create_ew_gateway_nodeport_helm ${context2} ${cluster2}
- Solo distribution of
Verify that the east-west gateway is successfully deployed in both clusters.
kubectl get svc -n istio-eastwest --context ${context1} kubectl get svc -n istio-eastwest --context ${context2}Example output:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE istio-eastwest LoadBalancer 172.20.205.104 <external_address> 15021:31655/TCP,15008:32699/TCP,15012:32166/TCP 55s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE istio-eastwest LoadBalancer 172.20.21.117 <external_address> 15021:30324/TCP,15008:31875/TCP,15012:31050/TCP 77s
Link clusters
Link clusters to enable cross-cluster service discovery and allow traffic to be routed through east-west gateways across clusters.
Optional: Before you link clusters, you can check the individual readiness of each cluster for linking by running the
istioctl multicluster check --precheckcommand. For more information about this command, see the CLI reference. If any checks fail, run the command with--verbose, and see Validate your multicluster setup.istioctl multicluster check --precheck --contexts="$context1,$context2"Before continuing to the next step, make sure that the following checks pass as expected:✅ Relevant environment variables on istiod are supported.✅ The license in use by istiod supports multicluster.✅ All istiod, ztunnel, and east-west gateway pods are healthy.✅ The east-west gateway is programmed.
Link clusters to enable cross-cluster service discovery and allow traffic to be routed through east-west gateways across clusters. Note that you can either link the clusters bi-directionally or asymmetrically. In a standard bi-directional setup, services in any of the linked clusters can send requests to and receive requests from the services in any of the other linked clusters. In an asymmetrical setup, you allow one cluster to send requests to another cluster, but the other cluster cannot send requests back to the first cluster.
In the Solo distribution of Istio 1.29 or later, you can use the
peeringHelm chart to link your clusters.For more information about thepeeringchart, see the Helm values reference.Get the addresses of the east-west gateway in each cluster. The following commands show examples for two clusters.
export CLUSTER1_EW_ADDRESS=$(kubectl get svc -n istio-eastwest istio-eastwest --context ${context1} -o jsonpath="{.status.loadBalancer.ingress[0]['hostname','ip']}") export CLUSTER2_EW_ADDRESS=$(kubectl get svc -n istio-eastwest istio-eastwest --context ${context2} -o jsonpath="{.status.loadBalancer.ingress[0]['hostname','ip']}") echo "Cluster1 east-west gateway: $CLUSTER1_EW_ADDRESS" echo "Cluster2 east-west gateway: $CLUSTER2_EW_ADDRESS"Link the clusters by creating Helm releases in each cluster to represent the other clusters. In each cluster where you create Helm releases, a Gateway resource is created that uses the
istio-remoteGatewayClass. This class allows the gateway to connect to other clusters by using the addresses of the east-west gateways.- Be sure to update the region, and optionally zone, of each cluster.
- If you want to use NodePorts instead of the gateway LoadBalancer IP address for cross-cluster traffic, change the
preferredDataplaneServiceTypetonodeport. This automatically configures the peering gateways for NodePort-based cross-cluster traffic. For more information about NodePort peering considerations and requirements, see Cross-cluster traffic addresses. - The following example depicts a bi-directional setup. In an asymmetrical setup, you create Helm releases to only represent the directionality you want to allow.
helm upgrade -i peering-remote oci://${HELM_REPO}/peering \ --version ${ISTIO_IMAGE} \ --namespace istio-eastwest \ --kube-context ${context1} \ -f - <<EOF remote: create: true items: # Remote peer configuration for cluster2 - name: istio-remote-peer-${cluster2} cluster: ${cluster2} network: ${cluster2} # Change to "Hostname" if using hostname addressType: IPAddress address: ${CLUSTER2_EW_ADDRESS} # Change to "nodeport" if using NodePorts preferredDataplaneServiceType: loadbalancer trustDomain: cluster.local region: "<cluster2_region>" # Uncomment as needed # zone: "<cluster2_zone>" EOF helm upgrade -i peering-remote oci://${HELM_REPO}/peering \ --version ${ISTIO_IMAGE} \ --namespace istio-eastwest \ --kube-context ${context2} \ -f - <<EOF remote: create: true items: # Remote peer configuration for cluster1 - name: istio-remote-peer-${cluster1} cluster: ${cluster1} network: ${cluster1} # Change to "Hostname" if using hostname addressType: IPAddress address: ${CLUSTER1_EW_ADDRESS} # Change to "nodeport" if using NodePorts preferredDataplaneServiceType: loadbalancer trustDomain: cluster.local region: "<cluster1_region>" # Uncomment as needed # zone: "<cluster1_zone>" EOF
Use the
istioctl multicluster linkcommand to quickly link clusters.Verify that the contexts for the clusters that you want to include in the multicluster mesh are listed in your kubeconfig file, which is required for the
istioctl multicluster linkcommand. If you do not have access to the kubeconfig files, use the Helm or declarative resources tabs.kubectl config get-contexts- If you have multiple kubeconfig files, you can generate a merged kubeconfig file by running the following command.
KUBECONFIG=<kubeconfig_file1>.yaml:<file2>.yaml kubectl config view --flatten
- If you have multiple kubeconfig files, you can generate a merged kubeconfig file by running the following command.
Using the names of the cluster contexts, link the clusters so that they can communicate. To take a look at the Gateway resources that this command creates, you can include the
--generateflag in the command. For more information about this command, see the CLI reference.Bi-directional: You can use the following
istioctlcommand to quickly link the clusters bi-directionally. In each cluster, Gateway resources are created that use theistio-remoteGatewayClass. This class allows the gateways to connect to other clusters by using the addresses of the east-west gateways.istioctl multicluster link --namespace istio-eastwest --contexts="$context1,$context2"If you want to use NodePorts instead of the gateway LoadBalancer IP address for cross-cluster traffic, use the
–preferred-data-plane-service-type nodeportflag when linking clusters. This enablement automatically configures the peering gateways for NodePort-based cross-cluster traffic. For more information about NodePort peering considerations and requirements, see Cross-cluster traffic addresses.Example output for two clusters:
Gateway istio-eastwest/istio-remote-peer-cluster1 applied to cluster "cluster2" pointing to cluster "cluster1" (network "cluster1") Gateway istio-eastwest/istio-remote-peer-cluster2 applied to cluster "cluster1" pointing to cluster "cluster2" (network "cluster2")Asymmetrical: You can use the following
istioctlcommand to quickly link the clusters asymmetrically. The services in the cluster in the--fromflag can send requests to services in the cluster in the--toflag, but sending requests in the reverse direction is not permitted.For example, this command allows services in
cluster1’s mesh to send requests to services incluster2’s mesh throughcluster2’s east-west gateway. However, the reverse is not permitted: services incluster2’s mesh cannot send requests throughcluster1’s east-west gateway to services incluster1.istioctl multicluster link --namespace istio-eastwest --from ${context1} --to ${context2}If you want to use NodePorts instead of the gateway LoadBalancer IP address for cross-cluster traffic, use the
–preferred-data-plane-service-type nodeportflag when linking clusters. This automatically configures the peering gateways for NodePort-based cross-cluster traffic. For more information about NodePort peering considerations and requirements, see Cross-cluster traffic addresses.Example output:
Gateway istio-eastwest/istio-remote-peer-cluster2 applied to cluster "cluster1" pointing to cluster "cluster2" (network "cluster2")
Link the clusters by declaratively creating
istio-remotepeer gateways.Bi-directional: Use the following Gateway resources to create an
istio-remotepeer gateway in each cluster. Theistio-remoteGatewayClass allows the gateways to connect to other clusters by using the addresses of the east-west gateways.Get the addresses of the east-west gateway in each cluster. The following commands show examples for two clusters.
export CLUSTER1_EW_ADDRESS=$(kubectl get svc -n istio-eastwest istio-eastwest --context ${context1} -o jsonpath="{.status.loadBalancer.ingress[0]['hostname','ip']}") export CLUSTER2_EW_ADDRESS=$(kubectl get svc -n istio-eastwest istio-eastwest --context ${context2} -o jsonpath="{.status.loadBalancer.ingress[0]['hostname','ip']}") echo "Cluster1 east-west gateway: $CLUSTER1_EW_ADDRESS" echo "Cluster2 east-west gateway: $CLUSTER2_EW_ADDRESS"Using the east-west gateway addresses, create a Gateway resource in each cluster to represent the other cluster.
- In the
labelssection, be sure to update the locality labels according to the region, and optionally zone, of each cluster. For more information about locality support, see the release notes. - If you want to use NodePorts instead of the gateway LoadBalancer IP address for cross-cluster traffic, uncomment the
peering.solo.io/preferred-data-plane-service-type: NodePortannotation from each Gateway resource. Additionally, you can comment out the HBONE listener in each gateway, because traffic is routed through the NodePort directly. For more information about NodePort peering considerations and requirements, see Cross-cluster traffic addresses.
kubectl apply --context ${context1} -f- <<EOF apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: annotations: gateway.istio.io/service-account: istio-eastwest gateway.istio.io/trust-domain: ${cluster2} # Uncomment for NodePort-based cross-cluster traffic # peering.solo.io/preferred-data-plane-service-type: NodePort labels: topology.istio.io/network: ${cluster2} topology.kubernetes.io/region: "<cluster2_region>" # Uncomment as needed # topology.kubernetes.io/zone: "<cluster2_zone>" name: istio-remote-peer-${cluster2} namespace: istio-eastwest spec: addresses: # Change to 'type: Hostname' as needed, such as for AWS hostnames - type: IPAddress value: $CLUSTER2_EW_ADDRESS gatewayClassName: istio-remote listeners: # Comment HBONE out for NodePort-based cross-cluster traffic - name: cross-network port: 15008 protocol: HBONE tls: mode: Passthrough - name: xds-tls port: 15012 protocol: TLS tls: mode: Passthrough EOF kubectl apply --context ${context2} -f- <<EOF apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: annotations: gateway.istio.io/service-account: istio-eastwest gateway.istio.io/trust-domain: ${cluster1} # Uncomment for NodePort-based cross-cluster traffic # peering.solo.io/preferred-data-plane-service-type: NodePort labels: topology.istio.io/network: ${cluster1} topology.kubernetes.io/region: "<cluster1_region>" # Uncomment as needed # topology.kubernetes.io/zone: "<cluster1_zone>" name: istio-remote-peer-${cluster1} namespace: istio-eastwest spec: addresses: # Change to 'type: Hostname' as needed, such as for AWS hostnames - type: IPAddress value: $CLUSTER1_EW_ADDRESS gatewayClassName: istio-remote listeners: # Comment HBONE out for NodePort-based cross-cluster traffic - name: cross-network port: 15008 protocol: HBONE tls: mode: Passthrough - name: xds-tls port: 15012 protocol: TLS tls: mode: Passthrough EOF- In the
Asymmetrical: Use the following Gateway resources to create an
istio-remotepeer gateway in only some clusters. Theistio-remoteGatewayClass allows the gateway in one cluster to connect to another cluster by using the address of the east-west gateway, but sending requests in the reverse direction is not permitted.Get the address of the east-west gateway in the cluster that you want to send traffic to. The following command shows an example for
cluster2.export CLUSTER2_EW_ADDRESS=$(kubectl get svc -n istio-eastwest istio-eastwest --context ${context2} -o jsonpath="{.status.loadBalancer.ingress[0]['hostname','ip']}") echo "Cluster2 east-west gateway: $CLUSTER2_EW_ADDRESS"Using the east-west gateway address, create a Gateway resource in the cluster that you want to send requests from. For example, this Gateway resource allows services in
cluster1’s mesh to send requests to services incluster2’s mesh throughcluster2’s east-west gateway. However, the reverse is not permitted: services incluster2’s mesh cannot send requests throughcluster1’s east-west gateway to services incluster1.- In the
labelssection, be sure to update the locality labels according to the region, and optionally zone, of each cluster. For more information about locality support, see the release notes. - If you want to use NodePorts instead of the gateway LoadBalancer IP address for cross-cluster traffic, uncomment the
peering.solo.io/preferred-data-plane-service-type: NodePortannotation from each Gateway resource. Additionally, you can comment out the HBONE listener in each gateway, because traffic is routed through the NodePort directly. For more information about NodePort peering considerations and requirements, see Cross-cluster traffic addresses.
kubectl apply --context ${context1} -f- <<EOF apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: annotations: gateway.istio.io/service-account: istio-eastwest gateway.istio.io/trust-domain: ${cluster2} # Uncomment for NodePort-based cross-cluster traffic # peering.solo.io/preferred-data-plane-service-type: NodePort labels: topology.istio.io/network: ${cluster2} topology.kubernetes.io/region: "<cluster2_region>" # Uncomment as needed # topology.kubernetes.io/zone: "<cluster2_zone>" name: istio-remote-peer-${cluster2} namespace: istio-eastwest spec: addresses: # Change to 'type: Hostname' as needed, such as for AWS hostnames - type: IPAddress value: $CLUSTER2_EW_ADDRESS gatewayClassName: istio-remote listeners: # Comment HBONE out for NodePort-based cross-cluster traffic - name: cross-network port: 15008 protocol: HBONE tls: mode: Passthrough - name: xds-tls port: 15012 protocol: TLS tls: mode: Passthrough EOF- In the
Verify that peer linking was successful by running the
istioctl multicluster checkcommand. If any checks fail, run the command with--verbose, and see Validate your multicluster setup.istioctl multicluster check --contexts="$context1,$context2"In this example output, the remote peer gateways are successfully connected, and all other checks passed successfully. No global services exist because no app services are exposed across clusters in the multicluster mesh yet.
=== Cluster: arn:aws:eks:us-east-1:111122223333:cluster/cluster1 === ✅ Incompatible Environment Variable Check: all relevant environment variables are valid ✅ License Check: license is valid for multicluster ✅ CNI DNS Capture Check: AMBIENT_DNS_CAPTURE is enabled ✅ Pod Check (istiod): all pods healthy ✅ Pod Check (ztunnel): all pods healthy ✅ Pod Check (eastwest gateway istio-eastwest/istio-eastwest): all pods healthy ✅ Gateway Check: all eastwest gateways programmed ✅ istio-eastwest/istio-eastwest available at aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-1111111111.us-east-1.elb.amazonaws.com ✅ Peers Check: all clusters connected ✅ Connected to cluster2 via bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb-2222222222.us-east-2.elb.amazonaws.com ✅ Remote Gateway Service Account Check: all remote gateway service accounts match ℹ️ Shared Services Check: no globally shared services found ✅ Service Check: no conflicting service aliases or hostnames found ✅ Peer Segments Check: no rejected peer segments found ✅ Missing Local Segment Check: all peer Segments have matching local Segment resources ✅ Hostname Conflict Check: no hostname conflicts detected in auto-generated ServiceEntry resources ====== === Cluster: arn:aws:eks:us-east-2:111122223333:cluster/cluster2 === ✅ Incompatible Environment Variable Check: all relevant environment variables are valid ✅ License Check: license is valid for multicluster ✅ CNI DNS Capture Check: AMBIENT_DNS_CAPTURE is enabled ✅ Pod Check (istiod): all pods healthy ✅ Pod Check (ztunnel): all pods healthy ✅ Pod Check (eastwest gateway istio-eastwest/istio-eastwest): all pods healthy ✅ Gateway Check: all eastwest gateways programmed ✅ istio-eastwest/istio-eastwest available at bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb-2222222222.us-east-2.elb.amazonaws.com ✅ Peers Check: all clusters connected ✅ Connected to cluster1 via aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-1111111111.us-east-1.elb.amazonaws.com ✅ Remote Gateway Service Account Check: all remote gateway service accounts match ℹ️ Shared Services Check: no globally shared services found ✅ Service Check: no conflicting service aliases or hostnames found ✅ Peer Segments Check: no rejected peer segments found ✅ Missing Local Segment Check: all peer Segments have matching local Segment resources ✅ Hostname Conflict Check: no hostname conflicts detected in auto-generated ServiceEntry resources ====== ✅ Intermediate Certs Compatibility Check: all clusters have compatible intermediate certificates ✅ Network Configuration Check: all network configurations are valid ✅ ISTIO_META_NETWORK Check: all ISTIO_META_NETWORK values match network label ✅ Stale Workloads Check: skipped (flat network not detected)Optional: Verify that the istiod control plane for each peered cluster is included in each cluster’s proxy status list.
istioctl proxy-status --context ${context1} istioctl proxy-status --context ${context2}Example output for
cluster1, in which you can verify that the istiod control plane forcluster2is listed:NAME CLUSTER ISTIOD VERSION SUBSCRIBED TYPES istio-eastwest-67fd5679dc-fhsxs.istio-eastwest cluster1 istiod-7b7c9cc4c6-bdm9c 1.30.1-solo-fips 2 (WADS,WDS) istiod-6bc6765484-5bbhd.istio-system cluster2 istiod-7b7c9cc4c6-bdm9c 1.30.1-solo-fips 3 (FSDS,SGDS,WDS) ztunnel-5f8rb.kube-system cluster1 istiod-7b7c9cc4c6-bdm9c 1.30.1-solo-fips 2 (WADS,WDS) ztunnel-f96kh.kube-system cluster1 istiod-7b7c9cc4c6-bdm9c 1.30.1-solo-fips 2 (WADS,WDS) ztunnel-vtj4f.kube-system cluster1 istiod-7b7c9cc4c6-bdm9c 1.30.1-solo-fips 2 (WADS,WDS)
Next: Add apps to the ambient mesh. For multicluster setups, this includes making specific services available across your linked cluster setup.
Next
- Add apps to the ambient mesh. For multicluster setups, this includes making specific services available across your linked cluster setup.
- In a multicluster mesh, the east-west gateway serves as a ztunnel that allows traffic requests to flow across clusters, but it does not modify requests in any way. To control in-mesh traffic, you can instead apply policies to waypoint proxies that you create for a workload namespace.
Optional: Validate your multicluster setup
Both before and after you link clusters into a multicluster mesh, you can use theistioctl multicluster check command, along with other observability checks, to verify multiple aspects of multicluster ambient mesh support and status.istioctl multicluster check
You can use the istioctl multicluster check --precheck command to check the individual readiness of each cluster before running istioctl multicluster link to link them in a multicluster mesh, and run it again after linking to confirm that the connections were successful. This command performs checks listed in the following sections, which you can review to understand what each check validates. Additionally, if any of the checks fail, run the command with the --verbose option, and review the following troubleshooting recommendations.
istioctl multicluster check --verbose --contexts="$context1,$context2"For more information about this command, see the CLI reference.
Starting in version 1.30, you can pass -o json or -o yaml to emit all check results grouped by cluster as a structured document. You can also run the command against an extracted bug-report directory to perform offline multicluster analysis without direct cluster access.
Incompatible environment variables
Checks whether the ENABLE_PEERING_DISCOVERY=true and optionally K8S_SELECT_WORKLOAD_ENTRIES=true environment variables are set incorrectly or are not supported for multicluster ambient mesh.
Example verbose output:
--- Incompatible Environment Variable Check ---
✅ Incompatible Environment Variable Check: K8S_SELECT_WORKLOAD_ENTRIES is valid ("")
✅ Incompatible Environment Variable Check: ENABLE_PEERING_DISCOVERY is valid ("true")
✅ Incompatible Environment Variable Check: all relevant environment variables are validIf this check fails, check your environment variables in your istiod configuration, such as by running helm get values --kube-context ${CLUSTER_CONTEXT} istiod -n istio-system -o yaml, and update your configuration.
License validity
Checks whether the license in use by istiod is valid for multicluster ambient mesh. Multicluster capabilities require an Enterprise level license for Solo Enterprise for Istio.
Example verbose output:
--- License Check ---
✅ License Check: license is valid for multiclusterIf your license does not support multicluster ambient mesh, contact your Solo account representative.
CNI DNS capture
Checks whether ambient DNS capture is enabled in the istio-cni-config ConfigMap. DNS capture is required for workloads to resolve global hostnames (.mesh.internal and custom Segment domains) used to route traffic to services across clusters. The check returns a warning if AMBIENT_DNS_CAPTURE is not set or is not true.
Example verbose output:
--- CNI DNS Capture Check ---
✅ CNI DNS Capture Check: AMBIENT_DNS_CAPTURE is enabledIf this check fails or warns, verify the value in the ConfigMap:
kubectl get configmap istio-cni-config -n istio-system \
-o jsonpath='{.data.AMBIENT_DNS_CAPTURE}'To enable DNS capture, set values.cni.ambient.dnsCapture=true in the istiod Helm chart values and upgrade your installation.
Pod health
Checks the health of the pods in the cluster. All istiod, ztunnel, and east-west gateway pods across the checked clusters must be healthy and running for the multicluster mesh to function correctly.
Example verbose output:
--- Pod Check (istiod) ---
NAME READY STATUS RESTARTS AGE
istiod-6d9cdf88cf-l47tf 1/1 Running 0 10m18s
✅ Pod Check (istiod): all pods healthy
--- Pod Check (ztunnel) ---
NAME READY STATUS RESTARTS AGE
ztunnel-dvlwk 1/1 Running 0 10m6s
✅ Pod Check (ztunnel): all pods healthy
--- Pod Check (eastwest gateway) ---
NAME READY STATUS RESTARTS AGE
istio-eastwest-857b77fc5d-qgnrl 1/1 Running 0 9m33s
✅ Pod Check (eastwest gateway): all pods healthyTo check any unhealthy pods, run the following commands. Consider checking the pod logs, and review Debug Istio.
kubectl get po -n istio-system
kubectl get po -n istio-eastwestEast-west gateway status
Checks the status of the east-west gateways in the cluster. When an east-west gateway is created, the gateway controller creates a Kubernetes service to expose the gateway. Once this service is correctly attached to the gateway and has an address assigned, the east-west gateway has a Programmed status of true.
Example verbose output:
--- Gateway Check ---
Gateway: istio-eastwest
Addresses:
- 172.18.7.110
Status: programmed ✅
✅ Gateway Check: all eastwest gateways programmedIf the Programmed status is not true, an issue might exist with the address allocation for the service. Check the east-west gateway with a command such as kubectl get svc -n istio-eastwest, and verify that your cloud provider can correctly allocate addresses to the service.
NodePort east-west gateway health
For east-west gateways configured for NodePort peering, checks that the gateway has not fallen back to reporting a ClusterIP address, and validates that the backing Service has a port named hbone.
Example verbose output:
--- NodePort Gateway Health Check ---
Gateway: istio-eastwest
Backing service has hbone port ✅
NodePort address detected ✅
✅ NodePort Gateway Health Check: all NodePort east-west gateways validIf this check fails, verify that the backing Service for the east-west gateway has a port named hbone and is correctly annotated for NodePort peering. You can also inspect the gloo.solo.io/NodePortConfigured status condition on the gateway: when True with reason Programmed, the HBONE port is correctly configured; when False with reason MissingHbonePort, the port is absent from the backing Service.
Remote peer gateway status
Checks the status of the remote peer gateways in the cluster, which represent the other peered clusters in the multicluster setup. These remote gateways configure the connection between the local cluster’s istiod control plane, and the peered clusters’ remote networks to enable xDS communication between peers. When the initial network connection between istiod and a remote peer is made, the gateway’s gloo.solo.io/PeerConnected status updates to true. Then, when the full xDS sync occurs between peers, the gateway’s gloo.solo.io/PeeringSucceeded status also updates to true. This check ensures that both statuses are true, and that the topology.istio.io/cluster label is set on the gateway.
Example verbose output:
--- Peers Check ---
Cluster: cluster2
Addresses:
- 172.18.7.130
Conditions:
- Accepted: True
- Programmed: True
- gloo.solo.io/PeerConnected: True
- gloo.solo.io/PeeringSucceeded: True
- gloo.solo.io/PeerDataPlaneProgrammed: True
Status: connected ✅
✅ Peers Check: all clusters connectedIf the connection is severed between the peers, the gloo.solo.io/PeerConnected status becomes false. A failed connection between peers can be due to either a misconfiguration in the peering setup, or a network issue blocking port 15008 on the remote cluster, which is the cross-network HBONE port that the east-west gateway listens on. Review the steps you took to link clusters together, such as the steps outlined in the Helm default network guide. Additionally, review any firewall rules or network policies that might block access through port 15008 on the remote cluster.
Remote gateway service account
Checks that the effective service account for each istio-remote Gateway resource matches the corresponding peer east-west gateway in the remote cluster. A mismatch can cause authentication failures when the local istiod control plane attempts to connect to the remote cluster.
Example verbose output:
--- Remote Gateway Service Account Check ---
Remote gateway istio-eastwest/istio-remote-peer-cluster2 service account: istio-eastwest/istio-eastwest ✅
✅ Remote Gateway Service Account Check: all remote gateway service accounts matchIf this check fails, review the service account configuration on your istio-remote Gateway resources and confirm they match the service account used by the east-west gateway in the corresponding remote cluster.
Intermediate certificate compatibility
Confirms the certificate compatibility between peered clusters. This check reads the root-cert.pem from the istio-ca-root-cert configmap in the istio-system namespace, and uses x509 certificate validation to confirm the root cert is compatible with all of the clusters’ ca-cert.pem intermediate certificate chains from the cacerts secret.
Example verbose output:
--- Intermediate Certs Compatibility Check ---
ℹ Intermediate Certs Compatibility Check: cluster cluster1 root certificate SHA256 sum: 6d18f32e134824c158d97f32618657c45d5a83839f838ada751757139481537e
ℹ Intermediate Certs Compatibility Check: cluster cluster2 root certificate SHA256 sum: 6d18f32e134824c158d97f32618657c45d5a83839f838ada751757139481537e
✅ Intermediate Certs Compatibility Check: cluster cluster1 has compatible intermediate certificates with cluster cluster2
✅ Intermediate Certs Compatibility Check: cluster cluster2 has compatible intermediate certificates with cluster cluster1
✅ Intermediate Certs Compatibility Check: all clusters have compatible intermediate certificatesIf this check fails because the root certs are not valid for each peered clusters’ intermediate certificate chain, you can check the istiod logs for TLS errors when attempting to communicate with a peered cluster, such as the following:
2025-12-04T22:09:22.474517Z warn deltaadsc disconnected, retrying in 24.735483751s: delta stream: rpc error: code = Unavailable desc = connection error: desc = "error reading server preface: remote error: tls: unknown certificate authority" target=peering-cluster2Ensure each cluster has a cacerts secret in the istio-system namespace. To regenerate invalid certificates for each cluster, follow the example steps in Create a shared root of trust.
Network configuration
Confirms the network configuration of the multicluster mesh. For multicluster peering setups that do not use a flat network topology, each cluster must occupy a unique network. The network name must be defined with the label topology.istio.io/network and set on both the istio-system namespace and the istio-eastwest gateway resource. The same network name must also be set as the NETWORK environment variable on the ztunnel daemonset. Each remote gateway that represents that cluster must have the topology.istio.io/network label equal to the network of the remote cluster.Each remote gateway must also have the topology.istio.io/cluster label set to the cluster ID of the remote cluster. This label is required for the peering controller to federate workload entries correctly. If a remote gateway is missing the topology.istio.io/cluster label, the check returns an error. If the label references a cluster ID not included in –contexts, the check returns an informational message.
Example verbose output:
--- Network Configuration Check ---
✅ Cluster cluster1 has network: cluster1
✅ Eastwest gateway istio-eastwest/istio-eastwest has correct network label: cluster1
✅ Cluster cluster2 has network: cluster2
✅ Eastwest gateway istio-eastwest/istio-eastwest has correct network label: cluster2
✅ Remote gateway istio-eastwest/istio-remote-peer-cluster2 references network cluster2 (clusters: [cluster2])
✅ Remote gateway istio-eastwest/istio-remote-peer-cluster1 references network cluster1 (clusters: [cluster1])
✅ Network Configuration Check: all network configurations are validMismatched network identities cause errors in cross-cluster communication, which leads to error logs in ztunnel pods that indicate a network timeout on the outbound communication. Notably, the destination address on these errors is a 240.X.X.X address, instead of the correct remote peer gateway address. You can run kubectl logs -l app=ztunnel -n istio-system --tail=10 --context ${CLUSTER_CONTEXT} | grep -iE "error|warn" to review logs such as the following:
2025-11-18T16:14:53.490573Z error access connection complete src.addr=240.0.2.27:46802 src.workload="ratings-v1-5dc79b6bcd-zm8v6" src.namespace="bookinfo" src.identity="spiffe://cluster.local/ns/bookinfo/sa/bookinfo-ratings" dst.addr=240.0.9.43:15008 dst.hbone_addr=240.0.9.43:9080 dst.service="productpage.bookinfo.mesh.internal" dst.workload="autogenflat.portfolio1-soloiopoc-cluster1.bookinfo.productpage-v1-54bb874995-hblwp.ee508601917c" dst.namespace="bookinfo" dst.identity="spiffe://cluster.local/ns/bookinfo/sa/bookinfo-productpage" direction="outbound" bytes_sent=0 bytes_recv=0 duration="10001ms" error="connection timed out, maybe a NetworkPolicy is blocking HBONE port 15008: deadline has elapsed"To troubleshoot these issues, be sure that you use unique network names to represent each cluster, and that you correctly labeled the cluster’s istio-system namespace with that network name, such as by running kubectl label namespace istio-system --context ${CLUSTER_CONTEXT} topology.istio.io/network=${CLUSTER_NAME}. You can also relabel the east-west gateway in the cluster, and the remote peer gateways in other clusters that represent this cluster.
ISTIO_META_NETWORK environment variable
Validates that all Istio proxy containers and sidecar-injector ConfigMap templates have ISTIO_META_NETWORK set, and that the value matches the topology.istio.io/network label on the istio-system namespace. A mismatch or missing value can cause network topology errors in multicluster routing.
Example verbose output:
--- ISTIO_META_NETWORK Check ---
✅ ISTIO_META_NETWORK Check: istiod proxy containers have ISTIO_META_NETWORK set to cluster1
✅ ISTIO_META_NETWORK Check: sidecar-injector ConfigMap template has ISTIO_META_NETWORK set to cluster1
✅ ISTIO_META_NETWORK Check: all ISTIO_META_NETWORK values match network labelIf this check fails, set ISTIO_META_NETWORK on your istiod deployment to match the topology.istio.io/network label on the istio-system namespace, and update the sidecar-injector ConfigMap template accordingly.
Stale workload entries
In flat network setups, checks for any outdated workload entries that must be removed from the multicluster mesh. Stale workload entries might exist from pods that were deleted, but the autogenerated entries for those workloads were not correctly cleaned up. If you do not use a flat network topology, no autogenerated workload entries exist to be validated, and this check can be ignored.
Example verbose output for a non-flat network setup:
--- Stale Workloads Check ---
⚠ Stale Workloads Check: no autogenflat workload entries foundIf you use a flat network topology, and this check fails with stale workload entries, run kubectl get workloadentries -n istio-system | grep autogenflat to list the autogenerated workload entries in the remote cluster, and compare the list to the output of kubectl get pods in the source cluster for those workloads. You can safely manually delete the stale workload entries in the remote cluster for pods that no longer exist in the source cluster, such as by running kubectl get workloadentries -n istio-system <entry_name>.
Missing local Segment resources
Checks for missing local Segment resources, which might cause auto-generated ServiceEntry resources that are received from a peered cluster to be silently dropped. If a remote cluster sends ServiceEntry resources for a Segment that does not exist locally, those entries are dropped without error. This check detects that condition and reports an actionable message for each rejected peer Segment.
Example verbose output:
--- Segment Readiness Check ---
✅ Segment Readiness Check: all peer Segments have matching local Segment resourcesIf this check reports missing Segment resources, create the corresponding Segment resource in the local cluster to match the one defined in the remote cluster.
Rejected peer Segment states
Checks for peer Segment resources that are in a rejected state due to hostname mismatches or overlapping Segment domains across clusters. Rejected segment states prevent the peering controller from federating workload entries for the affected services.
Example verbose output:
--- Peer Segment State Check ---
✅ Peer Segment State Check: no rejected peer Segments detectedIf this check fails, review the Segment resources in each cluster for conflicting spec.domain values or mismatched hostnames, and ensure that Segment domains are unique across the multicluster mesh.
Hostname conflicts
Checks for hostname conflicts in auto-generated ServiceEntry resources by reading conflict annotations added by the peering controller. Hostname conflicts occur when two services in different clusters or namespaces resolve to the same hostname in the mesh, causing one ServiceEntry to override the other.
Example verbose output:
--- Hostname Conflict Check ---
✅ Hostname Conflict Check: no hostname conflicts detected in auto-generated ServiceEntry resourcesIf this check reports conflicts, review the conflicting services and adjust your Segment configuration to ensure each service resolves to a unique hostname across the mesh.
Metrics
You can also check metrics that are built into the Solo distribution of Istio to verify multiple aspects of the multicluster peering status.
Each peering metric has the labels source and peer, which appear as fields in the metric. The source is the local istiod instance in the cluster where the metric is emitted, and peer is the peered remote cluster. The convergence time metrics are important for understanding how quickly configuration propagates to peer clusters. A high convergence time can indicate slow propagation, connectivity issues, or that the peer or network is under load.
Port-forward to the istiod pod in each cluster to access its metrics endpoint.
kubectl port-forward -n istio-system deploy/istiod 15014:15014 --context ${context1}In a separate terminal, query the metrics endpoint and filter for peer metrics.
curl -s http://localhost:15014/metrics | grep '^peer_'Repeat for each cluster in the mesh, updating the
--contextflag.
The following peer metrics are available.
| Metric | Description |
|---|---|
peer_connection_state | The connection state of peered remote clusters (1 = connected, 0 = disconnected). |
peer_convergence_time_bucket | The cumulative count of convergence times, which measures the delay between sending an xDS request to a peer cluster and receiving an ACK or NACK. This metric is captured in seconds for the following intervals (buckets): 0.01, 0.1, 0.5, 1, 3, 5, 10, 20, 30. |
peer_convergence_time_count | The total number of xDS requests to peer clusters for which an ACK or NACK was received since istiod was last started. |
peer_convergence_time_sum | The sum of all convergence times in seconds since istiod was last started. |
peer_xds_config_size_bytes_bucket | The distribution of xDS configuration sizes received from peer clusters. |
peer_xds_config_size_bytes_count | The number of xDS configurations received from peer clusters. |
peer_xds_config_size_bytes_sum | The sum of all xDS configuration sizes received from peer clusters since the last start of the Istio proxy. |
If you use Grafana to monitor Istio performance, you can also check out the Grafana dashboards in the Solo Communities of Practice (COP) repository. For example, you can use the istio-peering-dashboard to monitor and verify peering connection between clusters, and the istio-global-services-dashboard to monitor locality-aware traffic distribution and endpoint health across clusters, networks, zones, and regions.
Further debugging and observability
For additional guidance around observing your multicluster ambient mesh, check out the observability overview, which contains links to guides on using logs, metrics, and traces in your Istio environment.
For additional guidance around debugging your multicluster ambient mesh, check out the Istio troubleshooting guide.