راهنمای جامع Ceph
Englishشروع کنید
در این صفحه
فصل ۲

کار با ‏Ceph Block Device‏ (RBD)

۵۰ دقیقه مطالعهویرایش این صفحه
حوصله ندارم این فصل در چند خط
  • هر ‏image‏ در ‏RBD‏ مجموعه‌ای ‏thin‏ از اشیای ۴ مگابایتی ‏RADOS‏ است که روی همه‌ی ‏OSD‏های ‏pool‏ پخش می‌شود؛ به هر کلاینت کاربر جداگانه با ‏profile rbd‏ بدهید، نه ‏client.admin‏.
  • ‏krbd‏ سریع و بی‌وابستگی است اما در قابلیت‌ها عقب‌تر است؛ ‏librbd‏ (در ‏QEMU‏ و ‏rbd-nbd‏) کامل‌ترین مسیر است.
  • ‏snapshot‏ فوری و ‏copy-on-write‏ است، ‏clone‏ قابل نوشتن و ارزان است و ‏flatten‏ وابستگی ‏clone‏ به والد را قطع می‌کند؛ ‏snapshot‏ بدون ‏freeze‏ فقط ‏crash-consistent‏ است.
  • ‏mirroring‏ در حالت ‏journal‏ (RPO چند ثانیه، نوشتن دوبرابر) یا ‏snapshot‏ (RPO برابر زمان‌بندی) کار می‌کند؛ ‏promote‏ اجباری بدون ‏fencing‏ خطر ‏split-brain‏ دارد.

۲-۱ پیش از شروع#

۲-۱-۱ این فصل به چه سؤال‌هایی جواب می‌دهد؟#

‏RBD‏ (RADOS Block Device) پرکاربردترین رابط ‏Ceph‏ است. بیشتر کلاسترهای ‏Ceph‏ دنیا، دیسک ماشین‌های مجازی ‏OpenStack‏، ‏Proxmox‏ و ‏CloudStack‏ یا ‏volume‏های پایدار ‏Kubernetes‏ را از همین رابط تأمین می‌کنند. در این فصل یاد می‌گیریم:

  • یک ‏image‏ در ‏RBD‏ واقعاً چیست و چطور روی اشیای ‏RADOS‏ نگاشت می‌شود؟
  • چطور یک کلاینت را با دسترسی حداقلی پیکربندی کنیم و ‏image‏ را بسازیم، ‏map‏ کنیم و تغییر اندازه دهیم؟
  • ‏snapshot‏ و ‏clone‏ چطور کار می‌کنند و چرا اساس «قالب طلایی» ماشین‌های مجازی‌اند؟
  • ‏RBD mirroring‏ چیست، دو حالت ‏journal‏ و ‏snapshot‏ چه فرقی دارند و چطور تکثیر یک‌طرفه و دوطرفه را برقرار کنیم؟
  • هنگام سانحه چطور سایت پشتیبان را ارتقا دهیم و بعد از بازگشت سایت اصلی، داده را برگردانیم؟

۲-۱-۲ پیش‌نیاز#

کلاستر آزمایشی فصل ۱ با یک ‏pool‏ به نام ‏rbd‏ و ماشین ‏client-node1‏. برای بخش ‏mirroring‏ به یک کلاستر دوم نیاز دارید؛ ساده‌ترین راه، ‏bootstrap‏ یک کلاستر تک‌نودی دیگر با ‏cephadm‏ است (با ‏--single-host-defaults‏).

۲-۲ ‏RBD‏ از درون#

۲-۲-۱ ‏image‏، شیء و ‏striping‏#

یک ‏image‏ در ‏RBD‏ دیسکی مجازی با اندازه‌ی ثابت است که روی اشیای ‏RADOS‏ پخش می‌شود. به‌طور پیش‌فرض هر شیء ۴ مگابایت است (‏--object-size‏)، پس یک ‏image‏ صد گیگابایتی حداکثر ۲۵٬۶۰۰ شیء دارد. اما ‏RBD‏ ‏thin provisioned‏ است: شیئی که هنوز در آن نوشته نشده اصلاً وجود ندارد و فضایی مصرف نمی‌کند.

شکل ۲-۱: ساختار یک image در RADOS : شیء header برای فراداده و اشیای داده‌ی ۴ مگابایتی که فقط پس از نوشتن ساخته می‌شوند
شکل ۲-۱: ساختار یک ‏image‏ در ‏RADOS‏: شیء ‏header‏ برای فراداده و اشیای داده‌ی ۴ مگابایتی که فقط پس از نوشتن ساخته می‌شوند

برای هر ‏image‏ (در قالب ۲ که از سال‌ها پیش پیش‌فرض است) این اشیاء در ‏pool‏ وجود دارند:

شیءمحتوا
‏rbd_directory‏نگاشت نام ‏image‏ها به شناسه‌شان در ‏pool‏
‏rbd_id.<name>‏شناسه‌ی یکتای ‏image‏
‏rbd_header.<id>‏فراداده: اندازه، ترتیب شیء، ویژگی‌ها، فهرست ‏snapshot‏ها، والد (برای ‏clone‏)
‏rbd_data.<id>.<offset>‏داده‌ی واقعی؛ شماره‌ی ‏hex‏ نشان‌دهنده‌ی جای شیء در ‏image‏
‏rbd_object_map.<id>‏نقشه‌ی وجود اشیاء (اگر ویژگی ‏object-map‏ فعال باشد)
◆ عمیق‌تر

چون هر شیء ۴ مگابایتی با ‏CRUSH‏ مستقلاً روی ‏OSD‏ها جای‌گذاری می‌شود، یک ‏image‏ بزرگ عملاً روی همه‌ی ‏OSD‏های ‏pool‏ پخش است. این همان دلیلی است که ‏RBD‏ در ‏I/O‏ موازی (مثلاً عمق صف بالا) خیلی خوب مقیاس می‌گیرد، اما در ‏I/O‏ تک‌رشته‌ای با عمق صف ۱ تأخیر یک رفت‌وبرگشت شبکه و تکثیر را تمام و کمال می‌پردازد. ‏striping‏ سفارشی (‏--stripe-unit‏ و ‏--stripe-count‏) می‌تواند برای بارهای ترتیبی بزرگ مفید باشد، اما ‏krbd‏ فقط برخی ترکیب‌ها را پشتیبانی می‌کند؛ پیش‌فرض را بی‌دلیل تغییر ندهید.

۲-۲-۲ ویژگی‌های ‏image‏#

هر ‏image‏ مجموعه‌ای از ویژگی‌ها (‏features‏) دارد که هنگام ساخت تعیین می‌شود و برخی بعداً قابل روشن یا خاموش کردن است:

ویژگیکاربردوابستگی
‏layering‏پشتیبانی از ‏clone‏—
‏exclusive-lock‏تنها یک کلاینت در هر لحظه می‌تواند بنویسد؛ پیش‌نیاز ویژگی‌های پیشرفته—
‏object-map‏ردیابی اشیای موجود؛ سرعت دادن به ‏du‏، ‏diff‏، ‏export‏ و حذف‏exclusive-lock‏
‏fast-diff‏محاسبه‌ی سریع تفاضل بین ‏snapshot‏ها‏object-map‏
‏deep-flatten‏امکان ‏flatten‏ کردن ‏clone‏ همراه با ‏snapshot‏هایش—
‏journaling‏ثبت همه‌ی تغییرات در ‏journal‏ برای ‏mirroring‏ حالت ‏journal‏‏exclusive-lock‏

پیش‌فرض نسخه‌های جدید ‏layering, exclusive-lock, object-map, fast-diff, deep-flatten‏ است. ماژول هسته (‏krbd‏) در نسخه‌های جدید لینوکس همه‌ی این‌ها به‌جز ‏journaling‏ را پشتیبانی می‌کند؛ روی هسته‌های قدیمی ممکن است هنگام ‏map‏ خطای «‏image uses unsupported features‏» بگیرید.

۲-۲-۳ مسیرهای دسترسی کلاینت#

شکل ۲-۲: راه‌های دسترسی به RBD: librbd در فضای کاربر ( QEMU ، rbd-nbd ، دروازه‌ی NVMe-oF ) و krbd در هسته
شکل ۲-۲: راه‌های دسترسی به ‏RBD: librbd‏ در فضای کاربر (‏QEMU‏، ‏rbd-nbd‏، دروازه‌ی ‏NVMe-oF‏) و ‏krbd‏ در هسته
  • ‏librbd‏: کتابخانه‌ی فضای کاربر. ‏QEMU/KVM‏، ‏libvirt‏، ‏OpenStack‏ و بسیاری از ابزارها از آن استفاده می‌کنند. همه‌ی ویژگی‌ها، از جمله ‏journaling‏ و ‏cache‏ سمت کلاینت، را پشتیبانی می‌کند.
  • ‏krbd‏: راه‌انداز هسته‌ی لینوکس که ‏image‏ را به‌صورت ‏/dev/rbdX‏ نشان می‌دهد. سریع و بدون وابستگی به بسته‌های فضای کاربر برای مسیر داده، اما مجموعه‌ی ویژگی‌هایش به نسخه‌ی هسته بستگی دارد. ‏Ceph CSI‏ در ‏Kubernetes‏ به‌طور پیش‌فرض از آن استفاده می‌کند.
  • ‏rbd-nbd‏: پلی بین ‏librbd‏ و دستگاه ‏NBD‏ هسته؛ وقتی ویژگی‌ای لازم است که ‏krbd‏ ندارد.
  • دروازه‌ی ‏NVMe-oF‏: برای میزبان‌هایی که ‏Ceph‏ بومی ندارند (مثلاً ‏VMware ESXi‏ یا ویندوز). ‏image‏ از طریق پروتکل ‏NVMe/TCP‏ صادر می‌شود. این دروازه جانشین عملی دروازه‌ی ‏iSCSI‏ است؛ طبق مستندات رسمی، دروازه‌ی ‏iSCSI‏ از نوامبر ۲۰۲۲ در حالت «فقط نگه‌داری» است و قابلیت تازه‌ای نمی‌گیرد.

