سلام، من سرگئی الانتسف هستم، توسعه می دهم
ابتدا چند اصطلاح را معرفی می کنیم:
- VIP (IP مجازی) - آدرس IP متعادل کننده
- سرور، باطن، نمونه - یک ماشین مجازی که یک برنامه را اجرا می کند
- RIP (IP واقعی) - آدرس IP سرور
- بررسی سلامت - بررسی آمادگی سرور
- منطقه دسترسی، AZ - زیرساخت ایزوله در یک مرکز داده
- منطقه - اتحادیه ای از AZ های مختلف
متعادل کننده های بار سه کار اصلی را حل می کنند: آن ها خود تعادل را انجام می دهند، تحمل خطای سرویس را بهبود می بخشند و مقیاس بندی آن را ساده می کنند. تحمل خطا از طریق مدیریت خودکار ترافیک تضمین می شود: متعادل کننده وضعیت برنامه را نظارت می کند و مواردی را که از بررسی زنده بودن عبور نمی کنند را از تعادل حذف می کند. مقیاس بندی با توزیع یکنواخت بار در بین نمونه ها و همچنین به روز رسانی لیست نمونه ها در حال پرواز تضمین می شود. اگر تعادل به اندازه کافی یکنواخت نباشد، برخی از نمونه ها باری دریافت می کنند که از حد ظرفیت آنها فراتر می رود و خدمات کمتر قابل اعتماد می شود.
یک متعادل کننده بار اغلب توسط لایه پروتکل از مدل OSI که روی آن اجرا می شود طبقه بندی می شود. Cloud Balancer در سطح TCP کار می کند که مربوط به لایه چهارم L4 است.
بیایید به بررسی کلی معماری Cloud balancer بپردازیم. ما به تدریج سطح جزئیات را افزایش خواهیم داد. اجزای متعادل کننده را به سه دسته تقسیم می کنیم. کلاس config plane مسئول تعامل کاربر است و وضعیت هدف سیستم را ذخیره می کند. صفحه کنترل وضعیت فعلی سیستم را ذخیره میکند و سیستمهایی را از کلاس صفحه داده مدیریت میکند، که مستقیماً مسئول انتقال ترافیک از مشتریان به نمونههای شما هستند.
صفحه داده
ترافیک به دستگاه های گران قیمتی به نام روتر مرزی ختم می شود. برای افزایش تحمل خطا، چندین دستگاه به طور همزمان در یک مرکز داده کار می کنند. در مرحله بعد، ترافیک به سمت متعادل کننده ها می رود، که آدرس های IP anycast را از طریق BGP برای مشتریان به همه AZ ها اعلام می کنند.
ترافیک از طریق ECMP منتقل می شود - این یک استراتژی مسیریابی است که طبق آن می توان چندین مسیر به همان اندازه خوب به سمت هدف وجود داشت (در مورد ما، هدف آدرس IP مقصد خواهد بود) و بسته ها را می توان در امتداد هر یک از آنها ارسال کرد. ما همچنین از کار در چندین منطقه در دسترس مطابق با طرح زیر پشتیبانی می کنیم: ما در هر منطقه یک آدرس را تبلیغ می کنیم، ترافیک به نزدیکترین منطقه می رود و از محدوده آن فراتر نمی رود. بعداً در پست با جزئیات بیشتری به اتفاقاتی که برای ترافیک می افتد نگاه خواهیم کرد.
صفحه پیکربندی
جزء کلیدی صفحه پیکربندی API است که از طریق آن عملیات اساسی با متعادل کننده ها انجام می شود: ایجاد، حذف، تغییر ترکیب نمونه ها، به دست آوردن نتایج بررسی سلامت و غیره. از یک طرف، این یک API REST است و در دیگر، ما در Cloud اغلب از چارچوب gRPC استفاده میکنیم، بنابراین REST را به gRPC ترجمه میکنیم و سپس فقط از gRPC استفاده میکنیم. هر درخواستی منجر به ایجاد یک سری وظایف غیرهمزمان ناهمزمان می شود که روی یک استخر مشترک از کارگران Yandex.Cloud اجرا می شوند. وظایف به گونه ای نوشته شده اند که می توان آنها را در هر زمان به حالت تعلیق درآورد و سپس دوباره راه اندازی کرد. این امر مقیاس پذیری، تکرارپذیری و ثبت عملیات را تضمین می کند.
در نتیجه، وظیفه از API درخواستی را به کنترل کننده سرویس متعادل کننده می دهد که در Go نوشته شده است. می تواند متعادل کننده ها را اضافه و حذف کند، ترکیب پشتیبان ها و تنظیمات را تغییر دهد.
این سرویس وضعیت خود را در پایگاه داده Yandex ذخیره می کند، یک پایگاه داده مدیریت شده توزیع شده که به زودی می توانید از آن استفاده کنید. همانطور که قبلاً در Yandex.Cloud وجود دارد
بیایید به کنترل کننده متعادل کننده برگردیم. وظیفه آن ذخیره اطلاعات مربوط به متعادل کننده و ارسال یک وظیفه برای بررسی آمادگی ماشین مجازی به کنترل کننده سلامت است.
کنترلر چک سلامت
درخواستهای تغییر قوانین چک را دریافت میکند، آنها را در YDB ذخیره میکند، وظایف را بین گرههای healtcheck توزیع میکند و نتایج را جمعآوری میکند، که سپس در پایگاه داده ذخیره میشود و به کنترلکننده loadbalancer ارسال میشود. به نوبه خود، درخواستی برای تغییر ترکیب خوشه در صفحه داده به گره loadbalancer ارسال می کند که در زیر به آن خواهم پرداخت.
بیایید بیشتر در مورد بررسی های سلامت صحبت کنیم. آنها را می توان به چندین کلاس تقسیم کرد. ممیزی معیارهای موفقیت متفاوتی دارد. بررسی های TCP باید با موفقیت یک اتصال را در مدت زمان مشخصی برقرار کنند. بررسی های HTTP هم به اتصال موفق و هم به پاسخ با کد وضعیت 200 نیاز دارد.
همچنین، چک ها در کلاس عمل متفاوت هستند - آنها فعال و غیرفعال هستند. چکهای غیرفعال به سادگی آنچه را که در ترافیک اتفاق میافتد نظارت میکنند بدون اینکه اقدام خاصی انجام دهند. این کار روی L4 خیلی خوب کار نمی کند زیرا به منطق پروتکل های سطح بالاتر بستگی دارد: در L4 هیچ اطلاعاتی در مورد مدت زمان عملیات یا اینکه آیا تکمیل اتصال خوب یا بد بوده است وجود ندارد. بررسیهای فعال به تعادلگر نیاز دارند تا درخواستهایی را به هر نمونه سرور ارسال کند.
اکثر بار متعادل کننده ها خودشان چک های زنده بودن را انجام می دهند. در Cloud تصمیم گرفتیم این قسمتهای سیستم را جدا کنیم تا مقیاسپذیری را افزایش دهیم. این رویکرد به ما این امکان را میدهد که با حفظ تعداد درخواستهای بررسی سلامت به سرویس، تعداد متعادلکنندهها را افزایش دهیم. بررسیها توسط گرههای بررسی سلامت جداگانه انجام میشوند، که در سراسر آنها هدفهای چک خرد شده و تکرار میشوند. شما نمی توانید از یک میزبان بررسی کنید، زیرا ممکن است شکست بخورد. سپس ما وضعیت نمونه هایی را که او بررسی کرده است نخواهیم گرفت. ما هر یک از نمونهها را از حداقل سه گره بررسی سلامت بررسی میکنیم. ما اهداف بررسی بین گرهها را با استفاده از الگوریتمهای هش ثابت تقسیم میکنیم.
جدا کردن تعادل و بررسی سلامت می تواند منجر به مشکلات شود. اگر گره Healthcheck با دور زدن تعادلدهنده (که در حال حاضر به ترافیک سرویس نمیدهد) درخواستهایی را به نمونه ارسال کند، وضعیت عجیبی پیش میآید: به نظر میرسد منبع زنده است، اما ترافیک به آن نمیرسد. ما این مشکل را به این ترتیب حل می کنیم: ما تضمین می کنیم که ترافیک بررسی سلامت را از طریق متعادل کننده ها آغاز کنیم. به عبارت دیگر، طرح جابجایی بستهها با ترافیک از مشتریان و بررسیهای سلامت حداقل متفاوت است: در هر دو مورد، بستهها به متعادلکنندهها میرسند که آنها را به منابع هدف تحویل میدهند.
تفاوت این است که مشتریان برای VIP درخواست می کنند، در حالی که بررسی سلامت به هر RIP درخواست می کند. یک مشکل جالب اینجا پیش می آید: ما به کاربران خود این فرصت را می دهیم که منابعی را در شبکه های IP خاکستری ایجاد کنند. بیایید تصور کنیم که دو مالک مختلف ابری وجود دارند که خدمات خود را در پشت متعادل کننده ها پنهان کرده اند. هر کدام از آنها منابعی در زیر شبکه 10.0.0.1/24 با آدرس های یکسان دارند. شما باید بتوانید به نحوی آنها را متمایز کنید و در اینجا باید در ساختار شبکه مجازی Yandex.Cloud غوطه ور شوید. بهتر است جزییات بیشتر را در
گره های بررسی سلامت با استفاده از آدرس های به اصطلاح شبه IPv6 با متعادل کننده ها تماس می گیرند. شبه آدرس یک آدرس IPv6 با آدرس IPv4 و شناسه زیرشبکه کاربر در داخل آن تعبیه شده است. ترافیک به متعادل کننده می رسد، که آدرس منبع IPv4 را از آن استخراج می کند، IPv6 را با IPv4 جایگزین می کند و بسته را به شبکه کاربر ارسال می کند.
ترافیک معکوس نیز به همین صورت پیش می رود: متعادل کننده می بیند که مقصد یک شبکه خاکستری از Healthcheckers است و IPv4 را به IPv6 تبدیل می کند.
VPP - قلب صفحه داده
متعادل کننده با استفاده از فناوری پردازش بسته برداری (VPP)، چارچوبی از سیسکو برای پردازش دسته ای ترافیک شبکه پیاده سازی شده است. در مورد ما، چارچوب در بالای کتابخانه مدیریت دستگاه شبکه فضای کاربر - کیت توسعه هواپیمای داده (DPDK) کار می کند. این کار عملکرد بالای پردازش بسته را تضمین می کند: وقفه های بسیار کمتری در هسته رخ می دهد و هیچ سوئیچ زمینه ای بین فضای هسته و فضای کاربر وجود ندارد.
VPP از این هم فراتر می رود و با ترکیب بسته ها در دسته ها، عملکرد بیشتری را از سیستم خارج می کند. دستاوردهای عملکرد ناشی از استفاده تهاجمی از حافظه پنهان در پردازنده های مدرن است. هم از حافظه پنهان داده ها استفاده می شود (بسته ها در "بردارها" پردازش می شوند، داده ها نزدیک به یکدیگر هستند) و هم حافظه پنهان دستورالعمل: در VPP، پردازش بسته از یک نمودار پیروی می کند که گره های آن حاوی توابعی هستند که وظیفه مشابهی را انجام می دهند.
برای مثال، پردازش بستههای IP در VPP به ترتیب زیر انجام میشود: ابتدا سرصفحههای بسته در گره تجزیه تجزیه میشوند و سپس به گره ارسال میشوند که بستهها را طبق جداول مسیریابی بیشتر ارسال میکند.
کمی هاردکور نویسندگان VPP سازش در استفاده از حافظه پنهان پردازنده را تحمل نمی کنند، بنابراین کد معمولی برای پردازش یک برداری از بسته ها حاوی برداری دستی است: یک حلقه پردازش وجود دارد که در آن وضعیتی مانند "ما چهار بسته در صف داریم" پردازش می شود. سپس برای دو نفر یکسان، سپس - برای یک. دستورالعملهای Prefetch اغلب برای بارگذاری دادهها در حافظه پنهان استفاده میشوند تا دسترسی به آنها در تکرارهای بعدی افزایش یابد.
n_left_from = frame->n_vectors;
while (n_left_from > 0)
{
vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);
// ...
while (n_left_from >= 4 && n_left_to_next >= 2)
{
// processing multiple packets at once
u32 next0 = SAMPLE_NEXT_INTERFACE_OUTPUT;
u32 next1 = SAMPLE_NEXT_INTERFACE_OUTPUT;
// ...
/* Prefetch next iteration. */
{
vlib_buffer_t *p2, *p3;
p2 = vlib_get_buffer (vm, from[2]);
p3 = vlib_get_buffer (vm, from[3]);
vlib_prefetch_buffer_header (p2, LOAD);
vlib_prefetch_buffer_header (p3, LOAD);
CLIB_PREFETCH (p2->data, CLIB_CACHE_LINE_BYTES, STORE);
CLIB_PREFETCH (p3->data, CLIB_CACHE_LINE_BYTES, STORE);
}
// actually process data
/* verify speculative enqueues, maybe switch current next frame */
vlib_validate_buffer_enqueue_x2 (vm, node, next_index,
to_next, n_left_to_next,
bi0, bi1, next0, next1);
}
while (n_left_from > 0 && n_left_to_next > 0)
{
// processing packets by one
}
// processed batch
vlib_put_next_frame (vm, node, next_index, n_left_to_next);
}
بنابراین، Healthchecks از طریق IPv6 با VPP صحبت می کند، که آنها را به IPv4 تبدیل می کند. این کار توسط یک گره در نمودار انجام می شود که آن را NAT الگوریتمی می نامیم. برای ترافیک معکوس (و تبدیل از IPv6 به IPv4) همان گره الگوریتمی NAT وجود دارد.
ترافیک مستقیم از مشتریان متعادل کننده از طریق گره های نمودار می رود که خود تعادل را انجام می دهند.
اولین گره جلسات چسبنده است. هش را ذخیره می کند
هش 5 تایی به ما کمک می کند تا محاسبات کمتری را در گره هش ثابت بعدی انجام دهیم و همچنین تغییرات فهرست منابع را در پشت متعادل کننده بهتر مدیریت کنیم. هنگامی که بسته ای که هیچ جلسه ای برای آن وجود ندارد به متعادل کننده می رسد، به گره هش سازگار ارسال می شود. این جایی است که تعادل با استفاده از هش ثابت اتفاق می افتد: ما یک منبع را از لیست منابع "زنده" موجود انتخاب می کنیم. در مرحله بعد، بسته ها به گره NAT ارسال می شوند که در واقع آدرس مقصد را جایگزین می کند و جمع های چک را دوباره محاسبه می کند. همانطور که می بینید، ما از قوانین VPP پیروی می کنیم - like to like، گروه بندی محاسبات مشابه برای افزایش کارایی حافظه پنهان پردازنده.
هش کردن مداوم
چرا آن را انتخاب کردیم و حتی چیست؟ ابتدا بیایید کار قبلی را در نظر بگیریم - انتخاب یک منبع از لیست.
با هش ناسازگار، هش بسته ورودی محاسبه می شود و یک منبع از لیست با باقی مانده تقسیم این هش بر تعداد منابع انتخاب می شود. تا زمانی که لیست بدون تغییر باقی بماند، این طرح به خوبی کار میکند: ما همیشه بستههایی را با همان 5 تاپل به یک نمونه ارسال میکنیم. برای مثال، اگر برخی از منابع به بررسیهای سلامت پاسخ ندهند، برای بخش قابل توجهی از هشها، انتخاب تغییر خواهد کرد. اتصالات TCP مشتری قطع می شود: بسته ای که قبلاً به نمونه A رسیده است ممکن است شروع به رسیدن به نمونه B کند که با جلسه این بسته آشنا نیست.
هش کردن مداوم مشکل توصیف شده را حل می کند. ساده ترین راه برای توضیح این مفهوم این است: تصور کنید حلقه ای دارید که منابع را بر اساس هش (مثلاً توسط IP:port) در آن توزیع می کنید. انتخاب یک منبع به معنای چرخاندن چرخ توسط یک زاویه است که توسط هش بسته تعیین می شود.
این امر توزیع مجدد ترافیک را زمانی که ترکیب منابع تغییر می کند به حداقل می رساند. حذف یک منبع فقط بر بخشی از حلقه هش ثابت که منبع در آن قرار داشته است تأثیر می گذارد. افزودن یک منبع نیز توزیع را تغییر می دهد، اما ما یک گره جلسات چسبنده داریم که به ما امکان می دهد جلسات از قبل ایجاد شده را به منابع جدید تغییر ندهیم.
ما بررسی کردیم که چه اتفاقی برای ترافیک مستقیم بین متعادل کننده و منابع می افتد. حالا بیایید به ترافیک برگشتی نگاه کنیم. از همان الگوی ترافیک چک پیروی می کند - از طریق NAT الگوریتمی، یعنی از طریق NAT 44 معکوس برای ترافیک مشتری و از طریق NAT 46 برای ترافیک بررسی سلامت. ما به طرح خودمان پایبند هستیم: ترافیک بررسی سلامت و ترافیک کاربر واقعی را یکسان می کنیم.
Loadbalancer-node و اجزای مونتاژ شده
ترکیب متعادل کننده ها و منابع در VPP توسط سرویس محلی - loadbalancer-node گزارش می شود. این برنامه در جریان رویدادهای لودبالانر-کنترلر مشترک است و قادر است تفاوت بین حالت VPP فعلی و حالت هدف دریافتی از کنترل کننده را رسم کند. ما یک سیستم بسته دریافت میکنیم: رویدادهای API به کنترلکننده متعادلکننده میآیند، که وظایفی را به کنترلکننده بررسی سلامت اختصاص میدهد تا زنده بودن منابع را بررسی کند. این به نوبه خود وظایفی را به Healthcheck-node اختصاص می دهد و نتایج را جمع می کند و پس از آن آنها را به کنترل کننده تعادل ارسال می کند. Loadbalancer-node در رویدادهای کنترل کننده مشترک می شود و وضعیت VPP را تغییر می دهد. در چنین سیستمی، هر سرویس فقط آنچه را که در مورد خدمات همسایه لازم است می داند. تعداد اتصالات محدود است و ما این توانایی را داریم که به طور مستقل بخش های مختلف را عملیاتی و مقیاس بندی کنیم.
از چه مسائلی اجتناب شد؟
تمام خدمات ما در صفحه کنترل در Go نوشته شده اند و دارای مقیاس بندی و قابلیت اطمینان خوبی هستند. Go کتابخانه های متن باز بسیاری برای ساخت سیستم های توزیع شده دارد. ما به طور فعال از GRPC استفاده میکنیم، همه مؤلفهها شامل پیادهسازی منبع باز کشف سرویس هستند - سرویسهای ما عملکرد یکدیگر را نظارت میکنند، میتوانند ترکیب خود را به صورت پویا تغییر دهند، و ما این را با متعادلسازی GRPC مرتبط کردیم. برای معیارها، ما همچنین از یک راه حل منبع باز استفاده می کنیم. در صفحه داده، ما عملکرد مناسب و ذخیره منابع زیادی داشتیم: معلوم شد که مونتاژ پایهای که بتوانیم به جای یک کارت شبکه آهنی، روی عملکرد یک VPP تکیه کنیم، بسیار دشوار است.
مشکلات و راه حل ها
چه چیزی خیلی خوب کار نکرد؟ Go دارای مدیریت حافظه خودکار است، اما نشت حافظه همچنان اتفاق می افتد. ساده ترین راه برای مقابله با آنها این است که گوروتین ها را اجرا کنید و فراموش کنید که آنها را خاتمه دهید. غذای آماده: مراقب میزان مصرف حافظه برنامه های Go خود باشید. اغلب یک شاخص خوب تعداد گوروتین ها است. در این داستان یک نکته مثبت وجود دارد: در Go دریافت داده های زمان اجرا آسان است - مصرف حافظه، تعداد گوروتین های در حال اجرا و بسیاری از پارامترهای دیگر.
همچنین، Go ممکن است بهترین انتخاب برای تست های عملکردی نباشد. آنها کاملاً پرحرف هستند و رویکرد استاندارد "اجرای همه چیز در CI در یک دسته" برای آنها چندان مناسب نیست. واقعیت این است که تست های عملکردی نیاز به منابع بیشتری دارند و باعث وقفه های زمانی واقعی می شوند. به همین دلیل، آزمایش ها ممکن است با شکست مواجه شوند زیرا CPU با تست های واحد مشغول است. نتیجه گیری: در صورت امکان، تست های "سنگین" را جدا از تست های واحد انجام دهید.
معماری رویداد میکروسرویس پیچیدهتر از یکپارچه است: جمعآوری گزارشها در دهها ماشین مختلف خیلی راحت نیست. نتیجه: اگر میکروسرویس میسازید، فوراً به فکر ردیابی باشید.
برنامه های ما
ما یک متعادل کننده داخلی، یک متعادل کننده IPv6 راه اندازی می کنیم، پشتیبانی از اسکریپت های Kubernetes را اضافه می کنیم، به اشتراک گذاری خدمات خود ادامه می دهیم (در حال حاضر فقط healthcheck-node و healthcheck-ctrl به اشتراک گذاشته می شوند)، بررسی های سلامت جدید اضافه می کنیم، و همچنین جمع بندی هوشمند چک ها را اجرا می کنیم. ما در حال بررسی امکان مستقل کردن خدمات خود هستیم - به طوری که آنها مستقیماً با یکدیگر ارتباط برقرار نمی کنند، بلکه با استفاده از یک صف پیام. اخیراً یک سرویس سازگار با SQS در Cloud ظاهر شده است
اخیراً انتشار عمومی Yandex Load Balancer انجام شد. کاوش کنید
منبع: www.habr.com