سلام و درود
امیدوارم که عالی باشید. امروزه کلی در مورد کلاد و مزایای اون صحبت میشه. یه موضوعی که در کنار کلاد مهمه و باید بهش خیلی دقت کنیم اینه که آیا اپلیکیشن ما و سرویسی که میخواهیم روی کلاد بیاریم بالا متناسب این بستر هست یا نه. این موضوع خیلی مهمی هست. درسته که بستر کلاد هست و کلی امکانات خفن و متفاوت در اختیار ما میگذاره اما نیازه که خود اپلیکیشن ما هم قابلیت استفاده و تطابق با آن رو داشته باشه. امروز میخوایم به این موضوع بپردازیم.
خب یه مروری کنیم الان کجاییم
قبلا در مورد اینکه دواپس چیه و چرا لازمه؟ صحبت کردیم و بعدش در مورد مسیر شغلی دواپس صحبت کردیم که سعی کردم تجربه و پیشنهاد خودم رو باهاتون به اشتراک بذارم که خوبه حتما قبل از ادامه این پستها رو بخونید.
چطوری اپلیکیشن خودمون رو سازگار با کلاد کنیم؟
اینجا گزارههای متفاوتی هست و خب هر کسی تجربهای داره. اما یه رفرنس خیلی خفن هست که میشه بهش اعتماد کرد و ازش استفاده کرد. شرکت Heroku پس از تجربه و مشاهده استقرار تعداد زیادی اپلیکیشن روی کلاد بهش رسیده و ارائه کرده که به اسم The Twelve-Factors App میشناسیم و ازش استفاده میکنیم. این ۱۲ تا توصیه که هر کدومش به موضوع خاصی اشاره میکنه کلی کمک میکنه که مسیر پیش روی ما برای Cloud Ready شدن هموار بشه. یعنی نکاتی رو اشاره میکنه که اگر رعایت کنیم به راحتی میتونیم اپلیکیشن خودمون رو روی کلاد استقرار بدیم و ازش استفاده کنیم.
خب بریم ببینیم این ۱۲ تا آیتم چی هست و ما چطور میتونیم آنها رو انجام بدیم.
آیتم اول: Codebase
ببینید شاید این الان خندهدار باشه برای بعضی از شماها که بگیم حتما از کد بیس مثلا گیت استفاده کنید. اما زمانی که این ۱۲ تا آیتم ارائه شده بود این موضوع مهم بود. الان گستردگی کدبیسهای رایگان و استفاده از گیت اونقدر راحت و بیسیک شده که تقریبا نمیشه پروژهای رو بدون گیت تصور کرد ولی خب یکم قبلتر این طوری نبوده. اینجا میخواد در مورد این صحبت کنه که حتما از کد بیس استفاده کنید که من میگم حتما از گیت استفاده کنید که مزایای اون رو به صورت کامل داشته باشید. البته من تو ایران همین الان هم پروژههایی رو میبینم که کد بیس ندارن و دارن پروژه رو با فلش میبرن روی سرور یعنی اونقدر هم بعید و دور نیست. به صورت کلی توصیه می کنم حتی اگر برای خودتون هم کار میکنید از گیت استفاده کنید که کلی میتونه بهتون کمک کنه و جلوی خطاهای شما رو بگیره.
آیتم دوم: Dependencies
ببینید اینکه نیازمندیهای یک اپلیکیشن کنار اون باشه خیلی مهمه. الان با وجود داکر و انسیبل اینا زیاد شاید به چشم نیاد ولی در گذشته خیلی مهم بوده و مشکلات زیادی رو برای ما به همراه داشته. ما میرفتیم اپلیکیشن رو استقرار بدیم تو محیط جدید و باید کلی میگشتیم تا نیازمندیها و ملزومات اون رو پیدا کنیم و براش ایجاد کنیم که خب کار سختی بود. الان اگر از داکر استفاده کنید به صورت پیشفرض این موضوع رو داره و مشکل رو حل کرده. یا مثلا اگر از Ansible یا مثل اون استفاده کنید هم همینطور چون نیاز داره همه چیز رو اون هندل کنه پس تمام نیازمندیها و ملزومات مورد نیاز اپلیکیشن در کدهای ما وجود داره. تو این اصل داره به این میپردازه که حتما نیازمندی و ملزومات اپلیکیشن خودتون رو لیست کنید و اون لیست رو کنارش تو گیت قرار بدید که هر کسی خواست اون رو استقرار بده بدونه که باید چه ملزوماتی رو ایجاد کنه.
کلا وقتی به سمت Infrastructure as Code بریم این موضوع هم به صورت کامل حل خواهد شد.
آیتم سوم: Config
این موضوع خیلی مهمه و من زیاد دیدم که تو پروژهها بهش توجه نشده. اینکه کانفیگهای ما باید از وریبل باشه تا به ازای دیپلوی تو محیط جدید یا تغییر کانفیگ نیاز نباشه که بیلد مجدد گرفته بشه. این برای ما مهمه که همون نسخهای که تست میشه همون نسخه تو محیطهای مختلف اعم از آزمایشگاهی و عملیاتی استقرار پیدا کنه. اگر کانفیگها به صورت وریبلها نباشن ما برای تغییر کانفیگ مجبوریم که مجدد ایمیج خودمون رو بیلد کنیم و این اصلا چیز خوشایندی برای ما نیست. پس دقت کنیم که کانفیگهای ما هر تعدادی که هستند به صورت وریبل باشند. حتی اگر تعداد آنها زیاد است به صورت وریبل فایل باشد که ما میتونیم به ازای استقرار در محیطهای مختلف آنها رو تغییر بدیم و ازشون استفاده کنیم.
آیتم چهارم: Backing services
معمولا سرویسها و اپلیکیشنها ما برای عملکرد کامل خودشون نیاز دارن که از سامانههایی مثل دیتابیسها، کشها، کیوها و … سرویس بگیرند. اینجا توصیه میشه حتما ارتباط با این سرویسها رو به صورت جداگانه در نظر بگیرند و آنها رو موارد مستقلی در نظر داشته باشند. مثلا من خیلی این مورد رو دیدم که میخواد با ردیس به عنوان یه کش صحبت کنه داره با لوکال هاست خودش صحبت میکنه در صورتی که ممکنه و خیلی هم احتمالش زیاده که اون ردیس روی همون سرور نباشه و یا اصلا سرویس شما کانتینر بشه که دیگه اصلا لوکالهاستش آدرس درستی براش نیست. در این شرایط باید اینترفیس درستی از اون سرویس که بهش نیاز داریم و ایجاد و صدا کنیم. مثلا به جای localhost:6379 بگیم redis:6379 که این سرویسها میدونند و میتونند درخواست ما رو به redis برسونند. تو اینجا اشاره به این دارم که کلادها قابلیت سرویس دیسکاوری دارند و ما داریم میگیم redis و اون خودش میتونه درخواست ما رو به اون سرویس برسونه.
آیتم پنجم: Build, release, run
تو این آیتم داره به این اشاره میکنه که حتما محیطهای بیلد و اجرا از هم مستقل باشه که بتونیم بدون نیاز به بیلد مجدد در محیطهای مختلف اپلیکیشن خودمون رو استقرار بدیم. نکتهی مهم بعدی اینکه فرآیند CI/CD رو پیش ببریم که فرآیند خیلی مهم و لازمی هست. این بهمون کمک میکنه که خطاهای کمتری رو تو پروداکشن داشته باشیم و فرآیند توسعهی نرمافزارمون با خطای کمتر و صحیحتر پیش بره. یه نکتهی دیگه که شاید بشه اینجا بهش پرداخت موضوع GitOps هست که امروزه خیلی پر طرفدار است. موضوع GitOps هم به همین منوال خواهد بود که کد ما پس از تست احتمالا با برنچ مشخصی مرج میشود و پس از مرج تغییرات جدید ما روی کلاد استقرار پیدا میکند. کلا جدا کردن این محیطها از هم تمام این مزایاها رو برامون به همراه داره.
آیتم ششم: Processes
حتما حتما دقت کنید که اپلیکیشنی که شما توسعه میدید Stateless باشد. حالا این یعنی چی؟ یعنی اپلیکیشن شما برای عملکرد صحیح به لحظهی قبل خودش وابسته نباشد. این عدم وابستگی خیلی به ما کمک میکنه که به راحتی بتونیم اون رو اسکیل کنیم و تعدادش رو بیشتر کنیم. اگر حالا موضوعی داشتید که مجبور به نگهداری State بودید این کار رو انجام بدید:
- اگر Data دارید از databaseها استفاده کنید.
- اگر Cache یا State دارید از ابزارهای آن مثلا Redis استفاده کنید.
- اگر Static Object دارید از Object Storage مثلا Minio استفاده کنید.
- اگر Queue دارید از ابزارهای مدیریت صف مثلا از RabbitMq یا Kafka استفاده کنید.
- اگر Log از جنس دیتا دارید از CLM مثلا ELK استفاده کنید.
معمولا من خیلی حساسم که یه ابزار به استک تیم یا شرکت یا پروژه اضافه کنم. یعنی به سختی قبول میکنم که ابزار اضافه کنم مگر اینکه دلیل کاملی براش داشته باشم. ولی اینجا برای اینکه نرمافزار شما Stateless بشه حاضرم پنج تا ابزار و استک به تیم تحمیل کنم. دلیلش مشخصه اینکه ما میتونیم این ابزارهایی که گفتم رو Cluster کنیم و براشون راهکار داشته باشیم اما برای اپلیکیشن شما این موارد وجود نداره. پس حتما حتما دقت کنید که باید طوری رفتار کنیم که اپلیکیشن ما کاملا Stateless باشه تا ما بتونیم به راحتی تعداد آنها را زیاد کنیم یا اصطلاحا اونها رو Scale کنیم.
یه نکته اینجا اضافه کنم که تو برخی از پروژهها دیدم. سعی کنید از هر چیزی در جای درست خودش استفاده کنید. دیدم که میگم. طرف اومده لاگهای کاربر به همراه استاتیکهای اون مثلا اسکن شناسنامه و کارت ملی و …. رو تو دیتابیس ذخیره کرده و الان با یه دیتابیس 2TB مواجه شده. هر چیزی باید تو جای خودش باشه. درسته اون لاگ و استاتیک آبجکت خیلی براشون مهمه ولی باید حتما تو ابزار درستش نگهداری بشه. اگر آبجکتها و لاگها رو از اون دیتابیس خارج میکردیم با حجم 100GB مواجه بودیم که اصلا قابل مقایسه با وضعیت قبلی دیتابیس نیست. پس دقت کنید از هر ابزاری در جای درست خودش استفاده کنید.
آیتم هفتم: Port binding
این آیتم این توصیه رو داره که حتما برای سرویس خود یه پورت مشخص کنید تا از طریق آن بشود با سرویس شما ارتباط برقرار کرد. گاها دیده میشه که پورتی رو مشخص نمیکنند و پس از بالا اومدن روی یه رندم پورت سرویس بالا میاد که اصلا چیز جالبی نیست. نکتهی مهم اینه که ما معمولا هیچ پورتی رو از سرور به بیرون Expose نمیکنیم. همواره اگر قرار باشه پورتی به بیرون انتشار بدیم حتما از Reverse Proxy جلوی آن استفاده میکنیم و سرویسها رو پشت آن مدیریت میکنیم که با اسم فراخوانی شود. پس دقت کنید ما معمولا هیچ پورتی از سرور نمیگذاریم به بیرون Expose بشه به جز پورت ۸۰ که انتقال پیدا میکنه به ۴۴۳ و پورت سرویس SSH که معمولا پورت اون رو هم از ۲۲ به پورت دیگهای تغییر میدیم.
آیتم هشتم: Concurrency
اینجا توصیه میکنه که از نوع اسکیل افقی بتونیم استفاده کنیم به جای عمودی. یعنی بتونیم تعداد اون رو بیشتر کنیم تا اینکه میزان منابع اون رو افزایش بدیم. اگر توصیهی ششم به خوبی و درستی ایجاد شده باشد اینجا دیگه کار سخت نیست و به راحتی میتونیم تعداد آن رو افزایش دهیم.
مقیاسپذیری و انواع آن:
بد نیست اینجا در مورد Scaling و انواع آن یه صحبت ریزی کنم. ما معمولا دو جور Scaling داریم یکی به صورت horizontally و یکی هم به صورت vertically که هر کدوم در جای خودشون درست و کامل است. به صورت افقی یعنی تعداد رپلیکا رو بیشتر میکنیم که بهش Scale Out هم گفته میشود. به صورت عمودی یعنی میزان منابع رو بیشتر میکنیم که بهش Scale Up هم گفته میشود. معمولا اپلیکیشنهای Stateless رو به صورت افقی scale و اپلیکیشهای Stateful رو به صورت عمودی Scale میکنند.
یه مدل Scaling هم داریم که Auto Scaling هست که هم به صورت عمودی و هم به صورت افقی قابل انجام میباشد و فرآیند Scale کردن رو خودکار انجام میده و بعد از برطرف شدن نیاز دوباره سرویس رو کوچیک میکند.
آیتم نهم: Disposability
اینجا یه توصیه خیلی مهم داره که خیلی جاها اصلا بهش توجهی نمیکنند. نحوهی بالا اومدن سرویس و اطمینان از بالا اومدن آن و عملکردش و به همان اندازه هم نحوهی خاموش شدن و فرآیند خاموش شدن سرویس اهمیت داره. کلا مهمه که درست و به موقع بمیره و وقتی داره سرویس میمیره چطور این کار رو انجام بده. یعنی مثلا درخواستهایی که سمتش هست رو جواب بده یا همین الان یهو بمیره. کلا توصیه میشه که نرمافزارها و برنامهها به سرعت شروع به فعالیت کنند و به آرامی به سمت حالت ناپایدار و خاموشی بروند.
آیتم دهم: Dev/prod parity
اینجا داره توصیه میکنه که محیطهای مختلف که اپلیکیشن خودمون رو روی آنها استقرار میدیم تا جای امکان باید شبیه هم باشند. کلا توصیه اینه که آنها کاملا شبیه هم باشند و فقط در اندازه و اصطلاحا سایز باهم متفاوت باشند. کلا اینکه شما باید محیطهای مختلفی داشته باشید که اپلیکیشنتون رو اونجا استقرار بدید خیلی اهمیت داره و اینکه تعداد آن با توجه به هر پروژه میتونه متفاوت باشه. برخی از پروژهها هستند که تعداد زیادی محیط دارند ولی معمولا ۳ تا محیط توصیه میشه که داشته باشید. یک محیط به عنوان Development و مختص تیم توسعه و یک محیط به عنوان Pre-Production که نزدیکترین جا به پروداکشن و محیط عملیاتی میباشد. معمولا تو این محیط نسخهی جدید اپلیکیشن قبل از انتشار نهایی رو قرار میدهند. در آخر هم محیط Production که محیط عملیاتی و نهایی میباشد که مشتری ما با سرویس کار میکند.
معمولا این محیطها وجود داره که توصیه میشه سعی کنیم تمام این محیطها از نوع ساختار اپلیکیشن و استقرار آن کاملا شبیه هم باشند. البته که سایز و اندازهی آنها کاملا با هم متفاوته ولی توصیه میشه که کاملا شبیه یکدیگر باشند.
آیتم یازدهم: Logs
قبلا شاید فقط لاگ بود اما الان بیشتر در مورد Observability یا همون شهود نسبت به سرویسها و اپلیکیشن صحبت میشود. یعنی ما باید سعی کنیم تا نسبت به زیرساخت و اپلیکیشن خودمون شهود پیدا کنیم. باید بتونیم اون رو بهتر درک کنیم. امروز با وجود موضوع Observability این مسیر خیلی بهتر هموار شده و بهمون کمک میکنه که logها، metricها و traceها رو جمع کنیم و با تحلیل آنها به درک و شهود درستی از سرویس خودمون برسیم. به صورت کلی من معتقدم که لاگ و متریک چشم و گوش ما تو سیستمها و سرویسها هستند. بدون اونها ما هیچ شهودی از سرویسها و زیرساختمون نداریم. ما باید همواره تلاش کنیم که درکمون و شهودمون رو از سرویسها و زیرساختمون بیشتر کنیم. این فرآیند کار یک شبه و یک باره نیست. یه فرآیند مداوم هست که همواره باید تلاش کنیم که اون رو بهتر و بهترش کنیم. تو زمینهی لاگ و متریک باید به این نکته دقت کنیم، همون قدر که کم لاگ و متریک ایجاد کردن بده به همون اندازه هم زیاد ایجاد کردنش هم بده. چه سرویسهایی دیدم که اونقدر لاگ و متریک ایجاد کردن که کلا سرویس مانیتورینگ و لاگینگشون کارایی خودش رو از دست داده.
آیتم دوازدهم: Admin processes
اینجا داره به این موضوع اشاره میکنه که هر چیزی مثل ماگریشن دیتابیس یا ادمین پروسهای که میخواد ران بشه باید با همون اکوسیستم که خود اپلیکیشن رو اجرا میکنه اجرا بشه و فقط یکباره و تکراری شونده نیست. و اینکه حتما باید این موارد هم همانند خود اپلیکشنها تست و بررسی بشه و براش فرآیند داشته باشیم.
یا به بیان دیگه وقتی نیاز به انجام یک کار مدیریتی یا ادمین پروسه داریم، مثل اجرای ماگریشن دیتا، ایجاد بکاپ یا انجام یک فرآیند یکباره، باید این فرآیندها را به صورت مجزا و یکباره اجرا کنیم. این به این معناست که این فرآیندها باید به صورت اتوماتیک و بدون نیاز به نگهداری دائمی اجرا شوند و پس از انجام وظیفهی مورد نظر خاتمه یابند.
برخی از تجربیات خودم:
جداسازی اینترفیس read و write تو دیتابیس: داستان اینه که ما دیتابیسها رو کلاستر میکنیم. با کلاستر کردن معمولا writeها ما بیشتر نمیشه و هنوز رو یک نود هست. ولی read ما به تعداد replica که داریم بیشتر میشه. پس مسیر نوشتن ما با کلاسترینگ بیشتر نشد ولی مسیر خواندن ما بیشتر شد. تو دیتابیسهایی همانند redis مثلا ۹۰ درصد درخواستها read است. پس جدا کردن این دو تا خیلی میتونه تو کارایی سرویس موثر باشه. شما حتما این موضوع رو سمت اپلیکیشن خودتون لحاظ کنید که مسیر خواندن و نوشتن تو دیتابیس رو متفاوت کنید.
تست خوبه ولی برای همسایه: این که تست خوبه خب معلومه کاملا ولی این که تو پروژهها براش وقت نمیگذارید اصلا خوب نیست. Unit test و Integration Test هم خیلی لازمه و هم خیلی مهمه که هر پروژهای داشته باشه. ببین وقتی شما تست نمینویسید یعنی احتمال اینکه خطا بره تو پروداکشن رو میبرید بالا که این خیلی جذاب نیست و احتمال مشکل رو افزایش میده. پس تست خوبه و باید حتما براش وقت بگذارید.
احراز هویت برای دیتابیس: زیاد دیدم برای اینکه راحت باشن برای ارتباط با دیتابیس احراز هویت نمیگذارن یعنی برای اینکه راحتتر باشند بدون Auth با دیتابیس صحبت میکنند. خب شاید راحتتر باشید این طوری ولی خیلی ریسک بالایی رو دارید به سرویس وارد میکنید. کلا باید روی دیتاببیسهای خودتون Authentication بذارید تا این ارتباط امنتر بشه. شاید موضوع سادهای باشه اما خیلی مهمه.
استفاده از static objectها: تو همه سرویسها و سامانههایی که در حال حاضر وجود داره کلی استاتیک آبجکت مثل بکاپ، ایمیج و کلی چیز دیگه که نیاز هست تا جایی نگهداری شوند. سرویس آبجکت استوریج مختص این کار است و حتما سعی کنید که این موارد رو تو آبجکت استوریج ذخیره کنید. مثلا Minio یک آبجکت استوریج هست که واقعا کمک میکنه تا این موارد رو یه جا ذخیره کنید و هر زمان که نیاز داشتید ازش استفاده کنید. دقت کنید که این موارد رو نباید تو اپلیکیشن یا دیتابیس ذخیره کنید. دیدم که میگم.
ایجاد Probe برای اپلیکیشنها: وقتی شما برای اپلیکیشنهایی که دارید Probe بنویسید کمک میکنید که ما از وضعیت و حال اون باخبر باشیم. مثلا اینکه چه زمانی اپلیکیشن شما زنده است و چه زمانی آمادهی سرویسدهی میباشد. این دو تا پراب کلی بهمون کمک میکنه که وقتی روی کلاد یا مثلا کوبرنتیز اون رو استقرار میدیم بدونیم که هر لحظه تو چه وضعیتی هست. به پرابی که در زمان زنده بودن ست میشه Liveness و به پرابی که در زمان سرویسدهی ست میشه Readiness میگیم. به این صورت ما در هر لحظه میدونیم که وضعیت اپلیکیشن شما به چه صورت است. از چند مسیر هم میتونید این پرابها رو ایجاد کنید.