کبرنیٹس آپریٹر کیسے بنایا جائے: ڈویلپرز کے لیے ایک ہینڈ بک

Kubernetes ایسے کنٹرولرز کے ساتھ آتا ہے جو پہلے سے موجود وسائل کے ایک مقررہ سیٹ کا انتظام کرتے ہیں، بشمول تعیناتی، خدمات اور نوڈس۔

آپریٹرز اسی پیٹرن کو وسائل تک بڑھاتے ہیں جن کے بارے میں Kubernetes کو ڈیفالٹ سے معلوم نہیں ہوتا ہے، جس سے وہ کسٹم سسٹمز (اکثر بیرونی) کو اسی اعلاناتی طریقے سے منظم کر سکتے ہیں جس طرح وہ کلسٹر میں موجود ہر چیز کا نظم کرتے ہیں۔

اس گائیڈ کو چار حصوں میں تقسیم کیا گیا ہے: آپریٹر دراصل کیا ہوتا ہے، آپریٹر کی اناٹومی، اسے شروع سے بنانا، اور اسے پیداوار کے لیے تیار کرنا۔

انڈیکس

حصہ 1: تعارف

آپریٹر کیا ہے؟

Kubernetes اس ریاست کا موازنہ کرکے کام کرتا ہے جسے ہم بیان کرتے ہیں اصل حالت سے۔ تعیناتی کنٹرولر خلا کو پُر کرنے کے لیے ذمہ دار ہے، چاہے وہ اس پوڈ کی جگہ لے رہا ہو جسے ہم نے ختم کیا ہے یا اس پوڈ کو سکڑنا ہے جسے ہم مزید نہیں چاہتے ہیں۔

مشاہدہ، موازنہ، اور اداکاری کا یہ لوپ مفاہمت. اس کا مطلب ہے وسائل کی مطلوبہ اصل حالت کو تلاش کرنا، فیصلہ کرنا کہ آگے کیا کرنا ہے، اور ہر بار اس فیصلے کے چلنے پر دوبارہ گنتی کرنا، قطع نظر اس سے کہ کیا بدل گیا ہے۔

یہ وہی ہے جو لوپ کو زیادہ لچکدار بناتا ہے۔ آپ کو اس بات پر بھروسہ کرنے کی ضرورت نہیں ہے کہ آپ نے ہر واقعہ دیکھا ہے، آپ صرف اس بات پر بھروسہ کر سکتے ہیں کہ اسے واپس بلایا جائے گا۔

آپریٹرز بالکل وہی لوپ ان وسائل پر لاگو کرتے ہیں جنہیں Kubernetes مقامی طور پر نہیں سمجھتا ہے۔ آپ ایک حسب ضرورت وسائل کی وضاحت کرتے ہیں، اس میں مطلوبہ ڈومین کی حالت بیان کرتے ہیں، اور ایک کنٹرولر لکھتے ہیں جو جانتا ہے کہ اس ڈومین کو کیسے مربوط کرنا ہے۔

یہ پورا تصور ہے۔ اس گائیڈ میں باقی سب کچھ (معلومات فراہم کرنے والے، کام کی قطاریں، حتمی شکل دینے والے، ریاستی حالات) وسائل کی اقسام پر قابل اعتماد طریقے سے ایک لوپ بنانے کے لیے موجود ہے جس کے بارے میں صرف Kubernetes جانتے ہیں کیونکہ ہم نے ان کی وضاحت کی ہے۔

آپریٹر بمقابلہ کنٹرولر بمقابلہ CRD

یہ تینوں اصطلاحات اکثر ایک دوسرے کے بدلے استعمال ہوتی ہیں، لیکن وہ ایک ہی نظام کی تین مختلف پرتوں کو بیان کرتی ہیں۔

  • CustomResourceDefinition (CRD): ایک اسکیما جو نئے وسائل کی اقسام کے بارے میں سکھانے کے لیے Kubernetes API سرور کے ساتھ رجسٹر ہوتا ہے۔ CRD بذات خود کچھ نہیں کرتا۔ یہ صرف ایک فارم فراہم کرتا ہے جو API سرور کو اسٹور، تصدیق اور پیش کیا جا سکتا ہے۔

  • کنٹرولر: کوئی بھی سافٹ ویئر جو وسائل کی قسم پر کوآرڈینیشن لوپ چلاتا ہے، بلٹ ان تعیناتی کنٹرولر سے لے کر ایک حسب ضرورت کنٹرولر تک جو وسائل کی قسم کو مربوط کرتا ہے۔ PostgresCluster.

  • آپریٹر: ایک کنٹرولر یا چھوٹے کنٹرولرز کا مجموعہ جو حسب ضرورت وسائل کو نشانہ بناتا ہے اور اپنے پورے لائف سائیکل کو منظم کرنے کے لیے ڈومین سے متعلق کافی علم کو انکوڈ کرتا ہے، بشمول پروویژننگ، اپ گریڈ، اور فیل اوور، انسانوں کے بغیر۔

تمام آپریٹرز کنٹرولرز ہیں، لیکن تمام کنٹرولرز آپریٹرز نہیں ہیں۔ کنٹرولر کے بغیر ایک CRD صرف ایک اسکیما ہے جو کچھ نہیں کرتا ہے۔

ہیلم چارٹ، کرون جاب، یا اسکرپٹ کیوں نہیں؟

کوئی راستہ نہیں پچ چارٹ YAML پر سیٹ ویلیو رینڈر کریں اور اسے ایک بار لگائیں۔ بعد میں اس پر نظر رکھنے کا کوئی طریقہ نہیں ہے۔ اگر کوئی تخلیق شدہ وسیلہ حذف ہوجاتا ہے یا بڑھ جاتا ہے، تو ہیلم کو اس وقت تک پتہ نہیں چلے گا جب تک آپ اسے نہیں چلاتے۔ helm upgrade دوبارہ ہاتھ سے۔

کوئی راستہ نہیں کرون کام یہ گرینولریٹی کی قیمت پر لوپ بیکس فراہم کرتا ہے، ایک وقفہ تک تعطل، پھانسیوں کے درمیان کوئی حالت نہیں گزرتی، اور ایک کرون جاب کے لیے دوسرے کرون جاب میں ریاستی تبدیلیوں پر ردعمل ظاہر کرنے کا کوئی طریقہ نہیں ہوتا ہے۔

اور ایک وقتی اسکرپٹ یہ صرف اس وقت کام کرتا ہے جب دستی طور پر یا CI پائپ لائن کے ذریعہ متحرک کیا جاتا ہے اور رنز کے درمیان بڑھنے کے بارے میں کچھ نہیں کرتا ہے۔ مزید برآں، پہلی قسم کے مسائل کو شاذ و نادر ہی دوبارہ کوشش اور ذہن میں رکھ کر لکھا جاتا ہے۔

آپریٹرز یہاں کیوں جیتتے ہیں:

آپریٹرز ایونٹ پر مبنی اور ترتیب وار ہیں۔ جیسے ہی کوئی حسب ضرورت وسیلہ بنتا ہے، اپ ڈیٹ ہوتا ہے یا حذف ہوتا ہے API سرور آپ کو مطلع کرتا ہے، اور نہ صرف درخواست کے وقت، بلکہ اس وسائل کی پوری زندگی میں ایڈجسٹمنٹ کرتا رہتا ہے۔ یہ ان ریاستوں کے لیے سب سے اہم ہے جو آپس میں ملنے میں وقت لیتی ہیں، وقت سے پہلے ناکام ہو سکتی ہیں، اور ابتدائی طور پر بننے کے بعد بہہ سکتی ہیں۔

لیکن یہ ایک قیمت پر آتا ہے. آپریٹرز اپنے کردار پر مبنی رسائی کنٹرول (RBAC)، ناکامی کے طریقوں، اور مشاہداتی سطحوں کے ساتھ طویل عرصے سے چلنے والے عمل ہیں۔ یہ چارٹ یا اسکرپٹ کے مقابلے میں تعمیر اور کام کرنے کے قریب ہے۔

اگر آپ کا مسئلہ واقعی ایک بار کچھ YAML پیش کر رہا ہے، تو ہیلم چارٹس صحیح ٹول ہیں۔ جب چیزوں کو مستقل طور پر درست کرنے کا چیلنج ہوتا ہے، تو آپریٹرز صحیح نتائج حاصل کرنا جاری رکھ سکتے ہیں، اور اس گائیڈ کے باقی حصے کا مقصد یہی ہے۔

حصہ 2: آپریٹر تجزیہ

