Active Nerds
Module #15 · expert Article

Multi-Cluster Strategy for a Geographically Distributed Company: Architecture & Implementation

In-depth architectural breakdown and operational implementation for Multi-Cluster Strategy for a Geographically Distributed Company: Architecture & Implement...

52 min read+25 XPPublished 2026-06-10

Prerequisite knowledge: Kubernetes networking, DNS, GitOps, load balancing, service mesh


Question

Your company operates in US-East, EU-West, and APAC. Compliance requires EU data to stay in EU (GDPR), US financial data to stay in US (SOX), and APAC to serve as a DR site for both regions. SLA is 99.95% (< 4.4 hours/year downtime). Design a complete multi-cluster strategy.


Quick Answer (30 sec revision)

Deploy three regional clusters with Flux or ArgoCD for GitOps-based config synchronization, use Istio multi-cluster with flat network or gateway model for cross-cluster service discovery, implement Global Server Load Balancing (GSLB) with GeoDNS for traffic routing, enforce data residency with namespace-level policies and separate databases per region, and use Velero for cross-region backup with APAC as the DR target.


Detailed Answer

️ Architecture Overview

                        ┌─────────────────┐
                        │   Global DNS    │
                        │  (Route53/CF)   │
                        │  GeoDNS routing │
                        └────────┬────────┘
                                 │
              ┌──────────────────┼──────────────────┐
              │                  │                  │
    ┌─────────▼──────┐  ┌────────▼───────┐  ┌──────▼──────────┐
    │   US-East      │  │   EU-West      │  │   APAC          │
    │   Cluster      │  │   Cluster      │  │   Cluster       │
    │                │  │                │  │  (DR + Serve)   │
    │ • US workloads │  │ • EU workloads │  │ • APAC workloads│
    │ • US databases │  │ • EU databases │  │ • Cross-region  │
    │ • SOX compliant│  │ • GDPR compliant│ │   backup target │
    └────────┬───────┘  └────────┬───────┘  └──────┬──────────┘
             │                   │                  │
             └───────────────────┴──────────────────┘
                         Istio Multi-cluster
                      (cross-cluster service mesh)

️ Implementation

Component 1: GitOps Foundation with ArgoCD

# argocd-applicationset.yaml
# Deploy the same base config to all clusters with region-specific overlays
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: platform-services
  namespace: argocd
spec:
  generators:
    - list:
        elements:
          - cluster: us-east
            url: https://k8s-us-east.mycompany.com
            region: us-east-1
            environment: production
            dataResidency: us
          - cluster: eu-west
            url: https://k8s-eu-west.mycompany.com
            region: eu-west-1
            environment: production
            dataResidency: eu
          - cluster: apac
            url: https://k8s-apac.mycompany.com
            region: ap-southeast-1
            environment: production
            dataResidency: apac

  template:
    metadata:
      name: "platform-{{cluster}}"
    spec:
      project: platform
      source:
        repoURL: https://github.com/mycompany/k8s-config
        targetRevision: main
        path: "clusters/{{cluster}}"  # Region-specific Kustomize overlay
      destination:
        server: "{{url}}"
        namespace: platform
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        syncOptions:
          - CreateNamespace=true
# Repository structure for GitOps multi-cluster
k8s-config/
├── base/                          # Shared base resources
│   ├── deployments/
│   ├── services/
│   └── rbac/
├── clusters/
│   ├── us-east/                   # US-specific overlay
│   │   ├── kustomization.yaml
│   │   ├── patches/
│   │   │   ├── replicas.yaml      # Higher replica count for US
│   │   │   └── resources.yaml
│   │   └── region-config.yaml     # US-specific ConfigMap
│   ├── eu-west/                   # EU-specific overlay
│   │   ├── kustomization.yaml
│   │   ├── patches/
│   │   │   └── gdpr-annotations.yaml
│   │   └── data-residency-policy.yaml
│   └── apac/                      # APAC overlay
│       ├── kustomization.yaml
│       └── dr-config.yaml
└── policies/
    ├── us-data-residency.yaml     # OPA policies per region
    └── eu-gdpr-policy.yaml

Component 2: Data Residency Enforcement

