Logo

Personal Ops Runbook

Personal runbook covering infrastructure operations for Cloud, Kubernetes, OpenStack, and Ceph environments. Includes deployment and teardown procedures, node management, cluster monitoring setup, and incident response workflows compiled from day-to-day operational work. Intended strictly for personal reference — configurations and scripts are environment-specific and not guaranteed to work as-is elsewhere.

Runbook: Vận hành Scrub / Deep-Scrub cho PG trong Ceph

Phạm vi: xác định PG đang queue hay đang chạy scrub, các cờ và config cản trở,
cách ép chạy an toàn trên cluster prod, ước lượng thời gian, và xử lý sự cố liên quan.
Áp dụng cho Ceph Pacific / Quincy / Reef (ghi chú riêng chỗ nào khác nhau giữa version).


1. Kiến thức nền (đọc 1 lần)

1.1. Shallow scrub vs Deep scrub

Shallow scrub Deep scrub
Kiểm tra Metadata: size, omap, attrs, so khớp object giữa các replica/shard Toàn bộ nội dung object: đọc data, tính checksum, so giữa các bản
I/O Nhẹ (chủ yếu metadata) Nặng (đọc toàn bộ dung lượng PG)
Chu kỳ mặc định osd_scrub_min_interval (1 ngày) → osd_scrub_max_interval (7 ngày) osd_deep_scrub_interval (7 ngày)
Timestamp last_scrub_stamp last_deep_scrub_stamp

Ghi chú: deep-scrub bao hàm shallow scrub. Một lần deep hoàn tất sẽ cập nhật cả hai timestamp.

1.2. Hai loại scrub theo nguồn gốc

Periodic (tự động) Operator-forced (ceph pg scrub/deep-scrub)
Cờ trong pg query must_scrub: false must_scrub: true (+ must_deep_scrub nếu deep)
Trong dump_scrubs forced: false, sched_time thật forced: true, sched_time: 1.000000 (= epoch 1s = ASAP)
Bị chặn bởi khung giờ osd_scrub_begin/end_hour Không
Bị chặn bởi osd_scrub_load_threshold Không
Bị chặn bởi cờ noscrub/nodeep-scrub (cluster hoặc pool) (vẫn bị chặn!)
Bị chặn bởi osd_max_scrubs (hết slot)

Điểm hay nhầm nhất: forced scrub bỏ qua khung giờ và load, nhưng KHÔNG bỏ qua cờ noscrub/nodeep-scrub và giới hạn slot.

1.3. Điều kiện để một scrub thực sự khởi động

Tất cả phải thỏa cùng lúc:

  1. PG ở trạng thái active+clean (không đang recovery/backfill/degraded)
  2. Không có cờ noscrub / nodeep-scrubcluster-wide (ceph osd set)
  3. Không có cờ noscrub / nodeep-scrubpool chứa PG (ceph osd pool set)
  4. Primary OSD reserve được slot scrub trên TẤT CẢ OSD trong acting set (replicated size 3 → 3 OSD; EC 6+2 → cả 8 OSD phải rảnh). Mỗi OSD tối đa osd_max_scrubs scrub đồng thời (mặc định thay đổi theo version: 1 ở bản cũ, 3 từ Reef).
  5. Nếu là periodic: đúng khung giờ + load 1 phút của node < osd_scrub_load_threshold
  6. Nếu recovery đang diễn ra và osd_scrub_during_recovery=false (mặc định): scrub periodic bị hoãn

2. Chẩn đoán: PG đang QUEUE, đang CHẠY, hay ĐÃ XONG?

2.1. Bảng tra nhanh theo STATE

ceph pg ls | grep '^<PGID>'
STATE hiển thị Nghĩa
active+clean Không scrub gì, hoặc đang nằm queue
active+clean+scrubbing Đang shallow scrub
active+clean+scrubbing+deep Đang deep scrub
...+scrubbing+deep+repair Đang deep scrub kèm sửa lỗi (do pg repair)
active+clean+inconsistent Scrub trước đó phát hiện lỗi, chưa repair

Từ Quincy/Reef, cột cuối của ceph pg ls còn hiện chuỗi schedule dạng queued for scrub / queued for deep scrub / scrubbing for ... / periodic scrub scheduled @ <time> — đọc luôn được từ đây.

2.2. Phân biệt chính xác bằng pg query

ceph pg <PGID> query | jq '.scrubber'

Đang nằm queue (chưa chạy):

{
  "active": false,
  "must_scrub": true,
  "must_deep_scrub": true,
  "schedule": "queued for deep scrub"
}

Đang chạy:

{
  "active": true,
  "is_deep": true,           // true = deep, false = shallow
  "state": "...",            // pha hiện tại của scrubber FSM
  "start": "3:2f000000:::...",  // ranh giới chunk hiện tại trong keyspace
  "end":   "3:2f100000:::..."
}

