Skip to main content

A Pulumi provider for managing DevZero infrastructure resources.

Project description

pulumi-devzero

The official Python Pulumi provider for DevZero — manage clusters, workload policies, and node policies as code.

PyPI version License: MIT Pulumi Registry


Installation

pip install pulumi-devzero

Requires: pulumi>=3.0.0,<4.0.0, Python 3.9+


Configuration

Before using the DevZero Pulumi provider, configure your credentials using Pulumi config.

1. Generate a Personal Access Token (PAT)

Go to the DevZero user settings page to generate your PAT token:

https://www.devzero.io/settings/user-settings/general

Create a Personal Access Token and copy it.

2. Find your Team ID

You can find your DevZero Team ID in the organization settings:

https://www.devzero.io/settings/organization-settings/account

Copy the Team ID value from this page.

3. Set Pulumi configuration

Run the following commands in your Pulumi project:

pulumi config set --secret devzero:token <YOUR_PAT_TOKEN>
pulumi config set devzero:teamId <TEAM_ID>
pulumi config set devzero:url https://dakr.devzero.io  # optional, this is the default

Example

pulumi config set --secret devzero:token dz_pat_xxxxxxxxxxxxx
pulumi config set devzero:teamId team_123456789

The --secret flag ensures that your token is encrypted in the Pulumi state.


Quick Start

import pulumi
import pulumi_devzero as devzero

# 1. Create a cluster
cluster = devzero.resources.Cluster("prod-cluster",
    name="prod-cluster",
)

# 2. Create a workload policy with CPU vertical scaling
policy = devzero.resources.WorkloadPolicy("cpu-scaling-policy",
    name="cpu-scaling-policy",
    description="Policy with CPU vertical scaling enabled",
    action_triggers=["on_detection", "on_schedule"],  # apply on pod events AND on schedule
    cron_schedule="0 2 * * *",                        # daily at 2 am UTC (required for on_schedule)
    detection_triggers=["pod_creation", "pod_reschedule"],
    cpu_vertical_scaling=devzero.resources.VerticalScalingArgsArgs(
        enabled=True,
        target_percentile=0.95,
        min_request=50,
        max_request=4000,
        max_scale_up_percent=100,
        max_scale_down_percent=25,
        overhead_multiplier=1.1,
        limits_adjustment_enabled=True,
        limit_multiplier=1.5,
    ),
)

# 3. Apply the policy to the cluster for all Deployments
target = devzero.resources.WorkloadPolicyTarget("prod-cluster-target",
    name="prod-cluster-deployments-target",
    description="Apply cpu-scaling-policy to all Deployments in prod-cluster",
    policy_id=policy.id,
    cluster_ids=[cluster.id],
    kind_filter=["Deployment"],
    enabled=True,
)

pulumi.export("cluster_id", cluster.id)
pulumi.export("cluster_token", pulumi.Output.secret(cluster.token))
pulumi.export("policy_id", policy.id)
pulumi.export("target_id", target.id)
pulumi up

Resources

Cluster

Provision and manage a DevZero cluster.

import pulumi
import pulumi_devzero as devzero

cluster = devzero.resources.Cluster("my-cluster",
    name="my-cluster",
)

pulumi.export("id", cluster.id)
pulumi.export("token", pulumi.Output.secret(cluster.token))

WorkloadPolicy

Configure vertical and horizontal scaling policies for workloads.

import pulumi_devzero as devzero

policy = devzero.resources.WorkloadPolicy("my-policy",
    name="my-policy",
    description="Vertical scaling for CPU and memory",
    enable_pmax_protection=True,
    pmax_ratio_threshold=3.0,
    cpu_vertical_scaling=devzero.resources.VerticalScalingArgsArgs(
        enabled=True,
        target_percentile=0.95,
        min_request=50,
        max_request=4000,
        max_scale_up_percent=100,
        max_scale_down_percent=25,
        overhead_multiplier=1.1,
        limits_adjustment_enabled=True,
        limit_multiplier=1.5,
        adjust_req_even_if_not_set=True,
        limits_removal_enabled=False,
    ),
    memory_vertical_scaling=devzero.resources.VerticalScalingArgsArgs(
        enabled=True,
        target_percentile=0.9,
        min_request=128,
        max_request=8192,
        max_scale_up_percent=50,
        max_scale_down_percent=20,
        overhead_multiplier=1.2,
        limits_adjustment_enabled=True,
        limit_multiplier=1.3,
        adjust_req_even_if_not_set=True,
        limits_removal_enabled=False,
    ),
)

