This check flags GKE node pools running without automatic node upgrades. Without it, your nodes drift behind on Kubernetes and OS patches, leaving known CVEs exposed and risking control plane version skew. Fix it by enabling auto-upgrade on the node pool with gcloud container node-pools update --enable-autoupgrade.
Node auto-upgrade is one of those settings that quietly determines whether your cluster stays patched or slowly rots into a security liability. When a GKE node pool has auto-upgrade turned off, nobody is keeping the kubelet, container runtime, and underlying node OS current. Over weeks and months that gap turns into a real attack surface, and it also creates operational pain when the control plane moves on without the nodes.
The gke_noautoupgrade check looks at every node pool in your GKE clusters and reports any pool where automatic node upgrades are disabled.
What this check detects
GKE node pools have a management setting called auto-upgrade. When enabled, Google automatically upgrades the nodes in that pool to keep them aligned with the cluster control plane version. This covers the Kubernetes version on the kubelet plus the underlying Container-Optimized OS or Ubuntu node image, which includes kernel and package security patches.
The check inspects the management.autoUpgrade field on each node pool. If it reads false, the pool is flagged.
Note: Node auto-upgrade is separate from control plane upgrades. The GKE control plane (the API server, scheduler, and controller manager) is managed by Google and upgrades on its own schedule. Node upgrades are what this check covers, and they are the part you can accidentally leave disabled.
One important detail: if your node pool runs on a release channel (Rapid, Regular, or Stable), auto-upgrade is enabled and enforced automatically. The pools that fail this check are almost always on a static version, where Google leaves the upgrade decision to you and that decision defaults to never.
Why it matters
Disabling node upgrades does not break anything today. That is exactly why it is dangerous. The cost shows up later, and it compounds.
Unpatched CVEs on your nodes
Kubernetes and the Linux kernel both have a steady stream of security advisories. Container escape bugs, kubelet privilege escalations, and runc vulnerabilities all land on running nodes. A few real examples that drove urgent node upgrades:
- CVE-2022-0185, a Linux kernel heap overflow that allowed container escape to the host.
- CVE-2024-21626, a runc file descriptor leak enabling container breakout.
- Various kubelet and CRI flaws patched only by moving to a newer node version.
A node pool that has not upgraded in six months is sitting on every one of these. An attacker who lands a workload on that node, whether through a compromised image or an exposed application, has a known path to the host.
Version skew that breaks your cluster
Kubernetes supports a limited version skew between the control plane and the kubelet, generally two minor versions. The GKE control plane keeps moving forward on its own. If your nodes are frozen, eventually the skew exceeds the supported window. At that point Google may force an upgrade with little notice, or workloads can start behaving unpredictably because the kubelet is too old for the API server.
Warning: GKE will eventually auto-upgrade nodes that fall outside the supported skew window or reach end of life, regardless of your setting. A forced upgrade at a time you did not choose is far more disruptive than a scheduled one. Disabling auto-upgrade does not give you permanent control, it just removes the maintenance window.
Compliance and audit exposure
Most security frameworks (CIS GKE Benchmark, SOC 2, PCI DSS) expect patching to be timely and demonstrable. The CIS Google Kubernetes Engine Benchmark explicitly recommends enabling auto-upgrade. A node pool stuck on an old version is a finding waiting to happen in your next audit.
How to fix it
Enabling auto-upgrade on an existing node pool is a single configuration change. It does not immediately reboot or recreate your nodes, it just registers them for managed upgrades going forward.
Option 1: gcloud CLI
First, find which pools are affected:
gcloud container node-pools list \
--cluster=CLUSTER_NAME \
--region=REGION \
--format="table(name, management.autoUpgrade)"
Then enable auto-upgrade on the offending pool:
gcloud container node-pools update POOL_NAME \
--cluster=CLUSTER_NAME \
--region=REGION \
--enable-autoupgrade
Warning: Enabling auto-upgrade does not trigger an immediate upgrade, but a future upgrade will recreate nodes one at a time. This causes pod evictions. Make sure your workloads have proper PodDisruptionBudgets and that you have configured a maintenance window before nodes start cycling.
Option 2: Configure a maintenance window
Before letting upgrades run, control when they happen so they do not surprise you during peak traffic:
gcloud container clusters update CLUSTER_NAME \
--region=REGION \
--maintenance-window-start="2024-01-01T02:00:00Z" \
--maintenance-window-end="2024-01-01T06:00:00Z" \
--maintenance-window-recurrence="FREQ=WEEKLY;BYDAY=SA,SU"
You can also set maintenance exclusions to block upgrades during a code freeze or a high-traffic event.
Option 3: Move the pool to a release channel
The cleaner long-term fix is to enroll the cluster in a release channel, which enforces auto-upgrade and gives you a tested, predictable version progression. Regular is the common choice for production.
gcloud container clusters update CLUSTER_NAME \
--region=REGION \
--release-channel=regular
Note: Stable lags furthest behind and prioritizes proven versions, Regular is a balance, and Rapid gets new versions first. For most production workloads, Regular gives you timely patches without bleeding-edge risk.
Option 4: Terraform
If you manage GKE with Terraform, set auto_upgrade in the node pool management block. The key trap here is that the default is false when you define a node pool explicitly, so it is easy to ship this misconfiguration in IaC.
resource "google_container_node_pool" "primary_nodes" {
name = "primary-pool"
cluster = google_container_cluster.primary.id
location = "us-central1"
management {
auto_upgrade = true
auto_repair = true
}
# Prefer a release channel at the cluster level
}
resource "google_container_cluster" "primary" {
name = "production-cluster"
location = "us-central1"
release_channel {
channel = "REGULAR"
}
}
Tip: Enable auto_repair alongside auto_upgrade. Auto-repair detects unhealthy nodes and recreates them, which pairs naturally with keeping nodes patched. Both should be on for any production pool.
How to prevent it from happening again
Fixing the pools you have today is the easy part. Stopping the next one from shipping disabled is what keeps the check green.
Enforce it with Organization Policy
GCP has no single org policy constraint that forces node auto-upgrade directly, but you can enforce it through Policy Controller (the managed Gatekeeper distribution in GKE Enterprise) or with a custom constraint. Policy Controller can reject node pool configurations that set auto-upgrade to false.
Gate it in CI with policy-as-code
Catch the misconfiguration before it merges. Here is an OPA Conftest rule that fails any Terraform plan defining a node pool with auto-upgrade off:
package gke
deny[msg] {
resource := input.resource_changes[_]
resource.type == "google_container_node_pool"
management := resource.change.after.management[_]
management.auto_upgrade == false
msg := sprintf("Node pool '%s' must have auto_upgrade enabled", [resource.address])
}
Wire it into your pipeline against the Terraform plan output:
terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > tfplan.json
conftest test tfplan.json --policy policy/
Tip: Run the same check continuously, not just at deploy time. Lensix evaluates gke_noautoupgrade across all your clusters on an ongoing basis, so a pool created manually through the console or by another team still gets flagged even though it never went through your pipeline.
Best practices
- Default to release channels. Enrolling clusters in the Regular channel makes auto-upgrade the enforced default and removes the per-pool decision entirely.
- Always set a maintenance window. Upgrades will happen, so decide when. Pick low-traffic hours and keep them consistent.
- Use maintenance exclusions sparingly. They are right for a code freeze or a known traffic spike, but a permanent exclusion is just auto-upgrade disabled by another name.
- Define PodDisruptionBudgets for every workload. Node upgrades drain nodes. Without a PDB, an upgrade can take down all replicas of a service at once.
- Enable auto-repair too. Patched nodes and healthy nodes go together. Turn both on.
- Use surge upgrades for capacity. Configure surge settings so the pool can add temporary nodes during an upgrade rather than reducing available capacity.
gcloud container node-pools update POOL_NAME \
--cluster=CLUSTER_NAME \
--region=REGION \
--max-surge-upgrade=1 \
--max-unavailable-upgrade=0
Danger: Do not disable auto-upgrade as a way to "pin" a version indefinitely. Pinned nodes eventually hit end of life and get force-upgraded by Google at a time you do not control. If you need version stability, use the Stable release channel and maintenance exclusions, not a permanently disabled pool.
Node auto-upgrade is low-effort, high-leverage hygiene. Turn it on, set a maintenance window, give your workloads disruption budgets, and let Google keep your nodes patched while you focus on the things only your team can do.

