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

‏Ceph‏ از درون: ‏CRUSH‏، نقشه‌ها، احراز هویت و ‏PG‏ها

۴۹ دقیقه مطالعهویرایش این صفحه
حوصله ندارم این فصل در چند خط
  • ‏CRUSH‏ جای جدول مرکزی را می‌گیرد: سلسله‌مراتبی از ‏bucket‏ها به‌همراه قانون‌ها، ‏OSD‏های هر ‏PG‏ را محاسبه می‌کند و کلاینت خودش داده را پیدا می‌کند.
  • شیء با ‏hash‏ به یک ‏PG‏ و ‏PG‏ به ‏OSD‏هایی در دامنه‌های خرابی متفاوت نگاشت می‌شود؛ هر تغییر نقشه‌ها با یک ‏epoch‏ نسخه‌گذاری می‌شود.
  • ‏tunable‏ها رفتارهای قدیمی ‏CRUSH‏ را اصلاح می‌کنند اما نگاشت را تغییر می‌دهند، پس فعال کردنشان داده جابه‌جا می‌کند.
  • ‏cephx‏ احراز هویت دوطرفه‌ای شبیه ‏Kerberos‏ با ‏ticket‏های زمان‌دار است؛ ‏device class‏ یک ‏pool‏ را روی ‏OSD‏های انتخابی می‌نشاند.

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

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

تا اینجا ‏Ceph‏ را از بیرون دیدیم: دستورها، سرویس‌ها و رابط‌ها. این فصل به درون می‌رود. هر تصمیم مهم در طراحی و عیب‌یابی ‏Ceph‏ (چرا داده آنجاست، چرا ‏PG‏ گیر کرده، چرا یک قانون ‏CRUSH‏ جواب نمی‌دهد) به چند سازوکار پایه برمی‌گردد که در این فصل بررسی می‌کنیم:

  • داده چطور بدون جدول مرکزی جای خودش را پیدا می‌کند؟
  • نقشه‌ی ‏CRUSH‏ چه ساختاری دارد و قانون‌ها را چطور بنویسیم؟
  • ‏tunable‏ها چه هستند و چرا هشدار «‏non-optimal tunables‏» می‌آید؟
  • نقشه‌های کلاستر کدام‌اند و ‏MON‏ها چطور دسترس‌پذیر می‌مانند؟
  • ‏cephx‏ چطور کار می‌کند و ‏capability‏ها را چطور محدود کنیم؟
  • مسیر یک نوشتن از کلاینت تا دیسک چیست و ‏PG‏ها چه حالت‌هایی دارند؟
  • چطور یک ‏pool‏ را روی ‏OSD‏های خاص قرار دهیم؟

۸-۲ مقیاس‌پذیری بدون جدول مرکزی#

۸-۲-۱ مسئله‌ی جاشناسی#

بیشتر سامانه‌های ذخیره‌سازی توزیع‌شده‌ی قدیمی یک سرور فراداده یا جدول جست‌وجو داشتند که می‌گفت هر بلوک کجاست. این جدول در مقیاس بزرگ سه مشکل دارد: گلوگاه کارایی می‌شود، نقطه‌ی شکست واحد است و با هر تغییر سخت‌افزار باید به‌روز شود. ‏Ceph‏ این جدول را حذف کرده است: هر کلاینت و هر ‏OSD‏ خودش محل داده را محاسبه می‌کند. ورودی‌های این محاسبه فقط نام شیء، شناسه‌ی ‏pool‏ و نقشه‌ی کلاستر هستند که همه آن را دارند.

شکل ۸-۱: دو مرحله‌ی جاشناسی: نام شیء با hash به یک PG نگاشت می‌شود و CRUSH آن PG را به مجموعه‌ای از OSD ها نگاشت می‌کند
شکل ۸-۱: دو مرحله‌ی جاشناسی: نام شیء با ‏hash‏ به یک ‏PG‏ نگاشت می‌شود و ‏CRUSH‏ آن ‏PG‏ را به مجموعه‌ای از ‏OSD‏ها نگاشت می‌کند

۸-۲-۲ دو مرحله‌ی نگاشت#

  1. ۱. شیء به ‏PG‏: نام شیء ‏hash‏ می‌شود و باقیمانده‌ی آن بر تعداد ‏PG‏های ‏pool‏ (به‌صورت دقیق‌تر با الگوریتم ‏stable mod‏) شماره‌ی ‏PG‏ را می‌دهد. شناسه‌ی ‏PG‏ ترکیب شماره‌ی ‏pool‏ و شماره‌ی ‏PG‏ است، مثل ‏3.1f‏.
  2. ۲. ‏PG‏ به ‏OSD‏: الگوریتم ‏CRUSH‏ با ورودی شناسه‌ی ‏PG‏، نقشه‌ی ‏CRUSH‏ و قانون ‏pool‏، فهرست مرتبی از ‏OSD‏ها را برمی‌گرداند. اولین ‏OSD‏ فعال، ‏primary‏ است.
bashReef+
ceph osd map rbd rbd_data.12ab.0000000000000004
# osdmap e1234 pool 'rbd' (3) object '...' -> pg 3.8c7e1b1f (3.1f) -> up ([12,4,27], p12) acting ([12,4,27], p12)
ceph pg map 3.1f
◆ عمیق‌تر

چرا لایه‌ی میانی ‏PG‏؟ اگر ‏CRUSH‏ را مستقیماً روی هر شیء اجرا می‌کردیم، کلاستر برای هر شیء باید وضعیت تکثیر، بازیابی و ‏scrub‏ را جداگانه پیگیری می‌کرد؛ برای میلیاردها شیء غیرممکن است. ‏PG‏ها اشیا را به چند هزار گروه تقسیم می‌کنند و همه‌ی سازوکارهای حفاظت داده (‏peering‏، ‏log‏، بازیابی، ‏scrub‏) در سطح ‏PG‏ کار می‌کنند. تعداد ‏PG‏ یک مصالحه است: کم باشد، توزیع ناهموار می‌شود؛ زیاد باشد، حافظه و ‏CPU OSD‏ها و بار ‏peering‏ بالا می‌رود.

۸-۲-۳ ‏up‏ و ‏acting‏#

‏up set‏ خروجی خالص ‏CRUSH‏ است. ‏acting set‏ مجموعه‌ای است که واقعاً به درخواست‌ها پاسخ می‌دهد. این دو معمولاً یکسان‌اند؛ تفاوتشان یعنی ‏Ceph‏ موقتاً از ‏OSD‏های دیگری استفاده می‌کند (مثلاً ‏pg_temp‏ در حین ‏backfill‏ تا ‏OSD‏ جدید داده را کامل بگیرد) یا یک ‏upmap‏ صریح، نتیجه‌ی ‏CRUSH‏ را برای آن ‏PG‏ تغییر داده است.

۸-۳ سازوکار ‏CRUSH‏#

۸-۳-۱ سلسله‌مراتب#

شکل ۸-۲: یک سلسله‌مراتب CRUSH نمونه: root ، datacenter ، rack ، host و OSD ها با کلاس دستگاه
شکل ۸-۲: یک سلسله‌مراتب ‏CRUSH‏ نمونه: ‏root‏، ‏datacenter‏، ‏rack‏، ‏host‏ و ‏OSD‏ها با کلاس دستگاه

نقشه‌ی ‏CRUSH‏ از دو بخش تشکیل شده است: سلسله‌مراتب (درختی از ‏bucket‏ها که مکان فیزیکی هر ‏OSD‏ را نشان می‌دهد) و قانون‌ها (‏rules‏) که می‌گویند نسخه‌ها را چطور در این درخت پخش کنیم. انواع پیش‌فرض ‏bucket‏ از پایین به بالا عبارت‌اند از ‏osd‏، ‏host‏، ‏chassis‏، ‏rack‏، ‏row‏، ‏pdu‏، ‏pod‏، ‏room‏، ‏datacenter‏، ‏zone‏، ‏region‏ و ‏root‏. لازم نیست از همه استفاده کنید؛ فقط سطوحی را تعریف کنید که واقعاً دامنه‌ی خرابی مستقل‌اند.

bashReef+
ceph osd crush tree --show-shadow
ceph osd crush add-bucket rack1 rack
ceph osd crush move rack1 root=default
ceph osd crush move ceph-node1 rack=rack1
ceph osd crush set-device-class nvme osd.20
ceph osd crush class ls
💡 نکته

با ‏cephadm‏ می‌توانید مکان ‏CRUSH‏ میزبان را در ‏spec‏ میزبان (‏location: {rack: rack1}‏) تعیین کنید تا ‏OSD‏های تازه مستقیماً زیر ‏rack‏ درست قرار بگیرند. در غیر این صورت هر ‏OSD‏ زیر ‏host‏ و مستقیماً زیر ‏root=default‏ می‌رود و پس از جابه‌جایی دستی، داده یک بار دیگر حرکت می‌کند.