اگلا، اس سے پہلے کہ ہم کوآرڈینیشن لوپ کو تفصیل سے دیکھیں، آئیے ان پرزوں کو دیکھتے ہیں جو حقیقت میں اسے کام کرتے ہیں: حسب ضرورت وسائل خود، وہ مشین جو تبدیلیوں کا پتہ لگاتی ہے، اور مینیجر جو یہ سب چلاتا ہے۔

اپنی مرضی کے وسائل

اس سے پہلے کہ کوئی کنٹرولر کسی بھی چیز کو ترتیب دے سکے، اسے یہ جاننے کی ضرورت ہے کہ API سرور کس قسم کا مواد اسٹور کرتا ہے۔ CRD کا اندراج آپ کو بتائے گا کہ یہ کیسا لگتا ہے۔

تمام حسب ضرورت وسائل ایک ہی ID فیلڈ کو پاس کرتے ہیں جو پہلے سے ہی تمام Kubernetes اشیاء میں موجود ہے (kind, name, namespace, labelsوغیرہ)، اور دو فیلڈز ہیں جن کی وضاحت کرنا مکمل طور پر ہم پر منحصر ہے: تفصیلات اور ریاست۔ یہ تقسیم ایک انداز کا انتخاب نہیں ہے۔ یہ براہ راست کوآرڈینیشن لوپ پر نقشہ بناتا ہے۔

  • قیاس آرائی یہ مطلوبہ حالت ہے۔ کوئی بھی جو کوئی وسیلہ تخلیق کرتا ہے یا اس میں ترمیم کرتا ہے اسے لکھتا ہے، اور کنٹرولر صرف اسے پڑھتا ہے۔

  • صورت حال یہ ایک مشاہدہ شدہ ریاست ہے۔ نتائج اور کیے گئے اقدامات کو ریکارڈ کرنے کے لیے یہ مکمل طور پر کنٹرولر کے ذریعے لکھا جاتا ہے۔

کلائنٹ جو ریاست کو براہ راست لکھتے ہیں وہ کنٹرولر کے ذریعے کام کرنے کے بجائے اس کے ارد گرد کام کرتے ہیں، لہذا ریاست کو عام طور پر علیحدہ اجازتوں کے ساتھ اپنے ذیلی وسائل کے طور پر فراہم کیا جاتا ہے۔

آخری حصہ رجسٹریشن ہے۔ API سرور اور اس کے بارے میں بات کرنے والے تمام کلائنٹس کو انکوڈ اور ڈی کوڈ کی اقسام کے لیے مشترکہ، متفقہ طریقے کی ضرورت ہوتی ہے، اس لیے وہ کسی بھی چیز پر اسے استعمال کرنے سے پہلے اسکیم کے لیے ایک بار رجسٹر کر لیتے ہیں۔ اس کے بغیر، ہماری اقسام صرف تعریفیں ہیں جو کوئی فراہم نہیں کر سکتا۔ یہ API سرور کو انہیں بالکل اسی طرح ذخیرہ کرنے اور پیش کرنے کی اجازت دیتا ہے جس طرح یہ پوڈز یا تعیناتیوں کی خدمت کرتا ہے۔

حصہ 3 میں، ہم دیکھیں گے کہ جب آپ اسے حقیقی زندگی میں بناتے ہیں تو رجسٹری کیسی دکھتی ہے۔

تبدیلیوں کو دیکھیں

آرکیسٹریٹر API سرور کو ایک لوپ میں پول نہیں کرتا ہے کہ "کیا ابھی تک کچھ بدلا ہے؟” اس کو روکنے کے لیے تین ٹکڑے مل کر کام کرتے ہیں۔

نہیں مخبر API سرور پر ایک طویل المدت گھڑی کھولتا ہے اور دی گئی قسم کی تمام اشیاء کے لیے ایک مقامی ان-میموری کیش کو برقرار رکھتا ہے، ایونٹس کے شامل ہونے، اپ ڈیٹ کرنے اور حذف کرنے کے ساتھ ہی انہیں اپ ڈیٹ کرتا ہے۔

کوئی راستہ نہیں فہرست چونکہ یہ API سرور کے بجائے اپنے کیشے سے پڑھتا ہے، آرکیسٹریٹر چیک کرتا ہے "کیا یہ وسیلہ پہلے سے موجود ہے؟” مقامی نقشہ تلاش کرنے کے اخراجات کیے جاتے ہیں، نیٹ ورک کالز نہیں۔

کوئی راستہ نہیں کام کی قطار یہ مخبر اور صلح کرنے والے کے درمیان واقع ہے۔ جب مخبر تبدیلیاں دیکھتے ہیں تو وہ براہ راست کوآرڈینیٹر کو کال نہیں کرتے۔ اس کے بجائے، یہ خود اشیاء کی بجائے قطار میں کلیدیں، نام کی جگہیں اور نام شامل کرتا ہے۔ جب ایک کارکن قطار سے کلید نکالتا ہے اور اس میں مصالحت کرتا ہے، تو قطار ہمارے لیے ڈپلیکیشن اور ریٹ کو محدود کرتی ہے، اس لیے اسی آبجیکٹ کے لیے 10 فوری اپ ڈیٹس کو 10 فالتو مفاہمتوں کی بجائے ایک زیر التواء آئٹم تک کم کر دیا جاتا ہے۔

معلومات فراہم کرنے والے، کام کی قطاریں، اور کوآرڈینیٹر پائپ لائنز

یہی وجہ ہے کہ کوآرڈینیٹر کو چابیاں ملتی ہیں نہ کہ اشیاء۔ جب تک کارکن قطار سے کوئی کلید اٹھاتا ہے، اعتراض دوبارہ تبدیل ہو چکا ہو گا، اس لیے کوآرڈینیٹر ہمیشہ اس بات پر بھروسہ کرنے کے بجائے خود موجودہ حالت کو دیکھتا ہے کہ اسے کس چیز نے متحرک کیا۔

مینیجر

مینیجر وہ عمل ہے جو اس سب کا مالک ہے: مشترکہ کیش جسے معلومات فراہم کرنے والا آباد کرتا ہے، وہ کلائنٹ جنہیں کوآرڈینیٹر اشیاء کو پڑھنے اور لکھنے کے لیے استعمال کرتا ہے، اور ریاست، اسٹیجنگ، اور میٹرکس اینڈ پوائنٹس جو باقی کلسٹر اس بات کی تصدیق کے لیے استعمال کرتا ہے کہ کنٹرولر آپریشنل ہے۔ تمام ماڈریٹرز جو ہم رجسٹر کرتے ہیں وہ ایک ایڈمنسٹریٹر کے اندر چلتے ہیں۔

دستیابی کی وجوہات کی بناء پر، اگر آپ ایک ہی کنٹرولر کی ایک سے زیادہ نقل چلا رہے ہیں، تو آپ نہیں چاہتے کہ دونوں نقلیں ایک ہی وقت میں ایک ہی چیز کو مربوط کریں اور ایک دوسرے کے خلاف دوڑیں۔

منتظم اس کو مربوط کرتا ہے۔ لیڈر کا انتخاب. نقلیں لیز کے لیے مقابلہ کرتی ہیں، بالکل ایک کو رکھتی ہیں اور اسے فعال طور پر مربوط کرتی ہیں، جب کہ دیگر اس وقت تک بیکار رہتے ہیں جب تک کہ لیڈر ان کی تجدید کرنا بند نہ کر دے۔

ہم اس کی اصل تفصیلات پر دوبارہ حصہ 4 میں بحث کریں گے۔ ابھی کے لیے، یہ جاننا کافی ہے کہ مینیجرز ہی اس کو ممکن بناتے ہیں۔

مفاہمت لوپ

یہ سب سے اہم حصہ ہے۔ اگر آپ اس لوپ کو اچھی طرح سمجھتے ہیں، تو آپریٹر جو کچھ کرتا ہے اس میں سے زیادہ تر اسے تبدیل کرتا ہے۔

کوآرڈینیٹر کے انٹری پوائنٹ کو صرف نام کی جگہ اور نام کا استعمال کرتے ہوئے بلایا جاتا ہے۔ کوئی وضاحتیں، شرائط یا اختلافات نہیں ہیں۔ کوآرڈینیٹر کو آبجیکٹ کو خود لینا چاہیے، اس کی خصوصیات کا موازنہ اس کے ساتھ جو اس کی حقیقی حالت میں مشاہدہ کیا جا سکتا ہے، اور فیصلہ کرنا چاہیے کہ کیا کرنا ہے۔ یہ حد جان بوجھ کر ہے، اور ذیل کی ہر چیز کی وجہ ہے۔

