Tổng Quan
Trong hệ thống lưu trữ phân tán Ceph, việc điều chỉnh CRUSH weight của các OSD (Object Storage Daemon) là một thao tác quan trọng để cân bằng dữ liệu và đảm bảo hiệu suất hệ thống. Tuy nhiên, nếu không cẩn thận, quá trình này có thể gây ra hiện tượng backfilling dồn dập, ảnh hưởng đến hiệu năng toàn cluster.
Trong bài viết này, mình sẽ chia sẻ hai đoạn script bash được dùng để tự động:
- Giảm dần trọng số của các OSD (để rút dữ liệu ra hoặc chuẩn bị decommission OSD).
- Tăng dần trọng số của các OSD (để đưa OSD mới vào cluster một cách an toàn).
Hai đoạn script này rất phù hợp khi bạn cần thao tác với nhiều OSD mà vẫn muốn kiểm soát tình trạng backfilling, tránh quá tải cluster.
Bối Cảnh Sử Dụng
- Bạn có một số OSD đang cần rút khỏi cluster dần dần mà vẫn giữ hệ thống hoạt động ổn định.
- Bạn cần thêm OSD mới vào hệ thống, nhưng không muốn cluster backfill quá nhanh gây gián đoạn dịch vụ.
- Cần một cách an toàn, tự động, có kiểm soát để thay đổi CRUSH weight của nhiều OSD.
Mô Tả Script
1. Script Weight-down
Dùng để giảm CRUSH weight của một tập OSD, ví dụ từ 1.0 xuống 0.0, giúp từ từ rút dữ liệu ra khỏi OSD trước khi decommission.
#!/bin/bash
### NEED TO CHANGE
OSDs="8 11 14 19 24 29 34 39 44 49 54"
DECREMENT_WEIGHT=0.01 # IMPORTANT: negative for weight-down
TARGET_WEIGHT=0
CONTROL_BACKFILL=15
INTERVAL_CHECK=2 # second
WORK_DIR=/opt/weight-down
# basic validation on weird number
validate_weight () {
local w=$1
re='^[+-]?[0-9]+\.?[0-9]*$'
if ! [[ $w =~ $re && $DECREMENT_WEIGHT =~ $re && $TARGET_WEIGHT =~ $re && $INTERVAL_CHECK =~ $re ]] ; then
echo "xx Not a number" >&2
exit 1
elif (( $(echo "$w < $TARGET_WEIGHT" | bc -l) )); then
echo "xx Weight cannot smaller than target"
exit 1
elif (( $(echo "$TARGET_WEIGHT > 1.45" | bc -l) )) || (( $(echo "$TARGET_WEIGHT < 0" | bc -l) )); then
echo "xx TARGET_WEIGHT cannot be out of range [0;1.45]"
exit 1
elif (( $(echo "$DECREMENT_WEIGHT > 0.5" | bc -l) )) || (( $(echo "$DECREMENT_WEIGHT < 0" | bc -l) )); then
echo "xx DECREMENT_WEIGHT (0;0.5])"
exit 1
elif (( $(echo "$DECREMENT_WEIGHT < $TARGET_WEIGHT" | bc -l) )); then
echo "xx DECREMENT_WEIGHT cannot be lower than TARGET_WEIGHT"
exit 1
fi
return 0
}
## init the weight for listed OSD
## store to /opt/weight.X
for OSD in $OSDs; do
init_weight=$(printf '%.2f\n' $(ceph osd df | grep "^$OSD" | awk '{ print $3}'))
echo $init_weight > $WORK_DIR/weight.$OSD
echo "=Init $OSD with weight $init_weight"
validate_weight $init_weight
done
while true; do
sleep 1 &&
if (( $(ceph -s | grep "active+remapped+backfilling" | awk '{print $1}') < $CONTROL_BACKFILL )) 2>/dev/null; then
#TELERAM_MESSAGE=""
for OSD in $OSDs; do # 141 142
if (( $(ceph -s | grep "active+remapped+backfilling" | awk '{print $1}') < $CONTROL_BACKFILL )) 2>/dev/null; then
echo "+++++ Start: $(date)"
ceph -s | grep backfilling
## get current weight
w=$(printf '%.2f\n' $(ceph osd df | grep "^$OSD" | awk '{ print $3}'))
## only reweight if current > TARGET_WEIGHT
if (( $(echo "$w > $TARGET_WEIGHT" | bc -l) )); then
echo "++ osd.$OSD current weight: $w will increase by $DECREMENT_WEIGHT"
## increase the current weight
w=$(echo $w - $DECREMENT_WEIGHT | bc)
if (( $(echo "$w < 1" | bc -l) )); then w=0$w; fi # .21 -> 0.21
echo "++ reweight: $(date)"
## reweight
#if echo osd.$OSD $w; then
if ceph osd crush reweight osd.$OSD $w; then
#echo $w > $WORK_DIR/weight.$OSD;
#notify
TELERAM_MESSAGE="== New osd.$OSD weight $w\n$TELERAM_MESSAGE"
fi
else
continue
fi
sleep $INTERVAL_CHECK
fi
done
echo "++ Notify Telegram!"
else
### one endless loop case is when pgbackfill cleaned so fast and at least one OSD is not get TARGET
is_break=1
ceph osd df > /tmp/osd-df # reduce load ceph api
for OSD in $OSDs; do
w=$(printf '%.2f\n' $(grep "^$OSD" /tmp/osd-df | awk '{ print $3}'))
if (( $(echo "$w > $TARGET_WEIGHT" | bc -l) )); then
is_break=0
break
else
OSDs=$(echo $OSDs | sed s/$OSD//)
echo "++ osd is removed: $OSD. Current list: $OSDs"
fi
done
## value didn't changed thru for, no OSD < TARGET, then exit while
if (( $is_break == 1 )); then break 2; fi
fi
done
Quy trình:
OSD hiện tại: w=1.00
↓ Giảm mỗi bước: 0.01
↓ Kiểm tra backfill < CONTROL_BACKFILL
↓ Nếu OK, giảm tiếp
↓ Nếu quá ngưỡng, chờ
→ Dừng khi weight <= TARGET_WEIGHT
Sơ đồ mô phỏng:
[OSD.8] ---0.99---> 0.98 ---> 0.97 ---> ... ---> 0.00 (decommission)
^ ^ ^
kiểm tra backfilling <= CONTROL_BACKFILL
Các tham số quan trọng:
OSDs
: Danh sách OSD cần giảm.DECREMENT_WEIGHT
: Mức giảm mỗi lần.TARGET_WEIGHT
: Mức weight mục tiêu (thường là 0).CONTROL_BACKFILL
: Số lượng PG đang backfilling cho phép.INTERVAL_CHECK
: Thời gian chờ giữa các lần kiểm tra.
2. Script Weight-up
Dùng để từ từ đưa OSD mới vào cluster Ceph bằng cách tăng CRUSH weight từng bước.
#!/bin/bash
OSDs="568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589"
INCREMENT_WEIGHT=0.01 # IMPORTANT
TARGET_WEIGHT=1.7
INTERVAL_CHECK=1 # second
WORK_DIR=/opt/weight
# basic validation on weird number
validate_weight () {
local w=$1
re='^[+-]?[0-9]+\.?[0-9]*$'
if ! [[ $w =~ $re && $INCREMENT_WEIGHT =~ $re && $TARGET_WEIGHT =~ $re && $INTERVAL_CHECK =~ $re ]] ; then
echo "xx Not a number" >&2
exit 1
elif (( $(echo "$w > $TARGET_WEIGHT" | bc -l) )); then
echo "xx Weight cannot higher than target"
exit 1
elif (( $(echo "$TARGET_WEIGHT > 1.7" | bc -l) )) || (( $(echo "$TARGET_WEIGHT < 0" | bc -l) )); then
echo "xx TARGET_WEIGHT cannot be out of range [0;1.7]"
exit 1
elif (( $(echo "$INCREMENT_WEIGHT > 0.05" | bc -l) )) || (( $(echo "$INCREMENT_WEIGHT < 0" | bc -l) )); then
echo "xx INCREMENT_WEIGHT cannot be higher than 0.05"
exit 1
elif (( $(echo "$INCREMENT_WEIGHT > $TARGET_WEIGHT" | bc -l) )); then
echo "xx INCREMENT_WEIGHT cannot be higher than TARGET_WEIGHT"
exit 1
fi
return 0
}
## init the weight for listed OSD
## store to /opt/weight.X
for OSD in $OSDs; do
init_weight=$(printf '%.2f\n' $(ceph osd df | grep "^$OSD" | awk '{ print $3}'))
echo $init_weight > $WORK_DIR/weight.$OSD
echo "=Init $OSD with weight $init_weight"
validate_weight $init_weight
done
while true; do
sleep 1 &&
if (( $(ceph -s | grep -E "active\+remapped\+backfilling|active\+remapped\+backfill_wait" | awk '{sum+=$1} END {print sum}') < 15)) 2>/dev/null; then
for OSD in $OSDs; do # 141 142
echo "Waiting for osd.$OSD"
while true; do
if (( $(ceph -s | grep -E "active\+remapped\+backfilling|active\+remapped\+backfill_wait" | awk '{sum+=$1} END {print sum}') < 15 )) 2>/dev/null; then
echo "+++++ Start: $(date)"
ceph -s | grep backfill
## get current weight
w=$(printf '%.2f\n' $(ceph osd df | grep "^$OSD" | awk '{ print $3}'))
## only reweight if current < TARGET_WEIGHT
if (( $(echo "$w < $TARGET_WEIGHT" | bc -l) )); then
echo "++ osd.$OSD current weight: $w will increase by $INCREMENT_WEIGHT"
## increase the current weight
w=$(echo $w + $INCREMENT_WEIGHT | bc)
if (( $(echo "$w < 1" | bc -l) )); then w=0$w; fi # .21 -> 0.21
echo "++ reweight: $(date)"
## reweight
if ceph osd crush reweight osd.$OSD $w; then
#notify
TELERAM_MESSAGE="== New osd.$OSD weight $w\n$TELERAM_MESSAGE"
break # out while loop
fi
else # if current >= TARGET_WEIGHT
break # out while loop
fi # target-weight check
fi # backill check
sleep $INTERVAL_CHECK
done # inner while-loop
done # for-loop
else
### one endless loop case is when pgbackfill cleaned so fast and at least one OSD is not get TARGET
is_break=1
ceph osd df > /tmp/osd-df # reduce load ceph api
for OSD in $OSDs; do
w=$(printf '%.2f\n' $(grep "^$OSD" /tmp/osd-df | awk '{ print $3}'))
if (( $(echo "$w < $TARGET_WEIGHT" | bc -l) )); then
is_break=0
break
else
OSDs=$(echo $OSDs | sed s/$OSD//)
echo "++ osd is removed: $OSD. Current list: $OSDs"
fi
done
## value didn't changed thru for, no OSD < TARGET, then exit while
if (( $is_break == 1 )); then break 2; fi
fi
done
Quy trình:
OSD mới: w=0.00
↑ Tăng mỗi bước: 0.01
↑ Kiểm tra backfill < 15
↑ Nếu OK, tăng tiếp
→ Dừng khi weight >= TARGET_WEIGHT
Sơ đồ mô phỏng:
[OSD.570] 0.00 ---> 0.01 ---> 0.02 ---> ... ---> 1.70 (fully added)
^ ^ ^
kiểm tra backfill <= 15
Các tham số tương tự:
INCREMENT_WEIGHT
: Bước tăng mỗi lần.TARGET_WEIGHT
: Trọng số mục tiêu (có thể > 1 nếu bạn cần tăng thêm tải).
Ưu điểm
✅ Tự động hóa: Không cần can thiệp thủ công từng OSD.
✅ Kiểm soát chặt backfill: Luôn kiểm tra ngưỡng active+remapped+backfilling
trước khi tiếp tục.
✅ An toàn: Tránh gây quá tải mạng hoặc OSD do thao tác quá nhanh.
✅ Có thể mở rộng: Chỉ cần sửa danh sách OSD và trọng số, dùng được ở nhiều cluster khác nhau.
Nhược điểm
⚠️ Không xử lý song song hoàn toàn: Script tăng weight chạy lần lượt từng OSD.
⚠️ Phụ thuộc vào ceph osd df
và ceph -s
: Nếu các lệnh này trả về sai format hoặc quá tải, có thể gây lỗi.
⚠️ Không gửi thông báo: Đã để sẵn biến TELERAM_MESSAGE
nhưng chưa tích hợp thực tế.
So sánh 2 script
Tiêu chí | Script Giảm | Script Tăng |
---|---|---|
Mục tiêu | Rút OSD ra | Thêm OSD vào |
Trọng số mục tiêu | Gần 0 | ≥ 1.0–1.7 |
Ngưỡng kiểm soát backfill | CONTROL_BACKFILL tùy chọn | Mặc định 15 |
Cách xử lý OSD | Cho phép nhiều OSD chạy trong vòng lặp | Từng OSD xử lý riêng biệt |
Ví dụ Thực Tế
Giảm trọng số:
OSDs="11 12 13"
DECREMENT_WEIGHT=0.01
TARGET_WEIGHT=0.00
→ Kết quả: Sau vài phút, các OSD được giảm weight và bạn có thể safely ceph osd out X
.
Tăng trọng số:
OSDs="201 202"
INCREMENT_WEIGHT=0.01
TARGET_WEIGHT=1.7
→ Kết quả: Các OSD mới dần được thêm vào và bắt đầu nhận dữ liệu mới.
Lời khuyên khi sử dụng
- Test trước ở môi trường staging nếu chưa quen.
- Luôn giám sát cluster trong lúc script đang chạy, đề phòng lỗi logic hoặc quá tải bất ngờ.
- Điều chỉnh INTERVAL_CHECK phù hợp với tốc độ cluster. Cluster chậm nên để khoảng 3–5 giây.
- Kết hợp Telegram hoặc Prometheus Alertmanager để thông báo mỗi lần thay đổi weight.
✅ Bonus
Dùng nohup
cộng với “&” để chạy script trong nền rồi dùng tail -f
để theo dõi log là cách rất phổ biến và đơn giản:
nohup ./osd-reweight-up.sh &
tail -f nohup.out
– nohup
sẽ bỏ qua tín hiệu HUP (hang-up), cho phép tiến trình tiếp tục chạy ngay cả khi bạn đóng terminal.
– Dấu &
đẩy tiến trình ra background.
– Mặc định nohup
sẽ gom cả stdout và stderr vào file nohup.out
và tail -f
giúp bạn “live-view” nội dung file đó.
Một số gợi ý nâng cao
- Chuyển log ra file riêng
Thay vì đểnohup.out
, bạn có thể chỉ định rõ file log:nohup ./osd-reweight-up.sh > /var/log/ceph/osd-reweight-up.log 2>&1 & tail -f /var/log/ceph/osd-reweight-up.log
–> … 2>&1
gom cả output lẫn lỗi vào cùng file. - Dùng
disown
Nếu “quên” chạy bằngnohup
, bạn có thể:./osd-reweight-up.sh > my.log 2>&1 & disown
Khi đó tiến trình vẫn giữ chạy ngay cả khi bạn logout. - Sử dụng
screen
hoặctmux
– Tạo một session độc lập:screen -S reweight # trong session mới: ./osd-reweight-up.sh # nhấn Ctrl-A D để detach
– Bạn có thể reconnect bất cứ lúc nào:screen -r reweight
hoặctmux attach
. - Triển khai như một service (systemd)
Nếu bạn muốn script tự khởi động sau reboot hoặc dễ quản lý mà không cần login, có thể tạo một unit file:[Unit] Description=Ceph OSD Reweight Up [Service] ExecStart=/opt/weight/osd-reweight-up.sh StandardOutput=append:/var/log/ceph/osd-reweight-up.log StandardError=inherit Restart=on-failure [Install] WantedBy=multi-user.target
– Sau đósystemctl enable --now your-service.service
.
Kết luận
Hai đoạn script trên là công cụ nhỏ nhưng cực kỳ hiệu quả giúp quản trị viên Ceph kiểm soát trọng số OSD một cách an toàn, linh hoạt và tự động hóa cao. Dù có thể cải tiến thêm, nhưng chúng đã giúp mình rất nhiều khi thao tác với các cluster có hàng trăm OSD.
Nếu bạn đang phải quản lý Ceph trong môi trường production, đây là công cụ nên có trong “hộp đồ nghề” DevOps của bạn.
Nguồn tham khảo:
- Ceph Documentation – CRUSH Map
- Kinh nghiệm thực tế từ vận hành Ceph tại doanh nghiệp.