# EU GDPR: Enforce data stays in EU namespace
# OPA Gatekeeper policy — blocks cross-region database connections
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8sdataresidency
spec:
  crd:
    spec:
      names:
        kind: K8sDataResidency
      validation:
        openAPIV3Schema:
          type: object
          properties:
            allowedRegions:
              type: array
              items:
                type: string
            dataClassification:
              type: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8sdataresidency

        violation[{"msg": msg}] {
          # Check if pod has data-region annotation
          pod := input.review.object
          data_region := pod.metadata.annotations["mycompany.com/data-region"]
          cluster_region := input.parameters.clusterRegion

          # Reject if data region doesn't match cluster region
          data_region != cluster_region

          msg := sprintf(
            "Pod '%v' has data-region '%v' but cluster is in region '%v'. Data residency violation.",
            [pod.metadata.name, data_region, cluster_region]
          )
        }

        violation[{"msg": msg}] {
          # Require data-region annotation on all pods in regulated namespaces
          pod := input.review.object
          regulated_namespaces := {"eu-production", "us-production"}
          regulated_namespaces[pod.metadata.namespace]
          not pod.metadata.annotations["mycompany.com/data-region"]
          msg := sprintf(
            "Pod '%v' in regulated namespace '%v' must have 'mycompany.com/data-region' annotation",
            [pod.metadata.name, pod.metadata.namespace]
          )
        }
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sDataResidency
metadata:
  name: eu-data-residency
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
    namespaces:
      - eu-production
  parameters:
    clusterRegion: "eu-west-1"
    dataClassification: "gdpr-regulated"

Component 3: Istio Multi-Cluster Service Mesh

# Set up Istio multi-cluster with primary-remote model
# US-East is primary, EU-West and APAC are remotes

# Step 1: Create a shared root CA for all clusters
# (Required for cross-cluster mTLS)
mkdir -p certs && cd certs

# Generate root CA
openssl req -new -newkey rsa:4096 -days 3650 -nodes -x509 \
  -subj "/O=MyCompany/CN=Root CA" \
  -keyout root-ca-key.pem \
  -out root-ca-cert.pem

# Generate intermediate CA for each cluster
for cluster in us-east eu-west apac; do
  # Generate intermediate CA key and CSR
  openssl req -new -newkey rsa:4096 -nodes \
    -subj "/O=MyCompany/CN=Intermediate CA $cluster" \
    -keyout $cluster-ca-key.pem \
    -out $cluster-ca-csr.pem

  # Sign with root CA
  openssl x509 -req -days 730 \
    -CA root-ca-cert.pem \
    -CAkey root-ca-key.pem \
    -CAcreateserial \
    -in $cluster-ca-csr.pem \
    -out $cluster-ca-cert.pem

  # Create cert chain
  cat $cluster-ca-cert.pem root-ca-cert.pem > $cluster-ca-chain.pem
done

# Step 2: Install Istio certs in each cluster
for cluster in us-east eu-west apac; do
  kubectl create secret generic cacerts \
    -n istio-system \
    --context=k8s-$cluster \
    --from-file=cacert.pem=$cluster-ca-cert.pem \
    --from-file=cakey.pem=$cluster-ca-key.pem \
    --from-file=root-cert.pem=root-ca-cert.pem \
    --from-file=cert-chain.pem=$cluster-ca-chain.pem
done
# Install Istio on US-East (primary cluster)
# istio-us-east-values.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
  name: istio-primary
spec:
  profile: default
  values:
    global:
      meshID: global-mesh
      multiCluster:
        clusterName: us-east
      network: us-east-network
  components:
    ingressGateways:
      - name: istio-eastwestgateway
        label:
          istio: eastwestgateway
          app: istio-eastwestgateway
          topology.istio.io/network: us-east-network
        enabled: true
        k8s:
          env:
            - name: ISTIO_META_ROUTER_MODE
              value: "sni-dnat"
          service:
            ports:
              - port: 15021
                targetPort: 15021
                name: status-port
              - port: 15443
                targetPort: 15443
                name: tls
              - port: 15012
                targetPort: 15012
                name: tls-istiod
              - port: 15017
                targetPort: 15017
                name: tls-webhook
# Expose services for cross-cluster discovery
# Apply on each cluster to expose its services to the mesh

kubectl apply -n istio-system \
  --context=k8s-us-east \
  -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: cross-network-gateway
spec:
  selector:
    istio: eastwestgateway
  servers:
    - port:
        number: 15443
        name: tls
        protocol: TLS
      tls:
        mode: AUTO_PASSTHROUGH
      hosts:
        - "*.local"
EOF

# Enable endpoint discovery between clusters
istioctl x create-remote-secret \
  --context=k8s-eu-west \
  --name=eu-west | \
  kubectl apply -f - --context=k8s-us-east