بے حسی

کیونکہ کوآرڈینیٹر صرف چابیاں لاتا ہے اور ایک ہی چیز پر متعدد بار کال کی جا سکتی ہے (پے در پے، ترتیب سے باہر، یا ایک طویل وقفہ کے بعد)، اس سے قطع نظر کہ یہ کتنی ہی بار چلتا ہے ایک ہی نتیجہ نکالنا چاہیے۔ ایک کوآرڈینیٹر جو آنکھ بند کر کے کال کرتا ہے ہر بار تخلیق کرتا ہے جیسے ہی یہ دو بار چلتا ہے ٹوٹ جائے گا۔ اس کی وجہ یہ ہے کہ پہلے سے موجود آبجیکٹ کو دوسری کال ناکام ہو جائے گی۔

حل یہ ہے کہ کارروائی کرنے سے پہلے ہمیشہ موجودہ حیثیت کو چیک کریں۔ اسے صرف اس صورت میں بنائیں جب یہ غائب ہو، اسے صرف اس صورت میں اپ ڈیٹ کریں جب یہ مختلف ہو، اور اسے صرف اس صورت میں حذف کریں جب یہ موجود نہ ہو۔

ایونٹ پر مبنی اسکیلنگ

پیمانہ سازی کو وسیلہ کے موافق ہونے کے لیے دیکھنے کے واقعات کے ذریعے متحرک کیا جاتا ہے، اور قواعد کے مطابق، کسی بھی چیز کے لیے جو وسائل کی ملکیت ہے یا اس پر منحصر ہے۔ مزید برآں، زیادہ تر کنٹرولرز متواتر ری سنکرونائزیشن ترتیب دیتے ہیں، لہذا لوپ شیڈول کے مطابق چلتا ہے یہاں تک کہ اگر گھڑی کے واقعات بالکل بھی نہ ہوں۔ یہ اس وقت اہم ہے جب ریاست ان وجوہات کی بنا پر چلی جا سکتی ہے جن کی وجہ سے گھڑی نہیں پکڑتی ہے۔

Requiem

بعض اوقات ایک واحد مربوط ترسیل کسی کام کو مکمل نہیں کر سکتی کیونکہ قطار میں کھڑا کام اب بھی کہیں اور جاری ہے۔ ہو سکتا ہے کہ ایڈجسٹر اسے ناکامی نہ سمجھے اور تاخیر کے بعد واپس کال کرنے کی درخواست کر سکتا ہے۔ یہ ان چیزوں کے لیے پول کرنے کا ایک طریقہ ہے جو ایک کال کے اندر بلاک کرنے کے بجائے اکٹھا ہونے میں وقت لیتی ہیں۔

ہینڈلنگ میں خرابی

غلطی واپس کرنا کچھ ایسا ہی کرتا ہے۔ اسے دوبارہ قطار میں شامل کیا جاتا ہے، لیکن ایک مقررہ تاخیر کے بجائے ایک کفایتی بیک آف کے ساتھ۔ لہذا ایک مفاہمت جو مستقل طور پر ناکام رہتی ہے وہ ہر چیز کو برباد نہیں کرتی ہے جو ناکام ہوجاتی ہے۔

ان غلطیوں کے درمیان فرق کرنا اچھا خیال ہے جو دوبارہ کوشش کرنے کے قابل ہیں (جیسے ٹائم آؤٹ یا لاک تنازعات) اور جو نہیں ہیں (جیسے غلط وضاحتیں جنہیں ہمیشہ کے لیے دوبارہ کوشش کرنے کے بجائے اسٹیٹس کنڈیشنز کے طور پر نشان زد کیا جانا چاہیے)۔

بہاؤ کی اصلاح

اگر آپ مندرجہ بالا سب کو ایک ساتھ رکھتے ہیں، تو آپ کا لوپ خود بخود خود بخود ٹھیک ہو جائے گا۔ اس سے کوئی فرق نہیں پڑتا کہ ڈرفٹ کسی کی طرف سے آیا ہے جو اسے چلا رہا ہے، کیونکہ reconcile تبدیلیوں پر خاص طور پر رد عمل ظاہر نہیں کرتا ہے اور ہر بار پورے فرق کو دوبارہ شمار کرتا ہے۔ kubectl editیہ دوسرے کنٹرولرز یا وسائل کے لیے پہلے سے طے شدہ نظام ہے جو خود ریاست کی تبدیلیوں کی نشاندہی کرتا ہے۔ اگلی ایڈجسٹمنٹ، جو واچ ایونٹ یا دوبارہ مطابقت پذیری سے شروع ہوتی ہے، اسی فرق کو کسی بھی طریقے سے چیک کرے گی اور اسے اسی طرح بند کرے گی۔

حصہ 3: بلڈنگ آپریٹرز

اب تک سب کچھ اس کے لیے بنایا گیا ہے۔ اب ہم جانتے ہیں کہ آپریٹرز کیا ہیں، ان کے اردگرد کی اصطلاحات کیسے متعلق ہیں، اور کوآرڈینیشن لوپ کن حصوں پر مشتمل ہے۔

اب ہم یہ سب استعمال کرنے اور اسے بنانے جا رہے ہیں۔ وی ایم او آپریٹر. یہ آپریٹر ہے جو اس کا انتظام کرتا ہے۔ VirtualMachine موک کلاؤڈ فراہم کنندگان کے ذریعہ تعاون یافتہ حسب ضرورت وسائل بھی چھوٹی HTTP خدمات ہیں جو حقیقی خدمات کی جگہ لے لیتی ہیں۔

apiVersion: compute.example.com/v1
kind: VirtualMachine
spec:
  image: ubuntu-22.04
  cpu: 2
  memory: 4Gi
status:
  phase: Running
  id: vm-123

ہم کہتے ہیں کہ آپ کوبرنیٹس پر مبنی طریقے سے ورچوئل مشین کی نمائندگی کرنا چاہتے ہیں۔ kubectl apply آپ YAML فائلیں بنا سکتے ہیں اور کلاؤڈ کنسول یا علیحدہ CLI کو چھوئے بغیر VMs حاصل کر سکتے ہیں۔

VVMoperator کے پیچھے یہی محرک ہے۔ Kubernetes سے باہر کی کوئی بھی چیز کلسٹر کے اپنے ٹولز میں صرف ایک اور چیز بن جاتی ہے (kubectlRBAC، GitOps پائپ لائنز) پہلے ہی جانتے ہیں کہ اسے کیسے کرنا ہے۔

VMOperator فن تعمیر

ترتیب

آپ کو ایک مقامی کلسٹر کی ضرورت ہوگی، قسم حاصل کرنے کا سب سے آسان طریقہ ہے۔

kind create cluster --name vmoperator

اس کے علاوہ، ہم اس حصے میں Go کو بطور آپریٹر استعمال کریں گے۔ kubectl ذیل میں فرضی فراہم کنندہ کے لیے فلاسک کے ساتھ ایک نئے کلسٹر اور ازگر کی طرف اشارہ کریں۔

pip install flask

فرضی فراہم کنندہ

کنٹرولر کوڈ لکھنے سے پہلے، ہمیں ایسی چیز کی ضرورت ہے جسے کنٹرولر کنٹرول کر سکے۔ فرضی فراہم کنندہ ایک چھوٹی HTTP سروس ہے جس میں تین اختتامی پوائنٹس ہیں۔

اس کی حمایت صرف میموری میں ایک لغت سے ہوتی ہے۔

بنائے گئے تمام VM اس سے شروع ہوں گے: Provisioning اور اسے پلٹ دیں۔ Running کوآرڈینیٹر کو کامیابی ماننے کے بجائے اصل میں رائے شماری کرنے پر مجبور کرنے کے لیے چند سیکنڈز کافی ہیں۔

ہم اسے ازگر میں لکھ رہے ہیں، گو نہیں۔ اس کا آپریٹر کے کوڈ سے کوئی تعلق نہیں ہے کیونکہ یہ صرف مذاق کے مقاصد کے لیے استعمال ہوتا ہے۔

import random
import string
import threading
import time

from flask import Flask, jsonify, request

app = Flask(__name__)
vms = {}  # in-memory store, keyed by VM id

def provision(vm):
    time.sleep(5)  # simulate provisioning taking time
    vm["phase"] = "Running"