۸-۳-۲ وزن#

هر ‏OSD‏ یک وزن ‏CRUSH‏ دارد (به‌طور پیش‌فرض برابر ظرفیت به ترابایت) و وزن هر ‏bucket‏ مجموع وزن فرزندان آن است. ‏CRUSH‏ داده را به نسبت وزن پخش می‌کند. وزن دیگری به نام ‏reweight‏ (بین ۰ و ۱) هم وجود دارد که برای اصلاح موقت توزیع به کار می‌رفت؛ امروز ‏balancer‏ با ‏upmap‏ این کار را دقیق‌تر انجام می‌دهد.

bashReef+
ceph osd crush reweight osd.20 3.49309     # وزن دائمی CRUSH
ceph osd reweight 20 0.9                   # اصلاح موقت (ترجیحاً از balancer استفاده کنید)

۸-۳-۳ الگوریتم ‏bucket‏#

‏CRUSH‏ برای انتخاب فرزند در هر ‏bucket‏ الگوریتمی دارد. امروز عملاً فقط ‏straw2‏ استفاده می‌شود (از ‏Hammer‏، پیش‌فرض ‏bucket‏های تازه). مزیت کلیدی ‏straw2‏ این است که تغییر وزن یک آیتم فقط داده را بین آن آیتم و بقیه جابه‌جا می‌کند، نه بین آیتم‌های دیگر؛ یعنی حداقل جابه‌جایی لازم.

۸-۳-۴ قانون‌ها#

قانون ‏CRUSH‏ مراحل انتخاب را تعریف می‌کند. بیشتر وقت‌ها لازم نیست قانون را دستی بنویسید؛ دستورهای کمکی کافی‌اند:

bashReef+
# تکثیری: نسخه‌ها در hostهای مختلف، فقط روی SSD
ceph osd crush rule create-replicated rep-ssd-host default host ssd
# تکثیری با دامنه‌ی خرابی rack
ceph osd crush rule create-replicated rep-rack default rack
# EC از روی پروفایل
ceph osd erasure-code-profile set ec42-rack k=4 m=2 crush-failure-domain=rack crush-device-class=hdd
ceph osd crush rule create-erasure ec42-rack ec42-rack
ceph osd crush rule ls
ceph osd crush rule dump rep-ssd-host

برای حالت‌های پیچیده‌تر، نقشه را استخراج، ویرایش و پیش از اعمال آزمایش کنید:

bashReef+
ceph osd getcrushmap -o crush.bin
crushtool -d crush.bin -o crush.txt
# ویرایش crush.txt
crushtool -c crush.txt -o crush.new
crushtool -i crush.new --test --rule 1 --num-rep 3 --show-statistics --show-bad-mappings
ceph osd setcrushmap -i crush.new

نمونه‌ی یک قانون دستی که دو نسخه را در یک ‏datacenter‏ و نسخه‌ی سوم را در ‏datacenter‏ دیگر می‌گذارد:

text
rule rep-2dc {
    id 5
    type replicated
    step take default
    step choose firstn 2 type datacenter
    step chooseleaf firstn 2 type host
    step emit
}
⚠ هشدار

قانون بالا با ‏size=3‏ دو نسخه در ‏datacenter‏ اول و یک نسخه در دومی می‌گذارد، اما کدام ‏datacenter‏ دو نسخه می‌گیرد برای هر ‏PG‏ متفاوت است. اگر می‌خواهید هر دو سایت قطعاً نسخه داشته باشند و نوشتن هنگام قطع یک سایت ادامه یابد، به جای قانون دستی از ‏stretch mode‏ (فصل ۱۸) استفاده کنید که منطق ‏quorum‏ و ‏min_size‏ را هم مدیریت می‌کند.

✎ یادداشت

‏Squid‏ نوع تازه‌ای از قانون به نام ‏MSR‏ (‏Multi-Step Retry‏) اضافه کرد که به گفته‌ی یادداشت‌های انتشار، پیکربندی‌های انعطاف‌پذیرتری برای ‏EC‏ ممکن می‌کند؛ کاربرد رایج آن قرار دادن کنترل‌شده‌ی چند ‏shard‏ در یک دامنه‌ی خرابی است وقتی تعداد دامنه‌ها کمتر از ‏k+m‏ است. چون این نوع قانون در نقشه‌ی ‏CRUSH‏ ثبت می‌شود، ‏require_min_compat_client‏ هم روی آن اثر دارد؛ پیش از استفاده نسخه‌ی کلاینت‌ها را بررسی کنید.

۸-۳-۵ کلاس دستگاه و ‏shadow tree‏#

از ‏Luminous‏ هر ‏OSD‏ یک کلاس دستگاه (‏hdd‏، ‏ssd‏، ‏nvme‏ یا هر نام دلخواه) دارد. ‏CRUSH‏ برای هر کلاس یک درخت سایه (‏shadow‏) می‌سازد، مثلاً ‏default~ssd‏، که فقط ‏OSD‏های آن کلاس را دارد. قانونی که کلاس دارد روی همین درخت سایه اجرا می‌شود. این قابلیت جایگزین ترفند قدیمی کتاب ۲۰۱۹ است که برای جدا کردن ‏SSD‏ و ‏HDD‏ دو ‏root‏ جدا می‌ساخت.

۸-۴ ‏tunable‏ها#

۸-۴-۱ چرا ‏tunable‏؟#

‏CRUSH‏ از ابتدا کامل نبود. در برخی سلسله‌مراتب‌ها (مثلاً درخت‌های کوچک یا وقتی ‏OSD‏های زیادی ‏out‏ هستند) نمی‌توانست برای همه‌ی ‏PG‏ها نسخه‌ی کافی پیدا کند، یا با تغییرات کوچک داده‌ی زیادی را جابه‌جا می‌کرد. اصلاح این رفتارها نتیجه‌ی نگاشت را تغییر می‌دهد، پس نمی‌شد آن‌ها را بی‌صدا اعمال کرد: هر کلاینت باید همان نتیجه‌ای را محاسبه کند که ‏OSD‏ها محاسبه می‌کنند. راه‌حل ‏Ceph‏، ‏tunable‏هاست: پرچم‌هایی در نقشه‌ی ‏CRUSH‏ که رفتار الگوریتم را تعیین می‌کنند و به‌صورت پروفایلهایی به نام نسخه‌ها گروه‌بندی شده‌اند.

پروفایلویژگی اصلیحداقل هسته‌ی کلاینت (طبق مستندات)
‏argonaut‏ (legacy)رفتار اولیههمه
‏bobtail‏ (CRUSH_TUNABLES۲)کاهش شکست در یافتن نگاشت و اصلاح ‏retry‏ها۳.۹
‏firefly‏ (CRUSH_TUNABLES۳)اصلاح رفتار ‏chooseleaf‏ با پیمایش دوباره۳.۱۵
‏hammer‏ (CRUSH_V۴)‏bucket‏ نوع ‏straw2‏۴.۱
‏jewel‏ (CRUSH_TUNABLES۵)‏chooseleaf_stable‏: جابه‌جایی کمتر هنگام ‏out‏ شدن ‏OSD‏۴.۵
bashReef+
ceph osd crush show-tunables
ceph osd crush tunables optimal       # جابه‌جایی داده دارد!
ceph features                         # نسخه و قابلیت‌های کلاینت‌های متصل
ceph osd get-require-min-compat-client
⚠ هشدار

تغییر پروفایل ‏tunable‏ روی کلاستر پرداده، بخش بزرگی از داده را جابه‌جا می‌کند. این کار را در ساعات کم‌بار، با پروفایل ‏mClock‏ مناسب و پس از اطمینان از سازگاری همه‌ی کلاینت‌ها (به‌ویژه کلاینت‌های هسته‌ی قدیمی ‏RBD‏ و ‏CephFS‏) انجام دهید. کلاسترهایی که با نسخه‌های جدید ساخته شده‌اند از ابتدا ‏tunable‏های بهینه دارند و این بخش بیشتر برای کلاسترهای قدیمیِ ارتقایافته اهمیت دارد.

۸-۴-۲ هشدار ‏non-optimal‏#

اگر ‏tunable‏های کلاستر از پروفایل بهینه‌ی نسخه‌ی فعلی عقب باشند، هشدار ‏OLD_CRUSH_TUNABLES‏ نمایش داده می‌شود. می‌توانید آن را با به‌روزرسانی ‏tunable‏ها رفع کنید یا، اگر کلاینت قدیمی دارید که نمی‌توانید ارتقا دهید، با ‏mon_warn_on_legacy_crush_tunables=false‏ موقتاً خاموش کنید. هشدار مشابه ‏OLD_CRUSH_STRAW_CALC_VERSION‏ هم وقتی ظاهر می‌شود که نقشه هنوز از نسخه‌ی قدیمی و معیوب محاسبه‌ی وزن در ‏bucket‏های ‏straw‏ استفاده کند.

