We are at re:Invent 2026Book my merch

Cloud Intelligence™

The GKE Feature Most Clusters Still Aren’t Using: Custom ComputeClasses

This page is also available in Deutsch, Español, Français, Italiano, 日本語, and Português.

Nov 13, 202611 min read
Chimbu Chinnadurai

About Chimbu Chinnadurai

Senior Cloud Architect II

I've probably debugged a Kubernetes issue in more time zones than I care to count. Based in London, I help engineering teams across EMEA get their clusters to behave — and actually understand why they misbehaved in the first place.

I write, speak, and guest on podcasts about all things cloud-native. Away from the terminal: I enjoy cooking almost as much as simplifying overly complex systems.

My personal page

I see the same pattern in almost every GKE cluster review I run: a long list of static node pools, each tied to a single machine type. An n2-standard-8 pool for general workloads, an n2-highmem-16 pool for the memory-hungry services, a Spot pool someone created during a cost push last year, and maybe a GPU pool. Workloads are scheduled on the nodes through nodeSelector, taints, tolerations and node affinity rules built up over years.

It holds together until something shifts, and it's usually one of three things.

  • Zonal stockouts: A zone runs short of your chosen machine series. Cluster Autoscaler can only scale the specific pools you gave it, leaving pods stuck in Pending even while neighboring machine families sit idle in the same zone.

  • Spot preemption traps: Heavy Spot preemptions spill workloads onto on-demand fallback pools. Once capacity recovers, nothing actively migrates workloads back, so the savings you planned never show up on the bill.

  • Generational upgrades: Moving from N2 to N4 or C4 becomes a multi-week operational slog: provisioning new pools, updating Helm values across dozens of repositories, draining old nodes, and hoping no manifests hardcoded old labels.

GKE has had an answer to all three for a while now: custom ComputeClasses. The customers I talk to either haven't heard of them or assume they're Autopilot only. They aren't. Custom ComputeClasses are available in both Autopilot and Standard mode from GKE 1.30.3-gke.1451000, and they're enabled by default in all eligible clusters.

What Is a Custom ComputeClass?

A ComputeClass is a Kubernetes custom resource (computeclasses.cloud.google.com) that specifies which node profiles workloads prefer, ranked by priority. As Google's documentation explains, it gives GKE a profile of node attributes to use during autoscaling events.

  • Traditional node pools: You declare the infrastructure first, then steer workloads onto it.

  • ComputeClasses: You declare target intent ("Try N4 Spot first; if unavailable, try C4 Spot; otherwise, fall back to N4 on-demand"), and the autoscaler provisions the right nodes automatically.

This keeps responsibilities separate: platform teams define hardware profiles, and application developers pick a profile by name.

What It Fixes in Practice

  • Stockouts become automated fallbacks: If a high-priority rule fails due to quota or zonal availability, GKE immediately attempts the next rule in the list.
  • Fallback capacity doesn't linger: With active migration enabled, GKE moves workloads back to higher-priority nodes once preferred capacity returns, reclaiming cost savings automatically.
  • Upgrades stay on the platform side: Shifting workloads from N2 to N4, or testing C4, means updating one ComputeClass manifest. Application teams never touch their selectors.

A few smaller things help as well. You can decide centrally which Compute Engine reservations get consumed first, tune how aggressively each class consolidates underutilized nodes, and set a default class for a namespace or the whole cluster so teams get sensible hardware without writing a selector.

Key Configuration Knobs

Under spec.priorities, each rule defines a primary hardware target: machineFamily (for example n4), machineType (for example n4-standard-32), gpu or tpu. For Standard clusters a rule can point at existing node pools with nodepools. Rules accept flags for spot, minCores, minMemoryGb, boot disk settings, local SSDs, secondary boot disks, reservations and zones.

  • whenUnsatisfiable: Controls behavior if all priority tiers fail. ScaleUpAnyway creates a node with the cluster default configuration, while DoNotScaleUp leaves Pods pending until a matching node can be created. DoNotScaleUp has been the default since GKE 1.33 (before that, it was ScaleUpAnyway), so set it explicitly to avoid surprises when clusters on different versions share the same manifests.

  • autoscalingPolicy: Configures scale-down sensitivity with consolidationDelayMinutes, a CPU and memory consolidationThreshold, and a separate gpuConsolidationThreshold.

  • activeMigration: Active migration has two switches.

    • optimizeRulePriority: true actively shifts pods back to higher-priority nodes as capacity frees up.
    • ensureAllDaemonSetPodsRunning: true replaces nodes that are too small to run all required DaemonSet Pods with larger ones.
  • nodePoolAutoCreation.enabled: true: Lets GKE create and delete node pools to satisfy your priorities. From GKE 1.33.3-gke.1136000, you no longer need cluster-level node auto-provisioning enabled for this.