۲-۳ پیکربندی کلاینت ‏Ceph‏#

۲-۳-۱ ساختن کاربر با دسترسی حداقلی#

برای کلاینت هرگز از ‏client.admin‏ استفاده نکنید. ‏Ceph‏ ‏profile‏های آماده‌ای برای ‏RBD‏ دارد که دقیقاً همان مجوزهای لازم را می‌دهند:

bashReef+
ceph auth get-or-create client.rbd-lab \
  mon 'profile rbd' \
  osd 'profile rbd pool=rbd' \
  mgr 'profile rbd pool=rbd' \
  -o /etc/ceph/ceph.client.rbd-lab.keyring
ceph auth get client.rbd-lab
💡 نکته

برای کلاینتی که فقط باید بخواند (مثلاً سرور پشتیبان‌گیری که از ‏snapshot‏ها ‏export‏ می‌گیرد) از ‏profile rbd-read-only‏ استفاده کنید. برای جداسازی مستاجرها در یک ‏pool‏ هم می‌توانید از ‏namespace‏ استفاده کنید: ‏rbd namespace create rbd/tenant-a‏ و سپس ‏osd 'profile rbd pool=rbd namespace=tenant-a'‏.

۲-۳-۲ انتقال پیکربندی به کلاینت#

کلاینت به دو چیز نیاز دارد: فایل پیکربندی حداقلی (برای یافتن ‏MON‏ها) و ‏keyring‏ کاربر خودش.

bashReef+
# روی یک میزبان مدیریتی
ceph config generate-minimal-conf > /tmp/ceph.conf
scp /tmp/ceph.conf client-node1:/etc/ceph/ceph.conf
scp /etc/ceph/ceph.client.rbd-lab.keyring client-node1:/etc/ceph/
# روی client-node1
sudo dnf install -y ceph-common     # یا apt install ceph-common
rbd --id rbd-lab ls rbd
ceph --id rbd-lab -s                # profile rbd اجازه‌ی خواندن وضعیت را می‌دهد
⚠ هشدار

نسخه‌ی بسته‌های ‏ceph-common‏ کلاینت را با نسخه‌ی کلاستر نزدیک نگه دارید. کلاینت‌های خیلی قدیمی ممکن است ویژگی‌های جدید (مثلاً قالب ‏clone‏ نسخه‌ی ۲ یا ‏msgr2‏) را نشناسند. با ‏ceph features‏ می‌توانید نسخه‌ی کلاینت‌های متصل را ببینید و پیش از روشن کردن ویژگی‌هایی که ‏set-require-min-compat-client‏ لازم دارند، مطمئن شوید هیچ کلاینت قدیمی‌ای وصل نیست.

۲-۴ ساختن، ‏map‏ کردن و تغییر اندازه#

۲-۴-۱ ساختن ‏image‏#

bashReef+
rbd --id rbd-lab create rbd/disk01 --size 10G
rbd --id rbd-lab ls -l rbd
rbd --id rbd-lab info rbd/disk01

خروجی ‏rbd info‏ شامل اندازه، تعداد اشیاء، ‏order 22‏ (یعنی اشیای ‏2222^{22}‏ بایتی = ۴ مگابایت)، پیشوند نام اشیای داده (‏block_name_prefix‏) و فهرست ویژگی‌هاست.

۲-۴-۲ ‏map‏ کردن با ‏krbd‏#

bashReef+
sudo rbd --id rbd-lab device map rbd/disk01      # /dev/rbd0
rbd device list
sudo mkfs.xfs /dev/rbd0
sudo mkdir -p /mnt/disk01 && sudo mount /dev/rbd0 /mnt/disk01
df -h /mnt/disk01

برای ‏map‏ خودکار پس از راه‌اندازی مجدد، سرویس ‏rbdmap‏ را به کار ببرید:

bash
echo "rbd/disk01 id=rbd-lab,keyring=/etc/ceph/ceph.client.rbd-lab.keyring" | sudo tee -a /etc/ceph/rbdmap
echo "/dev/rbd/rbd/disk01 /mnt/disk01 xfs noauto,_netdev 0 0" | sudo tee -a /etc/fstab
sudo systemctl enable --now rbdmap
⚠ هشدار

گزینه‌ی ‏_netdev‏ در ‏fstab‏ را فراموش نکنید. بدون آن، سیستم ممکن است در زمان بوت پیش از بالا آمدن شبکه سعی کند فایل‌سیستم را ‏mount‏ کند و بوت متوقف شود. در خاموش کردن هم ترتیب اهمیت دارد: اول ‏umount‏، بعد ‏unmap‏، بعد قطع شبکه.

✚ عیب‌یابی

اگر ‏rbd map‏ با خطای ‏RBD image feature set mismatch‏ یا ‏unsupported features‏ شکست خورد، پیام ‏dmesg‏ را ببینید. ویژگی ناسازگار را خاموش کنید (‏rbd feature disable rbd/disk01 journaling‏) یا از ‏rbd-nbd‏ استفاده کنید. اگر ‏map‏ معطل ماند، دسترسی کلاینت به پورت‌های ‏MON‏ (۳۳۰۰ و ۶۷۸۹) و بازه‌ی پورت ‏OSD‏ها (۶۸۰۰ تا ۷۳۰۰) را با فایروال بررسی کنید.

۲-۴-۳ تغییر اندازه#

بزرگ کردن یک ‏image‏ در ‏Ceph‏ فوری است، چون فقط فراداده تغییر می‌کند. بعد باید فایل‌سیستم را هم بزرگ کرد:

bashReef+
rbd --id rbd-lab resize rbd/disk01 --size 20G
sudo xfs_growfs /mnt/disk01          # برای ext4: sudo resize2fs /dev/rbd0
lsblk /dev/rbd0

کوچک کردن خطرناک است و ‏Ceph‏ بدون پرچم صریح اجازه نمی‌دهد:

bashReef+
rbd resize rbd/disk01 --size 5G --allow-shrink
⚠ هشدار

پیش از کوچک کردن ‏image‏، اول فایل‌سیستم را کوچک کنید (‏XFS‏ اصلاً کوچک شدن را پشتیبانی نمی‌کند). کوچک کردن ‏image‏ زیر اندازه‌ی فایل‌سیستم یعنی بریدن انتهای فایل‌سیستم و از دست رفتن داده. در عمل، برای کاهش فضا ساختن ‏image‏ جدید و کپی داده امن‌تر است.

۲-۴-۴ فضای واقعی و آزادسازی#

چون ‏RBD thin‏ است، ‏rbd du‏ فضای واقعاً مصرف‌شده را نشان می‌دهد. وقتی فایل‌ها را در فایل‌سیستم حذف می‌کنید، فضا تنها وقتی به ‏Ceph‏ برمی‌گردد که فایل‌سیستم دستور ‏discard‏ بفرستد:

bashReef+
rbd du rbd/disk01
sudo fstrim -v /mnt/disk01        # یا فعال کردن fstrim.timer

در ماشین‌های مجازی ‏QEMU‏ هم برای رسیدن ‏discard‏ به ‏Ceph‏ باید دیسک با ‏discard=unmap‏ و کنترلر ‏virtio-scsi‏ (یا ‏virtio-blk‏ در نسخه‌های جدید) تعریف شده باشد.

۲-۵ ‏snapshot‏ و ‏clone‏#

۲-۵-۱ ‏snapshot‏: تصویر لحظه‌ای فقط‌خواندنی#

‏snapshot‏ در ‏RBD‏ نسخه‌ی فقط‌خواندنی ‏image‏ در یک لحظه‌ی مشخص است. ساختنش تقریباً آنی است، چون ‏Ceph‏ داده را کپی نمی‌کند. از آن لحظه به بعد، هر شیئی که تغییر کند، ‏OSD‏ نسخه‌ی قبلی‌اش را به‌عنوان «‏clone‏ شیء» نگه می‌دارد (‏copy-on-write‏ در سطح شیء ‏RADOS‏).

bashReef+
rbd snap create rbd/disk01@before-upgrade
rbd snap ls rbd/disk01
rbd du rbd/disk01                     # سهم هر snapshot از فضا
# بازگرداندن کل image به snapshot (image باید map نشده یا بی‌استفاده باشد)
rbd snap rollback rbd/disk01@before-upgrade
rbd snap rm rbd/disk01@before-upgrade
rbd snap purge rbd/disk01             # حذف همه‌ی snapshotها
⚠ هشدار