istioctl x create-remote-secret \
  --context=k8s-apac \
  --name=apac | \
  kubectl apply -f - --context=k8s-us-east

Component 4: Global Load Balancing with GeoDNS

# Route53 GeoDNS configuration (via Terraform)
# terraform/dns/main.tf

resource "aws_route53_health_check" "us_east" {
  fqdn              = "api-us-east.mycompany.com"
  port              = 443
  type              = "HTTPS"
  resource_path     = "/health"
  failure_threshold = "3"
  request_interval  = "10"

  tags = {
    Name = "us-east-health-check"
  }
}

resource "aws_route53_health_check" "eu_west" {
  fqdn              = "api-eu-west.mycompany.com"
  port              = 443
  type              = "HTTPS"
  resource_path     = "/health"
  failure_threshold = "3"
  request_interval  = "10"
}

# GeoDNS routing policy
resource "aws_route53_record" "api_us" {
  zone_id = aws_route53_zone.main.zone_id
  name    = "api.mycompany.com"
  type    = "A"

  geolocation_routing_policy {
    continent = "NA"   # North America → US-East
  }

  set_identifier = "us-east"
  health_check_id = aws_route53_health_check.us_east.id
  ttl     = 60
  records = [var.us_east_ingress_ip]
}

resource "aws_route53_record" "api_eu" {
  zone_id = aws_route53_zone.main.zone_id
  name    = "api.mycompany.com"
  type    = "A"

  geolocation_routing_policy {
    continent = "EU"   # Europe → EU-West
  }

  set_identifier  = "eu-west"
  health_check_id = aws_route53_health_check.eu_west.id
  ttl     = 60
  records = [var.eu_west_ingress_ip]
}

resource "aws_route53_record" "api_apac" {
  zone_id = aws_route53_zone.main.zone_id
  name    = "api.mycompany.com"
  type    = "A"

  geolocation_routing_policy {
    continent = "AS"   # Asia → APAC
  }

  set_identifier = "apac"
  ttl     = 60
  records = [var.apac_ingress_ip]
}

# Fallback: Route all other traffic to US-East
resource "aws_route53_record" "api_default" {
  zone_id = aws_route53_zone.main.zone_id
  name    = "api.mycompany.com"
  type    = "A"

  geolocation_routing_policy {
    country = "*"   # Default catch-all
  }

  set_identifier  = "default"
  health_check_id = aws_route53_health_check.us_east.id
  ttl     = 60
  records = [var.us_east_ingress_ip]
}

Component 5: Cross-Cluster Disaster Recovery with Velero

# Install Velero on all clusters with S3 cross-region replication
helm repo add vmware-tanzu https://vmware-tanzu.github.io/helm-charts
helm install velero vmware-tanzu/velero \
  --namespace velero \
  --create-namespace \
  --set configuration.provider=aws \
  --set configuration.backupStorageLocation.bucket=mycompany-k8s-backups \
  --set configuration.backupStorageLocation.config.region=us-east-1 \
  --set configuration.volumeSnapshotLocation.config.region=us-east-1 \
  --set initContainers[0].name=velero-plugin-for-aws \
  --set initContainers[0].image=velero/velero-plugin-for-aws:v1.8.0 \
  --set initContainers[0].volumeMounts[0].mountPath=/target \
  --set initContainers[0].volumeMounts[0].name=plugins
# Scheduled backups — EU cluster backs up to S3 with cross-region replication to APAC
apiVersion: velero.io/v1
kind: Schedule
metadata:
  name: eu-production-backup
  namespace: velero
spec:
  schedule: "0 */6 * * *"    # Every 6 hours
  template:
    includedNamespaces:
      - eu-production
    excludedResources:
      - events
      - events.events.k8s.io
    storageLocation: eu-s3-backup
    volumeSnapshotLocations:
      - eu-ebs-snapshots
    ttl: 720h0m0s             # 30 days retention
    labelSelector:
      matchLabels:
        backup: "true"        # Only backup labeled resources
---
# Disaster Recovery: Restore EU workloads to APAC cluster
# Run this when EU-West cluster fails
apiVersion: velero.io/v1
kind: Restore
metadata:
  name: eu-to-apac-restore
  namespace: velero
spec:
  backupName: eu-production-backup-20260709-060000
  includedNamespaces:
    - eu-production
  namespaceMapping:
    eu-production: eu-production-dr   # Restore into DR namespace
  restorePVs: true
  existingResourcePolicy: update

Component 6: Cross-Cluster Service Discovery

