Set up Istio multi-cluster service mesh with cross-cluster communication

Advanced 45 min Apr 08, 2026
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Deploy and configure Istio across multiple Kubernetes clusters with secure cross-cluster communication, shared service discovery, and unified traffic management for distributed microservices architecture.

Prerequisites

  • Two or more Kubernetes clusters with admin access
  • kubectl configured with multiple cluster contexts
  • LoadBalancer support or NodePort access
  • Network connectivity between clusters
  • Basic understanding of Kubernetes networking and service mesh concepts

What this solves

This tutorial shows you how to set up Istio service mesh across multiple Kubernetes clusters with cross-cluster communication. You need this when your microservices are distributed across different clusters for high availability, geographic distribution, or organizational boundaries while maintaining unified service discovery and security policies.

Step-by-step configuration

Install required tools and dependencies

Install kubectl, istioctl, and Helm on your management machine to interact with multiple clusters.

curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
curl -sL https://istio.io/downloadIstio | sh -
sudo mv istio-*/bin/istioctl /usr/local/bin/
curl https://baltocdn.com/helm/signing.asc | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list
sudo apt update && sudo apt install -y helm
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
curl -sL https://istio.io/downloadIstio | sh -
sudo mv istio-*/bin/istioctl /usr/local/bin/
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
chmod 700 get_helm.sh
./get_helm.sh

Set up cluster contexts and network configuration

Configure kubectl contexts for both clusters and set unique cluster names and network identifiers.

export CTX_CLUSTER1=cluster1
export CTX_CLUSTER2=cluster2

Verify cluster access

kubectl --context="${CTX_CLUSTER1}" get nodes kubectl --context="${CTX_CLUSTER2}" get nodes

Label clusters with unique network identifiers

kubectl --context="${CTX_CLUSTER1}" label namespace istio-system topology.istio.io/network=network1 kubectl --context="${CTX_CLUSTER2}" label namespace istio-system topology.istio.io/network=network2

Create Istio namespace and certificates

Create the istio-system namespace and generate shared root certificates for secure cross-cluster communication.

kubectl --context="${CTX_CLUSTER1}" create namespace istio-system
kubectl --context="${CTX_CLUSTER2}" create namespace istio-system

Generate root CA certificates

mkdir -p certs cd certs

Create root CA

openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 \ -subj '/O=example Inc./CN=example.com' \ -keyout root-key.pem -out root-cert.pem

Create intermediate CA for cluster1

openssl req -newkey rsa:2048 -nodes -keyout cluster1-key.pem \ -subj '/O=example Inc./CN=cluster1.example.com' \ -out cluster1.csr openssl x509 -req -days 365 -CA root-cert.pem -CAkey root-key.pem \ -set_serial 0 -in cluster1.csr -out cluster1-cert.pem

Create intermediate CA for cluster2

openssl req -newkey rsa:2048 -nodes -keyout cluster2-key.pem \ -subj '/O=example Inc./CN=cluster2.example.com' \ -out cluster2.csr openssl x509 -req -days 365 -CA root-cert.pem -CAkey root-key.pem \ -set_serial 1 -in cluster2.csr -out cluster2-cert.pem

Install certificates in both clusters

Deploy the CA certificates to each cluster for mutual TLS authentication between clusters.

# Install certificates in cluster1
kubectl --context="${CTX_CLUSTER1}" create secret generic cacerts -n istio-system \
  --from-file=root-cert.pem \
  --from-file=cert-chain.pem=cluster1-cert.pem \
  --from-file=ca-cert.pem=cluster1-cert.pem \
  --from-file=ca-key.pem=cluster1-key.pem

Install certificates in cluster2

kubectl --context="${CTX_CLUSTER2}" create secret generic cacerts -n istio-system \ --from-file=root-cert.pem \ --from-file=cert-chain.pem=cluster2-cert.pem \ --from-file=ca-cert.pem=cluster2-cert.pem \ --from-file=ca-key.pem=cluster2-key.pem cd ..

Configure Istio installation values

Create configuration files for both clusters with specific values for multi-cluster setup.

values:
  pilot:
    env:
      EXTERNAL_ISTIOD: false
  global:
    meshID: mesh1
    clusterName: cluster1
    network: network1
    remotePilotAddress: ""
