کنٹینر امیجز کی "سمارٹ" صفائی کا مسئلہ اور werf میں اس کا حل

کنٹینر امیجز کی "سمارٹ" صفائی کا مسئلہ اور werf میں اس کا حل

مضمون میں کنٹینر رجسٹریوں (ڈوکر رجسٹری اور اس کے اینالاگ) میں جمع ہونے والی تصاویر کی صفائی کے مسائل پر بحث کی گئی ہے جو کوبرنیٹس کو فراہم کی جانے والی کلاؤڈ مقامی ایپلی کیشنز کے لیے جدید CI/CD پائپ لائنوں کی حقیقتوں میں جمع ہوتی ہیں۔ تصاویر کی مطابقت کا بنیادی معیار اور اس کے نتیجے میں خودکار صفائی، جگہ بچانے اور ٹیموں کی ضروریات کو پورا کرنے میں پیش آنے والی دشواریوں کو دیا گیا ہے۔ آخر میں، ایک مخصوص اوپن سورس پروجیکٹ کی مثال استعمال کرتے ہوئے، ہم آپ کو بتائیں گے کہ ان مشکلات پر کیسے قابو پایا جا سکتا ہے۔

تعارف

کنٹینر کی رجسٹری میں تصاویر کی تعداد تیزی سے بڑھ سکتی ہے، زیادہ ذخیرہ کرنے کی جگہ لیتی ہے اور اس طرح اس کی قیمت میں نمایاں اضافہ ہوتا ہے۔ رجسٹری میں موجود جگہ کی قابل قبول نمو کو کنٹرول، محدود یا برقرار رکھنے کے لیے، یہ قبول کیا جاتا ہے:

  1. تصاویر کے لیے ایک مقررہ تعداد میں ٹیگ استعمال کریں۔
  2. کسی طرح سے تصاویر کو صاف کریں۔


پہلی حد بعض اوقات چھوٹی ٹیموں کے لیے قابل قبول ہوتی ہے۔ اگر ڈویلپرز کے پاس کافی مستقل ٹیگ ہیں (latest, main, test, boris وغیرہ)، رجسٹری سائز میں نہیں پھولے گی اور زیادہ دیر تک آپ کو اسے صاف کرنے کے بارے میں سوچنے کی ضرورت نہیں پڑے گی۔ سب کے بعد، تمام غیر متعلقہ تصاویر مٹ جاتی ہیں، اور صفائی کے لیے کوئی کام نہیں بچا ہے (سب کچھ ایک باقاعدہ کچرا اٹھانے والے کے ذریعے کیا جاتا ہے)۔

تاہم، یہ نقطہ نظر ترقی کو بہت حد تک محدود کرتا ہے اور جدید CI/CD منصوبوں پر شاذ و نادر ہی لاگو ہوتا ہے۔ ترقی کا ایک لازمی حصہ تھا۔ آٹومیشن، جو آپ کو صارفین کو بہت تیزی سے جانچنے، تعینات کرنے اور نئی فعالیت فراہم کرنے کی اجازت دیتا ہے۔ مثال کے طور پر، ہمارے تمام پروجیکٹس میں، ہر کمٹ کے ساتھ ایک CI پائپ لائن خود بخود بن جاتی ہے۔ اس میں، تصویر کو جمع کیا جاتا ہے، جانچا جاتا ہے، ڈیبگنگ اور بقیہ چیک کے لیے مختلف Kubernetes سرکٹس میں رول آؤٹ کیا جاتا ہے، اور اگر سب کچھ ٹھیک ہے تو تبدیلیاں آخری صارف تک پہنچ جاتی ہیں۔ اور یہ اب راکٹ سائنس نہیں ہے، بلکہ بہت سے لوگوں کے لیے روزمرہ کا واقعہ ہے - غالباً آپ کے لیے، چونکہ آپ یہ مضمون پڑھ رہے ہیں۔

