Design and operate applications across multiple cloud providers. Covers multi-cloud architecture, abstraction layers, data sovereignty, vendor lock-in mitigation, and the cost-benefit tradeoffs that determine when multi-cloud makes sense.
A multi-cloud strategy is the deliberate use of two or more cloud platforms—such as AWS, Azure, GCP, or a combination thereof—to run applications, manage data, and deliver services. It matters when an organization needs to avoid vendor lock-in, improve resilience, optimize costs across workloads, or leverage platform-specific capabilities where they deliver the most value. This approach is not about simply deploying services on multiple clouds but about orchestrating them as a coherent, integrated system.
A multi-cloud strategy begins with a clear definition of *why* and *what* across the enterprise. The core decision is to align cloud platforms with business, technical, and operational priorities.
Each cloud platform offers a different set of strengths: AWS excels in compute and storage breadth, Azure integrates tightly with Windows and on-premises environments, GCP leads in data analytics and machine learning. A strategy chooses platforms based on workload characteristics.
Platform selection is not static. Workloads evolve, and the strategy must allow for reassignment. A service initially on AWS may migrate to Azure after a performance bottleneck is identified, using a change in the cloud-provider configuration in a Kubernetes ConfigMap.
apiVersion: v1
kind: ConfigMap
metadata:
name: cloud-strategy
namespace: platform
data:
platform-strategy.yaml: |
workloads:
- name: payroll-service
preferred-cloud: aws
constraints:
- type: latency
target: 150ms
- type: cost
threshold: 20000
migration-plan:
- from: aws
to: azure
trigger: "latency > 180ms for 10 consecutive minutes"
- from: azure
to: gcp
trigger: "cost > 30000/month for 3 months"
The strategy assigns ownership of cloud platforms to teams, often by domain or capability. Platform teams manage the underlying cloud infrastructure, while application teams own services and their deployment.
Ownership is formalized through role-based access control (RBAC), with cloud-specific roles defined in a shared identity system (e.g., OpenID Connect).
// roles.json
{
"platform-admin": {
"permissions": [
"cloud:read",
"cloud:write",
"network:manage",
"monitoring:write"
],
"scope": "all-clouds"
},
"app-engineer": {
"permissions": [
"app:deploy",
"app:scale",
"app:config"
],
"scope": "my-app"
}
}
Once defined, the strategy is implemented through a set of consistent patterns and practices.
A single identity provider (IdP) manages access across all clouds. All cloud services authenticate against the IdP using OpenID Connect (OIDC), and service accounts are provisioned via a shared configuration.
# oidc-config.yaml
issuer: https://auth.example.com
client_id: app-platform
jwks_uri: https://auth.example.com/.well-known/jwks.json
authorization_endpoint: https://auth.example.com/authorize
token_endpoint: https://auth.example.com/token
Service accounts are created with metadata that includes cloud-specific attributes:
{
"service_account": "payment-worker",
"cloud": "aws",
"region": "us-east-1",
"role": "data-ingestor",
"permissions": [
"s3:read",
"dynamodb:write",
"lambda:invoke"
],
"tags": {
"owner": "payments-team",
"project": "invoice-processing"
}
}
When a service needs to access a resource in AWS, it acquires a JWT from the IdP and uses it to request a temporary AWS STS token:
curl -X POST \
-H "Authorization: Bearer <JWT>" \
-H "Content-Type: application/json" \
-d '{
"RoleArn": "arn:aws:iam::123456789012:role/payment-worker",
"DurationSeconds": 3600
}' \
https://sts.amazonaws.com/
Failure modes include:
401 Unauthorized errors.InvalidToken errors.aud claim: the aud field in the JWT is not set, leading to InvalidAudience responses.Network configuration is standardized across clouds using a common set of principles: shared VPCs, service mesh, and internal DNS.
Services are registered in a centralized service registry (e.g., using Kubernetes Service objects or a custom services.json file) with cloud-specific endpoints.
// services.json
[
{
"name": "order-processor",
"version": "v2.1.0",
"endpoints": {
"aws": "https://order-processor.us-east-1.elb.amazonaws.com",
"azure": "https://order-processor.centralus.cloudapp.azure.com",
"gcp": "https://order-processor-dot-myapp.uc.r.appspot.com"
},
"health-check": {
"path": "/health",
"interval": "30s",
"timeout": "10s"
}
}
]
A service discovery layer (e.g., Consul, etcd, or a simple JSON file served via HTTP) allows services to locate each other regardless of cloud.
Applications use environment variables or configuration files to resolve endpoints:
# app-config.yaml
services:
order-processor:
discovery: /services.json
protocol: http
endpoint: ${ORDER_PROCESSOR_ENDPOINT}
retry:
max-attempts: 5
backoff: exponential
base-delay: 500ms
Silent failure: a service assumes order-processor is available but fails to resolve the endpoint because the services.json file is stale and not updated after a deployment.
Deployment pipelines are designed to deploy services to multiple clouds with consistent configuration. The deploy command uses a configuration file that defines cloud-specific parameters.
deploy --config ./deploy.yaml --platform aws
deploy --config ./deploy.yaml --platform azure
The deploy.yaml file specifies infrastructure and runtime settings:
# deploy.yaml
app: payment-service
version: 1.2.0
platforms:
aws:
region: us-west-2
vpc: vpc-12345
subnets: [subnet-67890, subnet-abcde]
security-groups: [sg-123, sg-456]
load-balancer-type: application
autoscaling:
min-size: 2
max-size: 10
target: cpu
threshold: 70
azure:
region: westus
vnet: vnet-payment
subnet: subnet-payment
app-service-plan: payment-tier
scaling:
min-instances: 2
max-instances: 15
metric: cpu
threshold: 65
managed-identity: true
environment-variables:
DATABASE_URL: "sqlserver://db.payment-azure.com"
LOG_LEVEL: "info"
Deployment fails silently when the platform key is misspelled (plarform instead of platform) or when a required field is missing (region in AWS but not in Azure).
Data is shared across clouds via regular sync pipelines. A common pattern uses a combination of message queues and object storage.
# sync-data.sh
#!/bin/bash
set -euo pipefail
aws s3 sync ./data/ s3://data-warehouse/prod/ \
--exclude "*.tmp" \
--delete \
--region us-east-1
az storage blob sync --account-name datawarehouse \
--container-name prod \
--source ./data/ \
--region westus
gcloud storage rsync ./data/ gs://data-warehouse-prod/ \
--delete-removed
A service running in Azure consumes data from a GCP bucket using a gcp-connector service, which polls for new files and publishes events via a message queue.
{
"event": "file_uploaded",
"source": "gcp",
"bucket": "data-warehouse-prod",
"file": "sales/2024-03-01.parquet",
"size": 12345678,
"uploaded_at": "2024-03-01T12:00:00Z"
}
Failure modes include:
uploaded_at timestamp is in UTC but the consuming service expects local time, leading to incorrect processing schedules.The strategy is not a one-time setup. It requires ongoing monitoring, governance, and iteration.
A unified observability stack collects logs, metrics, and traces across all clouds. All services emit structured logs in JSON format, with a common schema.
{
"timestamp": "2024-03-01T12:00:00.123Z",
"level": "info",
"service": "order-processor",
"trace_id": "a1b2c3d4e5f6",
"span_id": "f6e5d4c3b2a1",
"message": "Order validated successfully",
"context": {
"order_id": "ORD-2024-001",
"user_id": "USR-12345",
"cloud": "aws",
"region": "us-east-1"
}
}
Logs are ingested into a central system (e.g., Elasticsearch, Loki, or a simple PostgreSQL table) and queried using standard tools.
-- Query: All errors in the order-processor service over the last 24 hours
SELECT * FROM logs
WHERE service = 'order-processor'
AND level = 'error'
AND timestamp >= NOW() - INTERVAL '24 hours'
ORDER BY timestamp DESC;
Metrics are collected via Prometheus, with a common job label and consistent instance and cloud dimensions:
# prometheus.yml
scrape_configs:
- job_name: 'order-processor'
metrics_path: '/metrics'
static_configs:
- targets: ['order-processor.aws:9100', 'order-processor.azure:9100']
labels:
cloud: aws
instance: order-processor-1
- targets: ['order-processor.azure:9100']
labels:
cloud: azure
instance: order-processor-2
Policies are defined as declarative configuration, validated before deployment.
# policy.yaml
name: "cross-cloud-compliance"
description: "Ensure all services meet multi-cloud standards"
rules:
- name: "consistent-logging"
description: "All services must emit JSON logs"
condition: |
"all services emit logs with 'timestamp' and 'level' fields"
action: |
"fail deployment if logs do not include 'timestamp' or 'level'"
- name: "minimum-availability"
description: "All services must be available across at least two clouds"
condition: |
"service has endpoints in at least two clouds, each with 99.9% uptime"
action: |
"send alert if any cloud endpoint drops below 99.9% availability for 10 minutes"
The policy is applied at deployment time using a validate-policy script:
validate-policy --policy policy.yaml --deployment ./deployment/
Common failure modes:
cloud: aws but the deployment includes only aws and azure, causing the rule to fail.description field: a policy rule is defined but lacks a description, leading to confusion during audits.condition syntax: a condition uses >= instead of =>, causing a parsing error.The strategy evolves through feedback loops and incident reviews. The cloud-strategy.yaml file is updated after each major incident, capturing decisions and outcomes.
A service migration from AWS to Azure is documented not just in the configuration but in a change log:
{
"change": "migrate-payment-service-to-azure",
"from": "aws",
"to": "azure",
"date": "2024-03-05",
"reason": "latency exceeded target after 3 months",
"duration": "3 hours",
"success": true,
"issues": [
"initial data sync was 10% incomplete",
"load balancer failed to route traffic during cutover"
],
"post-mortem": "https://wiki.example.com/postmortems/migration-2024-03"
}
The strategy is not static. It is a living document, continuously refined by teams across the organization.
This page was rewritten on 16 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy