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

  1. Refresh the node and skip the call when node.Spec.Unschedulable already equals the desired state
  2. Treat the error as a benign idempotency signal: log it and continue the surrounding drain/uncordon workflow
  3. 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

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


AI-assisted analysis of derailed/k9s@2d3ccc6ba2 (2026-08-15). Data as JSON: /api/errors/baf5ffa19f032d77. Report an issue: GitHub.