Your existing pools still work. Add the cloud.google.com/compute-class label and a matching NoSchedule taint to a manually created pool and it joins the class, so you can adopt classes gradually.

You can also apply a ComputeClass by default to a namespace by labelling it, or to the whole cluster by deploying a class named default and enabling the feature on the cluster. GKE also ships built-in ComputeClasses, including autopilot, which lets Standard clusters run selected Pods on the Autopilot container-optimized compute platform.

Two Patterns to Get Started

Pattern 1: Replacing Static Spot Pools with a Resilient Fallback Chain

Here is a general-purpose compute class that prioritizes N4 Spot, falls back to C4 Spot, and uses N4 on-demand as a safety valve. It consolidates underutilized nodes after 5 minutes and migrates pods back up the chain when Spot capacity recovers:

apiVersion: cloud.google.com/v1
kind: ComputeClass
metadata:
name: general-cost-optimized
spec:
nodePoolAutoCreation:
enabled: true
priorities:
- machineFamily: n4
spot: true
minCores: 4
- machineFamily: c4
spot: true
minCores: 4
- machineFamily: n4
spot: false
activeMigration:
optimizeRulePriority: true
autoscalingPolicy:
consolidationDelayMinutes: 5
consolidationThreshold: 70
whenUnsatisfiable: DoNotScaleUp

With optimizeRulePriority: true, GKE moves Pods back to the preferred Spot rule once capacity returns, then removes the fallback node.

Workloads consume this class through a single node selector:

apiVersion: apps/v1
kind: Deployment
metadata:
name: checkout-api
spec:
replicas: 3
selector:
matchLabels:
app: checkout-api
template:
metadata:
labels:
app: checkout-api
spec:
nodeSelector:
cloud.google.com/compute-class: general-cost-optimized
containers:
- name: api
image: registry.k8s.io/pause
resources:
requests:
cpu: "1"
memory: "2Gi"

There's no cloud.google.com/gke-spot selector, no tolerations and no machine family affinity, and that's on purpose. GKE rejects Pods that select a ComputeClass and also select system labels that have a matching ComputeClass field, such as cloud.google.com/gke-spot, cloud.google.com/machine-family or node.kubernetes.io/instance-type. Those choices belong inside the class. If you're migrating existing workloads, search your manifests and Helm charts for these labels first.

Pattern 2: Layering Classes Over Existing Custom Pools

If you run Standard clusters with specialized, pre-warmed or reservation-backed pools, you don't need to delete them. Configure the class to use existing pools first and create new ones only when those can't scale:

apiVersion: cloud.google.com/v1
kind: ComputeClass
metadata:
name: hybrid-fallback-class
spec:
nodePoolAutoCreation:
enabled: true
priorities:
- nodepools: [core-workload-pool]
- machineFamily: n4
- machineFamily: c4

Label and taint your existing pool so the class recognizes it:

gcloud container node-pools update core-workload-pool \
--cluster=example-cluster \
--node-labels="cloud.google.com/compute-class=hybrid-fallback-class"
gcloud container node-pools update core-workload-pool \
--cluster=example-cluster \
--node-taints="cloud.google.com/compute-class=hybrid-fallback-class:NoSchedule"

Note that --node-labels and --node-taints replace all labels and taints on the pool, so include any existing ones when running those commands.

The best practices guide recommends the same ordering: manually created pools get the highest priority and auto-created pools are the fallback.

Cloud bill shouldn't be a mystery

One platform for AI and Cloud optimization.

Notable Recent Additions

The feature has moved quickly over the last few GKE minor versions. These are the changes I'd flag for anyone who looked at ComputeClasses a year ago and moved on.

Weighted Priority Scores (1.35.2-gke.1842000+)

Instead of relying only on list order, rules can define an explicit priorityScore from 1 to 1000. It's all or nothing: either every rule in the class has a score or none do. Up to three rules can share a score, and GKE evaluates tied rules together, letting Cluster Autoscaler choose between them.

apiVersion: cloud.google.com/v1
kind: ComputeClass
metadata:
name: priority-rule-score-class
spec:
nodePoolAutoCreation:
enabled: true
priorities:
- machineFamily: e2
spot: true
priorityScore: 30
- machineFamily: n2
spot: true
priorityScore: 20
- machineFamily: t2d
spot: true
priorityScore: 20
- machineFamily: n1
spot: true
priorityScore: 10
whenUnsatisfiable: ScaleUpAnyway

Here N2 and T2D Spot count as equally good second choices. Grouped rules can add autoscaling latency, more so for broad machineFamily rules than narrow machineType ones, because GKE has to run more simulations.

Automatic Storage Selection (1.35.3-gke.1290000+)

Mixing machine generations used to carry a storage risk, because each series supports specific disk types (for example, N4 supports Hyperdisk, while N2 relies on standard PDs). You can now use a StorageClass with automated disk type selection, and GKE creates a disk type that works with whichever machine type the Pod lands on. That makes multi-generation fallback chains much safer for workloads that use dynamic provisioning. Pre-existing disks still need a disk type that every machine series in the class supports.