‏snapshot RBD‏ در سطح بلوک گرفته می‌شود و از فایل‌سیستم داخل ‏image‏ خبر ندارد. اگر روی ‏image‏ در حال استفاده ‏snapshot‏ بگیرید، نتیجه فقط ‏crash-consistent‏ است، یعنی مثل قطع ناگهانی برق. برای سازگاری بهتر پیش از ‏snapshot‏ فایل‌سیستم را با ‏fsfreeze -f /mnt/disk01‏ منجمد و بعد با ‏fsfreeze -u‏ آزاد کنید. در ماشین‌های مجازی ‏QEMU guest agent‏ همین کار را انجام می‌دهد. برای پایگاه داده‌ها از سازوکار خود پایگاه داده (مثلاً حالت ‏backup‏) استفاده کنید.

✎ یادداشت

‏rbd snap rollback‏ کل داده‌ی ‏image‏ را بازنویسی می‌کند و برای ‏image‏های بزرگ کند است. اگر فقط چند فایل لازم است، بهتر است از ‏snapshot‏ یک ‏clone‏ بسازید، آن را ‏map‏ کنید و فایل‌ها را کپی کنید.

۲-۵-۲ زمان‌بندی ‏snapshot‏#

از نسخه‌ی ‏Octopus‏، ماژول ‏MGR‏ به نام ‏rbd_support‏ امکان زمان‌بندی ‏snapshot‏ را برای ‏mirroring‏ فراهم می‌کند. برای ‏snapshot‏های پشتیبان‌گیری عمومی معمولاً از ابزار بیرونی (‏cron‏، ‏Velero‏، ‏Backy2/Benji‏ یا ابزار پلتفرم مجازی‌سازی) استفاده می‌شود.

۲-۵-۳ ‏clone‏: نوشتنی و ارزان#

‏clone‏ ‏image‏ی نوشتنی است که بر پایه‌ی یک ‏snapshot‏ ساخته می‌شود. تا وقتی ‏clone‏ در شیئی ننوشته، خواندن آن شیء از والد انجام می‌شود. این همان سازوکاری است که ساختن صدها ماشین مجازی از یک «‏image‏ طلایی» را در چند ثانیه ممکن می‌کند.

شکل ۲-۳: زنجیره‌ی image والد، snapshot و clone ؛ flatten وابستگی clone به والد را قطع می‌کند
شکل ۲-۳: زنجیره‌ی ‏image‏ والد، ‏snapshot‏ و ‏clone‏؛ ‏flatten‏ وابستگی ‏clone‏ به والد را قطع می‌کند
bashReef+
rbd create rbd/golden-os --size 20G
# ... نصب سیستم‌عامل روی golden-os ...
rbd snap create rbd/golden-os@v1
# در قالب clone نسخه‌ی ۱ لازم بود snapshot محافظت شود:
rbd snap protect rbd/golden-os@v1
rbd clone rbd/golden-os@v1 rbd/vm01
rbd clone rbd/golden-os@v1 rbd/vm02
rbd children rbd/golden-os@v1
rbd info rbd/vm01 | grep parent
◆ عمیق‌تر

از ‏Mimic‏ به بعد قالب ‏clone‏ نسخه‌ی ۲ وجود دارد که دیگر نیازی به ‏snap protect‏ ندارد. در این قالب، اگر ‏snapshot‏ والدی را که هنوز فرزند دارد حذف کنید، ‏Ceph‏ آن را به «سطل زباله‌ی ‏snapshot‏» منتقل می‌کند و وقتی آخرین فرزند ‏flatten‏ یا حذف شد، واقعاً پاکش می‌کند. شرط استفاده این است که کلاستر حداقل نسخه‌ی کلاینت سازگار را دست‌کم ‏Mimic‏ اعلام کرده باشد (‏ceph osd set-require-min-compat-client mimic‏). گزینه‌ی ‏rbd_default_clone_format‏ (با مقدار پیش‌فرض ‏auto‏) تعیین می‌کند کدام قالب استفاده شود.

۲-۵-۴ ‏flatten‏: بریدن بند ناف#

‏clone‏ تا وقتی والدش وجود دارد به آن وابسته است. اگر بخواهید ‏image‏ طلایی را حذف کنید یا ‏clone‏ را به ‏pool‏ دیگری مهاجرت دهید، باید آن را ‏flatten‏ کنید: همه‌ی اشیایی که ‏clone‏ هنوز از والد می‌خواند، در خود ‏clone‏ کپی می‌شوند.

bashReef+
rbd flatten rbd/vm02
rbd info rbd/vm02 | grep parent      # دیگر parent ندارد
rbd snap unprotect rbd/golden-os@v1  # فقط اگر قالب ۱ و دیگر فرزندی نمانده باشد
💡 نکته

زنجیره‌های ‏clone‏ عمیق (‏clone‏ از ‏clone‏ از ‏clone‏) کارایی خواندن را کم می‌کنند، چون خواندن شیء نانوشته باید از چند سطح والد عبور کند. ‏OpenStack‏ و پلتفرم‌های مشابه معمولاً فقط یک سطح ‏clone‏ می‌سازند. اگر زنجیره عمیق شد، ‏flatten‏ کنید.

۲-۵-۵ سطل زباله و حذف امن#

‏RBD‏ یک ‏trash‏ دارد. به‌جای حذف فوری، ‏image‏ را به آن منتقل کنید تا در صورت اشتباه قابل بازیابی باشد:

bashReef+
rbd trash mv rbd/vm01 --expires-at "2026-12-01"
rbd trash ls rbd
rbd trash restore rbd/<image-id>
rbd trash purge rbd                  # حذف همه‌ی imageهای منقضی‌شده

۲-۵-۶ مهاجرت زنده#

‏rbd migration‏ امکان انتقال ‏image‏ به ‏pool‏ دیگر (مثلاً از ‏HDD‏ به ‏NVMe‏ یا از تکثیری به ‏EC‏) را با کمترین قطعی فراهم می‌کند. کلاینت فقط در لحظه‌ی ‏prepare‏ باید ‏image‏ را رها کند و بعد می‌تواند روی ‏image‏ مقصد کارش را ادامه دهد، در حالی که داده در پس‌زمینه کپی می‌شود:

bashReef+
rbd migration prepare rbd/disk01 fast-nvme/disk01
# کلاینت حالا می‌تواند fast-nvme/disk01 را باز کند
rbd migration execute fast-nvme/disk01
rbd migration commit fast-nvme/disk01

در ‏Tentacle‏ امکان وارد کردن آنی ‏image‏ از یک کلاستر ‏Ceph‏ دیگر یا از منابع و قالب‌های بیرونی هم به مهاجرت زنده اضافه شده است.

۲-۶ بازیابی از سانحه با ‏RBD mirroring‏#

۲-۶-۱ مفهوم و دو حالت آن#

‏RBD mirroring‏ تکثیر ناهمگام ‏image‏ها بین دو (یا چند) کلاستر ‏Ceph‏ مستقل است. یک ‏daemon‏ به نام ‏rbd-mirror‏ در کلاستر مقصد اجرا می‌شود، تغییرات را از کلاستر مبدأ می‌خواند و روی نسخه‌ی محلی اعمال می‌کند. هر ‏image‏ در هر لحظه فقط در یک کلاستر ‏primary‏ (نوشتنی) است و در بقیه ‏non-primary‏ (فقط‌خواندنی).

شکل ۲-۴: دو حالت mirroring : در حالت journal همه‌ی نوشتن‌ها ثبت و بازپخش می‌شوند؛ در حالت snapshot تفاضل snapshot های دوره‌ای منتقل می‌شود
شکل ۲-۴: دو حالت ‏mirroring‏: در حالت ‏journal‏ همه‌ی نوشتن‌ها ثبت و بازپخش می‌شوند؛ در حالت ‏snapshot‏ تفاضل ‏snapshot‏های دوره‌ای منتقل می‌شود
جنبهحالت ‏journal‏حالت ‏snapshot‏
سازوکارهر نوشتن اول در ‏journal‏ درون ‏RADOS‏ ثبت و سپس روی ‏image‏ اعمال می‌شود‏snapshot‏های ‏mirror‏ به‌صورت دوره‌ای ساخته و تفاضل آن‌ها منتقل می‌شود
‏RPO‏ثانیه‌ها (وابسته به تأخیر بازپخش)برابر فاصله‌ی زمان‌بندی (مثلاً ۵ دقیقه یا ۱ ساعت)
سربار نوشتنتقریباً دو برابر (‏journal‏ + داده)کم؛ فقط در زمان ‏snapshot‏
پشتیبانی ‏krbd‏ندارد (‏journaling‏ پشتیبانی نمی‌شود)دارد
ترتیب نوشتنحفظ می‌شود (‏point-in-time consistent‏)‏crash-consistent‏ در لحظه‌ی ‏snapshot‏
توصیه‌ی امروزبرای ‏RPO‏ بسیار کم و کلاینت ‏librbd‏انتخاب پیش‌فرض برای بیشتر کاربردها

۲-۶-۲ حالت‌های ‏pool‏#

