Karpenter versus the DevZero node autoscaler
How the DevZero node autoscaler (dzkarp) extends open source Karpenter with commitment-aware pricing, stable spot selection, and workload-aware consolidation.
Karpenter versus the DevZero node autoscaler
The DevZero node autoscaler, also called dzkarp, is the provisioning and consolidation engine inside the DevZero Node Operator. It's built on open source Karpenter and runs on Amazon Elastic Kubernetes Service (Amazon EKS), Azure Kubernetes Service (AKS), and Google Kubernetes Engine (GKE). It keeps Karpenter's APIs, so your NodePool and NodeClaim resources and your provider node classes (EC2NodeClass, AKSNodeClass, and GCENodeClass) work without changes.
On top of that base, dzkarp and the DevZero platform change which nodes get launched and when they get removed. New nodes use your unused Savings Plan commitment first. Spot types that get reclaimed less often rank higher. Consolidation accounts for how long your workloads take to restart, and pods can move to a new node with their in-memory state through live migration. This page explains each difference and how to configure it.
Who this page is for
This page is for platform and infrastructure engineers who run or are evaluating Karpenter and want to know what changes when they switch to DevZero. It assumes you know Karpenter concepts such as NodePools, consolidation, and disruption budgets. It doesn't cover installation. For setup steps, see the Node Operator overview.
Summary
The following table compares open source Karpenter with the DevZero node autoscaler. For features that DevZero is building, see In development.
| Capability | Open source Karpenter | DevZero node autoscaler |
|---|---|---|
Karpenter APIs (NodePool, NodeClaim, provider node classes) | Yes | Yes, unchanged |
| Savings Plans awareness (AWS) | No | Uses unused EC2 Instance Savings Plan commitment before paying on-demand rates |
| Spot selection | Ranks by price | Also ranks spot types by published interruption rates, refreshed daily |
| Runtime settings | Flags and environment variables, applied on restart | KarpenterSettings resource, applied live |
Pod-level do-not-disrupt override | No | Per-NodePool opt-out |
| CloudNativePG-aware consolidation | No | Consolidates nodes that hold a PostgreSQL primary when a healthy replica exists |
| Per-node metadata annotations | No | Image, network, identity, and disk details on every node, plus hourly price and pod IP capacity on AWS |
| Decision tracing | Logs only | Correlation IDs and lifecycle events for each decision |
| Startup-time-aware consolidation | No | Measures each workload's time to ready and tunes consolidation for slow starters |
| Schedule-based requirements | Disruption budgets only | Scheduled overrides for instance types, capacity type, limits, and disruption settings |
| Live migration | No, evicted pods restart | Checkpoints running pods with CRIU and restores them on the new node |
| Continuous workload resizing | No | Resizes container requests, limits, and replicas from observed usage |
| What-if simulation | No | Previews node count, cost, and risks of a policy change before you apply it |
| Node compaction | Consolidation for Karpenter nodes only | Placement-aware node removal for Karpenter and Cluster Autoscaler clusters |
| Scheduling health | Metrics only | Dashboard view of node startup, pending pods, and consolidation blockers |
Cloud support
Every dzkarp feature on this page works on Amazon EKS, AKS, and GKE, with two exceptions. Savings Plans awareness and the node price and pod IP capacity annotations depend on AWS billing and networking APIs, so they're available on Amazon EKS only.
The DevZero platform features later on this page, such as live migration, continuous resizing, scheduled overrides, and Simulate, run in the DevZero control plane and the Workload Operator, not in the node autoscaler.
The Node Operator also supports Oracle Kubernetes Engine (OKE), and DevZero node policies can manage OKE NodePool and OciNodeClass resources. The OKE Karpenter provider is built on a different Karpenter core than dzkarp, so the dzkarp features in the following sections, from Savings Plans awareness through decision tracing, aren't available on OKE. For setup steps, see OCI setup.
Compatibility with Karpenter
dzkarp tracks upstream Karpenter releases and keeps the upstream APIs and their defaults. DevZero adds fields and resources in the devzero.karpenter.sh API group, and every addition is optional. If you already run Karpenter, see Migrate from Karpenter for a zero-downtime switch.
Savings Plans awareness
Open source Karpenter prices every on-demand instance at its list price. If you've bought EC2 Instance Savings Plans, Karpenter doesn't know about them, so it can launch an instance family you aren't committed to while your commitment goes unused.
On Amazon EKS, dzkarp reads your active EC2 Instance Savings Plans for the cluster's region and calculates how much hourly commitment each instance family has left. It then ranks capacity in this order, from first choice to last:
- On-Demand Capacity Reservations (ODCRs)
- On-demand instances in a family with unused Savings Plan commitment
- Spot instances
- On-demand instances at list price
To keep simultaneous launches from overspending the commitment, dzkarp reserves commitment for each launch request before it calls EC2.
Savings Plans tracking works as follows:
- Savings Plans data refreshes every 5 minutes.
- dzkarp assigns commitment to every running instance in the account and region, including instances that Karpenter didn't launch, and plans only against the commitment that's left.
- Compute Savings Plans apply to any instance family, so dzkarp doesn't need to steer launches toward them.
- The
devzero.io/price-per-hournode annotation always shows the real billable rate.
The feature is always on and has no settings. The node autoscaler's IAM role needs savingsplans:DescribeSavingsPlans and savingsplans:DescribeSavingsPlanRates. The DevZero AWS CloudFormation template grants both.
Reserved Instance awareness is in development. dzkarp models EC2 Instance Savings Plans and ODCRs. Reserved Instances still get their discount from AWS billing, but dzkarp doesn't steer launches toward the families they cover.
Stable spot
Open source Karpenter picks spot capacity mostly by price. On AWS, EC2 Fleet's price-capacity-optimized strategy also favors pools with spare capacity at launch. On every cloud, Karpenter has no record of which instance types get reclaimed most often in your region.
dzkarp adds a stability ranking on Amazon EKS, AKS, and GKE. Every day, DevZero's control plane collects the spot interruption data that the cloud provider publishes for the cluster's region. The data measures the share of capacity reclaimed over the trailing 30 days. The control plane writes it to the cluster as a SpotStabilityData resource.
dzkarp sorts each spot type into one of five tiers and adds a penalty to the price it uses for ranking. The following table lists the tiers:
| Tier | Meaning | Ranking penalty |
|---|---|---|
| 0 | Most stable | 0% |
| 1 | More stable | 5% |
| 2 | Middle, also used for types with no data | 10% |
| 3 | Less stable | 20% |
| 4 | Least stable | 40% |
A stable instance type that costs slightly more wins over a cheaper type that gets reclaimed often, so your nodes see fewer interruptions and your pods restart less.
The penalty only affects ranking. It never removes an instance type from consideration, so spot launches are never blocked. If the stable types aren't available, dzkarp falls back to the normal Karpenter order. Cost reporting uses the real price, not the penalized one.
dzkarp turns off the ranking automatically when the data is older than 7 days or doesn't match the cluster's provider and region.
Stable spot is on by default and needs no setup. To turn it off, start the controller with --disable-spot-stability-pricing or set DISABLE_SPOT_STABILITY_PRICING=true.
Runtime settings
In open source Karpenter, feature gates and most tuning options are flags or environment variables. Changing one means editing the Helm release and restarting the controller.
dzkarp reads the same options from a cluster-scoped KarpenterSettings resource named default and applies changes without a restart. Helm values still provide the starting values. The resource's status shows the bootstrap values, the revision in effect, when it was applied, and the last error, if any.
The following example sets the log level, the batching windows, and three feature gates:
apiVersion: devzero.karpenter.sh/v1alpha1
kind: KarpenterSettings
metadata:
name: default
spec:
logLevel: info
batch:
maxDuration: 10s
idleDuration: 1s
featureGates:
spotToSpotConsolidation: true
nodeRepair: true
cnpgAwareConsolidation: falseYou can set these fields at runtime:
logLevelbatch.maxDurationandbatch.idleDurationscheduling.preferencePolicyandscheduling.minValuesPolicyignoreDRARequestsanddisableClusterStateObservability- Feature gates:
spotToSpotConsolidation,nodeRepair,nodeOverlay,reservedCapacity,staticCapacity,cnpgAwareConsolidation,capacityBuffer,terminateFirstDrift,terminateFirstRepair, andpodDeletionCostManagement
Consolidation controls
Pod-level do-not-disrupt override
Many Helm charts add the karpenter.sh/do-not-disrupt: "true" annotation to their pods. In open source Karpenter, one such pod keeps its node from being consolidated, so large, mostly empty nodes can keep running.
dzkarp adds spec.disruption.ignorePodDoNotDisrupt to the NodePool. When it's true, consolidation, drift, and drain ignore the pod-level annotation on that pool. PodDisruptionBudgets and the node-level do-not-disrupt annotation still apply.
The following NodePool turns on the override:
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: general
spec:
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
ignorePodDoNotDisrupt: trueThe default is false. Changing the field doesn't drift existing nodes. dzkarp emits a PodDoNotDisruptIgnored event each time it overrides the annotation, so you can audit the decisions.
CloudNativePG-aware consolidation
A CloudNativePG primary is usually protected by a PodDisruptionBudget, so open source Karpenter doesn't consolidate the node it runs on.
With the cnpgAwareConsolidation feature gate on, dzkarp can treat that node as a consolidation candidate. It does this only when every primary on the node belongs to a healthy cluster with a ready replica on another node that isn't being disrupted. The CloudNativePG operator handles failover as usual. The karpenter_cnpg_candidates_unblocked_total metric counts how many nodes the gate made eligible.
The gate is off by default. Turn it on in KarpenterSettings:
apiVersion: devzero.karpenter.sh/v1alpha1
kind: KarpenterSettings
metadata:
name: default
spec:
featureGates:
cnpgAwareConsolidation: trueNode visibility
dzkarp writes annotations to every node in the cluster, including nodes that Cluster Autoscaler or managed node pools create. DevZero uses them for cost reporting and for migrating node groups to NodePools, and you can read them with kubectl.
The following table lists examples of the metadata annotations on each cloud:
| Cloud | Example annotations | What they hold |
|---|---|---|
| Amazon EKS | devzero.io/aws-ami-id, devzero.io/aws-subnet-id, devzero.io/aws-iam-role | Image, subnet, security groups, and IAM role |
| AKS | devzero.io/azure-image-id, devzero.io/azure-subnet-id, devzero.io/azure-managed-identity, devzero.io/azure-os-disk-size-gb | Image, virtual network and subnet, network security groups, managed identity, and OS disk size |
| GKE | devzero.io/gcp-image, devzero.io/gcp-subnetwork, devzero.io/gcp-service-account, devzero.io/gcp-accelerators | Image, network and pod range, service account, boot disk, and attached GPUs |
On Amazon EKS, dzkarp also writes the following price and capacity annotations:
| Annotation | What it holds |
|---|---|
devzero.io/price-per-hour | The node's billable hourly price, refreshed hourly |
devzero.io/pod-ipv4-capacity, devzero.io/effective-max-pods | How many pods the CNI can assign IP addresses to on the node |
devzero.io/cni-mode | Detected CNI mode: VPC CNI with ENIs or prefix delegation, or Cilium ENI or cluster pool |
devzero.io/network-bandwidth-mbps | Instance network bandwidth |
The karpenter_node_capacity_pod_ipv4_capacity_max_pods_divergence metric flags nodes where the kubelet's max-pods value is higher than the number of IP addresses the CNI can assign. Pods on those nodes can get stuck in ContainerCreating.
Decision tracing
Open source Karpenter logs its decisions, but tying a pending pod to the node that was launched for it, or a terminated node to the disruption that caused it, means searching logs.
dzkarp stamps each decision with a correlation ID and emits a Kubernetes event at each lifecycle step:
- Annotations:
karpenter.sh/provisioning-id,karpenter.sh/disruption-provisioning-id,karpenter.sh/disruption-termination-id, andkarpenter.sh/spot-interrupt-termination-id - Events:
ProvisioningTriggered,ProvisioningLaunched,ProvisioningReady,DisruptionDecided,DisruptionReplacementLaunched,TerminationBegun, andTerminationSucceeded
Spot interruption events carry the interruption ID too. To trace a decision from start to finish, filter the output of kubectl get events by its ID.
Features from the DevZero platform
The preceding sections describe changes inside dzkarp. The DevZero control plane and the Workload Operator add the following features. They work together with dzkarp, and some of them, such as scheduled overrides and Simulate, also work on clusters that run open source Karpenter.
Startup-time-aware consolidation
Consolidation saves money by moving pods off underused nodes. Each move restarts those pods, though. For a service that takes 10 minutes to warm up, a small saving can cost a lot of capacity while the service restarts.
Open source Karpenter doesn't know how long your workloads take to start. DevZero measures each workload's time to ready, from p50 to p99.9, and uses it to tune consolidation for each NodePool:
- DevZero recommends the
Balancedpolicy for churn-sensitive pools. A pool is churn sensitive if it runs workloads that take 5 minutes or more to restart at p95, or if 30% or more of its capacity has no PodDisruptionBudget or runs workloads with volatile CPU use.Balancedapproves a move only when the savings outweigh the disruption. - DevZero suggests a
terminationGracePeriodbased on p95 startup time when that time is over 30 seconds. - DevZero suggests a
consolidateAftervalue from each pool's daily utilization troughs. The value is never below 10 minutes, and the suggestion only shortens the current wait.
Review and apply these suggestions on the node policy in the DevZero dashboard.
Per-move startup weighting is in development. With it, dzkarp reads startup time on each consolidation move and ranks candidate nodes by the restart time a move costs.
Scheduled overrides
Open source Karpenter supports schedules only for disruption budgets. The node requirements themselves are fixed until you edit the NodePool.
DevZero node policies support scheduled overrides. Each override is a time window that changes part of the policy while it's active. For example, you can allow only spot capacity on weekends, block large instance types overnight, or pause consolidation during a release window. The following table lists the fields of an override:
| Field | Description |
|---|---|
schedule | When the window starts, as a 5-field cron expression |
duration | How long the window lasts, from 1m to 720h |
timezone | IANA time zone name, UTC by default |
priority | Which override wins when two set the same field |
enabled | Turns the override on or off |
An override can change instance categories, families, sizes, generations, and types, as well as zones, architectures, capacity types, and operating systems. It can also change labels, taints, CPU and memory limits, and disruption settings such as consolidateAfter, expireAfter, budgets, and ignorePodDoNotDisrupt.
DevZero resolves the active policy every 60 seconds and records the active overrides on the NodePool in dakr.devzero.io/active-overrides. When a window ends, nodes return to the base policy through normal drift, paced by your disruption budgets. If the cluster loses its connection to DevZero, it keeps the last policy it received.
The node policy editor shows each schedule in plain language with a badge for the active or next window. A preview toggle shows the effective policy at the present time or at a time you pick.
Live migration
When open source Karpenter consolidates a node, or a spot node gets reclaimed, it evicts the pods and Kubernetes starts them again from scratch. The pods lose their in-memory state, caches, and warm-up progress.
DevZero can move those pods with live migration instead. The Workload Operator checkpoints the running process with Checkpoint/Restore In Userspace (CRIU), restores the checkpoint into the replacement pod on the target node, and keeps emptyDir contents. The application picks up where it stopped.
Live migration runs on node drains, pod evictions, preemption, spot interruptions, and resource changes. Deployments, StatefulSets, and DaemonSets use rolling checkpoints. For Pods, Jobs, CronJobs, ReplicaSets, Argo Rollouts, Volcano jobs, Spark applications, Kubeflow notebooks, and CloudNativePG clusters, the operator takes a checkpoint before it recreates them.
Before it migrates a pod, the operator checks that the running image matches the pod template. If a pod can't be migrated, it restarts normally.
To turn on live migration, do the following:
- In the DevZero dashboard, turn on checkpointing for the node policy. DevZero labels the policy's nodes with
dakr.devzero.io/checkpoint-node=true. - On the workload optimization policy, set
live_migration_enabled.
The node group status changes to ENABLED after DevZero confirms that CRIU works on its nodes.
Continuous workload resizing
Karpenter picks nodes from pod requests. If the requests are too high, Karpenter launches nodes that sit mostly idle, and consolidation can't fix that.
DevZero resizes workloads continuously. Its multidimensional pod autoscaler sets each container's CPU and memory requests and limits and the replica count in one pass, based on observed usage over a lookback window (7 days by default). The in-cluster operator also reacts within seconds to out-of-memory (OOM) kills and sustained CPU throttling.
DevZero applies changes through in-place vertical scaling on Kubernetes 1.33 and later, through live migration, or through a normal rollout. As requests shrink, pods fit on fewer nodes, and dzkarp consolidates the nodes that are freed.
Set the targeting, triggers (manual, on a schedule, or on detection), CPU and memory rules, change threshold, and spike protection on a workload optimization policy.
Simulation and compaction
Karpenter simulates scheduling before each consolidation move. It can't show you how your cluster looks under a different NodePool before you apply one.
DevZero Simulate shows you that result first. Every node group on the DevZero node analysis page, including Karpenter NodePools, Cluster Autoscaler groups, and DevZero node policies, has a Simulate button. It packs the group's running pods onto the instance types that your edited constraints allow, counts DaemonSet overhead, and shows:
- Node count and monthly cost, before and after
- Risk flags such as pods that can't be placed, low instance diversity, tight headroom, architecture changes, a changed spot mix, and Reserved Instance or Savings Plan coverage that a family change leaves unused
You can also simulate removing specific nodes. Simulate is read-only and never changes the cluster.
Node compaction removes underused nodes on both Karpenter and Cluster Autoscaler clusters. In the dashboard, you preview the plan for a compaction policy and run it on demand. Your DevZero team can also turn on automatic planning, which plans removals every 15 minutes. Plans account for affinity, topology spread, taints, and zonal volumes. The operator cordons and drains each node, respects PodDisruptionBudgets, and never force-deletes pods. Compaction policies support ignore rules, an underutilization threshold, and maintenance windows.
Scheduling health
Open source Karpenter exposes Prometheus metrics, and you build the dashboards. The Scheduling health view on the DevZero node analysis page answers four questions for Karpenter and Cluster Autoscaler clusters. The following table describes each section:
| Section | Question it answers |
|---|---|
| Node provisioning | How long do launched nodes take to become schedulable? Shows p50 and p95 time to ready, with a phase breakdown for each node. |
| Pod scheduling delay | How long do pods stay pending, and why? Shows pending time by reason and the workloads that wait longest. |
| Consolidation blocking factors | What is stopping the autoscaler from removing nodes? Lists blocked nodes with the PodDisruptionBudget, annotation, or placement problem behind each one. |
| Consolidation impact | What did consolidation remove, and which workloads paid for it? Shows nodes disrupted, pods moved, and drain times. |
In development
DevZero is building the following features. For timelines or early access, contact your DevZero team.
| Feature | What it adds |
|---|---|
| Reserved Instance awareness | Steers launches toward families that your Reserved Instances cover, the same way dzkarp handles Savings Plans |
| Per-move startup weighting | dzkarp ranks each consolidation move by the restart time it costs |