bashReef+
# تبدیل bucketهای straw به straw2 با حداقل جابه‌جایی
ceph osd crush set-all-straw-buckets-to-straw2

۸-۵ نقشه‌های کلاستر و ‏MON‏ها#

شکل ۸-۳: نقشه‌های کلاستر در MON ها نگهداری و با epoch نسخه‌گذاری می‌شوند؛ کلاینت‌ها و OSD ها نقشه‌ی جدید را از MON یا از یکدیگر می‌گیرند
شکل ۸-۳: نقشه‌های کلاستر در ‏MON‏ها نگهداری و با ‏epoch‏ نسخه‌گذاری می‌شوند؛ کلاینت‌ها و ‏OSD‏ها نقشه‌ی جدید را از ‏MON‏ یا از یکدیگر می‌گیرند

۸-۵-۱ نقشه‌ها#

وضعیت کلاستر در چند نقشه نگهداری می‌شود که همه با یک ‏epoch‏ (شماره‌ی نسخه‌ی صعودی) نسخه‌گذاری شده‌اند:

نقشهمحتوادستور مشاهده
‏MonMap‏‏fsid‏، فهرست ‏MON‏ها و آدرس‌هایشان‏ceph mon dump‏
‏OSDMap‏‏pool‏ها، ‏OSD‏ها و وضعیت ‏up/in‏، ‏pg_temp‏ و ‏upmap‏ها، پرچم‌ها و نقشه‌ی ‏CRUSH‏‏ceph osd dump‏
‏MgrMap‏‏MGR‏ فعال، آماده‌به‌کارها و ماژول‌ها‏ceph mgr dump‏
‏FSMap‏فایل‌سیستم‌ها و ‏MDS‏ها‏ceph fs dump‏
‏PGMap‏آمار ‏PG‏ها (در ‏MGR‏، نه ‏MON‏)‏ceph pg dump‏
◆ عمیق‌تر

‏OSD‏ها نقشه‌ی جدید را لزوماً از ‏MON‏ نمی‌گیرند: هر پیام بین ‏OSD‏ها و کلاینت‌ها ‏epoch‏ فرستنده را دارد و طرفی که نقشه‌ی جدیدتری دارد، تغییرات (‏incremental‏) را برای دیگری می‌فرستد. این «شایعه‌پراکنی» (‏gossip‏) بار ‏MON‏ها را کم می‌کند. پیامد عملی: ‏OSD‏ی که مدتی خاموش بوده باید هنگام بالا آمدن همه‌ی ‏epoch‏های از دست رفته را پردازش کند، و اگر ‏MON‏ها ‏epoch‏های قدیمی را نگه داشته باشند (مثلاً چون کلاستر مدت‌ها ناسالم بوده) پایگاه داده‌ی ‏MON‏ بزرگ می‌شود.

۸-۵-۲ دسترس‌پذیری ‏MON‏ها#

‏MON‏ها با الگوریتم ‏Paxos‏ اجماع می‌کنند: هر تغییر نقشه فقط وقتی معتبر است که اکثریت ‏MON‏ها (‏quorum‏) آن را بپذیرند. یکی از ‏MON‏ها ‏leader‏ است و بقیه ‏peon‏.

bashReef+
ceph mon stat
ceph quorum_status -f json-pretty
ceph mon dump
ceph tell mon.ceph-node1 mon_status
تعداد ‏MON‏اکثریت لازمتحمل خرابی
۱۱۰
۳۲۱
۵۳۲
۷۴۳
⚠ هشدار

تعداد زوج ‏MON‏ تحمل خرابی را افزایش نمی‌دهد: چهار ‏MON‏ هم فقط یک خرابی را تحمل می‌کنند. ‏MON‏ها به ساعت دقیق حساس‌اند (هشدار ‏MON_CLOCK_SKEW‏)؛ ‏chrony‏ یا ‏NTP‏ را روی همه‌ی گره‌ها پیکربندی و پایش کنید. پایگاه داده‌ی ‏MON‏ (RocksDB) را روی ‏SSD‏ قرار دهید و فضای دیسک آن را پایش کنید؛ وقتی فضای آزاد کم شود، هشدار ‏MON_DISK_LOW‏ و در نهایت توقف ‏MON‏ پیش می‌آید.

✎ یادداشت

‏Umbrella‏ سازوکار اختیاری پشتیبان‌گیری و بازیابی ‏RocksDB MON‏ را اضافه کرده است (تنظیم ‏mon_backup_interval‏، دستور ‏ceph tell mon.* backup‏ و بازیابی آفلاین با ‏ceph-mon --restore-backup‏). پیش از هر عملیات پرخطر روی ‏MON‏ها (حذف، بازسازی یا ارتقای اصلی) از این امکان یا از روش‌های مستند بازسازی پایگاه ‏MON‏ آگاه باشید؛ از دست رفتن همه‌ی ‏MON‏ها بدون پشتیبان، بدترین سناریوی ‏Ceph‏ است.

۸-۶ از درون کد: جای‌گذاری، احراز هویت و پیام‌رسانی#

۸-۶-۱ از نام شیء تا ‏OSD‏#

شکل ۸-۴: سه مرحله‌ی نگاشت شیء به OSD : در کلاینت، نام شیء با ceph_str_hash درهم‌سازی و با ceph_stable_mod به شماره‌ی PG تبدیل می‌شود؛ سپس شماره‌ی PG با شناسه‌ی pool ترکیب و به‌عنوان ورودی x به crush_do_rule داده می‌شود؛ در پایان OSDMap OSD های ناموجود را حذف و upmap و pg_temp را اعمال می‌کند تا acting set و primary به دست آید
شکل ۸-۴: سه مرحله‌ی نگاشت شیء به ‏OSD‏: در کلاینت، نام شیء با ‏ceph_str_hash‏ درهم‌سازی و با ‏ceph_stable_mod‏ به شماره‌ی ‏PG‏ تبدیل می‌شود؛ سپس شماره‌ی ‏PG‏ با شناسه‌ی ‏pool‏ ترکیب و به‌عنوان ورودی ‏x‏ به ‏crush_do_rule‏ داده می‌شود؛ در پایان ‏OSDMap OSD‏های ناموجود را حذف و ‏upmap‏ و ‏pg_temp‏ را اعمال می‌کند تا ‏acting set‏ و ‏primary‏ به دست آید

مرحله‌ی اول در ‏pg_pool_t::hash_key()‏ (‏src/osd/osd_types.cc‏) انجام می‌شود: نام شیء، و در صورت وجود ‏namespace‏، با تابع درهم‌سازی ‏pool‏ (پیش‌فرض ‏rjenkins‏) به یک عدد ۳۲بیتی تبدیل می‌شود. سپس این عدد با ‏ceph_stable_mod()‏ به بازه‌ی ‏PG‏ها نگاشت می‌شود. این تابع کوچک یکی از هوشمندانه‌ترین قطعه‌های ‏Ceph‏ است:

c
/* from ceph/ceph, LGPL — src/include/rados.h (excerpt) */
static inline int ceph_stable_mod(int x, int b, int bmask)
{
	if ((x & bmask) < b)
		return x & bmask;
	else
		return x & (bmask >> 1);
}

‏b‏ تعداد ‏PG‏ها و ‏bmask‏ کوچک‌ترین توان ۲ منهای یک است که دست‌کم ‏b‏ باشد. اگر ‏pg_num‏ توان ۲ نباشد (مثلاً ۱۲ با ‏bmask=15‏)، مقادیر ۱۲ تا ۱۵ که ‏PG‏ متناظر ندارند به نیمه‌ی پایین (۴ تا ۷) «تا» می‌شوند. نتیجه‌ی مهم: وقتی ‏pg_num‏ از ۱۲ به ۱۳ افزایش می‌یابد، فقط اشیایی که به ‏PG‏ ۵ می‌رفتند و ‏hash‏ آن‌ها در ۱۳ می‌افتد جابه‌جا می‌شوند؛ یعنی تقسیم ‏PG‏ محلی است و همه‌ی داده بازچینی نمی‌شود. همین ویژگی است که افزایش تدریجی ‏pg_num‏ (از ‏Nautilus‏) را ممکن کرده است.

✎ یادداشت

به همین دلیل، وقتی ‏pg_num‏ توان ۲ نیست، اندازه‌ی ‏PG‏ها نابرابر است: ‏PG‏های «تاشده» تقریباً دو برابر داده دارند. این یکی از دلایل توصیه‌ی توان ۲ و رفتار ‏autoscaler‏ است (فصل ۱۹).

۸-۶-۲ ‏straw2‏: انتخاب با کشیدن قرعه‌ی نمایی#

