Commitments in Karpenter Enterprise

Cast AI automatically puts AWS commitments — Reserved Instances and Savings Plans — to work in Karpenter Enterprise clusters. Karpenter has no native understanding of commitments, so it may provision full-price on-demand or spot capacity while discounted committed capacity sits unused. Cast AI closes this gap by creating high-weight shadow NodePools that Karpenter prefers over regular NodePools, steering provisioning toward committed instance types.

The feature is fully managed: assign commitments to a Karpenter cluster in the Cast AI console, and Cast AI handles the rest — syncing, matching, limit rebalancing, and cleanup.

📘

Note

This feature is specific to Karpenter Enterprise clusters managed by Kentroller. With the standard Cast AI Autoscaler, commitments are consumed automatically — see Commitments.

Prerequisites

Commitment budgets need two things in place:

  1. Kentroller v0.1.162 or later — the minimum version with Savings Plan support. Older Kentroller versions receive only Reserved Instance budgets; Savings Plan budgets are omitted until the upgrade.
  2. The CommitmentBudgets controller enabled — on by default in the Kentroller Helm chart (castai.commitmentBudgets.enabled: true), so no action is needed unless it was explicitly turned off.
  3. Commitments must be assigned to the cluster through the Cast AI Commitments page. Until at least one commitment is assigned, no CommitmentBudget resources are synced and no shadow NodePools are created.

To enable (or re-enable) the controller in Helm, most Karpenter installations use the castai-umbrella chart with the kent profile, which bundles Kentroller as a subchart. Kentroller values are passed under the castai-kentroller key:

castai-kentroller:
  castai:
    commitmentBudgets:
      enabled: true

If you installed the standalone castai-kentroller chart, set the value at the top level:

castai:
  commitmentBudgets:
    enabled: true

How it works

The flow has two stages: Cast AI syncs the assigned commitments into the cluster, and Kentroller turns them into shadow NodePools.

1. Assign commitments in the Cast AI platform

  1. Cast AI reads the cluster's assigned commitments.
  2. Assigned commitments are converted to CommitmentBudget CRDs with the appropriate type (RI, Compute SP, or EC2 Instance SP). Multiple commitments may be merged into a single budget.
  3. For Savings Plans, Cast AI fetches per-family CPU pricing from the AWS pricing API and embeds it in the CRD spec.
  4. CRDs are synced to the cluster automatically — created, updated, or deleted as commitments change.
  5. Cast AI resyncs periodically, and immediately when a commitment is added, removed, or reassigned.

2. Kentroller creates shadow NodePools

Kentroller watches CommitmentBudget CRDs and reconciles them into shadow NodePools:

  1. Matches budgets to NodePools — For each synced budget, which may represent one or more merged commitments, Kentroller finds regular NodePools whose scheduling requirements are compatible with the budget's instance type or family constraints. Compatibility is validated against AWS instance inventory to ensure at least one real instance type satisfies both the NodePool's and the budget's constraints.
  2. Creates shadow NodePools — For each matching budget–NodePool pair, Kentroller creates a dedicated shadow NodePool with weight: 100 and a zzz- name prefix. Karpenter's equal-weight tiebreaker picks the lexicographically highest name, so these pools are preferred even over NodePools that also set weight: 100.
  3. Rebalances limits — Every 2 minutes, Kentroller redistributes the budget's limits across matched NodePools proportionally to their observed usage. For zonal RIs, usage is measured in node count; for regional RIs and Savings Plans, usage is measured in CPU millicores.
  4. Drains and deletes on removal — When a budget is removed because its commitments were unassigned or deleted in the platform, or the feature is disabled, Kentroller drains the shadow NodePools by setting all limits to 0, waits for nodes to reach 0, then deletes the NodePools.

Why only spot-allowing NodePools are matched

Shadow NodePools always run on-demand capacity. They add value by replacing spot instances with committed on-demand instances that cost less. A NodePool that only allows on-demand already provisions committed instances through normal Karpenter scheduling, so creating a shadow pool for it would add no benefit.

Supported commitment types

Cast AI supports all three AWS commitment types — Reserved Instances (in zonal and regional variants), Compute Savings Plans, and EC2 Instance Savings Plans:

Typespec.type valueScopeLimit unitCRD spec field
Zonal Reserved Instancereserved-instanceSpecific instance type + AZNode countspec.ri (with availabilityZone)
Regional Reserved Instancereserved-instanceInstance family, region-wideCPU + memoryspec.ri (without availabilityZone)
Compute Savings Plancompute-savings-planRegion-wide (all instance families)$/hr commitmentspec.computeSP
EC2 Instance Savings Planec2-instance-savings-planSingle instance family$/hr commitmentspec.ec2InstanceSP

How monetary commitments become CPU limits

For Compute Savings Plans and EC2 Instance Savings Plans, Kentroller converts the hourly dollar commitment into CPU millicores using the per-vCPU price:

  1. The total available CPU (in millicores) = floor(commitmentAmount / pricePerCPU × 1000) — the result is rounded down to a whole millicore
  2. This total is distributed across matched NodePools proportionally to their CPU usage, but every matched pool is guaranteed at least 1000 millicores (1 vCPU) so it can still accept workloads. A commitment too small to grant 1 vCPU to any pool is reported as InsufficientCommitment
  3. Each pool's CPU allocation is set as a ResourceCPU limit on the shadow NodePool

For EC2 Instance Savings Plans, the pricePerCPU is embedded in the CRD spec by Cast AI. For Compute Savings Plans, Cast AI embeds per-family pricing in the CRD, and Kentroller selects the most expensive valid pricePerCPU among the instance families supported by each NodePool, ensuring the allocation is conservative.

