Multi-Cloud: Strategy and Architecture to Avoid Vendor Lock-in
Cloud

Multi-Cloud: Strategy and Architecture to Avoid Vendor Lock-in

April 25, 202510 min readMulti-cloudArchitectureCloud

Multi-cloud is attractive on paper but complex in practice. Here is how to design a truly portable architecture without multiplying operational complexity.

Multi-Cloud in 2024: Real Numbers and Ground Truth

According to the Flexera 2024 State of the Cloud Report, 87% of enterprises use multiple clouds. Yet only 23% report having a deliberate multi-cloud strategy. The remaining 64% are what we call "accidental multi-cloud": different clouds in siloes, no real portability, no unified governance — typically the result of acquisitions or autonomous team decisions.

This guide does not advocate for systematic multi-cloud adoption. It gives you a framework to decide when multi-cloud is justified, how to architect it intelligently, and what the real cost of this strategy is.

Understanding True Lock-in: It Is Not the Containers

The common belief is that Kubernetes eliminates lock-in. This is partially false. There are three levels of portability, and enterprises often confuse the first — the easiest — with genuine portability:

Level 1: Container Portability (Theoretical)

A Kubernetes workload can theoretically migrate from EKS to GKE to AKS without modifying application code. In practice, as soon as you use cloud-specific managed services (AWS RDS, Google AlloyDB, Azure Cosmos DB), you create lock-in at the data layer — the hardest to undo.

# Theoretical Kubernetes portability
# Same manifest, three different clouds

# On AWS EKS
kubectl apply -f deployment.yaml  # context: eks-prod-eu-west-1

# On GKE
kubectl apply -f deployment.yaml  # context: gke-prod-europe-west1

# On OVH MKS
kubectl apply -f deployment.yaml  # context: ovh-prod-gra7

# In practice: it works... until your app calls
# aws dynamodb get-item or google.cloud.firestore.Client()

Level 2: Managed Service Abstraction

Real lock-in comes from dependencies on proprietary APIs: DynamoDB, Pub/Sub, Azure Service Bus, Cosmos DB. The solution is to place an abstraction layer between your code and these services:

# Abstraction layer for object storage (Python)
from typing import Protocol

class ObjectStorage(Protocol):
    def upload(self, bucket: str, key: str, data: bytes) -> None: ...
    def download(self, bucket: str, key: str) -> bytes: ...
    def delete(self, bucket: str, key: str) -> None: ...

# AWS S3 implementation
class S3Storage:
    def __init__(self, client):
        self._s3 = client

    def upload(self, bucket: str, key: str, data: bytes) -> None:
        self._s3.put_object(Bucket=bucket, Key=key, Body=data)

    def download(self, bucket: str, key: str) -> bytes:
        return self._s3.get_object(Bucket=bucket, Key=key)["Body"].read()

# OVH / any S3-compatible provider — same boto3 API
class S3CompatibleStorage(S3Storage):
    pass  # OVH Object Storage, MinIO, Cloudflare R2

# Dependency injection — switch cloud without modifying business code
storage: ObjectStorage = S3Storage(boto3.client("s3"))
# or
storage: ObjectStorage = S3CompatibleStorage(boto3.client("s3", endpoint_url="https://s3.gra.io.cloud.ovh.net"))

Level 3: Data Portability — The Real Lock-in

Egress fees are the most underestimated lock-in mechanism. AWS charges $0.09/GB for data leaving to the internet. For a 10 TB dataset, migration means $900 in egress fees alone, before counting migration time and complexity. OVH and Cloudflare do not charge egress — this is a real sovereignty and freedom argument, not just marketing.

Anti-Patterns: What Creates Irreversible Lock-in

  • Tight coupling to proprietary APIs: using DynamoDB's API directly in business code without an abstraction layer — migration to PostgreSQL = complete rewrite of the data layer
  • Lambda functions with AWS-only triggers: an architecture entirely built on Lambda + SQS + DynamoDB + EventBridge is highly efficient but completely non-portable
  • Cloud-specific networking and IAM: AWS Security Groups, VPC peering, and IAM roles do not export — the entire security layer must be rebuilt during migration
  • Proprietary data formats: Parquet on S3 is portable; DynamoDB binary format is not

Abstraction Patterns with Terraform

Terraform with its 200+ providers is the most effective tool for multi-cloud IaC. The reusable module pattern creates abstractions that work across clouds:

# Cloud-agnostic Terraform module for a Kubernetes cluster
# modules/kubernetes-cluster/main.tf

variable "provider" {
  type = string
  # "aws", "ovh", "gcp", "azure"
}

variable "region"     { type = string }
variable "node_count" { type = number; default = 3 }

resource "aws_eks_cluster" "this" {
  count = var.provider == "aws" ? 1 : 0
  name  = var.cluster_name
}

resource "ovh_cloud_project_kube" "this" {
  count  = var.provider == "ovh" ? 1 : 0
  name   = var.cluster_name
  region = var.region
}

# Unified output — kubeconfig regardless of implementation
output "kubeconfig" {
  value = var.provider == "aws" ? (
    aws_eks_cluster.this[0].certificate_authority[0].data
  ) : (
    ovh_cloud_project_kube.this[0].kubeconfig
  )
  sensitive = true
}

Data Sovereignty Multi-Cloud Architecture

A real and legitimate multi-cloud use case: segregating data by regulatory geography. Here is the architecture we deploy for clients with GDPR obligations and US operations:

# Unified control plane, two cloud regions
# EU data → OVH Public Cloud (European sovereignty)
module "eu_cluster" {
  source     = "./modules/kubernetes-cluster"
  provider   = "ovh"
  region     = "GRA7"
  node_count = 3
}

# US data → AWS (ecosystem, performance)
module "us_cluster" {
  source     = "./modules/kubernetes-cluster"
  provider   = "aws"
  region     = "us-east-1"
  node_count = 3
}

# Same application deployed to both clusters via ArgoCD ApplicationSet
module "app_eu" {
  source     = "./modules/helm-release"
  kubeconfig = module.eu_cluster.kubeconfig
  chart      = "./charts/my-app"
  values = { database_region = "eu", data_residency = "EU" }
}

module "app_us" {
  source     = "./modules/helm-release"
  kubeconfig = module.us_cluster.kubeconfig
  chart      = "./charts/my-app"
  values = { database_region = "us", data_residency = "US" }
}

Essential Multi-Cloud Tooling

  • Terraform + Terraform Cloud: multi-provider IaC with centralised state, drift detection, and approval policies
  • Datadog: unified monitoring for AWS, GCP, Azure, OVH — one console for metrics, logs, and traces
  • HashiCorp Vault: cloud-agnostic secret management, compatible with AWS/GCP/Azure/Kubernetes providers
  • ArgoCD + ApplicationSet: GitOps deployment across multiple Kubernetes clusters regardless of the underlying cloud
  • Crossplane: abstract cloud resources behind Kubernetes CRDs for platform teams

The Real Cost of Multi-Cloud: 20–30% Operational Overhead

Let us be direct: multi-cloud has a real cost. Based on our experience and industry analyses, managing a multi-cloud infrastructure represents 20–30% additional operational overhead compared to a well-optimised single-cloud setup. This cost breaks down as follows:

  • Training and maintaining expertise across multiple platforms
  • Identity management complexity (AWS IAM + GCP IAM + Kubernetes RBAC + ...)
  • Additional tooling (unified monitoring, multi-cloud cost management)
  • Network complexity (inter-cloud latency, VPN/peering, transfer fees)
  • Longer security review cycles (two attack surfaces to audit)

Decision Framework: When to Choose Multi-Cloud

Our recommendation is clear: single cloud by default, multi-cloud only when a specific reason justifies it. Legitimate reasons are:

  • Regulatory: EU data on OVH/AWS eu-west, US data on AWS us-east — non-negotiable legal obligations
  • Resilience: for applications where a complete cloud provider failure is unacceptable (rare in practice — providers have >99.99% SLA)
  • Commercial leverage: negotiating pricing by playing competing vendors against each other — effective during Enterprise Agreement renewals
  • M&A: acquiring a company on a different cloud — gradual migration more cost-effective than a big-bang migration

Conclusion

Multi-cloud is not an end goal — it is a strategic tool with a real operational cost. The vast majority of organisations would extract more value from a well-optimised single-cloud setup (Savings Plans, Reserved Instances, right-sized architecture) than from a poorly executed multi-cloud strategy. If multi-cloud is imposed by regulatory or resilience requirements, start with the data layer portability — that is the real lock-in mechanism. Kubernetes + Terraform + Helm will then deliver application-layer portability at a fraction of the additional complexity.

← Back to blog