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.
Don't miss new tech stories on Google
Add Tech Insider once in the Google app and our stories appear in your news suggestions.
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.
| Requirement | Version or setting | Why it matters |
|---|---|---|
| Source cluster | Kubernetes 1.36 on Amazon EKS | EKS control planes move one minor version per operation |
| Target version | Kubernetes 1.37 (EKS support since Oct. 2, 2026) | Newest version on EKS today |
| eksctl | 0.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 CLI | Version 2, current release | Needed for insights, add-on, and node group commands |
| kubectl | 1.36 or 1.37 client | Stay within one minor version of the API server |
| Helm | Current release | Chart renders must pass against 1.37 APIs |
| Scanners | pluto and kubent, latest builds | Find deprecated API calls before the control plane does |
| Staging cluster | Disposable EKS 1.36 cluster | Rehearsal 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.
| Layer | Who runs it | Upgrade order | Reversible? |
|---|---|---|---|
| Control plane | AWS | 1st | Treat as no |
| Managed add-ons | You, via the EKS add-on API | 2nd | Yes, pin an earlier compatible version |
| Managed node groups | You, rolling replacement | 3rd | Yes, until old nodes drain |
| Karpenter or Auto Mode nodes | You or AWS | 3rd | Yes, via node pool rollback |
| Workloads and charts | You | Before and after | Yes, 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.
| Steps | Task | Hands-on minutes |
|---|---|---|
| 1 to 3 | Version check, tooling, snapshot | 10 |
| 4 and 5 | Insights and API scans | 10 |
| 6 | Behavior-change tests | 10 |
| 7 | Add-on compatibility map | 5 |
| 8 | Rehearsal setup and review | 10 |
| 9 | Start and confirm control-plane update | 5 |
| 10 | Add-on updates | 5 |
| 11 | Node roll | 5 |
| 12 | Verification | 5 |
| 13 | IaC update and records | 5 |
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 exposed | Quick test |
|---|---|---|
| SELinux mount labeling on by default | Pods with seLinuxOptions, often Bottlerocket nodes | kubectl get csidriver and search Pods for seLinuxOptions |
| Static pods cannot reference Secrets or ConfigMaps | Self-managed nodes with custom manifests | Grep /etc/kubernetes/manifests on a node |
| Kubelet refuses deprecated cAdvisor flags | Custom AMIs and launch templates | Inspect the kubelet command line |
eventRecordQPS: 0 now means no limit | Nodes that set the field to 0 | Read configz for each node |
| Auto Mode default consolidation policy changes | NodePools without an explicit policy | List NodePools and their policies |
| HPA scale-to-zero enabled by default | HPAs with minReplicas: 0 | Query HPAs for zero minimums |
| kube-proxy IPVS mode deprecated | Clusters running IPVS | Check 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: 0now 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.
| # | Symptom | Likely cause | Fix |
|---|---|---|---|
| 1 | update-cluster-version fails with an unsupported version error | Target is more than one minor above current, or the CLI is too old | Upgrade one minor at a time and update the AWS CLI and eksctl |
| 2 | Insights show ERROR for deprecated APIs | A controller or CI job still calls a removed API | Read the user agent in describe-insight, upgrade that client, re-run insights |
| 3 | Add-on update stuck in UPDATING or DEGRADED | Custom configuration conflicts with the new default | Re-run with --resolve-conflicts OVERWRITE after saving your settings, then reapply them |
| 4 | Node group update times out | PDB blocks eviction or pods cannot reschedule | Loosen the PDB temporarily and check capacity, then retry |
| 5 | Pods stuck in ContainerCreating after node roll | Volume mount behavior changed, often SELinux labeling on shared volumes | Check the pod events and review the SELinux item from Step 6 |
| 6 | kubectl top returns an error | metrics-server missing or unhealthy after the roll | Check the metrics-server deployment and the APIService status |
| 7 | Kubelet fails to start on custom AMI nodes | Deprecated kubelet flags in user data | Remove the old flags from the launch template and relaunch nodes |
| 8 | DNS timeouts after the upgrade | CoreDNS version or config mismatch | Confirm CoreDNS reached the target default and review the Corefile |
| 9 | Workloads scale to zero unexpectedly | HPA with minReplicas: 0 and an external metric | Set 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
advancedComputefield 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.
| Factor | In-place upgrade | Blue-green cluster |
|---|---|---|
| Rollback | Control plane is one-way, nodes and add-ons can revert | Switch traffic back to the old cluster |
| Extra cost | Brief node overlap during rolls | A second control plane and node fleet while both run |
| Effort | Low, about the 13 steps here | High, needs DNS or load balancer cutover and data sync |
| Stateful workloads | Volumes stay attached | Needs backup and restore or replication |
| Best for | Most clusters with solid tests | Critical 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 size | Standard support per month | Extended support per month | Extra cost per month |
|---|---|---|---|
| 1 cluster | $73 | $438 | $365 |
| 5 clusters | $365 | Not listed | Not listed |
| 20 clusters | Not listed | $8,760 | Not 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.





