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
Symptom Cause Fix
Cross-cluster services not discoverable Missing cluster secrets or incorrect network labels Verify cluster secrets with kubectl get secrets -n istio-system and check network labels
mTLS connection failures Certificate mismatch or missing CA certs Recreate CA certificates and ensure they match across clusters
Eastwest gateway not accessible LoadBalancer service not getting external IP Check cloud provider configuration or use NodePort service type
Pilot not starting on secondary cluster Incorrect remotePilotAddress configuration Verify DISCOVERY_ADDRESS variable and eastwest gateway service
Services showing as unhealthy Network connectivity issues between clusters Test network connectivity between cluster nodes and check firewall rules
Next steps
- Implement Istio security policies with authorization and authentication
- Configure Istio traffic management with virtual services and destination rules
- Monitor Istio service mesh with Prometheus and Grafana dashboards
- Configure Istio multi-cluster disaster recovery and failover
- Implement Istio multi-cluster canary deployments and traffic splitting
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Default values
CTX_CLUSTER1="${1:-cluster1}"
CTX_CLUSTER2="${2:-cluster2}"
DOMAIN="${3:-example.com}"
ORG="${4:-example Inc.}"
usage() {
echo "Usage: $0 [cluster1_context] [cluster2_context] [domain] [organization]"
echo "Example: $0 prod-east prod-west mycompany.com 'MyCompany Inc.'"
exit 1
}
log() {
echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1"
}
warn() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
error() {
echo -e "${RED}[ERROR]${NC} $1" >&2
exit 1
}
cleanup() {
if [[ -d "certs" ]]; then
rm -rf certs
fi
if [[ -f "get_helm.sh" ]]; then
rm -f get_helm.sh
fi
}
trap cleanup ERR
check_prerequisites() {
log "[1/8] Checking prerequisites..."
if [[ $EUID -eq 0 ]]; then
error "This script should not be run as root. Use sudo for individual commands."
fi
if ! command -v sudo &> /dev/null; then
error "sudo is required but not installed"
fi
if ! sudo -n true 2>/dev/null; then
error "This script requires passwordless sudo access"
fi
}
detect_distro() {
log "[2/8] Detecting distribution..."
if [[ ! -f /etc/os-release ]]; then
error "Cannot detect distribution - /etc/os-release not found"
fi
source /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
PKG_UPDATE="apt update"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf update -y"
if ! command -v dnf &> /dev/null; then
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum update -y"
fi
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum update -y"
;;
*)
error "Unsupported distribution: $ID"
;;
esac
log "Detected: $PRETTY_NAME using $PKG_MGR"
}
install_dependencies() {
log "[3/8] Installing system dependencies..."
sudo $PKG_UPDATE
case "$PKG_MGR" in
apt)
sudo $PKG_INSTALL curl wget gnupg2 software-properties-common apt-transport-https ca-certificates openssl
;;
dnf|yum)
sudo $PKG_INSTALL curl wget gnupg2 openssl which
;;
esac
}
install_kubectl() {
log "[4/8] Installing kubectl..."
if command -v kubectl &> /dev/null; then
warn "kubectl already installed, skipping..."
return
fi
KUBECTL_VERSION=$(curl -L -s https://dl.k8s.io/release/stable.txt)
curl -LO "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl"
chmod 755 kubectl
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
rm kubectl
kubectl version --client &> /dev/null || error "kubectl installation failed"
}
install_istioctl() {
log "[5/8] Installing istioctl..."
if command -v istioctl &> /dev/null; then
warn "istioctl already installed, skipping..."
return
fi
curl -sL https://istio.io/downloadIstio | sh -
ISTIO_DIR=$(find . -name "istio-*" -type d | head -1)
if [[ -z "$ISTIO_DIR" ]]; then
error "Failed to find Istio directory"
fi
sudo install -o root -g root -m 0755 "${ISTIO_DIR}/bin/istioctl" /usr/local/bin/istioctl
rm -rf istio-*
istioctl version --remote=false &> /dev/null || error "istioctl installation failed"
}
install_helm() {
log "[6/8] Installing Helm..."
if command -v helm &> /dev/null; then
warn "Helm already installed, skipping..."
return
fi
case "$PKG_MGR" in
apt)
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 $PKG_INSTALL helm
;;
dnf|yum)
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
rm -f get_helm.sh
;;
esac
helm version &> /dev/null || error "Helm installation failed"
}
setup_cluster_contexts() {
log "[7/8] Setting up cluster contexts..."
export CTX_CLUSTER1
export CTX_CLUSTER2
log "Verifying cluster access for $CTX_CLUSTER1..."
if ! kubectl --context="${CTX_CLUSTER1}" get nodes &> /dev/null; then
error "Cannot access cluster context: $CTX_CLUSTER1"
fi
log "Verifying cluster access for $CTX_CLUSTER2..."
if ! kubectl --context="${CTX_CLUSTER2}" get nodes &> /dev/null; then
error "Cannot access cluster context: $CTX_CLUSTER2"
fi
log "Creating istio-system namespaces..."
kubectl --context="${CTX_CLUSTER1}" create namespace istio-system --dry-run=client -o yaml | kubectl --context="${CTX_CLUSTER1}" apply -f -
kubectl --context="${CTX_CLUSTER2}" create namespace istio-system --dry-run=client -o yaml | kubectl --context="${CTX_CLUSTER2}" apply -f -
log "Labeling clusters with network identifiers..."
kubectl --context="${CTX_CLUSTER1}" label namespace istio-system topology.istio.io/network=network1 --overwrite
kubectl --context="${CTX_CLUSTER2}" label namespace istio-system topology.istio.io/network=network2 --overwrite
}
generate_certificates() {
log "[8/8] Generating certificates..."
mkdir -p certs
cd certs
log "Creating root CA certificate..."
openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 \
-subj "/O=${ORG}/CN=${DOMAIN}" \
-keyout root-key.pem -out root-cert.pem
log "Creating intermediate CA for cluster1..."
openssl req -newkey rsa:2048 -nodes -keyout cluster1-key.pem \
-subj "/O=${ORG}/CN=cluster1.${DOMAIN}" \
-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
log "Creating intermediate CA for cluster2..."
openssl req -newkey rsa:2048 -nodes -keyout cluster2-key.pem \
-subj "/O=${ORG}/CN=cluster2.${DOMAIN}" \
-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
chmod 600 *.pem *.key
log "Installing 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 \
--dry-run=client -o yaml | kubectl --context="${CTX_CLUSTER1}" apply -f -
log "Installing 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 \
--dry-run=client -o yaml | kubectl --context="${CTX_CLUSTER2}" apply -f -
cd ..
}
verify_installation() {
log "Verifying installation..."
echo -e "${BLUE}Installed tools:${NC}"
kubectl version --client --short 2>/dev/null || echo "kubectl: installed"
istioctl version --remote=false 2>/dev/null | head -1 || echo "istioctl: installed"
helm version --short 2>/dev/null || echo "helm: installed"
echo -e "${BLUE}Cluster contexts:${NC}"
echo "Cluster 1: $CTX_CLUSTER1"
echo "Cluster 2: $CTX_CLUSTER2"
echo -e "${BLUE}Certificates:${NC}"
if [[ -f "certs/root-cert.pem" ]]; then
echo "✓ Root CA certificate generated"
fi
echo -e "${BLUE}Next steps:${NC}"
echo "1. Install Istio control plane: istioctl install --context=\$CTX_CLUSTER1 --set values.pilot.env.EXTERNAL_ISTIOD=true"
echo "2. Install Istio control plane: istioctl install --context=\$CTX_CLUSTER2 --set values.pilot.env.EXTERNAL_ISTIOD=true"
echo "3. Configure cross-network endpoint discovery"
echo "4. Set up cross-cluster service exposure"
}
main() {
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
usage
fi
check_prerequisites
detect_distro
install_dependencies
install_kubectl
install_istioctl
install_helm
setup_cluster_contexts
generate_certificates
verify_installation
log "Istio multi-cluster setup completed successfully!"
}
main "$@"
Review the script before running. Execute with: bash install.sh