How to Upgrade an EKS Cluster Safely: Control Plane, Add-ons, and Nodes
Upgrading an EKS cluster means moving the Kubernetes version running your control plane, node groups, and add-ons to a newer release. AWS handles the infrastructure side of the control plane, but you own the sequence: which components to upgrade, in what order, and when. This guide walks through the full process from planning to post-upgrade validation.
What gets upgraded (and what does not)
Think of an EKS cluster as a building with several distinct systems: the structural frame (control plane), the shared utilities running through the walls (managed add-ons), the floors themselves (node groups), and the equipment each tenant installed (self-managed add-ons). Each system needs upgrading separately, in the right order. You cannot renovate the tenant equipment before the floor it sits on has been updated.
What you explicitly upgrade:
- Control plane: the API server, scheduler, controller manager, and etcd. AWS manages the underlying infrastructure; you trigger the version bump.
- EKS managed add-ons: kube-proxy, CoreDNS, and the Amazon VPC CNI plugin. These run on your nodes and must be updated to versions compatible with the new control plane.
- Node groups: the EC2 instances that run your pods. Upgrading a node group replaces each node with one running a newer EKS-optimised AMI and kubelet version. See EKS Managed Node Groups for how this rollout works.
- Self-managed add-ons: components you installed via Helm or kubectl, such as the AWS Load Balancer Controller, metrics-server, cluster-autoscaler, and your observability stack.
What AWS upgrades automatically:
Nothing material. AWS will upgrade your control plane if it reaches end-of-support without action on your part, but that is a last-resort safety net. You should control when and how upgrades happen.
What keeps serving traffic during a typical upgrade:
Your running pods are not touched by the control plane upgrade. Applications stay up. The disruption risk comes primarily from the node group upgrade, where pods are drained and rescheduled. With correct Deployment replica counts and Pod Disruption Budgets, most workloads survive the node drain without user-visible downtime.
Once the control plane upgrade starts, there is no going back. EKS does not support downgrading a cluster version. Staging validation is your only safety net. This is the most important fact to understand before you start.
Why EKS upgrades matter
AWS supports each Kubernetes version on EKS for approximately 14 months after it becomes available in the service. When that window closes:
- Security patches stop. Vulnerabilities in both Kubernetes and the underlying node AMIs go unaddressed.
- AWS notifies you and can automatically upgrade your cluster if you do not act.
- You lose the ability to upgrade on your own schedule. Someone else sets the deadline.
Kubernetes itself moves quickly. A new minor version arrives roughly every four months, which means staying current requires a deliberate, routine process. Not a scramble when a support window is closing.
Delaying upgrades is like deferring car servicing. Each month you skip, the list of deferred work grows. At some point you face the entire backlog at once, and a job that should take an afternoon becomes a stressful multi-day event. A single-version upgrade is a focused, testable change. A multi-version gap is several upgrades compressed into one stressful window with less room for error.
Teams that upgrade one version at a time on a quarterly cadence almost never have an upgrade incident. Teams that defer until the forced-upgrade notice regularly do.
Stay one or two minor versions behind the latest EKS release. This gives you time to test before each upgrade without falling behind the support window. One upgrade per quarter is a realistic target for most teams.
# Check your current cluster version
aws eks describe-cluster \
--name my-cluster \
--region eu-west-1 \
--query 'cluster.version' \
--output text
# List Kubernetes versions currently supported by EKS
aws eks describe-addon-versions \
--query 'addons[0].addonVersions[*].compatibilities[*].clusterVersion' \
--output text | tr '\t' '\n' | sort -uHow the EKS upgrade process works
Every EKS upgrade follows the same conceptual sequence. Understanding the flow before you run any commands helps you diagnose issues when something stalls.
1. Validate readiness
Before touching anything, confirm your cluster is healthy, check for deprecated Kubernetes API usage in your manifests and Helm charts, and verify that the next minor version is your intended target. Most upgrade incidents start because this step was skipped.
2. Upgrade the control plane
You request the upgrade via the AWS CLI or Console. AWS replaces the control plane components in a rolling fashion without interrupting running pods. This takes roughly 15–25 minutes. When complete, the API server is running the new version.
3. Upgrade managed add-ons
kube-proxy, CoreDNS, and the Amazon VPC CNI plugin need to be updated to versions compatible with the new control plane. Each add-on update is a separate operation. You verify each one reaches ACTIVE status before moving to the next.
4. Upgrade node groups
Each managed node group is updated to an EKS-optimised AMI for the new Kubernetes version. AWS drains each node before replacing it, rescheduling pods to remaining nodes. This is the phase most likely to affect running workloads if Pod Disruption Budgets are misconfigured or if some pods are not managed by a controller.
5. Upgrade self-managed add-ons
Components installed via Helm, including the AWS Load Balancer Controller, metrics-server, cluster-autoscaler, your logging stack, and your monitoring stack, must be upgraded separately. Each has its own Kubernetes compatibility requirements.
6. Validate workloads and observability
After all components are updated, run a structured validation pass: check node readiness, pod health, ingress behaviour, DNS resolution, autoscaling, and your monitoring signals. Do not declare the upgrade complete until applications are behaving normally end-to-end.
Before you start
Think of this as a preflight checklist. Nothing takes off until the checks are done.
Version planning:
- Confirm your current cluster version with
aws eks describe-cluster. - Identify the next minor version as your target. Not the latest. You can only upgrade one minor version at a time.
- Read the Kubernetes changelog and EKS release notes for that specific version: API deprecations, removed APIs, and changed default behaviours.
- If you are more than one version behind your target, plan multiple upgrade windows, one per version.
API deprecation check:
Kubernetes removes old API versions in minor releases. A manifest using a removed API will fail to apply after the upgrade. The difficult part: that failure surfaces at your next deployment, not during the upgrade itself. Everything looks fine immediately after the upgrade, until you push your next release to production.
A removed API does not break your running pods. It breaks your ability to deploy them again. You may not discover this until days or weeks after the upgrade, when you try to push a new release. Run Pluto before every upgrade so this does not reach production.
# Install Pluto (open-source tool for scanning deprecated API usage)
brew install FairwindsOps/tap/pluto
# Scan Helm releases for APIs removed in your target version
# Replace X.Y with your target version
pluto detect-helm --target-versions k8s=vX.Y.0
# Scan a directory of YAML manifests
pluto detect-files -d ./k8s/Fix all issues found in staging before touching production.
Cluster health:
- Verify the cluster is not already in an
UPDATINGor degraded state. - Check that all nodes are in
Readystate:kubectl get nodes. - Review EKS upgrade insights in the Console: EKS > Clusters > Insights. AWS flags known issues that could block or complicate the upgrade.
Node version alignment:
Nodes must not be more than two minor versions behind the control plane. If your nodes are already lagging, bring them up before upgrading the control plane further.
Pod Disruption Budgets:
Review PDBs for workloads that run in production. A PDB that requires all replicas to be available at all times will block node drain entirely. For critical workloads, verify that PDBs allow at least one pod to be unavailable at a time. See Kubernetes Pods for a deeper look at how PDBs interact with eviction.
Staging validation:
Upgrade a staging cluster first. This is not optional for production clusters running real traffic. Run your full smoke test suite on staging after the upgrade. Confirm that deployments, services, ingresses, and database connections all work. Address any issues before touching production.
Client and tooling compatibility:
- Your
kubectlclient version must be within one minor version of the cluster. After upgrading the cluster, upgrade your local kubectl. - If you use Helm, check that your charts support the new Kubernetes API versions.
Rollback and recovery confidence:
EKS does not support rollback of the control plane once it is upgraded. Your safety net is staging validation. Confirm that you have application-level backups for any stateful workloads before starting.
Step by step: upgrade the control plane
Confirm the cluster is healthy, then trigger the upgrade:
# Confirm the cluster is ACTIVE before starting
aws eks describe-cluster \
--name my-cluster \
--region eu-west-1 \
--query 'cluster.{Version:version,Status:status}'
# Trigger the control plane upgrade (one minor version at a time)
# Replace 1.XX with your actual target version
aws eks update-cluster-version \
--name my-cluster \
--kubernetes-version 1.XX \
--region eu-west-1Monitor until the upgrade completes:
# Poll until status returns ACTIVE
aws eks describe-cluster \
--name my-cluster \
--region eu-west-1 \
--query 'cluster.{Version:version,Status:status}'The upgrade typically takes 15–25 minutes. Status shows UPDATING while in progress and returns to ACTIVE when complete. Running pods are unaffected. The API server may be briefly less responsive during the upgrade, so kubectl commands can fail momentarily. This is expected and temporary.
You can also trigger the control plane upgrade from the AWS Console: EKS > Clusters > your cluster > Configuration > Kubernetes version > Update now.
The control plane returning to ACTIVE status means this phase is done. You still have add-ons, node groups, and self-managed components to update. Do not stop here.
Step by step: upgrade EKS managed add-ons
After the control plane upgrade, update kube-proxy, CoreDNS, and the Amazon VPC CNI plugin. Each must be updated to a version compatible with the new Kubernetes version.
Finding compatible versions:
Use the API to discover which add-on versions are compatible with your new cluster version. Do not rely on a version string from a tutorial. Versions change, and what was current when that tutorial was written may no longer be correct:
# List compatible versions for kube-proxy on your target Kubernetes version
# Replace 1.XX with your upgraded cluster version
aws eks describe-addon-versions \
--addon-name kube-proxy \
--kubernetes-version 1.XX \
--region eu-west-1 \
--query 'addons[0].addonVersions[*].addonVersion'Replace kube-proxy with coredns or vpc-cni to check the other managed add-ons. The first result in the list is typically the latest compatible version; check the EKS release notes to confirm.
Checking what is currently installed:
# List all managed add-ons on the cluster
aws eks list-addons \
--cluster-name my-cluster \
--region eu-west-1
# Check the current version of a specific add-on
aws eks describe-addon \
--cluster-name my-cluster \
--addon-name kube-proxy \
--region eu-west-1 \
--query 'addon.{Version:addonVersion,Status:status}'Updating an add-on:
# Update an add-on to a compatible version (use the version returned from the query above)
aws eks update-addon \
--cluster-name my-cluster \
--addon-name kube-proxy \
--addon-version <version-from-above-query> \
--resolve-conflicts PRESERVE \
--region eu-west-1Use —resolve-conflicts PRESERVE to protect any custom configuration you have applied to the add-on. Use OVERWRITE only if you want AWS to reset it to default configuration. When in doubt, use PRESERVE.
Repeat for CoreDNS and the VPC CNI plugin. Update one add-on at a time and verify each one returns to ACTIVE status before moving to the next.
Step by step: upgrade node groups
For managed node groups, AWS handles the AMI replacement and node drain automatically. You trigger the update and monitor the rollout.
# Trigger a managed node group upgrade
aws eks update-nodegroup-version \
--cluster-name my-cluster \
--nodegroup-name general-workers \
--region eu-west-1
# Monitor the node group update
aws eks describe-nodegroup \
--cluster-name my-cluster \
--nodegroup-name general-workers \
--region eu-west-1 \
--query 'nodegroup.{Status:status,Health:health}'What happens during the rollout:
Think of this like clearing out one floor of an office building for renovation, one floor at a time. AWS cordons a node (marks it unschedulable), drains it (evicts all pods, which reschedule onto other nodes), launches a replacement node on the new AMI, waits for it to join the cluster and become ready, then terminates the old one. The process repeats for each node in the group.
The speed of the rollout is controlled by the maxUnavailable setting on the node group. The default is one node at a time. For large clusters, you can increase this to shorten the upgrade window at the cost of temporarily reduced capacity.
Watch during the rollout:
# Watch node status change
kubectl get nodes -w
# Watch pods being rescheduled across the cluster
kubectl get pods -A -w
# Check for pods stuck in Pending or Terminating
kubectl get pods -A --field-selector=status.phase!=RunningA node drain fails if a pod cannot be evicted. Common causes: a PodDisruptionBudget that prevents removing any pod, a bare pod with no controller to reschedule it, or a namespace that blocks eviction. Investigate the cause before forcing it. A forced eviction that overrides a PDB can silently degrade your application. Fix the issue first, then let the drain proceed normally.
Verify nodes after the rollout:
# All nodes should be Ready and running the upgraded kubelet version
kubectl get nodes -o wideThe VERSION column should match your upgraded cluster version across all nodes.
Step by step: self-managed add-ons and supporting components
Components installed via Helm or kubectl, outside of EKS managed add-ons, must be upgraded separately. Each has its own Kubernetes compatibility matrix.
AWS Load Balancer Controller:
The Load Balancer Controller manages your ingress resources and requires IAM Roles for Service Accounts (IRSA) to operate. Check the controller’s release notes for the minimum supported Kubernetes version, then upgrade if needed.
helm repo update
helm search repo eks/aws-load-balancer-controller
helm upgrade aws-load-balancer-controller eks/aws-load-balancer-controller \
-n kube-system \
--set clusterName=my-cluster \
--set serviceAccount.create=false \
--set serviceAccount.name=aws-load-balancer-controllerConfirm the IRSA annotation on the service account is intact after the upgrade.
metrics-server:
metrics-server is required for Horizontal Pod Autoscaling (HPA). Check its compatibility matrix and upgrade if needed.
helm search repo metrics-server/metrics-server
helm upgrade metrics-server metrics-server/metrics-server -n kube-systemAfter upgrading, verify that kubectl top pods returns results. HPA depends on this working correctly.
cluster-autoscaler:
The cluster-autoscaler version must match your Kubernetes minor version. An outdated autoscaler can silently fail to scale or produce unexpected behaviour. Check the cluster-autoscaler GitHub documentation for the compatibility table and update accordingly.
Logging and observability stack:
Your logging pipeline (Fluent Bit, Fluentd, or the CloudWatch agent) and your monitoring stack (Prometheus, Grafana, exporters) may have newer versions with improved compatibility for the upgraded Kubernetes API. Check each component and upgrade where the release notes indicate required or recommended changes.
The rule for all self-managed add-ons: check the compatibility notes, update Helm chart values if needed, upgrade, then verify the component is running and healthy before moving on.
Post-upgrade validation
The control plane showing ACTIVE and nodes showing Ready does not mean the upgrade is complete. Those statuses confirm the infrastructure is healthy. They say nothing about whether your applications are actually working.
It is entirely possible for a cluster to show ACTIVE status with all nodes Ready while your ingress is misconfigured, DNS is returning stale records, and your autoscaler is failing silently. Do not close the upgrade window until you have validated the full application stack.
Node and cluster health:
# All nodes should be Ready and running the upgraded version
kubectl get nodes -o wide
# Check for any pods not in Running or Completed state
kubectl get pods -A | grep -v Running | grep -v CompletedPod health:
# Check for pods in CrashLoopBackOff or Error
kubectl get pods -A --field-selector=status.phase!=Running
# Describe any failed pods to understand the cause
kubectl describe pod <pod-name> -n <namespace>Failed evictions:
If any pods were stuck during the node drain, investigate them now. Pods that were force-evicted without respecting their PDB may have left your application in a degraded state.
Ingress and load balancer checks:
# Check that ingress resources have address assignments
kubectl get ingress -A
# Describe a specific ingress to verify backend endpoints
kubectl describe ingress <ingress-name> -n <namespace>Test your application endpoints directly. Verify that TLS certificates are still being provisioned correctly and that load balancers are routing traffic as expected.
DNS and networking:
# Confirm CoreDNS pods are running
kubectl get pods -n kube-system -l k8s-app=kube-dns
# Test DNS resolution from inside the cluster
kubectl run -it --rm dns-test --image=busybox --restart=Never -- \
nslookup kubernetes.default.svc.cluster.localIf DNS resolution fails, your CoreDNS or VPC CNI upgrade may have introduced a misconfiguration. Check pod logs for errors. See the EKS networking model for context on how the CNI and DNS interact.
Autoscaling:
Verify that the cluster-autoscaler and any HPA objects are working correctly. If HPA relies on metrics-server, confirm that kubectl top pods returns results.
Metrics and logging:
Check your monitoring dashboards and confirm that metrics are flowing correctly. Verify that your logging pipeline is still ingesting logs without errors.
Application smoke tests:
Run your application-level smoke tests against the upgraded cluster. A healthy cluster with healthy pods is necessary but not sufficient. Verify that actual application behaviour is correct end-to-end before declaring the upgrade successful.
Rollback decision point:
EKS does not support rolling back a control plane upgrade. If serious issues are found post-upgrade that cannot be resolved quickly, your fallback is either migrating traffic back to a pre-upgrade cluster (only viable if you took a blue/green approach) or fixing forward. This is why staging validation before upgrading production matters so much.
In-place upgrade vs blue/green cluster migration
An in-place upgrade modifies your existing cluster. A blue/green migration creates a new cluster running the target version and migrates your workloads to it. Both are valid approaches. The right choice depends on your workloads, risk tolerance, and how far behind you are.
| In-place upgrade | Blue/green migration | |
|---|---|---|
| Rollback speed | No rollback. Fix forward. | Fast: redirect traffic back to the old cluster |
| Complexity | Lower. Well-understood AWS operation. | Higher. Requires re-deploying workloads and migrating state. |
| Risk during migration | Node drain can disrupt workloads if PDBs are misconfigured | Migration complexity introduces its own risks |
| Cost during upgrade | No extra cost | Two clusters running simultaneously during migration |
| Best for | Most clusters; regular one-version cadence upgrades | Large version gaps, stateful workloads, low tolerance for risk |
Use in-place upgrades when:
- You are upgrading one minor version at a time on a regular cadence.
- Your workloads are stateless or can be safely drained and rescheduled.
- You have a staging cluster where you can validate the upgrade first.
- You have confidence in your PDB configuration and application resilience.
Use a blue/green migration when:
- You are multiple minor versions behind and need to close a large gap.
- You run stateful workloads that are difficult to drain without risk.
- Your organisation requires a tested rollback path rather than fix-forward.
- You are taking the opportunity to rearchitect the cluster: new networking model, new node configuration, private cluster setup, etc.
Blue/green gives you a rollback option, but the migration itself introduces new risks: re-deploying workloads, syncing state, and managing two clusters simultaneously. For teams that stay current with in-place upgrades, the complexity of a full migration often outweighs its benefits.
When to use this approach
The in-place upgrade workflow described on this page is appropriate for:
- Production or staging clusters that are one minor version behind the target.
- Teams that want a predictable, repeatable upgrade process rather than an ad-hoc procedure.
- Clusters running stateless web applications, APIs, and background jobs where pod rescheduling is safe.
It is less appropriate for clusters with complex stateful workloads with restrictive PDBs, or where you cannot tolerate any risk of a configuration change affecting traffic. In those cases, a blue/green migration gives you a clean rollback boundary and a verified new cluster before you commit to switching traffic.
Common mistakes
- Not checking for deprecated API usage before upgrading. A manifest using a removed API will fail to apply after the upgrade. You will not discover this until your next deployment, which may be in production. Run Pluto before every upgrade and fix all issues in staging first.
- Upgrading nodes before managed add-ons. Follow the required order: control plane, then add-ons, then nodes. Node components need to be compatible with both the control plane version and the add-on versions during the transition. Skipping ahead introduces version mismatch errors that are annoying to diagnose.
- Trusting old manifests without re-reading them. Manifests written for an older Kubernetes version may use deprecated field names, removed API versions, or default values that have changed. Review and test them as part of your staging validation. Do not assume they are fine because they worked before.
- Forgetting self-managed add-on compatibility. After upgrading the cluster, the AWS Load Balancer Controller, metrics-server, cluster-autoscaler, and other self-managed components may have bugs or missing support if left on an older version. Check each one’s compatibility matrix and upgrade where needed.
- Upgrading production without staging validation. The control plane upgrade is irreversible. Discovering a breaking change in production after the upgrade is a much worse outcome than finding it in staging. Treat staging validation as mandatory.
- Ignoring PDB drain failures. If a node drain fails during the node group upgrade, investigate the cause before forcing it. A forced eviction that overrides a PDB can silently degrade your application. Fix the PDB or the stuck pod, then let the drain proceed normally.
- Not validating ingress, DNS, autoscaling, and observability after the upgrade. Each of these subsystems can fail independently. An
ACTIVEcluster withReadynodes does not mean they are all working. Work through the full post-upgrade checklist before closing the window. - Assuming workloads are safe because the control plane is ACTIVE. The control plane status says nothing about whether your application endpoints are responding, whether DNS is resolving, or whether your autoscaler is making correct decisions. Validate each layer separately.
Summary
- EKS upgrades cover the control plane, managed add-ons, node groups, and self-managed components, each upgraded separately, in that order.
- You cannot skip minor versions. Plan one upgrade per version, and validate each one in staging before touching production.
- The control plane upgrade does not stop your pods. The main disruption risk is the node group upgrade, where pods are drained and rescheduled.
- Check for deprecated API usage with Pluto before starting any upgrade. Removed APIs cause deployment failures that surface after the upgrade, not during it.
- EKS does not support rolling back a control plane upgrade. Staging validation is your safety net.
- Use in-place upgrades for regular cadence upgrades. Use a blue/green cluster migration when you need a guaranteed rollback path or are closing a large version gap.
Frequently asked questions
Can I skip Kubernetes minor versions when upgrading EKS?
No. EKS only supports upgrading one minor version at a time. If you are on 1.27 and want to reach 1.30, you must upgrade to 1.28, then 1.29, then 1.30. Each step is a separate upgrade that takes 15–25 minutes and requires its own validation pass.
Will my applications go down during an EKS upgrade?
The control plane upgrade does not stop your pods. Applications keep serving traffic. The API server may be briefly less responsive, so kubectl commands can fail for a short period. The real disruption risk comes from the node group upgrade, where pods are drained and rescheduled. If your Deployments have enough replicas and your PodDisruptionBudgets are configured correctly, most workloads stay up throughout.
What is the safest order for upgrading control plane, add-ons, and nodes?
Always upgrade in this order: (1) control plane, (2) EKS managed add-ons, (3) node groups, (4) self-managed add-ons. The control plane must lead because nodes cannot run a newer Kubernetes version than the control plane. Add-ons are updated before nodes because they need to be compatible with both the new control plane and the existing nodes during the transition.
When should I use a blue/green cluster migration instead of an in-place upgrade?
Use a blue/green approach when you need a guaranteed rollback path, when the upgrade spans multiple minor versions, when your cluster runs stateful workloads that are difficult to drain safely, or when your organisation has a low tolerance for risk during the upgrade window. In-place upgrades are fine for most clusters, but large or highly stateful clusters often benefit from the clean separation of a new cluster.
How do I check whether my manifests use removed Kubernetes APIs?
Use Pluto, an open-source CLI tool, to scan your Helm releases and YAML directories. Run pluto detect-helm and pluto detect-files before starting the upgrade. Kubernetes removes old API versions in minor releases. Any manifest that references a removed API will fail to apply after the upgrade.