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

‏Ceph‏ و ‏OpenStack‏

۵۶ دقیقه مطالعهویرایش این صفحه
حوصله ندارم این فصل در چند خط
  • یک کلاستر ‏Ceph‏ می‌تواند همه‌ی نیازهای ذخیره‌سازی ‏OpenStack‏ را پوشش دهد: ‏Glance‏، ‏Cinder‏، ‏Cinder Backup‏، دیسک‌های ‏ephemeral‏ در ‏Nova‏، ‏Manila‏ و ‏Swift/S3‏ از راه ‏RGW‏.
  • هر سرویس ‏pool‏ و کاربر ‏Ceph‏ خودش را دارد؛ ‏client.cinder‏ روی ‏pool‏ تصاویر فقط اجازه‌ی خواندن دارد.
  • ‏copy-on-write‏ از ‏image‏ به ‏volume‏ به تصویر با قالب ‏raw‏ و ‏show_image_direct_url = True‏ نیاز دارد؛ با ‏qcow2‏ بی‌صدا از دست می‌رود.
  • ‏UUID‏ مربوط به ‏secret‏ در ‏libvirt‏ باید روی همه‌ی ‏compute node‏ها یکسان و برابر ‏rbd_secret_uuid‏ باشد؛ ‏images_type = rbd‏ دیسک‌های ‏ephemeral‏ را هم به ‏Ceph‏ می‌برد و ‏live migration‏ و ‏evacuate‏ را ساده می‌کند.

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

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

‏OpenStack‏ و ‏Ceph‏ تقریباً هم‌زمان بالغ شدند و خیلی زود به «زوج پیش‌فرض» ابرهای خصوصی تبدیل شدند. نظرسنجی‌های کاربران ‏OpenStack‏ سال‌ها نشان داده‌اند که ‏Ceph‏ محبوب‌ترین پشتوانه‌ی ذخیره‌سازی بلوکی آن است. در این فصل می‌بینیم:

  • چرا ‏Ceph‏ این‌قدر با ‏OpenStack‏ جور است و کدام سرویس‌های ‏OpenStack‏ از کدام بخش ‏Ceph‏ استفاده می‌کنند؟
  • برای ‏Glance‏، ‏Cinder‏، ‏Cinder Backup‏ و ‏Nova‏ چه ‏pool‏ها و کاربرانی با چه مجوزهایی لازم است؟
  • پیکربندی هر سرویس چگونه است و ‏copy-on-write‏ چطور ساختن ماشین مجازی را از دقیقه‌ها به ثانیه‌ها می‌رساند؟
  • راه‌اندازی یک محیط آزمایشی ‏OpenStack‏ امروز با چه ابزاری ساده‌تر است؟
  • در ‏production‏ چه نکته‌هایی در کارایی، امنیت و عملیات اهمیت دارد؟

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

کلاستر ‏Ceph‏ فصل ۱ و آشنایی با ‏RBD‏ (فصل ۲). آشنایی مقدماتی با اجزای ‏OpenStack‏ کمک می‌کند. آموزش‌های قدیمی از بسته‌های ‏RDO‏ روی ‏CentOS 7‏ برای نصب ‏OpenStack‏ استفاده می‌کردند؛ ما گزینه‌های امروزی را معرفی می‌کنیم و پیکربندی‌ها را به شکلی می‌آوریم که مستقل از ابزار نصب باشد.

۳-۲ ‏Ceph‏: بهترین همراه ‏OpenStack‏#

۳-۲-۱ چرا این زوج موفق شد؟#

  • یک پشتوانه برای همه: ‏Glance‏ (imageها)، ‏Cinder‏ (volumeها)، ‏Cinder Backup‏، ‏Nova‏ (دیسک‌های موقت)، ‏Manila‏ (اشتراک فایل) و حتی ‏API‏ سازگار با ‏Swift‏ و ‏S3‏ همه از یک کلاستر ‏Ceph‏ سرویس می‌گیرند.
  • ‏copy-on-write‏ سرتاسری: وقتی ‏Glance‏ و ‏Cinder/Nova‏ هر دو روی ‏Ceph‏ هستند، ساختن ‏volume‏ یا دیسک ‏VM‏ از یک ‏image‏ فقط یک ‏rbd clone‏ است. نیازی به کپی چندگیگابایتی ‏image‏ روی دیسک ‏compute‏ نیست.
  • مهاجرت زنده بدون ذخیره‌سازی مشترک جداگانه: چون دیسک ‏VM‏ روی ‏Ceph‏ است، ‏live migration‏ فقط حافظه را جابه‌جا می‌کند.
  • مقیاس هم‌پای ابر: هر ‏compute‏ جدید فقط یک کلاینت دیگر است. ظرفیت و کارایی ذخیره‌سازی با افزودن ‏node‏ به ‏Ceph‏ بزرگ می‌شود.
  • متن‌باز و بدون قفل فروشنده: هر دو پروژه متن‌بازند و جامعه‌ی مشترک بزرگی دارند.
شکل ۳-۱: نگاشت سرویس‌های OpenStack به اجزای Ceph : هر سرویس pool یا رابط اختصاصی خودش را دارد
شکل ۳-۱: نگاشت سرویس‌های ‏OpenStack‏ به اجزای ‏Ceph‏: هر سرویس ‏pool‏ یا رابط اختصاصی خودش را دارد

۳-۲-۲ سرویس‌ها و ‏pool‏ها#

سرویس ‏OpenStack‏بخش ‏Ceph‏‏pool‏ پیشنهادیکاربر ‏Ceph‏
‏Glance‏‏RBD‏‏images‏‏client.glance‏
‏Cinder‏‏RBD‏‏volumes‏‏client.cinder‏
‏Cinder Backup‏‏RBD‏‏backups‏‏client.cinder-backup‏
‏Nova‏ (ephemeral)‏RBD‏‏vms‏‏client.cinder‏ (یا ‏client.nova‏)
‏Manila‏‏CephFS‏ (بومی یا ‏NFS‏)فایل‌سیستم ‏CephFS‏‏client.manila‏
‏Swift/S3‏‏RGW‏ با ‏Keystone‏‏pool‏های ‏RGW‏کاربر ‏RGW‏ (فصل ۴)

۳-۳ راه‌اندازی ‏OpenStack‏ آزمایشی#

۳-۳-۱ گزینه‌های امروز#

روش قدیمی نصب ‏OpenStack‏ با بسته‌های ‏RDO‏ و ابزار ‏Packstack‏ روی ‏CentOS 7‏ بود که امروز هر دو منسوخ‌اند. امروز معمول‌ترین راه‌ها این‌ها هستند:

ابزارمناسب براینکته
‏DevStack‏توسعه و آزمایش سریع روی یک ماشینبرای ‏production‏ نیست؛ با افزونه‌ی ‏devstack-plugin-ceph‏ می‌توان ‏Ceph‏ را هم وصل کرد
‏Kolla-Ansible‏آزمایشگاه و ‏production‏ کانتینریپشتیبانی جاافتاده از ‏Ceph‏ خارجی برای ‏Glance‏، ‏Cinder‏، ‏Nova‏ و ‏Manila‏
‏OpenStack-Ansible‏‏production‏ روی ‏LXC‏ یا مستقیمنقش‌های ‏Ansible‏ برای اتصال ‏Ceph‏
‏OpenStack Charms‏ / ‏Sunbeam‏محیط‌های ‏Canonical‏اتصال به ‏Ceph‏ از طریق رابط‌های ‏Juju‏

در این فصل فرض می‌کنیم یک ‏OpenStack‏ آزمایشی (مثلاً با ‏Kolla-Ansible‏ یا ‏DevStack‏) آماده است و ‏Ceph‏ از قبل وجود دارد، یعنی الگوی «‏Ceph‏ خارجی» که در ‏production‏ هم رایج‌ترین الگوست. پیکربندی‌ها را با نام فایل‌های استاندارد هر سرویس می‌آوریم؛ در ‏Kolla-Ansible‏ همین تنظیمات در پوشه‌ی ‏/etc/kolla/config/<service>/‏ قرار می‌گیرند.

💡 نکته

‏Ceph‏ را درون ‏OpenStack‏ (مثلاً با نقش‌های قدیمی ‏Kolla‏ یا ‏TripleO‏) مستقر نکنید. چرخه‌ی عمر دو سیستم را جدا نگه دارید: ‏Ceph‏ با ‏cephadm‏ مستقر و ارتقا داده شود و ‏OpenStack‏ فقط کلاینت آن باشد. این جداسازی ارتقای هر کدام را مستقل و کم‌خطر می‌کند. ‏TripleO‏ هم که زمانی این الگو را ترویج می‌کرد، کنار گذاشته شده است.

۳-۴ پیکربندی ‏OpenStack‏ به‌عنوان کلاینت ‏Ceph‏#

۳-۴-۱ ساختن ‏pool‏ها#

bashReef+
for p in images volumes backups vms; do
  ceph osd pool create $p
  rbd pool init $p
done
ceph osd pool ls detail | grep -E "images|volumes|backups|vms"

با فعال بودن ‏pg_autoscaler‏ نیازی به تعیین دستی تعداد ‏PG‏ نیست، اما بهتر است سهم مورد انتظار هر ‏pool‏ را به ‏autoscaler‏ بگویید تا از ابتدا ‏PG‏ مناسب بگیرد و بعداً موج تقسیم ‏PG‏ نداشته باشید:

bashReef+
ceph osd pool set volumes target_size_ratio 0.6
ceph osd pool set vms target_size_ratio 0.2
ceph osd pool set images target_size_ratio 0.1
ceph osd pool set backups target_size_ratio 0.1

۳-۴-۲ کاربران و مجوزها#

مستندات رسمی ‏Ceph‏ این سه کاربر را پیشنهاد می‌کنند. دقت کنید که ‏client.cinder‏ روی ‏images‏ فقط اجازه‌ی خواندن دارد، چون فقط باید از ‏image‏ها ‏clone‏ بسازد:

bashReef+
ceph auth get-or-create client.glance \
  mon 'profile rbd' osd 'profile rbd pool=images' mgr 'profile rbd pool=images'
