چرا کوبرنتیز این شکلی طراحی شده؟ - قسمت دوم

مقدمه

در قسمت قبل در مورد اصل اول طراحی کوبرنتیز گفتیم. چی بود؟ اینکه به جای imperative در کوبرنتیز از declarative API استفاده شده. توضیح هر کدوم هم دیدیم. حالا در ادامه‌ی بحث قبلی قراره اصل دوم رو ببینیم. این مطالب برگرفته از ارائه‌ی سعد علی از شرکت گوگل در kubecon سال ۲۰۱۸ هست. ایشون جزو توسعه‌دهندگان کوبرنتیز هست و تو این ارائه توضیح میده که اصول معماری کوبرنتیز چه چیزهایی هستن و دلیل هر کدوم چیه.

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


سوال

آخر قسمت قبل این سوال رو پرسیدم که «وقتی ما به کوبرنتیز (دقیق‌ترش رو بگم API server) گفتیم حالت دلخواهمون چیه چه شکلی ما رو به اون می‌رسونه» آیا API server خودش به نودها دستورات لازم رو میده؟ جوابش نه هست. برای این که ببینیم چجوری اصل دوم رو می‌بینیم.


اصل دوم: هیچ API پنهانی بین اجزای کوبرنتیز نیست

ممکنه یه کم مبهم باشه عنوانش. قدم به قدم جلو میریم تا ببینیم چی میگه.

معنی API پنهان چیه؟

بیاید تعریف این پاد رو در نظر بگیریم:

apiVersion: v1 
kind: Pod
metadata:
  name: frontend
  labels:
    app: cache
    tier: frontend
spec:
   containers:
        - name: web-server
          image: nginx:1.21.1


فرض کنید ماشین مستر خودش با نودها صحبت می‌کرد و پاد رو برای ما بالا می‌آورد. برای این باید به نودها درخواست می‌داد و می‌گفت یه کانتینر از روی ایمیج nginx:1.21.1 بسازید. در کنارش باید مدام وضعیت کانتینر رو روی نودها بررسی می‌کرد و اگه تغییری لازم داشتن اعمال می‌کرد. این حالت رو توی عکس‌های زیر می‌بینید.


برای این درخواست‌هایی که مستر به نودها میده، لازم هست یک API در نودها وجود داشته باشه تا کاملا توسط مستر کنترل بشن. این API از ما کاربران مخفیه و ازش استفاده نمی‌کنیم؛ به همین دلیل اسمش شد API پنهان. چیزی که اصل دوم میگه اینه که چنین APIی وجود نداره. نودها و بقیه‌ی اجزای کوبرنتیز دقیقا با همون APIی که ما (مثلا با kubectl) استفاده می‌کنیم با مستر صحبت می‌کنن و مستر مخفیانه با اون‌ها حرف نمی‌زنه. اگر مستر خودش می‌خواست تمام جزئیات رو مدیریت کنه بسیار سنگین می‌شد و مثل الان اجزاش قابل گسترش یا جایگزینی نبودن.

پس چجوری پادها بالا میان؟

درک جواب این سوال به ما کمک می‌کنه که تقریبا نحوه‌ی کار همه‌ی اجزای کوبرنتیز رو متوجه بشیم. بذارید مثال قبل رو ادامه بدیم. من به کوبرنتیز گفتم یه پاد درست کنه. وقتی دستور ساخت پاد به API server داده میشه (هنوز روی نودها کانتینری ساخته نشده) باید تعیین بشه روی کدوم نود بالا بیاد. اینجا scheduler که خودش یه عضوی از مستر هست می‌بینه یه پاد هست که نود نداره:


چجوری scheduler متوجه میشه؟ scheduler در هر لحظه API server رو watch می‌کنه که اگه پادی بدون نود ساخته شد، دست به کار بشه و با توجه به معیارهایی که داره یک نود رو برای هر پاد مشخص کنه. بعد از تعیین نود، آیا scheduler به اون نود میگه که پاد رو بسازه؟ نه. فقط به API server میگه پاد رو آپدیت کنه و مقدار نود رو تعیین می‌کنه:

تو این مثال مقدار رو node 2 قرار داده. نودها هم هميشه دارن API server رو watch می‌کنن که اگه یه پاد به اون‌ها داده شد، ایجادش کنن. الان نود ۲ می‌بینه یک پاد بهش اختصاص داده شده و هنوز اون رو نساخته. پس یه کانتینر رو از روی ایمیج nginx:1.21.1 می‌سازه (اگه لازم باشه ایمیج پول میشه):

از این به بعد حواس این نود هست که همیشه این پاد بالا باشه. اگر ما پاد رو از روی API server حذف کنیم، نود مطلع میشه و اون پاد رو حذف می‌کنه:

توجه کنید که منظورم از نود تو همه‌ی موارد kubelet بود.

این چه خوبی‌ای داره؟

با این روش سیستم ما در مقابل اتفاقات غیرمنتظره خیلی مقاوم‌تر هست. فرض کنید یک نود مدتی از دسترس خارج بشه و بعد برگرده. وقتی برمی‌گرده دوباره API server رو نگاه می‌کنه و اگه لازم باشه پادی بسازه، می‌سازه یا اگه باید پادی حذف بشه، حذفش می‌کنه. یعنی تنها وضعیت فعلی مهمه و تغییراتی که تو بازه‌ی قطعی افتاده تاثیری روی نود نداره. هم‌چنین اگه مستر از دست بره خود نود‌ها وضعیت فعلی‌شون رو حفظ می‌کنن تا دوباره مستر بالا بیاد. یعنی با از دست رفتن مستر کلاستر نابود نمیشه. نکته‌ی دیگه اینه که مستر خیلی بزرگ و سنگین نمیشه و اجزای مختلف در یک ساختار توزیع‌شده دست به دست هم میدن تا به حالت مطلوب کاربر برسن.

یکی از بزرگ‌ترین مزیت‌های این روش اینه که میشه کوبرنتیز رو گسترش داد. یعنی ممکنه من بخوام به جای scheduler کوبرنتیز یک scheduler مخصوص خودم بذارم. بدون تغییر در ساختار کوبرنتیز scheduler رو با برنامه‌ای که خودم نوشتم جایگزین می‌کنم. کافیه فقط بتونه با API server صحبت کنه و منطق خاص من رو برای scheduling پیاده کنه. هم‌چنین اگه یک کاری رو کوبرنتیز نمی‌کنه، من خودم می‌تونم به کوبرنتیز اضافه کنم. مفهوم CRD و اپراتور هم به این شکل در کوبرنتیز قرار گرفته. برای مثال اپراتورهایی که برای دیتابیس‌ها وجود دارن یا اپراتور پرومتئوس. از قدرتمند‌ترین ویژگی‌های کوبرنتیز همین گسترش‌پذیری‌اش هست.


جمع‌بندی

تو این قسمت در مورد اصل دوم در طراحی کوبرنتیز صحبت کردیم. به شکل خلاصه اگر بخوایم بگیم، در کوبرنتیز اجزا به طور مستقل API server رو watch می‌کنن و هر کدوم نقش خودشون رو برای رسیدن به وضعیت مطلوب ایفا می‌کنن. به این شکل دیگه لازم نیست در مستری چیزی باشه که به بقیه دستور بده چی کار کنن. این باعث میشه مستر سبک‌تر باشه، سیستم در مقابل اتفاقات مقاوم‌تر باشه و کوبرنتیز قابلیت گسترش داشته باشه.

امیدوارم براتون مطلب مفیدی بوده باشه. اگر سوال یا نکته‌ای دارین این پایین بفرمایین.


قسمت قبلی

قسمت بعدی