‏mirroring‏ در سطح ‏pool‏ با یکی از این حالت‌ها فعال می‌شود:

  • ‏image‏: هر ‏image‏ باید جداگانه و صریحاً برای ‏mirroring‏ فعال شود. انعطاف بیشتر و تنها حالت ممکن برای ‏mirroring‏ نوع ‏snapshot‏.
  • ‏pool‏: همه‌ی ‏image‏هایی که ویژگی ‏journaling‏ دارند خودکار ‏mirror‏ می‌شوند (فقط حالت ‏journal‏).
  • ‏init-only‏: ‏pool‏ برای ‏mirroring‏ آماده می‌شود اما خود ‏pool mirror‏ نمی‌شود؛ برای حالتی که فقط ‏namespace‏ها ‏mirror‏ شوند.

۲-۶-۳ استقرار ‏rbd-mirror‏#

bashReef+
# روی کلاستر مقصد (و برای دوطرفه، روی هر دو کلاستر)
ceph orch apply rbd-mirror --placement="2 ceph-node2 ceph-node3"
ceph orch ps --daemon-type rbd-mirror
💡 نکته

دو نمونه‌ی ‏rbd-mirror‏ در هر کلاستر اجرا کنید. نمونه‌ها کار را بین خودشان تقسیم می‌کنند و خرابی یکی، تکثیر را متوقف نمی‌کند. ‏rbd-mirror‏ پهنای باند و ‏CPU‏ قابل‌توجهی مصرف می‌کند؛ آن را روی میزبانی بگذارید که هم به شبکه‌ی عمومی محلی و هم به کلاستر راه دور دسترسی خوب دارد.

۲-۶-۴ پیکربندی تکثیر یک‌طرفه#

فرض کنید دو کلاستر ‏site-a‏ (اصلی) و ‏site-b‏ (پشتیبان) داریم و ‏pool‏ به نام ‏vms‏ در هر دو با همین نام ساخته شده است.

شکل ۲-۵: توپولوژی mirroring: rbd-mirror در site-b تغییرات را از site-a می‌خواند؛ برای failback به rbd-mirror در site-a هم نیاز است
شکل ۲-۵: توپولوژی ‏mirroring: rbd-mirror‏ در ‏site-b‏ تغییرات را از ‏site-a‏ می‌خواند؛ برای ‏failback‏ به ‏rbd-mirror‏ در ‏site-a‏ هم نیاز است
bashReef+
# روی هر دو کلاستر
ceph osd pool create vms && rbd pool init vms
# site-a
rbd mirror pool enable --site-name site-a vms image
# site-b
rbd mirror pool enable --site-name site-b vms image
# site-a: ساختن توکن bootstrap (شامل fsid، کاربر و نشانی MONها)
rbd mirror pool peer bootstrap create --site-name site-a vms > /root/vms-token
# انتقال امن توکن به site-b و وارد کردن آن
rbd mirror pool peer bootstrap import --site-name site-b --direction rx-only vms /root/vms-token
rbd mirror pool info vms
⚠ هشدار

توکن ‏bootstrap‏ حاوی کلید یک کاربر ‏Ceph‏ با دسترسی به ‏pool‏ است. آن را مثل رمز عبور منتقل کنید (‏scp‏ از کانال امن)، در تیکت یا چت قرار ندهید و پس از ‏import‏ پاکش کنید.

۲-۶-۵ فعال کردن ‏mirroring‏ برای ‏image‏ها#

bashReef+
# حالت snapshot (پیشنهادی)
rbd create vms/db01 --size 50G
rbd mirror image enable vms/db01 snapshot
# زمان‌بندی snapshotهای mirror برای کل pool یا یک image
rbd mirror snapshot schedule add --pool vms 15m
rbd mirror snapshot schedule ls --pool vms --recursive
# حالت journal (فقط برای کلاینت librbd)
rbd create vms/app01 --size 20G --image-feature exclusive-lock,journaling
rbd mirror image enable vms/app01 journal

۲-۶-۶ بررسی وضعیت همگام‌سازی#

bashReef+
rbd mirror pool status vms --verbose
rbd mirror image status vms/db01

وضعیت سالم برای یک ‏image non-primary‏ در حالت ‏snapshot‏ معمولاً ‏up+replaying‏ است و توضیح آن نشان می‌دهد آخرین ‏snapshot‏ همگام‌شده کدام است و چقدر از آن گذشته. در حالت ‏journal‏، فاصله‌ی ورودی‌های ‏journal‏ (‏entries_behind_primary‏) معیار عقب‌ماندگی است.

✚ عیب‌یابی

وضعیت ‏down+unknown‏ یعنی ‏rbd-mirror‏ در کلاستر مقصد اجرا نمی‌شود یا نمی‌تواند به کلاستر راه دور برسد. ‏ceph orch ps --daemon-type rbd-mirror‏، لاگ ‏daemon‏ و دسترسی شبکه به ‏MON‏ها و ‏OSD‏های راه دور را بررسی کنید. وضعیت ‏up+error‏ همراه با ‏split-brain‏ یعنی ‏image‏ در هر دو طرف ‏primary‏ شده است (بخش بعد را ببینید).

۲-۶-۷ تکثیر دوطرفه#

در حالت دوطرفه هر کلاستر هم مبدأ برخی ‏image‏ها و هم مقصد برخی دیگر است. کافی است ‏rbd-mirror‏ را روی هر دو کلاستر مستقر کنید و توکن را با جهت ‏rx-tx‏ (پیش‌فرض) وارد کنید:

bashReef+
rbd mirror pool peer bootstrap import --site-name site-b --direction rx-tx vms /root/vms-token

دوطرفه بودن به این معنی نیست که یک ‏image‏ در هر دو طرف هم‌زمان نوشتنی است. معنایش این است که ‏image‏های ‏primary‏ در ‏site-a‏ به ‏site-b‏ و ‏image‏های ‏primary‏ در ‏site-b‏ به ‏site-a‏ تکثیر می‌شوند، و پس از ‏failover‏ جهت تکثیر بدون پیکربندی دوباره برعکس می‌شود.

۲-۷ از درون کد: چیدمان اشیای ‏RBD‏، ‏object map‏ و ‏exclusive lock‏#

۲-۷-۱ یک تصویر ‏RBD‏ روی ‏RADOS‏ چگونه ذخیره می‌شود#

تصویر ‏RBD‏ در ‏RADOS‏ هیچ «فایل» یکپارچه‌ای نیست. ‏librbd‏ آن را به مجموعه‌ای از اشیای جدا تبدیل می‌کند. پیشوندها در ‏src/include/rbd_types.h‏ تعریف شده‌اند: ‏rbd_id.‏، ‏rbd_header.‏، ‏rbd_object_map.‏، ‏rbd_data.‏ و اشیای سطح ‏pool‏ یعنی ‏rbd_directory‏ و ‏rbd_trash‏.

شکل ۲-۶: چیدمان یک تصویر فرمت ۲ در RADOS . ردیف بالا فهرست‌های سطح pool را نشان می‌دهد: در omap شیء rbd_directory کلیدهای name_ و id_ نام و شناسه را به هم نگاشت می‌کنند. ردیف میانی اشیای فراداده‌ی هر تصویر است: rbd_id.<name> شناسه را نگه می‌دارد، و rbd_header.<id> اندازه، order ، features ، object_prefix ، snapshot ها و والد را در omap ذخیره می‌کند و watcher ها روی همین شیء ثبت می‌شوند. object map برای هر شیء داده دو بیت حالت دارد. ردیف پایین اشیای داده است که نامشان از object_prefix و یک عدد hex شانزده‌رقمی ساخته می‌شود؛ فقط اشیایی ساخته می‌شوند که واقعاً نوشته شده باشند ( thin provisioning )
شکل ۲-۶: چیدمان یک تصویر فرمت ۲ در ‏RADOS‏. ردیف بالا فهرست‌های سطح ‏pool‏ را نشان می‌دهد: در ‏omap‏ شیء ‏rbd_directory‏ کلیدهای ‏name_‏ و ‏id_‏ نام و شناسه را به هم نگاشت می‌کنند. ردیف میانی اشیای فراداده‌ی هر تصویر است: ‏rbd_id.<name>‏ شناسه را نگه می‌دارد، و ‏rbd_header.<id>‏ اندازه، ‏order‏، ‏features‏، ‏object_prefix‏، ‏snapshot‏ها و والد را در ‏omap‏ ذخیره می‌کند و ‏watcher‏ها روی همین شیء ثبت می‌شوند. ‏object map‏ برای هر شیء داده دو بیت حالت دارد. ردیف پایین اشیای داده است که نامشان از ‏object_prefix‏ و یک عدد ‏hex‏ شانزده‌رقمی ساخته می‌شود؛ فقط اشیایی ساخته می‌شوند که واقعاً نوشته شده باشند (‏thin provisioning‏)

فرمول یافتن شیء برای یک ‏offset‏، بدون ‏striping‏ سفارشی (‏stripe_unit‏ = اندازه‌ی شیء):

text
pseudocode (خلاصه‌ی این کتاب از ImageCtx/Striper)
  object_size = 1 << order                 # order=22 → 4 MiB
  objno       = offset // object_size
  obj_off     = offset %  object_size
  oid = sprintf("%s.%016llx", object_prefix, objno)   # object_prefix = "rbd_data.<id>"
bashReef+
rbd info rbd/vm1 | grep -E 'block_name_prefix|order|features'
rados -p rbd listomapvals rbd_directory | head
rados -p rbd listomapkeys rbd_header.$(rados -p rbd get rbd_id.vm1 - | strings)
rados -p rbd ls | grep -c "^rbd_data.<id>"    # تعداد اشیای واقعاً تخصیص‌یافته
rbd du rbd/vm1                                  # با fast-diff سریع است
💡 نکته