values:
  pilot:
    env:
      EXTERNAL_ISTIOD: false
  global:
    meshID: mesh1
    clusterName: cluster2
    network: network2
    remotePilotAddress: ""

Install Istio control plane on primary cluster

Install Istio on the first cluster as the primary control plane with cross-cluster discovery enabled.

istioctl install --context="${CTX_CLUSTER1}" --values=cluster1-values.yaml -f - <

Create eastwest gateway for cross-cluster communication

Deploy the eastwest gateway to handle inter-cluster traffic and service discovery.

apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
  name: eastwest
spec:
  revision: ""
  components:
    ingressGateways:
      - name: istio-eastwestgateway
        label:
          istio: eastwestgateway
          app: istio-eastwestgateway
        enabled: true
        k8s:
          service:
            type: LoadBalancer
            ports:
              - port: 15021
                targetPort: 15021
                name: status-port
              - port: 15010
                targetPort: 15010
                name: tls
              - port: 15011
                targetPort: 15011
                name: tls
              - port: 15012
                targetPort: 15012
                name: tls
istioctl --context="${CTX_CLUSTER1}" install -f eastwest-gateway.yaml

Expose Istio control plane through eastwest gateway

Create Gateway and VirtualService resources to expose the control plane for cross-cluster access.

apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: istiod-gateway
  namespace: istio-system
spec:
  selector:
    istio: eastwestgateway
  servers:
    - port:
        number: 15010
        name: tls
        protocol: TLS
      tls:
        mode: PASSTHROUGH
      hosts:
        - "*"
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: istiod-vs
  namespace: istio-system
spec:
  hosts:
  - "*"
  gateways:
  - istiod-gateway
  tls:
  - match:
    - port: 15010
      sniHosts:
      - "*"
    route:
    - destination:
        host: istiod.istio-system.svc.cluster.local
        port:
          number: 15010
kubectl --context="${CTX_CLUSTER1}" apply -f expose-istiod.yaml

Get eastwest gateway external IP

Retrieve the external IP address of the eastwest gateway for the secondary cluster configuration.