Custom Machine Shapes (1.33.2-gke.1111000+)

You can define custom shapes such as n4-custom-8-20480 directly in machineType, for workloads whose CPU-to-memory ratio doesn't fit the predefined shapes.

Boot Disk Settings on machineFamily Rules (1.34.1-gke.1431000+)

Define storage.bootDiskType and storage.bootDiskSize on flexible machineFamily rules instead of only on specific machineType rules:

priorities:
- machineFamily: n4
spot: true
minCores: 16
minMemoryGb: 64
storage:
bootDiskType: hyperdisk-balanced
bootDiskSize: 100

Labels and Taints in the Class (1.33.2 to 1.34.1)

Declare nodeLabels and taints per priority rule (labels from 1.33.2-gke.1111000, taints from 1.33.4-gke.1350000) or globally under nodePoolConfig (from 1.34.1-gke.3084002). Rule-level and global values must not overlap. This keeps labelling and isolation in the same object as the hardware decision.

Default Classes for System Workloads (1.33.1-gke.1788000+)

Namespaces can get a default class that applies only to non-DaemonSet Pods, using the cloud.google.com/default-compute-class-non-daemonset label. GKE uses this to run system Pods in namespaces such as kube-system and gmp-system separately from your workloads, while DaemonSet Pods aren't given the class selector.

Autopilot Compute Alongside a Custom Default (1.34.1-gke.1829001+)

If you set a custom class as the cluster default in an Autopilot cluster, Pods stop running on the Autopilot container-optimized platform by default. You can now run Autopilot compute platform for specific Pods by adding the cloud.google.com/compute-class: autopilot node selector to those Pods.

A Few Smaller Additions

priorityDefaults (1.32.1-gke.1729000) lets you set shared values once instead of repeating them in every rule. nodeSystemConfig brings kubelet and sysctl tuning into the class. Compact placement and workload policies are available for Autopilot from 1.33.2-gke.1335000.

Production Caveats and Edge Cases

ComputeClasses have limits, and you want to find them in a design review, not in production.

  • kube-scheduler doesn't know about priority rules: ComputeClasses influence autoscaling, but kube-scheduler doesn't consider them. If fallback nodes already have capacity, new Pods can land on them ahead of preferred nodes. Enabling activeMigration.optimizeRulePriority replaces those fallback nodes over time, and a tighter consolidationThreshold helps underutilized fallback nodes drain sooner.

  • Changes are forward-looking only: Editing a ComputeClass doesn't alter running nodes. Cordon and drain existing nodes during a maintenance window so they get replaced under the new rules.

  • Keep stateful workloads off active migration: Active migration evicts Pods and doesn't move data. The best practices guide says to disable it for classes used by stateful workloads. Put those workloads in a dedicated class, and either use automated disk type selection or limit the class to machine series that all support your disk type.

  • Namespace defaults don't migrate existing Pods: GKE injects the class selector only into Pods created after you label the namespace, so already-running Pods stay untouched. Restart workloads in the namespace after setting a default so recreated Pods pick up the selector and active migration. A cluster-level default can migrate existing Pods directly.

  • Watch out for high Pod churn: In clusters with very high Pod creation and deletion rates, Cluster Autoscaler might not evaluate lower rules before the backoff on higher rules expires, and Pods stay in Pending. If you hit this edge case, reduce consolidation frequency or disable active migration.

  • Reservation fallback behavior: With AnyBestEffort, if no matching reservation has capacity, GKE tries an on-demand node for that same rule before moving to the next rule. To drop straight to your next rule when reserved capacity is full, use affinity: Specific (for named reservations) or affinity: AnyThenFail.

Smaller Constraints

A workload can select only one ComputeClass, and each manually created node pool can belong to only one. On Autopilot, bootDiskType only supports pd-balanced. Class names can't start with gke or autopilot. Many of the features above need recent patch versions, so check your release channel before you design around them.

Conclusion

If your clusters still run on fixed node pools tied to machine types, start with one stateless namespace. Give it a Spot-first fallback chain, then watch how often Pods land on the fallback rules and whether active migration moves them back.

Keep in mind that a ComputeClass decides which kind of node to create, but node size depends on your Pod resource requests. For machineFamily rules, Cluster Autoscaler picks a machine size based on the aggregate requests of pending Pods. Oversized requests mean larger machines, poorer bin packing and a bigger fallback footprint.

That's where rightsizing pays off. PerfectScale continuously rightsizes workload requests, so the nodes your ComputeClasses create match what your Pods actually use. Its Node Recommendations feature helps you pick the most cost-effective machine families to put in your priority rules. If you're auditing cluster spend and want to see how these fallback setups perform in practice, book a demo with the team.

References