Migration Guide: GCP ↔ AWS
This guide provides step-by-step instructions for migrating Kubernetes deployments between GCP (GKE) and AWS (EKS).
Overview
Migration between GCP and AWS involves:
- Infrastructure Migration - VPC, clusters, networking
- Application Migration - Kubernetes workloads
- Configuration Updates - Authentication, networking, services
- Validation - Testing and verification
Migration Scenarios
Scenario 1: GCP → AWS Migration
Use Cases:
- Customer requires AWS
- Cost optimization (long-term)
- Compliance requirements
- Existing AWS infrastructure
Scenario 2: AWS → GCP Migration
Use Cases:
- Cost reduction (free control plane)
- Simpler architecture needs
- Google Workspace integration
- Compliance requirements
Pre-Migration Checklist
1. Inventory Current Resources
GCP Inventory:
# List GKE clusters
gcloud container clusters list
# List VPCs
gcloud compute networks list
# List firewall rules
gcloud compute firewall-rules list
# List Cloud SQL instances
gcloud sql instances listAWS Inventory:
# List EKS clusters
aws eks list-clusters --region REGION
# List VPCs
aws ec2 describe-vpcs --region REGION
# List security groups
aws ec2 describe-security-groups --region REGION
# List RDS instances
aws rds describe-db-instances --region REGION2. Document Current Configuration
Create a migration document with:
- Cluster configuration (node count, instance types, regions)
- Network topology (VPC CIDR, subnets, routes)
- Security rules (firewall rules / security groups)
- Authentication setup (Workload Identity / IRSA)
- Database connections
- Application dependencies
3. Plan Migration Strategy
Options:
- Big Bang - Migrate everything at once
- Phased - Migrate component by component
- Parallel - Run both during transition
- Blue-Green - New environment, cutover
GCP → AWS Migration
Phase 1: Infrastructure Setup
Step 1: Create AWS VPC
# Map GCP VPC to AWS VPC
module "vpc" {
source = "../../modules/aws/vpc"
# Map GCP CIDR to AWS
vpc_cidr = "10.0.0.0/16" # Match or adjust GCP CIDR
# Map GCP subnets to AWS subnets
public_subnet_cidr = "10.0.1.0/24" # GCP public subnet
private_subnet_cidr = "10.0.2.0/24" # GCP private subnet
availability_zones = ["us-west-2a", "us-west-2b"]
}Key Differences:
- GCP subnets are regional → AWS subnets are zonal
- Create one subnet per availability zone in AWS
Step 2: Create EKS Cluster
# Map GKE cluster to EKS cluster
module "eks_cluster" {
source = "../../modules/aws/eks-cluster"
cluster_name = var.cluster_name
region = "us-west-2" # Map GCP region to AWS region
subnet_ids = module.vpc.private_subnet_ids
# Map GKE node configuration
node_count = 2 # Match GKE node count
instance_type = "t3.medium" # Map GCP machine type
min_node_count = 1
max_node_count = 5
}Key Differences:
- GKE uses
machine_type→ EKS usesinstance_type - GKE node pools → EKS node groups
- EKS requires control plane ($0.10/hour)
Step 3: Map Security Rules
GCP Firewall Rules → AWS Security Groups:
# GCP Firewall Rule
resource "google_compute_firewall" "allow_ingress" {
name = "allow-ingress"
network = var.network_name
allow {
protocol = "tcp"
ports = ["80", "443"]
}
source_ranges = ["0.0.0.0/0"]
target_tags = ["allow-ingress"]
}
# AWS Security Group (equivalent)
resource "aws_security_group" "allow_ingress" {
name = "allow-ingress"
vpc_id = var.vpc_id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}Key Differences:
- GCP: Network-level, tag-based targeting
- AWS: Instance-level, security group attachment
Phase 2: Authentication Migration
GCP Workload Identity → AWS IRSA
GCP Configuration:
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-sa
namespace: default
annotations:
iam.gke.io/gcp-service-account: "app-sa@PROJECT.iam.gserviceaccount.com"AWS Configuration:
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-sa
namespace: default
annotations:
eks.amazonaws.com/role-arn: "arn:aws:iam::ACCOUNT:role/app-role"Migration Steps:
- Create IAM role in AWS
- Update service account annotations
- Update application code (if using provider SDKs)
- Test authentication
Phase 3: Database Migration
Cloud SQL → RDS
GCP Configuration:
# Cloud SQL Proxy Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: cloud-sql-proxy
spec:
template:
spec:
containers:
- name: cloud-sql-proxy
image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:latest
args:
- "PROJECT:REGION:INSTANCE"AWS Configuration:
# Application connects directly to RDS Proxy
apiVersion: v1
kind: ConfigMap
metadata:
name: db-config
data:
DB_HOST: "rds-proxy-endpoint.proxy-abc123.us-west-2.rds.amazonaws.com"
DB_PORT: "5432"Migration Steps:
- Create RDS instance (or use existing)
- Create RDS Proxy (optional, for connection pooling)
- Update connection strings
- Update authentication (IAM or credentials)
- Test connectivity
Phase 4: Application Migration
Kubernetes Workloads
Most Kubernetes workloads are cloud-agnostic and can be migrated directly:
# Export from GCP
kubectl get all --all-namespaces -o yaml > workloads.yaml
# Review and update:
# - Service account annotations (Workload Identity → IRSA)
# - ConfigMaps (database endpoints, etc.)
# - Secrets (if needed)
# - Ingress annotations (GCP-specific → AWS-specific)
# Apply to AWS
kubectl apply -f workloads.yamlKey Updates Needed:
Service Account Annotations:
yaml# Remove GCP annotation # iam.gke.io/gcp-service-account: "..." # Add AWS annotation eks.amazonaws.com/role-arn: "arn:aws:iam::ACCOUNT:role/ROLE"Database Endpoints:
yaml# Update ConfigMaps with new database endpoints DB_HOST: "new-rds-endpoint"Ingress Annotations:
yaml# GCP kubernetes.io/ingress.class: "gce" # AWS (NGINX Ingress) kubernetes.io/ingress.class: "nginx"
AWS → GCP Migration
Phase 1: Infrastructure Setup
Step 1: Create GCP VPC
# Map AWS VPC to GCP VPC
module "vpc" {
source = "../../modules/gcp/vpc-standard"
network_name = "${var.cluster_name}-vpc"
region = "us-central1" # Map AWS region to GCP region
# Map AWS subnets (combine zonal subnets into regional)
public_subnet_cidr = "10.0.1.0/24" # Combine AWS public subnets
private_subnet_cidr = "10.0.2.0/24" # Combine AWS private subnets
}Key Differences:
- AWS subnets are zonal → GCP subnets are regional
- Combine multiple AWS subnets into one GCP subnet
Step 2: Create GKE Cluster
# Map EKS cluster to GKE cluster
module "gke_cluster" {
source = "../../modules/gcp/gke-cluster"
project_id = var.project_id
cluster_name = var.cluster_name
region = "us-central1"
network = module.vpc.network_name
subnetwork = module.vpc.private_subnet_name
# Map EKS node configuration
node_count = 2 # Match EKS node count
machine_type = "e2-medium" # Map AWS instance type
min_node_count = 1
max_node_count = 5
}Key Differences:
- EKS uses
instance_type→ GKE usesmachine_type - EKS node groups → GKE node pools
- GKE control plane is free
Step 3: Map Security Rules
AWS Security Groups → GCP Firewall Rules:
# AWS Security Group
resource "aws_security_group" "allow_ingress" {
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
# GCP Firewall Rule (equivalent)
resource "google_compute_firewall" "allow_ingress" {
name = "allow-ingress"
network = var.network_name
allow {
protocol = "tcp"
ports = ["80"]
}
source_ranges = ["0.0.0.0/0"]
target_tags = ["allow-ingress"]
}Key Differences:
- AWS: Instance-level, security group attachment
- GCP: Network-level, tag-based targeting
Phase 2: Authentication Migration
AWS IRSA → GCP Workload Identity
AWS Configuration:
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-sa
namespace: default
annotations:
eks.amazonaws.com/role-arn: "arn:aws:iam::ACCOUNT:role/app-role"GCP Configuration:
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-sa
namespace: default
annotations:
iam.gke.io/gcp-service-account: "app-sa@PROJECT.iam.gserviceaccount.com"Migration Steps:
- Create GCP service account
- Enable Workload Identity on GKE cluster
- Bind GKE service account to GCP service account
- Update service account annotations
- Update application code (if using provider SDKs)
- Test authentication
Phase 3: Database Migration
RDS → Cloud SQL
AWS Configuration:
# Application connects to RDS Proxy
apiVersion: v1
kind: ConfigMap
metadata:
name: db-config
data:
DB_HOST: "rds-proxy-endpoint.proxy-abc123.us-west-2.rds.amazonaws.com"GCP Configuration:
# Cloud SQL Proxy Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: cloud-sql-proxy
spec:
template:
spec:
containers:
- name: cloud-sql-proxy
image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:latest
args:
- "PROJECT:REGION:INSTANCE"Migration Steps:
- Create Cloud SQL instance (or use existing)
- Deploy Cloud SQL Proxy as Kubernetes pod
- Update connection strings to use proxy service
- Update authentication (Workload Identity)
- Test connectivity
Common Migration Patterns
Pattern 1: Network CIDR Mapping
Challenge: GCP and AWS may use different CIDR ranges
Solution:
# Option 1: Use same CIDR (if no conflicts)
vpc_cidr = "10.0.0.0/16" # Same for both
# Option 2: Use different CIDR and update routes
# GCP: 10.0.0.0/16
# AWS: 10.1.0.0/16
# Update application configs to use new IPsPattern 2: Subnet Consolidation
Challenge: AWS has zonal subnets, GCP has regional subnets
Solution:
# AWS: Multiple zonal subnets
# us-west-2a: 10.0.1.0/24
# us-west-2b: 10.0.2.0/24
# GCP: One regional subnet covering both
# us-central1: 10.0.1.0/24 (spans all zones)Pattern 3: Security Rule Translation
Challenge: Different security models (firewall rules vs security groups)
Solution:
- Document all current rules
- Map to equivalent rules in target provider
- Test rule application
- Verify connectivity
Pattern 4: Authentication Translation
Challenge: Different authentication mechanisms
Solution:
- Create equivalent IAM/service account
- Map permissions
- Update service account annotations
- Test authentication
- Update application code if needed
Migration Validation
Post-Migration Checklist
Infrastructure:
- [ ] VPC created and configured
- [ ] Cluster deployed and healthy
- [ ] Nodes running and ready
- [ ] Network connectivity verified
Security:
- [ ] Firewall rules/security groups applied
- [ ] Network policies working
- [ ] Authentication configured
- [ ] Secrets migrated
Applications:
- [ ] All pods running
- [ ] Services accessible
- [ ] Ingress working
- [ ] Database connectivity verified
Functionality:
- [ ] End-to-end tests passing
- [ ] Performance acceptable
- [ ] Monitoring working
- [ ] Logging configured
Testing Strategy
# 1. Infrastructure Tests
kubectl get nodes
kubectl get pods --all-namespaces
# 2. Connectivity Tests
kubectl run test --image=curlimages/curl --rm -it --restart=Never -- \
curl -v https://www.google.com
# 3. Database Tests
kubectl run db-test --image=postgres:14-alpine --rm -it --restart=Never -- \
psql -h $DB_HOST -U $DB_USER -d $DB_NAME -c "SELECT version();"
# 4. Application Tests
# Run your application's test suite
# Verify all functionality worksRollback Plan
If Migration Fails
Keep Source Environment Running:
- Don't destroy source until migration validated
- Maintain parallel environments during transition
Document Issues:
- Record all problems encountered
- Note configuration differences
- Update migration plan
Rollback Steps:
- Revert to source environment
- Fix issues in migration plan
- Retry migration
Cost Considerations
Migration Costs
During Migration:
- Run both environments in parallel
- Double costs during transition
- Plan for 1-2 weeks overlap
After Migration:
- Destroy source environment
- Monitor new environment costs
- Optimize as needed
Cost Optimization
GCP:
- Use preemptible nodes for non-production
- Right-size node pools
- Use committed use discounts
AWS:
- Use Spot instances for non-production
- Right-size node groups
- Use Reserved Instances for production