شکل ۸-۵: الگوریتم straw2 در سه مرحله: برای هر آیتم bucket یک عدد شبه‌تصادفی شانزده‌بیتی از hash ورودی، شناسه‌ی آیتم و شماره‌ی تلاش ساخته و لگاریتم طبیعی آن با جدول محاسبه و بر وزن تقسیم می‌شود؛ آیتمی با بیشترین مقدار انتخاب می‌شود که از نظر ریاضی معادل انتخاب کمینه از متغیرهای نمایی با نرخ برابر وزن است و احتمال انتخاب هر آیتم را دقیقاً متناسب با وزن آن می‌کند؛ پیامد آن حداقل جابه‌جایی داده هنگام تغییر وزن است
شکل ۸-۵: الگوریتم ‏straw2‏ در سه مرحله: برای هر آیتم ‏bucket‏ یک عدد شبه‌تصادفی شانزده‌بیتی از ‏hash‏ ورودی، شناسه‌ی آیتم و شماره‌ی تلاش ساخته و لگاریتم طبیعی آن با جدول محاسبه و بر وزن تقسیم می‌شود؛ آیتمی با بیشترین مقدار انتخاب می‌شود که از نظر ریاضی معادل انتخاب کمینه از متغیرهای نمایی با نرخ برابر وزن است و احتمال انتخاب هر آیتم را دقیقاً متناسب با وزن آن می‌کند؛ پیامد آن حداقل جابه‌جایی داده هنگام تغییر وزن است

‏bucket_straw2_choose()‏ در ‏src/crush/mapper.c‏ برای هر آیتم ‏bucket‏ یک «قرعه» (‏draw‏) محاسبه و بزرگ‌ترین را انتخاب می‌کند. هسته‌ی محاسبه:

c
/* from ceph/ceph, LGPL — src/crush/mapper.c, generate_exponential_distribution() (excerpt) */
unsigned int u = crush_hash32_3(type, x, y, z);
u &= 0xffff;
__s64 ln = crush_ln(u) - 0x1000000000000ll;
return div64_s64(ln, weight);

ریاضیات پشت آن (به بیان این کتاب): فرض کنید ‏U‏ یکنواخت در (۰، ۱] باشد. آنگاه ‏−ln(U) / w‏ متغیری نمایی با نرخ ‏w‏ است. کمینه‌ی چند متغیر نمایی مستقل با نرخ‌های ‏w‏₁ تا ‏w‏ₙ، با احتمال ‏wᵢ / Σw‏ متعلق به آیتم ‏i‏ است. چون ‏ln‏(U) منفی است، ‏ln(U)/w‏ بزرگ‌ترین مقدار را برای همان آیتمی دارد که ‏−ln(U)/w‏ کمینه است. بنابراین:

text
draw_i = ln(u_i / 65536) / w_i          (u_i = hash(x, id_i, r) mod 2^16)
choose argmax_i draw_i   ⇒   P(i) = w_i / Σ_j w_j

‏crush_ln()‏ لگاریتم را با جدول‌های ممیز ثابت (بدون ممیز شناور، چون همین کد در هسته‌ی لینوکس هم اجرا می‌شود) تقریب می‌زند؛ خروجی آن در بازه‌ی [۰, ۲⁴⁸) است و تفریق ۲⁴⁸ آن را به مقدار منفی معادل ‏ln‏(u/۶۵۵۳۶) تبدیل می‌کند. وزن‌ها در قالب ۱۶.۱۶ ممیز ثابت‌اند (۰‏x10000‏ یعنی ۱.۰).

◆ عمیق‌تر

چرا ‏straw2‏ جابه‌جایی را کمینه می‌کند؟ ‏draw‏ هر آیتم فقط به ‏hash‏ خودش و وزن خودش بستگی دارد. اگر وزن آیتم ‏A‏ افزایش یابد، فقط ‏draw‏ آن بزرگ‌تر می‌شود؛ ‏PG‏هایی که قبلاً ‏A‏ را انتخاب کرده بودند همچنان ‏A‏ را انتخاب می‌کنند و برخی ‏PG‏های دیگر به ‏A‏ منتقل می‌شوند، اما هیچ ‏PG‏ی بین دو آیتم دیگر (‏B‏ و ‏C‏) جابه‌جا نمی‌شود. در الگوریتم قدیمی ‏straw‏، طول «نی»ها به وزن همه‌ی آیتم‌ها وابسته بود و این ویژگی برقرار نبود؛ به همین دلیل ‏straw2‏ از ‏Hammer‏ معرفی و بعدها پیش‌فرض شد.

آزمایشگاه

زمین بازی CRUSH

یک root، چند host و OSD بسازید، قاعده را انتخاب کنید و ببینید straw2 هر PG را کجا می‌گذارد؛ یک OSD را out یا down کنید یا وزنش را تغییر دهید و ببینید کدام PGها جابه‌جا می‌شوند.

۶۴ × ۳PG × نسخه
۹٫۹٪داده‌ای که تغییر آخر جابه‌جا کرد
۱۹PGهایی که مجموعهٔ upشان عوض شد
۰PGهایی با نسخه‌ای کمتر از size
۰جای خالی (NONE)

تغییر آخر: osd.4 out شد. shardهای جابه‌جاشده در فهرست برجسته شده‌اند.

osd.0: 29 (≈24)029osd.1: 14 (≈24)114osd.2: 21 (≈24)221osd.3: 0 (≈0)30osd.4: 38 (≈24)438osd.5: 26 (≈24)526osd.6: 27 (≈24)627osd.7: 14 (≈24)714osd.8: 23 (≈24)823
shardهای PG روی هر OSD (ستون) در برابر سهمی که وزنش می‌دهد (خط). قرمز: out؛ کهربایی: down.
$ ceph osd map pool1 rbd_data.10226b8b4567.0000000000000000ceph_str_hash_rjenkins → 0x87bd7b9a · ceph_stable_mod(·, 64, 63) → pg 1.1a · pps 0x8d2145b9 · up [4,2,8] p4
PG ← مجموعهٔ up (۶۴ PG؛ primary زیرخط‌دار)
  1. 1.0[0,6,5]
  2. 1.1[7,4,0]
  3. 1.2[4,7,0]
  4. 1.3[4,8,2]
  5. 1.4[1,8,5]
  6. 1.5[4,8,0]
  7. 1.6[6,4,2]
  8. 1.7[1,7,5]
  9. 1.8[6,0,4]
  10. 1.9[8,4,2]
  11. 1.a[1,5,7]
  12. 1.b[1,8,5]
  13. 1.c[6,5,2]
  14. 1.d[7,4,2]
  15. 1.e[1,8,4]
  16. 1.f[6,2,4]
  17. 1.10[5,2,8]
  18. 1.11[4,8,0]
  19. 1.12[0,6,5]
  20. 1.13[4,6,1]
  21. 1.14[5,8,1]
  22. 1.15[8,5,2]
  23. 1.16[5,0,6]
  24. 1.17[8,0,5]
  25. 1.18[6,2,4]
  26. 1.19[6,0,4]
  27. 1.1a[4,2,8]
  28. 1.1b[8,1,4]
  29. 1.1c[8,4,1]
  30. 1.1d[6,0,5]
  31. 1.1e[8,4,0]
  32. 1.1f[8,5,2]
  33. 1.20[2,6,5]
  34. 1.21[6,0,4]
  35. 1.22[2,4,6]
  36. 1.23[0,5,7]
  37. 1.24[0,4,6]
  38. 1.25[8,5,0]
  39. 1.26[1,4,6]
  40. 1.27[1,7,5]
  41. 1.28[5,7,0]
  42. 1.29[8,2,4]
  43. 1.2a[5,2,6]
  44. 1.2b[8,0,4]
  45. 1.2c[7,0,4]
  46. 1.2d[2,7,4]
  47. 1.2e[6,2,4]
  48. 1.2f[0,4,6]
  49. 1.30[6,5,0]
  50. 1.31[0,6,4]
  51. 1.32[0,6,4]
  52. 1.33[4,6,2]
  53. 1.34[8,0,5]
  54. 1.35[8,2,4]
  55. 1.36[7,0,4]
  56. 1.37[4,7,0]
  57. 1.38[6,4,2]
  58. 1.39[0,5,6]
  59. 1.3a[0,8,4]
  60. 1.3b[4,1,6]
  61. 1.3c[5,6,0]
  62. 1.3d[5,8,2]
  63. 1.3e[5,1,7]
  64. 1.3f[7,4,1]