VerticalScalingArgsArgs fields:

Field Type Description
enabled bool Enable this scaling axis
target_percentile float Percentile of observed usage to target (e.g. 0.95)
min_request int Minimum resource request (millicores / MiB)
max_request int Maximum resource request (millicores / MiB)
max_scale_up_percent float Max % to scale up in one step. Default: 1000
max_scale_down_percent float Max % to scale down in one step. Default: 1.0
overhead_multiplier float Multiplier added on top of the recommendation
limits_adjustment_enabled bool Whether to also adjust resource limits
limit_multiplier float Limits = request × limit_multiplier
min_data_points int Minimum data points before a recommendation is emitted. Default: 20
adjust_req_even_if_not_set bool Recommend requests even when the workload has no existing requests set. Default: false
limits_removal_enabled bool Actively remove limits from workloads (CPU axis only — memory limits removal is not supported). Takes precedence over limits_adjustment_enabled. Default: false

WorkloadPolicy pmax & VPA knob fields:

Field Type Description
enable_pmax_protection bool Raise requests to cover peak usage when max/recommendation ratio exceeds pmax_ratio_threshold. Default: false
pmax_ratio_threshold float Max-to-recommendation ratio that triggers pmax protection. Default: 3.0
loopback_period_seconds int Look-back period in seconds for usage data. Default: 86400 (24 h)
min_data_points int Global minimum data points for recommendations. Default: 15
min_change_percent float Global minimum change threshold. Default: 0.2 (20%)
min_vpa_window_data_points int Minimum data points in VPA analysis window. Default: 30
cooldown_minutes int Minutes between applying recommendations. Default: 300 (5 h)

WorkloadPolicyTarget

Apply a workload policy to one or more clusters with optional filters.

import pulumi_devzero as devzero

target = devzero.resources.WorkloadPolicyTarget("my-target",
    name="my-target",
    policy_id=policy.id,
    cluster_ids=[cluster.id],
    kind_filter=["Deployment", "StatefulSet"],
    namespace_selector=devzero.resources.LabelSelectorArgsArgs(match_labels={"env": "production"}),
    enabled=True,
)

Fields:

Field Type Description
name str Unique target name
policy_id str ID of the WorkloadPolicy to apply
cluster_ids list[str] Cluster IDs to target
description str Human-readable description (optional)
priority int Evaluation priority; higher value wins when targets overlap
kind_filter list[str] Pod | Deployment | StatefulSet | DaemonSet | Job | CronJob | ReplicaSet | ReplicationController | Rollout
workload_names list[str] Explicit list of workload names to include
node_group_names list[str] Restrict matching to specific node groups by name
name_pattern NamePatternArgsArgs Regex pattern to match workload names
namespace_selector LabelSelectorArgsArgs Select namespaces by labels (match_labels / match_expressions)
workload_selector LabelSelectorArgsArgs Select workloads by labels
enabled bool Activate the target

NodePolicy

Configure node provisioning and pooling (AWS / Azure) using dzkarp under the hood.

import pulumi_devzero as devzero

