derailed/k9s · warning
node is already cordoned
Error message
node is already cordoned
What it means
Node.ToggleCordon builds a drain.CordonHelper and calls UpdateIfRequired(cordon) to detect a no-op before patching. When the node's spec.unschedulable already equals the requested cordoned state, the patch would change nothing, so the DAO returns this error instead of issuing an empty patch.
Source
Thrown at internal/dao/node.go:63
slogs.Bool, cordon,
)
o, err := FetchNode(context.Background(), n.Factory, fqn)
if err != nil {
return err
}
h, err := drain.NewCordonHelperFromRuntimeObject(o, scheme.Scheme, n.gvr.GVK())
if err != nil {
slog.Debug("Fail to toggle cordon on node",
slogs.FQN, fqn,
slogs.Error, err,
)
return err
}
if !h.UpdateIfRequired(cordon) {
if cordon {
return fmt.Errorf("node is already cordoned")
}
return fmt.Errorf("node is already uncordoned")
}
dial, err := n.getFactory().Client().Dial()
if err != nil {
return err
}
err, patchErr := h.PatchOrReplace(dial, false)
if patchErr != nil {
return patchErr
}
if err != nil {
return err
}
return nil
}View on GitHub (pinned to 2d3ccc6ba2)
Solutions
- Refresh the node and skip the call when node.Spec.Unschedulable already equals the desired state
- Treat the error as a benign idempotency signal: log it and continue the surrounding drain/uncordon workflow
- In automation, read spec.unschedulable first and only toggle on difference
Example fix
// before
err := n.ToggleCordon(ctx, fqn, true)
// after
node, _ := dao.FetchNode(ctx, factory, fqn)
if node != nil && node.Spec.Unschedulable {
slog.Info("node already cordoned, skipping", slogs.FQN, fqn)
return nil
}
err := n.ToggleCordon(ctx, fqn, true) Defensive patterns
Strategy: validation
Validate before calling
node, err := dao.FetchNode(ctx, factory, fqn)
if err != nil { return err }
if node.Spec.Unschedulable {
slog.Info("node already cordoned", slogs.FQN, fqn)
return nil
}
return nodeDAO.ToggleCordon(ctx, fqn, true) Type guard
func isCordoned(n *v1.Node) bool {
return n != nil && n.Spec.Unschedulable
} Try / catch
if err := n.ToggleCordon(ctx, fqn, true); err != nil {
if strings.Contains(err.Error(), "already cordoned") {
return nil // idempotent success
}
return err
} Prevention
- Make cordon/uncordon workflows state-aware: read spec.unschedulable before toggling
- Treat both 'already' messages as no-op confirmations, never as hard failures
- After any drain helper runs, uncordon explicitly since drain leaves the node cordoned
When it happens
Trigger: Calling ToggleCordon(ctx, fqn, true) on a node whose spec.unschedulable is already true — double-cordon from stale UI state or unconditional scripts.
Common situations: Node view not refreshed between two cordon attempts; a drain workflow cordons first and the operator also cordons manually; automation that cordon/uncordons on a timer without reading current state.
Related errors
- node is already uncordoned
- node is cordoned
- expecting a maintainer for %q
- unable to locate node %s
- user is not authorized to list nodes
AI-assisted analysis of derailed/k9s@2d3ccc6ba2 (2026-08-15).
Data as JSON: /api/errors/baf5ffa19f032d77.
Report an issue: GitHub.