چونکہ کیڑے کو ٹھیک کرنا اور نئی فعالیت کو تیار کرنا متوازی طور پر کیا جاتا ہے، اور ریلیز دن میں کئی بار کی جا سکتی ہے، یہ ظاہر ہے کہ ترقی کے عمل کے ساتھ ایک قابل ذکر تعداد میں کمٹٹس بھی ہوتے ہیں، جس کا مطلب ہے رجسٹری میں بڑی تعداد میں تصاویر. نتیجے کے طور پر، رجسٹری کی مؤثر صفائی کو منظم کرنے کا مسئلہ پیدا ہوتا ہے، یعنی غیر متعلقہ تصاویر کو ہٹانا.

لیکن آپ یہ بھی کیسے طے کرتے ہیں کہ آیا کوئی تصویر متعلقہ ہے؟

تصویر کی مطابقت کے لیے معیار

زیادہ تر معاملات میں، اہم معیار یہ ہوں گے:

1. پہلی (سب سے زیادہ واضح اور سب سے اہم) وہ تصاویر ہیں جو فی الحال Kubernetes میں استعمال کیا جاتا ہے. ان تصاویر کو ہٹانے کے نتیجے میں اہم پیداواری وقت کے اخراجات ہو سکتے ہیں (مثال کے طور پر، تصاویر کو نقل کے لیے درکار ہو سکتا ہے) یا کسی بھی لوپ پر ٹیم کی ڈیبگنگ کی کوششوں کی نفی ہو سکتی ہے۔ (اس وجہ سے ہم نے ایک خاص بنایا پرومیتھیس برآمد کنندہ، جو کسی بھی Kubernetes کلسٹر میں ایسی تصاویر کی عدم موجودگی کا پتہ لگاتا ہے۔)