@app.post("/vms")
def create_vm():
    body = request.get_json()
    vm_id = "vm-" + "".join(random.choices(string.digits, k=6))
    vm = {"id": vm_id, "image": body["image"], "phase": "Provisioning"}
    vms[vm_id] = vm

    threading.Thread(target=provision, args=(vm,), daemon=True).start()  # flips to Running in the background

    return jsonify(vm)

@app.get("/vms/")
def get_vm(vm_id):
    vm = vms.get(vm_id)
    if vm is None:
        return "", 404
    return jsonify(vm)

@app.delete("/vms/")
def delete_vm(vm_id):
    vms.pop(vm_id, None)
    return "", 204

if __name__ == "__main__":
    app.run(port=8080, threaded=True)

آپ اسے اپنے کلسٹر کے ساتھ اس کے اپنے عمل کے طور پر چلاتے ہیں، ان بندرگاہوں پر سنتے ہیں جن پر آپ آپریٹر کو کال کرنے کے لیے ترتیب دیتے ہیں۔ Kubernetes کے بارے میں کچھ بھی موجود نہیں ہے۔ نقطہ یہ ہے کہ Kubernetes ایک حقیقی کلاؤڈ API کی نمائندگی کرتا ہے۔

ورچوئل مشین سی آر ڈی کی تعریف

ایک بار جب آپ کے پاس کنٹرول کرنے کے لئے کچھ ہے، تو آپ اس بات کی وضاحت کر سکتے ہیں کہ آپ کس چیز کو کنٹرول کرنا چاہتے ہیں۔ کہ VirtualMachine قسم حصہ 2 میں تصریحات اور شرائط کی بالکل پیروی کرتی ہے۔

type VirtualMachineSpec struct {
	Image  string `json:"image"`
	CPU    int    `json:"cpu"`
	Memory string `json:"memory"`
}

type VirtualMachineStatus struct {
	ID    string `json:"id,omitempty"`    // provider-assigned id, empty until first provisioned
	Phase string `json:"phase,omitempty"` // mirrors the provider's lifecycle phase
}

type VirtualMachine struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   VirtualMachineSpec   `json:"spec,omitempty"`
	Status VirtualMachineStatus `json:"status,omitempty"`
}

type VirtualMachineList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []VirtualMachine `json:"items"`
}

TypeMeta لے جانا kind اور apiVersionہر Kubernetes آبجیکٹ (بلٹ ان یا کسٹم) میں وہی دو فیلڈز آپ کو بتاتے ہیں کہ یہ کیا ہے۔

ListMeta یہ فہرست کی قسم سے مساوی ہے۔ resourceVersion اور continue صفحہ بندی کے لیے (اس کے بجائے name/namespace)۔ یہی وجہ ہے۔ VirtualMachineList اندراج ListMeta اس کے آگے TypeMeta جبکہ VirtualMachine اپنے اندر بنایا ObjectMeta.

تمام اقسام جو ہم رجسٹر کرتے ہیں وہ درج ذیل کو پورا کرنا ضروری ہے: runtime.Objectاس کا مطلب ہے نفاذ۔ DeepCopyObject. یہ عام طور پر ہمارے لیے تیار کیا جائے گا، لیکن چونکہ ہم یہ خود کرتے ہیں، اس لیے تیار کردہ کوڈ دراصل اس طرح نظر آئے گا: VirtualMachine. باقی اسی مکینیکل پیٹرن کی پیروی کرتے ہیں۔

func (in *VirtualMachine) DeepCopyObject() runtime.Object {
	out := VirtualMachine{
		TypeMeta:   in.TypeMeta,
		ObjectMeta: *in.ObjectMeta.DeepCopy(), // ObjectMeta already knows how to copy itself
		Spec:       in.Spec,                   // no pointers or slices in Spec, a plain copy is safe
		Status:     in.Status,
	}
	return &out
}

یہاں CRD مینی فیسٹ ہے جو API سرور کو اس کے بارے میں بتاتا ہے:

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: virtualmachines.compute.example.com
spec:
  group: compute.example.com
  scope: Namespaced
  names:
    kind: VirtualMachine
    listKind: VirtualMachineList
    plural: virtualmachines
    singular: virtualmachine
    shortNames: [vm] # lets us type `kubectl get vm` instead of the full plural
  versions:
    - name: v1
      served: true
      storage: true
      subresources:
        status: {} # splits status into its own subresource, see Part 2
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              required: [image, cpu, memory]
              properties:
                image: { type: string }
                cpu: { type: integer }
                memory: { type: string }
            status:
              type: object
              properties:
                phase: { type: string }
                id: { type: string }

کہ subresources.status لائنیں اہم ہیں۔ یہ کیا کرتا ہے ریاست کو اس کے اپنے اپ ڈیٹ پاتھ کے ساتھ ایک علیحدہ ذیلی وسائل بناتا ہے۔ حصہ 2 میں یہ حد ہے کہ کلائنٹ کیا لکھ سکتے ہیں اور کیا صرف کنٹرولرز لکھ سکتے ہیں۔

کہ names ایک بلاک بھی کیا ہے؟ kubectl کے خلاف فیصلہ کرنا، kubectl get virtualmachines یہ کام کرتا ہے کیونکہ plural وہ یہی کہتے ہیں۔ shortNames کیونکہ kubectl get vm یہ بھی اسی طرح کام کرتا ہے۔ kubectl get po پھلیوں پر کام کرتا ہے۔

کوآرڈینیٹر

ثالث کا کردار کاغذ پر چھوٹا ہے۔ VirtualMachineتصدیق کریں کہ فراہم کنندہ میں ایک مماثل VM موجود ہے اور اس کی حیثیت حقیقت کی عکاسی کرتی ہے۔ فراہم کنندہ کے HTTP API کو چھوٹے کلائنٹ کے پیچھے لپیٹیں تاکہ ثالث خود اسے پڑھ سکے۔

func (r *VirtualMachineReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	var vm computev1.VirtualMachine
	if err := r.Get(ctx, req.NamespacedName, &vm); err != nil {
		return ctrl.Result{}, client.IgnoreNotFound(err) // object was deleted, nothing left to do
	}

	if vm.Status.ID == "" {
		// no VM yet, this is the first time we've seen this object
		created, err := r.Provider.Create(ctx, vm.Spec.Image)
		if err != nil {
			return ctrl.Result{}, err
		}

		vm.Status.ID = created.ID
		vm.Status.Phase = created.Phase
		if err := r.Status().Update(ctx, &vm); err != nil {
			return ctrl.Result{}, err
		}

		return ctrl.Result{RequeueAfter: 2 * time.Second}, nil // check back shortly instead of blocking here
	}

	// VM already exists, poll the provider for whatever it knows right now
	current, err := r.Provider.Get(ctx, vm.Status.ID)
	if err != nil {
		return ctrl.Result{}, err
	}

	vm.Status.Phase = current.Phase
	if err := r.Status().Update(ctx, &vm); err != nil {
		return ctrl.Result{}, err
	}

	if current.Phase != "Running" {
		return ctrl.Result{RequeueAfter: 2 * time.Second}, nil // still provisioning, keep polling
	}

	return ctrl.Result{}, nil
}

دو باتیں قابل ذکر ہیں۔ سب سے پہلے، چونکہ آپ نے اپنی گھڑی یہاں رجسٹر کی ہے، اس لیے آپ کو اس تک رسائی حاصل نہیں ہے۔ VirtualMachine. API سرور آپ کو اس وقت مطلع کرتا ہے جب ایک API تخلیق یا ترمیم کیا جاتا ہے، جو پہلی کال کو متحرک کرتا ہے۔

دوسرا، ہر شاخ کا اختتام ایک تحریر کے ساتھ ہوتا ہے: vm.Statusہم ہر چیز کا نقشہ بناتے ہیں جو فراہم کنندہ ہمیں وسائل سے بتاتا ہے۔ Kubernetes فراہم کنندہ سے براہ راست بات نہیں کرتا ہے۔ یہ جاننے کا واحد طریقہ ہے کہ آیا VM چل رہا ہے کیونکہ گورنر نے اسے اپنی حیثیت میں لاگ کیا ہے۔

ناکامی کو سنبھالنا اور دوبارہ کوشش کرنا