node_policy = devzero.resources.NodePolicy("prod-node-policy",
    name="prod-node-policy",
    description="Cost-efficient node provisioning for production workloads",

    # Higher weight wins when multiple policies match the same node request.
    weight=10,

    # Instance categories: c (compute), m (general), r (memory), t (burstable).
    # Kept broad to maximise the instance pool and minimise cost.
    instance_categories=devzero.resources.LabelSelectorArgsArgs(
        match_expressions=[devzero.resources.LabelSelectorRequirementArgsArgs(
            operator="In", values=["c", "m", "r", "t"],
        )],
    ),

    # Instance generation: prefer modern hardware (gen 3+) for better performance/cost ratio.
    instance_generations=devzero.resources.LabelSelectorArgsArgs(
        match_expressions=[devzero.resources.LabelSelectorRequirementArgsArgs(
            operator="In", values=["3", "4", "5", "6"],
        )],
    ),

    # CPU architecture: amd64 (x86_64) — derived from active nodes in the cluster.
    architectures=devzero.resources.LabelSelectorArgsArgs(
        match_expressions=[devzero.resources.LabelSelectorRequirementArgsArgs(
            operator="In", values=["amd64"],
        )],
    ),

    # Capacity types: prefer spot for savings, fall back to on-demand for availability.
    capacity_types=devzero.resources.LabelSelectorArgsArgs(
        match_expressions=[devzero.resources.LabelSelectorRequirementArgsArgs(
            operator="In", values=["spot", "on-demand"],
        )],
    ),

    # Operating system: linux only.
    operating_systems=devzero.resources.LabelSelectorArgsArgs(
        match_expressions=[devzero.resources.LabelSelectorRequirementArgsArgs(
            operator="In", values=["linux"],
        )],
    ),

    # Disruption: how dzkarp consolidates and rotates nodes.
    disruption=devzero.resources.DisruptionPolicyArgsArgs(
        consolidation_policy="WhenEmptyOrUnderutilized", # reclaim empty and underused nodes
        consolidate_after="2h0m0s",                      # wait 2 h before consolidating
        expire_after="168h",                             # rotate nodes after 7 days
        budgets=[
            devzero.resources.DisruptionBudgetArgsArgs(
                # Disrupt up to 10% of nodes at once for these reasons.
                reasons=["Empty", "Drifted", "Underutilized"],
                nodes="10%",
            ),
            devzero.resources.DisruptionBudgetArgsArgs(
                nodes="1",  # always protect at least 1 node
            ),
        ],
    ),

    # Override the generated dzkarp CRD names (helps avoid collisions in shared clusters).
    node_pool_name="prod-nodepool",   # name of the dzkarp NodePool CR
    node_class_name="prod-nodeclass", # name of the dzkarp NodeClass CR

    # AWS-specific EC2 configuration.
    aws=devzero.resources.AWSNodeClassSpecArgsArgs(
        # Subnets where nodes will launch — discovered via the cluster tag.
        subnet_selector_terms=[devzero.resources.SubnetSelectorTermArgsArgs(
            tags={"karpenter.sh/discovery": "my-prod-cluster"},
        )],
        # Security groups for node instances — same discovery tag pattern.
        security_group_selector_terms=[devzero.resources.SecurityGroupSelectorTermArgsArgs(
            tags={"karpenter.sh/discovery": "my-prod-cluster"},
        )],
        # AMI: latest Amazon Linux 2023 managed alias (dzkarp keeps it up to date).
        ami_selector_terms=[devzero.resources.AMISelectorTermArgsArgs(alias="al2023@latest")],
        # IAM role dzkarp uses to launch and manage nodes (must already exist in AWS).
        role="KarpenterNodeRole-my-prod-cluster",
    ),
)

NodePolicyArgs fields:

Field Type Description
name str Unique policy name
description str Human-readable description
weight int Priority weight when multiple policies match (higher = preferred)
instance_categories LabelSelectorArgsArgs Filter by instance category letter: e.g. m, c, r (AWS) or D, E (Azure)
instance_families LabelSelectorArgsArgs Filter instance families (e.g. c5, m5)
instance_cpus LabelSelectorArgsArgs Filter by vCPU count
instance_sizes LabelSelectorArgsArgs Filter instance sizes (e.g. large, xlarge)
instance_types LabelSelectorArgsArgs Explicit instance types (e.g. m5.xlarge)
instance_generations LabelSelectorArgsArgs Filter by instance generation
instance_hypervisors LabelSelectorArgsArgs Filter by hypervisor type
zones LabelSelectorArgsArgs Availability zones to provision into
architectures LabelSelectorArgsArgs CPU architectures (e.g. amd64, arm64)
capacity_types LabelSelectorArgsArgs Capacity types: on-demand | spot | reserved
operating_systems LabelSelectorArgsArgs OS filter (e.g. linux, windows)
labels dict[str, str] Labels applied to provisioned nodes
taints list[TaintArgsArgs] Taints applied to provisioned nodes
disruption DisruptionPolicyArgsArgs Node disruption / consolidation settings
limits ResourceLimitsArgsArgs Max total CPU/memory this policy may provision
node_pool_name str Override the dzkarp NodePool name
node_class_name str Override the dzkarp NodeClass name
aws AWSNodeClassSpecArgsArgs AWS-specific node class configuration
azure AzureNodeClassSpecArgsArgs Azure-specific node class configuration
raw list[RawKarpenterSpecArgsArgs] Raw Karpenter YAML (escape hatch)