NodePool filtering

Not all NodePools need commitment coverage. Kentroller supports two filtering modes controlled by Helm configuration:

Opt-out mode (default)

All NodePools are included unless explicitly excluded. To exclude a NodePool:

apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: my-nodepool
  annotations:
    kentroller.cast.ai/commitment-nodepool-clone: "exclude"

Opt-in mode

Only NodePools explicitly annotated as included are considered:

apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: my-nodepool
  annotations:
    kentroller.cast.ai/commitment-nodepool-clone: "include"

Shadow NodePool behavior

Shadow NodePools created by Kentroller have the following characteristics:

PropertyValueReason
Name formatzzz-castai-budget-{budgetName}-{sourceNPName}Sorts last alphabetically; Karpenter's tiebreaker prefers it
Compute SP name formatzzz-castai-sp-{sourceNPName}All Compute Savings Plans aggregate into one pool per source NodePool
Name length limit63 bytesKarpenter uses the NodePool name as a label value on NodeClaims
Weight100Matches the maximum weight Karpenter allows; zzz- prefix wins the tiebreaker
Capacity typeOn-demand onlyShadow pools replace spot with committed on-demand
TaintsInherited from source NodePoolWorkloads scheduling onto shadow pools must tolerate the same taints
NodeClassInherited from source NodePoolSame EC2NodeClass as the source
DisruptionInherited from source NodePoolConsolidation policy and budgets copied from source
ConsolidateAfter5m default if source doesn't specifyPrevents "Never" (zero-value) from disabling consolidation

Labels on shadow NodePools

LabelExample valuePurpose
kentroller.cast.ai/managed-bycommitmentbudgetsIdentifies the NodePool as managed by the commitment budgets controller
kentroller.cast.ai/budget-idri-zonalLinks the NodePool to its source CommitmentBudget
kentroller.cast.ai/source-nodepoolmy-app-poolLinks to the source NodePool this pool was derived from
kentroller.cast.ai/budget-typereserved-instanceThe commitment type that created this pool

Enable or disable the feature

Commitment support is controlled at two levels:

  • Helm value (castai.commitmentBudgets.enabled): set at install or upgrade time. To disable the controller, set it to false and redeploy — Kentroller then performs a one-time cleanup of any existing shadow NodePools and stays inert. See Prerequisites for the Helm value.
  • Cluster feature flag: toggled dynamically by Cast AI through the cluster's feature configuration, without restarting or redeploying Kentroller. When it is turned off, Kentroller drains and deletes existing shadow NodePools, then goes idle. Contact Cast AI support to have this flag toggled on a cluster.

Inspecting commitments on the cluster

Since Cast AI syncs commitments automatically, the resulting CommitmentBudget resources can be inspected with kubectl:

kubectl get commitmentbudgets
NAME          TYPE                   INSTANCE TYPE   RI COUNT   RI FAMILY   COMMITMENT   MATCHED NODEPOOLS   COMPATIBLE   AGE
ri-zonal      reserved-instance      m5.xlarge       3                                   2                   Compatible   5m
ri-regional   reserved-instance                                 m5                       2                   Compatible   5m
sp-compute    compute-savings-plan                                          1.50         3                   Compatible   5m

Each CommitmentBudget reports its compatibility state through the CompatibleNodePools condition and the matchedNodePools status field. Use the condition reason to troubleshoot commitments that don't produce shadow NodePools:

Condition statusReasonMeaning
TrueCompatibleMatched N regular NodePool(s); shadow NodePools created
FalseIncompatibleNo regular NodePool matches this budget's instance type/family constraints
FalseInsufficientCommitmentCommitment amount is too small to allocate any CPU above the minimum vCPU threshold
FalseInvalidPricingcommitmentAmount or pricePerCPU is missing, zero, negative, or non-numeric
FalseNoCustomerNodePoolsNo regular NodePools exist in the cluster

CommitmentBudget CRD reference

CommitmentBudget resources are synced to the cluster by Cast AI. The reference below documents the CRD structure for observability and troubleshooting.

FieldTypeDescription
spec.budgetIDstringUnique identifier for the budget; one budget may span multiple merged commitments
spec.typeenumOne of reserved-instance, compute-savings-plan, ec2-instance-savings-plan
spec.ri.availabilityZonestringAZ for zonal RI; omitted for regional RI
spec.ri.instanceTypestringExact instance type for zonal RI (e.g. m5.xlarge)
spec.ri.countintegerNumber of reserved nodes (zonal RI)
spec.ri.instanceFamilystringInstance family for regional RI (e.g. m5)
spec.ri.cpuQuantityTotal CPU for the commitment (e.g. "40")
spec.ri.memoryQuantityTotal memory (e.g. "96Gi")
spec.computeSP.offeringIdstringAWS offering ID
spec.computeSP.commitmentAmountstringHourly commitment in USD (e.g. "1.50")
spec.computeSP.familyCPUPricesmapPer-family pricing: family → pricePerCPU + instanceType
spec.ec2InstanceSP.instanceFamilystringInstance family locked by this SP
spec.ec2InstanceSP.offeringIdstringAWS offering ID
spec.ec2InstanceSP.commitmentAmountstringHourly commitment in USD
spec.ec2InstanceSP.pricePerCPUstringCheapest per-vCPU price for the family
status.matchedNodePoolsintegerNumber of regular NodePools matched
status.conditions[]ConditionCompatibleNodePools condition with status and reason

Resource details: CommitmentBudget (short name: cb), cluster-scoped, API group autoscaling.cast.ai/v1alpha.

Related resources


Did this page help you?