مطابق با:

  • src/crush/mapper.c :: crush_do_rule اجرای گام‌های قاعده (take، chooseleaf، emit)
  • src/crush/mapper.c :: crush_choose_firstn, crush_choose_indep حلقه‌های انتخاب replicated (firstn) و EC (indep) با تلاش دوباره
  • src/crush/mapper.c :: bucket_straw2_choose, generate_exponential_distribution, crush_ln straw2: بزرگ‌ترین ln(hash)/weight برنده است
  • src/crush/mapper.c :: is_out بررسی reweight که OSD خارج‌شده را رد می‌کند
  • src/crush/hash.c :: crush_hash32_rjenkins1_2, _3 hash جنکینز که CRUSH به کار می‌برد
  • src/osd/osd_types.cc :: pg_pool_t::raw_pg_to_pps, hash_key بذر PG که وارد CRUSH می‌شود
  • src/include/rados.h :: ceph_stable_mod نگاشت hash شیء به PG وقتی pg_num توانی از ۲ نیست
  • src/common/ceph_hash.cc :: ceph_str_hash_rjenkins hash نام شیء
  • src/osd/OSDMap.cc :: _pg_to_raw_osds, _raw_to_up_osds از مجموعهٔ خام تا مجموعهٔ up (OSDهای down حذف یا NONE می‌شوند)
  • src/crush/CrushWrapper.cc :: add_simple_rule_at, insert_item; CrushWrapper.h :: set_tunables_jewel قاعده‌ای که ceph osd pool create می‌سازد و tunables نسخهٔ jewel
تصویری ثابت از شبیه‌ساز؛ نسخهٔ تعاملی در آزمایشگاه نسخهٔ وب است. آزمایشگاه تعاملی: زمین بازی CRUSH

۸-۶-۳ تلاش دوباره: ‏collision‏ و ‏reject‏#

‏crush_choose_firstn()‏ برای هر نسخه (‏rep‏) یک ‏bucket‏ را پایین می‌رود تا به نوع مورد نظر برسد. اگر آیتم انتخاب‌شده قبلاً انتخاب شده باشد (‏collide‏) یا ‏OSD‏ خارج یا بیش‌ازحد پر باشد (‏reject‏)، شمارنده‌ی ‏ftotal‏ افزایش می‌یابد و انتخاب با ‏r' = r + ftotal‏ تکرار می‌شود؛ تا سقف ‏choose_total_tries‏ (‏tunable‏، پیش‌فرض ۵۰ در پروفایل‌های جدید).

text
pseudocode: crush_choose_firstn (خلاصه‌ی این کتاب)
  for rep in 0..numrep-1:
      ftotal = 0
      loop:
          r = rep + ftotal
          item = descend(bucket, x, r) until type matches     # straw2 در هر سطح
          collide = item in out[0..rep-1]
          reject  = is_out(item, x) or (chooseleaf and leaf selection failed)
          if not collide and not reject: out[rep] = item; break
          ftotal += 1
          if ftotal >= choose_total_tries: give up on this rep

تابع ‏is_out()‏ جالب است: ‏OSD‏ی با ‏reweight‏ بین ۰ و ۱ به‌صورت احتمالاتی رد می‌شود؛ ‏hash(x, item) & 0xffff‏ با وزن ‏reweight‏ (۱۶.۱۶) مقایسه می‌شود. پس ‏ceph osd reweight 12 0.8‏ یعنی حدود ۲۰٪ از ‏PG‏هایی که ‏CRUSH‏ به ‏osd.12‏ می‌داد، با تلاش دوباره به ‏OSD‏ دیگری می‌روند. ‏reweight‏ به همین دلیل با وزن ‏CRUSH‏ (که سهم را در ‏straw2‏ تغییر می‌دهد) فرق دارد.

💡 نکته

‏crushtool --test‏ همین کد را خارج از کلاستر اجرا می‌کند. برای دیدن اثر یک تغییر پیش از اعمال: ‏ceph osd getcrushmap -o cm‏، ویرایش، سپس ‏crushtool -i cm --test --show-statistics --rule 0 --num-rep 3 --min-x 0 --max-x 100000‏ و مقایسه‌ی ‏--show-mappings‏ قبل و بعد.

۸-۶-۴ ‏cephx‏: بلیت‌ها و کلیدهای چرخشی#

شکل ۸-۶: جریان احراز هویت cephx : کلاینت با اثبات دانستن کلید مشترک از MON کلید جلسه و بلیت احراز می‌گیرد، سپس برای سرویس OSD بلیت سرویس رمزشده با کلید چرخشی آن سرویس دریافت می‌کند؛ OSD که کلیدهای چرخشی را از MON گرفته است، بلیت را رمزگشایی و انقضا و cap ها را بررسی می‌کند و پاسخ می‌دهد تا احراز دوطرفه کامل شود
شکل ۸-۶: جریان احراز هویت ‏cephx‏: کلاینت با اثبات دانستن کلید مشترک از ‏MON‏ کلید جلسه و بلیت احراز می‌گیرد، سپس برای سرویس ‏OSD‏ بلیت سرویس رمزشده با کلید چرخشی آن سرویس دریافت می‌کند؛ ‏OSD‏ که کلیدهای چرخشی را از ‏MON‏ گرفته است، بلیت را رمزگشایی و انقضا و ‏cap‏ها را بررسی می‌کند و پاسخ می‌دهد تا احراز دوطرفه کامل شود

ساختارهای اصلی در ‏src/auth/cephx/CephxProtocol.h‏ تعریف شده‌اند: ‏CephXServerChallenge‏، ‏CephXAuthenticate‏، ‏CephXTicketBlob‏، ‏CephXServiceTicket‏، ‏CephXServiceTicketInfo‏ و ‏CephXAuthorizer‏. سه نوع درخواست اصلی با ثابت‌های ‏CEPHX_GET_AUTH_SESSION_KEY‏ (۰‏x0100‏)، ‏CEPHX_GET_PRINCIPAL_SESSION_KEY‏ (۰‏x0200‏) و ‏CEPHX_GET_ROTATING_KEY‏ (۰‏x0400‏) مشخص می‌شوند.

  • کلید مشترک کلاینت هرگز روی شبکه منتقل نمی‌شود؛ کلاینت فقط ثابت می‌کند آن را دارد (پاسخ به ‏challenge‏).
  • بلیت سرویس با کلید چرخشی سرویس رمز می‌شود که ‏MON‏ دوره‌ای عوض می‌کند و ‏OSD‏ها می‌گیرند؛ بنابراین ‏OSD‏ برای بررسی بلیت نیازی به تماس با ‏MON‏ ندارد.
  • عمر بلیت‌ها با ‏auth_mon_ticket_ttl‏ (پیش‌فرض ۷۲ ساعت) و ‏auth_service_ticket_ttl‏ (پیش‌فرض ۱ ساعت) کنترل می‌شود؛ کلاینت پیش از انقضا تمدید می‌کند.
⚠ هشدار

چون بلیت‌ها زمان انقضا دارند، اختلاف ساعت بین کلاینت، ‏MON‏ و ‏OSD‏ می‌تواند احراز را بشکند. پیام‌هایی مانند «‏could not get service secret‏» یا خطای احراز پس از راه‌اندازی دوباره‌ی میزبانی با ساعت نادرست، اغلب از همین است. ‏NTP/chrony‏ روی همه‌ی میزبان‌ها و کلاینت‌ها الزامی است.

۸-۶-۵ ‏msgr2‏: قاب‌ها و حالت امن#

شکل ۸-۷: ساختار قاب در پروتکل msgr2: preamble با اندازه‌ی ثابت شامل برچسب نوع قاب، تعداد segment ها تا چهار و طول و هم‌ترازی هر segment و CRC جداگانه؛ سپس حداکثر چهار segment شامل سرآیند، front ، middle و data ؛ و در پایان epilogue که در حالت crc شامل CRC هر segment و در حالت secure رمز و احراز صحت AES-GCM کل قاب است
شکل ۸-۷: ساختار قاب در پروتکل ‏msgr2: preamble‏ با اندازه‌ی ثابت شامل برچسب نوع قاب، تعداد ‏segment‏ها تا چهار و طول و هم‌ترازی هر ‏segment‏ و ‏CRC‏ جداگانه؛ سپس حداکثر چهار ‏segment‏ شامل سرآیند، ‏front‏، ‏middle‏ و ‏data‏؛ و در پایان ‏epilogue‏ که در حالت ‏crc‏ شامل ‏CRC‏ هر ‏segment‏ و در حالت ‏secure‏ رمز و احراز صحت ‏AES-GCM‏ کل قاب است

در ‏src/msg/async/frames_v2.h‏، هر قاب با یک ‏preamble_block_t‏ شروع می‌شود که برچسب (‏enum class Tag‏: ‏HELLO‏، ‏AUTH_REQUEST‏، ‏AUTH_REQUEST_MORE‏، ...، ‏SESSION_RECONNECT‏، ‏MESSAGE‏، ‏KEEPALIVE2‏، ‏COMPRESSION_REQUEST‏ و...)، تعداد ‏segment‏ها (حداکثر ‏MAX_NUM_SEGMENTS = 4‏) و طول و هم‌ترازی هر ‏segment‏ را دارد. جدا کردن ‏segment‏ داده با هم‌ترازی مشخص اجازه می‌دهد بافر داده بدون کپی (‏zero-copy‏) مستقیم به ‏BlueStore‏ برسد.

bashReef+
ceph config get mon ms_cluster_mode          # crc یا secure (و ترتیب ترجیح)
ceph config get mon ms_service_mode
ceph config get mon ms_client_mode
ceph config set global ms_cluster_mode "secure"   # رمزنگاری ترافیک بین OSDها
ceph mon dump | grep -E "v2:|v1:"                 # نشانی‌های دو پروتکل
✎ یادداشت

حالت ‏secure‏ هزینه‌ی ‏CPU‏ دارد، اما روی پردازنده‌های دارای ‏AES-NI‏ معمولاً قابل‌تحمل است. پیش از فعال‌سازی روی شبکه‌ی ‏cluster‏ پرترافیک، اثر را در آزمایشگاه بسنجید. پورت پیش‌فرض ‏msgr2‏ روی ‏MON‏ ۳۳۰۰ و ‏msgr1‏ ۶۷۸۹ است.

۸-۷ جابه‌جایی داده با عدد: افزودن میزبان و تغییر وزن#

این بخش دو پرسش عملی را با نگاشت واقعی ‏CRUSH‏ پاسخ می‌دهد: «با افزودن یک میزبان چه مقدار داده جابه‌جا می‌شود؟» و «با تغییر وزن یک ‏OSD‏ چه چیزی جابه‌جا می‌شود؟». نقشه با ‏crushtool --build --num_osds 132 … host straw2 12 root straw2 0‏ ساخته شد. نسخه‌ی ده‌میزبانی همان نقشه با ‏--remove-item‏ به دست آمد تا شناسه‌ی همه‌ی ‏bucket‏ها و ‏OSD‏ها ثابت بماند، چون ‏straw2‏ شناسه‌ی آیتم را در ‏hash‏ به کار می‌برد. ‏pool‏ سه‌نسخه‌ای با ۸۱۹۲ ‏PG‏ در ‏osdmaptool --createsimple 256 --pg-bits 5 --pgp-bits 5‏ تعریف شد و نگاشت هر حالت با ‏--test-map-pgs-dump‏ گرفته شد. ابزارها از ‏Ceph 18.2.7‏ بودند و ‏tunable‏ها پیش‌فرض: ‏chooseleaf_vary_r 1‏، ‏chooseleaf_stable 1‏ و ‏choose_total_tries 50‏.

✎ یادداشت

یک دام در ‏osdmaptool‏: گزینه‌ی ‏--mark-up-in‏ به هر ‏OSD‏ با وزن ‏CRUSH‏ صفر وزن ۱.۰ می‌دهد (‏src/tools/osdmaptool.cc‏، بلوک ‏if (mark_up_in)‏ با ‏adjust_item_weightf(…, 1.0)‏). پس برای شبیه‌سازی «میزبان هنوز اضافه نشده» نمی‌توان وزن ‏OSD‏ها را صفر کرد و باید آن‌ها را از نقشه حذف کرد. ما اول همین اشتباه را کردیم و هیچ جابه‌جایی‌ای ندیدیم.

۸-۷-۱ افزودن یک میزبان#

∑ الگوریتم: شمارش جابه‌جایی با مقایسه‌ی دو نگاشت

منبع در کد: osdmaptool --test-map-pgs-dump پیش و پس از تغییر؛ bucket_straw2_choose و crush_choose_firstn در src/crush/mapper.c

پیچیدگی: ‏O‏(pg_num × ‏size‏)

text
pseudocode
A = up(pg) با نقشه‌ی قدیم ؛ B = up(pg) با نقشه‌ی جدید          # برای هر pg در [0, pg_num)
moved = Σ_pg |set(B[pg]) − set(A[pg])|                         # جای‌گذاری‌های تازه، بی‌توجه به ترتیب
to_new = Σ_pg |{o ∈ B[pg] − A[pg] : o روی میزبان جدید}|
ideal = pg_num × size × (وزن میزبان جدید / وزن کل)               # 24576 × 12/132

نتیجه. از ۲۴۵۷۶ جای‌گذاری ‏replica‏، ۲۱۸۴ مورد (۸.۸۹٪) به میزبان جدید رفت. حد ایده‌آل ۲۲۳۴ (۹.۰۹٪) است. این همان رفتاری است که ‏straw2‏ تضمین می‌کند: هر آیتم یک «قرعه»ی مستقل ‏ln(u)/w‏ دارد (تابع ‏generate_exponential_distribution‏) و افزودن یک آیتم فقط در جاهایی برنده را عوض می‌کند که قرعه‌ی آیتم تازه بالاتر باشد.

اما ۳۲۸ جای‌گذاری دیگر (۱.۳۳٪) بین ‏OSD‏های قدیمی جابه‌جا شد. نمونه‌ای واقعی از همین نگاشت: ‏PG‏ ‏‏1.58‏ از ‏[76, 113, 46]‏ به ‏[76, 125, 116]‏ رفت. میزبان جدید در جایگاه دوم نشست و میزبان ۹ به جایگاه سوم رانده شد. در ‏crush_choose_firstn‏ برگ درون میزبان با ‏sub_r = r >> (vary_r − 1)‏ انتخاب می‌شود، یعنی به ‏r‏ جایگاه بستگی دارد. پس در همان میزبان ۹ به‌جای ‏osd.113‏، ‏‏osd.116‏ انتخاب شد. نتیجه‌ی عملی این است که افزودن ظرفیت حدود ۱۵٪ بیشتر از حد ایده‌آل جابه‌جایی ایجاد می‌کند (۲۵۱۲ در برابر ۲۲۳۴). این را در برآورد زمان ‏backfill‏ لحاظ کنید.

شکل ۸-۸: افزودن میزبان یازدهم (۱۲ OSD ) به کلاستر ۱۰ × ۱۲ با straw2 ، pool سه‌نسخه‌ای و ۸۱۹۲ PG ، اندازه‌گیری‌شده با osdmaptool . خاکستری سهم ایده‌آل است، سبز replica هایی که به میزبان جدید رفتند و نارنجی جابه‌جایی بین OSD های قدیمی که از عوض شدن r جایگاه در chooseleaf ناشی می‌شود
شکل ۸-۸: افزودن میزبان یازدهم (۱۲ ‏OSD‏) به کلاستر ۱۰ × ۱۲ با ‏straw2‏، ‏‏pool‏ سه‌نسخه‌ای و ۸۱۹۲ ‏PG‏، اندازه‌گیری‌شده با ‏osdmaptool‏. خاکستری سهم ایده‌آل است، سبز ‏replica‏هایی که به میزبان جدید رفتند و نارنجی جابه‌جایی بین ‏OSD‏های قدیمی که از عوض شدن ‏r‏ جایگاه در ‏chooseleaf‏ ناشی می‌شود

۸-۷-۲ تغییر وزن یک ‏OSD‏#

∑ الگوریتم: دسته‌بندی جابه‌جایی‌ها پس از ‏reweight‏

منبع در کد: crushtool --reweight-item (وزن والدها را هم به‌روز می‌کند)؛ همان مقایسه‌ی A و B

پیچیدگی: ‏O‏(pg_num × ‏size‏)

text
pseudocode
برای هر جای‌گذاری تازه y ∈ B[pg] − A[pg]:
    اگر osd.5 در ورودی یا خروجی این PG باشد      → «خود osd.5»
    وگرنه اگر ورودی یا خروجی روی host0 باشد       → «دیگر OSDهای host0»   (وزن host0 در root عوض شده)
    وگرنه                                          → «میزبان‌های نامربوط»   (جابه‌جایی جایگاه/r)

نتیجه. وقتی وزن ‏osd.5‏ از ۱.۰ به ۲.۰ می‌رسد، ۴۱۱ جای‌گذاری عوض می‌شود: ۲۱۴ مورد خود ‏osd.5‏، ‏۱۶۹ مورد دیگر ‏OSD‏های ‏host0‏ و ۲۸ مورد میزبان‌های نامربوط. وقتی وزن به ۰.۸ کم می‌شود، ۸۴ جای‌گذاری عوض می‌شود: ۴۲، ۴۰ و ۲.

بخش بزرگ «دیگر ‏OSD‏های ‏host0‏» اشکال ‏straw2‏ نیست. ‏crushtool --reweight-item‏ وزن ‏host0‏ را در ‏root‏ هم از ۱۲ به ۱۳ می‌برد و ‏host0‏ سهم بیشتری از ‏PG‏ها می‌گیرد. ‏straw2‏ این سهم را درون ‏host0‏ به نسبت ۲/۱۳ برای ‏osd.5‏ و ۱/۱۳ برای هر ‏OSD‏ دیگر پخش می‌کند.

