Why move
A self-managed control plane means you own etcd backups, certificate rotation, upgrades and HA. EKS runs the control plane for you; Karpenter replaces fixed node groups with right-sized, just-in-time nodes.
Strategy: blue/green clusters
In-place conversion isn't possible. Build a new EKS cluster, deploy the same workloads, move traffic, then retire the old cluster. This also gives you a clean rollback.
1. Inventory the old cluster
kubectl get ns
kubectl api-resources --verbs=list --namespaced -o name | xargs -n1 kubectl get -A --ignore-not-found -o name | cut -d/ -f1 | sort | uniq -c
kubectl get crd
helm list -A
Record: ingress controller, storage classes, CNI/network policies, certificate manager, external-dns, secrets handling, monitoring, and any node-level hacks (hostPath, privileged DaemonSets).
2. Build EKS with IaC
Use Terraform (terraform-aws-modules/eks) or eksctl. Decide:
- Access: EKS access entries (API mode).
- Pod IAM: EKS Pod Identity (or IRSA) — replaces node-role-wide permissions or kiam/kube2iam.
- Add-ons: VPC CNI, CoreDNS, kube-proxy, EBS CSI driver as EKS managed add-ons; AWS Load Balancer Controller via Helm.
3. Karpenter
Install Karpenter and define a NodePool and EC2NodeClass:
apiVersion: karpenter.sh/v1
kind: NodePool
metadata: { name: general }
spec:
template:
spec:
requirements:
- { key: karpenter.sh/capacity-type, operator: In, values: ["on-demand", "spot"] }
- { key: kubernetes.io/arch, operator: In, values: ["amd64", "arm64"] }
- { key: karpenter.k8s.aws/instance-category, operator: In, values: ["c", "m", "r"] }
nodeClassRef: { group: karpenter.k8s.aws, kind: EC2NodeClass, name: default }
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
limits: { cpu: "500" }
Keep a small managed node group for Karpenter itself and critical system pods. Allow arm64 only if your images are multi-arch.
4. Move workloads
- Re-deploy from Git (Helm/Kustomize/Argo CD) into EKS rather than copying live objects — it exposes drift.
- Stateful workloads: Velero backup/restore with EBS snapshots, or application-level replication (databases) — then cut over.
- Swap annotations: old ingress classes → ALB ingress via the AWS Load Balancer Controller; storage classes →
gp3via EBS CSI.
velero backup create orders-ns --include-namespaces orders --snapshot-volumes
velero restore create --from-backup orders-ns # run against the EKS cluster context
5. Shift traffic
Weighted DNS records (Route 53) between the old ingress and the new ALB, 10% → 50% → 100%, watching error rate and latency per service.
aws route53 change-resource-record-sets --hosted-zone-id Z123 --change-batch file://weight-50-50.json
6. Retire the old cluster
After the soak period: take a final etcd and Velero backup, scale old nodes to zero, delete the cluster and its IAM, load balancers and volumes.
Warning
Check PersistentVolumes with
Retainpolicy before deleting the old cluster — their EBS volumes keep costing money and may hold the only copy of data.
Checklist
- Old cluster inventory with add-on equivalents on EKS
- EKS built by IaC with access entries and Pod Identity
- Karpenter NodePools with limits and consolidation
- Workloads deployed from Git; stateful data migrated and verified
- Weighted traffic shift complete; old cluster deleted