# Submariner: Cross-cluster L3 connectivity without service mesh
# Alternative to Istio multi-cluster for simpler use cases

# Install Submariner broker on US-East (hub cluster)
subctl deploy-broker \
  --kubeconfig ~/.kube/us-east.yaml \
  --globalnet                    # Enable GlobalNet for overlapping CIDRs

# Join EU-West to the broker
subctl join broker-info.subm \
  --kubeconfig ~/.kube/eu-west.yaml \
  --clusterid eu-west \
  --natt=false

# Join APAC to the broker
subctl join broker-info.subm \
  --kubeconfig ~/.kube/apac.yaml \
  --clusterid apac \
  --natt=false

# Export a service for cross-cluster discovery
# Once exported, service is accessible as:
# <svc-name>.<namespace>.svc.clusterset.local
kubectl apply -f - <<EOF
apiVersion: multicluster.x-k8s.io/v1alpha1
kind: ServiceExport
metadata:
  name: payment-service
  namespace: production
  # Applied on US-East cluster
  # Payment service now accessible from EU-West and APAC as:
  # payment-service.production.svc.clusterset.local
EOF

Component 7: Multi-Cluster Observability

# Thanos for multi-cluster Prometheus federation
# Each cluster runs Prometheus + Thanos Sidecar
# Central Thanos Query aggregates metrics from all clusters

# thanos-values.yaml (deploy on each cluster)
prometheus:
  prometheusSpec:
    externalLabels:
      cluster: us-east          # Region-specific label on all metrics
      region: us-east-1
      environment: production
    thanos:
      objectStorageConfig:
        key: thanos.yaml
        name: thanos-objstore-secret

# Thanos Query (deploy on central monitoring cluster)
thanos-query:
  stores:
    - dnssrv+_grpc._tcp.thanos-store-us-east.monitoring.svc.cluster.local
    - dnssrv+_grpc._tcp.thanos-store-eu-west.monitoring.svc.cluster.local
    - dnssrv+_grpc._tcp.thanos-store-apac.monitoring.svc.cluster.local
  replicaLabels:
    - cluster
    - replica

️ Trade-offs: Multi-Cluster Approaches

| Approach | Complexity | Isolation | Latency | Cost | Best For | |---|---|---|---|---|---| | Istio Multi-cluster | Very High | Strong | Low (direct) | Medium | mTLS, L7 policy | | Submariner | Medium | Medium | Low | Low | Simple L3 connectivity | | ArgoCD ApplicationSet | Low | N/A (GitOps only) | N/A | Low | Config sync only | | KubeFed v2 | High | Medium | Medium | Medium | Resource federation | | Liqo | Medium | Medium | Low | Low | Workload offloading |


️ Common Mistakes & Misconceptions

  • "I'll use one big cluster for everything." — Blast radius, compliance boundaries, and network latency all argue for regional clusters. A single cluster failure would violate your 99.95% SLA for all regions simultaneously.
  • "GeoDNS alone provides failover." — DNS TTLs mean failover takes minutes. Combine GeoDNS with health checks and low TTLs (60s), and pre-warm APAC so it can serve traffic immediately.
  • "Velero backups are enough for DR." — Velero restores take 15–45 minutes for large clusters. For 99.95% SLA, you need hot standby (APAC always running with synced data), not cold restore.
  • "Same CIDR ranges across clusters are fine." — Overlapping pod CIDRs break cross-cluster networking unless you use Submariner's GlobalNet or an overlay solution. Plan non-overlapping CIDRs from day one.

Key Takeaway

Multi-cluster architecture is fundamentally about trading operational complexity for isolation, compliance, and resilience. The key insight is that each layer — GitOps for config, service mesh for connectivity, GeoDNS for traffic, Velero for DR — solves a distinct problem. Start with GitOps synchronization (lowest complexity, highest value), add cross-cluster networking only when services genuinely need to communicate across regions, and implement data residency controls before going live in regulated regions, not after.


Self-Assessment Checklist

  • [ ] Can you explain why overlapping pod CIDRs are a problem in multi-cluster setups?
  • [ ] Can you describe the difference between Istio multi-cluster primary-remote and multi-primary models?
  • [ ] Can you explain what ServiceExport does in the Kubernetes multi-cluster API?
  • [ ] Can you describe how Thanos enables global metrics aggregation?
  • [ ] Can you explain why GeoDNS TTL matters for failover speed?
  • [ ] Can you articulate the difference between hot standby and cold restore DR strategies?

Finished reading?

Mark as complete to claim your +25 XP and track progress.