RawKarpenterSpecArgsArgs fields:

Field Type Description
nodepool_yaml str Raw YAML for a complete dzkarp NodePool resource
nodeclass_yaml str Raw YAML for a complete dzkarp NodeClass resource

DisruptionPolicyArgsArgs fields:

Field Type Description
consolidation_policy str WhenEmpty | WhenEmptyOrUnderutilized
consolidate_after str Wait time after node is empty before consolidating (e.g. 30s)
expire_after str Force-replace nodes after this duration (e.g. 720h)
ttl_seconds_after_empty int Seconds before an empty node is terminated (deprecated; prefer consolidate_after)
termination_grace_period_seconds int Grace period before forcefully terminating a draining node
budgets list[DisruptionBudgetArgsArgs] Limits on how many nodes may be disrupted at once

AWSNodeClassSpecArgsArgs fields:

Field Type Description
ami_family str AMI family: AL2, AL2023, Bottlerocket, Windows2019, Windows2022
role str IAM role name for nodes (Karpenter creates the instance profile)
instance_profile str IAM instance profile name (alternative to role)
subnet_selector_terms list[SubnetSelectorTermArgsArgs] Subnet selectors (by tag or ID)
security_group_selector_terms list[SecurityGroupSelectorTermArgsArgs] Security group selectors
capacity_reservation_selector_terms list[CapacityReservationSelectorTermArgsArgs] EC2 capacity reservation selectors
ami_selector_terms list[AMISelectorTermArgsArgs] AMI selectors (by alias, tag, or ID)
block_device_mappings list[BlockDeviceMappingArgsArgs] EBS volume configuration
instance_store_policy str NVMe instance store policy. Value: INSTANCE_STORE_POLICY_RAID0
tags dict[str, str] AWS tags on all provisioned resources
associate_public_ip_address bool Assign a public IP to nodes
detailed_monitoring bool Enable CloudWatch detailed monitoring
metadata_options MetadataOptionsArgsArgs EC2 IMDS options (IMDSv2, hop limit, etc.)
kubelet KubeletConfigurationArgsArgs Kubelet overrides (max_pods, eviction thresholds, etc.)
user_data str Custom launch template user data
context str Additional EC2 launch template context ARN for advanced customization

AzureNodeClassSpecArgsArgs fields:

Field Type Description
vnet_subnet_id str Azure VNet subnet resource ID
image_family str Image family: AzureLinux, Ubuntu2204, etc.
os_disk_size_gb int OS disk size in GB
fips_mode str Enabled | Disabled
max_pods int Max pods per node
tags dict[str, str] Azure tags on provisioned resources
kubelet AzureKubeletConfigurationArgsArgs Kubelet overrides for Azure nodes

WorkloadRule

Pin explicit resource rules directly to a single workload. Unlike WorkloadPolicy, which applies a shared policy to many workloads, a WorkloadRule targets one specific kind/namespace/name on a cluster.

import pulumi_devzero as devzero

