Tự động điều chỉnh CRUSH Weightc ho OSD trong Ceph

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 dfceph -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ảmScript Tăng
Mục tiêuRút OSD raThêm OSD vào
Trọng số mục tiêuGần 0≥ 1.0–1.7
Ngưỡng kiểm soát backfillCONTROL_BACKFILL tùy chọnMặc định 15
Cách xử lý OSDCho phép nhiều OSD chạy trong vòng lặpTừ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ả stdoutstderr vào file nohup.outtail -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ằng nohup, 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ặc tmux
    – 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ặc tmux 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ạttự độ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:

Bài viết gần đây

spot_img

Related Stories

Leave A Reply

Please enter your comment!
Please enter your name here

Đăng ký nhận thông tin bài viết qua email