حالت تاریک
فصل ۱۰ — زمان اجرای برنامه
زمان اجرای برنامه (Application Runtimes) را در «اجزای یک سیستم زیرساختی» در صفحه ۲۳ معرفی کردم؛ بخشی از مدلی که اجزای سیستم را در سه لایه سازماندهی میکند. در این مدل، منابع لایه زیرساخت را ترکیب میکنید تا بسترهای اجرایی (runtime platform) فراهم کنید که افراد بتوانند برنامهها را روی آنها مستقر کنند.
زمانهای اجرای برنامه از پشتههای زیرساختی (infrastructure stack) تشکیل شدهاند که با ابزارهای مدیریت زیرساخت تعریف و ایجاد میکنید، همانطور که در فصل ۵ توضیح داده شد و در شکل ۱۰-۱ نشان داده شده است.
شکل ۱۰-۱. لایه برنامه متشکل از پشتههای زیرساختی
نقطه شروع برای طراحی و پیادهسازی زیرساخت زمان اجرای برنامه، درک برنامههایی است که از آن استفاده خواهند کرد. با چه زبان و پشته اجرایی کار میکنند؟ آیا بستهبندی و مستقر روی سرورها میشوند، در کانتینرها، یا بهصورت کد FaaS بدون سرور؟ آیا برنامههای تکی در یک مکان مستقر میشوند، یا سرویسهای متعدد در یک کلاستر توزیع شدهاند؟ نیازمندیهای اتصال و داده آنها چیست؟
پاسخ این سؤالات به درک منابع زیرساختی که لایه زمان اجرای برنامه باید برای اجرای برنامهها تأمین و مدیریت کند میرسد. اجزای لایه زمان اجرای برنامه با اجزای بستر زیرساختی که در «منابع زیرساختی» در صفحه ۲۷ توضیح دادم مطابقت دارند. اینها شامل محیط اجرا بر پایه منابع محاسباتی، مدیریت داده بر پایه منابع ذخیرهسازی، و اتصالپذیری متشکل از منابع شبکهای خواهند بود.
این فصل هر یک از این روابط را خلاصه میکند و بر روشهای سازماندهی منابع زیرساختی به بسترهای اجرایی برای برنامهها تمرکز دارد. زمینه را برای فصلهای بعدی — که جزئیات بیشتری درباره تعریف و مدیریت آن منابع بهصورت کد میدهند — آماده میکند: سرورها بهصورت کد (فصل ۱۱) و کلاسترها بهصورت کد (فصل ۱۴).
Cloud Native و زیرساخت مبتنی بر برنامه
نرمافزار cloud native طوری طراحی و پیادهسازی میشود که از ماهیت پویا و پویای زیرساخت مدرن بهره ببرد. برخلاف نرمافزار نسل قدیمیتر، نمونههای یک برنامه cloud native میتوانند بهصورت شفاف به زیرساخت زیرین اضافه، حذف و جابهجا شوند. بستر زیرین بهصورت پویا منابع محاسباتی و ذخیرهسازی را تخصیص میدهد و ترافیک را به سمت برنامه و از آن هدایت میکند. برنامه بهطور یکپارچه با سرویسهایی مثل نظارت، لاگبرداری، احراز هویت و رمزنگاری یکپارچه میشود.
افراد Heroku متدولوژی twelve-factor را برای ساخت برنامههایی که روی زیرساخت ابری اجرا میشوند بیان کردند. عبارت cloud native اغلب با اکوسیستم Kubernetes مرتبط است.[۱]
[۱] Cloud Native Computing Foundation هدفش تعیین استاندارد برای این ژانر است.
بسیاری از سازمانها پرتفوی نرمافزاری موجودی دارند که cloud native نیست. ممکن است بخشی از نرمافزار را تبدیل یا بازنویسی کنند تا cloud native شود، اما در بسیاری موارد هزینه این کار نسبت به مزایا توجیه نمیشود. استراتژی زیرساخت مبتنی بر برنامه (application-driven infrastructure) شامل ساخت محیطهای زمان اجرای برنامه برای برنامهها با استفاده از زیرساخت مدرن و پویا است.
تیمها زمان اجرای برنامه را برای برنامههای جدیدی که بهصورت کد بدون سرور یا در کانتینرها اجرا میشوند فراهم میکنند. همچنین زیرساختی برای پشتیبانی از برنامههای موجود تأمین میکنند. تمام زیرساخت بهصورت کد تعریف، تأمین و مدیریت میشود. زیرساخت مبتنی بر برنامه ممکن است بهصورت پویا با استفاده از لایه انتزاع (به «ساخت یک لایه انتزاع» در صفحه ۲۸۴ مراجعه کنید) ارائه شود.
اهداف زمان اجرای برنامه
پیادهسازی استراتژی مبتنی بر برنامه با تحلیل نیازمندیهای زمان اجرای پرتفوی برنامه شما آغاز میشود. سپس راهحلهای زمان اجرای برنامه را برای برآورده کردن آن نیازمندیها طراحی میکنید و پشتهها، اجزای پشته و عناصر دیگر قابل استفاده مجدد را پیادهسازی میکنید تا تیمها بتوانند محیطهایی برای برنامههای خاص بسازند.
بخشهای قابل استقرار یک برنامه
یک انتشار قابل استقرار برای برنامه ممکن است عناصر مختلفی داشته باشد. مستندات و متاداده را کنار بگذارید؛ نمونههایی از آنچه ممکن است بخشی از استقرار برنامه باشد عبارتاند از:
فایلهای اجرایی (Executables) هسته یک انتشار، فایل یا فایلهای اجرایی است — چه باینری باشند چه اسکریپت تفسیرشده. میتوانید کتابخانهها و فایلهای دیگر مورد استفاده executables را هم در این دسته بدانید.
پیکربندی سرور (Server configuration) بسیاری از بستههای استقرار برنامه تغییراتی در پیکربندی سرور ایجاد میکنند. اینها میتواند شامل حسابهای کاربری که فرایندها تحت آن اجرا میشوند، ساختار پوشهها و تغییرات در فایلهای پیکربندی سیستم باشد.
ساختارهای داده (Data structures) وقتی برنامه از پایگاه داده استفاده میکند، استقرار آن ممکن است schemaها را ایجاد یا بهروز کند. یک نسخه مشخص از schema معمولاً با نسخه executable مطابقت دارد، پس بهتر است اینها را با هم بستهبندی و مستقر کنید.
داده مرجع (Reference data) استقرار برنامه ممکن است پایگاه داده یا ذخیرهسازی دیگر را با مجموعه اولیهای از داده پر کند. این میتواند داده مرجعی باشد که با نسخههای جدید تغییر میکند، یا داده نمونهای که به کاربران در اولین نصب برنامه کمک میکند شروع کنند.
اتصالپذیری (Connectivity) استقرار برنامه ممکن است پیکربندی شبکه مثل پورتهای شبکه را مشخص کند. همچنین ممکن است عناصری برای پشتیبانی از اتصالپذیری داشته باشد، مثل گواهیها یا کلیدهایی برای رمزنگاری یا احراز هویت اتصالات.
پارامترهای پیکربندی (Configuration parameters) بسته استقرار برنامه ممکن است پارامترهای پیکربندی را تنظیم کند — چه با کپی فایلهای پیکربندی روی سرور، چه با push تنظیمات به یک registry.
میتوانید مرز بین برنامه و زیرساخت را در جاهای مختلف بکشید. ممکن است کتابخانه مورد نیاز را در بسته استقرار برنامه بگنجانید، یا آن را بخشی از زیرساخت تأمین کنید.
مثلاً یک image کانتینر معمولاً بیشتر سیستمعامل و همچنین برنامهای که روی آن اجرا میشود را شامل میشود. یک سرور یا پشته immutable (تغییرناپذیر) این را بیشتر پیش میبرد و برنامه و زیرساخت را در یک موجودیت واحد ترکیب میکند. در انتهای دیگر طیف، دیدهام کد زیرساخت کتابخانهها و فایلهای پیکربندی را برای برنامه خاص تأمین کند و بسیار کم چیز در خود بسته برنامه بماند.
این سؤال در نحوه سازماندهی codebase شما هم نقش دارد. آیا کد برنامه را با کد زیرساخت نگه میدارید، یا جدا؟ سؤال مرزهای کد بعداً در این کتاب بررسی میشود (فصل ۱۸ را ببینید)، اما یک اصل این است که معمولاً ایده خوبی است ساختار codebase را با قطعات قابل استقرار همراستا کنید.
بستههای استقرار
برنامهها اغلب در بستههای استقرار سازماندهی میشوند و قالب بسته به نوع محیط زمان اجرا بستگی دارد. نمونههایی از قالبهای بسته استقرار و زمانهای اجرای مرتبط در جدول ۱۰-۱ فهرست شدهاند.
جدول ۱۰-۱. نمونههایی از زمانهای اجرای هدف و قالبهای بسته برنامه
| زمان اجرای هدف | نمونه بستهها |
|---|---|
| سیستمعامل سرور | فایلهای Red Hat RPM، فایلهای Debian .deb، بستههای نصب Windows MSI |
| موتور زمان اجرای زبان | Ruby gems، بستههای Python pip، فایلهای Java .jar، .war و .ear |
| زمان اجرای کانتینر | Docker images |
| کلاسترهای برنامه | Kubernetes Deployment Descriptors، Helm charts |
| FaaS بدون سرور | Lambda deployment package |
قالب بسته استقرار استانداردی است که به ابزارهای استقرار یا زمانهای اجرا امکان میدهد بخشهای برنامه را استخراج و در جای درست قرار دهند.
استقرار برنامه روی سرورها
سرورها — چه فیزیکی چه مجازی — بستر زمان اجرای سنتی هستند. برنامه با قالب بستهبندی سیستمعامل مثل RPM، فایل .deb یا Windows MSI بستهبندی میشود. یا با قالب زمان اجرای زبان، مثل Ruby gem یا فایل Java .war. اخیراً imageهای کانتینر، مثل Docker images، بهعنوان قالب بستهبندی و استقرار برنامه روی سرورها محبوب شدهاند.
تعریف و تأمین سرورها بهصورت کد موضوع فصل ۱۱ است. این موضوع با استقرار برنامه همپوشانی دارد، با توجه به نیاز به تصمیمگیری درباره زمان و نحوه اجرای دستورات استقرار (به «پیکربندی یک نمونه سرور جدید» در صفحه ۱۸۵ مراجعه کنید).
بستهبندی برنامه در کانتینرها
کانتینرها وابستگیها را از سیستمعامل میکشند و در بسته برنامه — image کانتینر — میگنجانند، همانطور که در شکل ۱۰-۲ نشان داده شده است.[۲]
شکل ۱۰-۲. وابستگیها ممکن است روی host OS نصب شوند یا در کانتینرها بستهبندی شوند
[۲] Docker قالب غالب کانتینر است. جایگزینها شامل CoreOS rkt و Windows Containers هستند.
گنجاندن وابستگیها در کانتینر آن را بزرگتر از بستههای معمول سیستمعامل یا زبان میکند، اما چند مزیت دارد:
- کانتینر محیط یکنواختتری برای اجرای برنامه ایجاد میکند. بدون کانتینر، برنامه به کتابخانهها، پیکربندی، حسابهای کاربری و عناصر دیگری وابسته است که ممکن است روی سرورهای مختلف متفاوت باشند. کانتینر محیط زمان اجرا را همراه با برنامه و وابستگیهایش بستهبندی میکند.
- برنامه کانتینریشده عمدتاً از سروری که روی آن اجرا میشود جدا است، که انعطاف بیشتری برای محل اجرا میدهد.
- با بستهبندی زمینه سیستمعامل برنامه در image کانتینر، نیازمندیهای host server را ساده و استاندارد میکنید. سرور فقط باید ابزار اجرای کانتینر نصب داشته باشد، و تقریباً چیز دیگری لازم نیست.
- کاهش تنوع محیطهای زمان اجرا برای برنامه، تضمین کیفیت را بهبود میدهد. وقتی یک نمونه کانتینر را در یک محیط آزمایش میکنید، میتوانید با اطمینان معقول انتظار داشته باشید در محیطهای دیگر هم همینطور رفتار کند.
استقرار برنامه روی کلاسترهای سرور
مردم از قبل از رواج کلاسترهای برنامه مبتنی بر کانتینر، برنامه را روی گروهی از سرورها مستقر میکردند. مدل معمول این است که یک کلاستر سرور داشته باشید (همانطور که در «منابع محاسباتی» در صفحه ۲۸ توضیح داده شد) و مجموعه یکسانی از برنامهها را روی هر سرور اجرا کنید. برنامهها را همانطور که برای یک سرور بستهبندی میکنید بستهبندی میکنید و فرایند استقرار را برای هر سرور در pool تکرار میکنید — شاید با ابزار اسکریپتنویسی دستور از راه دور مثل Capistrano یا Fabric. شکل ۱۰-۳ را ببینید.
شکل ۱۰-۳. برنامهها روی هر سرور در کلاستر مستقر میشوند
اگر برنامه را روی چند سرور مستقر کنید، باید تصمیم بگیرید استقرار را چگونه هماهنگ کنید. آیا برنامه را همزمان روی همه سرورها مستقر میکنید؟ آیا باید کل سرویس را آفلاین کنید؟ یا یک سرور در هر بار ارتقا میدهید؟ میتوانید از استقرار تدریجی روی سرورها برای استراتژیهای استقرار پیشرونده مثل الگوهای blue-green و canary بهره ببرید (برای این استراتژیها «تغییر زیرساخت زنده» در صفحه ۳۶۸ را ببینید).
علاوه بر استقرار کد برنامه روی سرورها، ممکن است نیاز باشد عناصر دیگری مثل تغییرات ساختار داده یا اتصالپذیری را هم مستقر کنید.
استقرار برنامه روی کلاسترهای برنامه
همانطور که در «منابع محاسباتی» در صفحه ۲۸ بحث شد، کلاستر میزبانی برنامه poolی از سرورهاست که یک یا چند برنامه را اجرا میکند. برخلاف کلاستر سرور، که هر سرور همان مجموعه برنامهها را اجرا میکند، سرورهای مختلف در کلاستر برنامه ممکن است گروههای متفاوتی از نمونههای برنامه را اجرا کنند (شکل ۱۰-۴ را ببینید).
شکل ۱۰-۴. برنامهها روی کلاستر مستقر میشوند و روی host nodeها توزیع میشوند
وقتی برنامه را روی کلاستر مستقر میکنید، scheduler تصمیم میگیرد نمونههای برنامه روی کدام host server اجرا شوند. scheduler ممکن است این توزیع را تغییر دهد و نمونههای برنامه را طبق الگوریتمها و تنظیمات مختلف در host serverها اضافه و حذف کند.
در گذشته نزدیک،[۳] محبوبترین کلاسترهای برنامه مبتنی بر Java بودند (Tomcat، Websphere، Weblogic، JBoss و غیره). چند سال پیش موجی از سیستمهای مدیریت کلاستر ظهور کرد، از جمله Apache Mesos و DC/OS، بسیاری الهامگرفته از Borg گوگل.[۴] در سالهای اخیر، سیستمهای متمرکز بر هماهنگی نمونه کانتینر بر application serverها و orchestratorهای کلاستر غلبه کردهاند.
[۳] «گذشته نزدیک» کمتر از ده سال پیش بود.
[۴] Borg سیستم مدیریت کلاستر داخلی و اختصاصی گوگل است که در مقاله «Large-Scale Cluster Management at Google with Borg» مستند شده.
تعریف و تأمین کلاسترها بهصورت کد موضوع فصل ۱۴ است. وقتی کلاستر را دارید، ممکن است مستقر کردن یک برنامه روی آن ساده باشد. برنامه را با Docker بستهبندی کنید و به کلاستر push کنید. اما برنامههای پیچیدهتر با قطعات بیشتر، نیازمندیهای پیچیدهتری دارند.
بستهها برای استقرار برنامه روی کلاسترها
برنامههای مدرن اغلب چند فرایند و جزء مستقرشده در زیرساخت پیچیده دارند. محیط زمان اجرا باید بداند این قطعات مختلف را چگونه اجرا کند:
- حداقل و حداکثر تعداد نمونهها برای اجرا چیست؟
- زمان اجرا چگونه بداند چه وقت نمونه اضافه یا حذف کند؟
- زمان اجرا چگونه بداند نمونه سالم است یا باید restart شود؟
- چه ذخیرهسازی باید تأمین و به هر نمونه متصل شود؟
- نیازمندیهای اتصالپذیری و امنیت چیست؟
بسترهای زمان اجرای مختلف قابلیتهای متفاوتی دارند و بسیاری قالب بستهبندی و پیکربندی خود را دارند. اغلب این بسترها از deployment manifest استفاده میکنند که به artifactهای واقعی استقرار (مثلاً image کانتینر) ارجاع میدهد، نه فایل آرشیوی که همه قطعات قابل استقرار را شامل شود. نمونههایی از manifestهای استقرار برنامه کلاستر:
- Helm charts برای کلاسترهای Kubernetes
- Weave Cloud Kubernetes deployment manifests
- AWS ECS Services، که میتوانید بهصورت کد با ابزار مدیریت پشته دلخواه تعریف کنید
- Azure App Service Plans
- CNAB Cloud Native Application Bundle
manifestها و بستههای استقرار مختلف در سطوح مختلف کار میکنند. برخی روی یک واحد قابل استقرار متمرکزند، پس برای هر برنامه یک manifest لازم است. برخی مجموعهای از سرویسهای قابل استقرار تعریف میکنند. بسته به ابزار، هر سرویس در مجموعه ممکن است manifest جدا داشته باشد، با manifest سطح بالاتر که عناصر مشترک و پارامترهای یکپارچهسازی را تعریف میکند.
manifest استقرار web server ShopSpinner ممکن است شبیه pseudocode نشاندادهشده در مثال ۱۰-۱ باشد.
مثال ۱۰-۱. نمونه manifest استقرار کلاستر برنامه
service:
name: webservers
organization: ShopSpinner
version: 1.0.3
application:
name: nginx
container_image:
repository: containers.shopspinner.xyz
path: /images/nginx
tag: 1.0.3
instance:
count_min: 3
count_max: 10
health_port: 443
health_url: /alive
connectivity:
inbound:
id: https_inbound
port: 443
allow_from: $PUBLIC_INTERNET
ssl_cert: $SHOPSPINNER_PUBLIC_SSL_CERT
outbound:
port: 443
allow_to: [ $APPSERVER_APPLICATIONS.https_inbound ]این مثال مشخص میکند image کانتینر (بلوک container_image) کجا و چگونه پیدا شود، چند نمونه اجرا شود و سلامت آنها چگونه بررسی شود. همچنین قوانین اتصال ورودی و خروجی را تعریف میکند.
استقرار برنامههای FaaS بدون سرور
در فصل ۳، برخی بسترهای زمان اجرای برنامه FaaS بدون سرور را بهعنوان نوعی منبع محاسباتی فهرست کردم (به «منابع محاسباتی» در صفحه ۲۸ مراجعه کنید). بیشتر آنها قالب خود را برای تعریف نیازمندیهای زمان اجرای برنامه و بستهبندی آنها با کد و قطعات مرتبط برای استقرار روی نمونه زمان اجرا دارند.
وقتی برنامه FaaS مینویسید، جزئیات هر سرور یا کانتینری که کد روی آن اجرا میشود از شما پنهان است. اما کد احتمالاً به زیرساخت نیاز دارد. مثلاً ممکن است به مسیریابی شبکه برای اتصالات ورودی یا خروجی، ذخیرهسازی یا صف پیام نیاز داشته باشید. فریمورک FaaS شما ممکن است با بستر زیرساخت زیرین یکپارچه شود و زیرساخت لازم را خودکار تأمین کند. یا ممکن است لازم باشد عناصر زیرساخت را در ابزار تعریف پشته جدا تعریف کنید. بسیاری از ابزارهای پشته مثل Terraform و CloudFormation به شما اجازه میدهند تأمین کد FaaS را بخشی از پشته زیرساخت اعلام کنید.
فصل ۱۴ برای تعریف و تأمین زمانهای اجرای FaaS برای اجرای کد شما مرتبط است.
داده برنامه
داده اغلب در استقرار و اجرای برنامه در نظر گرفته نمیشود. پایگاه داده و volumeهای ذخیرهسازی را تأمین میکنیم، اما همانطور که در بسیاری از بخشهای زیرساخت، تغییر دادن آنها سخت از آب درمیآید. تغییر داده و ساختارها زمانبر، نامرتب و پرریسک است.
استقرار برنامه اغلب شامل ایجاد یا تغییر ساختارهای داده است، از جمله تبدیل داده موجود وقتی ساختارها تغییر میکنند. بهروزرسانی ساختارهای داده باید نگرانی برنامه و فرایند استقرار برنامه باشد، نه بستر زیرساخت و زمان اجرا. با این حال، زیرساخت و سرویسهای زمان اجرای برنامه باید از حفظ داده وقتی زیرساخت و منابع زیرین دیگر تغییر میکنند یا از کار میافتند پشتیبانی کنند. برای رویکرد این چالش «تداوم داده در سیستم در حال تغییر» در صفحه ۳۸۲ را ببینید.
Schemaها و ساختارهای داده
برخی ذخیرهسازی داده ساختاریافته سختگیرانه است، مثل پایگاههای SQL، در حالی که برخی بدون ساختار یا schema-less هستند. پایگاه داده schema-driven ساختار را روی داده اعمال میکند و از ذخیره داده با قالب نادرست خودداری میکند. برنامههایی که از پایگاه schema-less استفاده میکنند مسئول مدیریت قالب داده هستند.
انتشار جدید برنامه ممکن است شامل تغییر ساختارهای داده باشد. برای پایگاه schema-driven، این شامل تغییر تعریف ساختارهای داده در پایگاه داده است. مثلاً انتشار ممکن است فیلد جدیدی به رکوردهای داده اضافه کند — نمونه کلاسیک تقسیم فیلد «name» واحد به فیلدهای جدا برای نام، نام میانی و نام خانوادگی است.
وقتی ساختارهای داده تغییر میکنند، هر داده موجودی باید به ساختار جدید تبدیل شود — با هر دو نوع پایگاه داده. اگر فیلد «name» را تقسیم میکنید، به فرایندی نیاز دارید که نامها در پایگاه داده را به فیلدهای جدا تقسیم کند.
تغییر ساختارهای داده و تبدیل داده مهاجرت schema (schema migration) نامیده میشود. چند ابزار و کتابخانه وجود دارد که برنامهها و ابزارهای استقرار میتوانند برای مدیریت این فرایند استفاده کنند، از جمله Flyway، DBDeploy، Liquibase و db-migrate.[۵] توسعهدهندگان میتوانند با این ابزارها تغییرات تدریجی پایگاه داده را بهصورت کد تعریف کنند. تغییرات را میتوان در کنترل نسخه check in کرد و بخشی از انتشار بستهبندی کرد. این کمک میکند schemaهای پایگاه داده با نسخه برنامه مستقرشده روی نمونه همگام بمانند.
[۵] DBDeploy این سبک مهاجرت schema پایگاه داده را — همراه با Ruby on Rails — محبوب کرد. اما پروژه در حال حاضر نگهداری نمیشود.
تیمها میتوانند از استراتژیهای تکامل پایگاه داده برای مدیریت ایمن و انعطافپذیر تغییرات مکرر داده و schema استفاده کنند. این استراتژیها با رویکردهای مهندسی نرمافزار Agile، از جمله CI و CD، و همچنین Infrastructure as Code همراستا هستند.[۶]
[۶] برای استراتژیها و تکنیکهای تکامل پایگاه داده، «Evolutionary Database Design» اثر Pramod Sadalage، Refactoring Databases اثر Scott Ambler و Pramod Sadalage (Addison-Wesley Professional)، و Database Reliability Engineering اثر Laine Campbell و Charity Majors (O'Reilly) را ببینید.
زیرساخت ذخیرهسازی برنامه Cloud Native
زیرساخت cloud native بهصورت پویا و بر اساس تقاضا به برنامهها و سرویسها تخصیص مییابد. برخی بسترها ذخیرهسازی cloud native و همچنین محاسبه و شبکه ارائه میدهند. وقتی سیستم نمونه برنامه اضافه میکند، میتواند خودکار دستگاه ذخیرهسازی را تأمین و متصل کند. نیازمندیهای ذخیرهسازی، از جمله هر قالببندی یا دادهای که هنگام تأمین بارگذاری شود، را در manifest استقرار برنامه مشخص میکنید (مثال ۱۰-۲ را ببینید).
مثال ۱۰-۲. نمونه manifest استقرار برنامه با ذخیرهسازی داده
application:
name: db_cluster
compute_instance:
memory: 2 GB
container_image: db_cluster_node_application
storage_volume:
size: 50 GB
volume_image: db_cluster_node_volumeاین مثال ساده نحوه ایجاد node برای کلاستر پایگاه داده مقیاسپذیر پویا را تعریف میکند. برای هر نمونه node، بستر نمونه کانتینر با نرمافزار پایگاه داده ایجاد میکند و volume دیسکی که از image با segment پایگاه داده خالی مقداردهیشده clone شده متصل میکند. وقتی نمونه boot میشود، به کلاستر متصل میشود و داده را با volume محلی همگام میکند.
اتصالپذیری برنامه
علاوه بر منابع محاسباتی برای اجرای کد و منابع ذخیرهسازی برای نگهداشتن داده، برنامهها به شبکه برای اتصال ورودی و خروجی نیاز دارند. بسته برنامه مبتنی بر سرور، مثل web server، ممکن است پورتهای شبکه را پیکربندی کند و کلیدهای رمزنگاری برای اتصالات ورودی اضافه کند. اما سنتیاً به کسی وابسته بودند که زیرساخت خارج از سرور را جداگانه پیکربندی کند.
میتوانید آدرسدهی، مسیریابی، نامگذاری، قوانین فایروال و نگرانیهای مشابه را بخشی از پروژه پشته زیرساخت تعریف و مدیریت کنید، و سپس برنامه را در زیرساخت حاصل مستقر کنید. رویکرد cloud nativeتر برای شبکه، تعریف نیازمندیهای شبکه بهعنوان بخشی از manifest استقرار برنامه و تخصیص پویا منابع توسط زمان اجرای برنامه است.
بخشی از مثال ۱۰-۱ را اینجا میبینید:
application:
name: nginx
connectivity:
inbound:
id: https_inbound
port: 443
allow_from: $PUBLIC_INTERNET
ssl_cert: $SHOPSPINNER_PUBLIC_SSL_CERT
outbound:
port: 443
allow_to: [ $APPSERVER_APPLICATIONS.https_inbound ]این مثال اتصالات ورودی و خروجی را تعریف میکند و به بخشهای دیگر سیستم ارجاع میدهد: اینترنت عمومی، احتمالاً یک gateway، و پورتهای HTTPS ورودی برای application serverها که با manifestهای استقرار خودشان روی همان کلاستر تعریف و مستقر شدهاند.
زمانهای اجرای برنامه بسیاری سرویسهای مشترک را به برنامهها ارائه میدهند. بسیاری از این سرویسها انواع service discovery هستند.
Service Discovery
برنامهها و سرویسهای در حال اجرا در زیرساخت اغلب باید بدانند چگونه برنامهها و سرویسهای دیگر را پیدا کنند. مثلاً برنامه وب frontend ممکن است درخواستها را به سرویس backend برای پردازش تراکنش کاربران بفرستد.
در محیط ایستا این کار سخت نیست. برنامهها ممکن است hostname شناختهشده برای سرویسهای دیگر استفاده کنند — شاید در فایل پیکربندی که در صورت نیاز بهروز میکنید.
اما در زیرساخت پویا که مکان سرویسها و سرورها سیال است، روش واکنشگراتری برای یافتن سرویسها لازم است.
چند مکانیزم discovery محبوب:
آدرسهای IP هاردکدشده (Hardcoded IP addresses) برای هر سرویس آدرس IP تخصیص دهید. مثلاً سرور نظارت همیشه روی 192.168.1.5 اجرا میشود. اگر باید آدرس را تغییر دهید یا چند نمونه از سرویس اجرا کنید (مثلاً برای rollout کنترلشده ارتقای بزرگ)، باید برنامهها را rebuild و دوباره مستقر کنید.
ورودیهای Hostfile (Hostfile entries) از پیکربندی سرور برای تولید فایل /etc/hosts (یا معادل) روی هر سرور استفاده کنید و نام سرویسها را به آدرس IP فعلی map کنید. این روش جایگزین نامرتبتری برای DNS است، اما دیدهام برای دور زدن پیادهسازیهای legacy DNS استفاده شده.[۷]
DNS (Domain Name System) از ورودیهای DNS برای map کردن نام سرویسها به آدرس IP فعلی استفاده کنید — چه با ورودیهای DNS مدیریتشده با کد، چه DDNS (Dynamic DNS). DNS راهحل بالغ و پشتیبانیشدهای برای این مسئله است.
برچسبهای منبع (Resource tags) منابع زیرساخت با tag مشخص میشوند که چه سرویسهایی ارائه میدهند و context مثل محیطها. discovery شامل استفاده از API بستر برای جستجوی منابع با tagهای مرتبط است. باید مراقب بود از coupling کد برنامه به بستر زیرساخت اجتناب کرد.
Registry پیکربندی (Configuration registry) نمونههای برنامه میتوانند جزئیات اتصال فعلی را در registry متمرکز نگه دارند (به «Registry پیکربندی» در صفحه ۹۹ مراجعه کنید) تا برنامههای دیگر آن را lookup کنند. وقتی به اطلاعات بیشتری از آدرس نیاز دارید — مثلاً سلامت یا سایر نشانههای وضعیت — مفید است.
Sidecar فرایند جدا کنار هر نمونه برنامه اجرا میشود. برنامه ممکن است sidecar را بهعنوان proxy برای اتصالات خروجی، gateway برای اتصالات ورودی، یا سرویس lookup استفاده کند. sidecarها خود به روشی برای network discovery نیاز دارند — یکی از مکانیزمهای دیگر، یا شاید پروتکل ارتباطی متفاوت.[۸] sidecarها معمولاً بخشی از service mesh هستند (service mesh را در «Service Mesh» در صفحه ۲۴۴ بحث میکنم) و اغلب بیش از service discovery ارائه میدهند. مثلاً sidecar ممکن است احراز هویت، رمزنگاری، لاگبرداری و نظارت را مدیریت کند.
API gateway API gateway سرویس HTTP متمرکزی است که routeها و endpointها را تعریف میکند. معمولاً سرویسهای بیشتری هم ارائه میدهد — مثلاً احراز هویت، رمزنگاری، لاگبرداری و نظارت. به عبارت دیگر، API gateway شبیه sidecar است، اما متمرکز بهجای توزیعشده.[۹]
[۷] نمونه معمول «پیادهسازی DNS سادهلوحانه» سازمانهایی است که به تیمها دسترسی تغییر ورودی DNS نمیدهند — معمولاً چون اتوماسیون و فرایندهای حاکمیت مدرنی ندارند که انجام ایمن آن را ممکن کند.
[۸] مستندات HashiCorp Consul توضیح میدهد sidecarهای آن چگونه ارتباط برقرار میکنند.
[۹] همکارانم نگرانی درباره تمایل به متمرکز کردن business logic در API gatewayها بیان کردهاند. Overambitious API gateways در ThoughtWorks Technology Radar را برای جزئیات بیشتر ببینید.
از مرزهای سخت بین زیرساخت، زمان اجرا و برنامه اجتناب کنید
در تئوری، ارائه زمانهای اجرای برنامه بهعنوان مجموعه کامل سرویسها به توسعهدهندگان — و پنهان کردن جزئیات زیرساخت زیرین از آنها — مفید میتواند باشد. در عمل، خطوط خیلی کدرتر از آنچه در این مدل نشان داده شده است. افراد و تیمهای مختلف به منابع در سطوح مختلف انتزاع با سطوح مختلف کنترل نیاز دارند. پس نباید سیستمها را با مرزهای مطلق طراحی و پیادهسازی کنید، بلکه قطعاتی تعریف کنید که بتوان ترکیب و به روشهای مختلف به کاربران مختلف ارائه کرد.
جمعبندی
هدف زیرساخت، اجرای برنامهها و سервیسهای مفید است. راهنمایی و ایدههای این کتاب باید در ارائه مجموعههایی از منابع زیرساختی در شکلهای لازم برای این کار کمک کند. رویکرد مبتنی بر برنامه به زیرساخت بر نیازمندیهای زمان اجرای برنامهها متمرکز است و به طراحی پشتهها، سرورها، کلاسترها و سازههای میانی دیگر برای اجرای برنامهها کمک میکند.