اوپر والا ثالث اپنے طور پر کسی چیز کی دوبارہ کوشش نہیں کرتا ہے۔ جب r.Provider.Create یا r.Provider.Get اگر یہ ناکام ہوجاتا ہے (مثال کے طور پر، نیٹ ورک کی خرابی کی وجہ سے یا فرضی فراہم کنندہ ابھی تک نہیں ہے)، یہ صرف ایک غلطی لوٹاتا ہے۔ یہ جان بوجھ کر ہے۔ غلطی کی واپسی ہماری جانب سے ایکسپونینشل بیک آف کا استعمال کرتے ہوئے کنٹرولر رن ٹائم سے درخواست کرنے کا ایک طریقہ ہے۔ اس کا مطلب ہے کہ آپ کو دستی طور پر دوبارہ کوشش کرنے والے لوپس سے نمٹنے کی ضرورت نہیں ہے، اور آپ ایسے فراہم کنندگان پر دوبارہ کوششیں نہیں کریں گے جو مسلسل ناقابل رسائی ہیں۔

ایک چیز جس سے ہوشیار رہنا چاہئے وہ ہے تمام ناکامیوں کا اسی طرح علاج کرنا۔ اپنے فراہم کنندہ سے بات کرنے کے لیے ٹائم آؤٹ لینا دوبارہ کوشش کرنے کے قابل ہو سکتا ہے۔ کوئی راستہ نہیں VirtualMachine جس کا spec.image سپلائر اسے کبھی قبول نہیں کرے گا۔ ہمیشہ کے لیے دوبارہ کوشش کرنے سے ایک مصروف لوپ بنتا ہے جو کبھی کامیاب نہیں ہوتا۔

میں اسے ریاستی حالات کے ذریعے اس امتیاز کو خارجی شکل دینے کی مشق پر چھوڑ دوں گا۔ مندرجہ بالا ایڈجسٹر کے بارے میں فکر کرنے کے لئے صرف ایک ناکامی موڈ ہے۔ اس کی وجہ یہ ہے کہ فرضی فراہم کنندہ درخواست کو یکسر مسترد نہیں کرتا ہے۔

ختم کرنے والا

اگر ہم حذف کرتے ہیں۔ VirtualMachine فی الحال، Kubernetes اشیاء کو بے دخل کرتا ہے، یتیم VMs کو پیچھے چھوڑ دیتا ہے جو فراہم کنندہ کے خیال میں اب بھی چل رہے ہیں۔ ٹرمینیٹر اس خلا کو پاٹتے ہیں۔ یہ اشیاء کی ایک تار ہے جو Kubernetes کو بتاتی ہے، "جب تک میں آپ کو نہ کہوں اسے اصل میں حذف نہ کریں۔”

const vmFinalizer = "compute.example.com/vm-cleanup"

func (r *VirtualMachineReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	var vm computev1.VirtualMachine
	if err := r.Get(ctx, req.NamespacedName, &vm); err != nil {
		return ctrl.Result{}, client.IgnoreNotFound(err)
	}

	if !vm.DeletionTimestamp.IsZero() {
		// being deleted, deprovision through the provider before letting it go
		if controllerutil.ContainsFinalizer(&vm, vmFinalizer) {
			if vm.Status.ID != "" {
				if err := r.Provider.Delete(ctx, vm.Status.ID); err != nil {
					return ctrl.Result{}, err
				}
			}
			controllerutil.RemoveFinalizer(&vm, vmFinalizer) // safe to let the delete proceed now
			return ctrl.Result{}, r.Update(ctx, &vm)
		}
		return ctrl.Result{}, nil
	}

	if !controllerutil.ContainsFinalizer(&vm, vmFinalizer) {
		controllerutil.AddFinalizer(&vm, vmFinalizer) // register before we ever provision anything
		if err := r.Update(ctx, &vm); err != nil {
			return ctrl.Result{}, err
		}
	}

	// ... provisioning logic from before
	return ctrl.Result{}, nil
}

کوئی راستہ نہیں kubectl delete کو VirtualMachine اگر کوئی فائنلائزر ہے تو اسے ہٹایا نہیں جائے گا۔ بلکہ سیٹ کرتا ہے۔ deletionTimestamp اور انتظار کرو۔

اگلی کال میں، آرکیسٹریٹر اس بات کو یقینی بناتا ہے کہ VM فراہم کنندہ کے ذریعے بند کر دیا گیا ہے اور پھر فائنلائزر کو ہٹاتا ہے۔ اس مقام پر، Kubernetes آخر میں اعتراض کو حذف کر دیتا ہے۔ فائنلائزر کے بغیر، اس بات کی کوئی گارنٹی نہیں ہے کہ صفائی چلے گی۔

پیش گوئی

مذکورہ بالا ماڈریٹر میں پہلے سے ہی ٹھیک ٹھیک کیڑے ہیں۔ جب بھی مجھے فون آتا ہے۔ r.Status().Updateتحریر بذات خود اعتراض کی تبدیلی ہے۔ یہ ایک خود نگرانی کو متحرک کرتا ہے جو مفاہمت کو واپس بلاتا ہے۔

اگر آپ اسے ویسا ہی چھوڑ دیتے ہیں، یہ ہمیشہ کے لیے نہیں گھومے گا۔ کیونکہ یہ اسی حالت کو دوبارہ شمار کرتا ہے جب تک کہ یہ مستحکم نہ ہو جائے۔ لیکن ہماری اپنی تحریر کے جواب میں ایڈجسٹمنٹ کرنا اب بھی ایک فضول مشق ہے۔

پیشین گوئی ان واقعات کو فلٹر کرتی ہے جو اصل میں کوڈ کے چلنے سے پہلے مفاہمت کی قطار لگاتے ہیں۔

func (r *VirtualMachineReconciler) SetupWithManager(mgr ctrl.Manager) error {
	return ctrl.NewControllerManagedBy(mgr).
		For(&computev1.VirtualMachine{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})). // drop status only events
		Complete(r)
}

generation یہ صرف اس وقت بڑھتا ہے جب: spec تبدیلی اسٹیٹس اپ ڈیٹس کو چھوا نہیں جاتا ہے۔ GenerationChangedPredicate اس کا استعمال کرتے ہوئے ہم اس واقعہ کو مسترد کرتے ہیں جس نے صرف ریاست کو منتقل کیا تھا، لہذا ہماری اپنی تحریریں دوبارہ متحرک نہیں ہوتی ہیں۔ یہ تب ہی اعتدال پر واپس آتا ہے جب کوئی معنی خیز تبدیلی آتی ہے یا جب آپ واضح طور پر درخواست کرنے کی درخواست کرتے ہیں۔

ملکیت کے وسائل

کوئی راستہ نہیں VirtualMachine وجود Running کہیں خود ہی یہ زیادہ کارآمد نہیں ہے، تو آئیے آپریٹر بھی بناتے ہیں۔ Secret VM کے کنکشن کی تفصیلات کلسٹر کے اندر رکھیں۔

func (r *VirtualMachineReconciler) reconcileConnectionSecret(ctx context.Context, vm *computev1.VirtualMachine) error {
	secret := &corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Name:      vm.Name + "-connection",
			Namespace: vm.Namespace,
		},
		StringData: map[string]string{"id": vm.Status.ID},
	}

	if err := controllerutil.SetControllerReference(vm, secret, r.Scheme); err != nil {
		return err // ties the Secret's lifecycle to this VirtualMachine
	}

	return r.Patch(ctx, secret, client.Apply, client.ForceOwnership, client.FieldOwner("vmoperator")) // create or update, either way
}

SetControllerReference یہ وہی ہے جو یہ بناتا ہے زیر ملکیت وسائلمالک کے حوالہ پر مہر لگائیں۔ Secret دوبارہ اشارہ VirtualMachine.

دونوں مفت فراہم کیے جاتے ہیں۔ حذف کرنا VirtualMachine اب یہ cascades اور Kubernetes کوڑا کرکٹ Secret یہ خاموشی سے چلتا ہے اور اسے فائنلائزر کی ضرورت نہیں ہے کیونکہ یہ بیرونی آبجیکٹ کے بجائے اندرونی کلسٹر آبجیکٹ ہے۔

اور اگر ہم شامل کریں۔ Owns(&corev1.Secret{}) پہلو بہ پہلو For(&computev1.VirtualMachine{}) کو SetupWithManagerترمیم کریں یا حذف کریں۔ Secret یہ اپنے طور پر ملکیت کی ایڈجسٹمنٹ کو دوبارہ شروع کرتا ہے۔ VirtualMachine. لہذا اگر کوئی اسے دستی طور پر حذف کرتا ہے، تو ہم اسے پہچان لیں گے اور اسے دوبارہ بنائیں گے۔

