Skip to main content
RunBook Academy

CephXCII · Removing Storage NodesRemoving Storage Nodes

CRUSH map hygiene after removals

Intermediate⏱ ~16 mincephcrushtool

What you'll learn

  • Identify CRUSH residue after removals
  • Decide what to clean and what to keep
  • Clean safely
  • Prevent accumulation

Prerequisites

None — start here.

Verified against Ceph Tentacle 20.2.x · Ceph Squid 19.2.x (supported previous) · cephadm matches the verified Ceph release · podman 4.x · csi-rbd and csi-cephfs current · RBD / CephFS / RGW current (matches Ceph release) · Linux kernel 5.15+ (5.10 minimum) · Ubuntu 24.04 LTS (Ceph host baseline) · Debian 12 (Bookworm) (Ceph host baseline) · Rocky Linux / RHEL / AlmaLinux 9.x (Ceph host baseline) · Proxmox VE 9.x (cross-course integration) · Kubernetes 1.31+ (cross-course integration) · 2026-08-18

Not yet marked complete on this device.

Why this matters in production

A CRUSH map accumulates empty buckets, stale rules, and orphaned entries. None breaks anything and together they make the map an unreliable description of the cluster.

The residue

ResidueLeft by
Empty host bucketsremoving all a host’s OSDs
Empty rack or zone bucketsremoving the last host in them
Unused rulespools deleted, or rules created for testing
destroyed OSD entries--replace where the replacement never arrived
Orphaned auth entriespartial OSD removal
Device classes with no membersall devices of a class removed
ceph osd crush tree
ceph osd crush rule ls
ceph osd tree | grep -E 'destroyed|DNE'
ceph osd crush class ls

Finding it

# empty buckets
ceph osd crush tree --format json | python3 -c '
import sys, json
def walk(n, path=""):
    p = path + "/" + n["name"]
    kids = n.get("children", [])
    if n["type"] not in ("osd",) and not kids:
        print("empty:", n["type"], n["name"])
    for c in kids:
        if isinstance(c, dict): walk(c, p)
for r in json.load(sys.stdin)["nodes"]:
    if r.get("type") == "root": walk(r)'
# rules not used by any pool
used=$(ceph osd pool ls detail | grep -oE 'crush_rule [0-9]+' | awk '{print $2}' | sort -u)
ceph osd crush rule ls | while read r; do
  id=$(ceph osd crush rule dump "$r" | python3 -c 'import sys,json;print(json.load(sys.stdin)["rule_id"])')
  echo "$used" | grep -qx "$id" || echo "unused rule: $r (id $id)"
done
# destroyed OSDs with no replacement
ceph osd tree | grep destroyed

What to clean and what to keep

ItemDecision
Empty host bucket, host gone permanentlyremove
Empty host bucket, host returningkeep
Empty rack, no plans for itremove
Empty rack, expansion planned into itkeep
Unused rule, no plansremove
Unused rule, used by a documented procedurekeep and document
destroyed OSD, replacement comingkeep — the ID is reserved for it
destroyed OSD, no replacementpurge to release the ID
# the destroyed OSD id from the `ceph osd tree | grep destroyed` output above:
OSD_ID=13

ceph osd purge "$OSD_ID" --yes-i-really-mean-it

Cleaning safely

# a bucket must be empty before removal
RULENAME=rulename
ceph osd crush remove ceph-07

# a rule must be unused
ceph osd crush rule rm ${RULENAME}

Both commands refuse if the object is in use, which makes the cleanup safe: an attempt that would break something fails rather than succeeding.

# verify the tree afterwards
ceph osd crush tree
ceph -s

Preventing accumulation

Make cleanup part of the removal procedure rather than a separate task:
  remove OSDs
  remove the host
  remove the host bucket
  remove the rack bucket if now empty
  purge any destroyed IDs not awaiting a replacement
# a periodic audit
ceph osd crush tree | grep -B1 -A1 'rack\|host' | head -40
ceph osd tree | grep -cE 'destroyed|DNE'

Quiz

Knowledge check · 4 questions

  1. Q1. What does a `destroyed` OSD entry in the CRUSH tree reserve?

  2. Q2. CRUSH cleanup commands require careful verification because they can break placement.

  3. Q3. Audit a CRUSH map after several years of changes.

    A cluster has had many hosts added and removed over several years. The CRUSH tree contains empty buckets and there are several destroyed OSD entries of unknown age.

  4. Q4. Why does CRUSH residue matter if it breaks nothing?

Passing score: 75%. Answers are checked in this browser.

Production discipline

Add CRUSH cleanup to the removal procedure rather than treating it as a separate task — empty buckets and destroyed entries accumulate and make failure domain calculations wrong. The cleanup commands refuse when the object is in use, so the audit is low-risk.

Cross-course references

  • Kubernetes: stale resource definitions accumulate the same way and mislead the same way
  • Linux: configuration describing hardware that no longer exists misleads every reader