ceph auth get-or-create client.cinder \
  mon 'profile rbd' \
  osd 'profile rbd pool=volumes, profile rbd pool=vms, profile rbd-read-only pool=images' \
  mgr 'profile rbd pool=volumes, profile rbd pool=vms'
ceph auth get-or-create client.cinder-backup \
  mon 'profile rbd' osd 'profile rbd pool=backups' mgr 'profile rbd pool=backups'
شکل ۳-۲: توزیع کاربران و keyring ها: سرویس‌های کنترلی هر کدام کاربر خودشان را دارند و nova-compute از طریق secret در libvirt به کلید client.cinder دسترسی پیدا می‌کند
شکل ۳-۲: توزیع کاربران و ‏keyring‏ها: سرویس‌های کنترلی هر کدام کاربر خودشان را دارند و ‏nova-compute‏ از طریق ‏secret‏ در ‏libvirt‏ به کلید ‏client.cinder‏ دسترسی پیدا می‌کند

۳-۴-۳ توزیع ‏keyring‏ و ‏ceph.conf‏#

bashReef+
ceph config generate-minimal-conf > /tmp/ceph.conf
# گره Glance
scp /tmp/ceph.conf glance-host:/etc/ceph/ceph.conf
ceph auth get client.glance | ssh glance-host sudo tee /etc/ceph/ceph.client.glance.keyring
ssh glance-host sudo chown glance:glance /etc/ceph/ceph.client.glance.keyring
# گره‌های cinder-volume و nova-compute
ceph auth get client.cinder | ssh cinder-host sudo tee /etc/ceph/ceph.client.cinder.keyring
ceph auth get client.cinder | ssh compute1 sudo tee /etc/ceph/ceph.client.cinder.keyring
# گره cinder-backup
ceph auth get client.cinder-backup | ssh backup-host sudo tee /etc/ceph/ceph.client.cinder-backup.keyring
⚠ هشدار

‏keyring‏ها را با مالکیت و مجوز درست (‏chmod 600‏ و مالک کاربر سرویس) نگه دارید. اگر سرویس ‏OpenStack‏ نتواند ‏keyring‏ را بخواند، خطای معمولاً گمراه‌کننده‌ای مثل «‏error connecting to the cluster‏» یا «‏permission denied‏» با کد ۱۳ می‌گیرید.

۳-۴-۴ ‏secret‏ در ‏libvirt‏#

‏QEMU‏ روی گره‌های ‏compute‏ کلید ‏Ceph‏ را از ‏libvirt‏ می‌گیرد، نه از فایل. باید یک ‏secret‏ با ‏UUID‏ ثابت بسازیم و همان ‏UUID‏ را بعداً در ‏Cinder‏ و ‏Nova‏ به کار ببریم:

bash
UUID=$(uuidgen)        # یک‌بار بسازید و روی همه‌ی computeها همین را استفاده کنید
cat > secret.xml <<X
<secret ephemeral='no' private='no'>
  <uuid>${UUID}</uuid>
  <usage type='ceph'><name>client.cinder secret</name></usage>
</secret>
X
sudo virsh secret-define --file secret.xml
sudo virsh secret-set-value --secret ${UUID} \
  --base64 $(ceph auth get-key client.cinder)
sudo virsh secret-list
💡 نکته

‏UUID secret‏ باید روی همه‌ی ‏compute‏ها یکسان باشد، وگرنه ‏live migration‏ شکست می‌خورد، چون ‏XML‏ دامنه با همان ‏UUID‏ به میزبان مقصد منتقل می‌شود. ابزارهای استقرار مثل ‏Kolla-Ansible‏ این کار را خودکار انجام می‌دهند؛ مقدار ‏UUID‏ را در ‏inventory‏ نگه دارید.

۳-۵ پیکربندی ‏Glance‏ با پشتوانه‌ی ‏Ceph‏#

۳-۵-۱ تنظیمات ‏glance-api‏#

‏Glance image‏ها را به‌شکل ‏image‏‌های ‏RBD‏ در ‏pool‏ ‏images‏ نگه می‌دارد و پس از آپلود، از هر ‏image‏ یک ‏snapshot‏ به نام ‏snap‏ می‌سازد تا ‏Cinder‏ و ‏Nova‏ بتوانند از آن ‏clone‏ بگیرند.

ini
# /etc/glance/glance-api.conf
[DEFAULT]
enabled_backends = ceph:rbd
show_image_direct_url = True

[glance_store]
default_backend = ceph

[ceph]
rbd_store_pool = images
rbd_store_user = glance
rbd_store_ceph_conf = /etc/ceph/ceph.conf
rbd_store_chunk_size = 8
⚠ هشدار

