Every Honcho pod on the tenant fleet is pinned to a custom ComputeClass —
a GKE Autopilot object that tells the autoscaler which machine types to create
nodes from, in what order. The classes are defined once in
argo-source/base_networking/templates/computeClass.yaml and selected per
workload with a nodeSelector.
This page is why the classes are shaped the way they are, and what to know before changing them.
The three classes
n4-n2d-any— general.n4thenn2d, any size.n4-n2d-highmem-110— prodapp-api.n4-highmem-8→n2d-highmem-8, 110 pods/node, on-demand.c3-n2-highmem-110-spot— prodapp-deriver.c3→n2→c2dhighmem-8 on spot, falling back to on-demandn4.
Why maxPodsPerNode: 110
A Honcho pod requests 63m CPU / 500Mi memory / 128Mi ephemeral. At 110 slots minus ~15 DaemonSet pods a node can hold 95 of them, so the binding constraint is memory, not CPU:
shape $/mo pods/node $/pod-month
n4-standard-8 @64 265 49 5.40
n4-standard-8 @110 265 53 5.00
n4-highmem-8 @110 348 95 3.66
highmem is $83/mo more per node. The saving comes only from needing fewer
of them, so it is a rounding effect — a cluster with fewer than ~110 pods would
be cheaper on standard-8.
Why the deriver runs on spot and the API does not
The deriver is a queue worker. It handles SIGTERM, and a work unit
abandoned by a hard kill is reclaimed by cleanup_stale_work_units after
STALE_SESSION_TIMEOUT_MINUTES. A preemption therefore costs one session a few
minutes of derivation latency — not data.
The API cannot tolerate that. A preemption mid-request is a 5xx to a
customer. It stays on on-demand n4.
That asymmetry is the whole design, and it is why the two workloads want different machine families.
Why c3 for spot and n4 for on-demand
Verified against the Cloud Billing catalog (service 6F81-5844-456A,
us-east4), highmem-8, $/node-month, August 2026:
- c3 — on-demand 386.34, spot 98.68 (74.5%) ← spot floor
- n2 — on-demand 430.83, spot 108.50 (74.8%)
- c2d — on-demand 402.73, spot 128.67 (68.1%)
- n4 — on-demand 347.54, spot 187.27 (46.1%) ← on-demand floor
- n2d — on-demand 374.83, spot 224.95 (40.0%)
n4 is the cheapest on-demand highmem-8 and nearly the worst on spot.
c3-highmem-8 is the same 8 vCPU / 64 GB geometry for 47% less on spot. So the
spot ladder is ordered strictly cheapest-first and its fallback floor is n4
on-demand, not c3 on-demand — n4 is $44/mo cheaper there.
Ordering priorities cheapest-first is also Google’s documented workaround for
the autoscaler comparing spot prices against a us-central1 baseline.
activeMigration is load-bearing
activeMigration:
optimizeRulePriority: trueWithout it GKE never moves a workload back up the ladder — a deriver that
fell back to on-demand n4 during a spot shortage would stay there
indefinitely. From Google’s docs: “If the activeMigration.optimizeRulePriority
field is set to false or omitted, GKE won’t automatically move workloads to
higher-priority nodes when they become available.”
Quota
Spot draws on PREEMPTIBLE_CPUS, a separate quota from your on-demand CPU
quota. In us-east4 it is 10,000 against ~0 prior usage; the fleet needs ~824
vCPU, which fits easily. Note the families are not interchangeable for quota
either — we hold 500 c3D vCPUs but effectively unlimited c3A.
What it looks like when it is working
A converged production cluster carries both classes side by side:
kubectl --context gke_plastic-labs-prod_us-east4_cluster-33 \
get nodes -L cloud.google.com/compute-class,cloud.google.com/gke-spot3 c3-n2-highmem-110-spot true <- derivers
3 n4-n2d-highmem-110 <- api
2 <- default pool (system)
Rolling out a change to the classes
Changing a nodeSelector in argo-source/base/templates/ recreates every
pod it selects, fleet-wide. The pattern that worked for the spot migration:
- Canary one cluster. Ship the class change behind an allowlist keyed on
$.Values.clusterset(the same shape as theprivateNodeAllowListstill inhoncho-deriver.yaml), containing a single cluster. - Watch it converge — nodes created, pods Running, restart counts flat. Give it long enough to see a preemption, not just a rollout.
- Widen to a handful, then remove the allowlist in a separate PR.
See also
- ArgoCD operations — what a fleet-wide rollout does to the control plane
- Infra — the cluster and IP plan
- GitOps pipeline — Autopilot’s resource-request rewriting under the
Defaultclass