GKE node auto-provisioning (ComputeClass) over Karpenter on GCP
Status: Accepted Date: 2026-08-18 Deciders: Platform Team Related Design: GCP Support — Dual-Cloud Platform Design
Context
On AWS the platform autoscales nodes with Karpenter, expressed as three pairs of manifests
in infrastructure/base/karpenter-nodepools/ and infrastructure/base/karpenter-nodepools-gpu/:
| Purpose | Manifests |
|---|---|
| general workloads | default-nodepool.yaml + default-ec2nc.yaml |
| IO-heavy workloads | io-nodepool.yaml + io-ec2nc.yaml |
| GPU (L4) workloads | gpu-l4-nodepool.yaml + gpu-l4-ec2nc.yaml |
Adding GCP requires an equivalent. Because ADR-0005
runs self-managed Cilium, whatever provisions nodes must be able to place
node.cilium.io/agent-not-ready=true:NoSchedule on every node it creates, including nodes
created seconds before a pending pod is scheduled. If it cannot, pods land on nodes with no
working CNI — an intermittent failure that looks like a Cilium bug and is painful to diagnose.
Decision Drivers
- Production readiness — dual-cloud means both implementations are maintained and relied on.
- Cilium taint propagation to autoscaled nodes — a hard requirement from ADR-0005, not a nicety.
- Node OS control — Cilium has kernel requirements, so the image must be pinnable.
- Conceptual distance from Karpenter — how much of the mental model transfers.
- GPU support — the LLM platform needs L4-class accelerators.
Considered Options
Option 1: GKE node auto-provisioning via ComputeClass
The modern NAP interface: a ComputeClass custom resource with nodePoolAutoCreation enabled
(GKE >= 1.33.3-gke.1136000), declaring an ordered priorities[] list of machine shapes.
Pros:
- Production-ready, Google-supported, and the blessed autoscaling path on GKE.
nodePoolConfig.taints[]applies taints to auto-created node pools, which satisfies the Ciliumagent-not-readyrequirement declaratively.nodePoolConfig.imageTypepinscos_containerd/ubuntu_containerdfor Cilium’s kernel requirements.priorities[]carriesmachineFamily,machineType,spot,gpu,storage.bootDiskTypeandnodeSystemConfig— a close conceptual match to Karpenter requirements plusEC2NodeClassfields, including ordered fallback (spot first, then on-demand).- Collapses six manifests into three
ComputeClassobjects.
Cons:
- No equivalent to Karpenter’s
disruption/ consolidation semantics. Bin-packing and node-replacement behaviour is the GKE autoscaler’s, not ours to tune the same way. - Cannot set a minimum node count above zero on an auto-created pool, by design — a non-zero minimum would prevent removal of empty pools.
- A GCP-only API: the manifests are not shared with AWS, they are a sibling.
Option 2: karpenter-provider-gcp
A community Karpenter provider for GCP, initiated and primarily developed by CloudPilot AI.
Pros:
- Same API as AWS (
NodePool+ a GCP node class), so one mental model and potentially shared manifest structure. - Karpenter’s consolidation and disruption budgets carry over.
Cons:
- Preview / alpha, and explicitly not recommended for production. Not in
kubernetes-sigs. - Disqualifying for a cloud that is meant to be maintained in parallel with AWS, not a demo.
- Would make the platform’s GCP node lifecycle depend on a single-vendor pre-1.0 controller.
- Compare Azure, where
karpenter-provider-azurereached GA and backs AKS NAP; GCP has no equivalent maturity.
Option 3: Static managed node pools with cluster autoscaler
Fixed node pools, scaled by the standard cluster autoscaler.
Pros:
- Simplest, most predictable, fully supported. Taints and image type are set on the pool.
- No new API to learn.
Cons:
- Requires enumerating machine shapes up front; no automatic shape selection.
- Loses the workload-driven provisioning that makes Karpenter valuable on the AWS side, so the two clouds’ capabilities diverge in a way users would notice.
- Poor fit for the GPU/LLM workloads, which are bursty and shape-specific.
Decision Outcome
Chosen option: “Option 1 — GKE node auto-provisioning via ComputeClass”
Rationale: Option 2 is the only option that would have preserved a single autoscaling API across both clouds, and it is disqualified by maturity alone — a preview-grade, single-vendor controller cannot sit under a cloud the platform claims to maintain. Between the remaining two, ComputeClass keeps workload-driven provisioning (the property that made Karpenter worth having) while clearing the two hard constraints ADR-0005 imposes: it can taint auto-created pools and it can pin the node image. Option 3 remains the fallback if ComputeClass taint propagation turns out to be unreliable in practice, which the autoscaling slice tests directly.
Note that Option 3 is not wasted work either way: the foundation slice brings up the cluster with a single
static tainted node pool, exactly as eks/init creates managed node groups before Karpenter
arrives. ComputeClass is layered on afterwards, mirroring the AWS sequence.
Consequences
Positive
- Blessed, supported autoscaling on GCP, with GPU and Spot support.
- Three
ComputeClassmanifests replace six Karpenter manifests. - Cilium’s readiness taint is handled declaratively at the pool level rather than by a post-provisioning hook.
Negative
- Consolidation semantics are not portable. Karpenter’s
disruptionblock, consolidation policy and disruption budgets have no ComputeClass equivalent; cost/packing behaviour will differ measurably between the two clouds.- Mitigation: the autoscaling slice ships an explicit written statement of the gap rather than leaving it to be discovered. This is a documented divergence, not an abstraction to be faked.
- No
min > 0per auto-created pool, so “always keep N warm” must be expressed differently (for example a small static pool alongside the auto-created ones). - Karpenter knowledge does not transfer cleanly; operators need to learn a second model.
Neutral
infrastructure/base/runtimeclass-nvidia/is AWS-only and is not ported. It exists because the Bottlerocket NVIDIA AMI pre-configures annvidiacontainerd runtime handler and advertisesnvidia.com/gpunatively, so aRuntimeClassis needed but a device plugin is not. GKE installs drivers through its own managed installer and advertisesnvidia.com/gpuwithout aRuntimeClass, so GCP needs a different and smaller GPU shim.
Implementation Notes
Each ComputeClass sets nodePoolConfig.taints[] to include
node.cilium.io/agent-not-ready=true:NoSchedule and pins nodePoolConfig.imageType.
priorities[] is ordered spot-first then on-demand, mirroring the capacity-type preference in
the existing Karpenter NodePools.
The load-bearing test is not “does it scale” but “does a freshly auto-created node come up carrying the taint, and does Cilium clear it before any workload pod is scheduled there”. The design makes that a success criterion.
References
- About node pool auto-creation (GKE)
- Configure node pool auto-creation (GKE)
- ComputeClass CRD reference —
nodePoolConfig.taints[],nodePoolConfig.imageType,priorities[] - cloudpilot-ai/karpenter-provider-gcp — preview status
- ADR-0005 — the self-managed Cilium decision that imposes the taint requirement
- Existing Karpenter manifests:
infrastructure/base/karpenter-nodepools/,infrastructure/base/karpenter-nodepools-gpu/