Upgrade EKS Cluster to 1.37: 13 Steps, 70 Min [2026]

Amazon EKS has supported Kubernetes 1.37 since October 2, 2026, and AWS opened both new 1.37 clusters and in-place upgrades on that day. If your EKS cluster still runs 1.36 or older, the clock is already ticking. This tutorial walks through the full upgrade in 13 steps and about 70 minutes of hands-on time, from the first version check to the final cost review.

The goal is a boring upgrade. You will run EKS upgrade insights, scan for deprecated APIs, rehearse on a throwaway cluster, move the control plane, then add-ons, then nodes, and finish with a rollback plan you can actually execute. If you want the upstream-only view of the same release, our Kubernetes 1.37 upgrade guide covers the breaking changes outside of AWS. This article stays on the managed-service side.

Every version, date, and price below comes from AWS and Kubernetes project pages fetched on October 25, 2026. Where a figure rests on a single source, the text says so. Command output is illustrative, so expect your cluster names, IDs, and patch builds to differ.

Google · Preferred Sources

Don't miss new tech stories on Google

Add Tech Insider once in the Google app and our stories appear in your news suggestions.

Add Now

What Changed in Amazon EKS With Kubernetes 1.37

The Kubernetes project shipped v1.37, codenamed “Garhwal,” in its upstream release. AWS then needed some time to qualify it for EKS and EKS Distro, and support landed in every Region where EKS runs, including AWS GovCloud (US). That gap is normal. It gives you a window to prepare before the version is even selectable.

Three features headline the release for EKS operators. The Metrics API, metrics.k8s.io/v1, is now generally available. It feeds CPU and memory numbers to the Horizontal Pod Autoscaler and to kubectl top. Device taints and tolerations for Dynamic Resource Allocation (DRA) also reached general availability, which lets you fence off GPUs and similar devices from workloads that should not touch them. And HPA scale-to-zero entered beta, enabled by default for suitable object or external metric setups that set minReplicas: 0.

A fourth item matters if you run EKS Auto Mode. On October 1, 2026, AWS added declarative advanced compute configuration through the advancedCompute field on NodeClass. It covers kubelet settings, Linux kernel sysctls, user namespaces, eviction thresholds, log rotation, network limits, and hugepages. The announcement does not tie the feature to 1.37, so treat it as an Auto Mode capability you can adopt separately.

Why 1.36 Is No Longer the Target

Older tutorials still describe Kubernetes 1.36 as the newest EKS version. That became outdated on October 2. Version 1.36 is now the predecessor, and the right move for most teams on 1.35 or 1.36 is to reach 1.37 one minor version at a time.

The Metrics API Going Stable: What It Means for HPA

The stable metrics.k8s.io/v1 group is the quiet winner of this release. Every Horizontal Pod Autoscaler that scales on CPU or memory reads from it, and so does kubectl top. Graduation does not change what your autoscalers do on day one. It does remove the beta label from a dependency that many production clusters have leaned on for years, which matters for audits and for teams that ban beta APIs in regulated environments. After the upgrade, run kubectl get apiservice and confirm your metrics provider shows as available, because a broken registration here makes every CPU-based HPA stop scaling.

DRA Device Taints for GPU and Accelerator Fleets

Dynamic Resource Allocation lets workloads request devices such as GPUs through claims instead of fixed node resources. Device taints and tolerations, now generally available, add the missing guardrail. An operator can mark a device so that only workloads carrying a matching toleration may use it. For a cluster that mixes inference jobs, training jobs, and ordinary services, that keeps scarce accelerators from being consumed by the wrong pods. If you run no accelerators, you can skip this feature entirely and lose nothing in the upgrade.

HPA Scale-to-Zero in Practice

Scale-to-zero is beta and on by default for eligible setups, meaning an HPA that targets object or external metrics and sets minReplicas: 0. Here is what an intentional configuration looks like. The queue-driven worker drops to zero when the queue is empty and returns when messages arrive.

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: queue-worker
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: queue-worker
  minReplicas: 0
  maxReplicas: 10
  metrics:
    - type: External
      external:
        metric:
          name: queue_messages_ready
        target:
          type: AverageValue
          averageValue: "30"

The risk is accidental zero. A Helm chart default or a copied manifest can carry minReplicas: 0 into a service that must always answer requests. Search for it before the upgrade, as Step 6 shows.

Prerequisites and Versions You Need Before Starting

You need an existing EKS cluster running Kubernetes 1.36, an IAM principal that can update clusters, add-ons, and node groups, and a terminal with the tools below. If you have no cluster yet, our walkthrough on setting up Kubernetes on AWS EKS gets you a test bed. Newcomers to the platform may prefer the Kubernetes cluster tutorial first.