‏show_image_direct_url = True‏ برای ‏copy-on-write‏ ضروری است، چون ‏Cinder‏ و ‏Nova‏ از روی همین نشانی (‏rbd://fsid/images/<id>/snap‏) تشخیص می‌دهند ‏image‏ روی همان کلاستر است و می‌توانند ‏clone‏ بسازند. اما این گزینه محل ذخیره‌ی ‏image‏ را از طریق ‏API‏ نشان می‌دهد. اگر ‏API Glance‏ در دسترس کاربران نهایی است، با سیاست‌های ‏Glance‏ دسترسی به این فیلد را محدود کنید یا از قابلیت چند نشانی و ‏endpoint‏ داخلی استفاده کنید.

۳-۵-۲ قالب ‏image‏: همیشه ‏raw‏#

‏copy-on-write‏ فقط وقتی کار می‌کند که ‏image‏ در ‏Glance‏ با قالب ‏raw‏ ذخیره شده باشد. اگر ‏qcow2‏ آپلود کنید، ‏Cinder‏ و ‏Nova‏ باید هر بار آن را دانلود و تبدیل کنند و مزیت اصلی از بین می‌رود.

bashReef+
qemu-img convert -f qcow2 -O raw ubuntu-24.04.qcow2 ubuntu-24.04.raw
openstack image create ubuntu-24.04 \
  --disk-format raw --container-format bare \
  --file ubuntu-24.04.raw \
  --property hw_scsi_model=virtio-scsi --property hw_disk_bus=scsi \
  --property hw_qemu_guest_agent=yes --property os_require_quiesce=yes
rbd -p images ls -l --id glance

ویژگی‌های بالا که مستندات ‏Ceph‏ هم پیشنهاد می‌کنند، به این معنی‌اند: کنترلر ‏virtio-scsi‏ (که ‏discard‏ را پشتیبانی می‌کند)، اتصال دیسک‌ها به آن، فعال کردن ‏QEMU guest agent‏ و درخواست ‏quiesce‏ (fsfreeze) هنگام ‏snapshot‏ برای سازگاری بهتر.

✎ یادداشت

نسخه‌های جدید ‏Glance‏ امکان «‏image conversion‏» هنگام ‏import‏ را دارند (‏import plugin‏). با فعال کردن آن، کاربر می‌تواند ‏qcow2‏ آپلود کند و ‏Glance‏ خودش آن را به ‏raw‏ تبدیل و در ‏Ceph‏ ذخیره می‌کند. این برای ابرهای عمومی که کاربران نهایی ‏image‏ آپلود می‌کنند، راحت‌تر از آموزش دادن به آن‌هاست.

۳-۵-۳ غیرفعال کردن ‏cache‏ محلی ‏Glance‏#

اگر ‏cache‏ مدیریتی ‏Glance‏ فعال باشد، ‏image‏ها روی دیسک محلی گره ‏Glance‏ هم کپی می‌شوند که با ‏Ceph‏ بی‌فایده است. در پیکربندی ‏pipeline‏، ‏flavor‏ را از ‏keystone+cachemanagement‏ به ‏keystone‏ برگردانید.

۳-۶ پیکربندی ‏Cinder‏#

۳-۶-۱ ‏backend RBD‏#

ini
# /etc/cinder/cinder.conf
[DEFAULT]
enabled_backends = ceph
glance_api_version = 2

[ceph]
volume_driver = cinder.volume.drivers.rbd.RBDDriver
volume_backend_name = ceph
rbd_pool = volumes
rbd_ceph_conf = /etc/ceph/ceph.conf
rbd_flatten_volume_from_snapshot = false
rbd_max_clone_depth = 5
rbd_store_chunk_size = 4
rados_connect_timeout = -1
rbd_user = cinder
rbd_secret_uuid = 457eb676-33da-42ec-9a8c-9293d545c337
گزینهمعناتوصیه
‏rbd_flatten_volume_from_snapshot‏‏volume‏ ساخته‌شده از ‏snapshot‏ فوراً ‏flatten‏ شود؟‏false‏ برای سرعت؛ ‏true‏ اگر می‌خواهید ‏snapshot‏ را زود پاک کنید
‏rbd_max_clone_depth‏حداکثر عمق زنجیره‌ی ‏clone‏ پیش از ‏flatten‏ خودکار۵ (پیش‌فرض) مناسب است
‏rbd_store_chunk_size‏اندازه‌ی شیء به مگابایت۴ (همان پیش‌فرض ‏RBD‏)
‏rbd_secret_uuid‏‏UUID secret‏ در ‏libvirt‏باید با ‏secret‏ تعریف‌شده روی ‏compute‏ها یکی باشد
‏rbd_exclusive_cinder_pool‏‏pool‏ فقط در اختیار ‏Cinder‏ است‏true‏ برای سرعت محاسبه‌ی ظرفیت در ‏pool‏های بزرگ

۳-۶-۲ نوع ‏volume‏ و چند ‏backend‏#

در ‏production‏ معمولاً بیش از یک کلاس ذخیره‌سازی دارید، مثلاً یک ‏pool‏ روی ‏NVMe‏ و یکی روی ‏HDD‏. هر کدام یک ‏backend‏ جداگانه در ‏Cinder‏ و یک ‏volume type‏ می‌شوند:

ini
[DEFAULT]
enabled_backends = ceph-nvme,ceph-hdd

[ceph-nvme]
volume_driver = cinder.volume.drivers.rbd.RBDDriver
volume_backend_name = ceph-nvme
rbd_pool = volumes-nvme
rbd_user = cinder
rbd_secret_uuid = 457eb676-33da-42ec-9a8c-9293d545c337

[ceph-hdd]
volume_driver = cinder.volume.drivers.rbd.RBDDriver
volume_backend_name = ceph-hdd
rbd_pool = volumes-hdd
rbd_user = cinder
rbd_secret_uuid = 457eb676-33da-42ec-9a8c-9293d545c337
bash
openstack volume type create fast
openstack volume type set fast --property volume_backend_name=ceph-nvme
openstack volume type create capacity
openstack volume type set capacity --property volume_backend_name=ceph-hdd

یادتان باشد که مجوز ‏client.cinder‏ باید ‏pool‏های جدید را هم شامل شود (‏ceph auth caps‏). ‏pool NVMe‏ هم با قاعده‌ی ‏CRUSH‏ مبتنی بر ‏device class‏ (‏ceph osd crush rule create-replicated nvme-rule default host nvme‏) روی دیسک‌های درست قرار می‌گیرد (فصل ۸).

💡 نکته

برای کلاسترهای چندهزار ‏volume‏، ‏Cinder active-active‏ و نیز گزینه‌ی ‏backend_host‏ (یا ‏cluster‏ در نسخه‌های جدید) را در نظر بگیرید تا چند سرویس ‏cinder-volume‏ بتوانند یک ‏backend Ceph‏ را مدیریت کنند و خرابی یکی، مدیریت ‏volume‏ها را متوقف نکند.

۳-۷ پیکربندی ‏Cinder Backup‏#

پشتیبان‌گیری از ‏volume‏ به یک ‏pool‏ جداگانه (یا ترجیحاً یک کلاستر ‏Ceph‏ دیگر) انجام می‌شود. درایور ‏Ceph‏ برای ‏Cinder Backup‏ قابلیت پشتیبان‌گیری افزایشی را دارد: اگر مبدأ و مقصد هر دو ‏Ceph‏ باشند، فقط تفاضل تغییرات با ‏rbd export-diff‏ و ‏import-diff‏ منتقل می‌شود.

ini
[DEFAULT]
backup_driver = cinder.backup.drivers.ceph.CephBackupDriver
backup_ceph_conf = /etc/ceph/ceph.conf
backup_ceph_user = cinder-backup
backup_ceph_chunk_size = 134217728
backup_ceph_pool = backups
backup_ceph_stripe_unit = 0
backup_ceph_stripe_count = 0
restore_discard_excess_bytes = true
⚠ هشدار

پشتیبانی که روی همان کلاستر ‏Ceph‏ نگه داشته شود، در برابر خرابی خود کلاستر (اشتباه مدیریتی، باگ نرم‌افزاری، باج‌افزاری که به اعتبارنامه‌ی مدیر رسیده) محافظتی ندارد. قاعده‌ی ۳-۲-۱ را فراموش نکنید: برای داده‌ی حیاتی، ‏backup‏ را با ‏backup_ceph_conf‏ به یک کلاستر دیگر (ترجیحاً در سایت دیگر) بفرستید.

۳-۸ پیکربندی ‏Nova‏#

۳-۸-۱ بوت مستقیم از ‏Ceph‏ (ephemeral روی ‏RBD‏)#

به‌طور پیش‌فرض ‏Nova‏ دیسک‌های موقت ماشین‌ها را روی دیسک محلی ‏compute‏ می‌سازد. با پیکربندی زیر، دیسک ریشه‌ی هر ‏VM‏ یک ‏image‏ در ‏pool‏ ‏vms‏ می‌شود که از ‏image Glance clone‏ شده است:

ini
# /etc/nova/nova.conf روی همه‌ی computeها
[libvirt]
images_type = rbd
images_rbd_pool = vms
images_rbd_ceph_conf = /etc/ceph/ceph.conf
rbd_user = cinder
rbd_secret_uuid = 457eb676-33da-42ec-9a8c-9293d545c337
disk_cachemodes = "network=writeback"
hw_disk_discard = unmap
live_migration_permit_auto_converge = true
شکل ۳-۳: copy-on-write سرتاسری: هم volume در Cinder و هم دیسک موقت Nova clone همان snapshot در Glance ‌اند
شکل ۳-۳: ‏copy-on-write‏ سرتاسری: هم ‏volume‏ در ‏Cinder‏ و هم دیسک موقت ‏Nova clone‏ همان ‏snapshot‏ در ‏Glance‏‌اند
◆ عمیق‌تر

‏disk_cachemodes = "network=writeback"‏ حالت ‏cache‏ دیسک‌های شبکه‌ای در ‏QEMU‏ را ‏writeback‏ می‌کند تا ‏cache‏ کلاینت ‏librbd‏ فعال شود. این امن است، چون ‏librbd‏ به ‏flush‏های سیستم‌عامل مهمان احترام می‌گذارد و گزینه‌ی ‏rbd cache writethrough until flush‏ (پیش‌فرض ‏true‏) تضمین می‌کند تا وقتی مهمان نشان نداده ‏flush‏ می‌فرستد، ‏cache‏ در حالت ‏writethrough‏ بماند. ‏hw_disk_discard = unmap‏ هم ‏discard‏ مهمان را به ‏Ceph‏ می‌رساند تا فضای حذف‌شده واقعاً آزاد شود.

۳-۸-۲ اتصال ‏volume‏های ‏Cinder‏#

برای ‏attach‏ کردن ‏volume‏ها نیازی به پیکربندی بیشتر در ‏Nova‏ نیست، به شرط آن‌که ‏secret libvirt‏ تعریف شده و ‏keyring‏ ‏client.cinder‏ روی ‏compute‏ باشد. ‏Nova‏ از ‏Cinder‏ اطلاعات اتصال (پروتکل ‏rbd‏، ‏pool‏، نام ‏image‏، کاربر و ‏UUID secret‏) را می‌گیرد و در ‏XML‏ دامنه یک دیسک از نوع ‏network‏ با پروتکل ‏rbd‏ تعریف می‌کند.

شکل ۳-۴: مسیر بوت از volume: Cinder volume را با clone می‌سازد و nova-compute از طریق libvirt و librbd آن را به QEMU می‌دهد
شکل ۳-۴: مسیر بوت از ‏volume: Cinder volume‏ را با ‏clone‏ می‌سازد و ‏nova-compute‏ از طریق ‏libvirt‏ و ‏librbd‏ آن را به ‏QEMU‏ می‌دهد

۳-۸-۳ ‏admin socket‏ و لاگ برای ماشین‌های مجازی#

مستندات ‏Ceph‏ پیشنهاد می‌کنند روی هر ‏hypervisor‏ برای کلاینت‌های ‏QEMU‏، ‏admin socket‏ و فایل لاگ فعال شود. این کار هنگام عیب‌یابی کارایی یک ‏VM‏ خاص بسیار ارزشمند است:

ini
# /etc/ceph/ceph.conf روی computeها
[client]
rbd cache = true
rbd cache writethrough until flush = true
admin socket = /var/run/ceph/guests/$cluster-$type.$id.$pid.$cctid.asok
log file = /var/log/qemu/qemu-guest-$pid.log
rbd concurrent management ops = 20
bashReef+
sudo mkdir -p /var/run/ceph/guests/ /var/log/qemu/
sudo chown qemu:libvirt /var/run/ceph/guests /var/log/qemu/   # نام کاربر و گروه به توزیع بستگی دارد
# بعد از راه‌اندازی دوباره‌ی VM:
sudo ceph --admin-daemon /var/run/ceph/guests/ceph-client.cinder.<pid>.<cctid>.asok perf dump

۳-۹ از درون کد: ‏clone‏، ‏copy-on-write‏ و ‏copyup‏ در ‏librbd‏#

۳-۹-۱ چرا این بخش برای ‏OpenStack‏ مهم است#

وقتی ‏Glance‏ و ‏Cinder/Nova‏ هر دو روی ‏Ceph‏ باشند، ساختن یک ‏VM‏ جدید به معنای کپی کردن تصویر نیست. سرویس یک ‏snapshot‏ از تصویر ‏Glance‏ می‌گیرد و یک ‏clone‏ لایه‌ای (‏feature‏ ‏layering‏) از آن می‌سازد. ‏clone‏ فقط فراداده است: یک ‏header‏ جدید با اشاره‌گر ‏parent‏ به (‏pool‏، ‏image id‏، ‏snap id‏) و ‏overlap‏. هزینه‌ی واقعی بعداً و به‌صورت تدریجی پرداخت می‌شود، یعنی در اولین نوشتن روی هر شیء با عملیاتی به نام ‏copyup‏.

شکل ۳-۵: الگوی رایج OpenStack روی RBD . ردیف بالا نشان می‌دهد که Glance تصویر را با یک snapshot ذخیره می‌کند. در clone v1 این snapshot باید protect شود؛ در clone v2 (پیش‌فرض rbd_default_clone_format=auto ، در صورتی که min_compat_client حداقل mimic باشد) این کار لازم نیست و snapshot والد حتی با وجود فرزند قابل حذف است و به trash می‌رود. ردیف میانی صدها volume را نشان می‌دهد که بدون کپی داده از یک snapshot ساخته شده‌اند. ردیف پایین پیامدهای عملیاتی است: خواندن ناحیه‌های نوشته‌نشده از والد سرویس داده می‌شود، اولین نوشتن روی هر شیء یک copyup به اندازه‌ی کل شیء ایجاد می‌کند، و flatten وابستگی را قطع ولی کل فضا را مصرف می‌کند
شکل ۳-۵: الگوی رایج ‏OpenStack‏ روی ‏RBD‏. ردیف بالا نشان می‌دهد که ‏Glance‏ تصویر را با یک ‏snapshot‏ ذخیره می‌کند. در ‏clone v1‏ این ‏snapshot‏ باید ‏protect‏ شود؛ در ‏clone v2‏ (پیش‌فرض ‏rbd_default_clone_format=auto‏، در صورتی که ‏min_compat_client‏ حداقل ‏mimic‏ باشد) این کار لازم نیست و ‏snapshot‏ والد حتی با وجود فرزند قابل حذف است و به ‏trash‏ می‌رود. ردیف میانی صدها ‏volume‏ را نشان می‌دهد که بدون کپی داده از یک ‏snapshot‏ ساخته شده‌اند. ردیف پایین پیامدهای عملیاتی است: خواندن ناحیه‌های نوشته‌نشده از والد سرویس داده می‌شود، اولین نوشتن روی هر شیء یک ‏copyup‏ به اندازه‌ی کل شیء ایجاد می‌کند، و ‏flatten‏ وابستگی را قطع ولی کل فضا را مصرف می‌کند

۳-۹-۲ ماشین حالت ‏CopyupRequest‏#

در ‏src/librbd/io/CopyupRequest.cc‏ توابع به ترتیب اجرا این‌ها هستند: ‏send‏ → ‏read_from_parent‏ → (‏deep_copy‏ در صورت نیاز) → ‏update_object_maps‏ → ‏copyup‏ → ‏finish‏. چند درخواست نوشتن هم‌زمان روی یک شیء با ‏append_request‏ به یک ‏copyup‏ مشترک متصل می‌شوند تا داده‌ی والد فقط یک بار خوانده شود.

شکل ۳-۶: مسیر نوشتن روی یک تصویر clone . اگر شیء فرزند وجود داشته باشد، نوشتن مستقیم است. در غیر این صورت، CopyupRequest کل محدوده‌ی شیء را از snapshot والد می‌خواند، در صورت وجود deep-flatten و snapshot های فرزند داده را به آن‌ها هم کپی می‌کند ( deep_copy )، object map را به‌روز می‌کند و در نهایت یک عملیات اتمیک واحد روی OSD می‌فرستد. این عملیات شامل متد cls به نام rbd.copyup یا sparse_copyup است که داده‌ی والد را فقط در صورت نبود شیء می‌نویسد، و به دنبال آن خود write کاربر می‌آید. اتمیک بودن این ترکیب تضمین می‌کند که هیچ خواننده‌ای شیء نیمه‌کپی‌شده نبیند
شکل ۳-۶: مسیر نوشتن روی یک تصویر ‏clone‏. اگر شیء فرزند وجود داشته باشد، نوشتن مستقیم است. در غیر این صورت، ‏CopyupRequest‏ کل محدوده‌ی شیء را از ‏snapshot‏ والد می‌خواند، در صورت وجود ‏deep-flatten‏ و ‏snapshot‏های فرزند داده را به آن‌ها هم کپی می‌کند (‏deep_copy‏)، ‏object map‏ را به‌روز می‌کند و در نهایت یک عملیات اتمیک واحد روی ‏OSD‏ می‌فرستد. این عملیات شامل متد ‏cls‏ به نام ‏rbd.copyup‏ یا ‏sparse_copyup‏ است که داده‌ی والد را فقط در صورت نبود شیء می‌نویسد، و به دنبال آن خود ‏write‏ کاربر می‌آید. اتمیک بودن این ترکیب تضمین می‌کند که هیچ خواننده‌ای شیء نیمه‌کپی‌شده نبیند
cpp
// from ceph/ceph, LGPL — src/librbd/io/CopyupRequest.cc, copyup() (excerpt)
    cls_client::sparse_copyup(op, m_copyup_extent_map, m_copyup_data);
...
    cls_client::copyup(op, thick_bl);
text
pseudocode: مسیر خواندن/نوشتن clone (خلاصه‌ی این کتاب)
  read(objno, extent):
     r = osd_read(child_obj)
     if r == -ENOENT and extent within parent_overlap:
         r = read parent image @snap at same image offset
         if rbd_clone_copy_on_read: schedule copyup(objno)   # پیش‌فرض false
     return r
  write(objno, extent, data):
     if child object may not exist (object map != EXISTS) and within overlap:
         copyup = get_or_create CopyupRequest(objno); copyup.append(this)
     else: osd_write(child_obj, data)
گزینه (‏rbd.yaml.in‏)پیش‌فرضاثر
‏rbd_default_clone_format‏‏auto‏‏v2‏ اگر ‏min_compat_client‏ حداقل ‏mimic‏ باشد
‏rbd_clone_copy_on_read‏‏false‏‏copyup‏ هنگام خواندن (گرم کردن فرزند، با هزینه‌ی نوشتن)
‏rbd_parent_cache_enabled‏‏false‏کش فقط‌خواندنی والد روی میزبان (‏immutable object cache‏)
‏rbd_sparse_read_threshold_bytes‏۶۴_‏K‏آستانه‌ی استفاده از ‏sparse-read‏
⚠ هشدار

«طوفان ‏copyup‏»: پس از راه‌اندازی هم‌زمان صدها ‏VM‏ از یک تصویر، هر نوشتن کوچکی (مثل لاگ یا ‏cloud-init‏) روی شیئی دست‌نخورده یک خواندن و نوشتن ۴‏MiB‏ ایجاد می‌کند. نشانه‌ها: افزایش ناگهانی ‏write bandwidth‏ با ‏IOPS‏ پایین در ‏ceph osd pool stats‏. راه‌حل‌ها: اشیای کوچک‌تر برای تصاویر پایه، ‏rbd flatten‏ تدریجی برای ‏VM‏های پرنوشتن، یا محدود کردن شدت ‏boot‏.

✚ عیب‌یابی

بررسی زنجیره‌ی والد:

bashReef+
rbd info volumes/volume-aaa | grep -A3 parent
rbd children images/ubuntu@snap           # همه‌ی فرزندان (v2 شامل pool دیگر)
rbd du volumes/volume-aaa                 # مصرف واقعی پس از copyupها
rbd flatten volumes/volume-aaa            # عملیات طولانی؛ پیشرفت را گزارش می‌دهد
ceph osd get-require-min-compat-client

۳-۱۰ ‏librbd‏ زیر پای ‏OpenStack‏، با عدد#

هر دیسکی که ‏Nova‏، ‏Cinder‏ یا ‏Glance‏ روی ‏Ceph‏ می‌سازند، در نهایت یک ‏image‏ در ‏librbd‏ است. این بخش چهار الگوریتم ‏librbd‏ را که مستقیم روی رفتار ماشین‌های مجازی ‏OpenStack‏ اثر می‌گذارند، با کد و عدد دنبال می‌کند: نگاشت بایت‌های دیسک به شیء، ‏object map‏، محاسبه‌ی تفاوت میان ‏snapshot‏ها (پایه‌ی پشتیبان افزایشی ‏cinder-backup‏) و محدودکننده‌ی ‏QoS‏. همه‌ی ارجاع‌های کد به شاخه‌ی ‏main‏ مخزن ‏Ceph‏ است که در نوامبر ۲۰۲۶ بررسی شد. عددهای مثال‌ها با بازپیاده‌سازی پایتونی همین توابع دوباره حساب شده‌اند.

۳-۱۰-۱ از ‏offset‏ دیسک تا نام شیء#

∑ الگوریتم: نگاشت بازه‌ی ‏image‏ به شیءهای ‏RADOS‏ (striping)

منبع در کد: src/osdc/Striper.cc :: Striper::file_to_extents؛ src/librbd/ImageCtx.cc :: ImageCtx::init_layout

پیچیدگی: ‏O‏(len / ‏stripe_unit‏) قطعه برای هر درخواست؛ ‏O‏(۱) برای هر قطعه

text
pseudocode
os = object_size ; su = stripe_unit ; sc = stripe_count
if sc == 1: su = os                      # حالت پیش‌فرض RBD: هر شیء یک «بلوک»
spo = os / su                             # stripe در هر شیء
cur = off ; left = len
while left > 0:
    blockno   = cur / su
    stripeno  = blockno / sc ; stripepos = blockno mod sc
    objectset = stripeno / spo
    objectno  = objectset × sc + stripepos
    x_off = (stripeno mod spo) × su + cur mod su
    x_len = min(left, su − cur mod su)
    به extent شیء objectno اضافه کن (buffer_extents ترتیب بافر کاربر را نگه می‌دارد)
    cur += x_len ; left −= x_len
oid(objectno) = object_prefix + "." + hex16(objectno)   # image قالب 2: rbd_data.<id>.%016llx

مثال عددی. یک ‏image‏ با ‏order = 22‏ (شیء ۴ ‏MiB‏) و ‏stripe‏ پیش‌فرض (‏stripe_count‏ = ۱) را در نظر بگیرید. ‏VM‏ مهمان ۱ ‏MiB‏ را از ‏offset‏ ‏۱۰.۵ ‏MiB‏ می‌نویسد. چون ‏sc‏ = ۱ است، ‏su‏ برابر ۴ ‏MiB‏ می‌شود. ‏blockno‏ = ⌊۱۰.۵ / ۴⌋ = ۲، پس ‏objectno‏ = ۲ و ‏offset‏ درون شیء ۱۰.۵ − ۸ = ۲.۵ ‏MiB‏ (۰x۲۸۰۰۰۰) است. کل نوشتن یک ‏extent‏ روی شیء ‏rbd_data.<id>.0000000000000002‏ می‌شود.

حالا همان ‏image‏ را با «‏striping‏ پیشرفته» بسازید (‏--stripe-unit 65536 --stripe-count 4‏، که ویژگی ‏STRIPINGV2‏ را لازم دارد). ‏su‏ = ۶۴ ‏KiB‏ و ‏spo‏ = ۴ ‏MiB‏ / ۶۴ ‏KiB‏ = ۶۴. ‏offset‏ ‏۱۰.۵ ‏MiB‏ یعنی ‏blockno‏ = ۱۶۸، ‏stripeno‏ = ۴۲، ‏stripepos‏ = ۰، ‏objectset‏ = ⌊۴۲ / ۶۴⌋ = ۰ و ‏objectno‏ = ۰. نوشتن ۱ ‏MiB‏ به ۱۶ قطعه‌ی ۶۴ ‏KiB‏ می‌شکند (‏blockno 168‏ تا ۱۸۳). قطعه‌ها به نوبت به شیءهای ۰، ۱، ۲ و ۳ می‌روند: قطعه‌ی ۰ به شیء ۰، قطعه‌ی ۱ به شیء ۱ و همین‌طور تا آخر. هر شیء چهار قطعه‌ی پشت‌سرهم در بازه‌ی ۰‏x2a0000‏ تا ۰‏x2e0000‏ می‌گیرد (‏stripeno 42‏ تا ۴۵ ضرب در ۶۴ ‏KiB‏). نتیجه‌ی عملی این است که یک نوشتن ترتیبی بزرگ همزمان چهار ‏PG‏ و احتمالاً چهار ‏OSD‏ اصلی را درگیر می‌کند، اما هر درخواست ۴ ‏KiB‏ تصادفی همچنان به یک شیء می‌رود. ‏QEMU‏ و ‏Nova‏ هیچ‌کدام از این را نمی‌بینند. ‏striping‏ فقط هنگام ساخت ‏image‏ تعیین می‌شود و بعداً قابل تغییر نیست.

شکل ۳-۷: نگاشت یک نوشتن ۱ مگابایتی از offset ۱۰.۵ MiB به شیءهای RADOS در دو چیدمان. بالا: stripe_count = ۱ (پیش‌فرض)، که کل نوشتن در شیء شماره‌ی ۲ و از offset ۰ x280000 قرار می‌گیرد. پایین: stripe_unit = ۶۴ K و stripe_count = ۴، که همان نوشتن به ۱۶ قطعه‌ی ۶۴ K تقسیم و به نوبت میان چهار شیء پخش می‌شود. شماره‌ی قطعه‌های بافر روی هر شیء نوشته شده است. همه‌ی قطعه‌ها در «مجموعه‌ی شیء» صفر هستند، چون ۴۲ تا ۴۵ کمتر از ۶۴ stripe در هر شیء است
شکل ۳-۷: نگاشت یک نوشتن ۱ مگابایتی از ‏offset‏ ‏۱۰.۵ ‏MiB‏ به شیءهای ‏RADOS‏ در دو چیدمان. بالا: ‏stripe_count‏ = ۱ (پیش‌فرض)، که کل نوشتن در شیء شماره‌ی ۲ و از ‏offset‏ ‏۰‏x280000‏ قرار می‌گیرد. پایین: ‏stripe_unit‏ = ۶۴‏K‏ و ‏stripe_count‏ = ۴، که همان نوشتن به ۱۶ قطعه‌ی ۶۴‏K‏ تقسیم و به نوبت میان چهار شیء پخش می‌شود. شماره‌ی قطعه‌های بافر روی هر شیء نوشته شده است. همه‌ی قطعه‌ها در «مجموعه‌ی شیء» صفر هستند، چون ۴۲ تا ۴۵ کمتر از ۶۴ ‏stripe‏ در هر شیء است

نمودار بالا یک نکته‌ی عملی را روشن می‌کند. وقتی پشتیبان‌گیری یا مهاجرت یک ‏volume‏ با ‏rbd export‏ انجام می‌شود، کارایی آن به تعداد شیءهایی بستگی دارد که هم‌زمان خوانده می‌شوند. با ‏stripe‏ پیش‌فرض، ‏librbd‏ برای موازی‌سازی به ‏rbd_concurrent_management_ops‏ و چند درخواست هم‌زمان تکیه می‌کند. با ‏striping‏ پیشرفته، حتی یک درخواست بزرگ واحد هم چند شیء را با هم درگیر می‌کند. برای ‏VM‏های ‏OpenStack‏ معمولاً همان پیش‌فرض بهتر است، چون بار مهمان‌ها کوچک و تصادفی است.

۳-۱۰-۲ ‏object map‏: دو بیت برای هر شیء#

∑ الگوریتم: اندازه و وضعیت‌های ‏object map‏

منبع در کد: src/include/rbd/object_map_types.h؛ src/librbd/ObjectMap.cc :: ObjectMap::is_compatible؛ src/cls/rbd/cls_rbd_types.h :: MAX_OBJECT_MAP_OBJECT_COUNT؛ src/librbd/io/ObjectRequest.cc :: AbstractObjectWriteRequest؛ src/librbd/object_map/SnapshotCreateRequest.cc :: update_object_map

پیچیدگی: حافظه ۲·‏N‏ بیت برای ‏N‏ شیء؛ هر به‌روزرسانی ‏O‏(۱) به‌علاوه‌ی یک فراخوانی ‏cls‏ روی شیء ‏rbd_object_map‏

text
pseudocode
N = ceil(image_size / object_size)
if N > 256000000: object map ناسازگار ⇒ ویژگی object-map/fast-diff قابل استفاده نیست
map = BitVector<2>[N]  (00 NONEXISTENT، 01 EXISTS، 10 PENDING، 11 EXISTS_CLEAN)
write(objectno):  اگر map[o] ≠ EXISTS: ابتدا map[o] = EXISTS (پیش از نوشتن داده)
discard/remove(o): map[o] = PENDING ; حذف شیء ; سپس map[o] = NONEXISTENT
snap create:      کپی map در rbd_object_map.<id>.<snapid> ؛ در HEAD همه‌ی EXISTS ⇒ EXISTS_CLEAN
read(o):          اگر map[o] == NONEXISTENT ⇒ بدون رفتن به OSD (یا مستقیم به parent)

مثال عددی. ‏volume‏ یک ترابایتی با شیء ۴ ‏MiB‏ برابر ‏240/222=2621442^{40} / 2^{22} = 262144‏ شیء است. ‏object map‏ آن ۲۶۲۱۴۴ × ۲ / ۸ = ۶۵۵۳۶ بایت، یعنی ۶۴ ‏KiB‏ می‌شود و به‌راحتی در حافظه‌ی هر کلاینت جا می‌گیرد. سقف ۲۵۶۰۰۰۰۰۰ شیء با شیء ۴ ‏MiB‏ یعنی ‏image‏ حداکثر ۲۵۶۰۰۰۰۰۰ × ۴ ‏MiB‏ ≈ ۹۷۶.۶ ‏TiB‏. همین سقف با شیء ۶۴ ‏KiB‏ به حدود ۱۵.۳ ‏TiB‏ کاهش می‌یابد. ‏map‏ در این حالت ۲۵۶۰۰۰۰۰۰ × ۲ / ۸ = ۶۴۰۰۰۰۰۰ بایت (حدود ۶۱ ‏MiB‏) است. اگر کسی برای پایگاه داده‌ای ‏image‏ با ‏order = 16‏ بسازد، باید بداند که بالاتر از این اندازه ‏object map‏ و در نتیجه ‏fast-diff‏ و ‏rbd du‏ سریع را از دست می‌دهد.

شکل ۳-۸: اندازه‌ی object map برحسب اندازه‌ی image ، برای سه اندازه‌ی شیء، روی محورهای لگاریتمی. هر شیء دو بیت می‌گیرد، پس اندازه‌ی map با اندازه‌ی image نسبت خطی دارد. خط‌چین‌های عمودی بزرگ‌ترین image سازگار با object map برای هر اندازه‌ی شیء هستند، یعنی جایی که تعداد شیءها به ۲۵۶ میلیون می‌رسد. نقطه‌ی سیاه volume یک ترابایتی معمولی با شیء ۴ MiB است که فقط ۶۴ KiB map دارد
شکل ۳-۸: اندازه‌ی ‏object map‏ برحسب اندازه‌ی ‏image‏، برای سه اندازه‌ی شیء، روی محورهای لگاریتمی. هر شیء دو بیت می‌گیرد، پس اندازه‌ی ‏map‏ با اندازه‌ی ‏image‏ نسبت خطی دارد. خط‌چین‌های عمودی بزرگ‌ترین ‏image‏ سازگار با ‏object map‏ برای هر اندازه‌ی شیء هستند، یعنی جایی که تعداد شیءها به ۲۵۶ میلیون می‌رسد. نقطه‌ی سیاه ‏volume‏ یک ترابایتی معمولی با شیء ۴ ‏MiB‏ است که فقط ۶۴ ‏KiB map‏ دارد
شکل ۳-۹: ماشین حالت یک خانه‌ی object map . خانه‌ها در آغاز NONEXISTENT هستند. پیش از نخستین نوشتن به EXISTS می‌روند، چون librbd map را قبل از داده به‌روز می‌کند تا پس از خرابی کلاینت، شیءِ موجودی «ناموجود» علامت نخورد. هنگام ساخت snapshot همه‌ی خانه‌های EXISTS در map فعلی ( HEAD ) به EXISTS_CLEAN تبدیل می‌شوند. هر نوشتن بعدی خانه را دوباره EXISTS می‌کند. حذف دو مرحله دارد: ابتدا PENDING ، سپس پس از حذف موفق NONEXISTENT
شکل ۳-۹: ماشین حالت یک خانه‌ی ‏object map‏. خانه‌ها در آغاز ‏NONEXISTENT‏ هستند. پیش از نخستین نوشتن به ‏EXISTS‏ می‌روند، چون ‏librbd map‏ را قبل از داده به‌روز می‌کند تا پس از خرابی کلاینت، شیءِ موجودی «ناموجود» علامت نخورد. هنگام ساخت ‏snapshot‏ همه‌ی خانه‌های ‏EXISTS‏ در ‏map‏ فعلی (‏HEAD‏) به ‏EXISTS_CLEAN‏ تبدیل می‌شوند. هر نوشتن بعدی خانه را دوباره ‏EXISTS‏ می‌کند. حذف دو مرحله دارد: ابتدا ‏PENDING‏، سپس پس از حذف موفق ‏NONEXISTENT‏

ترتیب «اول ‏map‏، بعد داده» هزینه دارد: نخستین نوشتن روی هر شیء یک رفت‌وبرگشت اضافه به ‏OSD‏ نگه‌دارنده‌ی ‏rbd_object_map.<image id>‏ اضافه می‌کند. این هزینه فقط یک بار برای هر شیء پرداخت می‌شود و پس از آن خانه ‏EXISTS‏ است. پس از هر ‏snapshot‏، خانه‌ها ‏CLEAN‏ می‌شوند و نخستین نوشتن روی هر شیء دوباره یک به‌روزرسانی ‏map‏ دارد. به همین دلیل، ‏snapshot‏های بسیار مکرر (مثلاً هر دقیقه برای ‏mirroring‏ مبتنی بر ‏snapshot‏) روی ‏volume‏های پرنوشتن کمی تأخیر نوشتن را بالا می‌برند. ‏object map‏ فقط با قفل انحصاری (‏exclusive-lock‏) کار می‌کند، چون فقط یک کلاینت باید ‏map‏ را به‌روز کند.

۳-۱۰-۳ تفاوت میان ‏snapshot‏ها (‏fast-diff‏)#

∑ الگوریتم: محاسبه‌ی ‏diff‏ از روی ‏object map‏ها

منبع در کد: src/librbd/object_map/DiffRequest.cc :: DiffRequest::handle_load_object_map

پیچیدگی: ‏O‏(N × ‏S‏) برای ‏N‏ شیء و ‏S object map‏ میان دو ‏snapshot‏؛ بدون خواندن داده

text
pseudocode
# برای diff-iterate از snap_start تا snap_end
نخستین map (snap_start):  NONEXISTENT ⇒ HOLE ، وگرنه DATA (PENDING ⇒ DATA_UPDATED)
برای هر map بعدی m و هر شیء o:
    HOLE:          اگر m[o] ≠ NONEXISTENT و این map آخر است ⇒ DATA_UPDATED (در mapهای میانی HOLE می‌ماند)
    DATA:          NONEXISTENT ⇒ HOLE_UPDATED ؛ EXISTS یا PENDING ⇒ DATA_UPDATED ؛ EXISTS_CLEAN ⇒ بدون تغییر
    HOLE_UPDATED:  اگر m[o] ≠ NONEXISTENT ⇒ DATA_UPDATED
    DATA_UPDATED:  NONEXISTENT ⇒ HOLE_UPDATED
خروجی: شیءهای *_UPDATED تغییرکرده‌اند ؛ HOLE_UPDATED یعنی «صفر/حذف‌شده گزارش کن»

مثال عددی. شش شیء ‏o0‏ تا ‏o5‏ و سه ‏map‏ داریم: ‏snap1‏، ‏snap2‏ و ‏HEAD. cinder-backup‏ می‌خواهد پشتیبان افزایشی از ‏snap1‏ تا وضعیت فعلی بسازد (همان کاری که ‏rbd export-diff --from-snap snap1‏ می‌کند).

  • ‏o0‏: در ‏snap1 EXISTS‏، در ‏snap2‏ و ‏HEAD EXISTS_CLEAN‏. از ‏DATA‏ شروع می‌شود و ‏DATA‏ می‌ماند. تغییر نکرده و منتقل نمی‌شود.
  • ‏o1‏: پیش از ‏snap2‏ نوشته شده است (در ‏snap2 EXISTS‏). در ‏snap2‏ به ‏DATA_UPDATED‏ می‌رود و همان می‌ماند. منتقل می‌شود.
  • ‏o2‏: در ‏snap1‏ وجود ندارد (‏HOLE‏). در ‏snap2‏ ساخته شده، اما چون ‏snap2‏ میانی است ‏HOLE‏ می‌ماند. در ‏HEAD‏ (map آخر) به ‏DATA_UPDATED‏ می‌رود. منتقل می‌شود.
  • ‏o3‏: در ‏snap2‏ حذف شده است و به ‏HOLE_UPDATED‏ می‌رود. به‌صورت «ناحیه‌ی صفر» گزارش می‌شود.
  • ‏o4‏: هیچ‌جا وجود ندارد و ‏HOLE‏ است. هیچ داده‌ای منتقل نمی‌شود.
  • ‏o5‏: پس از ‏snap2‏ دوباره نوشته شده است (در ‏HEAD EXISTS‏). از ‏DATA‏ به ‏DATA_UPDATED‏ می‌رود. منتقل می‌شود.

از شش شیء، سه شیء داده و یک شیء علامت صفر منتقل می‌کنند. هیچ شیئی برای مقایسه خوانده نشد: کل ‏diff‏ فقط از سه ‏map‏ کوچک به دست آمد. برای ‏volume‏ یک ترابایتی، این یعنی پیمایش ۳ × ۶۴ ‏KiB‏ به‌جای خواندن یک ترابایت.

شکل ۳-۱۰: جدول حالت محاسبه‌ی diff برای شش شیء نمونه از snap1 تا HEAD . هر خانه وضعیت شیء در آن object map را نشان می‌دهد (نارنجی EXISTS ، آبی EXISTS_CLEAN ، خاکستری NONEXISTENT ) و زیر آن حالت diff پس از پردازش همان map آمده است. ستون آخر نتیجه است: قرمز یعنی داده‌ی تغییرکرده که باید منتقل شود، بنفش یعنی ناحیه‌ای که باید صفر گزارش شود، سبز و سفید یعنی بدون تغییر. o2 نشان می‌دهد چرا HOLE در snapshot میانی تغییر نمی‌کند: نتیجه فقط به وضعیت در map آخر بستگی دارد
شکل ۳-۱۰: جدول حالت محاسبه‌ی ‏diff‏ برای شش شیء نمونه از ‏snap1‏ تا ‏HEAD‏. هر خانه وضعیت شیء در آن ‏object map‏ را نشان می‌دهد (نارنجی ‏EXISTS‏، آبی ‏EXISTS_CLEAN‏، خاکستری ‏NONEXISTENT‏) و زیر آن حالت ‏diff‏ پس از پردازش همان ‏map‏ آمده است. ستون آخر نتیجه است: قرمز یعنی داده‌ی تغییرکرده که باید منتقل شود، بنفش یعنی ناحیه‌ای که باید صفر گزارش شود، سبز و سفید یعنی بدون تغییر. ‏o2‏ نشان می‌دهد چرا ‏HOLE‏ در ‏snapshot‏ میانی تغییر نمی‌کند: نتیجه فقط به وضعیت در ‏map‏ آخر بستگی دارد

این الگوریتم به یک فرض مهم تکیه دارد: ‏object map‏ باید معتبر باشد. اگر کلاینتی بدون آزاد کردن قفل از کار بیفتد، ممکن است ‏librbd‏ پرچم «‏object map invalid‏» بگذارد. در آن حالت ‏diff‏ به پیمایش کامل (‏list-snaps‏ روی هر شیء) برمی‌گردد که بسیار کندتر است. ‏rbd object-map check‏ و ‏rbd object-map rebuild‏ برای بازسازی هستند. در عمل اگر پشتیبان‌های افزایشی ‏cinder-backup‏ ناگهان کند شدند، اولین چیزی که باید بررسی شود همین پرچم در ‏rbd info‏ است.

۳-۱۰-۴ ‏QoS‏ سمت کلاینت: سطل توکن ‏librbd‏#

∑ الگوریتم: سطل توکن ‏TokenBucketThrottle‏ برای ‏rbd_qos_*‏

منبع در کد: src/common/Throttle.cc :: TokenBucketThrottle::set_limit، tokens_this_tick، add_tokens، Bucket::put، Bucket::get؛ src/librbd/io/QosImageDispatch.cc

پیچیدگی: ‏O‏(۱) برای هر ‏tick‏؛ ‏O‏(b) برای آزاد کردن ‏b‏ درخواست منتظر

text
pseudocode
set_limit(avg, burst, burst_seconds):
    tick_ms = max(1000 / avg, rbd_qos_schedule_tick_min = 50)
    ticks_per_s = 1000 / tick_ms
    max = (burst == 0) ? avg : burst ; capacity = max × max(burst_seconds, 1) ; remain = capacity
هر tick:
    tokens = ⌊i × avg / ticks_per_s⌋ − ⌊(i−1) × avg / ticks_per_s⌋     # i = 1..ticks_per_s
    remain = min(capacity, remain + tokens)
    inc = tokens × (max / avg)  اگر max > avg ، وگرنه tokens         # در burst سریع‌تر پر می‌شود
    available = min(available + inc, min(remain, max))
    درخواست‌های منتظر را به ترتیب ورود تا جایی که available کافی است آزاد کن
get(c): از available و remain کم کن ؛ اگر کافی نبود، درخواست در صف می‌ماند

مثال عددی. محدودیت ‏rbd_qos_iops_limit = 30‏ را در نظر بگیرید. ‏tick‏ = ‏max‏(۱۰۰۰ / ۳۰ = ۳۳، ۵۰) = ۵۰ ‏ms‏، پس ۲۰ ‏tick‏ در ثانیه داریم. توکن‌های هر ‏tick‏ به شکل ⌊۱.۵ ‏i‏⌋ − ⌊۱.۵ (‏i‏−۱)⌋ هستند، یعنی ۱، ۲، ۱، ۲ و همین‌طور تا آخر. جمع آن‌ها دقیقاً ۳۰ است. برای ‏avg‏ = ۱۰۰، هر ‏tick‏ دقیقاً ۵ توکن می‌گیرد. برای ‏avg‏ = ۷، ‏tick‏ برابر ⌊۱۰۰۰ / ۷⌋ = ۱۴۲ ‏ms‏ است و ⌊۱۰۰۰ / ۱۴۲⌋ = ۷ ‏tick‏ در ثانیه داریم، هر کدام یک توکن.

اکنون ‏burst‏ را ببینیم: ‏avg‏ = ۱۰۰، ‏burst‏ = ۳۰۰ و ‏burst_seconds‏ = ۲. پس ‏max‏ = ۳۰۰ و ‏capacity‏ = ۶۰۰. ضریب ‏burst‏ ‏۳۰۰ / ۱۰۰ = ۳ است و ‏available‏ در هر ‏tick‏ ‏۱۵ واحد بالا می‌رود، در حالی که ‏remain‏ فقط ۵ واحد پر می‌شود. یک کلاینت اشباع (که همیشه درخواست منتظر دارد) در شبیه‌سازی ما این تعداد ‏IO‏ را در ثانیه‌های پیاپی می‌گیرد: ۳۰۰، ۳۰۰، ۲۹۵، ۱۰۰، ۱۰۰. ‏remain‏ در هر ‏tick‏ خالص ۱۰ واحد کم می‌شود و بعد از حدود ۶۰ ‏tick‏ (سه ثانیه) تمام می‌شود. سپس نرخ به میانگین برمی‌گردد. برای کلاینتی که سه ثانیه بی‌کار بوده، ‏available‏ تا سقف ۳۰۰ پر شده است و ثانیه‌ی نخست فعالیت ۵۸۵ ‏IO‏ می‌گیرد: ۳۰۰ از ذخیره و ۱۵ در هر یک از ۱۹ ‏tick‏ بعدی. توجه کنید که ‏burst_seconds‏ = ۲ به معنای «دقیقاً دو ثانیه ‏burst‏» نیست. چون سطل در حین ‏burst‏ هم پر می‌شود، طول ‏burst‏ کمی بیشتر است.

شکل ۳-۱۱: سمت چپ: تعداد IO پذیرفته‌شده در هر ثانیه برای یک کلاینت اشباع، در سه پیکربندی. بدون burst نرخ ثابت ۱۰۰ است. با burst ۳۰۰ و burst_seconds ۲، نرخ حدود سه ثانیه ۳۰۰ می‌ماند و بعد به ۱۰۰ برمی‌گردد. اگر کلاینت پیش از آن سه ثانیه بی‌کار بوده باشد، ثانیه‌ی نخست ۵۸۵ IO می‌گیرد. سمت راست: توکن‌های افزوده‌شده در هر tick برای سه مقدار avg . پخش صحیح avg میان tick ها، جمع هر ثانیه را دقیق نگه می‌دارد. این عددها از بازپیاده‌سازی پایتونی TokenBucketThrottle با فرض تقاضای پیوسته به دست آمده‌اند، نه از اندازه‌گیری روی کلاستر
شکل ۳-۱۱: سمت چپ: تعداد ‏IO‏ پذیرفته‌شده در هر ثانیه برای یک کلاینت اشباع، در سه پیکربندی. بدون ‏burst‏ نرخ ثابت ۱۰۰ است. با ‏burst‏ ‏۳۰۰ و ‏burst_seconds‏ ‏۲، نرخ حدود سه ثانیه ۳۰۰ می‌ماند و بعد به ۱۰۰ برمی‌گردد. اگر کلاینت پیش از آن سه ثانیه بی‌کار بوده باشد، ثانیه‌ی نخست ۵۸۵ ‏IO‏ می‌گیرد. سمت راست: توکن‌های افزوده‌شده در هر ‏tick‏ برای سه مقدار ‏avg‏. پخش صحیح ‏avg‏ میان ‏tick‏ها، جمع هر ثانیه را دقیق نگه می‌دارد. این عددها از بازپیاده‌سازی پایتونی ‏TokenBucketThrottle‏ با فرض تقاضای پیوسته به دست آمده‌اند، نه از اندازه‌گیری روی کلاستر

در ‏OpenStack‏ دو راه برای محدود کردن ‏IO‏ یک ‏volume‏ وجود دارد. ‏QoS‏ «‏front-end‏» در ‏Cinder‏ محدودیت را به ‏libvirt‏ می‌دهد (‏<iotune>‏) و ‏QEMU‏ آن را پیش از ‏librbd‏ اعمال می‌کند. ‏QoS‏ سمت ‏librbd‏ با گزینه‌های ‏rbd_qos_*‏ روی ‏image‏ یا ‏pool‏ تنظیم می‌شود (‏rbd config image set vol rbd_qos_iops_limit 300‏) و درون فرایند ‏QEMU‏ و در لایه‌ی ‏QosImageDispatch‏ اجرا می‌شود. اولی را ‏Cinder‏ مدیریت می‌کند و با ‏migrate‏ کردن ‏VM‏ همراه آن می‌رود. دومی در فراداده‌ی خود ‏image‏ ذخیره می‌شود و برای هر کلاینتی که ‏image‏ را باز کند (حتی ‏rbd bench‏) اعمال می‌شود. هر دو کلاینت‌محور هستند و از ‏OSD‏ها محافظت سراسری نمی‌کنند. برای آن کار، ‏mClock‏ در ‏OSD‏ (فصل ۲۱) لازم است.

۳-۱۱ آزمایش سرتاسری#

۳-۱۱-۱ بوت یک ماشین از ‏volume‏#

bashReef+
openstack volume create --image ubuntu-24.04 --size 20 --type fast vol-web01
openstack volume show vol-web01 -c status
rbd -p volumes ls -l --id cinder
rbd info volumes/volume-<uuid> --id cinder | grep parent
openstack server create --flavor m1.small --network private \
  --volume vol-web01 web01
openstack server show web01 -c status

اگر ‏copy-on-write‏ درست کار کند، ساختن ‏volume‏ بیست‌گیگابایتی چند ثانیه طول می‌کشد و خروجی ‏rbd info‏ یک ‏parent‏ در ‏pool‏ ‏images‏ نشان می‌دهد.

۳-۱۱-۲ اتصال ‏volume‏ اضافه#

bash
openstack volume create --size 50 data01
openstack server add volume web01 data01
# داخل VM
lsblk

۳-۱۱-۳ بررسی از سمت ‏compute‏#

bash
sudo virsh list
sudo virsh dumpxml instance-0000000a | grep -A6 "protocol='rbd'"

در خروجی باید یک ‏<source protocol='rbd' name='volumes/volume-...'>‏ با فهرست ‏MON‏ها و یک ‏<auth username='cinder'>‏ با ارجاع به ‏secret‏ ببینید.

✚ عیب‌یابی

چند مشکل رایج و نشانه‌هایشان:

ساخت ‏volume‏ کند است و فضای ‏compute‏ پر می‌شود: ‏image‏ در قالب ‏qcow2‏ است یا ‏show_image_direct_url‏ خاموش است؛ ‏CoW‏ کار نمی‌کند و ‏Cinder image‏ را دانلود می‌کند.

‏VM‏ بوت نمی‌شود و در لاگ ‏libvirt‏ خطای ‏error connecting: Permission denied‏ هست: ‏UUID secret‏ با ‏rbd_secret_uuid‏ یکی نیست، یا کلید داخل ‏secret‏ قدیمی است.

‏attach‏ کار می‌کند اما ‏live migration‏ شکست می‌خورد: ‏secret‏ روی ‏compute‏ مقصد تعریف نشده یا ‏UUID‏ متفاوت است.

حذف ‏volume‏ با خطای «‏has dependent snapshots‏» یا «‏image has children‏» متوقف می‌شود: ‏volume‏ از ‏snapshot‏ یا ‏image‏ والد ‏clone‏ شده و زنجیره باید ابتدا ‏flatten‏ یا پاک شود. ‏rbd children‏ را ببینید.

ظرفیت گزارش‌شده در ‏Cinder‏ با ‏ceph df‏ نمی‌خواند: ‏Cinder‏ ظرفیت را از ‏MAX AVAIL‏ و سهم ‏pool‏ محاسبه می‌کند و با ‏thin provisioning‏ و نسبت ‏max_over_subscription_ratio‏ تنظیم می‌شود.

۳-۱۲ فراتر از بلوک: ‏Manila‏ و ‏API‏ شیء#

۳-۱۲-۱ ‏Manila‏ با ‏CephFS‏#

‏Manila‏ سرویس اشتراک فایل ‏OpenStack‏ است و دو درایور برای ‏CephFS‏ دارد:

  • ‏CephFS‏ بومی (‏native‏): مهمان‌ها با کلاینت ‏CephFS‏ (kernel یا ‏FUSE‏) مستقیم به کلاستر وصل می‌شوند. سریع‌ترین مسیر، اما شبکه‌ی ‏Ceph‏ باید به ‏VM‏ها در دسترس باشد و مهمان‌ها باید قابل‌اعتماد باشند.
  • ‏CephFS‏ از طریق ‏NFS‏: ‏Manila‏ از طریق ماژول ‏NFS‏ در ‏Ceph‏ (NFS-Ganesha مستقرشده با ‏cephadm‏) اشتراک‌ها را صادر می‌کند. مهمان فقط ‏NFS‏ می‌بیند و جداسازی شبکه حفظ می‌شود. در نسخه‌های جدید ‏Manila‏ با ماژول ‏nfs‏ در ‏MGR‏ کار می‌کند.
bashReef+
ceph fs volume create manila-fs
ceph auth get-or-create client.manila mon 'allow r' mgr 'allow rw'

مجوز ‏mgr 'allow rw'‏ برای ‏Manila‏ کافی است، چون ‏Manila‏ از طریق رابط ‏volumes‏ در ‏MGR‏ (‏ceph fs subvolume ...‏) ‏subvolume‏ها و کاربرانشان را می‌سازد (فصل ۶).

۳-۱۲-۲ ‏Swift‏ و ‏S3‏#

بسیاری از ابرهای ‏OpenStack‏ به‌جای ‏Swift‏ از ‏RGW‏ استفاده می‌کنند و آن را در ‏Keystone‏ به‌عنوان ‏endpoint‏ سرویس ‏object-store‏ ثبت می‌کنند. ‏RGW‏ هم ‏API‏ سازگار با ‏Swift‏ و هم ‏S3‏ را ارائه می‌دهد و می‌تواند توکن‌های ‏Keystone‏ را بپذیرد. جزئیات این یکپارچه‌سازی در فصل ۴ آمده است.

۳-۱۳ نکته‌های ‏production‏#

۳-۱۳-۱ کارایی#

  • شبکه‌ی ذخیره‌سازی جداگانه برای ‏compute‏ها: ترافیک ‏librbd‏ را از ترافیک ‏tenant‏ جدا کنید. پهنای باند هر ‏compute‏ باید با تعداد ‏VM‏ها و نیازشان متناسب باشد.
  • ‏librbd cache‏ و ‏readahead‏: با ‏disk_cachemodes=network=writeback‏ ‏cache‏ فعال می‌شود. برای بارهای ترتیبی، ‏readahead‏ کلاینت (‏rbd_readahead_*‏) هم اثر دارد.
  • ‏QoS‏: ‏Cinder‏ می‌تواند ‏QoS front-end‏ (اعمال در ‏QEMU‏) تعریف کند: ‏openstack volume qos create --consumer front-end --property total_iops_sec=3000 iops3k‏. این روش با هر ‏backend‏ی کار می‌کند و از ‏librbd QoS‏ برای ابر چندمستاجری قابل‌کنترل‌تر است.
  • توزیع ‏pool‏ روی ‏device class‏: ‏volume‏های پایگاه داده روی ‏NVMe‏، آرشیو روی ‏HDD‏.

۳-۱۳-۲ عملیات#

  • ارتقای ‏Ceph‏ و ‏OpenStack‏ را جدا انجام دهید. کلاینت‌های ‏librbd‏ روی ‏compute‏ها را به نسخه‌ی سازگار برسانید. کلاینت‌های جدیدتر با کلاستر قدیمی‌تر و برعکس در بازه‌ی چند نسخه کار می‌کنند، اما پیش از روشن کردن ویژگی‌های جدید (مثل ‏require-min-compat-client‏) ‏ceph features‏ را بررسی کنید.
  • پاک‌سازی ‏snapshot‏های یتیم: حذف ناقص در ‏OpenStack‏ گاهی ‏image‏ها یا ‏snapshot‏هایی در ‏Ceph‏ باقی می‌گذارد. دوره‌ای فهرست ‏image‏های ‏pool‏ را با فهرست ‏volume‏ها و ‏instance‏های ‏OpenStack‏ مقایسه کنید.
  • ‏rbd-mirror‏ برای ‏DR‏: ‏volume‏های حساس را با ‏mirroring‏ حالت ‏snapshot‏ به سایت دوم بفرستید. ‏Cinder‏ از نسخه‌های ‏Queens‏ به بعد از ‏replication‏ در درایور ‏RBD‏ (‏replication_device‏) و ‏failover‏ در سطح ‏backend‏ پشتیبانی می‌کند.
⚠ هشدار

در بسیاری از ابرهای واقعی، دیسک ‏compute‏ که اشتباهاً ‏images_type=rbd‏ نداشته، باعث شده بخشی از ‏VM‏ها روی دیسک محلی باشند. وقتی آن ‏compute‏ خراب می‌شود، این ‏VM‏ها با داده‌شان از دست می‌روند و ‏evacuate‏ کار نمی‌کند. پیکربندی همه‌ی ‏compute‏ها را با ابزار مدیریت پیکربندی یکسان نگه دارید و با ‏openstack server show‏ (فیلد ‏OS-EXT-SRV-ATTR‏) و بررسی ‏XML‏ دامنه، صحتش را آزمون کنید.

۳-۱۳-۳ امنیت#

  • هر سرویس کاربر ‏Ceph‏ خودش را با کمترین مجوز دارد؛ ‏client.admin‏ هرگز روی گره‌های ‏OpenStack‏ قرار نمی‌گیرد.
  • اگر از رمزنگاری ‏volume‏ در ‏Cinder‏ (با ‏Barbican‏ و ‏LUKS‏) استفاده می‌کنید، رمزنگاری در سمت ‏compute‏ (dm-crypt یا ‏LUKS‏ در ‏librbd‏ در نسخه‌های جدید) انجام می‌شود و ‏Ceph‏ داده‌ی رمزشده می‌بیند.
  • ‏msgr2‏ با حالت ‏secure‏ (‏ms_client_mode = secure‏) ترافیک بین ‏compute‏ و ‏Ceph‏ را رمزنگاری می‌کند؛ هزینه‌ی ‏CPU‏ دارد، پس پیش از فعال کردن آن را بسنجید.

۳-۱۴ تکمیلی و به‌روز#

  • ‏Ceph CSI‏ و ‏Kubernetes‏: امروز بسیاری از ابرهای مبتنی بر ‏OpenStack‏، ‏Kubernetes‏ را هم روی همان ‏Ceph‏ سرویس می‌دهند. ‏Ceph CSI‏ (درایور ‏RBD‏ و ‏CephFS‏) همان الگوی کاربر اختصاصی و ‏pool‏ جداگانه را دنبال می‌کند.
  • ‏Glance‏ با چند ‏store‏: ‏Glance‏ از چند ‏backend‏ هم‌زمان پشتیبانی می‌کند (‏enabled_backends‏)، مثلاً یک کلاستر ‏Ceph‏ در هر منطقه‌ی دسترس‌پذیری، و می‌تواند ‏image‏ را بین ‏store‏ها کپی کند (‏copy-image‏). این قابلیت برای ابرهای چندسایتی که هر سایت ‏Ceph‏ مستقل دارد، اهمیت دارد.
  • دروازه‌ی ‏NVMe-oF‏: برای محیط‌های مختلط که کنار ‏OpenStack‏، ‏VMware‏ هم دارند، دروازه‌ی ‏NVMe-oF‏ راه ارائه‌ی ‏RBD‏ به ‏ESXi‏ است.
  • ابزار استقرار: ‏Packstack‏ و ‏TripleO‏ کنار گذاشته شده‌اند؛ ‏Kolla-Ansible‏ و ‏OpenStack-Ansible‏ رایج‌ترین گزینه‌های متن‌باز امروزند.

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

۳-۱۵-۱ خلاصه‌ی فصل#

نکته‌های کلیدی
  • ‏Ceph‏ همه‌ی نیازهای ذخیره‌سازی ‏OpenStack‏ را از یک کلاستر تأمین می‌کند: ‏Glance‏، ‏Cinder‏، ‏Backup‏، ‏Nova‏، ‏Manila‏ و ‏Swift/S3‏.
  • هر سرویس ‏pool‏ و کاربر ‏Ceph‏ خودش را دارد؛ ‏client.cinder‏ روی ‏images‏ فقط خواندنی است.
  • ‏copy-on-write‏ سرتاسری نیازمند ‏image‏ با قالب ‏raw‏ و ‏show_image_direct_url = True‏ است.
  • ‏QEMU‏ کلید ‏Ceph‏ را از ‏secret‏ در ‏libvirt‏ می‌گیرد و ‏UUID‏ آن باید روی همه‌ی ‏compute‏ها یکسان و برابر ‏rbd_secret_uuid‏ باشد.
  • ‏images_type = rbd‏ دیسک‌های موقت را هم به ‏Ceph‏ می‌برد و ‏live migration‏ و ‏evacuate‏ را ساده می‌کند.
  • ‏Ceph‏ را خارج از ‏OpenStack‏ و با ‏cephadm‏ مدیریت کنید؛ ‏OpenStack‏ فقط کلاینت باشد.

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

  • آپلود ‏image‏ها با قالب ‏qcow2‏ و از دست دادن ‏CoW‏.
  • ‏UUID‏ متفاوت ‏secret‏ روی ‏compute‏ها و شکست ‏live migration‏.
  • استفاده از یک کاربر ‏Ceph‏ با مجوز گسترده برای همه‌ی سرویس‌ها.
  • نگه داشتن ‏backup‏ روی همان کلاستر.
  • ‏compute‏هایی که بی‌سروصدا هنوز دیسک محلی دارند.

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

  1. ۱. چهار ‏pool‏ و سه کاربر این فصل را بسازید و با ‏ceph auth get‏ مجوزها را بررسی کنید. نشان دهید ‏client.cinder‏ نمی‌تواند در ‏pool‏ ‏images‏ بنویسد (‏rbd --id cinder create images/test --size 1G‏).
  2. ۲. با ‏DevStack‏ یا ‏Kolla-Ansible‏ یک ‏OpenStack‏ تک‌نودی بسازید و ‏Glance‏، ‏Cinder‏ و ‏Nova‏ را به ‏Ceph‏ وصل کنید.
  3. ۳. یک ‏image‏ را یک‌بار با قالب ‏qcow2‏ و یک‌بار ‏raw‏ آپلود کنید و زمان ساخت ‏volume‏ بیست‌گیگابایتی از هر کدام را مقایسه کنید.
  4. ۴. یک ‏volume type‏ برای ‏NVMe‏ و یکی برای ‏HDD‏ بسازید و با ‏rbd ls‏ نشان دهید ‏volume‏ها در ‏pool‏ درست ساخته می‌شوند.
  5. ۵. ‏admin socket‏ کلاینت ‏QEMU‏ را فعال کنید و با ‏perf dump‏ تأخیر خواندن و نوشتن یک ‏VM‏ را ببینید.

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

  1. ۱. سه دلیل اصلی که ‏Ceph‏ را به پشتوانه‌ی محبوب ‏OpenStack‏ تبدیل کرده کدام‌اند؟
  2. ۲. چرا ‏client.cinder‏ روی ‏pool‏ ‏images‏ فقط مجوز خواندن دارد؟
  3. ۳. چه دو شرطی برای ‏copy-on-write‏ بین ‏Glance‏ و ‏Cinder‏ لازم است؟
  4. ۴. ‏secret‏ در ‏libvirt‏ چه نقشی دارد و چرا ‏UUID‏ آن باید روی همه‌ی ‏compute‏ها یکسان باشد؟
  5. ۵. گزینه‌ی ‏images_type = rbd‏ در ‏Nova‏ چه چیزی را عوض می‌کند و چه مزیتی برای ‏evacuate‏ دارد؟
  6. ۶. ‏rbd_flatten_volume_from_snapshot‏ و ‏rbd_max_clone_depth‏ چه مصالحه‌ای را کنترل می‌کنند؟
  7. ۷. چرا ‏backup‏ روی همان کلاستر ‏Ceph‏ کافی نیست؟
  8. ۸. دو درایور ‏Manila‏ برای ‏CephFS‏ چه تفاوتی دارند؟
  9. ۹. ‏disk_cachemodes = "network=writeback"‏ چرا امن است؟
  10. ۱۰. چرا بهتر است ‏Ceph‏ را مستقل از ابزار استقرار ‏OpenStack‏ مدیریت کنیم؟

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

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

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

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