rule = devzero.resources.WorkloadRule("my-app-rule",
    cluster_id="cluster-abc123",
    namespace="production",
    kind="Deployment",
    name="my-api",
    cpu_rule=devzero.resources.ResourceRuleConfigArgsArgs(
        enabled=True,
        min_request=10,     # 10m CPU
        max_request=4000,   # 4 cores
        target_percentile=0.95,
        limits_adjustment_enabled=True,
        limit_multiplier=1.5,
    ),
    memory_rule=devzero.resources.ResourceRuleConfigArgsArgs(
        enabled=True,
        min_request=67108864,    # 64 MiB
        max_request=536870912,   # 512 MiB
    ),
    emergency_response=devzero.resources.EmergencyResponseConfigArgsArgs(
        oom_enabled=True,
        oom_memory_multiplier=1.5,
        oom_max_reactions=3,
        oom_cooldown_seconds=60,
        cpu_throttling_enabled=True,
        cpu_throttling_threshold=0.1,
        cpu_throttling_multiplier=1.25,
    ),
    action_triggers=["on_detection"],
    detection_triggers=["pod_creation", "pod_reschedule"],
    cooldown_minutes=60,
)

pulumi.export("rule_id", rule.id)

Auto-generate: Set auto_generate=True to let the engine fill in all fields from observed usage:

rule = devzero.resources.WorkloadRule("my-app-rule",
    cluster_id="cluster-abc123",
    namespace="production",
    kind="Deployment",
    name="my-api",
    auto_generate=True,
)

Fields:

Field Type Description
cluster_id str ID of the cluster the workload lives in
namespace str Kubernetes namespace of the workload
kind str Deployment | StatefulSet | DaemonSet | CronJob | Job
name str Name of the Kubernetes workload
auto_generate bool When True, the engine fills all rule fields automatically
cpu_rule ResourceRuleConfigArgsArgs CPU vertical scaling rule
memory_rule ResourceRuleConfigArgsArgs Memory vertical scaling rule
gpu_rule ResourceRuleConfigArgsArgs GPU vertical scaling rule
hpa_rule HPARuleConfigArgsArgs Horizontal (replica) scaling rule
emergency_response EmergencyResponseConfigArgsArgs OOM and CPU-throttle emergency reactions
action_triggers list[str] on_detection | on_schedule
cron_schedule str Cron expression (5-field UTC). Required when action_triggers includes on_schedule
detection_triggers list[str] pod_creation | pod_update | pod_reschedule
startup_period_seconds int Seconds after workload start to exclude from usage data
cooldown_minutes int Minimum minutes between recommendation applications
scheduler_plugins list[str] Kubernetes scheduler plugins to activate
defragmentation_schedule str Cron expression for node defragmentation
live_migration_enabled bool Allow live pod migration when applying recommendations
use_in_place_vertical_scaling bool Use in-place pod vertical scaling instead of pod restarts
containers list[ContainerResourceRuleConfigArgsArgs] Per-container resource overrides

ResourceRuleConfigArgsArgs fields (used for cpu_rule, memory_rule, gpu_rule):

Note: max_scale_up_percent and max_scale_down_percent are not supported on per-container rules.

Field Type Description
enabled bool Enable this resource axis rule
min_request int Minimum resource request (millicores for CPU, bytes for memory/GPU)
max_request int Maximum resource request
target_percentile float Percentile of observed usage to target (0–1)
max_scale_up_percent float Max % to scale up in one step (workload-level only)
max_scale_down_percent float Max % to scale down in one step (workload-level only)
limits_adjustment_enabled bool Also adjust resource limits
limit_multiplier float Limits = request × limit_multiplier
limits_removal_enabled bool Actively remove limits (CPU only)

HPARuleConfigArgsArgs fields:

Field Type Description
enabled bool Enable horizontal scaling
min_replicas int Minimum replicas
max_replicas int Maximum replicas
target_utilization float Target utilization ratio (0–1)
primary_metric str cpu | memory | gpu | network_ingress | network_egress
max_replica_change_percent float Max % change in replica count per cycle

EmergencyResponseConfigArgsArgs fields:

Field Type Description
oom_enabled bool React to OOM kills by increasing memory
oom_memory_multiplier float Multiplier applied to memory on OOM. Example: 1.5
oom_max_reactions int Max OOM reactions before giving up
oom_cooldown_seconds int Seconds between OOM reactions
cpu_throttling_enabled bool React to CPU throttling
cpu_throttling_threshold float Throttle ratio (0–1) that triggers a reaction
cpu_throttling_multiplier float Multiplier applied to CPU request on throttle reaction