RequirementVersion or settingWhy it matters
Source clusterKubernetes 1.36 on Amazon EKSEKS control planes move one minor version per operation
Target versionKubernetes 1.37 (EKS support since Oct. 2, 2026)Newest version on EKS today
eksctl0.231.0 or newer (reportedly the latest release as of Oct. 2, 2026)AWS names eksctl as a supported upgrade path for 1.37
AWS CLIVersion 2, current releaseNeeded for insights, add-on, and node group commands
kubectl1.36 or 1.37 clientStay within one minor version of the API server
HelmCurrent releaseChart renders must pass against 1.37 APIs
Scannerspluto and kubent, latest buildsFind deprecated API calls before the control plane does
Staging clusterDisposable EKS 1.36 clusterRehearsal target for Step 8

The eksctl row deserves a caveat. The 0.231.0 figure comes from a single GitHub release page, so check the eksctl 0.231.0 release and the releases list for anything newer before you install. An AWS ML-cluster guide also lists eksctl 0.227.0 as a floor for its Auto Mode example, which uses --version=1.37.

How an EKS Version Upgrade Actually Works

An EKS upgrade has three layers, and you move them in a fixed order. The control plane goes first because AWS runs it. Managed add-ons come second (VPC CNI, CoreDNS, kube-proxy, and anything else you installed through the EKS add-on API). Worker nodes go last, since kubelets may trail the API server within the skew the Kubernetes version policy allows, but must never lead it.

AWS lists three supported routes for the control plane: the EKS console, eksctl, and infrastructure-as-code tools. This tutorial uses the AWS CLI and eksctl for clarity and shows the Terraform change in the advanced section.

Plan as if the control-plane step is one-way. EKS does not give you a downgrade button for a cluster, so your safety net is everything you capture and rehearse before Step 9. That framing drives the order of operations in the rest of the article.

LayerWho runs itUpgrade orderReversible?
Control planeAWS1stTreat as no
Managed add-onsYou, via the EKS add-on API2ndYes, pin an earlier compatible version
Managed node groupsYou, rolling replacement3rdYes, until old nodes drain
Karpenter or Auto Mode nodesYou or AWS3rdYes, via node pool rollback
Workloads and chartsYouBefore and afterYes, via GitOps revert

A Realistic 70-Minute Hands-On Timeline

The 70 minutes in the title count your active keyboard time. Waiting for AWS to finish the control-plane update and for nodes to roll comes on top, and your rehearsal tells you how long that takes. Use the table to schedule the work.

StepsTaskHands-on minutes
1 to 3Version check, tooling, snapshot10
4 and 5Insights and API scans10
6Behavior-change tests10
7Add-on compatibility map5
8Rehearsal setup and review10
9Start and confirm control-plane update5
10Add-on updates5
11Node roll5
12Verification5
13IaC update and records5

Steps 1 to 3: Audit Your Version, Tooling, and Backups

Step 1: Confirm the Current Version and Support Window

Start by writing down what you run today. Export two variables so every later command stays copy-paste safe, then read the cluster version, status, and platform details.

export AWS_REGION=us-east-1
export CLUSTER=prod-eks

aws eks describe-cluster --name "$CLUSTER" --region "$AWS_REGION" \
  --query 'cluster.{name:name,version:version,status:status,platform:platformVersion}' \
  --output table

Example output (illustrative):

-------------------------------------------------
|                DescribeCluster                |
+-----------+----------+----------+-------------+
|   name    | platform |  status  |   version   |
+-----------+----------+----------+-------------+
|  prod-eks | eks.N    |  ACTIVE  |  1.36       |
+-----------+----------+----------+-------------+

The status must read ACTIVE. If a previous update is still running, wait for it to finish. AWS gives each Kubernetes version 14 months of standard support after its EKS release, followed by 12 months of extended support, according to the EKS Distro documentation. Extended support costs more per hour, which the cost section below breaks down. Knowing which side of that line you sit on tells you how urgent the upgrade is.

Step 2: Install and Verify the Tooling

Check each tool before you touch the cluster. A stale eksctl or an old kubectl produces confusing errors halfway through the upgrade.

aws --version
eksctl version
kubectl version --client
helm version --short
pluto version
kubent --version

# Point kubectl at the cluster and confirm the API server version
aws eks update-kubeconfig --name "$CLUSTER" --region "$AWS_REGION"
kubectl version
kubectl get nodes -o wide

Compare the server version with your client. The kubectl client should sit within one minor version of the API server. A 1.36 or 1.37 client works for this upgrade. Also confirm that every node reports the same kubelet version, because mixed node versions before the upgrade make the post-upgrade checks harder to read.

Step 3: Capture a Recoverable Snapshot

EKS hides etcd from you, so a snapshot means capturing what you control: manifests, add-on versions, node group settings, and persistent volume data. Our guide to Velero Kubernetes backup and restore covers the volume side in depth. For the configuration side, dump everything to a dated folder.

STAMP=$(date +%Y%m%d-%H%M)
OUT=./snapshots/$CLUSTER-$STAMP
mkdir -p "$OUT"

