NewCompare CPU & GPU pricing across AWS, Azure & GCP
Pulumi

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

ParameterTypeRequiredDescription
namestringYesUnique name for the node policy
descriptionstringNoHuman-readable description
weightintNoPriority when multiple policies match (higher = preferred)
capacityTypesLabelSelectorArgsNoCapacity types: on-demand, spot, reserved
instanceCategoriesLabelSelectorArgsNoFilter by instance category letter (e.g. m, c, r on AWS; D, E on Azure)
instanceFamiliesLabelSelectorArgsNoFilter instance families (e.g. c5, m5)
instanceCpusLabelSelectorArgsNoFilter by vCPU count
instanceSizesLabelSelectorArgsNoFilter instance sizes (e.g. large, xlarge)
instanceTypesLabelSelectorArgsNoExplicit instance types (e.g. m5.xlarge)
instanceGenerationsLabelSelectorArgsNoFilter by instance generation number (e.g. 2, 3)
instanceHypervisorsLabelSelectorArgsNoFilter by hypervisor type (e.g. nitro)
zonesLabelSelectorArgsNoAvailability zones to provision into
architecturesLabelSelectorArgsNoCPU architectures (e.g. amd64, arm64)
operatingSystemsLabelSelectorArgsNoOS filter (e.g. linux, windows)
labelsmap[string]stringNoLabels applied to provisioned nodes
taintsTaintArgs[]NoTaints applied to provisioned nodes
disruptionDisruptionPolicyArgsNoNode disruption and consolidation settings
limitsResourceLimitsArgsNoMax total CPU/memory this policy may provision
nodePoolNamestringNoOverride name for the generated Karpenter NodePool resource
nodeClassNamestringNoOverride name for the generated Karpenter NodeClass resource
awsAWSNodeClassSpecArgsNoAWS-specific configuration (AMI, subnets, IAM role, EBS, etc.)
azureAzureNodeClassSpecArgsNoAzure-specific configuration (subnet, image family, disk, etc.)
rawRawKarpenterSpecArgs[]NoRaw 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

OutputTypeDescription
idstringThe node policy ID assigned by DevZero

LabelSelectorArgs

Used for capacityTypes, instanceCategories, instanceFamilies, instanceCpus, instanceSizes, instanceTypes, zones, architectures, and operatingSystems.

ParameterTypeRequiredDescription
operatorstringYesIn or NotIn
valuesstring[]YesList of values to match

TaintArgs

ParameterTypeRequiredDescription
keystringYesTaint key
valuestringNoTaint value
effectstringYesNoSchedule, PreferNoSchedule, or NoExecute

DisruptionPolicyArgs

ParameterTypeRequiredDescription
consolidationPolicystringNoWhenEmpty or WhenEmptyOrUnderutilized
consolidateAfterstringNoWait time after a node is empty before consolidating (e.g. 30s)
expireAfterstringNoForce-replace nodes after this duration (e.g. 720h)
ttlSecondsAfterEmptyintNoSeconds before an empty node is terminated. Deprecated — prefer consolidateAfter
terminationGracePeriodSecondsintNoGrace period before forcefully terminating a draining node
budgetsDisruptionBudgetArgs[]NoLimits 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

ParameterTypeRequiredDescription
cpustringNoMaximum total CPU this policy may provision (e.g. "1000")
memorystringNoMaximum total memory (e.g. "1000Gi")

AWSNodeClassSpecArgs

ParameterTypeRequiredDescription
amiFamilystringNoAMI family: AL2, AL2023, Bottlerocket, Windows2019, Windows2022
rolestringNoIAM role name for nodes (Karpenter creates the instance profile)
instanceProfilestringNoIAM instance profile name (alternative to role)
subnetSelectorTermsSubnetSelectorTermArgs[]NoSubnet selectors (by tag or ID)
securityGroupSelectorTermsSecurityGroupSelectorTermArgs[]NoSecurity group selectors
capacityReservationSelectorTermsCapacityReservationSelectorTermArgs[]NoEC2 capacity reservation selectors
amiSelectorTermsAMISelectorTermArgs[]NoAMI selectors (by alias, tag, or ID)
blockDeviceMappingsBlockDeviceMappingArgs[]NoEBS volume configuration
instanceStorePolicystringNoNVMe instance store policy. Value: INSTANCE_STORE_POLICY_RAID0
tagsmap[string]stringNoAWS tags applied to all provisioned resources
associatePublicIpAddressboolNoAssign a public IP to nodes
detailedMonitoringboolNoEnable CloudWatch detailed monitoring
metadataOptionsMetadataOptionsArgsNoEC2 IMDS options (IMDSv2, hop limit, etc.)
kubeletKubeletConfigurationArgsNoKubelet overrides (maxPods, eviction thresholds, etc.)
userDatastringNoCustom launch template user data
contextstringNoAdditional 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.

ParameterTypeRequiredDescription
nodepoolYamlstringNoRaw YAML for a complete Karpenter NodePool resource
nodeclassYamlstringNoRaw YAML for a complete Karpenter NodeClass resource

Python: nodepool_yaml, nodeclass_yaml. Go: NodepoolYaml, NodeclassYaml.

AzureNodeClassSpecArgs

ParameterTypeRequiredDescription
vnetSubnetIdstringNoAzure VNet subnet resource ID
imageFamilystringNoImage family: AzureLinux, Ubuntu2204, etc.
osDiskSizeGbintNoOS disk size in GB
fipsModestringNoEnabled or Disabled
maxPodsintNoMax pods per node
tagsmap[string]stringNoAzure tags on provisioned resources
kubeletAzureKubeletConfigurationArgsNoKubelet 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

ParameterTypeRequiredDescription
namestringYesUnique name for the target
policyIdstringYesID of the NodePolicy to attach
clusterIdsstring[]YesCluster IDs to target. At most 1 entry — the backend rejects more than one
descriptionstringNoHuman-readable description
enabledboolNoWhether the target is active (default: true)

Python uses snake_case: policy_id, cluster_ids. Go uses PascalCase: PolicyId, ClusterIds.

Note: pulumi destroy removes 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.

On this page