ژوپیتر چیست؟

در کافه بازار مهندسان علوم داده و تحلیل‌گران داده در تیم‌های مختلف، تلاش می‌کنند تا با انجام محاسبات تحلیلی بر روی داده‌های محصولی به رشد محصول کمک کنند. آن‌‌ها برای انجام این محاسبات، به زیرساخت‌هایی مانند GPU یا CPU نیاز دارند و در بعضی موارد نیازمند استفاده از ابزارهایی مانند Spark و Flink هستند. ما در تیم زیرساخت کافه بازار برای اینکه به این نیاز پاسخ بدهیم، باید ابزار توسعه کاری ارائه می‌دادیم که قادر به رفع تمامی این نیازها به صورت همزمان باشد. تیم ما از ۵ نفر تشکیل شده است که ماموریت ما، ایجاد ابزارها و روش‌ها، استقرار و نظارت بر آن‌ها در کافه بازار به منظور تسهیل توسعه و نگه‌داری و کاهش هزینه تولید محصول و تضمین پایداری آن می‌باشد.

اعضای تیم زیرساخت
اعضای تیم زیرساخت


در این پست می‌خواهیم راجع به یکی از ابزارهای توسعه کار، ژوپیتر(Jupyter) ، صحبت کنیم که مسئولیت توسعه و نگهداری این ابزار با تیم زیرساخت کافه بازار است.

مهندسان علوم داده و تحلیل‌گران می‌توانند با استفاده از این ابزار، محاسبات داده‌ای خود را با زبان‌ها و زیرساخت‌های مختلف انجام دهند، بدون آن‌که نیازی به راه‌اندازی و نگهداری ابزاری در سطح تیم خود داشته باشند.

در ابتدا چرایی انتخاب این ابزار را بیان می‌کنیم و در ادامه معماری و ساختار آن را بررسی خواهیم کرد.

چرا ژوپیتر؟

مهمترین سوالی که قرار است در این بخش به آن پاسخ داده شود، این است که چرا تصمیم گرفتیم از ژوپیتر استفاده کنیم و نحوه راه‌اندازی آن به چه صورت است؟

ژوپیتر اولین ابزار توسعه کار در کافه بازار نیست و قبل از ژوپیتر، از زپلین (Zeppelin) به عنوان ابزار توسعه کار استفاده می‌شد، که شامل مفسر(interpreter)های اسپارکی به زبان‌های پایتون(Python) و اسکالا (Scala) بود. معماری زپلین به شکلی بود که فقط می‌توانستیم آن را بر روی یک ماشین راه‌اندازی کنیم و مقیاس‌پذیر نبود. در نتیجه با افزایش درخواست‌های کاربران به شدت ناپایدار و به‌طور کامل از دسترس خارج می‌شد. برای حل این مشکل تغییرات زیادی در معماری آن اعمال کردیم تا قابل اجرا بر روی کوبرنتیز باشد و بتوانیم مقیاس آن را به راحتی، بدون نیاز به ماشین دیگری افزایش دهیم. در ابتدا که تعداد کاربران محدود بود، زپلین با ساختار جدید، به خوبی ارائه خدمت می‌کرد، اما پس از افزایش تعداد کاربران، با مشکلات زیادی از جمله دسترس ناپذیری مواجه شد و حتی با افزایش مقیاس آن بر روی کوبرنتیز نیز، این مشکلات برطرف نشد و عملا کاربران نمی‌توانستند با زپلین کار کنند. در نتیجه می‌بایست راه حلی سریع در جهت حل این مشکل ارائه می‌دادیم و نیاز به یک ابزاری پایدار و مقیاس‌پذیر داشتیم. با بررسی‌های انجام شده، زپلین نمی‌توانست نیازهای ما را در زمان رشد و گسترش کاربر و محصول پاسخ دهد، چون حتی با تغییر در ساختار آن و اجرای آن بر روی کوبرنتیز همچنان مشکلات قبلی وجود داشت و با افزایش تعداد درخواست‌ها از دسترس خارج می‌شد. به عبارت دیگر تنها در تعداد درخواست‌ها و کاربران محدود قادر به ارائه سرویس بود. در نهایت با بررسی‌ راه‌حل‌های موجود، تصمیم گرفتیم به ژوپیتر مهاجرت کنیم. ژوپیتر ابزاری است که در صورت پیاده‌سازی درست، بسیار پایدار و به راحتی مقیاس پذیر است.

