چگونه برنامه cloud-native بنویسیم؟

برنامه‌های cloud-native برنامه‌هایی هستن که انعطاف پذیری بالایی دارن، میشه خیلی سریع تغییرات‌مون رو روشون اعمال کنیم و می‌تونیم اون‌هارو scale کنیم.

تعریف خود CNCF رو از داکیومنت رسمی‌ بخونید:

Cloud-native technologies empower organizations to build and run scalable applications in modern, dynamic environments such as public, private, and hybrid clouds. Containers, service meshes, microservices, immutable infrastructure, and declarative APIs exemplify this approach

These techniques enable loosely coupled systems that are resilient, manageable, and observable. Combined with robust automation, they allow engineers to make high-impact changes frequently and predictably with minimal toil

یک روشی که تقریبا به طور گسترده‌ای همه پذیرفتن برنامه‌های cloud-native رو طبق اون اصول بنویسن 12factor هست. این مجموعه، اصول و روش‌هایی رو توصیف می‌کنه که توسعه دهنده‌ها برای ساخت برنامه‌های بهینه شده برای cloud باید اونهارو رعایت کنن. و توجه ویژه‌ای روی قابلیت جابه‌جایی و اتومیت کردن برنامه داره.

برنامه‌های نوشته شده با این قواعد به سرعت می‌تونن deploy و scale بشن و خیلی سریع تغیرات‌شون رو به دست کاربر برسونن.

بیاید ببینیم قواعد 12factor چیا هستن:

۱. Code Base : مایکروسرویس شما به هرشکلی که می‌خواد باشه باید روی یک کدبیس قرار بگیره و با version controlها مدیریت بشه که این کدبیس می‌تونه روی محیط های مختلف deploy بشه.
۲. Dependencies : هر مایکروسرویس ایزوله هست و وابستگی‌های خودش صریحا توی یک فایل مشخص می‌کنه.
۳. Configurations : همه configها به خارج کد اصلی منتقل می‌شن و توی env ذخیره می‌شن.
۴. Backing Services : منابع جانبی مورد نیاز به صورت جداگانه اجرا می‌شن و از طریق URL در دسترس هستن.
۵. Build, Release, Run : مراحل build و release و run کاملا از هم جدا هستن. هرکدوم باید با یک unique id برچسب گذاری بشن تا بتونیم راحت role back کنیم.
۶. Processes : برنامه باید به صورت stateless باشد یعنی یک رکوئست هیچ ارتباطی با رکوئست قبلی نداشته باشد و دیتاها روی backing serviceهای متصل شده ذخیره بشن.
۷. Port Binding :‌ هر مایکروسرویس باید روی یک پورت شبکه سرویس‌دهی کند.
۸. Concurrency : مایکروسرویس باید بتونه بر اساس تعداد پراسس‌ها scale بکند.
۹. Disposability :‌ مایکروسرویس ها باید به راحتی انهدام بشن تا ورژن جدید به سرعت بالا بیاد.
۱۰. Dev/Prod Parity : محیط‌ توسعه باید تا حد امکان با محیط staging و production شبیه باشد.
۱۱. Logging : مایکروسرویس‌ها باید لاگ هاشون رو بریزن روی stdout.
۱۲. Admin Processes : تسک‌های ادمین باید به صورت یک‌بار مصرف و روی پروسس جدا اجرا بشن و روی برنامه اصلی نباشن.

البته توی کتاب Beyond the Twelve-Factor App علاوه بر توضیح کامل ۱۲فاکتور، ۳فاکتور دیگه رو هم برای برنامه‌های مدرن امروزی مطرح می‌کنه.

۱۳. API First
۱۴. Telemetry
۱۵. Authentication/ Authorization

علاوه بر قواعد 12factor، چندین تصمیم مهم دیگه در زمینه طراحی وجود دارد که زمان ساخت برنامه‌های cloud-native باید به اونا توجه کنیم.