اگر ‏rbd du‏ روی تصویری بزرگ ثانیه‌ها یا دقیقه‌ها طول می‌کشد، احتمالاً ‏object map/fast-diff‏ غیرفعال یا نامعتبر است. ‏rbd info‏ را برای ‏flags‏ نگاه کنید (‏object map invalid‏) و با ‏rbd object-map rebuild‏ بازسازی کنید.

۲-۷-۲ ‏object map‏: دو بیت که ‏I/O‏ را سریع می‌کنند#

ثابت‌های ‏src/include/rbd/object_map_types.h‏:

مقدارناممعنا
۰‏OBJECT_NONEXISTENT‏شیء وجود ندارد؛ خواندن صفر برمی‌گرداند و نیازی به رفتن به ‏OSD‏ نیست
۱‏OBJECT_EXISTS‏شیء وجود دارد و پس از آخرین ‏snapshot‏ تغییر کرده است
۲‏OBJECT_PENDING‏حذف/‏discard‏ در جریان است
۳‏OBJECT_EXISTS_CLEAN‏شیء وجود دارد ولی از آخرین ‏snapshot‏ تغییر نکرده است (پایه‌ی ‏fast-diff‏)

‏object map‏ فقط زمانی قابل اعتماد است که یک نویسنده داشته باشد. به همین دلیل ‏feature‏ ‏object-map‏ به ‏exclusive-lock‏ وابسته است. سقف تعداد اشیای پوشش‌داده‌شده در ‏cls_rbd_types.h‏ برابر ‏MAX_OBJECT_MAP_OBJECT_COUNT = 256000000‏ است؛ با اشیای ۴‏MiB‏، این حدود ۹۷۶ ‏TiB‏ می‌شود.

text
pseudocode: نوشتن با object map (خلاصه‌ی این کتاب)
  write(objno, data):
     if objmap[objno] in {NONEXISTENT, EXISTS_CLEAN}:
         objmap_update(objno, EXISTS)        # ابتدا به‌روزرسانی شیء rbd_object_map (cls_rbd)
     send write to rbd_data.<id>.<objno>
  discard(objno):
     objmap_update(objno, PENDING) → remove object → objmap_update(objno, NONEXISTENT)

۲-۷-۳ ‏exclusive lock‏ و ‏ManagedLock‏#

پیش‌فرض ‏rbd_default_features‏ در ‏rbd.yaml.in‏ برابر ‏layering,exclusive-lock,object-map,fast-diff,deep-flatten‏ است. قفل با کلاس ‏cls_lock‏ و نام ‏rbd_lock‏ (‏RBD_LOCK_NAME‏) روی شیء ‏header‏ گرفته می‌شود. ماشین حالت ‏librbd/managed_lock/AcquireRequest‏ سه گام اصلی دارد: ‏GET_LOCKER‏، ‏LOCK_IMAGE‏ و در صورت مرده بودن مالک، ‏BREAK_LOCK‏.

شکل ۲-۷: انتقال exclusive lock بین دو کلاینت. B ابتدا مالک فعلی را می‌خواند و اگر قفل گرفته شده باشد، از طریق watch/notify روی header درخواست قفل می‌فرستد. اگر A سالم باشد، طبق policy (پیش‌فرض: آزادسازی خودکار) کش را flush و قفل را رها می‌کند. اگر A دیگر watch نداشته باشد، B قفل را می‌شکند و با rbd_blocklist_on_break_lock=true (پیش‌فرض) آدرس A را blocklist می‌کند تا نوشتن‌های دیررسیده‌ی A پذیرفته نشوند. سپس B قفل را می‌گیرد و در PostAcquire ، object map و journal را باز می‌کند
شکل ۲-۷: انتقال ‏exclusive lock‏ بین دو کلاینت. ‏B‏ ابتدا مالک فعلی را می‌خواند و اگر قفل گرفته شده باشد، از طریق ‏watch/notify‏ روی ‏header‏ درخواست قفل می‌فرستد. اگر ‏A‏ سالم باشد، طبق ‏policy‏ (پیش‌فرض: آزادسازی خودکار) کش را ‏flush‏ و قفل را رها می‌کند. اگر ‏A‏ دیگر ‏watch‏ نداشته باشد، ‏B‏ قفل را می‌شکند و با ‏rbd_blocklist_on_break_lock=true‏ (پیش‌فرض) آدرس ‏A‏ را ‏blocklist‏ می‌کند تا نوشتن‌های دیررسیده‌ی ‏A‏ پذیرفته نشوند. سپس ‏B‏ قفل را می‌گیرد و در ‏PostAcquire‏، ‏object map‏ و ‏journal‏ را باز می‌کند
⚠ هشدار

‏exclusive lock‏ سازوکار حفاظت از چند نویسنده‌ی هم‌زمان نیست. این قفل به‌صورت همکارانه بین کلاینت‌ها دست‌به‌دست می‌شود. اگر یک تصویر را روی دو ‏VM‏ هم‌زمان ‏mount‏ کنید (مثلاً با ‏ext4‏)، داده خراب می‌شود. برای جلوگیری سخت، ‏rbd map --exclusive‏ (‏krbd‏) یا قفل‌های سطح ‏orchestrator‏ لازم است.

✚ عیب‌یابی

دستورهای عیب‌یابی قفل و ‏watcher‏:

bashReef+
rbd status rbd/vm1                  # watcherها
rbd lock ls rbd/vm1                 # قفل‌های مشورتی و مالک
ceph osd blocklist ls               # آیا کلاینت قبلی blocklist شده است؟
rbd feature disable rbd/vm1 fast-diff object-map   # ترتیب: وابسته‌ها اول

۲-۸ ‏RBD‏ با عدد: شناسه‌ی ‏image‏، ‏discard‏ و ‏rbd du‏#

این بخش سه الگوریتم کوچک ‏librbd‏ را بررسی می‌کند که در کار روزمره زیاد دیده می‌شوند: نام‌گذاری شیءهای یک ‏image‏، تبدیل ‏discard‏ به عملیات شیء، و چگونگی شمارش فضای مصرفی در ‏rbd du‏. ارجاع‌ها به ‏main‏ در نوامبر ۲۰۲۶ است. ‏striping‏ و ‏object map‏ در فصل ۳ آمده‌اند.

۲-۸-۱ شناسه و شیءهای یک ‏image‏#

∑ الگوریتم: ساخت شناسه‌ی ‏image‏ و نام شیءهای آن (قالب ۲)

منبع در کد: src/librbd/Utils.cc :: generate_image_id؛ src/include/rbd_types.h (پیشوندها)؛ src/cls/rbd/cls_rbd.cc :: dir_add_image

پیچیدگی: ‏O‏(۱)

text
pseudocode
bid   = rados.get_instance_id()          # شناسه‌ی سراسری این اتصال به کلاستر
extra = عدد تصادفی 32 بیتی
id    = hex(bid) + hex(extra)            # بدون صفر پیشرو؛ اگر > 14 نویسه: 14 نویسه‌ی آخر
rbd_id.<name>           ← id
rbd_directory (omap)    ← name_<name> = id ، id_<id> = name
rbd_header.<id>         ← size، order، features، snapshotها، parent (omap)
rbd_object_map.<id>     ← 2 بیت برای هر شیء (اگر object-map فعال باشد)
rbd_data.<id>.%016x     ← شیء داده با شماره‌ی hex شانزده‌رقمی، فقط پس از اولین نوشتن

مثال نمادین. اگر ‏instance id‏ برابر ۴۲۳۵ (۰‏x108b‏) و عدد تصادفی ۰‏x1d6e2f3a‏ باشد، ‏id‏ برابر ‏108b1d6e2f3a‏ (۱۲ نویسه) است و شیء سوم داده ‏rbd_data.108b1d6e2f3a.0000000000000002‏ نام دارد. سقف ۱۴ نویسه (‏RBD_MAX_IMAGE_ID_LENGTH‏) از ‏RBD_MAX_BLOCK_NAME_SIZE‏ برابر ۲۴ می‌آید: «‏rbd_data‏.» (۹ نویسه)، ۱۴ نویسه‌ی ‏id‏ و نویسه‌ی پایان. چون شیءهای داده فقط پس از اولین نوشتن ساخته می‌شوند، ‏rbd create --size 10T‏ در کسری از ثانیه تمام می‌شود و هیچ فضایی مصرف نمی‌کند. برای دیدن شیءهای موجود، ‏rados -p <pool> ls | grep 108b1d6e2f3a‏ را اجرا کنید.

شکل ۲-۸: زنجیره‌ی شیءهای یک image با قالب ۲. ردیف اول ساختن id از instance id و یک عدد تصادفی است. ردیف دوم نگاشت نام به id در rbd_id و rbd_directory ، و سرآیند image است. ردیف سوم object map ، شیءهای داده‌ای که با اولین نوشتن ساخته می‌شوند، و ورودی trash هنگام حذف است. id نمونه نمادین است
شکل ۲-۸: زنجیره‌ی شیءهای یک ‏image‏ با قالب ۲. ردیف اول ساختن ‏id‏ از ‏instance id‏ و یک عدد تصادفی است. ردیف دوم نگاشت نام به ‏id‏ در ‏rbd_id‏ و ‏rbd_directory‏، و سرآیند ‏image‏ است. ردیف سوم ‏object map‏، شیءهای داده‌ای که با اولین نوشتن ساخته می‌شوند، و ورودی ‏trash‏ هنگام حذف است. ‏id‏ نمونه نمادین است

