Automatic node remediation is in public preview. To use it, deploy the custom resources described below.
How to Configure Automatic Node Remediation for DigitalOcean Kubernetes Clusterspublic
Last verified 8 Jul 2026
DigitalOcean Kubernetes (DOKS) is a Kubernetes service with a fully managed control plane, high availability, and autoscaling. DOKS integrates with standard Kubernetes toolchains and DigitalOcean’s load balancers, volumes, CPU and GPU Droplets, API, and CLI.
DigitalOcean Kubernetes (DOKS) can automatically replace worker nodes that stay unhealthy. The Data Plane Operator carries out remediation. It runs in your cluster, watches Node objects, and applies rules you define through cluster-scoped custom resources.
Remediation is opt-in. You enable it for the cluster, define rules for which node conditions trigger a replacement, and monitor remediation through Kubernetes events.
How Automatic Node Remediation Works
When remediation is enabled, the Data Plane Operator watches each worker Node object and, for each one:
- Checks whether remediation is enabled cluster-wide in the
DataPlaneOperatorConfigobject. - Selects the
NodeRemediationConfig(NRC) whosenodeSelectorbest matches the node. - Evaluates the NRC’s
spec.unhealthyConditionsagainst the node’sstatus.conditions. - After the configured grace and duration thresholds pass, issues a remediation action that replaces the worker through the DigitalOcean API.
The operator does not decide what counts as unhealthy for your applications. It reacts to node conditions on each worker: standard kubelet conditions (for example, Ready=False) and custom condition types you define for your environment. You, or software you run in the cluster, set those conditions on the node. The operator replaces the worker when a condition matches your NodeRemediationConfig rules for long enough.
You can set custom conditions with tools such as Node Problem Detector, a DaemonSet or operator that patches node status, or automation tied to your own health checks. The condition type and status strings in your rules must match exactly what you publish on the node.
%%{init: {"themeVariables": {"edgeLabelBackground": "transparent"}} }%%
sequenceDiagram
participant User as Your CR configuration
participant DPO as Data Plane Operator
participant Node as Node (status.conditions)
participant API as DigitalOcean API
User ->> DPO: DataPlaneOperatorConfig (enabled)
User ->> DPO: NodeRemediationConfig (rules)
Node ->> DPO: Condition updates
DPO ->> DPO: Match NRC, check duration and budget
DPO ->> Node: Remediation annotations
DPO ->> API: cordon-drain-replace or force-replace
DPO ->> User: Events on NodeRemediationConfig
Prerequisites
Before you configure remediation, make sure you have:
kubectlaccess to the cluster, with permission to create cluster-scoped resources in thedataplane-operator.doks.digitalocean.comAPI group.- Worker nodes that carry the
doks.digitalocean.com/node-pool-idlabel. The operator requires this label to scope replacements to the correct node pool and to observe healthy replacements. DOKS applies it automatically. A node without this label is not remediated. - Workloads that tolerate node loss. Remediation replaces the underlying Droplet, so use PodDisruptionBudgets, multiple replicas, and similar safeguards.
API Group and Resources
You configure remediation with two cluster-scoped custom resources in the dataplane-operator.doks.digitalocean.com/v1alpha1 API group:
| Resource | Scope | Purpose |
|---|---|---|
DataPlaneOperatorConfig |
Cluster | Turns cluster-wide remediation on or off. |
NodeRemediationConfig |
Cluster | Defines per-policy unhealthy rules, limits, and actions. |
In kubectl, refer to them by these names:
dataplaneoperatorconfig/dataplaneoperatorconfigsnoderemediationconfig/noderemediationconfigs
Apply or update these resources with kubectl apply or your GitOps workflow. The operator reads the live objects from the Kubernetes API. Changes to a NodeRemediationConfig take effect on the next node reconcile.
You can add NodeRemediationConfig objects for different node pools or custom health signals.
Enable Remediation for the Cluster
Remediation is gated for the entire cluster by a single DataPlaneOperatorConfig object named user-config. The operator reads only the object with this exact name and ignores any other name. If user-config does not exist, remediation is disabled until you create the object and set spec.nodeConditionRemediation.enabled to true.
To enable remediation, create the object:
apiVersion: dataplane-operator.doks.digitalocean.com/v1alpha1
kind: DataPlaneOperatorConfig
metadata:
name: user-config
spec:
nodeConditionRemediation:
enabled: trueThe spec.nodeConditionRemediation block is optional. When it is absent, remediation behaves as disabled (the same as enabled: false). When it is present, enabled is required:
| Field | Type | Description |
|---|---|---|
enabled |
boolean | When true, the node reconciler can remediate nodes that match a NodeRemediationConfig. When false, the operator updates metrics only and issues no remediation. |
Per-policy limits, such as inFlightLimit, observationDeadline, and the unhealthy rules, are set on NodeRemediationConfig, not on this object.
To verify that remediation is enabled, check the object:
kubectl get dataplaneoperatorconfig user-config -o yamlEven when remediation is disabled, the operator can still export node condition metrics. It issues no replacements until remediation is enabled.
Define Remediation Rules
A NodeRemediationConfig (NRC) is a cluster-scoped policy that defines which node conditions trigger remediation, how long to wait, and what action to take. You can define multiple NRCs. The operator selects one per node, as described in Node Selector Matching.
An NRC supports the following top-level spec fields:
| Field | Required | Type | Description |
|---|---|---|---|
nodeSelector |
No | map[string]string |
When non-empty, only nodes whose labels match every key/value pair use this config. When empty or omitted, this config acts as a default fallback for nodes that no more specific selector matches. |
startupGracePeriod |
Yes | duration string | Measured from node.metadata.creationTimestamp, the operator does not remediate the node until this period elapses, even if its conditions look unhealthy. This is a one-time bootstrap safeguard (for kubelet, CNI, and image pulls), not a requirement that the node stay healthy for this long. For example, 10m or 600s. Must be 0s or greater. |
inFlightLimit |
Yes | integer, 0 or greater |
Maximum number of remediations in flight for this NRC at once. 0 disables remediation for nodes that select this config, even when cluster-wide enabled is true. |
observationDeadline |
Yes | duration string | Maximum time to wait for a healthy replacement before releasing an in-flight slot without observation. This acts as a safety valve. For example, 4h. Must be 0s or greater. |
unhealthyConditions |
No | list | Rules that map a node condition type and status to a wait duration and remediation action. An empty list means this NRC never triggers remediation. |
Unhealthy Condition Rules
Each entry in unhealthyConditions has the following shape:
| Field | Required | Description |
|---|---|---|
type |
Yes | The node condition type (string). Standard kubelet types include Ready, DiskPressure, MemoryPressure, PIDPressure, and NetworkUnavailable. Any type present on node.status.conditions is valid, including custom types you define. |
status |
Yes | The condition status to treat as unhealthy: True, False, or Unknown (Kubernetes condition status strings). |
duration |
Yes | The minimum time the (type, status) pair must hold continuously on the node before remediation. Measured from condition.lastTransitionTime. For example, 5m or 300s. Must be 0s or greater. |
remediationAction |
Yes | The action to take when the rule fires. There is no default. You must set it explicitly. |
remediationAction accepts the following values:
| Value | Behavior |
|---|---|
cordon-drain-replace |
Replaces the worker through the DigitalOcean API with a drain, so workloads are evacuated before the replacement. |
force-replace |
Replaces the worker through the DigitalOcean API without a drain (SkipDrain: true). |
There is no alert-only action. Both supported values replace the worker.
The operator checks rules in list order. The first matching unhealthy condition whose duration has elapsed drives remediation.
Built-In Node Conditions
The following are the standard Kubernetes node conditions. Pair the type and status to match your monitoring or alerting model:
| Unhealthy signal | type |
status |
Notes |
|---|---|---|---|
| Not ready | Ready |
False |
Node not passing ready checks. |
| Ready unknown | Ready |
Unknown |
kubelet or contact issues. |
| Disk pressure | DiskPressure |
True |
|
| Memory pressure | MemoryPressure |
True |
|
| PID pressure | PIDPressure |
True |
|
| Network unavailable | NetworkUnavailable |
True |
Custom Node Conditions
Kubernetes allows any condition type string on Node.status.conditions. Use custom types when the built-in signals (Ready, DiskPressure, and so on) are not enough, for example, application-specific health, GPU drivers, or checks only your team understands.
The operator does not create custom conditions for you. You choose the type name, publish the condition on unhealthy nodes, and reference the same type and status in unhealthyConditions. The operator then:
- Compares each configured
(type, status)pair to the node’s current conditions. - Waits for the configured
durationafterlastTransitionTime. - Treats a new worker as healthy enough to complete observation when it is
Ready=Trueand does not match anyunhealthyConditionsentry on the selected NRC.
To add a custom condition:
- Choose a unique
typestring for your organization or workload, for exampleCustomNodeCondition. - Set that condition on nodes your health system considers failed. The
statusis usually"True". - Add a matching entry to your
NodeRemediationConfigwith adurationand aremediationAction. - Confirm the condition appears on a test node with
kubectl describe node <node-name>, under Conditions.
The following NRC fragment matches a custom condition:
unhealthyConditions:
- type: CustomNodeCondition
status: "True"
duration: 10m
remediationAction: cordon-drain-replaceUse the same type and status strings everywhere: in the detector, the NRC, and your runbooks.
Node Selector Matching
Only one NodeRemediationConfig applies to a given node at a time. When more than one NRC could apply to a node based on its nodeSelector, the operator picks the best match using this precedence:
- The config with the largest number of matching selector keys wins.
- If no selector matches, the operator uses an NRC with an empty
nodeSelector({}or omitted) as the default, if one exists. - If no selector matches and there is no default, the node is not remediated under any NRC.
In the following example, a pool-specific config overrides the default:
# Default for all nodes
apiVersion: dataplane-operator.doks.digitalocean.com/v1alpha1
kind: NodeRemediationConfig
metadata:
name: default
spec:
startupGracePeriod: 15m
inFlightLimit: 3
observationDeadline: 4h
nodeSelector: {}
unhealthyConditions:
- type: Ready
status: "False"
duration: 10m
remediationAction: cordon-drain-replace
---
# Stricter policy for a GPU pool
apiVersion: dataplane-operator.doks.digitalocean.com/v1alpha1
kind: NodeRemediationConfig
metadata:
name: gpu-pool
spec:
startupGracePeriod: 20m
inFlightLimit: 1
observationDeadline: 4h
nodeSelector:
doks.digitalocean.com/node-pool: gpu-workers
unhealthyConditions:
- type: CustomGPUHealthSignal
status: "True"
duration: 5m
remediationAction: cordon-drain-replaceWe recommend a longer startup grace period for large GPU nodes, which can take longer to initialize.
Disable Remediation for a Subset of Nodes
To disable remediation for a group of nodes, set inFlightLimit: 0 on the NRC that those nodes select. A cluster-wide enabled: true does not override this.
Remediation Lifecycle
Preconditions
The operator remediates a node only when all of the following hold:
DataPlaneOperatorConfig/user-confighasspec.nodeConditionRemediation.enabled: true.- A matching NRC exists with an
inFlightLimitgreater than0. - The node is past its
startupGracePeriod, measured from creation. - An
unhealthyConditionsrule matches and itsdurationhas elapsed sincelastTransitionTime. - The node is not already marked with a remediation-issued annotation.
- The node has the
doks.digitalocean.com/node-pool-idlabel, which is required for pool-scoped replacement and observation.
Remediation Steps
When the preconditions hold, the operator:
- Reserves an in-flight slot on the matched NRC.
- Sets remediation annotations on the node.
- Calls the DigitalOcean API to replace the worker (
cordon-drain-replaceorforce-replace). - Emits a
RemediationIssuedevent on the NRC. - Releases the slot and emits a
RemediationObservedevent when a healthy replacement appears in the same node pool.
In-Flight Remediation Budget
Each NRC has its own cap, set by inFlightLimit. A remediation is in flight from the moment it is issued until one of the following happens:
- A healthy replacement is observed in the same
doks.digitalocean.com/node-pool-id(aRemediationObservedevent). - The
observationDeadlineelapses without observation (aRemediationUnobservedevent).
When the budget is full, the operator does not issue another remediation for that NRC. It rechecks about once a minute and starts a new remediation when a slot frees up.
Monitor Remediation with Events
The operator records Kubernetes events on the NodeRemediationConfig that applied to the node, not on the Node object. Events are the primary way to see what happened during remediation.
To view events for an NRC:
kubectl describe noderemediationconfig <nrc-name>To list remediation events across all NRCs:
kubectl get events --field-selector involvedObject.kind=NodeRemediationConfigThe operator emits the following events:
| Reason | Type | When | Meaning |
|---|---|---|---|
RemediationIssued |
Normal | After a slot is reserved and the replace API is called. | Remediation started for a node. The message includes the action, node name, and pool ID. |
RemediationObserved |
Normal | A healthy replacement is observed. | The in-flight slot is released and the budget is freed. |
RemediationUnobserved |
Warning | The observationDeadline is exceeded. |
The slot is released without seeing a healthy replacement. |
RemediationFailed |
Warning | The replace API or a prior step failed. | The slot and remediation annotations are rolled back, and remediation is retried. |
InFlightLimitReached |
Warning | The budget became full while blocking a node. | This NRC’s in-flight cap is reached, and node remediation is requeued. |
InFlightLimitCleared |
Normal | The budget went from full to not full. | Capacity is available again on this NRC. |
MissingNodePoolLabel |
Warning | The node lacks the doks.digitalocean.com/node-pool-id label. |
Remediation is refused for that node. |
A RemediationIssued message looks like this:
Issued cordon-drain-replace on node "worker-pool-abc123" (pool "uuid-...").A RemediationObserved message looks like this:
Observed healthy replacement for node "worker-pool-abc123" in pool "uuid-..." (action cordon-drain-replace issued at 2026-05-29T12:00:00Z).Full Configuration Example
The following manifest enables remediation for the cluster and defines a default NRC that replaces nodes reporting a custom unhealthy signal:
apiVersion: dataplane-operator.doks.digitalocean.com/v1alpha1
kind: DataPlaneOperatorConfig
metadata:
name: user-config
spec:
nodeConditionRemediation:
enabled: true
---
apiVersion: dataplane-operator.doks.digitalocean.com/v1alpha1
kind: NodeRemediationConfig
metadata:
name: default
spec:
startupGracePeriod: 10m
inFlightLimit: 3
observationDeadline: 4h
nodeSelector: {}
unhealthyConditions:
- type: CustomNodeSignal
status: "True"
duration: 10m
remediationAction: cordon-drain-replaceTroubleshooting
| Symptom | What to check |
|---|---|
| No remediation | Confirm DataPlaneOperatorConfig/user-config has enabled: true, a matching NRC exists, inFlightLimit is greater than 0, the node is past its startupGracePeriod, the condition duration has elapsed, and the node is not already annotated with remediation-action-issued-at. |
| Wrong policy applied | Review selector precedence when multiple NRCs exist. Check node labels with kubectl get nodes --show-labels. |
| Stuck in flight | Run kubectl describe noderemediationconfig <nrc-name> and look for RemediationUnobserved or RemediationObserved events. Check the status ConfigMap entries and the observationDeadline. |
| Custom condition ignored | Confirm the type and status strings match exactly, the condition is present on the node (kubectl describe node <node-name>), and lastTransitionTime is old enough for the configured duration. |
| Remediation refused | Look for a MissingNodePoolLabel event and confirm the node has the doks.digitalocean.com/node-pool-id label. |
| Node never remediated again | Remediation annotations are still present. Remove them manually to allow a re-run on the same object name. |