Configuration
This guide details the configuration options for the Karpenter Provider for Linode, covering environment variables, operating modes, and the LinodeNodeClass Custom Resource.
Environment Variables
| Variable | Description | Required? | Default |
|---|---|---|---|
LINODE_TOKEN | Your Linode API Personal Access Token. | Yes | - |
CLUSTER_NAME | The name of your LKE cluster. Used to discover the cluster ID in lke mode. | Yes (in lke mode) | - |
LINODE_REGION | The Linode region code. See available regions. | No | us-east |
CLUSTER_ENDPOINT | The external Kubernetes cluster endpoint. Discovered automatically if not provided. | No | - |
KARPENTER_MODE | Operating mode: lke or instance. See Modes below. | No | lke |
LINODE_CLIENT_TIMEOUT | Timeout in seconds for Linode API client requests. | No | - |
VM_MEMORY_OVERHEAD_PERCENT | Additional memory overhead to simplify calculation (0.075 = 7.5%). | No | 0.075 |
IGNORE_DRA_REQUESTS | Karpenter core setting. Set to false to enable alpha whole-GPU DRA scheduling. See DRA. | No | true |
DISABLE_DRY_RUN | Set to true to disable dry-run validation for LinodeNodeClasses. | No | false |
These environment variables are injected into the controller pod via a Kubernetes Secret. By default, the Helm chart creates a secret called karpl-credentials from the apiToken, apiURL, apiVersion, and region values.
Note: Using an external secret (GitOps)
For GitOps workflows where credentials should not be stored in Helm values, you can set
credentialsSecretRefto point to a pre-existing Secret instead. When set, the chart skips creating its own secret and uses the referenced one. This is useful when secrets are managed externally via ExternalSecrets, SealedSecrets, Vault, or similar tools.The externally managed secret must contain the following keys:
LINODE_TOKEN,LINODE_URL,LINODE_API_VERSION, and optionallyCLUSTER_REGION.helm upgrade --install karpenter charts/karpenter \ --set settings.clusterName=my-cluster \ --set credentialsSecretRef=my-linode-credentials
Modes
The provider can operate in two distinct modes, controlled by the KARPENTER_MODE environment variable.
LKE Mode (lke)
- Default Mode.
- Provisions nodes by creating LKE Node Pools with a count of 1.
- The nodes are automatically joined to your LKE cluster.
- Pros: Simplest setup, managed by LKE.
- Cons: Limited customization (cannot use custom images or advanced networking/storage options usually available to raw Linodes). Instance-only
LinodeNodeClassfields are rejected in this mode.
Instance Mode (instance)
- Provisions nodes by creating raw Linode Instances.
- Status: ⚠️ In Development. Not Fully Functional
- This mode represents the roadmap for full instance control but is not currently functional for creating working cluster nodes.
- It currently lacks bootstrapping and cluster joining logic (such as User Data injection). Instances will start but will not join the Kubernetes cluster.
- Pros: Full control over the instance configuration (SSH keys, placement groups, etc.).
- Note:
VPCIDand custom disk configurations are defined in the API but not yet fully implemented in the provider logic.
LinodeNodeClass Spec
The LinodeNodeClass allows you to configure specific settings for the nodes managed by Karpenter. Support for specific fields depends on the Mode you are running.
Predicted ephemeral storage
The pre-registration estimate uses the Linode plan’s advertised disk size as local storage capacity (plan details).
The scheduler’s usable ephemeral storage is capacity minus kubeReserved, systemReserved, and the effective filesystem eviction threshold. The default hard thresholds (nodefs.available: 10%, imagefs.available: 15%) reserve the stricter 15% once, rather than being added together. A non-empty evictionHard map replaces the default hard filesystem thresholds, while configured soft thresholds are combined conservatively using the stricter value. These percentages come from the documented default kubelet hard eviction thresholds.
Once a worker registers, its reported Node.Status.Capacity replaces the prediction for each reported memory and ephemeral-storage resource. Later observations for the same instance type and image can lower that cached capacity, but do not raise it; the first registered worker therefore overrides the prediction, and subsequent workers retain the lowest observed capacity per resource.
Field Reference
Fields marked Instance or LKE are only meaningful in that mode. Setting an Instance-only field while running in lke mode (or an LKE-only field while running in instance mode) is rejected: the LinodeNodeClass reports ValidationSucceeded=False with reason UnsupportedInstanceFields or UnsupportedLKEFields (listing the offending fields) and does not become Ready.
| Field | Type | Supported Modes | Description |
|---|---|---|---|
tags | []string | All | Tags to apply to Linode instances. Each complete tag is limited to 50 characters. In LKE mode, user tags are reconciled directly on instances; generated Karpenter-managed pool tags also must fit the limit and are rejected rather than shortened. |
firewallID | int | Instance | The ID of the Cloud Firewall to attach to a directly created Linode instance. Rejected in lke mode; LKE node pools use the cluster’s default Firewall. |
kubelet | object | All | Kubelet settings (e.g. kubeReserved, systemReserved, eviction thresholds). Currently used only for Karpenter’s allocatable capacity and ephemeral storage estimates; the provider does not push them to nodes. |
lkeK8sVersion | string | LKE | Specific Kubernetes version for LKE Enterprise worker nodes. Upgrade the cluster control plane to this version first; otherwise reconciliation fails and replacement NodeClaims will not come up on the requested version. For Enterprise clusters, the update strategy automatically defaults to on_recycle. Rejected in instance mode. |
image | string | Instance | The Image ID to deploy (default: linode/ubuntu22.04). Rejected in lke mode. |
authorizedKeys | []string | Instance | SSH Public Keys to add to the root user. Rejected in lke mode. |
authorizedUsers | []string | Instance | List of Linode usernames whose SSH keys will be added. Rejected in lke mode. |
backupsEnabled | bool | Instance | Enable backups service for the instance. Rejected in lke mode. |
diskEncryption | enum | Instance | enabled or disabled. Rejected in lke mode (not yet sent to LKE node pools). |
swapSize | int | Instance | Size of the swap disk in MiB. Rejected in lke mode. |
placementGroup | object | Instance | Place instance in a specific Placement Group (id, compliant_only). Rejected in lke mode. |
osDisk | object | Instance | Root disk configuration. Defined in the API but not yet implemented in the provider. Rejected in lke mode. |
dataDisks | object | Instance | Additional disks (sdb–sdh). Defined in the API but not yet implemented in the provider. Rejected in lke mode. |
configuration | object | Instance | Instance configuration profile (e.g. kernel). Defined in the API but not yet implemented in the provider. Rejected in lke mode. |
vpcID | int | Instance | ID of an existing VPC. Defined in the API but not yet implemented in the provider. Rejected in lke mode. |
ipv6Options | object | Instance | IPv6 options (enableSLAAC, enableRanges, isPublicIPv6). Defined in the API but not yet implemented in the provider. Rejected in lke mode. |
Taints and Standard Labels
Karpenter taints and LKE node pool taints have different lifecycles:
- Persistent taints: Define persistent taints in the Karpenter
NodePoolatspec.template.spec.taints. In LKE mode, the provider sends these taints to the LKE Node Pool API so LKE applies them to every node in the pool. - Platform startup taints: The provider automatically teaches Karpenter about temporary taints added during LKE node bootstrap. Standard LKE uses
lke.linode.com/labels-taints=waiting:NoScheduleandnode.kubernetes.io/network-unavailable:NoSchedule. LKE Enterprise also usesnode.cilium.io/agent-not-ready:NoScheduleandnode.cluster.x-k8s.io/uninitialized:NoSchedule. Karpenter ignores the corresponding tier’s temporary taints for scheduling while a node initializes, then waits for LKE to remove them before marking the NodeClaim initialized. - User-defined startup taints: LKE does not currently expose a startup-only taints field. The provider therefore does not send
spec.template.spec.startupTaintsto the persistent LKE Node Pool API. Arbitrary startup taints require a separate bootstrap mechanism to appear on the node. - Kubernetes labels: Define standard scheduling labels in the Karpenter
NodePoolatspec.template.metadata.labels. Karpenter ensures these labels are applied to the Node object. - LKE labels: LKE Node Pool labels are derived from labels resolved onto the NodeClaim, originating from the NodePool template metadata labels.
Example: LKE Mode
apiVersion: karpenter.k8s.linode/v1alpha1
kind: LinodeNodeClass
metadata:
name: default
spec:
tags:
- "karpenter-node"
- "env:production"
Example: Instance Mode
apiVersion: karpenter.k8s.linode/v1alpha1
kind: LinodeNodeClass
metadata:
name: custom-instance
spec:
tags:
- "custom-workload"
firewallID: 12345
image: "linode/ubuntu22.04"
authorizedUsers:
- "my-user"
backupsEnabled: true
swapSize: 512
diskEncryption: "enabled"
Selecting nodes
With nodeSelector you can ask for a node that matches selected key-value pairs. This can include well-known labels or custom labels you create yourself.
You can use affinity to define more complicated constraints, see Node Affinity for the complete specification.
Labels
Well-known labels may be specified as NodePool requirements or pod scheduling constraints. You can also define your own custom labels by specifying requirements or labels on your NodePool and select them using nodeAffinity or nodeSelectors on your Pods.
Well-Known Labels
| Label | Example | Description |
|---|---|---|
| topology.kubernetes.io/region | us-east | Regions are defined by your cloud provider (linode) |
| topology.linode.com/region | us-east | Uses the cluster’s region. |
| node.kubernetes.io/instance-type | g6-dedicated-16 | Instance types are defined by your cloud provider (linode) |
| kubernetes.io/os | linux | Operating systems are defined by GOOS values on the instance |
| kubernetes.io/arch | amd64 | Architectures are defined by GOARCH values on the instance |
| karpenter.k8s.linode/instance-generation | 6 | [Linode Specific] Instance type generation number |
| karpenter.k8s.linode/instance-cpu | 32 | [Linode Specific] Number of CPUs on the instance |
| karpenter.k8s.linode/instance-memory | 32768 | [Linode Specific] Number of megabytes of memory on the instance |
| karpenter.k8s.linode/instance-disk | 655360 | [Linode Specific] Number of megabytes of storage on the instance |
| karpenter.k8s.linode/instance-gpu-name | gtx6000 | [Linode Specific] Name of the GPU on the instance, if available |
| karpenter.k8s.linode/instance-gpu-count | 1 | [Linode Specific] Number of GPUs on the instance |
| karpenter.k8s.linode/instance-transfer | 16000 | [Linode Specific] Number of gigabytes for network transfer |
| karpenter.k8s.linode/instance-network-out | 10000 | [Linode Specific] Number of megabits per second for network outbound bandwidth |
| karpenter.k8s.linode/instance-accelerated-devices-count | 1 | [Linode Specific] Number of accelerated devices on the instance |
| karpenter.k8s.linode/instance-class | dedicated | [Linode Specific] Instance class types include nanode, standard, dedicated, highmem, and gpu |
Capacity type
Linode currently provides only on-demand capacity. Constrain each NodePool accordingly so Karpenter does not consider unsupported spot replacements during consolidation:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["on-demand"]
User-Defined Labels
Karpenter is aware of several well-known labels, deriving them from instance type details. If you specify a nodeSelector or a required nodeAffinity using a label that is not well-known to Karpenter, it will not launch nodes with these labels and pods will remain pending. For Karpenter to become aware that it can schedule for these labels, you must specify the label in the NodePool requirements with the Exists operator:
requirements:
- key: user.defined.label/type
operator: Exists
Node selectors
Here is an example of a nodeSelector for selecting nodes:
nodeSelector:
topology.kubernetes.io/region: us-east
karpenter.sh/capacity-type: on-demand
This example features a well-known label (topology.kubernetes.io/region) and a label that is well known to Karpenter (karpenter.sh/capacity-type).
If you want to create a custom label, you should do that at the NodePool level. Then the pod can declare that custom label.
See nodeSelector in the Kubernetes documentation for details.