۲-۸-۲ ‏discard‏: از محدوده‌ی ‏image‏ تا ‏remove‏، ‏truncate‏ یا ‏zero‏#

∑ الگوریتم: هرس و انتخاب عمل ‏discard‏

منبع در کد: src/librbd/io/ImageRequest.cc :: ImageDiscardRequest::prune_object_extents؛ src/librbd/io/ObjectRequest.h :: ObjectDiscardRequest؛ src/librbd/ImageCtx.cc (skip_partial_discard)

پیچیدگی: ‏O‏(تعداد شیءهای درگیر)

text
pseudocode
g = rbd_skip_partial_discard (true) و بدون dirty cache ? rbd_discard_granularity_bytes (64K) : 0
g = min(g, object_size)
برای هر extent شیء (off, len):
    if g > 0: off = roundup(off, g) ; end = align(off + len, g) ; if off ≥ end: حذف extent
    if کل شیء: REMOVE    (clone با parent: TRUNCATE یا create+truncate تا parent پنهان شود)
    elif به انتهای شیء می‌رسد: TRUNCATE
    else: ZERO

مثال عددی (شیء ۴ ‏MiB‏). ‏discard‏ ‏۱۰ ‏KiB‏ در ‏offset‏ ‏۱۰۰ ‏KiB‏ به بازه‌ی [۱۲۸‏K, 64K‏) گرد می‌شود که خالی است، پس کاری انجام نمی‌شود. ‏discard‏ ‏۲۰۰ ‏KiB‏ در ‏offset‏ ‏۱۰۰ ‏KiB‏ به ‏zero‏ در بازه‌ی ۱۲۸‏K‏ تا ۲۵۶‏K‏ تبدیل می‌شود. ‏discard‏ ‏۱ ‏MiB‏ در ‏offset‏ ‏۳.۵ ‏MiB‏ دو عمل می‌سازد: ‏truncate‏ شیء ۰ در ۳۵۸۴‏K‏ و ‏zero‏ بازه‌ی ۰ تا ۵۱۲‏K‏ در شیء ۱. ‏discard‏ ‏۴ ‏MiB‏ در ‏offset‏ ‏۲ ‏MiB‏، شیء ۰ را از ۲ ‏MiB‏ به بعد ‏truncate‏ و نیمه‌ی اول شیء ۱ را ‏zero‏ می‌کند. ‏discard‏ ‏۸ ‏MiB‏ در ‏offset‏ ‏۴ ‏MiB‏ شیءهای ۱ و ۲ را کامل حذف می‌کند. پس ‏fstrim‏ روی فایل‌سیستمی که بلوک‌های کوچک پراکنده آزاد می‌کند، تقریباً هیچ فضایی پس نمی‌دهد. چیزی که فضا را واقعاً آزاد می‌کند ‏truncate‏ یا ‏remove‏ شیء است، یا ‏zero‏ در ‏BlueStore‏ که اگر هم‌تراز با ‏min_alloc_size‏ باشد، آن بلوک‌ها را آزاد می‌کند.

شکل ۲-۹: پنج درخواست discard روی سه شیء چهارمگابایتی. نوار خاکستری بالای هر ردیف بازه‌ی درخواستی است. قرمز remove ، نارنجی truncate ، آبی zero و هاشور «رد شده» است. گرد شدن به ۶۴ KiB باعث می‌شود discard های کوچک‌تر از یک واحد کامل حذف شوند
شکل ۲-۹: پنج درخواست ‏discard‏ روی سه شیء چهارمگابایتی. نوار خاکستری بالای هر ردیف بازه‌ی درخواستی است. قرمز ‏remove‏، نارنجی ‏truncate‏، آبی ‏zero‏ و هاشور «رد شده» است. گرد شدن به ۶۴ ‏KiB‏ باعث می‌شود ‏discard‏های کوچک‌تر از یک واحد کامل حذف شوند
شکل ۲-۱۰: مسیر تصمیم discard در librbd : تبدیل به extent های شیء، هرس با واحد ۶۴ KiB (مگر اینکه skip_partial_discard خاموش باشد یا cache کثیف وجود داشته باشد) و انتخاب remove ، truncate یا zero . برای clone با parent ، حذف کامل به truncate تبدیل می‌شود تا داده‌ی parent دوباره پیدا نشود
شکل ۲-۱۰: مسیر تصمیم ‏discard‏ در ‏librbd‏: تبدیل به ‏extent‏های شیء، هرس با واحد ۶۴ ‏KiB‏ (مگر اینکه ‏skip_partial_discard‏ خاموش باشد یا ‏cache‏ کثیف وجود داشته باشد) و انتخاب ‏remove‏، ‏truncate‏ یا ‏zero‏. برای ‏clone‏ با ‏parent‏، حذف کامل به ‏truncate‏ تبدیل می‌شود تا داده‌ی ‏parent‏ دوباره پیدا نشود

۲-۸-۳ ‏rbd du‏: کل شیء یا ‏extent‏ دقیق؟#

∑ الگوریتم: شمارش فضای مصرفی در ‏rbd du‏

منبع در کد: src/tools/rbd/action/DiskUsage.cc :: get_image_disk_usage (diff_iterate2 با whole_object = !exact)؛ src/librbd/api/DiffIterate.cc

پیچیدگی: با ‏fast-diff: O‏(تعداد شیء) از ‏object map‏؛ با --‏exact: O‏(تعداد شیء) فراخوانی ‏list-snaps‏

text
pseudocode
whole_object = not --exact
diff_iterate2(from_snap, 0, size, include_parent=false, whole_object, cb)
    if whole_object و fast-diff معتبر: از object map، هر شیء EXISTS یا EXISTS_CLEAN = یک بازه‌ی کامل
    else: برای هر شیء list-snaps ← calc_snap_set_diff
          # بدون snapshot: بازه‌ی [0, size شیء)، نه extentهای پراکنده‌ی نوشته‌شده
USED = Σ طول بازه‌ها                      # PROVISIONED = اندازه‌ی image
اگر fast-diff نامعتبر یا غیرفعال باشد: هشدار و کندی (پیمایش همه‌ی شیءها)

اندازه‌گیری واقعی. این چهار بار کاری را روی یک کلاستر آزمایشی سه‌‏OSD‏ی با ‏Ceph 18.2.7‏ (Reef، بسته‌های ‏Debian‏) و ‏image‏های ده گیگابایتی با شیء ۴ ‏MiB‏ و ‏feature‏های پیش‌فرض (۶۱، یعنی ‏layering‏، ‏exclusive-lock‏، ‏object-map‏، ‏fast-diff‏ و ‏deep-flatten‏) اجرا کردیم:

بار کاریبایت نوشته‌شده‏rbd du‏ --‏exact‏‏rbd du‏ پیش‌فرض
‏A‏: صد نوشتن ۱ ‏KiB‏ در آفست ۰ صد شیء۱۰۰ ‏KiB‏۱۰۰ ‏KiB‏۴۰۰ ‏MiB‏
‏B‏: هزار نوشتن ۱ ‏KiB‏ در آفست ۰ هزار شیء۱۰۰۰ ‏KiB‏۱۰۰۰ ‏KiB‏۴۰۰۰ ‏MiB‏
‏C: 256 MiB‏ ترتیبی۲۵۶ ‏MiB‏۲۵۶ ‏MiB‏۲۵۶ ‏MiB‏
‏D‏: دوهزار نوشتن ۴ ‏KiB‏ تصادفی در ۲ ‏GiB‏ نخست (۵۰۳ شیء)۷.۸ ‏MiB‏۱.۵۱ ‏GiB‏۱.۹۶ ‏GiB‏

سطر ‏D‏ نکته‌ای را نشان می‌دهد که از نام ‏--exact‏ برنمی‌آید. در ‏calc_snap_set_diff‏ (فایل ‏src/librados/snap_set_diff.cc‏) اگر شیء در ‏snapshot‏ آغاز وجود نداشته باشد، ‏diff‏ برابر کل بازه‌ی ‏[0, size)‏ شیء گذاشته می‌شود. اندازه‌ی یک شیء ‏RADOS‏ بالاترین بایت نوشته‌شده در آن است. پس ‏--exact‏ تا آخرین بایت هر شیء را می‌شمارد، نه ‏extent‏های پراکنده را. نوشتن ۴ ‏KiB‏ در انتهای یک شیء آن را ۴ ‏MiB‏ حساب می‌کند. در ‏D‏ هر شیء به‌طور میانگین حدود چهار نوشتن تصادفی دارد. امید بیشینه‌ی چهار نقطه‌ی یکنواخت ۴/۵ طول است و همین ۵۰۳ × ۴ ‏MiB‏ × ۰.۸ ≈ ۱.۵۷ ‏GiB‏ را پیش‌بینی می‌کند که به ۱.۵۱ ‏GiB‏ اندازه‌گیری‌شده نزدیک است. ‏--exact‏ فقط وقتی به داده‌ی واقعاً نوشته‌شده نزدیک است که نوشتن‌ها از ابتدای شیء پیوسته باشند (‏A‏، ‏B‏ و ‏C‏). هیچ‌کدام از دو عدد مصرف خام را نشان نمی‌دهد. مصرف خام به ‏size‏ یا ‏k+m‏ و به ‏min_alloc_size‏ ‏‏BlueStore‏ (۴ KiB در نسخه‌های اخیر) هم بستگی دارد و ‏BlueStore‏ بخش‌های نانوشته را تخصیص نمی‌دهد. برای ظرفیت‌سنجی از ‏ceph df detail‏ استفاده کنید و ‏rbd du‏ را فقط برای مقایسه‌ی ‏image‏ها به کار ببرید.