export DISCOVERY_ADDRESS=$(kubectl --context="${CTX_CLUSTER1}" get svc istio-eastwestgateway \
    -n istio-system -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo "Discovery address: $DISCOVERY_ADDRESS"

If using a cloud provider that returns hostname instead of IP

if [ -z "$DISCOVERY_ADDRESS" ]; then export DISCOVERY_ADDRESS=$(kubectl --context="${CTX_CLUSTER1}" get svc istio-eastwestgateway \ -n istio-system -o jsonpath='{.status.loadBalancer.ingress[0].hostname}') fi

Install Istio on secondary cluster

Install Istio on the second cluster configured to connect to the primary cluster's control plane.

istioctl install --context="${CTX_CLUSTER2}" -f - <

Create cluster secrets for cross-cluster discovery

Create secrets containing kubeconfig information for cross-cluster service discovery.

# Create secret for cluster2 in cluster1
kubectl --context="${CTX_CLUSTER1}" create secret generic cluster2-secret \
  --from-file=cluster2=/path/to/cluster2/kubeconfig -n istio-system
kubectl --context="${CTX_CLUSTER1}" label secret cluster2-secret istio/cluster=cluster2 -n istio-system
kubectl --context="${CTX_CLUSTER1}" annotate secret cluster2-secret networking.istio.io/cluster=cluster2 -n istio-system

Create secret for cluster1 in cluster2

kubectl --context="${CTX_CLUSTER2}" create secret generic cluster1-secret \ --from-file=cluster1=/path/to/cluster1/kubeconfig -n istio-system kubectl --context="${CTX_CLUSTER2}" label secret cluster1-secret istio/cluster=cluster1 -n istio-system kubectl --context="${CTX_CLUSTER2}" annotate secret cluster1-secret networking.istio.io/cluster=cluster1 -n istio-system

Enable sidecar injection

Label namespaces in both clusters to enable automatic Istio sidecar injection.

kubectl --context="${CTX_CLUSTER1}" create namespace sample
kubectl --context="${CTX_CLUSTER1}" label namespace sample istio-injection=enabled
kubectl --context="${CTX_CLUSTER2}" create namespace sample
kubectl --context="${CTX_CLUSTER2}" label namespace sample istio-injection=enabled

Deploy test applications

Deploy sample applications in both clusters to test cross-cluster communication.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: httpbin
  namespace: sample
spec:
  replicas: 1
  selector:
    matchLabels:
      app: httpbin
      version: v1
  template:
    metadata:
      labels:
        app: httpbin
        version: v1
    spec:
      containers:
      - image: docker.io/kennethreitz/httpbin
        imagePullPolicy: IfNotPresent
        name: httpbin
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: httpbin
  namespace: sample
  labels:
    app: httpbin
spec:
  ports:
  - name: http
    port: 8000
    targetPort: 80
  selector:
    app: httpbin
kubectl --context="${CTX_CLUSTER1}" apply -f app-cluster1.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sleep
  namespace: sample
spec:
  replicas: 1
  selector:
    matchLabels:
      app: sleep
  template:
    metadata:
      labels:
        app: sleep
    spec:
      containers:
      - name: sleep
        image: curlimages/curl
        command: ["/bin/sleep", "infinity"]
        imagePullPolicy: IfNotPresent
kubectl --context="${CTX_CLUSTER2}" apply -f app-cluster2.yaml

Configure cross-cluster service discovery

Create ServiceEntry resources to enable service discovery across clusters.

apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
  name: httpbin-cross-cluster
  namespace: sample
spec:
  hosts:
  - httpbin.sample.global
  location: MESH_EXTERNAL
  ports:
  - number: 8000
    name: http
    protocol: HTTP
  resolution: DNS
  addresses:
  - 240.0.0.1
  endpoints:
  - address: httpbin.sample.svc.cluster.local
    network: network1
kubectl --context="${CTX_CLUSTER2}" apply -f cross-cluster-service.yaml

Create traffic management policies

Configure DestinationRule and VirtualService for cross-cluster traffic management.

apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: httpbin-destination
  namespace: sample
spec:
  host: httpbin.sample.global
  trafficPolicy:
    tls:
      mode: ISTIO_MUTUAL
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: httpbin-virtual-service
  namespace: sample
spec:
  hosts:
  - httpbin.sample.global
  http:
  - match:
    - headers:
        end-user:
          exact: jason
    route:
    - destination:
        host: httpbin.sample.global
        subset: v1
  - route:
    - destination:
        host: httpbin.sample.global
kubectl --context="${CTX_CLUSTER2}" apply -f traffic-policy.yaml

Verify your setup

# Check Istio installation status in both clusters
istioctl --context="${CTX_CLUSTER1}" proxy-status
istioctl --context="${CTX_CLUSTER2}" proxy-status

Verify cross-cluster connectivity

kubectl --context="${CTX_CLUSTER2}" exec -n sample -c sleep \ $(kubectl --context="${CTX_CLUSTER2}" get pod -n sample -l app=sleep -o jsonpath='{.items[0].metadata.name}') \ -- curl -v httpbin.sample.global:8000/headers

Check service endpoints

istioctl --context="${CTX_CLUSTER1}" proxy-config endpoints \ $(kubectl --context="${CTX_CLUSTER1}" get pod -n istio-system -l app=istiod -o jsonpath='{.items[0].metadata.name}') \ -n istio-system

Verify mTLS configuration

istioctl --context="${CTX_CLUSTER1}" authn tls-check httpbin.sample.svc.cluster.local istioctl --context="${CTX_CLUSTER2}" authn tls-check sleep.sample.svc.cluster.local

Common issues

SymptomCauseFix
Cross-cluster services not discoverableMissing cluster secrets or incorrect network labelsVerify cluster secrets with kubectl get secrets -n istio-system and check network labels
mTLS connection failuresCertificate mismatch or missing CA certsRecreate CA certificates and ensure they match across clusters
Eastwest gateway not accessibleLoadBalancer service not getting external IPCheck cloud provider configuration or use NodePort service type
Pilot not starting on secondary clusterIncorrect remotePilotAddress configurationVerify DISCOVERY_ADDRESS variable and eastwest gateway service
Services showing as unhealthyNetwork connectivity issues between clustersTest network connectivity between cluster nodes and check firewall rules

Next steps

Automated install script

Run this to automate the entire setup

#istio #kubernetes #service-mesh #multi-cluster #cross-cluster-communication

Need help?

Don't want to manage this yourself?

We handle infrastructure for businesses that depend on uptime. From initial setup to ongoing operations.

Talk to an engineer