Skip to content

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:

  1. Infrastructure Migration - VPC, clusters, networking
  2. Application Migration - Kubernetes workloads
  3. Configuration Updates - Authentication, networking, services
  4. 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:

bash
# 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 list

AWS Inventory:

bash
# 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 REGION

2. 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:

  1. Big Bang - Migrate everything at once
  2. Phased - Migrate component by component
  3. Parallel - Run both during transition
  4. Blue-Green - New environment, cutover

GCP → AWS Migration

Phase 1: Infrastructure Setup

Step 1: Create AWS VPC

hcl
# 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

hcl
# 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 uses instance_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:

hcl
# 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:

yaml
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:

yaml
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:

  1. Create IAM role in AWS
  2. Update service account annotations
  3. Update application code (if using provider SDKs)
  4. Test authentication

Phase 3: Database Migration

Cloud SQL → RDS

GCP Configuration:

yaml
# 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:

yaml
# 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:

  1. Create RDS instance (or use existing)
  2. Create RDS Proxy (optional, for connection pooling)
  3. Update connection strings
  4. Update authentication (IAM or credentials)
  5. Test connectivity

Phase 4: Application Migration

Kubernetes Workloads

Most Kubernetes workloads are cloud-agnostic and can be migrated directly:

bash
# 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.yaml

Key Updates Needed:

  1. 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"
  2. Database Endpoints:

    yaml
    # Update ConfigMaps with new database endpoints
    DB_HOST: "new-rds-endpoint"
  3. 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

hcl
# 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

hcl
# 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 uses machine_type
  • EKS node groups → GKE node pools
  • GKE control plane is free

Step 3: Map Security Rules

AWS Security Groups → GCP Firewall Rules:

hcl
# 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:

yaml
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:

yaml
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:

  1. Create GCP service account
  2. Enable Workload Identity on GKE cluster
  3. Bind GKE service account to GCP service account
  4. Update service account annotations
  5. Update application code (if using provider SDKs)
  6. Test authentication

Phase 3: Database Migration

RDS → Cloud SQL

AWS Configuration:

yaml
# 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:

yaml
# 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:

  1. Create Cloud SQL instance (or use existing)
  2. Deploy Cloud SQL Proxy as Kubernetes pod
  3. Update connection strings to use proxy service
  4. Update authentication (Workload Identity)
  5. Test connectivity

Common Migration Patterns

Pattern 1: Network CIDR Mapping

Challenge: GCP and AWS may use different CIDR ranges

Solution:

hcl
# 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 IPs

Pattern 2: Subnet Consolidation

Challenge: AWS has zonal subnets, GCP has regional subnets

Solution:

hcl
# 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:

  1. Document all current rules
  2. Map to equivalent rules in target provider
  3. Test rule application
  4. Verify connectivity

Pattern 4: Authentication Translation

Challenge: Different authentication mechanisms

Solution:

  1. Create equivalent IAM/service account
  2. Map permissions
  3. Update service account annotations
  4. Test authentication
  5. Update application code if needed

Migration Validation

Post-Migration Checklist

  1. Infrastructure:

    • [ ] VPC created and configured
    • [ ] Cluster deployed and healthy
    • [ ] Nodes running and ready
    • [ ] Network connectivity verified
  2. Security:

    • [ ] Firewall rules/security groups applied
    • [ ] Network policies working
    • [ ] Authentication configured
    • [ ] Secrets migrated
  3. Applications:

    • [ ] All pods running
    • [ ] Services accessible
    • [ ] Ingress working
    • [ ] Database connectivity verified
  4. Functionality:

    • [ ] End-to-end tests passing
    • [ ] Performance acceptable
    • [ ] Monitoring working
    • [ ] Logging configured

Testing Strategy

bash
# 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 works

Rollback Plan

If Migration Fails

  1. Keep Source Environment Running:

    • Don't destroy source until migration validated
    • Maintain parallel environments during transition
  2. Document Issues:

    • Record all problems encountered
    • Note configuration differences
    • Update migration plan
  3. 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

Additional Resources

Released under the MIT License.