شکل ۲-۱۱: USED اندازه‌گیری‌شده روی کلاستر آزمایشی Ceph 18.2.7 برای چهار بار کاری. خاکستری بایت‌های واقعاً نوشته‌شده است، آبی rbd du --exact و نارنجی rbd du پیش‌فرض (شمارش کل شیء از object map ). محور عمودی لگاریتمی است. در بار تصادفی D ، --exact هم حدود دویست برابر داده‌ی نوشته‌شده را نشان می‌دهد، چون هر شیء را از بایت ۰ تا بالاترین بایت نوشته‌شده می‌شمارد
شکل ۲-۱۱: ‏USED‏ اندازه‌گیری‌شده روی کلاستر آزمایشی ‏Ceph 18.2.7‏ برای چهار بار کاری. خاکستری بایت‌های واقعاً نوشته‌شده است، آبی ‏rbd du --exact‏ و نارنجی ‏rbd du‏ پیش‌فرض (شمارش کل شیء از ‏object map‏). محور عمودی لگاریتمی است. در بار تصادفی ‏D‏، ‏‏--exact‏ هم حدود دویست برابر داده‌ی نوشته‌شده را نشان می‌دهد، چون هر شیء را از بایت ۰ تا بالاترین بایت نوشته‌شده می‌شمارد

۲-۹ بازگشت از سانحه#

۲-۹-۱ سه سناریو#

در عمل با سه موقعیت متفاوت روبه‌رو می‌شویم و روش هر کدام فرق دارد:

سناریووضعیت ‏site-a‏اقداماز دست رفتن داده
جابه‌جایی برنامه‌ریزی‌شده (‏switchover‏)سالم‏demote‏ در ‏A‏، صبر تا همگام شدن، ‏promote‏ در ‏B‏صفر
سانحه (‏failover‏)در دسترس نیست‏promote‏ --‏force‏ در ‏B‏به اندازه‌ی ‏RPO‏
بازگشت (‏failback‏)دوباره سالم شده‏demote‏ و ‏resync‏ در ‏A‏، سپس ‏switchover‏ معکوسصفر (پس از ‏resync‏)
شکل ۲-۱۲: ترتیب گام‌های switchover ، failover اجباری و failback
شکل ۲-۱۲: ترتیب گام‌های ‏switchover‏، ‏failover‏ اجباری و ‏failback‏

۲-۹-۲ جابه‌جایی برنامه‌ریزی‌شده#

bashReef+
# ۱. توقف برنامه‌ها و umount در site-a
# ۲. تنزل image در site-a
rbd --cluster site-a mirror image demote vms/db01
# ۳. صبر تا site-b آخرین snapshot را دریافت کند
rbd --cluster site-b mirror image status vms/db01
# ۴. ارتقا در site-b و راه‌اندازی برنامه‌ها آن‌جا
rbd --cluster site-b mirror image promote vms/db01

برای کل ‏pool‏ هم دستورهای ‏rbd mirror pool demote vms‏ و ‏rbd mirror pool promote vms‏ وجود دارند.

۲-۹-۳ ‏failover‏ اجباری#

وقتی ‏site-a‏ در دسترس نیست، ‏demote‏ ممکن نیست. ‏image‏ را در ‏site-b‏ به‌اجبار ارتقا می‌دهیم:

bashReef+
rbd --cluster site-b mirror image promote --force vms/db01
⚠ هشدار

‏promote‏ اجباری یعنی پذیرفتن از دست رفتن تغییراتی که هنوز به ‏site-b‏ نرسیده بودند. پیش از آن مطمئن شوید ‏site-a‏ واقعاً از کار افتاده یا کاملاً ایزوله شده است. اگر ‏site-a‏ فقط از دید شما قطع است اما برنامه‌هایش هنوز روی ‏image‏ می‌نویسند، با ‏promote‏ اجباری ‏split-brain‏ ساخته‌اید: دو نسخه‌ی ‏primary‏ با تاریخچه‌های متفاوت. ‏Fencing‏ (قطع دسترسی کلاینت‌های قدیمی) بخشی جدایی‌ناپذیر از هر ‏runbook‏ بازیابی از سانحه است.

۲-۹-۴ ‏failback‏#

وقتی ‏site-a‏ برگشت، ‏image‏ آن‌جا هنوز خودش را ‏primary‏ می‌داند. باید آن را تنزل دهیم و دوباره همگامش کنیم:

bashReef+
rbd --cluster site-a mirror image demote vms/db01
rbd --cluster site-a mirror image resync vms/db01
rbd --cluster site-a mirror image status vms/db01   # تا up+replaying
# پس از همگام شدن، switchover معکوس:
rbd --cluster site-b mirror image demote vms/db01
rbd --cluster site-a mirror image promote vms/db01

‏resync‏ نسخه‌ی محلی را دور می‌ریزد و از نسخه‌ی ‏primary‏ فعلی دوباره می‌سازد. برای ‏image‏های بزرگ این یعنی انتقال کامل داده؛ پهنای باند و زمان لازم را در برنامه‌ریزی ‏RTO‏ در نظر بگیرید.

▶ دست‌به‌کار شوید

در آزمایشگاه، یک ‏image‏ را با حالت ‏snapshot‏ و زمان‌بندی یک دقیقه‌ای ‏mirror‏ کنید، داخلش فایلی بنویسید، دو دقیقه صبر کنید، سپس ‏rbd-mirror‏ کلاستر مقصد را متوقف کنید و فایل دیگری بنویسید. حالا با ‏promote‏ اجباری در مقصد ببینید کدام فایل‌ها آن‌جا هستند. این آزمایش ساده مفهوم ‏RPO‏ را ملموس می‌کند.

۲-۹-۵ ملاحظات طراحی ‏DR‏#

  • نام ‏pool‏ها در دو طرف باید یکسان باشد (یا از ‏remap namespace‏ در نسخه‌های جدید استفاده کنید).
  • ظرفیت مقصد باید برای همه‌ی ‏image‏های ‏mirror‏‌شده به‌علاوه‌ی ‏snapshot‏های ‏mirror‏ کافی باشد.
  • پهنای باند بین‌سایتی: نرخ تغییر روزانه‌ی داده (نه اندازه‌ی کل) تعیین‌کننده است. با ‏rbd mirror image status‏ و آمار ‏rbd-mirror‏ در ‏Prometheus‏، عقب‌ماندگی را پایش کنید.
  • پایش و هشدار: هشدار برای ‏image‏هایی که بیش از دو برابر فاصله‌ی زمان‌بندی از آخرین همگام‌سازی‌شان گذشته است.
  • آزمون دوره‌ای: ‏runbook‏ بازیابی از سانحه‌ای که آزموده نشده، فقط یک سند امیدوارکننده است. دست‌کم هر فصل یک‌بار ‏switchover‏ واقعی انجام دهید.

۲-۱۰ تکمیلی و به‌روز#

۲-۱۰-۱ دروازه‌ی ‏NVMe-oF‏#

از ‏Squid‏ به بعد دروازه‌ی ‏NVMe over Fabrics‏ (روی ‏TCP‏) راه رسمی ارائه‌ی ‏RBD‏ به میزبان‌هایی است که کلاینت ‏Ceph‏ ندارند. این دروازه بر پایه‌ی ‏SPDK‏ ساخته شده و با ‏cephadm‏ مستقر می‌شود. دروازه‌ها در ‏gateway group‏ سازمان می‌یابند. طبق مستندات، هر گروه در حال حاضر تا هشت دروازه را پشتیبانی می‌کند و دسترس‌پذیری بالا به‌صورت فعال/آماده‌به‌کار برای هر ‏namespace‏ کار می‌کند: هر ‏namespace‏ به یک ‏load-balancing group‏ تعلق دارد و با خرابی یک دروازه، دروازه‌ی دیگری مسئولیتش را بر عهده می‌گیرد. برای ‏HA‏ دست‌کم دو دروازه لازم است. داشبورد ‏Ceph‏ از ‏Tentacle‏ مدیریت گروه‌ها و ‏namespace‏های متعدد ‏NVMe-oF‏ را پشتیبانی می‌کند و در ‏Umbrella‏ امکاناتی مثل ‏TLS/mTLS‏، احراز هویت ‏DHCHAP‏ و ‏KMIP‏ به آن افزوده شده است.

bashSquid+
ceph osd pool create nvmeof-pool && rbd pool init nvmeof-pool
ceph orch apply nvmeof nvmeof-pool group1 --placement="ceph-node2,ceph-node3"

۲-۱۰-۲ رمزنگاری سطح ‏image‏#

