متدولوژی «۱۲ فاکتور»، یک رویکرد بهینه برای توسعه، ارائه و نگداری نرمافزارهای قابل مقیاس است که توسط تیمی از توسعه دهندگان Heroku در سال ۲۰۱۱ معرفی شد. این متدولوژی اصول و دستورالعملهایی را برای طراحی و پیادهسازی سیستمهای توزیعشده و مقیاسپذیر ارائه میدهد و ایده اصلی آن، جداسازی مولفههای سیستم به صورت مستقل و قابل تغییر است تا امکان پیادهسازی، انتشار و مقیاسپذیری نرمافزار های Cloud Native را فراهم کند.
به طور خلاصه Cloud Native رویکردی مدرن در توسعه، استقرار و نگهداری نرمافزارها با بهره گیری از محیطهای محاسباتی ابری است. و اما نقل قول هایی از بزرگان..
Amazon AWS :
"Cloud native is the software approach of building, deploying, and managing modern applications in cloud computing environments. Modern companies want to build highly scalable, flexible, and resilient applications that they can update quickly to meet customer demands. To do so, they use modern tools and techniques that inherently support application development on cloud infrastructure. These cloud-native technologies support fast and frequent changes to applications without impacting service delivery, providing adopters with an innovative, competitive advantage."
Cloud Native Computing Foundation - 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.”
نرم افزار های Cloud Native بر اساس مجموعهای از اصول و روشها ساخته میشوند تا کاملاً از قابلیتهایی که توسط پلتفرمهای ابری ارائه میشود بهره ببرند. این نرمافزار ها معمولاً به صورت مجموعهای از سرویسهای کوچک و مجزا، توسعه مییابند که قابلیت نصب، مقیاسپذیری و مدیریت مستقل را دارند و موجب افزایش بهرهوری، کاهش هزینه ها و اطمینان از Availability میشوند.
متدولوژی ۱۲ فاکتور به دلیل همخوانی با اصول میکروسرویسها و ارائه الگویی مؤثر برای طراحی برنامههای قابل مقیاس، بسیار مورد توجه قرار گرفته است.
۱۲ فاکتور اصلی این متدولوژی به شرح زیر است که هر قسمت را به شکل مجزا بررسی خواهیم کرد:
نرمافزارهای Cloud Native باید همواره از یک مبنای کد واحد تشکیل شده باشند و از طریق یک سیستم ورژن کنترل قابل ردیابی باشند. مبنای کد یک ریپازیتوری یا مجموعه ای از ریپازیتوری هاست که ریشه مشترکی را به اشتراک میگذارند و برای تولید هر تعدادی از انتشارهای بیمتغیر استفاده میشوند. باید یک رابطه ۱:۱ بین یک برنامه و یک مبنای کد وجود داشته باشد، اما یک رابطه یک به چند بین مبنای کد و استقرارهای یک برنامه وجود داشته باشد. این مبنای کد واحد به تسهیل همکاری بین تیمهای توسعه کمک میکند و امکان نسخهبندی صحیح برنامهها را فراهم میکند.
این مبنای کد میتواند یک مخزن گیت (شامل GitHub، GitHub Enterprise، GitLab و غیره) باشد. گیت یک ابزار ورژن کنترل است که به توسعه دهندگان یک تیم اجازه میدهد به صورت مشترک به توسعه یک برنامه بپردازند. از سایر جایگزین ها میتوان به مواردی مثل BitBucket، SourceForge، AWS CodeCommit، Google Cloud Source Repositories، Azure Repos اشاره کرد.
بیشتر برنامهها نیاز به استفاده از وابستگیهای خارجی دارند؛ برای مثال فرض کنید که از Liberty استفاده میکنیم و وابستگی های نظیر servlet-3.1، jsonp-1.0 و jaxrs-2.0 داشته باشیم. این وابستگیها باید در طول فرآیند ساخت (Build) دانلود شوند. زیرا نمیتوان تضمین کرد که وابستگیهای خاصی که برنامه شما به آنها وابسته است در حین زمان اجرا از قبل موجود باشند. یک برنامه Cloud Native نمیتواند به وجود ضمنی پکیج ها در سراسر سیستم اعتماد کند. در واقع هدف این فاکتور، تشویق به اعلام صریح و جداکردن وابستگیهای برنامه است. این فاکتور کمک میکند تا سازگاری بین محیطهای توسعه و تولید (Dev/Prod) ایجاد شود، که نهایتا قابلیت سیار بودن بین پلتفرمهای ابری را فراهم کند.
گام اول برای رسیدن به این فاکتور، شناسایی، اعلام و جداکردن هر گونه وابستگی خارجی در برنامه شماست. اکثر زبانهای برنامهنویسی ابزارها یا امکاناتی برای مدیریت این وابستگیها دارند. در زبان جاوا، دو ابزار بسیار محبوب برای مدیریت وابستگیها Maven و Gradle هستند. این ابزارها به سادگی پیچیدگیهای مربوط به مدیریت وابستگیها را کاهش میدهند و اجازه میدهند تا توسعهدهندگان وابستگیهای برنامه را اعلام کرده و مسئولیت برآورده کردن این وابستگی ها را به عهده میگیرند. پس به جای گنجاندن کتابخانه های third-party به شکل مستقیم در میکرو سرویس های خود، میتوانید تمام وابستگی ها را در یک فایل pom.xml برای Maven و یا در یک فایل settings.gradle برای Gradle مشخص کنید. این رویکرد به شما امکان میدهد به آسانی به نسخههای جدیدتر وابستگی ها مهاجرت کنید و اطمینان داشته باشید که وابستگی ها توسط ابزار ساخت مهیا و کنترل میشوند و نه توسط توسعه دهندگان.
تنظیمات به هر مقداری اشاره دارد که میتواند در استقرارهای مختلف (Development, QA, Production) متفاوت باشد. از جمله تنظیمات میتوان به موارد زیر اشاره کرد:
مهم است که تنظیمات و مجوزها از کد برنامه جدا شوند. مجوزها اطلاعات بسیار حساسی هستند و هرگز نباید در داخل کد برنامه قرار بگیرند زیرا این کار باعث میشود سرویسهای پشتیبانی برنامه، آدرسهای داخلی، منابع و سرویسهایی که برنامه به آنها وابسته است، قابل دسترسی باشند. جداسازی تنظیمات نیز اهمیت دارد زیرا این امکان را به ما میدهد تا برنامههای خود را در چندین محیط مختلف استقرار دهیم. به عبارت دیگر، نباید مهم باشد که برنامه در چه محیطی اجرا میشود و ما نباید نیاز داشته باشیم برنامه را برای اجرا در محیط های متفاوت تغییر دهیم. ذخیره این مقادیر تنظیمات در متغیرهای محیطی (Environment Variables) به عنوان بهترین روش برای جداسازی تنظیمات در نظر گرفته میشود. این رویکرد به سادهتر شدن استقرار برنامه در محیطهای مختلف، کاهش ریسک نشت اطلاعات احراز هویت و رمز عبور و امکان مدیریت بهتر انتشار کمک میکند.
با استفاده از ابزارهایی مثل MicroProfile Config میتوانید با قرار دادن تنظیمات در فایلهای پیکربندی، امکان جداسازی تنظیمات را فراهم کنید، بهگونهای که بتوانید تنظیمات را بدون نیاز به کامپایل مجدد سرویسهای کوچکتان بهروز کنید. این امر به این معناست که تنظیمات ما در محیط ذخیره میشوند و در زمان اجرا به آنها دسترسی پیدا میکنیم، به جای جایگزین کردن آنها در کد خود برنامه. این روش همچنین امکان تغییرات پویا در مقادیر تنظیمات را به سادگی فراهم میکند. حین استفاده از MicroProfile میتوان تنظیمات را درون یک فایل (microprofile-config.properties) در خارج از سورس کد برنامه قرار داد.
سرویس پشتیبان، سرویسی است که برنامه شما برای عملکرد خود به آن وابسته است. از برخی متداول ترین سرویسها میتوان به دیتابیس ها، سیستم های Messaging و Caching اشاره کرد.
این فاکتور بر نحوه برخورد با این سرویس های پشتیبان به عنوان منابع متصل تمرکز دارد. یک منبع متصل، فقط یک وسیله برای اتصال برنامه شما به سرویس پشتیبان است. یک منبع متصل برای یک دیتابیس ممکن است شامل نام کاربری، رمز عبور و آدرسی باشد که امکان مصرف این منبع را در برنامه شما فراهم میکند. یک برنامه Cloud Native باید نیاز خود به یک سرویس پشتیبان را اعلام کند اما اجازه دهد که محیط ابری انجام واقعی اتصال را بر عهده بگیرد. همچنین متصل سازی یک برنامه به سرویسهای پشتیبان باید از طریق پیکربندی خارجی انجام شود. باید بتوان بدون نیاز به استقرار مجدد برنامه، سرویسهای پشتیبان را به دلخواه از برنامه جدا و به آن متصل کرد. در سورس کد شما هیچ خط کدی نباید وجود داشته باشد که برنامه را به یک سرویس پشتیبان خاص وابسته کند.
پذیرفتن و استفاده از سرویسهای پشتیبان به عنوان منابع متصل در برنامه های Cloud Native، امکانات و انعطافپذیری و ایستابپذیری بیشتری را برای برنامه فراهم میکند. این امر باعث ایجاد اتصالهای ضعیف بین سرویسها و استقرارها میشود (loosely-coupled services) به عنوان مثال، یک مدیر سیستم که متوجه شده است پایگاه داده دچار خطا شده است، میتواند نمونه تازهای از پایگاه داده را راهاندازی کرده و سپس اتصال برنامه خود را به این پایگاه داده جدید تغییر دهد.
این فاکتور، بر یک فرآیند واضح بدون چرخهها و فراخوانی های اضافه در هر یک از مراحل استقرار، تمرکز دارد. در اینجا، یک کدبیس واحد از طریق فرآیند ساخت به یک مولفه کامپایل شده تبدیل میشود که سپس با اطلاعات پیکربندی خارجی از برنامه ترکیب شده و یک نسخه غیرقابل تغییر را تولید میکند. این نسخه سپس به یک محیط ابری (development, QA, production و غیره) تحویل داده میشود و اجرا میشود. نکته کلیدی این است که هر یک از مراحل استقرار، جداگانه و مستقل انجام میشوند.
مرحله Build بر روی ساخت همه چیزی که برای برنامه ما نیاز است تمرکز دارد؛ در این مرحله نسخه ای از کد را با تمام وابستگی ها و تنظیماتش به یک برنامه قابل اجرا تبدیل میکنیم. این فرآیند برای استقرار برنامه در محیط های مختلف ضروری است.
مرحله Release بر ترکیب خروجی مرحله قبل با مقادیر تنظیمات (تنظیمات محیطی و تنظیمات مختص برنامه) تمرکز دارد. با برچسبگذاری این نسخهها با شناسههای منحصر به فرد، امکان برگشت به نسخههای قبلی در صورت بروز هر گونه مشکل وجود دارد.
مرحه Run که در یک ارائه دهنده ابری اتفاق میافتد، معمولا از ابزارهای مثل کانتینر ها و پروسس ها برای اجرای برنامه استفاده میکند. و هنگامی که برنامه اجرا شود، Cloud Runtime است که وظایفی چون نگهداری و مقیاسپذیری پویا را به عهده دارد.
فاکتور فرآیندها بر اجرای برنامهها به عنوان یک فرآیند تک نقطهای و بدون حالت ماندگار (Stateless) تأکید میکند. به عبارت دیگر، همه حالتهای ماندگار باید خارج از برنامه و توسط سرویس های پشتیبان ذخیره/فراهم شوند و نه توسط برنامه. این یک فاکتور مفید است زیرا اگر Instance ای از برنامه شما خاموش شود و یا به دلیل خطا متوقف شود، شما استیت یا حالتی برای از دست دادن ندارید. همچنین به Load-Balancing کمک میکند چون برنامه شما به هیچ استیت خاصی از یک سرویس وابستگی ندارد.
سیستم های مبتنی بر پارادایم REST استیت لِس (stateless) هستند. به این ترتیب، زیرساخت میتواند میکروسرویس های جدید را بدون از دست دادن هیچ اطلاعاتی ایجاد و یا حذف کند. در زبان جاوا برای دستیابی به یک معماری RESTful میتوان از JAX-RS برای ساخت وب سرویس ها استفاده کرد. همچنین در کنار آن MicroProfile Rest Client را داریم که در نقش کلاینت عمل میکند.
فاکتور بایند پورت میگوید که برنامههای مبتنی بر ابر باید با استفاده از پورت بایندینگ خدمات را ارائه دهند. به عبارت دیگر، هاست و پورت مورد استفاده برای دسترسی به سرویس باید توسط مولفه های محیطی ارائه شده و در برنامه جایگذاری نشوند، تا شما برای این آدرسها به سرویسهای موجود یا مجزا اعتماد نکنید. ارائه دهنده ابری شما باید وظیفه مدیریت تخصیص پورت را بر عهده داشته باشد زیرا احتمالاً مدیریت مسیریابی، مقیاسپذیری، و مقاومت در برابر خطا را هم در دست خواهد داشت و همه اینها نیازمندی ها را برای ارائه دهنده ابری برآورده خواهد کرد.
فاکتور همزمانی (Concurrency) بر این نکته تأکید میکند که میکروسرویسها باید بتوانند به شکل الاستیک بر اساس بار (Load) کاریشان، خودشان بزرگتر یا کوچکتر شوند. در گذشته، زمانی که بسیاری از برنامهها به عنوان مونولیتها طراحی و به صورت لوکالی اجرا میشدند، افزایش مقیاس عمودی (Vertical Scaling) با افزودن پردازندهها، حافظه RAM و منابع دیگر (مجازی یا فیزیکی) به سیستم انجام میشد. اما اکنون که برنامههای ما به شکل برنامه های کوچکتر و در محیط ابری اجرا میشوند، میتوانیم از رویکردی مدرن و ایدهآل برای امکان مقیاسپذیری الاستیک به صورت افقی (Horizontal Scaling) استفاده کنیم. به جای افزایش منابع برای یک فرآیند بزرگ، شما چندین فرآیند کوچک ایجاد میکنید و بار کاربردی را بین این فرآیندها توزیع میکنید. این رویکرد با قابلیت مقیاسپذیری الاستیکی که محیط های ابری ارائه میدهند، هماهنگی خوبی دارد و امکان بهرهبرداری بهتر از منابع را فراهم میکند.
ابزار خودکار مقیاسپذیری کوبرنتیز (Kubernetes) میتواند در این مورد کمک کند. ویژگی Horizontal Pod Autoscaler به طور خودکار تعداد پادها در یک رپلیکیشن کنترلر، یا مجموعهای از رپلیکاها را بر اساس استفاده از CPU، یا بر اساس متریکهای سفارشی، و یا برخی از متریکهای ارائه شده توسط برنامه، مقیاسپذیر میکند. این ابزار در قالب یک ریسورس و یک کنترلر پیاده سازی شده است. ریسورس رفتار کنترلر را مشخص میکند و کنترلر به صورت دورهای رپلیکا ها را تنظیم میکند تا مصرف متوسط CPU را با هدف مشخص شده توسط کاربر هماهنگ کند. این قابلیت اجرای چند نمونه از برنامهی شما و افزایش مقیاس خودکار آن، به معنای دسترسی بالا (High Availability) برای برنامه شما است.
فرآیندهای یک برنامه کلاود-نیتیو باید قابل حذف باشند، به این معنی که بتوانند به سرعت راهاندازی و خاموش شوند. اگر یک برنامه نتواند به سرعت راهاندازی شود و به طور صحیحی خاموش شود، نمیتواند به سرعت مقیاس بندی، استقرار، انتشار یا بازیابی شود. این مسئله به ویژه در برنامههای کلاود-نیتیو بسیار مهم است زیرا اگر شما یک برنامه را راهاندازی کنید و زمانی طول بکشد تا به وضعیت ثابت برسد، در جهانی با ترافیک بالا، این ممکن است به معنای عدم پاسخ به صدها یا هزاران درخواست کاربران باشد.
علاوه بر این، بسته به پلتفرمی که برنامه شما در آن استقرار مییابد، زمان راهاندازی آهسته ممکن است هشدار یا هشدارهایی را فعال کند زیرا برنامه در مرحله بررسی وضعیت سلامت (Health Check) خود با خطا روبرو میشود. زمان راهاندازی بسیار آهسته حتی ممکن است باعث شود که برنامه شما به طور کامل در محیط ابری اجرا نشود. اگر بار برنامه در حال افزایش باشد و شما نیاز به سریعتر راهاندازی Instance های بیشتر برای مدیریت این بار داشته باشید، هر تاخیری در مرحله راهاندازی میتواند توانایی برنامه برای مدیریت این بار را محدود کند.
از طرف دیگر اگر برنامه به سرعت و به طور صحیحی خاموش نشود، ممکن است در روند راهاندازی مجدد برنامه پس از رفع خطا مشکلاتی پدید آید. ناتوانی در خاموش کردن به اندازه کافی سریع میتواند خطر عدم موفقیت در بازگرداندن منابع را به همراه داشته باشد که میتواند به خراب شدن دادهها (Data Corruption) منجر شود.
فاکتور توازن میان محیطهای توسعه و تولید (Development / Production) بر اهمیت حفظ تشابه بین محیطهای توسعه، استیجینگ و تولید تمرکز دارد. این موضوع بسیار مهم است تا بتوانید اطمینان حاصل کنید که همه باگها و خرابیها در مرحله توسعه و آزمایش شناسایی میشوند و نه زمانی که برنامه در محیط تولید قرار میگیرد. این کمک میکند تا اظهارات مشهور توسعهدهندگان مانند "این برنامه بر روی لپتاپ من اجرا میشود" از بین برود. با توجه به اینکه بسیاری از برنامهها در حال حاضر در محیطی ابری اجرا میشوند و با بسیاری از سرویسهای دیگر در یک اکوسیستم بزرگ از سرویسها تعامل دارند، این مهم است که محیطی مشابه را هنگام توسعه و آزمایش برنامههای خود تکثیر کنیم.
استفاده از ابزارهایی مانند Docker میتواند این توازن بین محیطهای توسعه، آزمایش و تولید را فراهم کند. مزیت یک کانتینر این است که محیطی مطلقاً یکنواخت را برای اجرای کد فراهم میکند. این محیط به سادگی "از بین رفته" و مجدداً ایجاد میشود. سهولت در قفل کردن جزئیات محیطی (شامل تنظیمات سیستم عامل، نصب و تنظیمات بستههای نرمافزاری، متغیرهای محیطی و سرویسهای پشتیبان)، عامل مهمی برای یکسان سازی محیط های توسعه و پروداکشن است.
کانتینرها امکان ایجاد و استفاده از Image های یکسان را در محیط توسعه، استیجینگ و پروداکشن فراهم میکنند. همچنین به کمک آنها میتوان اطمینان حاصل کرد که در تمام محیط ها از سرویس های پشتیبان یکسان استفاده شده است. با استفاده از این مفهوم و ابزارهای آزمایشی مانند MicroShed Testing (ابزاری جالب در اکو سیستم جاوا، عموما برای اکثر زبان های ترند نمونه مشابهی وجود داره) میتوان اطمینان حاصل کرد که محیط تست ما تا حد امکان شبیه به پروداکشن است.
فاکتور لاگها بر اهمیت تأمین این مسئولیت متمرکز میشود که برنامه شما نباید با مسئولیت هدایت، ذخیره سازی یا تجزیه و تحلیل جریان خروجی خود (یعنی لاگها) سر و کار داشته باشد. در برنامههای کلود-نیتیو، تجمیع، پردازش و ذخیرهسازی این لاگها مسئولیت ارائه دهنده کلود یا سایر ابزارهایی است که در کنار پلتفرم کلود استفاده میشوند (مانند مجموعه ابزار های ELK، Splunk، Sumologic و غیره).
این امر به ویژه در برنامههای کلود-نیتیو به دلیل قابلیتهای مقیاسپذیری الاستیکی که دارند بسیار مهم است. به عنوان مثال، وقتی که برنامه شما از یک Instance تغییر کرده و تعداد Instance ها به بیش از ۱۰۰ آیتم افزایش مییابد، ممکن است سخت باشد که بدانید که این Instance ها در کجا اجرا میشوند و از کنترل و سازماندهی تمامی لاگها آگاهی داشته باشید. با سادهسازی نقش برنامه شما در فرآیند تجمیع و تجزیه و تحلیل لاگها، کدبیس برنامه ساده تر شده و تمرکز بیشتری روی بیزینس لاجیک خواهد داشت. این فاکتور انعطافپذیری برای بررسی رفتار در طول زمان را تسهیل میکند و امکان جمعآوری و تحلیل بهینه متریک های Real-Time را فراهم میکند.
یک روش بهینه برای اجرای این فاکتور، استفاده از رویداد ها به صورت لحظه ای است. تا اگر Instance ای از برنامه از بین رفت، باعث از دست دادن لاگ ها نشود.
این فاکتور توصیه میکند که وظایف یا مسائل مدیریتی را درون میکروسرویس های خود قرار ندهید. برای مثال مایگریشن دیتابیس و یا اجرای اسکریپت هایی که ۱ بار اجرا میشوند (Clean up و غیره). در واقع این فرآیند ها میتوانند در قالب تسک های Kubernetes اجرا شوند. این روش باعث میشود میکروسرویس های شما بر روی بیزینس لاجیک خود تمرکز کنند. همچنین به شما امکان اجرا و دیباگینگ ایمن برنامههای تولیدی را میدهد و برای برنامههای کلاود-نیتیو انعطاف بیشتری را به همراه دارد.
به طور کلی، هدف این مقاله ارائه یک درک بهتر از فاکتور های مهم کلاود-نیتیو و کاربردهای عملی آنها در سناریوهای واقعی است. با بررسی این عوامل، شما میتوانید با نگاهی دقیقتر به اصول و شیوههای کلیدی بپردازید که میتوانند توسعه و پیادهسازی برنامههای کلاود-نیتیو را بهبود بخشند.
علاوه بر این، در این مقاله درباره ابزارها و فناوریهای مختلفی صحبت شد که به شما کمک میکنند تا این فاکتور ها را به طور موثر پیادهسازی کنید. این ابزارها مانند Kubernetes، MicroProfile، Docker و غیره، قابلیتهای ارزشمندی را برای دستیابی به معماری کلاود-نیتیو و مقابله با چالشهای خاصی از جمله مقیاسپذیری، تحملپذیری، مدیریت پیکربندی، سرویس دیسکاوری و غیره فراهم میکنند.
با پذیرش این فاکتور ها و بهرهبرداری از ابزارهای مناسب، میتوانید برنامههایی بسیار قابل مقیاس، تحملپذیر، قابل حمل و کارآمد ایجاد کنید. و این میتواند منجر به جریان کار بهتر، پیادهسازی سریعتر، کاهش هزینههای عملیاتی و تجربه کاربری بهتر شود.