aws eks describe-cluster --name "$CLUSTER" > "$OUT/cluster.json"
aws eks list-addons --cluster-name "$CLUSTER" > "$OUT/addons-list.json"
for a in $(aws eks list-addons --cluster-name "$CLUSTER" --query 'addons[]' --output text); do
  aws eks describe-addon --cluster-name "$CLUSTER" --addon-name "$a" > "$OUT/addon-$a.json"
done
aws eks list-nodegroups --cluster-name "$CLUSTER" > "$OUT/nodegroups.json"

kubectl get all,ingress,pvc,hpa,pdb -A -o yaml > "$OUT/workloads.yaml"
kubectl get crd -o yaml > "$OUT/crds.yaml"
helm list -A -o json > "$OUT/helm-releases.json"

ls -lh "$OUT"

Commit that folder to a private repository or copy it to a versioned S3 bucket. The add-on JSON files matter most. If an add-on update misbehaves, those files tell you the exact version and configuration to restore.

Steps 4 to 6: Run Insights, Scan APIs, and Test the Breaking Changes

Step 4: Run EKS Upgrade Insights

EKS cluster insights check your cluster against the next version and flag deprecated API usage seen by the control plane, along with version skew problems. Pull the upgrade-readiness category first.

aws eks list-insights --cluster-name "$CLUSTER" \
  --filter '{"categories":["UPGRADE_READINESS"]}' \
  --query 'insights[].{id:id,name:name,status:insightStatus.status}' \
  --output table

# Drill into any insight that is not PASSING
aws eks describe-insight --cluster-name "$CLUSTER" --id <insight-id>

Treat any ERROR status as a blocker and any WARNING as something you must explain before Step 9. The insight detail lists the offending resources and the user agents that called the deprecated API, which usually points straight at an old controller or CI job.

Step 5: Scan Live Objects and Charts for Deprecated APIs

Insights only see what hit the API server recently. A yearly CronJob or a rarely rendered Helm template can hide from them. Cover both live objects and source files. If your charts are due for modernization anyway, pair this step with the Helm 3 to Helm 4 migration.

# Live cluster
kubent

# GitOps repo and rendered Helm output, targeted at 1.37
pluto detect-files -d ./k8s --target-versions k8s=v1.37.0
pluto detect-helm -o wide --target-versions k8s=v1.37.0

Fix findings in source control, not by patching live objects. Otherwise the next sync reverts your fix and the warning returns after the upgrade, when it can no longer be ignored.

Step 6: Test the Behavior Changes That Break Workloads

Most of 1.37 is additive. One upgrade checklist on DEV Community, last verified October 22, 2026, lists seven changes that can break running workloads going from 1.36 to 1.37. It is a single community source, so confirm each item against the official release notes. The table summarizes them with the quick test you can run.

Change (per the checklist)Who is exposedQuick test
SELinux mount labeling on by defaultPods with seLinuxOptions, often Bottlerocket nodeskubectl get csidriver and search Pods for seLinuxOptions
Static pods cannot reference Secrets or ConfigMapsSelf-managed nodes with custom manifestsGrep /etc/kubernetes/manifests on a node
Kubelet refuses deprecated cAdvisor flagsCustom AMIs and launch templatesInspect the kubelet command line
eventRecordQPS: 0 now means no limitNodes that set the field to 0Read configz for each node
Auto Mode default consolidation policy changesNodePools without an explicit policyList NodePools and their policies
HPA scale-to-zero enabled by defaultHPAs with minReplicas: 0Query HPAs for zero minimums
kube-proxy IPVS mode deprecatedClusters running IPVSCheck the kube-proxy config map mode

Run the quick tests that apply to your setup. The two that catch the most teams are the HPA and kube-proxy checks, because both run on managed clusters with no custom node work.

# HPAs that can now scale to zero
kubectl get hpa -A -o json | jq -r '
  .items[] | select(.spec.minReplicas == 0)
  | "\(.metadata.namespace)/\(.metadata.name)"'

# kube-proxy mode
kubectl -n kube-system get cm kube-proxy-config -o yaml | grep -i mode

# NodePools (Auto Mode or Karpenter) with no explicit consolidation policy
kubectl get nodepools -o custom-columns=NAME:.metadata.name,POLICY:.spec.disruption.consolidationPolicy

# Event rate on a sample node
NODE=$(kubectl get nodes -o jsonpath='{.items[0].metadata.name}')
kubectl get --raw "/api/v1/nodes/$NODE/proxy/configz" | jq '.kubeletconfig.eventRecordQPS'

Set minReplicas: 1 anywhere a zero minimum is accidental, such as a Helm default or a copy-pasted manifest. If you run KEDA or a Prometheus adapter and want true scale-to-zero, our walkthrough on autoscaling with Kubernetes HPA and KEDA shows how to wire it up on purpose.

Steps 7 and 8: Check Add-on Compatibility and Rehearse on Staging

