# GKE Node Pool Underutilized

> GKE node pools whose peak CPU stays under 20% and peak memory under 30% for the whole window are over-provisioned on both axes. ZopNight prices consolidation onto the next-smaller machine type in the same family from live SKU rates, though per-pool cost is deliberately zero today to avoid double-counting member VMs.

Source: https://zop.dev/integrations/gcp/recommendations/gke-node-pool-underutilized
Updated: 2026-08-19

---

## Peaks on both axes, not averages

An average hides the moment that matters. This rule gates on the *maximum* of both
allocatable-utilization series over the window: node CPU must peak under 20% and node
memory under 30% simultaneously. A pool that ever spiked past either ceiling is left
alone, because the spike is exactly the capacity claim a consolidation would break. Both
series are the kubernetes.io allocatable-utilization metrics, scaled to percent and read
per pool through `monitoring.timeSeries.list` (`roles/monitoring.viewer`).

## Stricter sibling of the CPU-only rule

RC-1228 downsizes pools on a 50% CPU average alone. This rule is its conservative subset:
both axes, peak-based, and much lower ceilings. What it buys with that strictness is
confidence that the pool is not merely CPU-quiet but genuinely small on every dimension.
That is the shape you get from a pool built for a workload that has since moved away. The lever
and the arithmetic match the sibling: the next-smaller machine type in the same GCE
family, priced as cost × (current rate − target rate) ÷ current rate from live catalog
rates, and abstaining whenever the ladder has no smaller rung or either rate is missing.

## Why pools carry no cost of their own

ZopNight prices GKE nodes as the individual Compute Engine VMs they are; giving the pool
a price too would count the same machines twice. Per-pool cost is therefore zero by
design in the calculated path, which keeps this rule's dollar figure gated off on live
fleets. Node savings surface today through the per-VM rightsizing rules instead, while
this detection logic stands ready should a per-pool cost basis land.

## Sample node utilization

```bash
gcloud container node-pools describe my-pool --cluster my-cluster \
  --location us-central1 --format="value(config.machineType,initialNodeCount)"
```

Pair the machine type with the Kubernetes Engine → Nodes utilization charts over 30 days
before acting.

## The consolidation path

Confirm aggregate pool pressure and PodDisruptionBudgets, enable the cluster autoscaler
so emptied nodes actually leave, and lower the pool minimum. The rule never emits on
missing metrics, an unparseable or floor machine type, or absent rates. Silence beats a
guessed downsize.