2. دوسرا (کم واضح، لیکن بہت اہم اور دوبارہ استحصال سے متعلق) - تصاویر جو سنگین مسائل کا پتہ لگانے کی صورت میں رول بیک کے لیے ضروری ہے۔ موجودہ ورژن میں. مثال کے طور پر، ہیلم کے معاملے میں، یہ وہ تصاویر ہیں جو ریلیز کے محفوظ کردہ ورژن میں استعمال ہوتی ہیں۔ (ویسے، ہیلم میں پہلے سے طے شدہ حد 256 نظرثانی ہے، لیکن اس بات کا امکان نہیں ہے کہ کسی کو واقعی بچانے کی ضرورت ہو اس طرح ورژنز کی ایک بڑی تعداد؟... اگر ضروری ہو تو ان کے پاس "رول بیک" کریں۔

3. تیسرا - ڈویلپر کی ضروریات: تمام تصاویر جو ان کے موجودہ کام سے متعلق ہیں۔ مثال کے طور پر، اگر ہم ایک PR پر غور کر رہے ہیں، تو یہ سمجھ میں آتا ہے کہ آخری کمٹ کے مطابق تصویر چھوڑ دیں اور، کہہ لیں، پچھلی کمٹ: اس طرح ڈویلپر کسی بھی کام پر تیزی سے واپس آ سکتا ہے اور تازہ ترین تبدیلیوں کے ساتھ کام کر سکتا ہے۔

4. چوتھا - وہ تصاویر جو ہماری درخواست کے ورژن کے مطابق، یعنی حتمی مصنوعات ہیں: v1.0.0، 20.04.01/XNUMX/XNUMX، سیرا، وغیرہ۔

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

اہلیت اور موجودہ حل

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

* مخصوص کنٹینر رجسٹری کے نفاذ پر منحصر ہے۔ ہم نے درج ذیل حل کے امکانات پر غور کیا: Azure CR، Docker Hub، ECR، GCR، GitHub Packages، GitLab کنٹینر رجسٹری، ہاربر رجسٹری، JFrog Artifactory، Quay.io - ستمبر 2020 تک۔

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

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

پہلے دو معیاروں کے ساتھ صورتحال یکساں ہے: وہ کسی بیرونی نظام سے ڈیٹا حاصل کیے بغیر مطمئن نہیں ہوسکتے ہیں - وہ جہاں ایپلی کیشنز کو تعینات کیا جاتا ہے (ہمارے معاملے میں، Kubernetes)۔

Git میں ورک فلو کی مثال

ہم کہتے ہیں کہ آپ گٹ میں کچھ اس طرح کام کر رہے ہیں:

کنٹینر امیجز کی "سمارٹ" صفائی کا مسئلہ اور werf میں اس کا حل

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

اگر صفائی کی پالیسیاں صرف تصاویر کو برقرار رکھنے کی اجازت دیتی ہیں تو کیا ہوگا (حذف نہیں کیا گیا) دیئے گئے ٹیگ کے ناموں سے?

کنٹینر امیجز کی "سمارٹ" صفائی کا مسئلہ اور werf میں اس کا حل

ظاہر ہے کہ ایسا منظر کسی کو خوش نہیں کرے گا۔

اگر پالیسیاں تصاویر کو حذف نہ کرنے کی اجازت دیتی ہیں تو کیا تبدیلی آئے گی؟ ایک مقررہ وقت کے وقفہ / آخری کمٹ کی تعداد کے مطابق?

کنٹینر امیجز کی "سمارٹ" صفائی کا مسئلہ اور werf میں اس کا حل

نتیجہ بہت بہتر ہو گیا ہے، لیکن اب بھی مثالی سے دور ہے. بہر حال، ہمارے پاس اب بھی ایسے ڈویلپرز ہیں جنہیں کیڑے کو ڈیبگ کرنے کے لیے رجسٹری میں تصاویر کی ضرورت ہے (یا یہاں تک کہ K8s میں بھی تعینات)...

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

تاہم، ہم ایک عالمگیر حل تلاش کر رہے تھے جو مختلف رجسٹریوں کا استعمال کرتے ہوئے مختلف ٹیموں کے لیے تصویر کی صفائی کو خودکار کر دے...

عالمگیر تصویر کی صفائی کا ہمارا راستہ

یہ ضرورت کہاں سے آتی ہے؟ حقیقت یہ ہے کہ ہم ڈویلپرز کا ایک الگ گروپ نہیں ہیں، بلکہ ایک ٹیم ہے جو ایک ساتھ ان میں سے بہت سے لوگوں کی خدمت کرتی ہے، جو CI/CD کے مسائل کو جامع طریقے سے حل کرنے میں مدد کرتی ہے۔ اور اس کے لیے اہم تکنیکی ٹول اوپن سورس یوٹیلیٹی ہے۔ werf. اس کی خاصیت یہ ہے کہ یہ ایک فنکشن انجام نہیں دیتا ہے، لیکن تمام مراحل پر مسلسل ترسیل کے عمل کے ساتھ ہے: اسمبلی سے تعیناتی تک۔

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

* اگرچہ رجسٹریاں خود مختلف ہوسکتی ہیں (Docker Registry، GitLab Container Registry، Harbor، وغیرہ)، ان کے صارفین کو ایک جیسے مسائل کا سامنا ہے۔ ہمارے معاملے میں عالمگیر حل رجسٹری کے نفاذ پر منحصر نہیں ہے، کیونکہ خود رجسٹریوں سے باہر چلتا ہے اور سب کے لیے ایک جیسا سلوک پیش کرتا ہے۔

اگرچہ ہم werf کو ایک مثال کے نفاذ کے طور پر استعمال کر رہے ہیں، ہم امید کرتے ہیں کہ استعمال کیے جانے والے نقطہ نظر اسی طرح کی مشکلات کا سامنا کرنے والی دوسری ٹیموں کے لیے مفید ثابت ہوں گے۔

تو ہم مصروف ہو گئے۔ بیرونی تصاویر کی صفائی کے لیے ایک طریقہ کار کا نفاذ - ان صلاحیتوں کے بجائے جو پہلے سے کنٹینرز کی رجسٹریوں میں بنی ہوئی ہیں۔ پہلا قدم یہ تھا کہ ٹیگز کی تعداد اور ان کی تخلیق کے وقت (اوپر ذکر کیا گیا) کے لیے ایک جیسی ابتدائی پالیسیاں بنانے کے لیے Docker Registry API کا استعمال کیا جائے۔ ان میں شامل کیا گیا۔ تعینات انفراسٹرکچر میں استعمال ہونے والی تصاویر کی بنیاد پر فہرست کی اجازت دیں۔، یعنی کوبرنیٹس۔ مؤخر الذکر کے لیے، تمام تعینات کردہ وسائل کے ذریعے اعادہ کرنے اور اقدار کی فہرست حاصل کرنے کے لیے Kubernetes API کا استعمال کرنا کافی تھا۔ image.

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

اسکیموں کو ٹیگ کرنا

شروع کرنے کے لیے، ہم نے ایک نقطہ نظر کا انتخاب کیا جس میں حتمی تصویر میں صفائی کے لیے ضروری معلومات کو ذخیرہ کرنا چاہیے، اور اس عمل کو ٹیگنگ اسکیموں پر بنایا گیا ہے۔ تصویر شائع کرتے وقت، صارف نے ایک مخصوص ٹیگنگ آپشن کا انتخاب کیا (git-branch, git-commit یا git-tag) اور متعلقہ قدر کا استعمال کیا۔ CI نظاموں میں، یہ اقدار ماحولیاتی متغیرات کی بنیاد پر خود بخود ترتیب دی گئی تھیں۔ حقیقت میں حتمی تصویر ایک مخصوص گٹ پرائمیٹو سے وابستہ تھی۔لیبلز میں صفائی کے لیے ضروری ڈیٹا کو محفوظ کرنا۔

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

  • Git میں برانچ/ٹیگ کو حذف کرتے وقت، رجسٹری میں منسلک تصاویر خود بخود حذف ہو جاتی تھیں۔
  • گٹ ٹیگز اور کمٹ کے ساتھ وابستہ امیجز کی تعداد کو منتخب کردہ اسکیما میں استعمال ہونے والے ٹیگز کی تعداد اور اس سے منسلک کمٹ بنانے کے وقت سے کنٹرول کیا جا سکتا ہے۔

مجموعی طور پر، نتیجے میں عمل درآمد نے ہماری ضروریات کو پورا کر دیا، لیکن جلد ہی ایک نیا چیلنج ہمارا انتظار کر رہا ہے۔ حقیقت یہ ہے کہ Git primitives پر مبنی ٹیگنگ اسکیموں کا استعمال کرتے ہوئے، ہمیں بہت سی کوتاہیوں کا سامنا کرنا پڑا۔ (چونکہ ان کی تفصیل اس مضمون کے دائرہ کار سے باہر ہے، اس لیے ہر کوئی اس کی تفصیلات سے خود کو واقف کر سکتا ہے۔ یہاں.) اس لیے، ٹیگنگ (مواد پر مبنی ٹیگنگ) کے لیے زیادہ موثر انداز اختیار کرنے کا فیصلہ کرنے کے بعد، ہمیں تصویر کی صفائی کے نفاذ پر دوبارہ غور کرنا پڑا۔

نیا الگورتھم

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

صفائی کے نئے الگورتھم کے لیے، ٹیگنگ اسکیموں سے ہٹ کر تعمیر کرنے کا فیصلہ کیا گیا۔ میٹا امیج کا عمل، جن میں سے ہر ایک کا ایک گروپ ذخیرہ کرتا ہے:

  • وہ عہد جس پر اشاعت کی گئی تھی (اس سے کوئی فرق نہیں پڑتا ہے کہ کنٹینر رجسٹری میں تصویر کو شامل کیا گیا، تبدیل کیا گیا یا وہی رہا)؛
  • اور ہمارا داخلی شناخت کنندہ اسمبل شدہ تصویر سے مطابقت رکھتا ہے۔

دوسرے الفاظ میں، یہ فراہم کیا گیا تھا شائع شدہ ٹیگز کو گٹ میں کمٹ کے ساتھ جوڑنا.

حتمی ترتیب اور عمومی الگورتھم

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

  • بہت سے حوالہ جات، یعنی Git ٹیگز یا Git شاخیں جو سکیننگ کے دوران استعمال ہوتی ہیں؛
  • اور سیٹ سے ہر حوالہ کے لیے تلاش کی گئی تصاویر کی حد۔

واضح کرنے کے لیے، پہلے سے طے شدہ پالیسی کنفیگریشن اس طرح نظر آنے لگی:

cleanup:
  keepPolicies:
  - references:
      tag: /.*/
      limit:
        last: 10
  - references:
      branch: /.*/
      limit:
        last: 10
        in: 168h
        operator: And
    imagesPerReference:
      last: 2
      in: 168h
      operator: And
  - references:  
      branch: /^(main|staging|production)$/
    imagesPerReference:
      last: 10

اس ترتیب میں تین پالیسیاں شامل ہیں جو درج ذیل اصولوں کی تعمیل کرتی ہیں:

  1. تصویر کو آخری 10 گٹ ٹیگز کے لیے محفوظ کریں (ٹیگ بنانے کی تاریخ کے لحاظ سے)۔
  2. پچھلے ہفتے میں شائع ہونے والی 2 سے زیادہ تصاویر کو پچھلے ہفتے کی سرگرمی کے ساتھ 10 سے زیادہ تھریڈز کے لیے محفوظ نہ کریں۔
  3. شاخوں کے لیے 10 تصاویر محفوظ کریں۔ main, staging и production.

حتمی الگورتھم درج ذیل مراحل پر ابلتا ہے:

  • کنٹینر رجسٹری سے مینی فیسٹس کو بازیافت کرنا۔
  • Kubernetes میں استعمال ہونے والی تصاویر کو چھوڑ کر، کیونکہ ہم نے پہلے ہی K8s API پولنگ کے ذریعے ان کو پہلے سے منتخب کر لیا ہے۔
  • Git کی تاریخ کو اسکین کرنا اور مخصوص پالیسیوں کی بنیاد پر تصاویر کو خارج کرنا۔
  • باقی تصاویر کو ہٹایا جا رہا ہے۔

ہماری مثال پر واپس آتے ہوئے، werf کے ساتھ یہی ہوتا ہے:

کنٹینر امیجز کی "سمارٹ" صفائی کا مسئلہ اور werf میں اس کا حل

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

حاصل يہ ہوا

  • جلد یا بدیر، زیادہ تر ٹیموں کو رجسٹری اوور فلو کے مسئلے کا سامنا کرنا پڑتا ہے۔
  • حل تلاش کرتے وقت، سب سے پہلے تصویر کی مطابقت کے معیار کا تعین کرنا ضروری ہے۔
  • مشہور کنٹینر رجسٹری سروسز کے ذریعہ پیش کردہ ٹولز آپ کو ایک بہت ہی آسان صفائی کا اہتمام کرنے کی اجازت دیتے ہیں جو "بیرونی دنیا" کو مدنظر نہیں رکھتے: Kubernetes میں استعمال ہونے والی تصاویر اور ٹیم کے ورک فلو کی خصوصیات۔
  • ایک لچکدار اور موثر الگورتھم کو CI/CD کے عمل کی سمجھ ہونی چاہیے اور نہ صرف Docker امیج ڈیٹا کے ساتھ کام کرنا چاہیے۔

PS

ہمارے بلاگ پر بھی پڑھیں:

ماخذ: www.habr.com

نیا تبصرہ شامل کریں