ایک ہی پیٹرن، SetControllerReference کال کریں Owns() رجسٹر کرنے سے دوسرا ملکیتی وسیلہ بنتا ہے۔ Service کلسٹر میں VMs کے سامنے واقع ہے۔ یہ دو مختلف قسم کے وسائل ہیں جو ایک کی ملکیت ہیں۔ VirtualMachineبس کثیر ملکیتی وسائل میرا اصل میں مطلب ہے۔ مختلف اقسام کے لیے ایک ہی پیٹرن کو دو بار کال کرنے سے زیادہ کچھ نہیں۔

وسائل کے درمیان ہم آہنگی۔

ہر VirtualMachine اب تک ہم ایک ہارڈ کوڈڈ فراہم کنندہ اینڈ پوائنٹ کے ساتھ بات چیت کرتے ہیں۔ حقیقی تعیناتیوں میں، یہ قابل ترتیب ہونا ضروری ہے اور شاذ و نادر ہی ایک بار ہوتا ہے۔ VirtualMachines ایک ہی AWS اکاؤنٹ میں ایک ہی اختتامی نقطہ اور اسناد کا اشتراک کرتے ہیں، اور Azure میں اس کے بجائے 100 مزید ہوسکتے ہیں۔

ہم ہیں endpoint براہ راست میدان VirtualMachineSpecتاہم، اگر آپ اپنے اسناد کو گھماتے ہیں یا ٹائپنگ کی غلطی کو درست کرتے ہیں، تو آپ کو ہر چیز میں ترمیم کرنا پڑے گی۔ VirtualMachine ایک وقت میں ایک استعمال کریں۔ اگر آپ اسے اس کی اپنی چیز میں کھینچیں گے تو بہت سے لوگ کریں گے۔ VirtualMachineاس کے بجائے، آپ اسے نام سے حوالہ دیتے ہیں، لہذا ایک ہی ترمیم تمام اشیاء پر پھیل جاتی ہے۔

اب ایک دوسری چھوٹی سی آر ڈی شامل کریں۔

type ProviderConfigSpec struct {
	Endpoint string `json:"endpoint"`
}

اور providerRef میدان VirtualMachineSpec اس کے نام سے ایک کی طرف اشارہ کریں۔ دلچسپ بات یہ ہے کہ یہ کوئی نئی قسم نہیں ہے۔ ایسا ہوتا ہے جب: ProviderConfig تبدیلی

کوئی راستہ نہیں VirtualMachine نہیں دیکھتے ProviderConfig یہ آسان ہے کیونکہ یہ براہ راست ہے اور دونوں کے درمیان مالک کا کوئی حوالہ نہیں ہے۔ Owns() میں نہیں کروں گا۔ اس کے بجائے، ہم اقسام کا مشاہدہ کرتے ہیں اور ہر واقعہ کو تمام واقعات سے نقشہ بناتے ہیں۔ VirtualMachine اس سے کیا مراد ہے:

وسائل کے درمیان مربوط فین آؤٹ

func (r *VirtualMachineReconciler) SetupWithManager(mgr ctrl.Manager) error {
	return ctrl.NewControllerManagedBy(mgr).
		For(&computev1.VirtualMachine{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})).
		Owns(&corev1.Secret{}).
		Owns(&corev1.Service{}).
		Watches(
			&computev1.ProviderConfig{}, // not owned, so Owns() won't catch its changes
			handler.EnqueueRequestsFromMapFunc(r.findVirtualMachinesForProviderConfig),
		).
		Complete(r)
}

func (r *VirtualMachineReconciler) findVirtualMachinesForProviderConfig(ctx context.Context, obj client.Object) []reconcile.Request {
	var vms computev1.VirtualMachineList
	if err := r.List(ctx, &vms, client.InNamespace(obj.GetNamespace())); err != nil {
		return nil
	}

	var requests []reconcile.Request
	for _, vm := range vms.Items {
		if vm.Spec.ProviderRef == obj.GetName() { // only re-enqueue VMs that actually reference this config
			requests = append(requests, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(&vm)})
		}
	}
	return requests
}

یہ ہے وسائل کے درمیان ہم آہنگی۔: ایک وسیلہ میں تبدیلیاں دیگر وسائل کی اقسام کو مکمل طور پر اپنانے کا سبب بنتی ہیں اور صرف فیلڈ ویلیوز سے منسلک ہوتی ہیں، ملکیت سے نہیں۔

یہیں سے اس پورے منصوبے کا تھیم دوبارہ ظاہر ہوتا ہے۔ ProviderConfig اس آپریٹر کا پروڈکشن ورژن AWS، Azure، یا GCP پر حقیقی اسناد اور حقیقی اختتامی نقطہ ہونا ہے۔ فرضی فراہم کنندہ بالکل اسی حد کو سنبھالتا ہے۔

آر بی اے سی

مندرجہ بالا میں سے کوئی بھی اس پر عمل کرنے کے لیے آپ کی اجازت کے بغیر کام نہیں کرے گا۔ مینی فیسٹ صرف ان آئٹمز کی فہرست دیتا ہے جنہیں ہم اصل میں چھوتے ہیں۔ VirtualMachine اور ProviderConfig اعتراض VirtualMachine الگ سے ذیلی وسائل کی حیثیت Secret/Service وہ اشیاء جو ہم بناتے ہیں:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: vmoperator-manager-role
rules:
  - apiGroups: ['compute.example.com']
    resources: ['virtualmachines', 'providerconfigs']
    verbs: ['get', 'list', 'watch', 'create', 'update', 'patch', 'delete']
  - apiGroups: ['compute.example.com']
    resources: ['virtualmachines/status'] # separate rule, it's a separate subresource
    verbs: ['get', 'update', 'patch']
  - apiGroups: ['']
    resources: ['secrets', 'services']
    verbs: ['get', 'list', 'watch', 'create', 'update', 'patch', 'delete']
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: vmoperator-manager-rolebinding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: vmoperator-manager-role
subjects:
  - kind: ServiceAccount
    name: vmoperator-controller-manager
    namespace: vmoperator-system

میمو: VVMoperator کلسٹر سے باہر وسائل کو مکمل طور پر ایک غیر فعال HTTP کلائنٹ کے ذریعے منظم کرتا ہے، لیکن یہ کوئی نیا نمونہ نہیں ہے۔ Crossplane، AWS کنٹرولر برائے Kubernetes، Cluster API، اور cert-manager سبھی اسی طرح CRDs کے ذریعے بیرونی یا غیر Kubernetes ریاست کو مربوط کرتے ہیں۔ ایک بار جب آپ اس پیٹرن کے ساتھ آرام دہ محسوس کرتے ہیں، تو یہ ان وسائل کو پڑھنے کے قابل ہے.

ایک اور نوٹ: ہم جان بوجھ کر VVMOperator کے دائرہ کار کو تنگ کر رہے ہیں۔ چلنے والے VMs کا سائز تبدیل کریں، VMs کو روکیں اور دوبارہ شروع کریں، سنیپ شاٹس لیں، ایک سے زیادہ جسمانی فراہم کنندگان کے لیے سپورٹ کریں ProviderConfig یہ یہاں جو کچھ ہے اس کی قدرتی توسیع ہے، اور جب بنیادی لوپ ٹھوس محسوس ہوتا ہے تو ایک معقول اگلا مرحلہ۔

حصہ 4: پیداوار اور تقسیم

اب جب کہ VVMoperator کام کر رہا ہے، آئیے دیکھتے ہیں کہ اسے کس طرح پیکج اور تعینات کیا جائے، اور پیداواری استعمال کے لیے اسے بہتر بنایا جائے۔

پیکیجنگ اور تقسیم

اب تک کی ہر چیز ہماری مشینوں پر بائنری کے طور پر چل رہی ہے۔ go run کسی بھی کلسٹر کے لیے kubectl کچھ ایسا ہوتا ہے جس کی نشاندہی کی جاتی ہے۔

کوئی راستہ نہیں Deployment اس کے بجائے، ایک تصویر کی ضرورت ہے، لہذا آپریٹر کو ایک کثیر مرحلہ ملتا ہے۔ Dockerfile: مرتب کرنے کے لیے ایک قدم اور حقیقت میں چلانے کے لیے ایک بہت چھوٹی دوسری تصویر:

FROM golang:1.26 AS build
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o /vmoperator ./cmd/manager

FROM gcr.io/distroless/static-debian12
COPY --from=build /vmoperator /vmoperator
USER 65532:65532 # nonroot, matches the security context on the Deployment below
ENTRYPOINT ["/vmoperator"]

