Node Policies
Configure node provisioning policies for Karpenter-based autoscaling.
Node Policies
NodePolicy configures dzkarp-based node provisioning rules — instance types, capacity types, disruption behaviour, and cloud-provider-specific settings. NodePolicyTarget attaches a node policy to one or more clusters.
Node policies manage Karpenter NodePool and NodeClass resources. Ensure Karpenter is installed on your target clusters before attaching node policies.
NodePolicy
Example
import { resources } from "@devzero/pulumi-devzero";
const nodePolicy = new resources.NodePolicy("standard-nodes", {
name: "standard-nodes",
description: "On-demand x86 nodes for general workloads",
weight: 10,
capacityTypes: { operator: "In", values: ["on-demand"] },
instanceCategories: { operator: "In", values: ["m", "c"] },
instanceSizes: { operator: "In", values: ["large", "xlarge", "2xlarge"] },
architectures: { operator: "In", values: ["amd64"] },
operatingSystems: { operator: "In", values: ["linux"] },
disruption: {
consolidationPolicy: "WhenEmptyOrUnderutilized",
consolidateAfter: "30m",
expireAfter: "168h",
},
aws: {
amiFamily: "AL2",
role: "KarpenterNodeRole",
subnetSelectorTerms: [{ tags: { "karpenter.sh/discovery": "my-cluster" } }],
securityGroupSelectorTerms: [{ tags: { "karpenter.sh/discovery": "my-cluster" } }],
},
});from pulumi_devzero.resources import NodePolicy, NodePolicyArgs
from pulumi_devzero.resources.types import (
LabelSelectorArgs,
DisruptionPolicyArgs,
AWSNodeClassSpecArgs,
SubnetSelectorTermArgs,
SecurityGroupSelectorTermArgs,
)
node_policy = NodePolicy("standard-nodes", args=NodePolicyArgs(
name="standard-nodes",
description="On-demand x86 nodes for general workloads",
weight=10,
capacity_types=LabelSelectorArgs(operator="In", values=["on-demand"]),
instance_categories=LabelSelectorArgs(operator="In", values=["m", "c"]),
instance_sizes=LabelSelectorArgs(operator="In", values=["large", "xlarge", "2xlarge"]),
architectures=LabelSelectorArgs(operator="In", values=["amd64"]),
operating_systems=LabelSelectorArgs(operator="In", values=["linux"]),
disruption=DisruptionPolicyArgs(
consolidation_policy="WhenEmptyOrUnderutilized",
consolidate_after="30m",
expire_after="168h",
),
aws=AWSNodeClassSpecArgs(
ami_family="AL2",
role="KarpenterNodeRole",
subnet_selector_terms=[SubnetSelectorTermArgs(tags={"karpenter.sh/discovery": "my-cluster"})],
security_group_selector_terms=[SecurityGroupSelectorTermArgs(tags={"karpenter.sh/discovery": "my-cluster"})],
),
))package main
import (
"github.com/devzero-inc/pulumi-provider-devzero/sdk/go/devzero/resources"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
nodePolicy, err := resources.NewNodePolicy(ctx, "standard-nodes", &resources.NodePolicyArgs{
Name: pulumi.String("standard-nodes"),
Description: pulumi.StringPtr("On-demand x86 nodes for general workloads"),
Weight: pulumi.IntPtr(10),
CapacityTypes: &resources.LabelSelectorArgs{
Operator: pulumi.String("In"),
Values: pulumi.StringArray{pulumi.String("on-demand")},
},
InstanceCategories: &resources.LabelSelectorArgs{
Operator: pulumi.String("In"),
Values: pulumi.StringArray{pulumi.String("m"), pulumi.String("c")},
},
InstanceSizes: &resources.LabelSelectorArgs{
Operator: pulumi.String("In"),
Values: pulumi.StringArray{pulumi.String("large"), pulumi.String("xlarge"), pulumi.String("2xlarge")},
},
Architectures: &resources.LabelSelectorArgs{
Operator: pulumi.String("In"),
Values: pulumi.StringArray{pulumi.String("amd64")},
},
OperatingSystems: &resources.LabelSelectorArgs{
Operator: pulumi.String("In"),
Values: pulumi.StringArray{pulumi.String("linux")},
},
Disruption: &resources.DisruptionPolicyArgs{
ConsolidationPolicy: pulumi.StringPtr("WhenUnderutilized"),
ConsolidateAfter: pulumi.StringPtr("30s"),
ExpireAfter: pulumi.StringPtr("720h"),
},
Aws: &resources.AWSNodeClassSpecArgs{
AmiFamily: pulumi.StringPtr("AL2"),
Role: pulumi.StringPtr("KarpenterNodeRole"),
SubnetSelectorTerms: resources.SubnetSelectorTermArray{
{Tags: pulumi.StringMap{"karpenter.sh/discovery": pulumi.String("my-cluster")}},
},
SecurityGroupSelectorTerms: resources.SecurityGroupSelectorTermArray{
{Tags: pulumi.StringMap{"karpenter.sh/discovery": pulumi.String("my-cluster")}},
},
},
})
if err != nil {
return err
}
ctx.Export("nodePolicyId", nodePolicy.ID())
return nil
})
}Arguments
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Unique name for the node policy |
description | string | No | Human-readable description |
weight | int | No | Priority when multiple policies match (higher = preferred) |
capacityTypes | LabelSelectorArgs | No | Capacity types: on-demand, spot, reserved |
instanceCategories | LabelSelectorArgs | No | Filter by instance category letter (e.g. m, c, r on AWS; D, E on Azure) |
instanceFamilies | LabelSelectorArgs | No | Filter instance families (e.g. c5, m5) |
instanceCpus | LabelSelectorArgs | No | Filter by vCPU count |
instanceSizes | LabelSelectorArgs | No | Filter instance sizes (e.g. large, xlarge) |
instanceTypes | LabelSelectorArgs | No | Explicit instance types (e.g. m5.xlarge) |
instanceGenerations | LabelSelectorArgs | No | Filter by instance generation number (e.g. 2, 3) |
instanceHypervisors | LabelSelectorArgs | No | Filter by hypervisor type (e.g. nitro) |
zones | LabelSelectorArgs | No | Availability zones to provision into |
architectures | LabelSelectorArgs | No | CPU architectures (e.g. amd64, arm64) |
operatingSystems | LabelSelectorArgs | No | OS filter (e.g. linux, windows) |
labels | map[string]string | No | Labels applied to provisioned nodes |
taints | TaintArgs[] | No | Taints applied to provisioned nodes |
disruption | DisruptionPolicyArgs | No | Node disruption and consolidation settings |
limits | ResourceLimitsArgs | No | Max total CPU/memory this policy may provision |
nodePoolName | string | No | Override name for the generated Karpenter NodePool resource |
nodeClassName | string | No | Override name for the generated Karpenter NodeClass resource |
aws | AWSNodeClassSpecArgs | No | AWS-specific configuration (AMI, subnets, IAM role, EBS, etc.) |
azure | AzureNodeClassSpecArgs | No | Azure-specific configuration (subnet, image family, disk, etc.) |
raw | RawKarpenterSpecArgs[] | No | Raw Karpenter NodePool/NodeClass YAML (escape hatch) |
Python uses snake_case for all fields (e.g. capacity_types, instance_categories, instance_families, instance_cpus, instance_sizes, instance_types, operating_systems). Go uses PascalCase (e.g. CapacityTypes, InstanceCategories).
Outputs
| Output | Type | Description |
|---|---|---|
id | string | The node policy ID assigned by DevZero |
LabelSelectorArgs
Used for capacityTypes, instanceCategories, instanceFamilies, instanceCpus, instanceSizes, instanceTypes, zones, architectures, and operatingSystems.
| Parameter | Type | Required | Description |
|---|---|---|---|
operator | string | Yes | In or NotIn |
values | string[] | Yes | List of values to match |
TaintArgs
| Parameter | Type | Required | Description |
|---|---|---|---|
key | string | Yes | Taint key |
value | string | No | Taint value |
effect | string | Yes | NoSchedule, PreferNoSchedule, or NoExecute |
DisruptionPolicyArgs
| Parameter | Type | Required | Description |
|---|---|---|---|
consolidationPolicy | string | No | WhenEmpty or WhenEmptyOrUnderutilized |
consolidateAfter | string | No | Wait time after a node is empty before consolidating (e.g. 30s) |
expireAfter | string | No | Force-replace nodes after this duration (e.g. 720h) |
ttlSecondsAfterEmpty | int | No | Seconds before an empty node is terminated. Deprecated — prefer consolidateAfter |
terminationGracePeriodSeconds | int | No | Grace period before forcefully terminating a draining node |
budgets | DisruptionBudgetArgs[] | No | Limits on how many nodes may be disrupted at once |
Python: consolidation_policy, consolidate_after, expire_after, ttl_seconds_after_empty, termination_grace_period_seconds. Go: ConsolidationPolicy, ConsolidateAfter, ExpireAfter, TtlSecondsAfterEmpty, TerminationGracePeriodSeconds.
ResourceLimitsArgs
| Parameter | Type | Required | Description |
|---|---|---|---|
cpu | string | No | Maximum total CPU this policy may provision (e.g. "1000") |
memory | string | No | Maximum total memory (e.g. "1000Gi") |
AWSNodeClassSpecArgs
| Parameter | Type | Required | Description |
|---|---|---|---|
amiFamily | string | No | AMI family: AL2, AL2023, Bottlerocket, Windows2019, Windows2022 |
role | string | No | IAM role name for nodes (Karpenter creates the instance profile) |
instanceProfile | string | No | IAM instance profile name (alternative to role) |
subnetSelectorTerms | SubnetSelectorTermArgs[] | No | Subnet selectors (by tag or ID) |
securityGroupSelectorTerms | SecurityGroupSelectorTermArgs[] | No | Security group selectors |
capacityReservationSelectorTerms | CapacityReservationSelectorTermArgs[] | No | EC2 capacity reservation selectors |
amiSelectorTerms | AMISelectorTermArgs[] | No | AMI selectors (by alias, tag, or ID) |
blockDeviceMappings | BlockDeviceMappingArgs[] | No | EBS volume configuration |
instanceStorePolicy | string | No | NVMe instance store policy. Value: INSTANCE_STORE_POLICY_RAID0 |
tags | map[string]string | No | AWS tags applied to all provisioned resources |
associatePublicIpAddress | bool | No | Assign a public IP to nodes |
detailedMonitoring | bool | No | Enable CloudWatch detailed monitoring |
metadataOptions | MetadataOptionsArgs | No | EC2 IMDS options (IMDSv2, hop limit, etc.) |
kubelet | KubeletConfigurationArgs | No | Kubelet overrides (maxPods, eviction thresholds, etc.) |
userData | string | No | Custom launch template user data |
context | string | No | Additional EC2 launch template context ARN for advanced customization |
Python uses snake_case (e.g. ami_family, instance_profile, subnet_selector_terms, security_group_selector_terms, capacity_reservation_selector_terms, ami_selector_terms, block_device_mappings, instance_store_policy, associate_public_ip_address, detailed_monitoring, metadata_options). Go uses PascalCase.
RawKarpenterSpecArgs
Use this as an escape hatch when you need full control over the Karpenter NodePool/NodeClass resources and the structured fields don't cover your use case.
| Parameter | Type | Required | Description |
|---|---|---|---|
nodepoolYaml | string | No | Raw YAML for a complete Karpenter NodePool resource |
nodeclassYaml | string | No | Raw YAML for a complete Karpenter NodeClass resource |
Python: nodepool_yaml, nodeclass_yaml. Go: NodepoolYaml, NodeclassYaml.
AzureNodeClassSpecArgs
| Parameter | Type | Required | Description |
|---|---|---|---|
vnetSubnetId | string | No | Azure VNet subnet resource ID |
imageFamily | string | No | Image family: AzureLinux, Ubuntu2204, etc. |
osDiskSizeGb | int | No | OS disk size in GB |
fipsMode | string | No | Enabled or Disabled |
maxPods | int | No | Max pods per node |
tags | map[string]string | No | Azure tags on provisioned resources |
kubelet | AzureKubeletConfigurationArgs | No | Kubelet overrides for Azure nodes |
Python: vnet_subnet_id, image_family, os_disk_size_gb, fips_mode, max_pods. Go: VnetSubnetId, ImageFamily, OsDiskSizeGb, FipsMode, MaxPods.
NodePolicyTarget
NodePolicyTarget attaches a NodePolicy to one or more clusters.
Example
import { resources } from "@devzero/pulumi-devzero";
const nodePolicyTarget = new resources.NodePolicyTarget("cluster-nodes", {
name: "cluster-nodes",
policyId: nodePolicy.id,
clusterIds: [cluster.id],
enabled: true,
});from pulumi_devzero.resources import NodePolicyTarget, NodePolicyTargetArgs
node_policy_target = NodePolicyTarget("cluster-nodes", args=NodePolicyTargetArgs(
name="cluster-nodes",
policy_id=node_policy.id,
cluster_ids=[cluster.id],
enabled=True,
))_, err = resources.NewNodePolicyTarget(ctx, "cluster-nodes", &resources.NodePolicyTargetArgs{
Name: pulumi.String("cluster-nodes"),
PolicyId: nodePolicy.ID(),
ClusterIds: pulumi.StringArray{cluster.ID()},
Enabled: pulumi.BoolPtr(true),
})
if err != nil {
return err
}Arguments
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Unique name for the target |
policyId | string | Yes | ID of the NodePolicy to attach |
clusterIds | string[] | Yes | Cluster IDs to target. At most 1 entry — the backend rejects more than one |
description | string | No | Human-readable description |
enabled | bool | No | Whether the target is active (default: true) |
Python uses snake_case: policy_id, cluster_ids. Go uses PascalCase: PolicyId, ClusterIds.
Note:
pulumi destroyremoves this resource from Pulumi state but does not delete it on the DevZero backend. You must remove it manually via the dashboard or API if needed.