Step 7: Map Add-on Versions to Kubernetes 1.37

Do not guess add-on versions. Ask the EKS API which version is the default for 1.37, then compare it with what you run now.

# Default add-on versions for the target release
aws eks describe-addon-versions --kubernetes-version 1.37 \
  --query 'addons[].{name:addonName,default:addonVersions[?compatibilities[?defaultVersion==`true`]]|[0].addonVersion}' \
  --output table

# What the cluster runs today
aws eks list-addons --cluster-name "$CLUSTER" --query 'addons[]' --output text | tr '\t' '\n' | while read a; do
  v=$(aws eks describe-addon --cluster-name "$CLUSTER" --addon-name "$a" --query 'addon.addonVersion' --output text)
  echo "$a $v"
done

Anything you installed outside the add-on API needs its own check. That includes ingress controllers, the AWS Load Balancer Controller, cert-manager, external-dns, and CSI drivers. Read each project’s compatibility table for Kubernetes 1.37. Our Kubernetes ingress controller setup explains why ingress is the usual weak spot after a controller reaches end of life.

Step 8: Rehearse on a Throwaway Cluster

A rehearsal costs a few dollars and removes most of the fear. Create a small 1.36 cluster that mirrors production add-ons and a sample of real workloads, then run Steps 9 to 12 against it. The eksctl config below keeps the rehearsal cheap and repeatable.

# rehearsal.yaml
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
  name: rehearsal-eks
  region: us-east-1
  version: "1.36"
managedNodeGroups:
  - name: ng-rehearsal
    instanceType: m6i.large
    desiredCapacity: 2
    minSize: 2
    maxSize: 3
addons:
  - name: vpc-cni
  - name: coredns
  - name: kube-proxy
eksctl create cluster -f rehearsal.yaml
# deploy a representative slice of production here, then:
eksctl upgrade cluster --name rehearsal-eks --region us-east-1 --version 1.37 --approve

Time each phase. The rehearsal gives you a realistic duration for the control-plane update and the node roll, and those numbers set your maintenance window. Delete the cluster when you finish with eksctl delete cluster -f rehearsal.yaml, otherwise the hourly control-plane fee keeps running.

Step 9: Upgrade the EKS Control Plane to 1.37

Pick a low-traffic window, pause deploy pipelines, and tell the on-call engineer. The control plane stays available during the update in the usual case, though you should not rely on that for anything you cannot afford to lose. Then start the update with the AWS CLI.

UPDATE_ID=$(aws eks update-cluster-version \
  --name "$CLUSTER" --region "$AWS_REGION" \
  --kubernetes-version 1.37 \
  --query 'update.id' --output text)
echo "Update: $UPDATE_ID"

# Poll until status is Successful
aws eks describe-update --name "$CLUSTER" --update-id "$UPDATE_ID" \
  --query 'update.{status:status,type:type,errors:errors}' --output table

# Or block until the cluster is ACTIVE again
aws eks wait cluster-active --name "$CLUSTER" --region "$AWS_REGION"

The eksctl equivalent is a single line: eksctl upgrade cluster --name "$CLUSTER" --region "$AWS_REGION" --version 1.37 --approve. Both routes call the same EKS API. Expected output while the update runs:

------------------------------------------
|              DescribeUpdate            |
+----------+-------------+---------------+
|  errors  |   status    |     type      |
+----------+-------------+---------------+
|  None    |  InProgress |  VersionUpdate|
+----------+-------------+---------------+

When the status flips to Successful, confirm the new version with kubectl version. Do not start Step 10 until the API server answers on 1.37 and kubectl get --raw /readyz returns ok. Nodes still run 1.36 kubelets, which is expected and supported for now.

Steps 10 and 11: Upgrade Add-ons and Worker Nodes

Step 10: Update the Managed Add-ons

Update add-ons one at a time, starting with kube-proxy, then vpc-cni, then coredns, then storage and observability add-ons. Use the default version from Step 7 and let EKS preserve your custom configuration.

ADDON=kube-proxy
VER=$(aws eks describe-addon-versions --addon-name "$ADDON" --kubernetes-version 1.37 \
  --query 'addons[0].addonVersions[?compatibilities[?defaultVersion==`true`]]|[0].addonVersion' --output text)

aws eks update-addon --cluster-name "$CLUSTER" --addon-name "$ADDON" \
  --addon-version "$VER" --resolve-conflicts PRESERVE

aws eks wait addon-active --cluster-name "$CLUSTER" --addon-name "$ADDON"
kubectl -n kube-system rollout status ds/kube-proxy

Repeat the block for each add-on. Watch CoreDNS closely. A DNS regression shows up as scattered timeouts across unrelated services, so run a quick lookup test right after:

kubectl run dnscheck --rm -it --restart=Never --image=busybox:stable -- nslookup kubernetes.default

Step 11: Roll the Nodes to 1.37

