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.