برای راه‌اندازی ژوپیتر دو گزینه وجود داشت. در گزینه اول هر تیم به صورت مجزا باید ژوپیتر خود را راه‌اندازی می‌کرد و توسعه می‌داد. در گزینه دوم باید یک ژوپیتر مرکزی ایجاد می‌شد که می‌توانست به تمامی نیازهای مختلف تیم‌ها هم‌زمان پاسخ دهد. دو نکته وجود داشت:
۱- هزینه راه‌اندازی ژوپیتر توسط هر تیم چه از لحاظ مالی و چه از لحاظ انسانی بیشتر از داشتن ابزاری متمرکز بود که همه تیم‌ها بتوانند از آن استفاده کنند.
۲- در صورت راه‌اندازی ژوپیتر به صورت مرکزی تیم‌ها می‌توانستند از کل توان پردازشی موجود استفاده کنند و اگر این ابزار به صورت تیمی توسعه می‌یافت هر تیم فقط محدود به استفاده از منابع تیم خود بود.
با توجه به این دو نکته، تصمیم گرفتیم ابزاری ارائه دهیم که بتواند به تمام نیازهای متخصصان داده در تیم‌های مختلف، هم‌زمان پاسخگو باشد. با ساخت چنین ابزاری، تیم‌ها دیگر درگیر نگهداری و توسعه آن نبودند و تمرکزشان بر روی ماموریت‌های خودشان بود. در نتیجه با ارائه این راه حل در تمامی هزینه‌ها اعم از هزینه‌های مالی و انسانی صرفه‌جویی کردیم.

معماری ژوپیتر چگونه است؟

همانطور که اشاره کردیم برای راه‌اندازی ژوپیتر باید طوری عمل می‌کردیم که از طریق آن بتوانیم به زیرساخت‌های موجودمان با زبان‌ها و ابزارهای مختلف دسترسی پیدا کنیم. برای هر کاربر در زمان شروع کار با ژوپیتر یک سرور اختصاص می‌یابد که با کاربر در تعامل است و درخواست‌های کاربر را مدیریت می‌کند. کاربران پس از انتخاب کرنل مورد نظر، می‌توانند محاسبات خود را با زبان انتخابی و بر روی زیرساخت انتخابی اجرا کنند.

برای مدیریت دسترسی‌ها و احراز هویت کاربر چالش‌هایی وجود داشت، از جمله این‌که بعد از احراز هویت، کاربر بتواند با استفاده از یک ارتباط امن با سرور خود در تعامل باشد از سوی دیگر می‌بایستی این ارتباطات با سرور پایدار بوده و کاربر به راحتی و بدون قطعی ارتباطاتش کدهای خود را اجرا کند. در ابتدا از ingress خود بازار برای این قسمت استفاده کردیم پس از مدتی کاربران از ریستارت شدن ارتباطات خود شکایت می‌کردند پس از بررسی‌های انجام شده به این نکته پی بردیم که در صورت هر گونه عملیات بر روی ingress بازار ارتباطات کاربران با ژوپیتر نیز دچار اختلال می‌شد، در نتیجه برآن شدیم تا برای حل این چالش‌ها از یک nginx استفاده کنیم که مستقل از ingress خود بازار می‌باشد و درخواست‌های کاربران را به پروکسی ژوپیتر ارسال می‌کند و این ارتباطات را مدیریت می‌کند.

چالش دیگری که با آن مواجه بودیم طریقه نگهداری کدهای کاربران تحت ژوپیتر نوتبوک بود, برای این قسمت از PVC کوبرنتیز استفاده کرده‌ایم که هنگام ایجاد سرور برای کاربر این PVC ها به آن متصل می‌شوند. همچنین در هر لحظه که کاربر در حال کدزنی است از نوتبوک‌هایش به فاصله زمانی مشخص بک‌آپ گرفته می‌شود تا در صورتی که ارتباطاتش دچار مشکل شد، نوت‌بوکهایش از بین نروند.

شکل زیر معماری ژوپیتر کافه بازار را نمایش می‌دهد، که شامل دو قسمت اصلی کوبرنتیز و DockerSwarm می‌باشد. در ادامه هر قسمت را با جزییات بیشتری بررسی خواهیم کرد.

معماری ژوپیتر
معماری ژوپیتر

بخش Kubernetes:

این قسمت واحدهایی را که کاربر با آن درگیر است مدیریت می‌کند که مهم‌ترین آن مدیریت هاب (Hub) است. از هاب برای پشتیبانی چند کاربره بودن ژوپیتر و ساخت سرور استفاده می‌شود. هر کاربر برای تعامل و اجرای کدهای خود نیازمند این است تا یک سرور ژوپیتر در اختیار داشته باشد. مراحل زیر برای ساخت سرور کاربر، طی می‌شود:

کاربر درخواست خود را به ژوپیتر برای ایجاد سرور ارسال می‌کند.ین درخواست به وسیله یک Nginx ای که بر روی یک ماشین مجازی قرار دارد مدیریت می‌شود و آن به یک پروکسی(Proxy) که تمامی درخواست‌ها را به سمت هاب ارسال می‌کند متصل است. پس از آن‌که درخواست‌ها به قسمت هاب ارسال شد، هاب هویت کاربر را بررسی می‌کند و پس از احراز آن یک سرور برای کاربر بر روی کوبرنتیز ایجاد می‌کند و Notebook های قبلی کاربر که بر روی PVC کوبرنتیز قرار داشتند، به این سرور متصل می‌شوند. در آخر پروکسی به سرور کاربر متصل شده و تمامی درخواست‌های کاربر را به سرور او ارسال می‌کند و منتظر پاسخ می‌ماند.

تا این قسمت کاربر به ژوپیتر متصل شده است، اما امکان اجرای هیچ کدی برای او وجود ندارد. در ادامه نحوه نصب و اجرای Gateway که مسئول ایجاد کرنل‌ها و برنامه‌های کاربر می‌باشد را شرح می‌دهیم.

بخش DockerSwarm:

این بخش متشکل از چند ماشین مجازی و فیزیکی است که برخی از آن‌ها دارای GPU می‌باشند. این کلاستر وظیفه محاسبه و اجرای کدهای کاربران را بر عهده دارد که با استفاده از یک GatewayProxy با سرور کاربران در تعامل است.

برای اجرای کدهای کاربر، پس از ساخت سرور کاربر، کاربر می‌تواند از بین کرنل‌های زیر یک کرنل را برای اجرای کد خود انتخاب کند:

  • Scala with Spark on Yarn cluster: با استفاده از این کرنل کاربران می‌توانند به زبان اسکالا کدهای اسپارکی خود را بر روی کلاستر یارن اجرا کنند.
  • Scala: با استفاده از این کرنل کاربران می‌توانند کدهای اسکالا خود را بر روی ماشین‌های مجازی اجرا کنند.
  • Scala with Flink: ما در بازار برای کارهای پردازشی جریانی از Flink استفاده می‌کنیم. با استفاده از این کرنل کاربران می‌توانند برنامه‌های اجرایی خود را توسط Flink بررسی کنند.
  • Python with Spark on Yarn cluster: این کرنل این امکان را به کاربر می‌دهد تا برنامه‌های اسپارکی خود را با استفاده از زبان پایتون اجرا کنند.
  • Python: با استفاده از این کرنل کاربران می‌توانند برنامه‌های پایتونی خود را اجرا کنند. بر روی این کرنل ابزار conda برای نصب هرگونه پکیج مورد نیاز کاربر وجود دارد.
  • Python with GPU: این کرنل برنامه‌ها را بر روی ماشین‌های مجازی که دارای gpu هستند اجرا می‌کند. مهندسان علوم داده از این کرنل‌ها برای آموزش مدل‌های خود استفاده می‌کنند. همانند کرنل Python در این کرنل‌ها نیز ابزار conda برای نصب پکیج‌های موردنیاز کاربر وجود دارد.

پس از انتخاب کرنل توسط کاربر این درخواست از طریق GatewayProxy به Gateway ارسال و با توجه به نوع کرنل انتخابی کدهای کاربر بر روی ماشین‌های مجازی و یا کلاستر Yarn اجرا می‌شود.

از این بهتر هم می‌شود؟

پس از مهاجرت از زپلین به ژوپیتر، با توجه به پایداری بالای ژوپیتر احساس رضایت کاربران افزایش پیدا کرده بود، اما با توجه به این‌که برخی از ویژگی‌های مثبت زپلین مانند اشتراک گذاری نوتبوک‌ها و نمایش نوار پیشرفت جابهای اسپارکی در ژوپیتر وجود نداشت، در مواردی دچار مشکل شده بودیم، درنتیجه بر آن شدیم تا این قابلیت‌ها را به عنوان یک افزونه به ژوپیتر اضافه کنیم.

اولین افزونه‌ای که به ژوپیتر اضافه شد قابلیت اشتراک گذاری نوتبوک‌ها بود. کاربران تنها با یک کلیک بر روی نوتبوک قادر بودند تا نوتبوک‌های خود را با دیگران به اشتراک بگذارند.

افزونه بعدی نمایش نوار پیشرفت جابهای اسپارک بود. به دلیل عدم نمایش آن کاربران از میزان پیشرفت جاب خود اطلاعی نداشتند و سردرگم می‌شدند. در نتیجه این افزونه به شکل زیر هنگام اجرای کدهای اسپارک در Notebook، به کاربر را از میزان پیشرفت تسک‌های خود اطلاع می‌دهد.

نوار پیشرفت جابهای اسپارکی
نوار پیشرفت جابهای اسپارکی

با افزودن این افزونه‌ها به ژوپیتر هم از ویژگی‌های مثبت این ابزار استفاده کردیم و هم ویژگی‌های مثبت زپلین را که باعث ایجاد برتری آن نسبت به ژوپیتر می‌شد حذف کردیم.