Your node route depends on how you provision capacity. Managed node groups take one command and replace nodes in a rolling fashion that respects PodDisruptionBudgets.

for ng in $(aws eks list-nodegroups --cluster-name "$CLUSTER" --query 'nodegroups[]' --output text); do
  aws eks update-nodegroup-version \
    --cluster-name "$CLUSTER" --nodegroup-name "$ng" \
    --kubernetes-version 1.37
  aws eks wait nodegroup-active --cluster-name "$CLUSTER" --nodegroup-name "$ng"
done

kubectl get nodes -o custom-columns=NAME:.metadata.name,VERSION:.status.nodeInfo.kubeletVersion

With Karpenter, update the node class AMI selector so new nodes launch on 1.37, then let drift replace the old ones. Our Karpenter on EKS autoscaling guide shows the node pool and disruption budget settings that keep that replacement orderly. On EKS Auto Mode, AWS manages node versions for you, so confirm the new nodes report 1.37 and review your NodePool consolidation policy from Step 6.

Self-managed node groups need a new launch template with the 1.37 EKS-optimized AMI, then a controlled drain of the old group. Cordon first, drain with a timeout, and delete the old group only after workloads settle on the new one.

Steps 12 and 13: Verify the Cluster and Review Cost

Step 12: Verify Workloads, the Metrics API, and Autoscaling

Run a short verification suite. Each command answers one question: is everything on 1.37, is anything unhealthy, and does autoscaling still see metrics through the now-stable Metrics API?

kubectl version
kubectl get nodes
kubectl get pods -A --field-selector=status.phase!=Running,status.phase!=Succeeded
kubectl get apiservice v1beta1.metrics.k8s.io v1.metrics.k8s.io 2>/dev/null
kubectl top nodes
kubectl get hpa -A
kubectl get events -A --sort-by=.lastTimestamp | tail -20

Example healthy output (illustrative):

NAME                         STATUS   ROLES    AGE   VERSION
ip-10-0-1-23.ec2.internal    Ready    <none>   6m    v1.37.x-eks-xxxxxxx
ip-10-0-2-41.ec2.internal    Ready    <none>   6m    v1.37.x-eks-xxxxxxx

NAME                         CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
ip-10-0-1-23.ec2.internal    212m         5%     1904Mi          26%
ip-10-0-2-41.ec2.internal    188m         4%     1822Mi          25%

Then run your own smoke tests: log in, place a test order, hit a health endpoint through the load balancer. Compare error rates and p95 latency on your dashboards against the hour before the upgrade. Keep the old and new numbers side by side in the change ticket.

Step 13: Update IaC, Re-run Insights, and Record the Result

The last step prevents drift. Change the version in your infrastructure-as-code so the next apply does not try to downgrade. Then re-run upgrade insights, which should now point at the next version, and archive a post-upgrade snapshot beside the one from Step 3.

# Terraform (aws_eks_cluster)
resource "aws_eks_cluster" "this" {
  name     = var.cluster_name
  version  = "1.37"   # was "1.36"
  role_arn = aws_iam_role.cluster.arn
  # ...
}

# Then confirm no drift
terraform plan

Close the change ticket with the start and end times, the insight results, and the rollback decision you did not need. That record makes the next upgrade shorter.

Monitor the First 48 Hours

Problems often surface after the change window closes. A weekly job, a cache expiry, or a traffic peak can exercise a code path your smoke tests never touched. Keep a short watch list for two days: API server error rates, pod restart counts, node NotReady events, HPA scaling decisions, and DNS latency. Compare each against the same hour on the previous week rather than against yesterday, so ordinary weekly patterns do not look like regressions.

Also review audit logs for deprecated API calls once more. The insights tab will now evaluate the cluster against the version after 1.37, and anything it flags today is your head start on the next upgrade. EKS ships a new minor version on a steady cadence, so the cheapest habit you can build is a quarterly rehearsal that reuses the scripts in this article.

Complete Working Project: A Repeatable EKS Upgrade Runner

The 13 steps work by hand, but a script turns them into a checklist the whole team can run. The project below has one preflight script and one upgrade script. Preflight is read-only and safe to run any day. The upgrade script refuses to continue if preflight finds blockers.

eks-upgrade/
  preflight.sh     # Steps 1-7, read-only
  upgrade.sh       # Steps 9-12, changes the cluster
  rehearsal.yaml   # Step 8 throwaway cluster
  snapshots/       # Step 3 output

First the preflight script. It checks status, runs insights, lists add-on versions, and exits non-zero when anything blocks the upgrade.

#!/usr/bin/env bash
# preflight.sh - read-only checks before an EKS minor upgrade
set -euo pipefail

CLUSTER=${1:?usage: preflight.sh CLUSTER REGION TARGET}
REGION=${2:?region}
TARGET=${3:-1.37}
fail=0

