Moving a cluster from Google Kubernetes Engine to Amazon EKS is the cleanest migration in the GCP→AWS playbook: your manifests, Helm charts, and container images are portable. The work lives at the cluster boundary — six things GKE did for you that EKS does differently: identity (Workload Identity → IRSA / Pod Identity), ingress (GCLB → AWS Load Balancer Controller), storage (PD → EBS/EFS CSI), registry (GCR/Artifact Registry → ECR), autoscaling (→ Karpenter), and pod networking (→ VPC CNI). This page maps each, then walks the image/secret move and a zero-downtime cutover. On a qualifying migration, AWS MAP funds a vetted partner to run it — often at little-to-no cost.
A Kubernetes-to-Kubernetes move is the friendliest migration in the GCP→AWS catalog because the contract you build against — the Kubernetes API — is identical on both. It pays to be precise about exactly what transfers so you don't over-scope the project.
Your workload manifests are portable. Deployments, StatefulSets, DaemonSets, Services, ConfigMaps, Secrets (the objects), Jobs, CronJobs, HorizontalPodAutoscalers, PodDisruptionBudgets, NetworkPolicies, and RBAC all apply to an EKS cluster exactly as they applied to GKE — the Kubernetes object schema does not change between clouds, so a Deployment that ran on GKE 1.30 runs on EKS 1.30. Your Helm charts and Kustomize overlays are portable too: the packaging layer sits above the cluster, so what you change inside a chart is the small set of values that reference cloud-specific things — the StorageClass name, the ingress class and annotations, the image registry prefix, and the service-account-to-identity binding. Replica counts, resource requests/limits, probes, affinity rules, and template structure are untouched.
Your container images are portable in content but not in location. The image runs identically on EKS nodes; what changes is where it lives — images move from Google Container Registry (gcr.io) or Artifact Registry (*-docker.pkg.dev) to Amazon ECR, and every manifest reference repoints to the ECR URL. That's a registry migration plus a find-and-replace, not a rebuild. So the honest scope is keep the app, re-author the cluster boundary: the friction is entirely in the cloud-provider integration layer — the controllers, CSI drivers, and IAM glue that bind the cluster to GCP vs. AWS services. The sections that follow take the six boundary concepts in turn — identity, ingress, storage, registry, autoscaling, networking — explaining how GKE did each and exactly what you change. Scope it as "six integrations to swap," not "a Kubernetes rewrite," and the estimate stays honest.
The single most important re-author — get it wrong and pods end up with no AWS permissions or over-broad ones. GKE's Workload Identity lets a Kubernetes service account act as a Google one; EKS does the equivalent with IAM, and there are now two ways to do it.
On GKE, Workload Identity binds a Kubernetes service account (KSA) to a Google service account (GSA), so a pod using that KSA gets the GSA's GCP permissions with no key files. EKS has a direct analog — two of them. The established option is IAM Roles for Service Accounts (IRSA): you associate an OIDC provider with the cluster, create an IAM role with a trust policy scoped to a specific service account (by namespace + name), and annotate that KSA with the role ARN (eks.amazonaws.com/role-arn). Pods then assume the role via the OIDC token and get short-lived STS credentials — exactly the keyless model Workload Identity gave you. The mapping is 1:1: GSA → IAM role; the WI binding → the IRSA trust policy; the KSA annotation → the role-ARN annotation.
The newer, simpler option — GA since late 2023 and the one most fresh clusters should prefer in 2026 — is EKS Pod Identity. Instead of per-cluster OIDC plumbing you install the Pod Identity Agent add-on and create a Pod Identity association mapping (cluster, namespace, service account) → IAM role, which makes roles reusable across clusters and simplifies the trust policy to a single principal. Prefer it unless a dependency needs IRSA; you can run both during the transition. Either way the work item is the same — for every KSA that had a Workload Identity binding, create one IAM role with a least-privilege policy and bind it. This is genuine work, not a find-and-replace, and where rushed migrations leave the biggest security gaps: re-derive permissions from what each workload actually calls rather than porting blindly, and delete any leftover JSON key files. Both IRSA and Pod Identity issue short-lived STS credentials, so the keyless posture carries over. Inventory the bindings first.
On GKE an Ingress object provisions a Google Cloud Load Balancer via the built-in controller. EKS has no built-in cloud Ingress controller — you install the AWS Load Balancer Controller, which turns Ingress objects into ALBs and Service type LoadBalancer into NLBs.
The conceptual mapping is clean — Ingress still means "expose HTTP(S) through a managed L7 load balancer" — but the annotations are entirely different, and that's the part you rewrite. On GKE you used kubernetes.io/ingress.class: "gce", BackendConfig/FrontendConfig CRDs (health checks, CDN, IAP), and GCP-managed certs via ManagedCertificate. None of those carry over. On EKS you install the AWS Load Balancer Controller (a Helm chart that needs its own IAM role via IRSA/Pod Identity), and your Ingress uses ingressClassName: alb with annotations under alb.ingress.kubernetes.io/ — scheme (internet-facing vs internal), target-type (ip for VPC-CNI pod IPs is the common choice), certificate-arn for TLS, healthcheck-*, and group.name to share one ALB across Ingresses. For L4 traffic, a Service of type: LoadBalancer with the aws-load-balancer-type: "external" annotation provisions an NLB.
TLS and DNS change with it. GKE's ManagedCertificate becomes a certificate in AWS Certificate Manager (ACM) referenced by ARN; the DNS record that pointed at the GCLB now points at the ALB's DNS name in Route 53 (an alias record). Add ExternalDNS so Ingress hostnames auto-create Route 53 records — it makes the cutover a controlled DNS flip. Where GKE used Identity-Aware Proxy (IAP), the EKS equivalent is an OIDC authentication action on the ALB listener (Cognito or your IdP) — different mechanism, same intent. Validate the health-check path end to end before shifting any real traffic to the new endpoints.
Stateless workloads have no storage migration. Stateful ones do, and it's the part of a GKE→EKS move that needs the most care because block volumes are zonal and do not travel across clouds — you migrate the data, not the disk.
On GKE a PersistentVolumeClaim provisions a Compute Engine Persistent Disk via the PD CSI driver; on EKS the equivalent is the Amazon EBS CSI driver (a managed add-on) provisioning gp3 volumes by default. The PVC/PV/StorageClass objects are the same kind — what changes is the StorageClass provisioner (pd.csi.storage.gke.io → ebs.csi.aws.com) and its parameters. But the hard truth about block storage is that a Persistent Disk and an EBS volume are both zonal, cloud-specific resources — you cannot attach a GCP PD to an AWS node. So for any StatefulSet the migration is a data move, not a volume re-attach: migrate a database at the data layer (DMS or native replication/snapshot-restore) rather than copying the raw disk, and copy generic file data into a fresh EBS-backed PVC. Plan a sync-and-cutover per stateful workload, just like any database migration.
For shared, ReadWriteMany volumes — where GKE used Filestore — the EKS equivalent is Amazon EFS via the EFS CSI driver, with the data move a file copy (DataSync or rsync into the mount). Mind the access-mode difference up front: EBS is ReadWriteOnce (one node at a time), so a workload that genuinely needs RWX must land on EFS — discovering that at cutover is a classic late-stage surprise. Finish with the easy-to-forget housekeeping: set a default StorageClass (so PVCs without one don't fail), choose reclaimPolicy: Retain for anything you can't lose, and use volumeBindingMode: WaitForFirstConsumer so volumes provision in the same AZ as their pod — the AWS analog of GKE's zonal-disk scheduling.
Stateless pods migrate by re-applying manifests; stateful pods migrate by moving data. A GCP Persistent Disk never becomes an AWS EBS volume — provision a fresh EBS (or EFS for RWX) PVC and sync the contents, cutting over per workload. Databases go at the data layer (DMS / native replication), not by copying raw block storage.
The last three boundary concepts: where images live, how nodes scale, and how pods get IPs. The registry move touches every manifest, the autoscaling choice shapes node economics for the life of the cluster, and the networking model is the one that surprises GKE-native teams at scale.
Every image reference points at gcr.io/... or *-docker.pkg.dev/... today; on EKS those repoint to Amazon ECR. Two steps: copy the images with a tag-and-digest-preserving tool (crane copy, skopeo copy) rather than a manual pull/tag/push loop, and repoint references across manifests, Helm values, and CI. Nodes pull from same-account ECR via their node IAM role (AmazonEC2ContainerRegistryReadOnly), so there's no imagePullSecret to manage. Add an ECR lifecycle policy to expire old images, and if you're moving to Graviton (arm64) nodes, build multi-arch images so one tag serves amd64 and arm64.
The pod-level HorizontalPodAutoscaler is identical on EKS and carries over unchanged. At the node level, the recommended choice in 2026 is Karpenter rather than the classic Cluster Autoscaler: it watches for unschedulable pods and provisions right-sized nodes directly from EC2 — fitting instance types to pending pods, consolidating onto fewer nodes as demand drops, and making Spot capacity easy for fault-tolerant workloads. Instead of GKE's rigid node pools you declare NodePools and EC2NodeClasses. Run managed node groups for baseline workloads and let Karpenter handle the elastic tier — this is usually where a chunk of the post-migration cost savings comes from (bin-packing + Spot + Graviton).
EKS ships with the Amazon VPC CNI, which gives each pod a real IP from your VPC subnet — pods are first-class on the VPC, directly addressable and governed by security groups (this is what enables native ALB target-type: ip). The catch: pods consume VPC IP space, and the number-one networking gotcha is IP exhaustion — undersized subnets run out of addresses before compute. Size subnet CIDRs generously across AZs and use prefix delegation to pack far more pod IPs per node. NetworkPolicy objects are portable (the VPC CNI enforces them natively; Calico/Cilium remain valid, and Cilium is also the route to a GKE-like overlay). The VPC design underneath changes too: GKE's global VPC becomes an AWS regional VPC with per-AZ subnets, NAT Gateways, and Transit Gateway/PrivateLink for cross-account links.
With the boundary concepts mapped, execution is mechanical and scriptable. Because Kubernetes workloads are mostly stateless and the data lives in databases and object storage, the cutover itself is primarily a traffic-shift problem.
Stand up EKS, migrate images, transform manifests, re-create secrets. Provision the cluster and install the integration layer up front — VPC CNI, CoreDNS, kube-proxy, EBS/EFS CSI, the AWS Load Balancer Controller, Karpenter, ExternalDNS, and the Pod Identity Agent — then set a default StorageClass and IngressClass. Copy images GCR/Artifact Registry → ECR with crane/skopeo (digest-preserving; multi-arch for Graviton). Transform manifests via the boundary swaps — ECR prefix, StorageClass provisioner, ALB Ingress annotations, identity annotations, dropping GKE-only CRDs — mostly a few Helm values and a base overlay. Re-create secrets in AWS Secrets Manager/SSM surfaced via the Secrets Store CSI Driver or External Secrets Operator, granting access through the pod's IAM role; never paste plaintext into manifests. Apply to a non-prod namespace first.
Run both clusters in parallel, shift traffic gradually, hold rollback open. Bring EKS to full readiness while GKE still serves 100%, keeping stateful backends synced (DMS CDC for databases, DataSync for object/file data). Then move traffic with weighted DNS in Route 53 — records for both the GCLB endpoint and the EKS ALB, weight shifted 5% → 25% → 50% → 100% with error rates and p99 latency watched at each stage. The principle is ramp, don't flip: because the switch is DNS weight, rollback is a weight change back to GKE, not a re-migration. Keep GKE warm and replication reversible through a soak; only after EKS holds 100% clean do you stop replication, drain GKE, and decommission — the last step, never a same-day one.
Most of these are not Kubernetes problems — they're cloud-integration assumptions baked into a GKE-native team's muscle memory, the kind teams doing their first move tend to hit at least once. Knowing them in advance turns a week of debugging into a line on the checklist.
Everything above is the honest engineering, and it's genuinely doable. The reason teams hand it off is the same reason they hand off a SOC 2 audit: it's a time-boxed, specialist project that pulls senior platform engineers off the roadmap for weeks. CloudRoute's job is to make it someone else's project — and to make AWS pay for most of it.
The mechanism is the AWS Migration Acceleration Program (MAP). On a qualifying migration — one with a meaningful AWS-spend commitment after you land — AWS funds the Assess phase (readiness + TCO, often free), credits a share of Mobilize (the EKS landing zone + add-ons + pilot), and credits a large share of Migrate & Modernize (the full workload move, the IAM re-author, and modernization like Karpenter + Graviton). The partner is paid through MAP, so you move from GKE to EKS at little-to-no cost. CloudRoute routes you to a vetted AWS partner matched to a Kubernetes-heavy stack who runs the whole execution — cluster + controllers, ECR migration, manifest transformation, Workload Identity → IRSA/Pod Identity, Karpenter + ALB controller, the stateful data moves, and the weighted cutover — with your team reviewing rather than executing. We don't run the migration and we don't charge you; the partner is paid via MAP, and CloudRoute is paid by the partner.
The honest framing: MAP funding applies to qualifying migrations only — typically those with a real post-migration AWS spend commitment. If yours doesn't qualify, CloudRoute is still useful as a vetted-partner referral that de-risks the cutover — a team that has done dozens of GKE→EKS moves instead of doing your first one in production. The funding ties into the broader AWS credits picture; see the credits cluster for how the pools fit together.
You keep the architecture decisions, the cutover go/no-go, and the final AWS account; the partner runs the execution and, on qualifying migrations, is funded by AWS MAP. See $100K AWS credits and AWS POC funding explained for the funding mechanics, AWS landing zone for the account foundation, and GCP to AWS for the rest of the estate beyond the cluster.
The reference table for the whole migration: for each GKE concept, the EKS equivalent and the one thing that actually changes. The Kubernetes API objects at the top are portable; everything below is the cloud-integration layer you re-author.
| GKE concept | EKS equivalent | What changes on the move | Effort |
|---|---|---|---|
| Deployments / StatefulSets / Services | Same (upstream Kubernetes) | Nothing — manifests are portable | None |
| Helm charts / Kustomize | Same | Edit a few values: registry, StorageClass, ingress class, identity | Low |
| HorizontalPodAutoscaler / NetworkPolicy | Same (HPA / NetworkPolicy) | Nothing — both are portable Kubernetes objects | None |
| Workload Identity (KSA→GSA) | IRSA or EKS Pod Identity | GSA → IAM role; binding → trust policy / Pod Identity association | Med |
| GKE Ingress + ManagedCertificate | AWS LB Controller (ALB) + ACM | Install controller; rewrite annotations; BackendConfig → ALB; cert by ARN | Med |
| Service type LoadBalancer | NLB via AWS LB Controller | aws-load-balancer-type annotation; scheme internal/internet-facing | Low |
| Persistent Disk CSI (block) | EBS CSI driver (gp3) | StorageClass provisioner swap; data move (PD can't attach to EKS) | Med–High |
| Filestore (shared RWX) | Amazon EFS via EFS CSI | File copy into EFS; RWX must use EFS, not EBS | Med |
| GCR / Artifact Registry | Amazon ECR | Copy images (crane/skopeo); repoint registry prefix; node-role pull | Low–Med |
| Cluster Autoscaler / node auto-prov. | Karpenter | Node pools → NodePool/EC2NodeClass; enables Spot + right-sizing | Med |
| Pod networking (GKE dataplane) | Amazon VPC CNI | Pods get real VPC IPs; size subnets; prefix delegation for density | Med |
| Secret Manager + Cloud DNS | Secrets Manager / SSM + Route 53 | Re-create secrets via CSI/ESO + pod role; DNS to ALB (ExternalDNS) | Med |
Situation: Their largest enterprise accounts were AWS-standardized and the next deal required AWS data residency. The 3-person platform team had no spare capacity, had never operated EKS, and had a hard "no customer-visible downtime" constraint — worried most about re-authoring Workload Identity, the Postgres data move, and the Ingress/TLS rewrite.
What CloudRoute did: Routed within 24 hours to a US-East partner with a Kubernetes-migration track record. The partner ran the MAP Assess (AWS-funded, $0) and the concept map, then Mobilize stood up an EKS landing zone with the full add-on stack (VPC CNI with prefix delegation, EBS + EFS CSI, AWS Load Balancer Controller, Karpenter, ExternalDNS, Pod Identity Agent) and piloted one stateless service. Migrate phase: images copied Artifact Registry → ECR (rebuilt multi-arch for Graviton); manifests transformed via the existing Helm charts; every Workload Identity binding re-authored into a Pod Identity association; GKE Ingress rewritten to ALB annotations with ACM certs and ExternalDNS-managed Route 53 records; Filestore copied to EFS; the Postgres StatefulSet moved via DMS CDC kept in lockstep through the parallel run; Karpenter + Spot for the stateless tier.
Outcome: Cutover by weighted Route 53 DNS — 5% → 25% → 50% → 100% over four days, p99 and error rates watched at each step, rollback a weight change throughout. GKE held warm through a 10-day soak, then was decommissioned. AWS MAP funded the assessment and credited a large share of the migration plus the Karpenter/Graviton modernization — the partner was paid through MAP, so the customer's out-of-pocket cost was effectively internal review time, and the Karpenter + Spot + Graviton combination then cut steady-state node spend roughly a third. CloudRoute's commission was paid by the partner; the customer paid CloudRoute $0.
cutover: weighted DNS over 4 days · soak: 10 days · downtime: none · node-spend cut: ~⅓ (Karpenter+Spot+Graviton) · cost to customer: review time only
CloudRoute routes you to an AWS partner who stands up the EKS cluster and add-ons, migrates images to ECR, re-authors Workload Identity into IRSA/Pod Identity, sets up Karpenter and the AWS Load Balancer Controller, and runs the weighted traffic-shift cutover. On qualifying migrations, AWS MAP funds the work — you pay little-to-no cost. No procurement, no first-migration-live risk.