Communication
اینکه front-end چجوری با back-end ما ارتباط برقرار می‌کنه؟ اجازه می‌دیم که به طور مستقیم وصل شه به سرویس‌های back-end؟ یا ممکنه یک سرویس دیگه به اسم api gateway داریم که برنامه‌ها برای کنترل پذیری و امنیت و قابل انعطاف بودن با اون ارتباط برقرار می‌کنن؟
یا مثلا back-end سرویس‌ها چطور باهم ارتباط برقرار می‌کنن؟ اجازه می‌دیم که با http متد باهم ارتباط بگیرن؟ یا از message queue ها استفاده می‌کنیم؟

Resiliency
توی معماری مایکروسرویس اگر وضعیت یک سرویس down شد، چه اتفاقی می‌افته اگر سرویس B روی شبکه جوابی از سرویس A دریافت نکنه؟ یا چه اتفاقی می‌افته اگر سرویس C به صورت موقت از دسترس خارج بشه و سرویس‌های دیگه که با اون تماس می‌گیرن مسدود بشن؟

Distributed Data
اینکه هر مایکروسرویس دیتا‌ی خودش رو خودش ذخیره می‌کنه و با یک اینترفیس عمومی با بقیه ارتباط میگیره. توی این حالت هر مایکروسرویس چجوری با بقیه ارتباط می‌گیره و دیتایی که می‌خواد رو بدست میاره؟

پ.ن: این مطلب برداشتی هست از Defining cloud native

چرا Cloud-Native برنامه بنویسیم؟

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

شما این شغل رو قبول می‌کنید و پس از چند هفته توی یک جلسه طراحی قرار می‌شود یک سایت eCommerce طراحی کنید که از نظر نرم افزار توانایی رقابت با بقیه رقبای تراز اول رو داشته باشه.

شما چجوری می‌سازید؟

اگر مثل متدهای ۱۵ سال پیش بخواهید بسازید، شبیه به شکل زیر خواهید ساخت.

Traditional monolithic design
Traditional monolithic design

یک برنامه که یک هسته بسیار بزرگ نرم‌افزاری شامل احراز هویت، دسته بندی محصولات،‌ ثبت سفارش،‌سبد خرید و خیلی امکانات دیگه رو داخل خودش جا داده و با یک دیتابیس بزرگ relational برای ذخیره دیتا ارتباط برقرار می‌کنه.

تبریک! شما یک برنامه مونولیتیک ساختید.

همه چیز اینطور که می‌گن بد نیست. برنامه های مونولیتیک یک سری مزایای مشخص دارن. اگر بخوایم به چندتا مزیت سرراستش اشاره کنیم:

  • بیلد آسان
  • تست آسان
  • دیپلوی آسان
  • نگهداری آسان

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

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

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

راهکار چیه؟

بسیاری از شرکت‌ها برای فرار از این چرخه ترس، شروع به ساخت برنامه‌ها به صورت cloud-native کردن. شکل زیر یک سیستم با استفاده از تکنیک‌ها و متد‌های cloud-native رو نشون میده.

Cloud-Native Design
Cloud-native design

ببینید چطور برنامه شکسته شده به سرویس‌های کوچک و کاملا ایزوله. هر سرویس مستقل هست و کدها، دیتاها و وابستگی‌های خودش رو توی یک محیط ایزوله محصور می‌کنه. به‌جای یک دیتابیس بزرگ رابطه‌ای، هر سرویس دیتابیس خودش رو داره که حتی نوع این دیتابیس هم بر اساس نیاز می‌تونه متفاوت باشه. بعضی سرویس‌ها نیاز به دیتابیس‌های SQLبیس دارن و بعضی‌ها می‌تونن از NoSQLها استفاده کنن. تمام ترافیک به وسیله یک API Gateway به سرویس مورد نظر می رسه. اگر یک سرویس کرش کرد فقط همون بخش سیستم از کار میوفته. و از همه مهم‌تر این برنامه خیلی راحت اسکیل میشه و خیلی راحت میشه انتظار availability رو ازش داشت.

پ.ن: این مطلب برداشتی هست از Introduction to cloud-native applications