status=$(aws eks describe-cluster --name "$CLUSTER" --region "$REGION" --query 'cluster.status' --output text)
current=$(aws eks describe-cluster --name "$CLUSTER" --region "$REGION" --query 'cluster.version' --output text)
echo "cluster=$CLUSTER status=$status version=$current target=$TARGET"
[ "$status" = "ACTIVE" ] || { echo "BLOCKER: cluster not ACTIVE"; fail=1; }

# Target must be exactly one minor above current
cur_minor=${current#*.}
tgt_minor=${TARGET#*.}
[ $((tgt_minor - cur_minor)) -eq 1 ] || { echo "BLOCKER: target must be one minor above $current"; fail=1; }

# Upgrade insights: count anything not PASSING
bad=$(aws eks list-insights --cluster-name "$CLUSTER" --region "$REGION" \
  --filter '{"categories":["UPGRADE_READINESS"]}' \
  --query 'length(insights[?insightStatus.status!=`PASSING`])' --output text)
echo "insights not passing: $bad"
[ "$bad" = "0" ] || { echo "BLOCKER: review insights with describe-insight"; fail=1; }

# Add-ons: show current vs default for the target
for a in $(aws eks list-addons --cluster-name "$CLUSTER" --region "$REGION" --query 'addons[]' --output text); do
  now=$(aws eks describe-addon --cluster-name "$CLUSTER" --addon-name "$a" --region "$REGION" --query 'addon.addonVersion' --output text)
  want=$(aws eks describe-addon-versions --addon-name "$a" --kubernetes-version "$TARGET" --region "$REGION" \
    --query 'addons[0].addonVersions[?compatibilities[?defaultVersion==`true`]]|[0].addonVersion' --output text)
  echo "addon $a: now=$now target-default=$want"
done

# HPAs that can now scale to zero
zero=$(kubectl get hpa -A -o json | jq '[.items[] | select(.spec.minReplicas == 0)] | length')
echo "HPAs with minReplicas 0: $zero"

exit $fail

Now the upgrade runner. It calls preflight, updates the control plane, waits, rolls add-ons to the target default, rolls managed node groups, then prints verification output.

#!/usr/bin/env bash
# upgrade.sh - control plane, add-ons, managed node groups
set -euo pipefail

CLUSTER=${1:?usage: upgrade.sh CLUSTER REGION TARGET}
REGION=${2:?region}
TARGET=${3:-1.37}

./preflight.sh "$CLUSTER" "$REGION" "$TARGET"
read -r -p "Preflight passed. Upgrade $CLUSTER to $TARGET? (type yes) " ans
[ "$ans" = "yes" ] || exit 1

echo "== control plane"
uid=$(aws eks update-cluster-version --name "$CLUSTER" --region "$REGION" \
  --kubernetes-version "$TARGET" --query 'update.id' --output text)
aws eks wait cluster-active --name "$CLUSTER" --region "$REGION"
aws eks describe-update --name "$CLUSTER" --region "$REGION" --update-id "$uid" \
  --query 'update.status' --output text

echo "== add-ons"
for a in kube-proxy vpc-cni coredns; do
  v=$(aws eks describe-addon-versions --addon-name "$a" --kubernetes-version "$TARGET" --region "$REGION" \
    --query 'addons[0].addonVersions[?compatibilities[?defaultVersion==`true`]]|[0].addonVersion' --output text)
  aws eks update-addon --cluster-name "$CLUSTER" --region "$REGION" --addon-name "$a" \
    --addon-version "$v" --resolve-conflicts PRESERVE >/dev/null
  aws eks wait addon-active --cluster-name "$CLUSTER" --region "$REGION" --addon-name "$a"
  echo "updated $a to $v"
done

echo "== managed node groups"
for ng in $(aws eks list-nodegroups --cluster-name "$CLUSTER" --region "$REGION" --query 'nodegroups[]' --output text); do
  aws eks update-nodegroup-version --cluster-name "$CLUSTER" --region "$REGION" \
    --nodegroup-name "$ng" --kubernetes-version "$TARGET" >/dev/null
  aws eks wait nodegroup-active --cluster-name "$CLUSTER" --region "$REGION" --nodegroup-name "$ng"
  echo "rolled $ng"
done

echo "== verify"
kubectl get nodes -o custom-columns=NAME:.metadata.name,VERSION:.status.nodeInfo.kubeletVersion
kubectl get pods -A --field-selector=status.phase!=Running,status.phase!=Succeeded
kubectl top nodes

Run it like this: chmod +x *.sh && ./preflight.sh prod-eks us-east-1 1.37. Preflight printing zero blockers is your green light. Run the same pair against rehearsal-eks first. The script covers managed node groups and the three core add-ons only, so extend the add-on list with whatever list-addons returns on your cluster, and handle Karpenter or self-managed nodes by hand as described in Step 11.

Common Pitfalls That Derail EKS Upgrades

Most failed upgrades trace back to a short list of avoidable mistakes. These six account for the bulk of the pain.

  • Skipping minor versions. EKS control planes upgrade one minor version at a time. A cluster on 1.34 needs 1.35, 1.36, and 1.37 in turn, with add-on and node checks between each hop.
  • Upgrading nodes before add-ons. The safe order is control plane, add-ons, nodes. Reversing it can leave a new kubelet talking to a stale CNI.
  • Trusting insights alone. Insights see recent API traffic only. A seasonal CronJob that calls a removed API will pass insights and fail in production.
  • Forgetting PodDisruptionBudgets. A PDB set to zero allowed disruptions blocks every node drain and stalls the managed node group update until it times out.
  • Ignoring default changes. HPA scale-to-zero is on by default in 1.37, so a stray minReplicas: 0 now has teeth. Audit it in Step 6.
  • Leaving IaC behind. If Terraform or CDK still says 1.36, the next apply tries to reconcile and may fail or fight the real state. Update the code the same day.

Troubleshooting: 9 Problems and Fixes

Use this section when a step stalls. Each item lists the symptom, the likely cause, and the fix.

#SymptomLikely causeFix
1update-cluster-version fails with an unsupported version errorTarget is more than one minor above current, or the CLI is too oldUpgrade one minor at a time and update the AWS CLI and eksctl
2Insights show ERROR for deprecated APIsA controller or CI job still calls a removed APIRead the user agent in describe-insight, upgrade that client, re-run insights
3Add-on update stuck in UPDATING or DEGRADEDCustom configuration conflicts with the new defaultRe-run with --resolve-conflicts OVERWRITE after saving your settings, then reapply them
4Node group update times outPDB blocks eviction or pods cannot rescheduleLoosen the PDB temporarily and check capacity, then retry
5Pods stuck in ContainerCreating after node rollVolume mount behavior changed, often SELinux labeling on shared volumesCheck the pod events and review the SELinux item from Step 6
6kubectl top returns an errormetrics-server missing or unhealthy after the rollCheck the metrics-server deployment and the APIService status
7Kubelet fails to start on custom AMI nodesDeprecated kubelet flags in user dataRemove the old flags from the launch template and relaunch nodes
8DNS timeouts after the upgradeCoreDNS version or config mismatchConfirm CoreDNS reached the target default and review the Corefile
9Workloads scale to zero unexpectedlyHPA with minReplicas: 0 and an external metricSet minReplicas: 1 or confirm the zero is intended

What to Do If the Upgrade Goes Wrong

Because the control plane step is one-way, rollback means restoring service on the same cluster or moving to a known-good one. For add-ons, pin the earlier version from your Step 3 JSON files. For nodes, scale the old node group back up or relaunch from the old AMI. For a deeper failure, keep your rehearsal config handy and rebuild a 1.36 cluster for a blue-green cutover, restoring volume data from your backups.

Advanced Tips for Safer, Faster EKS Upgrades

Once the manual run feels routine, tighten the loop. These practices come from the shape of the 1.37 release and the way EKS splits responsibility between AWS and you.

  • Upgrade through code, not the console. Bump the version in Terraform, OpenTofu, or CDK, review the plan, and apply it from CI. If you also run self-managed clusters, the kubeadm and OpenTofu cluster setup shows the same version-pinning discipline outside EKS.
  • Make insights a CI gate. Run the upgrade-readiness query nightly and fail the pipeline when a new ERROR appears. You then learn about a deprecated API the day someone deploys it, not the day you upgrade.
  • Pin explicit add-on versions in IaC. Defaults move. A pinned version plus a dated review makes upgrades a diff you can read.
  • Protect GPU nodes with device taints. Device taints and tolerations for DRA are generally available in 1.37. If you run accelerators, plan to adopt them so general workloads cannot land on scarce devices. EKS does not install a DRA driver for you, according to the DEV Community checklist, so that part stays on your side.
  • Set Auto Mode compute settings declaratively. The advancedCompute field lets you manage kubelet settings, sysctls, eviction thresholds, and hugepages in the NodeClass instead of custom user data.
  • Move off kube-proxy IPVS. The checklist reports IPVS mode as deprecated in 1.37, so use this release to plan a move to nftables or iptables mode.
  • Stagger fleets. Upgrade dev, then staging, then a low-risk production cluster, and wait a few days between rings. Qovery’s release write-up includes a section on which clusters should upgrade first and which should wait, which is a useful starting point for ranking your own fleet by risk.

Add a Post-Upgrade Smoke Test to the Pipeline

A tiny Job that runs after every upgrade catches regressions before users do. This example checks DNS, the Metrics API, and outbound connectivity.

apiVersion: batch/v1
kind: Job
metadata:
  name: post-upgrade-smoke
spec:
  backoffLimit: 0
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: smoke
          image: busybox:stable
          command: ["sh", "-c"]
          args:
            - nslookup kubernetes.default && wget -q -O- --timeout=5 https://aws.amazon.com > /dev/null && echo SMOKE_OK

Run it with kubectl apply -f smoke.yaml && kubectl wait --for=condition=complete job/post-upgrade-smoke --timeout=120s, then read the logs for SMOKE_OK.

In-Place Upgrade or Blue-Green Cluster: Which Fits Your Team

Both strategies work on EKS. In-place upgrades keep the same cluster, endpoints, and IAM bindings. Blue-green builds a second cluster on 1.37 and shifts traffic across. The right choice depends on how much risk you can tolerate and how much duplicate infrastructure you can pay for.

FactorIn-place upgradeBlue-green cluster
RollbackControl plane is one-way, nodes and add-ons can revertSwitch traffic back to the old cluster
Extra costBrief node overlap during rollsA second control plane and node fleet while both run
EffortLow, about the 13 steps hereHigh, needs DNS or load balancer cutover and data sync
Stateful workloadsVolumes stay attachedNeeds backup and restore or replication
Best forMost clusters with solid testsCritical clusters, or jumps across several versions

Most teams should run in-place upgrades with a rehearsal, and reserve blue-green for the cluster whose outage would make the news. The rehearsal from Step 8 already gives you most of the confidence blue-green buys.

What Staying Behind Costs: Support Windows and Pricing

Delaying an upgrade has a price. According to the EKS pricing page updated October 13, 2026, a Kubernetes version in standard support costs $0.10 per cluster-hour. After standard support ends, the same version moves to extended support at $0.60 per cluster-hour, which is the standard fee plus a $0.50 surcharge. AWS gives each version 14 months of standard support, then 12 months of extended support.

Fleet sizeStandard support per monthExtended support per monthExtra cost per month
1 cluster$73$438$365
5 clusters$365Not listedNot listed
20 clustersNot listed$8,760Not listed

The monthly figures use 730 hours per month, the same basis AWS uses in its pricing examples. They cover the cluster fee only. Nodes, load balancers, and data transfer come on top. Extended support costs $0.60 per cluster-hour against $0.10 in standard support, so a large fleet left in extended support adds a meaningful monthly bill, which pays for a lot of upgrade engineering time.

Cost-minded teams should also note an adjacent change. On October 23, 2026, AWS cut the EMR charge for EMR on EKS by 30 percent, while the underlying EKS prices stayed the same. For more guides in this area, browse our cloud computing coverage.

FAQ: Upgrading Amazon EKS to Kubernetes 1.37

Is Kubernetes 1.37 available on Amazon EKS?

Yes. AWS announced support for Kubernetes 1.37 on Amazon EKS and Amazon EKS Distro on October 2, 2026, in all Regions where EKS runs, including AWS GovCloud (US). New clusters can start on 1.37 and existing clusters can upgrade.

How long does an EKS cluster upgrade take?

AWS publishes no fixed duration. Your rehearsal in Step 8 gives the best estimate, since control-plane time and node roll time both depend on cluster size and PodDisruptionBudgets. The hands-on effort in this tutorial is about 70 minutes, and the waiting time comes on top.

Can I skip from 1.34 straight to 1.37?

No. EKS control planes upgrade one minor version per operation, so you step through each intermediate version. Check insights and add-on compatibility at every hop.

Can I roll back the control plane after upgrading?

Treat the control-plane upgrade as irreversible. Roll back add-ons and nodes where needed, and keep a blue-green fallback plan for critical clusters.

What does extended support cost?

Extended support costs $0.60 per cluster-hour versus $0.10 in standard support, according to the AWS pricing page updated October 13, 2026. Each version gets 14 months of standard support followed by 12 months of extended support.

Do I need eksctl, or can I use the console or Terraform?

You can use any of them. AWS names the EKS console, eksctl, and infrastructure-as-code tools as supported ways to upgrade to 1.37. Pick the one that matches how the cluster was built, so state stays consistent.

Is HPA scale-to-zero stable in 1.37?

No. It is beta in 1.37 and enabled by default for eligible object or external metric setups with minReplicas: 0. The Metrics API (metrics.k8s.io/v1), by contrast, is generally available.

Does the upgrade change my EKS bill?

Moving to a version in standard support keeps you at $0.10 per cluster-hour for the control plane. The upgrade itself adds only short-lived node overlap while old and new nodes coexist during the roll.

Sofia Lindström

Sofia Lindström

Editor-in-Chief

Sofia Lindström is the Editor-in-Chief at Tech Insider, where she leads editorial strategy and oversees coverage across AI, cybersecurity, and enterprise technology. With over a decade in Swedish tech journalism, she previously served as technology editor at Dagens Industri and covered the Nordic startup ecosystem for Breakit. Sofia holds an MSc in Media Technology from KTH Royal Institute of Technology and is a frequent speaker at Web Summit and Slush. She is passionate about making complex technology accessible to business leaders.

View all articles