در این آزمایش الگوریتم قدیمی ‏straw‏ نتیجه‌ی تقریباً یکسانی داد (۴۰۰ در برابر ۴۱۱ برای وزن ۲.۰)، چون همه‌ی آیتم‌ها جز یکی هم‌وزن بودند. ایراد مستند ‏straw‏ قدیمی (جابه‌جایی بین آیتم‌های دیگرِ همان ‏bucket‏ هنگام تغییر وزن یکی) در ‏bucket‏های با وزن‌های نابرابر بروز می‌کند. با این حال، ‏straw2‏ تنها الگوریتمی است که این ویژگی را برای هر توزیع وزنی تضمین می‌کند.

💡 نکته

پیش از هر تغییر وزن یا افزودن میزبان روی کلاستر واقعی، همین مقایسه را آفلاین انجام دهید: ‏ceph osd getmap -o om‏، سپس ‏osdmaptool om --export-crush cm‏، تغییر روی ‏cm‏ با ‏crushtool‏، ‏osdmaptool om --import-crush cm2‏ روی یک کپی، و مقایسه‌ی دو خروجی ‏--test-map-pgs-dump‏. در این حالت ‏--mark-up-in‏ لازم نیست، چون ‏osdmap‏ واقعی وضعیت ‏up‏ و ‏in‏ را دارد.

شکل ۸-۹: تغییر وزن CRUSH یک OSD (osd.۵ روی host0 ) از ۱.۰ به پنج مقدار و دسته‌بندی جای‌گذاری‌های جابه‌جاشده: آبی خود osd.5 ، زرد دیگر OSD های host0 که با تغییر وزن میزبان سهم تازه می‌گیرند یا از دست می‌دهند، قرمز میزبان‌های نامربوط. چپ straw2 و راست straw قدیمی، با ۸۱۹۲ PG و اندازه‌گیری osdmaptool
شکل ۸-۹: تغییر وزن ‏CRUSH‏ یک ‏OSD‏ (osd.۵ روی ‏host0‏) از ۱.۰ به پنج مقدار و دسته‌بندی جای‌گذاری‌های جابه‌جاشده: آبی خود ‏osd.5‏، زرد دیگر ‏OSD‏های ‏host0‏ که با تغییر وزن میزبان سهم تازه می‌گیرند یا از دست می‌دهند، قرمز میزبان‌های نامربوط. چپ ‏straw2‏ و راست ‏straw‏ قدیمی، با ۸۱۹۲ ‏PG‏ و اندازه‌گیری ‏osdmaptool‏

۸-۸ احراز هویت و مجوزها#

۸-۸-۱ ‏cephx‏#

‏Ceph‏ از پروتکل ‏cephx‏ برای احراز هویت دوطرفه بین کلاینت‌ها و ‏daemon‏ها استفاده می‌کند. ایده‌ی آن شبیه ‏Kerberos‏ است: کلید مخفی هرگز روی شبکه ارسال نمی‌شود و ‏MON‏ به‌عنوان مرجع، ‏ticket‏های زمان‌دار صادر می‌کند که کلاینت با آن‌ها مستقیماً با ‏OSD‏ها و ‏MDS‏ها صحبت می‌کند.

شکل ۸-۱۰: جریان cephx : کلاینت هویتش را به MON ثابت می‌کند، ticket می‌گیرد و با آن مستقیماً با OSD کار می‌کند
شکل ۸-۱۰: جریان ‏cephx‏: کلاینت هویتش را به ‏MON‏ ثابت می‌کند، ‏ticket‏ می‌گیرد و با آن مستقیماً با ‏OSD‏ کار می‌کند
◆ عمیق‌تر

اگر هر درخواست ‏I/O‏ برای بررسی مجوز به ‏MON‏ می‌رفت، ‏MON‏ها گلوگاه می‌شدند. با ‏ticket‏، ‏OSD‏ فقط با کلید سرویس خودش (که از ‏MON‏ گرفته و دوره‌ای چرخانده می‌شود) اعتبار ‏ticket‏ را بررسی می‌کند. به همین دلیل اختلاف ساعت بین گره‌ها می‌تواند به خطاهای احراز هویت منجر شود: ‏ticket‏ها زمان انقضا دارند.

۸-۸-۲ کاربران و ‏capability‏ها#

هر هویت در ‏Ceph‏ به شکل ‏type.id‏ است (مثلاً ‏client.backup‏ یا ‏osd.3‏) و یک کلید و مجموعه‌ای ‏capability‏ دارد. ‏capability‏ها برای هر نوع ‏daemon‏ جداگانه تعریف می‌شوند:

bashReef+
ceph auth ls
ceph auth get client.admin
# کاربر فقط برای RBD در یک pool
ceph auth get-or-create client.vmhost mon 'profile rbd' osd 'profile rbd pool=vms' mgr 'profile rbd pool=vms'
# کاربر فقط‌خواندنی برای پایش
ceph auth get-or-create client.monitor mon 'allow r' mgr 'allow r'
# کاربر محدود به یک namespace
ceph auth get-or-create client.tenant1 mon 'allow r' osd 'allow rw pool=shared namespace=tenant1'
ceph auth caps client.vmhost mon 'profile rbd' osd 'profile rbd pool=vms, profile rbd-read-only pool=images'
ceph auth rm client.old-app
💡 نکته

همیشه از ‏profile‏ها (‏profile rbd‏، ‏profile rbd-read-only‏، ‏profile crash‏، ‏profile bootstrap-osd‏) به جای نوشتن دستی ‏capability‏های سطح پایین استفاده کنید. ‏profile‏ها ‏capability‏های لازم برای عملیاتی مثل ‏blocklist‏ کردن کلاینت قبلی (برای قفل ‏exclusive-lock‏ در ‏RBD‏) را هم دارند که در ‏capability‏های دستی معمولاً فراموش می‌شوند.

۸-۸-۳ چرخش کلید و رمزنگاری#

  • کلید یک کاربر را می‌توان با ‏ceph auth rotate‏ (در نسخه‌های جدید) یا ساختن کلید تازه و جایگزینی تدریجی چرخاند.
  • ‏Umbrella cephx‏ را سخت‌تر کرد: رمز تازه‌ی ‏AES256KRB5‏ به جای انواع کلید قدیمی‌تر پیش‌فرض شد و چرخش آنلاین کلیدها اضافه شد. پیش از ارتقا، سازگاری کلاینت‌های قدیمی (به‌ویژه کلاینت‌های هسته) را بررسی کنید.
  • برای رمزنگاری ترافیک شبکه، حالت ‏secure‏ در ‏msgr2‏ را فعال کنید (‏ms_cluster_mode‏، ‏ms_service_mode‏، ‏ms_client_mode‏). این حالت سربار ‏CPU‏ دارد؛ آن را روی سخت‌افزار واقعی اندازه بگیرید.
bashReef+
ceph config set global ms_client_mode secure
ceph config set global ms_service_mode secure
ceph config get mon ms_cluster_mode

۸-۹ مسیر ‏I/O‏ و ‏PG‏ها#

۸-۹-۱ مسیر یک نوشتن#

شکل ۸-۱۱: نوشتن در pool تکثیری: کلاینت فقط با primary صحبت می‌کند؛ primary به‌طور موازی به ثانویه‌ها تکثیر می‌کند و پس از commit همه‌ی نسخه‌ها ack می‌دهد
شکل ۸-۱۱: نوشتن در ‏pool‏ تکثیری: کلاینت فقط با ‏primary‏ صحبت می‌کند؛ ‏primary‏ به‌طور موازی به ثانویه‌ها تکثیر می‌کند و پس از ‏commit‏ همه‌ی نسخه‌ها ‏ack‏ می‌دهد
  1. ۱. کلاینت (‏librados‏) با نقشه‌ای که دارد، ‏PG‏ و ‏primary‏ را محاسبه می‌کند.
  2. ۲. درخواست را فقط به ‏primary‏ می‌فرستد.
  3. ۳. ‏primary‏ عملیات را در ‏log‏ آن ‏PG‏ ثبت و به‌طور موازی به ثانویه‌ها می‌فرستد.
  4. ۴. هر ‏OSD‏ تراکنش را در ‏BlueStore commit‏ می‌کند.
  5. ۵. ‏primary‏ پس از دریافت تأیید از همه‌ی ‏OSD‏های ‏acting set‏ به کلاینت ‏ack‏ می‌دهد.

بنابراین تأخیر نوشتن برابر کندترین ‏OSD‏ در ‏acting set‏ به‌علاوه‌ی دو رفت‌وبرگشت شبکه است. همین دلیل است که یک دیسک کند کل کلاستر را کند می‌کند (فصل ۱۷). در ‏pool EC‏، ‏primary‏ داده را به ‏k‏ قطعه‌ی داده و ‏m‏ قطعه‌ی ‏parity‏ تقسیم و هر قطعه را به یک ‏OSD‏ می‌فرستد (فصل ۱۳).

✎ یادداشت

خواندن به‌طور پیش‌فرض از ‏primary‏ انجام می‌شود. کلاینت‌های ‏RBD‏ و ‏RGW‏ می‌توانند در شرایط خاص از نزدیک‌ترین نسخه بخوانند (مثلاً گزینه‌ی ‏rbd_read_from_replica_policy‏ با مقادیر ‏balance‏ و ‏localize‏)، که در کلاسترهای گسترده بین سایت‌ها مفید است.

