Skip to main content
RunBook Academy

CephXCII · Removing Storage NodesRemoving Storage Nodes

Verifying an evacuation is complete

Intermediate⏱ ~16 minceph

What you'll learn

  • Define what a completed evacuation looks like
  • Verify it thoroughly
  • Recognise a stalled evacuation
  • Confirm before proceeding to removal

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

Removing an OSD that still holds PGs the cluster needs is a data loss event. Verifying emptiness is what prevents it.

What complete means

An evacuated OSD:
  holds zero PGs
  reports zero used bytes beyond its overhead
  appears in no acting set
  the cluster is HEALTH_OK with no degraded or misplaced objects

All four, not just the first.

# PGs on the OSD
ID=12
ceph pg ls-by-osd ${ID} | wc -l

# its reported usage
ceph osd df | awk -v o=${ID} '$1==o {print $6, $7}'

# whether it appears in any acting set
ceph pg dump pgs --format json 2>/dev/null | python3 -c '
import sys, json
osd = ${ID}
d = json.load(sys.stdin)
n = sum(1 for pg in d.get("pg_stats", []) if osd in pg.get("acting", []) or osd in pg.get("up", []))
print("appears in", n, "PG sets")'

Verifying thoroughly

for id in $(ceph osd ls-tree ceph-07); do
  printf 'osd.%-4s pgs=%-5s ' "$id" "$(ceph pg ls-by-osd $id 2>/dev/null | tail -n +2 | wc -l)"
  ceph osd df | awk -v o="$id" '$1==o {printf "used=%s\n", $7}'
done
osd.84   pgs=0     used=1.1G
osd.85   pgs=0     used=1.1G

A small residual usage is BlueStore’s own overhead and is expected; the PG count reaching zero is the signal.

ceph -s
  data:
    pgs: 4096 active+clean

Every PG active+clean with no misplaced or degraded objects means the movement finished rather than stopped.

Recognising a stall

SignalMeaning
Misplaced count staticmovement stopped
PGs remaining on the OSD, unchangedthose PGs cannot be placed
backfill_toofull presentdestinations are full
ceph orch osd rm status shows no progressthe drain is stuck
ID=12
ceph orch osd rm status
ceph health detail | grep -i backfill
ceph pg ls-by-osd ${ID} | head
# PGID: a PG id from the ceph pg ls-by-osd output above
PGID=3.1f

# why can a specific PG not move?
ceph pg "$PGID" query | python3 -c '
import sys,json; d=json.load(sys.stdin)
print("up:", d.get("up"), "acting:", d.get("acting"))
for s in d.get("recovery_state", [])[:1]:
    print(s.get("name"), s.get("blocked_by"))'

Confirming before removal

The gate before removing anything:
  every OSD on the node reports zero PGs
  the cluster is HEALTH_OK
  no misplaced or degraded objects remain
  ceph orch osd rm status shows the drain complete
ceph -s | grep -E 'HEALTH_OK' && \
  [ "$(for id in $(ceph osd ls-tree ceph-07); do
        ceph pg ls-by-osd $id 2>/dev/null | tail -n +2 | wc -l; done | paste -sd+ | bc)" = "0" ] && \
  echo "SAFE TO REMOVE" || echo "NOT COMPLETE"

Quiz

Knowledge check · 4 questions

  1. Q1. A drained OSD reports zero PGs but still shows 1.1 GB used. What does this indicate?

  2. Q2. Zero PGs on an OSD is sufficient confirmation that it can be removed.

  3. Q3. Confirm an evacuation before removal.

    A node drain has been running for six hours. Some OSDs report zero PGs and others still hold a few. The misplaced count has not changed in an hour.

  4. Q4. What four conditions define a completed evacuation?

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

Production discipline

Require zero PGs on every OSD and HEALTH_OK with no misplaced objects before removing anything — the PG count alone can be zero while the cluster is still settling. Ignore residual usage on a drained OSD; BlueStore’s own structures never reach zero.

Cross-course references

  • Kubernetes: a drained node with no pods is not the same as a settled cluster
  • Linux: an evacuated array member must be confirmed unused before removal