Why Coral is not removing a node

The checks to run when a node you expected to go is still in the cluster.

Updated 29 October 2026 · Published by Zoorik

Short answer. As Coral rebalances your cluster, it empties nodes you don’t need, and your autoscaler removes them. A node often stays because a switch is off, a pod can’t move safely, there’s no room elsewhere, or the pool is at its minimum. Node list → “What’s holding them” shows the reason for each held node.

Check Node optimization and the Evictor are on

Look at: Settings, and the Evictor’s “Advanced settings”.

What it means: With Node optimization off, Coral plans no node removal. With the Evictor off, Coral still drains nodes while Node optimization is on.

The Evictor moves pods off lightly used nodes, and has its own switch in Settings → General. In “Dry run”, it only writes the nodes it would empty to the Event log.

What to do: Switch Node optimization on. When you are ready for the Evictor to move pods, switch it on and untick “Dry run”.

Check the node’s pool is enabled

Look at: the pool’s “Enabled” switch in Configuration → Node pools.

What it means: When it is off, Coral leaves that pool’s nodes alone.

What to do: Switch the pool on if Coral may empty its nodes.

Check what is holding the node

Look at: Node list → “What’s holding them”. Its “Nodes held” figure counts the nodes Coral can’t empty.

What it means: A held node is one Coral won’t empty, because a pod on it can’t be moved safely. The common reasons, and the fix for each, are below. How each one holds a node in Kubernetes is in Cluster Autoscaler not scaling down.

ReasonFix
The PodDisruptionBudget allows no disruption.
Add replicas or relax the PodDisruptionBudget.
A kube-system pod has no PodDisruptionBudget.
Add a PodDisruptionBudget that allows at least one disruption.
Data on the node’s own disk (hostPath).
Move the data to a persistent volume, or mark the pod safe to evict.
Nothing would recreate the pod.
Run it under a Deployment or StatefulSet.
Marked do-not-disrupt, not safe to evict, or excluded.
Remove the marker if moving it is fine.

What to do: Apply the fix for each reason. These markers keep a pod, or its node, in place:

  • karpenter.sh/do-not-disrupt: "true", on a pod or a node.
  • cluster-autoscaler.kubernetes.io/safe-to-evict: "false", on a pod.
  • coral.zoorik.com/removal-disabled: "true", on a node or a pod.

Which autoscaler reads which marker is in Safe-to-evict and do-not-disrupt.

Check the other nodes have room

Look at: the room on the cluster’s other Ready nodes, and the Optimization constraints page.

What it means: Before it moves anything, the agent checks the live cluster. Every pod on the node must fit on another Ready node, with its affinity, taints, spread, ports and zones. If a pod doesn’t fit, the node stays.

What to do: If the other nodes have no room, the node is still needed. If placement rules hold a pod here, open Optimization constraints. It lists workload setups that make optimization risky, such as topology spread, with the fix for each.

Check the pool is above its minimum

Look at: how many nodes the pool has, and your autoscaler’s minimum for it.

What it means: Every pool keeps at least one node. Coral also respects your autoscaler’s minimum pool size.

By default, the agent may reduce a pool’s minimum. With the Terraform module’s lower_minimums = false, it can’t.

What to do: The last node in a pool stays.

  • If you set lower_minimums = false, set it back to true, the default, to let the agent reduce the minimum.
  • If Terraform manages your node pools, each apply undoes what the agent changes. Tell Terraform to ignore those fields in your own code, as Your own node pool code shows.

Check who removes the empty node

Look at: a node Coral has emptied that is still in the cluster.

What it means: Coral empties the node, and your autoscaler removes it.

  • After “Wait before removing empty nodes”, Coral hands the empty node to your autoscaler. Its own timers decide when the node goes.
  • Pools that Karpenter or NAP already consolidates are left to them.
  • Coral deletes the machine itself only in fixed-size pools that nothing else manages.

What to do: Check your autoscaler’s own settings. To change Coral’s wait, set “Wait before removing empty nodes” in Configuration → Node scaling.

For a fixed-size pool, Coral needs the permission to remove nodes. If you declined it at connect, run:

zoorikctl cluster grant-node-deletion

If you installed with Helm, Install Coral with Terraform + Helm creates the same permission as code.

Check the node is not cordoned

Look at: the node’s state, and any cluster upgrade under way.

What it means: Coral skips cordoned, NotReady and deleting nodes. It never drains a node someone else cordoned. During a cluster upgrade, it keeps working but stays out of the upgrade’s way.

What to do: If you cordoned the node yourself, Coral leaves it to you. Uncordon it if Coral may empty it. During an upgrade window, we recommend pausing Node optimization and the Evictor.

Check the drain finished

Look at: the Event log, which lists everything Coral did or saw, newest first.

What it means: Coral cordons and drains one node at a time. If a drain can’t finish in time, the node goes back into service.

What to do: Find the node in the Event log. Then check what is holding it, as above.

Still stuck?

The Coral product guide and the Coral FAQ have the full detail.

Email founders@zoorik.com, or pick a time to talk (opens in a new tab). Tell us what Node list and the Event log show for the node. Remove tokens, keys and other secrets from anything you send.

Find your own number

One line in your cloud console. One PDF report with your estimated saving at list price.

Estimate your saving

Talk to the builders

Thirty minutes with the people who build Coral.

Book a demo (opens in a new tab)

Or write to founders@zoorik.com