تعمیر کے مرحلے میں مکمل گو ٹول کٹ اور تمام سورس فائلیں شامل ہیں، جن میں سے کسی کو بھی بھیجنے کی ضرورت نہیں ہے۔ حتمی تصویر صرف مرتب شدہ بائنری پر مشتمل ہے، جس میں زیادہ تر وہ چیزیں شامل ہیں جن کی کنٹینر سیکیورٹی سیاق و سباق کو بعد میں اس سیکشن میں درخواست کرنے کی ضرورت ہے۔ اس سے پہلے بھی قدم جمانے کے لیے کوئی خول نہیں ہے۔ runAsNonRoot یہ مقرر ہے.

docker build -t registry.example.com/vmoperator:v0.1.0 .
docker push registry.example.com/vmoperator:v0.1.0

وہ تصویر ہے۔ Deployment ذیل میں بیان کیا گیا ہے۔ config/manager/ میں اصل میں اس کا حوالہ دیتا ہوں۔ ایک بار جب آپ اسے کسی ایسی جگہ پر دھکیل دیتے ہیں جہاں کلسٹر اسے کھینچ سکتا ہے، باقی مانی فیسٹس (CRD، RBAC، آپریٹر کا مینی فیسٹ) آگے بڑھ سکتے ہیں۔ Deploymentاور Deployment اور Service فرضی سپلائرز کے لیے۔ لہذا آپ اسے اپنے کمپیوٹر پر ایک ضمنی عمل کے طور پر نہیں چلا رہے ہیں۔

kubectl apply -f config/crd/
kubectl apply -f config/rbac/
kubectl apply -f config/manager/

کسی بھی چیز سے پہلے CRD انسٹال کریں۔ دوسری صورت میں، آپریٹر کی Deployment جب آپ اسے شروع کرتے ہیں تو یہ فوری طور پر کریش ہو جاتا ہے (API سرور کسی ایسے وسائل کی قسم کو دیکھنے کی کوشش کر رہا ہے جس کے بارے میں اس نے کبھی نہیں سنا ہو گا)۔

اسکیما کی تبدیلیاں وہ ہیں جہاں ہاتھ سے لکھے ہوئے مینی فیسٹ ہمیں براہ راست محسوس کیے جاتے ہیں۔ فیلڈ شامل کریں۔ VirtualMachineSpec یہ بے ضرر ہے، لیکن یہ موجودہ اشیاء پر سیٹ نہیں ہے۔ ہم اس کا نام تبدیل نہیں کر رہے ہیں اور نہ ہی اس کی ساخت کو تبدیل کر رہے ہیں۔ سب کچھ محفوظ ہے۔ VirtualMachine پچھلی پیشی کے لیے سیریلائز کیا گیا۔

سی آر ڈی versions فہرست بالکل اسی کے لیے بنائی گئی تھی۔ کیونکہ ایک سے زیادہ ورژن ہو سکتے ہیں۔ served فوری طور پر ایک نشان زد ہے۔ storage یہ آپ کو بتاتا ہے کہ اصل میں کس شکل والی چیز کو رکھا گیا ہے، اور جب کلائنٹ اسٹور شدہ ورژن کے علاوہ کسی دوسرے ورژن کی درخواست کرتا ہے تو کنورژن ویب ہک دو آبجیکٹ کے درمیان بدل جاتا ہے۔

فی الحال VVMOperator کو اس کی ضرورت نہیں ہے۔ v1 یہ واحد ورژن ہے جو کبھی موجود ہے۔ لیکن اسی لیے versions فیلڈ ہمارے لکھے ہوئے پہلے مینی فیسٹ میں ایک واحد قدر کے بجائے ایک فہرست تھی۔

مندرجہ بالا میں سے کوئی بھی رنر کی جگہ نہیں لیتا۔ kubectl apply ہمیشہ کے لیے ہاتھ سے۔ ایک CI پائپ لائن جو آپریٹر کی شبیہہ بناتی ہے، اسے آگے بڑھاتی ہے، اور ضم ہونے پر مینی فیسٹ کو بیس پر لاگو کرتی ہے ایک قدرتی اگلا مرحلہ ہے۔ یہ عام سی آئی/سی ڈی ہے، اور اگر مینی فیسٹ خود گٹ میں ہے، تو یہ آپریٹر سے متعلق نہیں ہے۔

کارکردگی اور لچک

پہلے سے طے شدہ طور پر، کنٹرولر ایک وقت میں صرف ایک ایڈجسٹمنٹ پر کارروائی کرتا ہے۔ یہ تب تک ٹھیک ہے جب تک کہ ہم صرف ٹیسٹ کر رہے ہیں، لیکن سیکڑوں لوگ ٹیسٹ کر رہے ہیں۔ VirtualMachine آبجیکٹ کا مطلب ہے کہ ان میں سے اکثر اپنی باری کے انتظار میں کام کی قطار میں بیٹھے ہیں، حالانکہ ایک بلاک کو کوآرڈینیٹ کرنے کے بارے میں کچھ بھی دوسرے بلاکس کو ہم آہنگ نہیں کرے گا۔

MaxConcurrentReconciles میں درج ذیل کو اٹھاتا ہوں:

func (r *VirtualMachineReconciler) SetupWithManager(mgr ctrl.Manager) error {
	return ctrl.NewControllerManagedBy(mgr).
		For(&computev1.VirtualMachine{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})).
		Owns(&corev1.Secret{}).
		Owns(&corev1.Service{}).
		Watches(&computev1.ProviderConfig{}, handler.EnqueueRequestsFromMapFunc(r.findVirtualMachinesForProviderConfig)).
		WithOptions(controller.Options{MaxConcurrentReconciles: 5}). // five VMs in flight instead of one
		Complete(r)
}

کیشنگ اس کوآرڈینیٹر کے صرف ایک طرف کی مدد کرتی ہے۔ پڑھنا vm سے واپس آیا r.Get یہ پہلے سے ہی تیز اور مقامی ہے کیونکہ مخبر اسے میموری میں رکھتا ہے۔ لیکن r.Provider.Get یہ ہر بار ایک حقیقی HTTP راؤنڈ ٹرپ ہے، اور اس کے سامنے کوئی کیش نہیں ہے۔

یہ توازن قابل غور ہے کیونکہ یہ حصہ 2 کی طرح ہی ہے۔ چونکہ Kubernetes نے ہمارے لیے کیشنگ پرت بنائی ہے، اس لیے ان کلسٹر ریڈز سستے ہیں اور کلسٹر سے باہر ریڈز کی لاگت بالکل وہی ہے جو تار کے دوسرے سرے پر ہے۔ آپ فراہم کنندہ کے کلائنٹس کے سامنے قلیل مدتی کیش شامل کر سکتے ہیں، لیکن یہ ایک حقیقی قیمت پر آتا ہے۔ Running VMs کے لیے جو ابھی ناکام ہو چکے ہیں، کیشے کی میعاد ختم ہونے تک ریاست کو دہرایا جاتا ہے۔

فراہم کنندہ کے سامنے کوئی کیش نہ ہونے کا مطلب ہے کہ ہمیں کیشے میں خلل ڈالنے سے روکنے کے لیے کوئی چیز نہیں ہے۔ مفاہمت کا دھماکہ، ہر لفظ VirtualMachine کلسٹر کے دوبارہ شروع ہونے کے بعد، ایک ٹچ بغیر کسی کوآرڈینیشن کے HTTP کالوں میں اضافے کا سبب بنتا ہے۔ ناکامی پر، کام کی قطار کلائنٹ کو ریٹ لمیٹر تھروٹلنگ میں لپیٹ دیتی ہے، قطع نظر اس کے کہ وہ پہلے سے کر رہا ہے کوئی بیک آف۔

type Client struct {
	baseURL string
	http    *http.Client
	limiter *rate.Limiter // shared across every reconcile using this client
}

func (c *Client) Create(ctx context.Context, image string) (*VM, error) {
	if err := c.limiter.Wait(ctx); err != nil {
		return nil, err
	}
	// ... existing HTTP call
}

لیڈر کا انتخاب دوسرا نصف ہے جو محفوظ طریقے سے ایک سے زیادہ نقل چلاتا ہے۔ ہم نے اسے حصہ 2 میں تصور میں تبدیل کیا، لیکن حقیقت میں ہمارے مینیجر میں دو فیلڈز ہیں۔

mgr, err := ctrl.NewManager(cfg, ctrl.Options{
	LeaderElection:   true,
	LeaderElectionID: "vmoperator-leader",
})