‏RBD‏ از نسخه‌ی ‏Pacific‏ می‌تواند ‏image‏ را با قالب ‏LUKS1‏ یا ‏LUKS2‏ در خود ‏librbd‏ رمزنگاری کند (‏rbd encryption format‏). کلید رمزنگاری با ‏passphrase‏ کاربر محافظت می‌شود و هیچ‌گاه ذخیره نمی‌شود. این قابلیت با ‏librbd‏ و ‏rbd-nbd‏ کار می‌کند. برای ‏krbd‏، رمزنگاری معمول با ‏dm-crypt‏ روی دستگاه ‏/dev/rbdX‏ انجام می‌شود (روشی که ‏Ceph CSI‏ هم برای ‏volume‏های رمزنگاری‌شده به کار می‌برد).

bashReef+
rbd encryption format rbd/secure01 luks2 /root/passphrase.bin
rbd-nbd map rbd/secure01 -o encryption-format=luks2,encryption-passphrase-file=/root/passphrase.bin

۲-۱۰-۳ کیفیت سرویس (‏QoS‏)#

‏librbd‏ محدودیت‌های ‏QoS‏ سمت کلاینت دارد که می‌توان آن‌ها را در سطح ‏pool‏ یا ‏image‏ تعیین کرد، مثل ‏rbd_qos_iops_limit‏ و ‏rbd_qos_bps_limit‏:

bashReef+
rbd config image set rbd/vm01 rbd_qos_iops_limit 2000
rbd config pool set rbd rbd_qos_bps_limit 209715200   # 200 MiB/s
rbd config image ls rbd/vm01 | grep qos

این محدودیت‌ها فقط روی ‏librbd‏ اعمال می‌شوند، نه ‏krbd‏. برای کلاینت‌های ‏krbd‏، کنترل باید در لایه‌ی ‏cgroup‏ میزبان انجام شود.

۲-۱۰-۴ تغییرات دیگر از زمان کتاب#

  • ‏snapshot‏ گروهی: ‏rbd group‏ امکان گرفتن ‏snapshot‏ سازگار از چند ‏image‏ هم‌زمان را می‌دهد؛ برای برنامه‌هایی که داده‌شان روی چند دیسک است (مثلاً داده و لاگ پایگاه داده). در ‏Tentacle‏ دستورهای مربوط به اطلاعات گروه و ‏snapshot‏ گروهی گسترش یافته‌اند.
  • ‏remap namespace‏ در ‏mirroring‏: در ‏Tentacle‏ امکان ‏mirror‏ کردن یک ‏namespace‏ به ‏namespace‏ با نام دیگر در کلاستر مقابل اضافه شده است.
  • پیش‌فرض ‏msgr2‏ برای ‏krbd‏: از ‏Tentacle‏، ‏rbd device map‏ به‌طور پیش‌فرض از پروتکل ‏msgr2‏ استفاده می‌کند.
  • ‏diff-iterate‏ محلی: در ‏Squid‏، ‏diff-iterate‏ می‌تواند محلی اجرا شود که کارایی همگام‌سازی زنده‌ی دیسک در ‏QEMU‏ و ابزارهای پشتیبان‌گیری مبتنی بر تفاضل را به‌شدت بهتر کرده است.
  • دروازه‌ی ‏iSCSI‏: در حالت نگه‌داری است؛ برای استقرار تازه ‏NVMe-oF‏ را انتخاب کنید.

۲-۱۱ جمع‌بندی و مرور#

۲-۱۱-۱ خلاصه‌ی فصل#

نکته‌های کلیدی
  • ‏image‏ در ‏RBD‏ روی اشیای ۴ مگابایتی ‏RADOS‏ نگاشت می‌شود، ‏thin‏ است و روی همه‌ی ‏OSD‏های ‏pool‏ پخش می‌شود.
  • کلاینت باید کاربر اختصاصی با ‏profile rbd‏ داشته باشد، نه ‏client.admin‏.
  • ‏krbd‏ سریع و بدون وابستگی است اما همه‌ی ویژگی‌ها را ندارد؛ ‏librbd‏ کامل‌ترین مسیر است.
  • ‏snapshot‏ فوری و ‏COW‏ است؛ ‏clone‏ نوشتنی و ارزان است و ‏flatten‏ وابستگی آن را قطع می‌کند. ‏snapshot‏ بدون ‏freeze‏ فقط ‏crash-consistent‏ است.
  • ‏RBD mirroring‏ در دو حالت ‏journal‏ (RPO ثانیه‌ای، سربار بالا) و ‏snapshot‏ (RPO برابر زمان‌بندی، سربار کم) کار می‌کند و با ‏promote‏ و ‏demote‏ جهت آن عوض می‌شود.
  • ‏promote‏ اجباری بدون ‏fencing‏ یعنی خطر ‏split-brain‏.

۲-۱۱-۲ اشتباهات رایج#

  • استفاده از ‏client.admin‏ روی همه‌ی ‏hypervisor‏ها.
  • فراموش کردن ‏_netdev‏ و ترتیب خاموش شدن، و گیر کردن بوت یا خاموش شدن میزبان.
  • کوچک کردن ‏image‏ بدون کوچک کردن فایل‌سیستم.
  • فعال نکردن ‏discard‏ و پر شدن تدریجی کلاستر با فضای حذف‌شده‌ی داخل ماشین‌های مجازی.
  • نگه داشتن صدها ‏snapshot‏ قدیمی که هر نوشتن را گران‌تر و ‏scrub‏ را سنگین‌تر می‌کنند.
  • فعال کردن ‏journaling‏ روی ‏image‏هایی که با ‏krbd map‏ می‌شوند.
  • نداشتن ‏runbook‏ آزموده‌شده برای ‏failover‏ و ‏failback‏.

۲-۱۱-۳ تمرین عملی#

  1. ۱. یک کاربر با ‏profile rbd‏ محدود به ‏namespace‏ بسازید و نشان دهید نمی‌تواند ‏image‏های ‏namespace‏ دیگر را ببیند.
  2. ۲. یک ‏image‏ بسازید، ‏map‏ و فرمت کنید، ۱ گیگابایت داده بنویسید و ‏rbd du‏ را ببینید. سپس فایل‌ها را حذف کنید، ‏fstrim‏ بزنید و دوباره ‏rbd du‏ را مقایسه کنید.
  3. ۳. یک ‏image‏ طلایی با ‏snapshot‏ بسازید، ده ‏clone‏ از آن ایجاد کنید و زمان ساخت را اندازه بگیرید. یکی از ‏clone‏‌ها را ‏flatten‏ کنید و فضای مصرفی را مقایسه کنید.
  4. ۴. دو کلاستر تک‌نودی بسازید و ‏mirroring‏ حالت ‏snapshot‏ با زمان‌بندی ۵ دقیقه‌ای برقرار کنید. یک ‏switchover‏ کامل و یک ‏failover‏ اجباری با ‏failback‏ انجام دهید و همه‌ی گام‌ها را به شکل ‏runbook‏ بنویسید.
  5. ۵. با ‏rbd bench --io-type write --io-size 4K --io-threads 16 rbd/disk01‏ و سپس با ‏--io-size 4M‏ کارایی را بسنجید و تفاوت ‏IOPS‏ و توان را توضیح دهید.

۲-۱۱-۴ پرسش‌های مرور#

  1. ۱. یک ‏image‏ صد گیگابایتی تازه‌ساخته‌شده که هنوز در آن نوشته نشده، چند شیء در ‏RADOS‏ دارد؟ چرا؟
  2. ۲. تفاوت ‏krbd‏، ‏librbd‏ و ‏rbd-nbd‏ چیست و هر کدام کجا مناسب است؟
  3. ۳. چرا ویژگی ‏object-map‏ به ‏exclusive-lock‏ وابسته است؟
  4. ۴. ‏snapshot crash-consistent‏ با ‏application-consistent‏ چه فرقی دارد و چطور دومی را به دست می‌آوریم؟
  5. ۵. در قالب ‏clone‏ نسخه‌ی ۲ چه چیزی نسبت به نسخه‌ی ۱ عوض شده است؟
  6. ۶. حالت‌های ‏journal‏ و ‏snapshot‏ در ‏mirroring‏ را از نظر ‏RPO‏، سربار و سازگاری با ‏krbd‏ مقایسه کنید.
  7. ۷. تفاوت ‏--direction rx-only‏ و ‏rx-tx‏ در ‏bootstrap import‏ چیست؟
  8. ۸. چرا ‏promote‏ اجباری بدون ‏fencing‏ خطرناک است؟
  9. ۹. گام‌های ‏failback‏ پس از بازگشت سایت اصلی کدام‌اند؟
  10. ۱۰. برای میزبان ‏VMware‏ که کلاینت ‏Ceph‏ ندارد، امروز کدام راه برای استفاده از ‏RBD‏ توصیه می‌شود؟

پاسخ این پرسش‌ها در پیوست ه: پاسخ پرسش‌ها آمده است. پاسخ پرسش‌های این فصل →

۲-۱۱-۵ منابع این فصل#

راهنمای جامع Ceph · ۱٫۶ · آبان ۱۴۰۵
© 2026 Ramin Najarbashi · CC BY-NC-SA 4.0

حریم خصوصی: این سایت کوکی نمی‌گذارد. برای شمارش بازدیدها از Cloudflare Web Analytics استفاده می‌شود که کوکی ندارد و افراد را دنبال نمی‌کند.