Quy tắc vàng: active: false = chưa chạy, bất kể must_* là gì. must_deep_scrub: true chỉ nghĩa là lệnh đã được ghi nhận — cờ pool vẫn có thể chặn ở khâu thực thi.

2.3. Xem hàng đợi trên OSD primary

ceph pg map <PGID>                      # lấy primary, vd osd.889
ceph tell osd.<ID> dump_scrubs | jq '.[] | select(.pgid | startswith("<PGID>"))'

Đọc kết quả: - forced: true + sched_time: "1.000000" → operator-forced, xếp đầu hàng, chờ điều kiện - sched_time trong tương lai → periodic, chưa tới giờ - sched_time trong quá khứ mà vẫn chưa chạy → bị chặn bởi cờ/slot/khung giờ

Lưu ý với EC pool: PGID trong dump_scrubs có hậu tố shard, vd 3.477s0 — dùng startswith khi lọc.

2.4. Xác nhận đã xong

ceph pg <PGID> query | jq '.info.stats | {last_scrub_stamp, last_deep_scrub_stamp, last_scrub_duration}'
  • last_deep_scrub_stamp nhảy sang thời điểm vừa rồi → deep-scrub hoàn tất
  • last_scrub_duration (giây) → dùng để ước lượng lần sau
  • Kết quả OK/lỗi nằm trong log OSD primary:
# host chứa primary OSD
grep '<PGID>' /var/log/ceph/*/ceph-osd.<ID>.log | grep -i scrub
# Mong đợi:  "deep-scrub starts"  →  "deep-scrub ok"
# Nếu lỗi:   "deep-scrub 1 errors" → PG chuyển inconsistent

2.5. Nhìn toàn cluster

ceph pg ls scrubbing                 # mọi PG đang scrub
ceph pg ls scrubbing+deep            # chỉ deep
ceph pg ls scrubbing | wc -l         # đếm nhanh
ceph pg dump pgs_brief 2>/dev/null | awk '$2 ~ /scrubbing/'

3. Các cờ và config cản trở — checklist tra theo thứ tự

Khi PG nằm queue mãi không chạy, kiểm tra lần lượt:

3.1. Cờ cluster-wide

ceph osd dump | head -5 | grep flags
# hoặc: ceph status  (hiện cảnh báo "noscrub,nodeep-scrub flag(s) set")

Gỡ / set:

ceph osd unset nodeep-scrub      # gỡ
ceph osd set   nodeep-scrub      # set

3.2. Cờ per-pool ⚠️ dễ bị bỏ sót nhất

Cờ pool KHÔNG hiện trong ceph status, KHÔNG hiện ở dòng flags đầu của osd dump. Phải nhìn đúng dòng pool:

ceph osd dump | grep "^pool <ID> "
# Tìm: flags hashpspool,...,noscrub,nodeep-scrub,...

Gỡ / set:

ceph osd pool set <POOL> nodeep-scrub false   # false = XÓA cờ
ceph osd pool set <POOL> noscrub     false
ceph osd pool set <POOL> nodeep-scrub true    # set lại

Bản chất: cờ là 1 bit trong OSDMap, chặn ở khâu thực thi. Gỡ cờ: - KHÔNG di chuyển dữ liệu, không peering, không backfill - KHÔNG tự khởi động scrub — chỉ ngừng chặn - Chỉ bump 1 epoch OSDMap mới, chi phí ~0 - Tùy version, noscrub có thể chặn luôn cả deep-scrub → nếu gỡ nodeep-scrub rồi vẫn im, gỡ thêm noscrub

3.3. Config scheduler

ceph config get osd osd_max_scrubs              # slot đồng thời / OSD
ceph config get osd osd_scrub_begin_hour        # khung giờ (giờ UTC của node,
ceph config get osd osd_scrub_end_hour          #  theo đồng hồ hệ thống OSD)
ceph config get osd osd_scrub_load_threshold    # loadavg 1' tối đa
ceph config get osd osd_scrub_sleep             # nghỉ giữa các chunk (throttle)
ceph config get osd osd_scrub_during_recovery   # cho scrub khi đang recovery?
ceph config get osd osd_scrub_begin_week_day    # 0=CN..6=T7 (nếu giới hạn theo ngày)
ceph config get osd osd_scrub_end_week_day

Nhớ: begin/end hour và load threshold chỉ chặn periodic, không chặn forced.

3.4. Trạng thái PG và cluster

ceph pg <PGID> query | jq '.state'    # phải là active+clean
ceph status                            # recovery/backfill đang chạy?

PG đang degraded/recovering sẽ không scrub. Với EC, chỉ cần 1 trong các OSD của acting set đang bận scrub PG khác là cả PG phải đợi reserve.

3.5. Bảng triệu chứng → nguyên nhân

Triệu chứng Nguyên nhân khả dĩ
must_deep_scrub: true, active: false, nằm im nhiều giờ Cờ pool/cluster nodeep-scrub, hoặc hết slot trên 1 OSD trong acting set
Periodic không bao giờ chạy, forced thì chạy Ngoài khung giờ, hoặc load > threshold
dump_scrubsforced:true nhưng STATE mãi active+clean Gần như chắc chắn cờ noscrub/nodeep-scrub
Nhiều PG scrubbing treo lâu bất thường OSD chậm/ổ lỗi trong acting set; xem ceph pg <PGID> query \| jq '.scrubber.state' có kẹt 1 pha không
last_deep_scrub_stamp cũ hàng chục ngày trên cả pool Cờ pool bị set rồi bị quên (rất phổ biến sau đợt recovery)

4. Thao tác chuẩn

4.1. Ép scrub 1 PG

ceph pg scrub <PGID>          # shallow
ceph pg deep-scrub <PGID>     # deep (bao hàm shallow)
ceph pg repair <PGID>         # deep + tự sửa lỗi tìm thấy — chỉ dùng khi đã hiểu lỗi

Sau khi ra lệnh, xác nhận đã ghi nhận:

ceph pg <PGID> query | jq '.scrubber | {active, must_scrub, must_deep_scrub, schedule}'

Theo dõi tới khi chạy:

watch -n 10 'ceph pg ls | grep "^<PGID>"'
# chờ STATE = active+clean+scrubbing+deep

4.2. Ép scrub theo OSD / theo pool

ceph osd scrub <OSD_ID>            # mọi PG mà OSD này là primary
ceph osd deep-scrub <OSD_ID>
ceph osd deep-scrub all            # toàn cluster — cẩn trọng

4.3. Trick: ép deep-scrub 1 PG trên pool đang bị khóa noscrub (prod)

Bài toán: pool có nodeep-scrub để bảo vệ prod, nhưng cần deep 1 PG cụ thể. Gỡ cờ sẽ mở cho cả nghìn PG khác → tận dụng việc periodic bị chặn bởi khung giờ còn forced thì không:

# 0. Ghi nhận trạng thái + giờ hiện tại
date -u
ceph osd dump | grep "^pool <ID> "

# 1. Đảm bảo cửa sổ periodic đang ĐÓNG tại thời điểm này
#    (nếu giờ UTC hiện tại nằm trong [begin_hour, end_hour) thì tạm dời cửa sổ đi chỗ khác)
ceph config set osd osd_scrub_begin_hour 3
ceph config set osd osd_scrub_end_hour 4

# 2. Van an toàn phụ
ceph config set osd osd_max_scrubs 1
ceph config set osd osd_scrub_sleep 0.5

# 3. Đặt lệnh forced TRƯỚC, rồi mới mở cổng
ceph pg deep-scrub <PGID>
ceph osd pool set <POOL> nodeep-scrub false

# 4. Theo dõi: PG mục tiêu phải chạy, tổng số scrubbing không được vọt
watch -n 5 'ceph pg ls | grep "^<PGID>"; echo ---; ceph pg ls scrubbing | wc -l'

# 5. Khi STATE = scrubbing+deep → đóng cổng lại ngay
ceph osd pool set <POOL> nodeep-scrub true
ceph pg ls | grep "^<PGID>"     # xác nhận scrub vẫn tiếp tục
#   (scrub forced đang chạy thường không bị abort khi cờ set lại;
#    nếu thấy rớt về active+clean thì gỡ cờ lần nữa và đợi xong hẳn)

# 6. Trả config
ceph config set osd osd_scrub_begin_hour <cũ>
ceph config set osd osd_scrub_end_hour   <cũ>
ceph config set osd osd_max_scrubs       <cũ>
ceph config rm  osd osd_scrub_sleep

Nút dừng khẩn cấp mọi lúc:

ceph osd pool set <POOL> nodeep-scrub true    # chặn mọi scrub CHƯA bắt đầu

4.4. Trả nợ backlog scrub cả pool (sau thời gian dài bị khóa)

Không thả tự do. Mở dần với throttle:

ceph config set osd osd_max_scrubs 1
ceph config set osd osd_scrub_sleep 0.2          # tăng nếu client bị ảnh hưởng
# giữ nguyên khung giờ đêm, rồi gỡ cờ:
ceph osd pool set <POOL> noscrub false
ceph osd pool set <POOL> nodeep-scrub false

Theo dõi hằng đêm:

ceph pg ls scrubbing | wc -l
ceph pg dump pgs 2>/dev/null | awk '{print $22}' | sort | uniq -c   # phân bố last_deep_scrub_stamp
ceph status    # cảnh báo "pgs not deep-scrubbed in time" phải giảm dần

Xong backlog thì nới osd_max_scrubs về mặc định.


5. Ước lượng thời gian & impact

5.1. Ước lượng

  • Nguồn tốt nhất: last_scrub_duration (giây) trong pg query .info.stats — của chính PG đó lần trước.
  • Deep-scrub đọc toàn bộ dung lượng vật lý của PG trên mỗi OSD tham gia:
  • Replicated size N: mỗi OSD đọc ~ BYTES của PG
  • EC k+m: mỗi OSD đọc ~ BYTES / k (mỗi shard)
  • HDD với object nhỏ/nhiều: bị IOPS-bound chứ không phải throughput-bound. PG ~1M objects trên HDD thường mất 1–3 giờ deep, cộng thêm nếu osd_scrub_sleep > 0.
  • osd_scrub_sleep 0.5 có thể kéo dài scrub lên nhiều lần nhưng gần như triệt tiêu impact lên client — đánh đổi hợp lý cho prod.

5.2. Impact

  • Deep-scrub là read-only trên data. Không ghi, không di chuyển dữ liệu.
  • Phạm vi impact = đúng các OSD trong acting set của PG (EC 6+2 = 8 OSD). Không cluster-wide.
  • Knob throttle chính: osd_scrub_sleep, osd_max_scrubs, osd_scrub_chunk_min/max, và ở bản mới osd_scrub_cost / mClock profile (Quincy+: scrub được xếp lớp QoS qua mClock, profile high_client_ops tự hạ ưu tiên scrub).

6. Xử lý kết quả scrub lỗi

Khi deep-scrub phát hiện lỗi, PG → active+clean+inconsistent, ceph status báo HEALTH_ERR: X scrub errors.

rados list-inconsistent-pg <POOL>
rados list-inconsistent-obj <PGID> --format=json-pretty   # xem lỗi ở object nào, shard nào, loại gì

Đọc kỹ output trước khi repair: lỗi read_error (ổ đĩa) khác với omap_digest_mismatch khác với size_mismatch. Kiểm tra SMART/dmesg của OSD nghi vấn:

ceph device ls-by-daemon osd.<ID>
smartctl -a /dev/<dev>

Sửa:

ceph pg repair <PGID>

pg repair = deep-scrub + ghi đè bản hỏng bằng bản được bầu là đúng (authoritative). Trên replicated bản rất cũ có rủi ro chọn nhầm bản; trên các bản hiện tại và trên EC (dựng lại từ parity) là an toàn trong đa số trường hợp. Nếu nghi ổ đĩa hỏng vật lý → cân nhắc out OSD đó thay vì chỉ repair.


7. Cheatsheet 1 màn hình

# PG này đang gì?
ceph pg ls | grep '^<PGID>'                                  # STATE + schedule
ceph pg <PGID> query | jq '.scrubber'                        # active? must_*? is_deep?
# active:false = còn queue. active:true + is_deep:true = đang deep.

# Vì sao chưa chạy?
ceph osd dump | head -5 | grep flags                         # cờ cluster
ceph osd dump | grep "^pool <ID> "                           # cờ pool  ← hay quên
ceph config get osd osd_max_scrubs
ceph config get osd osd_scrub_begin_hour; ceph config get osd osd_scrub_end_hour
ceph pg map <PGID>                                           # acting set
ceph tell osd.<PRIMARY> dump_scrubs | jq '.[]|select(.pgid|startswith("<PGID>"))'

# Ép chạy / dừng
ceph pg deep-scrub <PGID>
ceph osd pool set <POOL> nodeep-scrub false                  # mở cổng
ceph osd pool set <POOL> nodeep-scrub true                   # đóng cổng khẩn cấp

# Xong chưa?
ceph pg <PGID> query | jq '.info.stats|{last_scrub_stamp,last_deep_scrub_stamp,last_scrub_duration}'

# Toàn cảnh
ceph pg ls scrubbing+deep
ceph status

8. Ghi chú version

  • Pacific trở về trước: osd_max_scrubs mặc định 1; output .scrubber trong pg query ít trường hơn; chuỗi schedule trong pg ls chưa có.
  • Quincy: mClock scheduler thành mặc định — throttle scrub qua mClock profile; osd_scrub_sleep bị mClock bỏ qua ở một số cấu hình (dùng profile thay vì sleep).
  • Reef+: osd_max_scrubs mặc định 3; scrubber FSM viết lại, dump_scrubs và trường .scrubber.state chi tiết hơn.
  • Luôn đối chiếu ceph config help <option> trên đúng version của cluster trước khi tin mặc định trong tài liệu.

Cập nhật lần cuối: 2026-08. Kiểm chứng lệnh trên cluster staging trước khi đưa vào quy trình chính thức.