NodePolicyTarget

Apply a node policy to one or more clusters.

import pulumi_devzero as devzero

node_policy_target = devzero.resources.NodePolicyTarget("my-node-policy-target",
    name="my-node-policy-target",
    policy_id=node_policy.id,
    cluster_ids=[cluster.id],
    enabled=True,
)

Fields:

Field Type Description
name str Unique target name
policy_id str ID of the NodePolicy to apply
cluster_ids list[str] Cluster IDs to target. At most 1 entry — the backend rejects more than one.
description str Human-readable description (optional)
enabled bool Activate the target

Note: pulumi destroy removes this resource from Pulumi state but does not delete it on the DevZero backend — no delete RPC exists for NodePolicyTarget.


Data Sources

get_cluster_id_by_name

Look up an existing cluster by name and return its ID. Use this when a cluster was registered manually (not created by Pulumi) and you need its ID to attach policies or inject into values.yaml / a Kubernetes secret.

import pulumi
import pulumi_devzero as devzero

existing = devzero.resources.get_cluster_id_by_name(
    name="my-existing-cluster",
    # team_id is optional — defaults to devzero:teamId from provider config
    # region="us-east-1",        # optional: filter by region
    # cloud_provider="AWS",      # optional: AWS | GCP | AKS | OCI
    # liveness="PREFER_LIVE",    # optional: IGNORE | PREFER_LIVE | REQUIRE_LIVE
)

# Attach a policy to the existing cluster
target = devzero.resources.WorkloadPolicyTarget("my-target",
    name="my-target",
    policy_id=policy.id,
    cluster_ids=[existing.cluster_id],
    kind_filter=["Deployment"],
    enabled=True,
)

pulumi.export("existing_cluster_id", existing.cluster_id)

Inputs:

Field Type Required Description
name str yes Cluster name to look up
team_id str no Defaults to devzero:teamId from provider config
region str no Filter by region name (e.g. us-east-1)
cloud_provider str no Filter by cloud provider: AWS | GCP | AKS | OCI
liveness str no IGNORE (default) | PREFER_LIVE | REQUIRE_LIVE

Outputs:

Field Type Description
cluster_id str UUID of the matching cluster

Note: If multiple clusters share the same name, the newest one is returned by default. Use liveness="PREFER_LIVE" or "REQUIRE_LIVE" to filter by heartbeat freshness.


Destroying Resources

# Tear down all resources in the stack
pulumi destroy

# Remove the stack itself
pulumi stack rm <stack-name>

Links


License

MIT — Copyright (c) 2026 DevZero Inc.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pulumi_devzero-0.1.13.tar.gz (65.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pulumi_devzero-0.1.13-py3-none-any.whl (68.1 kB view details)

Uploaded Python 3

File details

Details for the file pulumi_devzero-0.1.13.tar.gz.

File metadata

  • Download URL: pulumi_devzero-0.1.13.tar.gz
  • Upload date:
  • Size: 65.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for pulumi_devzero-0.1.13.tar.gz
Algorithm Hash digest
SHA256 d0bbae28dce1e0c81e888c59541e01b963334d31980b4d93e1d424c37cb7d968
MD5 b8b005879dac373e165471f5354a4014
BLAKE2b-256 d9ffd116cb157e02209df0c567b067a399fb0dc3be3e9c9ca0ec294a02ba5385

See more details on using hashes here.

File details

Details for the file pulumi_devzero-0.1.13-py3-none-any.whl.

File metadata

File hashes

Hashes for pulumi_devzero-0.1.13-py3-none-any.whl
Algorithm Hash digest
SHA256 b2935c3eecd4858d02cdd42caf51aa1fc4925f7ee5750ac01b1445523f6a2092
MD5 ff0c60ef9540b0100eebc82dced48a45
BLAKE2b-256 83dc808396e1159922fd244294c2eb666b7822cbf9071317b49250fdcba0b93d

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page