حالت تاریک
فصل ۵ — ساخت پشتههای زیرساخت بهصورت کد
این فصل فصلهای ۳ و ۴ را ترکیب میکند و روشهای استفاده از کد برای مدیریت منابع زیرساخت تأمینشده از یک پلتفرم زیرساخت را توضیح میدهد.
مفهومی که برای بحث دربارهٔ این کار استفاده میکنم پشتهٔ زیرساخت (infrastructure stack) است. یک پشته مجموعهای از منابع زیرساخت است که با هم تعریف و تغییر میکنند. منابع یک پشته با هم و با ابزار مدیریت پشته تأمین میشوند تا یک نمونهٔ پشته (stack instance) ساخته شود. تغییرات روی کد پشته اعمال و با همان ابزار روی یک نمونه اعمال میشوند.
این فصل الگوهای گروهبندی منابع زیرساخت در پشتهها را توضیح میدهد.
پشتهٔ زیرساخت چیست؟
یک پشتهٔ زیرساخت مجموعهای از منابع زیرساخت است که بهعنوان یک واحد تعریف، تأمین و بهروزرسانی میکنید (شکل ۵-۱).
کد منبع مینویسید تا عناصر یک پشته را تعریف کنید — منابع و سرویسهایی که پلتفرم زیرساخت شما فراهم میکند. مثلاً پشتهٔ شما ممکن است شامل یک virtual machine («منابع محاسباتی» در صفحهٔ ۲۸)، disk volume («منابع ذخیرهسازی» در صفحهٔ ۲۹) و subnet («منابع شبکه» در صفحهٔ ۳۱) باشد.
یک ابزار مدیریت پشته اجرا میکنید که کد منبع پشته را میخواند و با API پلتفرم cloud عناصر تعریفشده در کد را برای تأمین یک نمونه از پشتهٔ شما assemble میکند.
شکل ۵-۱. یک پشتهٔ زیرساخت مجموعهای از عناصر زیرساخت است که بهصورت گروهی مدیریت میشوند
نمونههایی از ابزارهای مدیریت پشته:
- HashiCorp Terraform
- AWS CloudFormation
- Azure Resource Manager
- Google Cloud Deployment Manager
- OpenStack Heat
- Pulumi
- Bosh
برخی ابزارهای پیکربندی سرور (که در فصل ۱۱ بیشتر دربارهٔ آنها صحبت میکنم) extension برای کار با پشتههای زیرساخت دارند. مثالها Ansible Cloud Modules، Chef Provisioning (اکنون end-of-life)، Puppet Cloud Management و Salt Cloud هستند.
«پشته» بهعنوان اصطلاح
بیشتر ابزارهای مدیریت پشته خود را ابزار مدیریت پشته نمینامند. هر ابزار اصطلاح خود را برای واحد زیرساختی که مدیریت میکند دارد. در این کتاب الگوها و روشهایی را توضیح میدهم که برای هر یک از این ابزارها relevant باشند.
من کلمهٔ stack را انتخاب کردهام.
افراد مختلف به من گفتهاند اصطلاح بهتری برای این مفهوم وجود دارد. هر کدام کلمهٔ کاملاً متفاوتی در ذهن داشتند. تا زمان نوشتن این کتاب، در صنعت توافقی دربارهٔ نام این چیز وجود ندارد. پس تا وقتی باشد، از کلمهٔ stack استفاده میکنم.
کد پشته
هر پشته با کد منبعی تعریف میشود که عناصر زیرساختی که باید شامل شود را declarative میکند. کد Terraform (فایلهای .tf) و templateهای CloudFormation هر دو نمونهٔ کد پشتهٔ زیرساخت هستند. یک پروژهٔ پشته (stack project) کد منبعی را دارد که زیرساخت یک پشته را تعریف میکند.
مثال ۵-۱ ساختار پوشهٔ یک پروژهٔ کد منبع پشته برای ابزار خیالی Stackmaker را نشان میدهد.
مثال ۵-۱. ساختار پوشهٔ پروژهٔ پشته با ابزار خیالی
text
stack-project/
├── src/
│ ├── dns.infra
│ ├── load_balancers.infra
│ ├── networking.infra
│ └── webserver.infra
└── test/نمونهٔ پشته
میتوانید از یک پروژهٔ پشته برای تأمین بیش از یک نمونهٔ پشته استفاده کنید. وقتی ابزار پشته را برای پروژه اجرا میکنید، از API پلتفرم استفاده میکند تا مطمئن شود نمونهٔ پشته وجود دارد و با کد پروژه مطابقت دارد. اگر نمونه وجود نداشته باشد، ابزار آن را میسازد. اگر وجود دارد اما دقیقاً با کد مطابقت ندارد، ابزار نمونه را تغییر میدهد تا مطابقت پیدا کند.
اغلب این فرایند را «اعمال» (applying) کد روی یک نمونه توصیف میکنم.
اگر کد را تغییر دهید و ابزار را دوباره اجرا کنید، نمونهٔ پشته را طوری تغییر میدهد که با تغییرات شما مطابقت داشته باشد. اگر یک بار دیگر بدون تغییر کد ابزار را اجرا کنید، باید نمونهٔ پشته را همانطور که بود بگذارد.
پیکربندی سرورها در یک پشته
codebaseهای کد زیرساخت برای سیستمهایی که معماری برنامهٔ کاملاً container-based یا serverless نیستند، معمولاً کد زیادی برای تأمین و پیکربندی سرور دارند. حتی سیستمهای container-based هم برای اجرای کانتینرها به ساخت host server نیاز دارند.
اولین ابزارهای mainstream Infrastructure as Code، مانند CFEngine، Puppet و Chef، برای پیکربندی سرور استفاده میشدند.
باید کدی که سرورها را میسازد از کدی که پشتهها را میسازد جدا کنید. این کار کد را قابلفهمتر میکند، تغییرات را با decouple کردن سادهتر میکند و استفادهٔ مجدد و آزمون کد سرور را پشتیبانی میکند.
کد پشته معمولاً مشخص میکند چه سرورهایی ساخته شوند و با فراخوانی یک ابزار پیکربندی سرور، اطلاعاتی دربارهٔ محیطی که در آن اجرا میشوند را پاس میدهد. مثال ۵-۲ نمونهای از تعریف پشته است که ابزار خیالی servermaker را برای پیکربندی یک سرور فراخوانی میکند.
مثال ۵-۲. نمونهٔ تعریف پشته که ابزار پیکربندی سرور را فراخوانی میکند
text
virtual_machine:
name: appserver-waterworks-${environment}
source_image: shopspinner-base-appserver
memory: 4GB
provision:
tool: servermaker
parameters:
maker_server: maker.shopspinner.xyz
role: appserver
environment: ${environment}این پشته یک نمونهٔ سرور برنامه را تعریف میکند، ساختهشده از تصویر سروری به نام shopspinner-appserver، با ۴ گیگابایت RAM. تعریف شامل بندی برای trigger کردن فرایند provisioning است که Servermaker را اجرا میکند. کد چند پارامتر برای ابزار Servermaker هم پاس میدهد. این پارامترها شامل آدرس یک configuration server (maker_server) که فایلهای پیکربندی را host میکند؛ و نقش appserver که Servermaker برای تصمیمگیری دربارهٔ پیکربندیهای اعمالشده روی این سرور خاص استفاده میکند. همچنین نام محیط را پاس میدهد که پیکربندیها میتوانند برای سفارشیسازی سرور از آن استفاده کنند.
زبانهای زیرساخت سطح پایین
بیشتر زبانهای محبوب ابزار مدیریت پشته، زبانهای زیرساخت سطح پایین (low-level infrastructure languages) هستند. یعنی زبان مستقیماً منابع زیرساختی را expose میکند که پلتفرم زیرساخت کاری با آن دارید (انواع منابع فهرستشده در «منابع زیرساخت» در صفحهٔ ۲۷).
کار شما بهعنوان coder زیرساخت این است که کدی بنویسید که این منابع را به چیز مفیدی wire کند، مانند مثال ۵-۳.
مثال ۵-۳. نمونهٔ کد پشتهٔ زیرساخت سطح پایین
text
address_block:
name: application_network_tier
address_range: 10.1.0.0/24"
vlans:
- appserver_vlan_A
address_range: 10.1.0.0/16
virtual_machine:
name: shopspinner_appserver_A
vlan: application_network_tier.appserver_vlan_A
gateway:
name: public_internet_gateway
address_block: application_network_tier
inbound_route:
gateway: public_internet_gateway
public_ip: 192.168.99.99
incoming_port: 443
destination:
virtual_machine: shopspinner_appserver_A
port: 8443این مثال pseudocode ساختگی و سادهشده یک virtual machine، address block و VLAN، و internet gateway را تعریف میکند. سپس آنها را wire میکند و اتصال inbound تعریف میکند که اتصالات ورودی به https://192.168.99.99 را به port 8443 روی virtual machine route میکند.¹
پلتفرم خود ممکن است لایهٔ abstraction سطح بالاتری فراهم کند؛ مثلاً application hosting cluster. عنصر cluster که پلتفرم فراهم میکند ممکن است بهطور خودکار نمونههای سرور و network route را تأمین کند. اما کد زیرساخت سطح پایین مستقیماً به منابع و گزینههای exposeشده توسط API پلتفرم map میشود.
زبانهای زیرساخت سطح بالا
یک زبان زیرساخت سطح بالا (high-level infrastructure language) entityهایی را تعریف میکند که مستقیماً به منابع فراهمشده توسط پلتفرم زیرین map نمیشوند. مثلاً نسخهٔ سطح بالای کد مثال ۵-۳ ممکن است اصول سرور برنامه را declarative کند، همانطور که در مثال ۵-۴ نشان داده شده.
مثال ۵-۴. نمونهٔ کد پشتهٔ زیرساخت سطح بالا
text
application_server:
public_ip: 192.168.99.99در این مثال، اعمال کد یا منابع networking و سرور از مثال قبل را تأمین میکند، یا منابع موجود را برای استفاده کشف میکند. ابزار یا کتابخانهای که این کد فراخوانی میکند تصمیم میگیرد برای network port و VLAN چه مقادیری استفاده شود و virtual server چگونه ساخته شود.
بسیاری از راهحلهای hosting برنامه، مانند پلتفرم PaaS یا packaged cluster (نگاه کنید به «توزیع cluster بستهبندیشده» در صفحهٔ ۲۳۱)، این سطح abstraction را فراهم میکنند. deployment descriptor برنامه را مینویسید و پلتفرم منابع زیرساخت را برای استقرار آن allocate میکند.
در موارد دیگر، ممکن است لایهٔ abstraction خود را با نوشتن کتابخانه یا ماژول بسازید. برای اطلاعات بیشتر به فصل ۱۶ مراجعه کنید.
الگوها و anti-patternها برای ساختاردهی پشتهها
یکی از چالشهای طراحی زیرساخت، تصمیمگیری دربارهٔ اندازه و ساختار پشتههاست. میتوانید یک پروژهٔ کد پشته برای مدیریت کل سیستم بسازید. اما با رشد سیستم این unwieldy میشود. در این بخش الگوها و anti-patternها برای ساختاردهی پشتههای زیرساخت را توضیح میدهم.
الگوهای زیر همگی روشهای گروهبندی قطعات یک سیستم در یک یا چند پشته را توصیف میکنند. میتوانید آنها را بهعنوان یک continuum ببینید:
- یک پشتهٔ monolithic کل سیستم را در یک پشته قرار میدهد.
- یک پشتهٔ application group چندین قطعهٔ مرتبط یک سیستم را در پشتهها گروهبندی میکند.
- یک پشتهٔ service همهٔ زیرساخت یک برنامه را در یک پشته قرار میدهد.
- یک micro stack زیرساخت یک برنامه یا سرویس را به چندین پشته تقسیم میکند.
Anti-pattern: پشتهٔ monolithic
یک پشتهٔ monolithic پشتهٔ زیرساختی است که عناصر بیش از حد شامل میشود و نگهداری آن را دشوار میکند (شکل ۵-۲).
شکل ۵-۲. یک پشتهٔ monolithic پشتهٔ زیرساختی است که عناصر بیش از حد شامل میشود و نگهداری آن را دشوار میکند
آنچه پشتهٔ monolithic را از الگوهای دیگر متمایز میکند این است که تعداد یا رابطهٔ عناصر زیرساخت درون پشته بهخوبی قابلمدیریت نیست.
انگیزه
مردم پشتههای monolithic میسازند چون سادهترین راه افزودن عنصر جدید به سیستم، افزودن آن به پروژهٔ موجود است. هر پشتهٔ جدید moving part بیشتری اضافه میکند که ممکن است نیاز به orchestrate، integrate و test داشته باشند.
یک پشتهٔ واحد سادهتر برای مدیریت است. برای مجموعهٔ modest از عناصر زیرساخت، پشتهٔ monolithic ممکن است منطقی باشد. اما اغلبتر، پشتهٔ monolithic بهطور organic از کنترل خارج میشود.
قابلاعمال بودن
پشتهٔ monolithic وقتی سیستم کوچک و ساده است ممکن است مناسب باشد. وقتی سیستم رشد میکند و تأمین و بهروزرسانی طولانیتر میشود، مناسب نیست.
پیامدها
تغییر یک پشتهٔ بزرگ riskier از تغییر پشتهٔ کوچکتر است. چیزهای بیشتری ممکن است اشتباه شود — blast radius بزرگتری دارد. تأثیر یک تغییر ناموفق ممکن است گستردهتر باشد چون سرویسها و برنامههای بیشتری درون پشته هستند. پشتههای بزرگتر هم کندتر تأمین و تغییر میکنند که مدیریت آنها را سختتر میکند.
در نتیجهٔ سرعت و ریسک تغییر پشتهٔ monolithic، مردم تمایل دارند کمتر تغییر دهند و بیشتر طول بکشد. این اصطکاک اضافی میتواند به technical debt بالاتر منجر شود.
Blast Radius
blast radius فوری دامنهٔ کدی است که دستور اعمال تغییر شما شامل میشود.² مثلاً وقتی
terraform applyاجرا میکنید، blast radius مستقیم همهٔ کد پروژهٔ شما را شامل میشود. blast radius غیرمستقیم عناصر دیگر سیستم را شامل میشود که به منابع در blast radius مستقیم وابستهاند و ممکن است با خراب شدن آن منابع تحت تأثیر قرار گیرند.
پیادهسازی
پشتهٔ monolithic را با ساخت یک پروژهٔ پشتهٔ زیرساخت و سپس افزودن مداوم کد میسازید، نه تقسیم آن به چندین پشته.
الگوهای مرتبط
مخالف پشتهٔ monolithic، micro stack است (نگاه کنید به «الگو: Micro Stack» در صفحهٔ ۶۲) که هدفش کوچک نگهداشتن پشتهها برای نگهداری و بهبود آسانتر است. پشتهٔ monolithic ممکن است یک application group stack (نگاه کنید به «الگو: Application Group Stack» در صفحهٔ ۵۹) باشد که از کنترل خارج شده.
آیا پشتهٔ من monolith است؟
اینکه پشتهٔ زیرساخت شما monolith است یا نه موضوع قضاوت است. علائم پشتهٔ monolithic شامل:
- درک اینکه قطعات پشته چگونه به هم میخورند دشوار است (ممکن است خیلی messy باشند یا شاید خوب fit نکنند).
- افراد جدید مدتی طول میکشد codebase پشته را یاد بگیرند.
- اشکالزدایی مشکلات پشته سخت است.
- تغییرات پشته مکرراً مشکل ایجاد میکنند.
- زمان زیادی صرف نگهداری سیستمها و فرایندهایی میکنید که هدفشان مدیریت پیچیدگی پشته است.
یک شاخص کلیدی اینکه آیا پشته monolithic میشود، چند نفر در هر زمان روی تغییرات آن کار میکنند. هرچه رایجتر باشد چند نفر همزمان روی پشته کار کنند، بیشتر زمان صرف هماهنگی تغییرات میشود. چند تیم که روی یک پشته تغییر میدهند بدتر است. اگر مکرراً هنگام deploy تغییرات روی یک پشته failure و conflict دارید، شاید خیلی بزرگ شده.
feature branching استرategی برای مقابله با این است، اما میتواند اصطکاک و overhead به تحویل اضافه کند. استفادهٔ عادتی feature branch برای کار روی یک پشته پیشنهاد میکند پشته monolithic شده.
CI راه پایدارتر برای ایمنتر کردن کار چند نفر روی یک پشته است. با این حال، با monolithicتر شدن پشته، build CI بیشتر طول میکشد و حفظ انضباط build خوب سختتر میشود. اگر CI تیم sloppy است، نشانهٔ دیگری است که پشته monolith است.
این مسائل مربوط به یک تیم که روی یک پشته کار میکند است. چند تیم روی یک پشتهٔ shared نشانهٔ واضحی است که آن را به قطعات manageableتر تقسیم کنید.
الگو: Application Group Stack
یک application group stack زیرساخت چندین برنامه یا سرویس مرتبط را شامل میشود. زیرساخت همهٔ این برنامهها بهصورت گروهی تأمین و مدیریت میشود (شکل ۵-۳).
شکل ۵-۳. یک application group stack چندین process را در یک نمونه از پشته host میکند
مثلاً application stack محصول ShopSpinner شامل سرویسهای جداگانه برای browse محصول، جستجوی محصول و مدیریت سبد خرید است. سرورها و زیرساخت دیگر همهٔ اینها در یک نمونهٔ پشته ترکیب شدهاند.
انگیزه
تعریف زیرساخت چندین سرویس مرتبط با هم میتواند مدیریت برنامه را بهعنوان یک واحد آسانتر کند.
قابلاعمال بودن
این الگو وقتی خوب کار میکند که یک تیم مالک زیرساخت و استقرار همهٔ قطعات برنامه باشد. application group stack میتواند مرزهای پشته را با مسئولیتهای تیم همراستا کند.
multiservice stack گاهی گام incremental از monolithic stack به service stack مفید است.
پیامدها
گروهبندی زیرساخت چندین برنامه با هم، زمان، ریسک و سرعت تغییرات را هم ترکیب میکند. تیم باید ریسک کل پشته را برای هر تغییر مدیریت کند، حتی اگر فقط یک بخش تغییر کند. این الگو ناکارآمد است اگر برخی بخشهای پشته مکررتر از بقیه تغییر کنند.
زمان تأمین، تغییر و آزمون یک پشته بر اساس کل پشته است. پس اگر رایج است فقط یک بخش پشته در هر زمان تغییر کند، گروهبندی overhead و ریسک غیرضروری اضافه میکند.
پیادهسازی
برای ساخت application group stack، پروژهٔ زیرساختی تعریف میکنید که همهٔ زیرساخت مجموعهای از سرویسها را میسازد. میتوانید همهٔ قطعات برنامه را با یک دستور تأمین و destroy کنید.
الگوهای مرتبط
این الگو خطر رشد به monolithic stack دارد (نگاه کنید به «Anti-pattern: پشتهٔ monolithic» در صفحهٔ ۵۶). در جهت دیگر، جدا کردن هر سرویس در application group stack به پشتهٔ جداگانه، service stack میسازد.
الگو: Service Stack
یک service stack زیرساخت هر component قابلاستقرار برنامه را در یک پشتهٔ زیرساخت جداگانه مدیریت میکند (شکل ۵-۴).
شکل ۵-۴. یک service stack زیرساخت هر component قابلاستقرار برنامه را در یک پشتهٔ زیرساخت جداگانه مدیریت میکند
انگیزه
service stack مرزهای زیرساخت را با نرمافزاری که روی آن اجرا میشود همراستا میکند. این همراستایی blast radius یک تغییر را به یک سرویس محدود میکند که فرایند زمانبندی تغییرات را ساده میکند. تیمهای سرویس میتوانند زیرساخت مرتبط با نرمافزار خود را مالک باشند.
قابلاعمال بودن
service stack میتواند با معماری microservice برنامه³ خوب کار کند. همچنین به سازمانهایی با تیمهای autonomous کمک میکند هر تیم زیرساخت خود را مالک باشد.⁴
پیامدها
اگر چندین برنامه دارید و هر کدام یک پشتهٔ زیرساخت، ممکن است duplication غیرضروری کد وجود داشته باشد. مثلاً هر پشته ممکن است کدی داشته باشد که نحوهٔ تأمین application server را مشخص میکند. duplication میتواند inconsistency تشویق کند، مانند استفاده از نسخههای مختلف OS یا پیکربندی شبکهٔ متفاوت. میتوانید با استفاده از ماژول برای اشتراک کد (همانطور که در فصل ۱۶) این را mitigate کنید.
پیادهسازی
هر برنامه یا سرویس یک پروژهٔ کد زیرساخت جداگانه دارد. هنگام ساخت برنامهٔ جدید، تیم ممکن است کد را از زیرساخت برنامهٔ دیگر copy کند. یا از پروژهٔ reference با boilerplate code برای ساخت پشتههای جدید استفاده کند.
در برخی موارد، هر پشته ممکن است complete باشد و زیرساختی با پشتههای برنامهٔ دیگر share نکند. در موارد دیگر، تیمها ممکن است پشتههایی با زیرساختی بسازند که از چندین application stack پشتیبانی میکند. دربارهٔ الگوهای مختلف برای این در فصل ۱۷ بیشتر میآموزید.
الگوهای مرتبط
الگوی service stack بین application group stack («الگو: Application Group Stack» در صفحهٔ ۵۹) که چندین برنامه در یک پشته دارد، و micro stack که زیرساخت یک برنامه را بین چندین پشته تقسیم میکند قرار دارد.
الگو: Micro Stack
الگوی micro stack زیرساخت یک سرویس را بین چندین پشته تقسیم میکند (شکل ۵-۵).
شکل ۵-۵. micro stackها زیرساخت یک سرویس را بین چندین پشته تقسیم میکنند
مثلاً ممکن است پروژهٔ پشتهٔ جداگانهای برای networking، سرورها و database داشته باشید.
انگیزه
بخشهای مختلف زیرساخت یک سرویس ممکن است با نرخهای متفاوت تغییر کنند. یا ویژگیهای متفاوتی دارند که مدیریت جداگانه را آسانتر میکند. مثلاً برخی روشهای مدیریت نمونهٔ سرور شامل destroy و rebuild مکرر آنهاست.⁵ با این حال، برخی سرویسها از دادهٔ persistent در database یا disk volume استفاده میکنند. مدیریت سرورها و داده در پشتههای جداگانه یعنی life cycle متفاوت — پشتهٔ سرور خیلی بیشتر از پشتهٔ داده rebuild میشود.
پیامدها
اگرچه پشتههای کوچکتر خودشان سادهترند، داشتن moving part بیشتر پیچیدگی اضافه میکند. فصل ۱۷ تکنیکهای مدیریت integration بین چندین پشته را توضیح میدهد.
پیادهسازی
افزودن micro stack جدید شامل ساخت پروژهٔ پشتهٔ جدید است. باید مرزها را در جای درست بین پشتهها بکشید تا اندازه و مدیریت مناسب بمانند. الگوهای مرتبط شامل راهحلهایی برای این است. ممکن است همچنین نیاز به integrate کردن پشتههای مختلف داشته باشید که در فصل ۱۷ توضیح میدهم.
الگوهای مرتبط
micro stack طرف مقابل spectrum از monolithic stack است (نگاه کنید به «Anti-pattern: پشتهٔ monolithic» در صفحهٔ ۵۶)، جایی که یک پشته همهٔ زیرساخت یک سیستم را دارد.
جمعبندی
پشتههای زیرساخت building blockهای بنیادی برای زیرساخت خودکار هستند. الگوهای این فصل نقطهٔ شروعی برای فکر کردن دربارهٔ سازماندهی زیرساخت در پشتهها هستند.
¹ نه، آن در واقع public IP address نیست.
² Charity Majors اصطلاح blast radius را در بافت Infrastructure as Code popular کرد. پست او «Terraform, VPC, and Why You Want a tfstate File Per env» دامنهٔ آسیب بالقوهای که یک تغییر میتواند به سیستم وارد کند را توضیح میدهد.
³ نگاه کنید به «Microservices» نوشتهٔ James Lewis.
⁴ نگاه کنید به «The Art of Building Autonomous Teams» نوشتهٔ John Ferguson Smart، از میان منابع بسیار دیگر.
⁵ مثلاً immutable server (نگاه کنید به «الگو: Immutable Server» در صفحهٔ ۱۹۵).