اس سیٹ کے ساتھ، تمام نقلیں شروع کی جائیں گی، لیکن صرف لیز پر رکھنے والی نقلیں اصل میں صلح کی جائیں گی۔ باقی اس وقت سنبھالنے کے لئے تیار ہیں جب وہ وقت پر اپ ڈیٹ نہیں ہوتے ہیں۔

ہم آہنگی بھی ایک تنازعہ کو سطح پر لاتی ہے جس کو حصہ 3 میں واضح کیا گیا ہے۔ فرض کریں کہ کوآرڈینیٹر کال کرتا ہے۔ r.Provider.Createفراہم کنندہ ایک VM بناتا ہے، اس کی ID واپس کرتا ہے، اور پھر عمل ختم ہونے سے پہلے کریش ہو جاتا ہے۔ r.Status().Update کبھی دوڑیں گے۔ vm.Status.ID یہ ابھی بھی خالی ہے، لہذا اگلی مفاہمت ان اشیاء کی جانچ کرے گی جن کے پاس ابھی تک VM نہیں ہے اور کال کریں: Create دوبارہ اب فراہم کنندہ کے پاس ایک کے لیے دو VMs ہیں۔ VirtualMachine.

کچھ نہیں MaxConcurrentReconciles یا لیڈر کا انتخاب اس سے روکتا ہے۔ یہ تخلیق کے مرحلے میں ہی ایک خلا ہے، اور صرف اس صورت میں ظاہر ہوتا ہے جب بیرونی کال اور اسے ریکارڈ کرنے والی تحریر کے درمیان کچھ غلط ہو جائے۔

درحقیقت بند کرنے کا مطلب یہ ہے کہ فراہم کنندہ کو ایک آئیڈیمپوٹینٹ کلید کو قبول کرنا چاہیے جو ایک بار بنائی گئی تھی اور پہلی کلید سے پہلے آبجیکٹ میں محفوظ کی گئی تھی۔ Create لہذا اگر آپ دوبارہ تخلیق کی کوشش کریں گے، تو یہ پہچان لے گا کہ یہ دوسرا VM بنانے کے بجائے پہلے ہی ہوچکا ہے۔

سیکورٹی

کہ ClusterRole حصہ 3 کام کرتا ہے، لیکن ضرورت سے زیادہ وسیع گنجائش رکھتا ہے۔ یہ تمام فعل دیتا ہے۔ secrets اور services پورے کلسٹر میں، آپریٹرز صرف اپنی ملکیت والی اشیاء کو چھوتے ہیں۔

ایک سخت ورژن اس کا استعمال کرتے ہوئے ایک ہی نام کی جگہ پر اسکوپ کرتا ہے: Role/RoleBinding اس کے بجائے ClusterRole/ClusterRoleBinding VVMOperator صرف ایک کے چلنے کی توقع رکھتا ہے اور ہم وہ فعل چھوڑ دیتے ہیں جنہیں ہم کال نہیں کرتے۔ ہم کبھی نہیں list یا watch بے ترتیب Secretصرف وہی ہے جو ہم سے باہر ہے اور جو ہم نے بنایا ہے۔ یہ RBAC بھی ہے جس کا حوالہ پچھلے حصے میں لاگو کردہ مینی فیسٹ کے ذریعہ دیا گیا ہے۔

اسناد ایک اور خلا ہیں۔ ProviderConfig میرے پاس فی الحال ایک سادہ متن کا اختتامی نقطہ ہے اور اصل فراہم کنندہ کو API کلید کے ساتھ API کلید کی ضرورت ہے۔ CRD تصریح کے ساتھ کوئی کاروبار نہیں بیٹھا ہے جو کسی کو بھی اس چیز تک پڑھنے کی رسائی کے ساتھ نظر آتا ہے۔ اس کا تعلق ہے Secretنام سے حوالہ دیا گیا، شامل نہیں:

type ProviderConfigSpec struct {
	Endpoint  string                      `json:"endpoint"`
	SecretRef corev1.LocalObjectReference `json:"secretRef"` // Secret holding the provider's API key
}

ثالث اسے حل کرے گا۔ SecretRef جس وقت آپ فراہم کنندہ کلائنٹ بناتے ہیں، کلید کلائنٹ سے پڑھی جاتی ہے۔ Secretہم اسے کسی بھی ایسی چیز پر لاگ یا دوبارہ نہیں لکھتے ہیں جس میں پڑھنے کی وسیع رسائی ہو، بشمول ڈیٹا۔ VirtualMachineاپنی حالت.

آخری حصہ آپریٹر کا اپنا پوڈ ہے۔ غیر جڑ صارف کے طور پر چلنے والا ایک کنٹینر سیکیورٹی سیاق و سباق ایک صرف پڑھنے کے لیے روٹ فائل سسٹم ترتیب دیتا ہے، لینکس کی غیر ضروری خصوصیات کو ہٹاتا ہے، اور اگر بائنری خود خراب ہو تو ممکنہ فعالیت کو کم کر دیتا ہے۔ یہ آپریٹر کے لیے مخصوص نہیں ہے۔ یہ تمام کام کے بوجھ کے لیے معیاری مشق ہے۔

اگر آپ نے اس گائیڈ میں کہیں بھی قبولیت کا ویب ہک شامل کیا ہے، تو وہ سرٹیفکیٹ بھی وہیں ہوگا۔ ہمیں VVMOperator کے لیے اس کی ضرورت نہیں تھی، اس لیے ہم اسے کنفیگر کرنے کی بجائے پوائنٹر کے طور پر چھوڑ دیں گے۔

مشاہدہ

منتظم ہمیں کچھ لکھے بغیر پرومیتھیس کے اختتامی نقطہ کو بے نقاب کرتا ہے۔ کام کی قطار کی گہرائی، مفاہمت کی مدت، اور مفاہمت کی غلطی کی گنتی فی کنٹرولر پہلے سے ہی دستیاب ہے۔

فراہم کنندہ کے بارے میں خود بخود کچھ نہیں ہے، لہذا آپ میٹرکس کو اسی طرح شامل کرتے ہیں جس طرح آپ Go سروس کے لیے کرتے ہیں۔

var providerCallDuration = prometheus.NewHistogramVec(
	prometheus.HistogramOpts{
		Name: "vmoperator_provider_call_duration_seconds",
		Help: "Duration of calls to the VM provider, by operation",
	},
	[]string{"operation"},
)

func init() {
	metrics.Registry.MustRegister(providerCallDuration) // shares the manager's existing /metrics endpoint
}

اس کے ارد گرد، ہر فراہم کنندہ کال کو ٹائمر میں لپیٹنا آپ کو اندازہ لگانے سے لے کر اس سوال کو گراف کرنے تک لے جاتا ہے، "کیا میرا فراہم کنندہ سست ہے؟”

اسی جبلت سے لاگنگ کے فوائد۔ log.FromContext(ctx) اندر Reconcile میں اسے پہلے ہی اپنے ساتھ لے جاتا ہوں۔ VirtualMachineاسے ایک بار سیٹ کریں اور ہر لائن پر نام اور نام کی جگہ نظر آئے گی۔ SetupWithManager. شامل کرنا vm.Status.ID اس لاگر کے سیٹ اپ ہونے کے فوراً بعد اس سے جڑنے کا مطلب یہ ہے کہ اس مفاہمت کے لیے تمام بعد کی لاگ لائنیں بھی VM کے لیے فراہم کنندہ کے منفرد شناخت کنندہ کو پاس کر دی جائیں گی۔ یہ ایک فیلڈ آپ کو ایک ہی درخواست کے لیے فرضی فراہم کنندہ لاگز اور آپریٹر لاگز جمع کرنے اور ایک ہی ناکامی کے دونوں اطراف تلاش کرنے کی اجازت دیتا ہے۔

اگلے اقدامات

اس گائیڈ میں، آپ نے سیکھا کہ Kubernetes آپریٹر کیا ہے، اسے شروع سے کیسے بنایا جائے، اور اسے پیداوار کے لیے کیسے تیار کیا جائے۔ آپ نے فائنلائزرز، پیشین گوئیاں، ملکیتی وسائل، اور وسائل کے درمیان ہم آہنگی کے بارے میں بھی سیکھا۔

اس میں سے کوئی بھی VM مینجمنٹ تک محدود نہیں ہے۔ درج ذیل آپریٹرز یکساں نظر آتے ہیں اس سے کوئی فرق نہیں پڑتا ہے کہ وہ کس چیز کا انتظام کرتے ہیں:

آپ ذیل کے وسائل کا جائزہ لے کر بھی سیکھنا جاری رکھ سکتے ہیں۔

Scroll to Top