۸-۹-۲ حالت‌های ‏PG‏#

شکل ۸-۱۲: حالت‌های اصلی PG و گذارهای رایج بین آن‌ها
شکل ۸-۱۲: حالت‌های اصلی ‏PG‏ و گذارهای رایج بین آن‌ها
حالتمعنینگران‌کننده؟
‏active+clean‏همه‌ی نسخه‌ها موجود و همگامخیر؛ حالت مطلوب
‏peering‏‏OSD‏ها در حال توافق بر وضعیت ‏PG‏فقط اگر طول بکشد
‏degraded‏برخی اشیا نسخه‌ی کافی ندارندتا بازیابی کامل، ریسک بالاتر
‏recovering‏همگام‌سازی از روی ‏log‏طبیعی پس از خرابی کوتاه
‏backfilling, backfill_wait‏کپی کامل ‏PG‏ به ‏OSD‏ جدیدطبیعی پس از تغییر نقشه
‏remapped‏‏acting set‏ موقتاً با ‏up set‏ فرق داردطبیعی در حین ‏backfill‏
‏undersized‏تعداد ‏OSD‏ها کمتر از ‏size‏بررسی شود
‏inconsistent‏‏scrub‏ ناسازگاری پیدا کردهبله؛ باید بررسی و ‏repair‏ شود
‏incomplete, down‏نسخه‌ی معتبر کافی برای فعال شدن نیستبله؛ ‏I/O‏ این ‏PG‏ متوقف است
‏stale‏‏primary‏ مدتی آمار نفرستادهبله؛ ‏OSD‏ها را بررسی کنید
bashReef+
ceph pg stat
ceph pg dump_stuck inactive
ceph pg dump_stuck unclean
ceph pg ls degraded
ceph pg 3.1f query | jq '.recovery_state'
✚ عیب‌یابی

برای ‏PG‏ی که در ‏peering‏ یا ‏incomplete‏ گیر کرده، خروجی ‏ceph pg <id> query‏ را از پایین به بالا بخوانید. بخش ‏recovery_state‏ معمولاً می‌گوید منتظر کدام ‏OSD‏ است (‏blocked_by‏ یا ‏down_osds_we_would_probe‏). اگر آن ‏OSD‏ برای همیشه از دست رفته است، تصمیم‌هایی مثل ‏ceph osd lost‏ یا علامت‌گذاری اشیای گم‌شده ریسک از دست رفتن داده دارند؛ قبل از اجرا فصل ۱۷ را بخوانید.

۸-۹-۳ ‏min_size‏ و ‏size‏#

‏size‏ تعداد نسخه‌هاست و ‏min_size‏ حداقل نسخه‌ی سالمی که ‏PG‏ برای پذیرش ‏I/O‏ لازم دارد. برای ‏pool‏ تکثیری سه‌تایی، ‏min_size=2‏ استاندارد است. کاهش آن به ۱ کلاستر را در حین خرابی «در دسترس» نگه می‌دارد اما ریسک از دست رفتن داده را به‌شدت بالا می‌برد، چون یک نوشتن ممکن است فقط روی یک دیسک وجود داشته باشد.

۸-۱۰ ‏pool‏ روی ‏OSD‏های مشخص#

راه امروزی قرار دادن یک ‏pool‏ روی گروه خاصی از ‏OSD‏ها، کلاس دستگاه است. اگر کلاس‌های پیش‌فرض کافی نیستند (مثلاً می‌خواهید دیسک‌های ‏NVMe‏ یک مدل خاص را برای یک مستأجر کنار بگذارید)، کلاس دلخواه تعریف کنید:

bashReef+
for id in 30 31 32 33 34 35; do
  ceph osd crush rm-device-class osd.$id
  ceph osd crush set-device-class fastnvme osd.$id
done
ceph osd crush rule create-replicated fast-host default host fastnvme
ceph osd pool create tenant-a-db 64 64 replicated fast-host
ceph osd pool application enable tenant-a-db rbd
ceph pg ls-by-pool tenant-a-db | head
💡 نکته

در ‏spec OSD‏ در ‏cephadm‏ هم می‌توانید ‏crush_device_class‏ را تعیین کنید تا ‏OSD‏های تازه از ابتدا کلاس درست را بگیرند و نیازی به اجرای دوباره‌ی دستورهای بالا نباشد.

نکات ‏SRE‏ این فصل
  • محل داده محاسبه می‌شود، نه جست‌وجو؛ ‏ceph osd map‏ ابزار اول شماست.
  • فقط دامنه‌های خرابی واقعی را در ‏CRUSH‏ تعریف کنید و مکان میزبان را در ‏spec‏ بدهید.
  • قبل از اعمال هر نقشه‌ی دستی، ‏crushtool --test‏ اجرا کنید.
  • برای ‏SSD/HDD‏ از کلاس دستگاه استفاده کنید، نه ‏root‏های جدا.
  • تغییر ‏tunable‏ یعنی جابه‌جایی بزرگ؛ سازگاری کلاینت‌ها را با ‏ceph features‏ بررسی کنید.
  • ‏MON‏ فرد (۳ یا ۵)، روی ‏SSD‏، با ساعت دقیق.
  • ‏capability‏ با ‏profile‏ و حداقل دسترسی؛ ‏min_size‏ را پایین نیاورید.

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

  • کلاس دستگاه و ‏shadow tree‏ (‏Luminous‏) جایگزین ‏root‏های جداگانه‌ی ‏SSD/HDD‏ شد که در منابع قدیمی دیده می‌شود.
  • ‏upmap‏ و ‏balancer‏: جای ‏reweight‏ دستی را گرفته‌اند (فصل ۷).
  • قانون ‏MSR‏ (‏Squid‏) برای ‏EC‏ انعطاف بیشتری می‌دهد.
  • ‏cephx‏ در ‏Umbrella‏: رمز ‏AES256KRB5‏ به‌عنوان پیش‌فرض و چرخش آنلاین کلید.
  • پشتیبان ‏RocksDB MON‏ (‏Umbrella‏).
  • ‏read balancer‏ (‏Reef‏) برای توزیع ‏primary‏ها.

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

  1. ۱. برای سه شیء مختلف ‏ceph osd map‏ بگیرید و مسیر ‏PG‏ و ‏OSD‏ها را با ‏ceph pg map‏ مقایسه کنید.
  2. ۲. یک سلسله‌مراتب دو ‏rack‏ی بسازید، میزبان‌ها را جابه‌جا کنید و قانون ‏rep-rack‏ را روی یک ‏pool‏ آزمایشی اعمال کنید. حجم جابه‌جایی را مشاهده کنید.
  3. ۳. نقشه‌ی ‏CRUSH‏ را استخراج کنید، قانون دو ‏datacenter‏ی را اضافه کنید و پیش از اعمال، با ‏crushtool --test‏ توزیع را بررسی کنید.
  4. ۴. کاربری با ‏profile rbd‏ فقط برای یک ‏pool‏ بسازید و ثابت کنید به ‏pool‏ دیگر دسترسی ندارد.
  5. ۵. یک ‏OSD‏ را متوقف کنید و حالت‌های ‏PG‏ را در طول زمان با ‏ceph pg stat‏ ثبت کنید.
  6. ۶. با کلاس دستگاه دلخواه یک ‏pool‏ روی سه ‏OSD‏ خاص بسازید و توزیع ‏PG‏ها را بررسی کنید.

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

  1. ۱. دو مرحله‌ی نگاشت شیء به ‏OSD‏ کدام‌اند و چرا ‏PG‏ لازم است؟
  2. ۲. تفاوت ‏up set‏ و ‏acting set‏ چیست؟
  3. ۳. مزیت ‏straw2‏ نسبت به الگوریتم‌های قبلی چیست؟
  4. ۴. چرا تغییر ‏tunable‏ها باعث جابه‌جایی داده می‌شود و پیش از آن چه چیزی را باید بررسی کرد؟
  5. ۵. چرا چهار ‏MON‏ از سه ‏MON‏ تحمل خرابی بیشتری ندارد؟
  6. ۶. ‏OSD‏ها نقشه‌ی جدید را از کجا دریافت می‌کنند؟
  7. ۷. چرا ‏cephx‏ از ‏ticket‏ استفاده می‌کند؟
  8. ۸. چرا تأخیر نوشتن به کندترین ‏OSD acting set‏ بستگی دارد؟
  9. ۹. حالت‌های ‏incomplete‏ و ‏inconsistent‏ چه تفاوتی دارند؟
  10. ۱۰. راه توصیه‌شده برای قرار دادن یک ‏pool‏ روی ‏OSD‏های خاص چیست؟

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

۸-۱۴ منابع این فصل#

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

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