Home / AWS

AWSRunbookAdvanced

Transformation: self-managed Kubernetes on EC2 to Amazon EKS with Karpenter

Move workloads from kops, kubeadm or Rancher clusters on EC2 to Amazon EKS. Build the target cluster with IaC, translate add-ons and IAM, autoscale nodes with Karpenter, migrate namespaces with blue/green and retire the old control plane.

Deepak Bhardwaj3 min read

Applies toAmazon EKS 1.29+, Karpenter v1, EKS Pod Identity, AWS Load Balancer Controller, Velero

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 → gp3 via 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 Retain policy 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