اذهب إلى المحتوى

كوبرنيتس Kubernetes هو نظام تنسيق حاويات شهير ومفتوح المصدر، يستخدم لأتمتة نشر البرامج وتوسيعها وإدارتها، ويزداد الاعتماد عليه في الشركات والمؤسسات يومًا بعد يوم لتسهيل عمليات التوسعة الأفقية horizontal scaling لموارد الخادم، ويُقصد بها إضافة المزيد من الخوادم حسب الحاجة لزيادة الموارد المتاحة لتطبيقك، مثل: الحجوم التخزينية وقدرة المعالجة وغيرها. يمكنك الحصول على حلول Kubernetes السحابية من مزودي خدمات سحابية متعددين ولكل خدمة مميزات خاصة في الإدارة وغيرها.

يتطلب العمل مع كوبرنيتس Kubernetes استخدامًا مكثفًا للموارد الحاسوبية، لأنه يعمل في نظام عنقودي يتكون من عدة خوادم، ويُشكل ذلك عبئًا إضافيًا على المطورين وخصوصًا في مرحلة ما قبل الإنتاج، فسيحتاجون الكثير من الموارد لتطوير مكدس Kubernetes تجريبي واختباره قبل النشر في البيئة الفعلية، لذا أنشأ مطورو Kubernetes مشروعًا مساعدًا مخصص لهذا الغرض يدعى minikube، الذي يعمل مع بيئات تشغيل الحاويات مثل دوكر  Docker وغيره، ويستطيع محاكاة عنقود Kubernetes كامل على آلة واحدة فقط هي حاسوبك الشخصي مثلًا.

فما هو minikube المحاكي الشهير لنظام Kubernetes؟ وكيف يستخدم لاختبار إعدادات Kubernetes قبل نشرها في بيئة الإنتاج؟ وما هي لوحة معلومات Kubernetes المضمنة فيه؟ سيجيبك المقال عن هذه الأسئلة، ويعطيك طريقة تثبيته على حاسوبك المحلي أو خادمك البعيد، ثم سنعمل معًا على نشر تطبيق تجريبي بسيط، ونحاول الوصول إليه عبر minikube، وفي الختام سنتعرف على طريقة استخدام Minikube مع عناقيد Kubernetes البعيدة بواسطة ملفات تعريف الإعدادات configuration profiles.

متطلبات العمل

ستحتاج المتطلبات الأولية التالية لتطبيق خطوات العمل المذكورة في المقال:

  • فهم أساسيات Kubernetes، تفيدك مطالعة مقال تعرّف على كوبرنيتس Kubernetes لتكوين فكرة جيدة عن أبرز مفاهيم Kubernetes ومكوناته.
  • تثبيت بيئة تشغيل الحاويات Docker على حاسوبك الذي ستعمل منه، إذ سنُشغل منها minikube. إذا كنت تستخدم نظام تشغيل لينكس، فستساعدك الخطوات الواردة في مقال كيفية تثبيت دوكر واستخدامه على دبيان، ولسهولة العمل احرص على تنفيذ الخطوة المتعلقة بضبط الإعدادات اللازمة لتشغيل Docker بدون الحاجة لكتابة sudo في بداية كل أمر. أما إذا كنت تعتمد نظام تشغيل ويندوز أو ماك فيمكنك الاستعانة بتوثيقات Docker الرسمية لإتمام عملية التثبيت.
  • مدير الحزم Homebrew، يمكنك الاسترشاد بالخطوات الواردة في هذا المقال على DigitalOcean لتثبيته على نظام تشغيل ماك، أو بمقال لتثبيته على لينكس، وفي حال كنت تستخدم نظام ويندوز فتستطيع تثبيته باستخدام WSL نظام ويندوز الفرعي لنظام لينكس .
  • توفير الموارد الحاسوبية اللازمة للبيئة التي ستُثَبِّت فيها Minikube، وهي بالحد الأدنى: وحدتي معالجة مركزية 2CPUs، وذاكرة مخبئية 2GB RAM، ومساحة تخزينية على القرص الصلب بسعة 20GB.

الخطوة 1: تثبيت Minikube وتشغيله

ثبّت minikube بواسطة مدير الحزم Homebrew كما يلي:

$ brew install minikube

وستحصل على خرج يشبه التالي، يبين لك نجاح التثبيت:

…
==> Installing minikube
==> Pouring minikube--1.25.2.x86_64_linux.bottle.tar.gz
==> Caveats
Bash completion has been installed to:
  /home/sammy/.linuxbrew/etc/bash_completion.d
==> Summary
🍺  /home/sammy/.linuxbrew/Cellar/minikube/1.25.2: 9 files, 70.0MB
…

ملاحظة: يتطلب تثبيت minikube على نظام ويندوز الانتباه لبعض التفاصيل المهمة: يعمل minikube مع WSL2 (وهي النسخة المتوفرة من WSL لتاريخ نشر المقال)، وينبغي تهيئته ليستخدم Docker واجهةً خلفية backend بدلًا من واجهته الخلفية الافتراضية. لذا بعد تثبيت Docker احرص على تفعيل ميزة دعم WSL2 باتباع إرشادات توثيقات Docker الخاصة بالموضوع ثم ثبت minikube ونفذ الأمر minikube config set driver docker.

اكتب الآن الأمر start وفق التالي لبدء تشغيل minikube، وسينشأ بداخله آليًّا عنقود Kubernetes محلي بأحدث إصدار مستقر متوفر، ويتضمن عدة حاويات Docker:

$ minikube start

سيتطلب التشغيل بعض الوقت، وستحصل في نهايته على الخرج التالي، مع تجهيز الأداة kubectl لتستخدمها للاتصال مع العنقود cluster، كما يوضح السطر الأخير من الخرج:

👍  Starting control plane node minikube in cluster minikube
🚜  Pulling base image ...
💾  Downloading Kubernetes v1.23.1 preload ...
    > preloaded-images-k8s-v16-v1...: 504.42 MiB / 504.42 MiB  100.00% 81.31 Mi
    > gcr.io/k8s-minikube/kicbase: 378.98 MiB / 378.98 MiB  100.00% 31.21 MiB p
🔥  Creating docker container (CPUs=2, Memory=1987MB) ...
🐳  Preparing Kubernetes v1.23.1 on Docker 20.10.12 ...
    ▪ kubelet.housekeeping-interval=5m
    ▪ Generating certificates and keys ...
    ▪ Booting up control plane ...
    ▪ Configuring RBAC rules ...
🔎  Verifying Kubernetes components...
    ▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5
🌟  Enabled addons: default-storageclass, storage-provisioner
🏄  Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default

ملاحظة: يمكنك اختيار إصدار Kubernetes الذي يناسبك لأسباب تتعلق بالتوافقية أو غيرها، بدلًا من الاعتماد على الإصدار الافتراضي الذي يوفره minikube، وذلك بكتابة رقم الإصدار المطلوب بعد الأمر minikube start بهذا الشكل kubernetes-version v.1.2.3--.

يسمح لك تثبيت minikube بواسطة مدير الحزم Homebrew بالعمل مباشرةً مع kubectl الأداة الأساسية لإدارة عناقيد Kubernetes باستخدام سطر الأوامر، وبالتالي يمكنك كتابة الأمر kubectl get كما يلي لاستعراض جميع pods العاملة في العنقود بالطريقة نفسها المتبعة مع عناقيد Kubernetes العادية:

$ kubectl get pods -A

يعرض الوسيط A- كافة pods العاملة في جميع مساحات الأسماء namespaces الموجودة ضمن العنقود، ألقِ نظرة على شكل الخرج للأمر السابق:

NAMESPACE     NAME                               READY   STATUS    RESTARTS      AGE
kube-system   coredns-64897985d-ttwl9            1/1     Running   0             46s
kube-system   etcd-minikube                      1/1     Running   0             57s
kube-system   kube-apiserver-minikube            1/1     Running   0             61s
kube-system   kube-controller-manager-minikube   1/1     Running   0             57s
kube-system   kube-proxy-ddtgd                   1/1     Running   0             46s
kube-system   kube-scheduler-minikube            1/1     Running   0             57s
kube-system   storage-provisioner                1/1     Running   1 (14s ago)   54s

لديك الآن عنقود Kubernetes محلي، تستطيع إدارته باستخدام أدوات Kubernetes المألوفة مثل kubectl، وسنعرض في الفقرات القادمة وظائف إضافية يوفرها لك minikube لمراقبة عناقيد Kubernetes وإدارتها والتعديل عليها.

الخطوة 2: الوصول إلى لوحة معلومات Kubernetes

يوفر minikube لمستخدميه وصولًا سهلًا للوحة معلومات النظام Kubernetes Dashboard، التي يمكنك استخدامها لمراقبة سلامة العنقود ولنشر التطبيقات يدويًا ولغيرها من أعمال الإدارة. وفور تثبيت minikube محليًا على حاسوبك تستطيع الوصول إلى لوحة معلومات Kubernetes بكتابة الأمر minikube dashboard:

$ minikube dashboard

سيُشغّل هذا الأمر لوحة المعلومات آليًّا، ويفتح منفذًا port خاصًا داخل Kubernets يوجه حركة البيانات إلى العنقود، ثم يعرض رابط اللوحة الذي يشير إلى رقم المنفذ مباشرةً أمامك في متصفح الويب، كما في الصورة التالية.

img01 kubernetes dashboard

يؤدي تشغيل لوحة المعلومات إلى تعطيل النافذة الطرفية terminal التي كَتَبّتَ أمر التشغيل فيها، فلا يمكنك كتابة أوامر أخرى ضمنها، لذا يلجأ المستخدمون إلى تشغيل لوحة المعلومات في نافذة طرفية أخرى غير التي يعملون عليها. يمكنك إيقاف هذه العملية المُعطِّلة وغيرها من العمليات المشابهة بالضغط على Ctrl+C.

أما إذا كنت تستخدم minikube على خادم بعيد، فأضِف الوسيط url-- إلى الأمر minikube dashboard السابق، وسيعطيك في الخرج رابط URL الخاص بلوحة المعلومات، بدلًا من فتحه مباشرة في المتصفح. ألقِ نظرة على الأمر التالي الخاص بتشغيل اللوحة للخادم البعيد:

$ minikube dashboard --url

وسيكون الخرج كما يلي:

🤔  Verifying dashboard health ...
🚀  Launching proxy ...
🤔  Verifying proxy health ...
http://127.0.0.1:34197/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/

يختلف رقم المنفذ الذي يفتحه minikube للوحة المعلومات من نظامٍ إلى آخر، ستلاحظ أن رقمه على حاسوبك مختلفٌ عن رقمه هنا في هذا المثال.

تمنع إعدادات الأمان الافتراضية لنظام Kubernetes الوصول إلى عنوان URL هذا من الأجهزة البعيدة لحمايته، لذا ينبغي عليك بدايةً إعداد قناة SSH آمنة مع الخادم قبل فتحه. اكتب إذاً الأمر التالي مع الراية L- لفتح قناة ssh بين الحاسوب المحلي والخادم البعيد، واكتب ضمنه رقم منفذ لوحة المعلومات الظاهر في الخرج السابق، وعنوان IP لخادمك البعيد ليصبح بهذه الصيغة:

$ ssh -L 34197:127.0.0.1:34197 sammy@your_server_ip

يمكنك بعد ذلك الدخول إلى لوحة المعلومات باستخدام الرابط: http://127.0.0.1:34197/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/.

يمكنك معرفة المزيد عن تقنية الاتصال الآمن SSH بمطالعة الفيديو التالي:

الآن بعد أن اختبرنا التعامل مع minikube مثل أي عنقود Kubernetes كامل عن طريق لوحة المعلومات، سننتقل للخطوة التالية، ونحاول نشر تطبيق تجريبي بسيط في هذا العنقود لنتأكد من عمله كما هو بالطريقة المرجوة منه.

الخطوة 3: نشر تطبيق تجريبي واختباره

يمكنك استخدام الأمر kubectl لنشر تطبيق تجريبي في عنقود Minikube. اكتب مثلًا الأمر التالي الذي سيؤدي إلى نشر تطبيق Kubernetes تجريبي متاح للاختبارات من شركة جوجل يدعى hello-app.

$ kubectl create deployment web --image=gcr.io/google-samples/hello-app:1.0

يُنشئ هذا الأمر عملية نشر deployment داخل العنقود تدعى web، وتُبنى انطلاقًا من صورة بعيدة تسمى hello-app موجودة في سجل حاويات جوجل المسمى gcr.io.

سنُعَرِّف الآن عملية النشر web بصفتها خدمة من خدمات Kubernetes، ونحدد منفذًا ثابتًا للاتصال معه بكتابة المحددين port=8080-- و type=NodePort--، وفق التالي:

$ kubectl expose deployment web --type=NodePort --port=8080

يمكنك معرفة المزيد عن خدمات Kubernetes وكيفية الاتصال معها بمطالعة مقال تعرّف على كوبرنيتس Kubernetes.

لنتحقق فيما إذا كانت الخدمة تعمل أم لا؟ بواسطة الأمر kubectl get service مع كتابة اسم الخدمة بعده، كما يلي:

$ kubectl get service web

ستحصل على خرج يشبه الخرج التالي، مع اختلاف في أرقام المنافذ لأن NodePort توزع أرقام المنافذ عشوائيًا على خدمات Kubernetes:

NAME   TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
web    NodePort   10.109.254.242   <none>        8080:31534/TCP   10s

يمكننا الآن استخدام minikube للحصول على عنوان URL المتاح من خارج الحاوية، يسمح لك هذا العنوان بالاتصال مع خدمة التطبيق hello-app العاملة على المنفذ 8080 داخل العنقود. إذا كنت تستخدم Minikube على حاسوبك المحلي، فلست بحاجة لإعادة توجيه حركة البيانات من منفذ لآخر كما سنذكر لاحقًا، فقط نفذ الأمر minikube service web --url التالي، وستحصل على عنوان URL لتطبيقك التجريبي:

$ minikube service web --url

وسيكون الخرج عنوان URL مثل التالي:

http://192.168.49.2:31534

اختبر عنوان URL بواسطة crul، وهو أحد أشهر برامج سطر الأوامر command line المستخدمة لإرسال أنواع مختلفة من طلبات الويب، يستخدم للتحقق من إمكانية عمل عناوين URL ضمن المتصفح في ظروف مناسبة، لذا ننصحك بفحص الروابط باستخدام crul دائمًا قبل تجربتها في المتصفح، وذلك وفق التالي:

$ curl http://192.168.49.2:31534

يبين لك الخرج التالي نجاح العملية:

Hello, world!
Version: 1.0.0
Hostname: web-746c8679d4-j92tb

يمكنك الآن استعراض عنوان URL هذا في المتصفح مباشرةً إذا كانت تستخدم minikube محليًا، وستحصل على النص السابق غير المنسق نفسه الذي حصلت عليه بتعليمة crul. أما إذا كنت تعمل على جهاز بعيد، استخدم اتصال SSH كما في الخطوة 2، ثم استعرض العنوان في المتصفح.

أصبحت لديك الأساسيات اللازمة لنشر تطبيق عبر minikube فما ينطبق على التطبيق التجريبي البسيط الذي نشرناه يتنطبق نفسه على المشاريع الأكبر حجمًا، والتخصيص الإضافي الذي ستحتاجه فيها يتعلق بنظام Kubernetes وليس بوظائف minikube.

سنتعلم في خطوتنا التالية طريقة استخدام بعض أدوات Minikube المدمجة لتغيير بعض الإعدادات الافتراضية للعنقود.

الخطوة 4: إدارة نظام ملفات Minikube وموارده

يوفر لك minikube عددًا من الأوامر الخاصة بتعديل إعدادات العنقود، فمثلًا يمكنك استخدام الأمر minikube config لتعديل الذاكرة المتوفرة للعنقود وفق التالي، علمًا أن الذاكرة هنا تقدر بالميجابايت MB وبالتالي يقابل الأمر minikube config 4096 توفير ذاكر بسعة 4GB لعنقودك:

$ minikube config set memory 4096

ستحصل على الخرج التالي:

  These changes will take effect upon a minikube delete and then a minikube start

يشير الخرج السابق إلى أن تعديل الذاكرة يتطلب إعادة نشر العنقود ليأخذ مفعوله.

ملاحظة: لا تتطلب جميع التعديلات على الموارد إعادة نشر عنقود Kubernetes لتأخذ مفعولها، ففي بيئات الإنتاج يسري تغيير الذاكرة مباشرةً بدون إعادة نشر، لكن مع ذلك حاول أن لا تجري الكثير من التعديلات على عناقيد Kubernetes قيد التشغيل اعتمادًا على ملفات الإعدادات فقط بدون إجراء إعادة نشر، حاول الالتزام بإعادة النشر بعد كل تعديل على الموارد.

تتضمن إعادة النشر مرحلتين هما minikube delete و minikube start، اكتب أولًا الأمر:

$ minikube delete

وستحصل على الخرج التالي:

🔥  Deleting "minikube" in docker ...
🔥  Deleting container "minikube" ...
🔥  Removing /home/sammy/.minikube/machines/minikube ...
💀  Removed all traces of the "minikube" cluster.

ثم اكتب الأمر:

$ minikube start

يتيح لك minikube وصل أي مجلد من نظام ملفاتك المحلي الموجود على حاسوبك إلى داخل العنقود وصلًا مؤقتًا باستخدام الأمر minikube mount.

أما كيفية كتابة الأمر mount قواعديًا فهي على الشكل التالي: local_path:minikube_host_path. يرمز local_path إلى مسار المجلد المحلي الذي تريد إيصاله إلى داخل العنقود، ويشير الجزء الآخر أي minikube_host_path إلى الموقع أو المجلد داخل VM أو داخل حاوية Minikube الذي تود أن تصل منه إلى ملفاتك.

ألقِ نظرة على الأمر التالي الذي يوصل المجلد الأساسي الخاص بك home directory إلى المجلد host/ داخل عنقود minikube:

$ minikube mount $HOME:/host

وستحصل على الخرج التالي الذي يؤكد لك نجاح العملية:

📁  Mounting host path /home/sammy into VM as /host ...
    ▪ Mount type:
    ▪ User ID:      docker
    ▪ Group ID:     docker
    ▪ Version:      9p2000.L
    ▪ Message Size: 262144
    ▪ Options:      map[]
    ▪ Bind Address: 192.168.49.1:43605
🚀  Userspace file server: ufs starting
  Successfully mounted /home/sammy to /host

📌  NOTE: This process must stay alive for the mount to be accessible ...

تفيدك هذه الطريقة في الحالات التي تحتاج فيها للحفاظ على مدخلات أو مخرجات ثابتة لعملك مع العنقود، مثل عمليات تسجيل الأحداث logging لعنقود minikube.

تعطل هذه العملية نافذة الطرفية فلا يعود بإمكانك كتابة الأوامر فيها، يمكنك الخروج منها بالضغط على Ctrl+C، تمامًا كما فعلنا في حالة فتح المنفذ الخاصة بلوحة المعلومات التي ذكرناها في الخطوة 2. سنتعلم في الخطوة التالية كيف تتنقل بكفاءة بين minikube وعنقود Kubernetes كامل موجود على خادم بعيد.

التعامل مع أكثر من عنقود Kubernetes

يستطيع minikube التعامل مع عناقيد Kubernetes محلية متعددة في الوقت نفسه، فيُهيئ لكل عنقود ملف تعريف profile خاص به. فقد تحتاج في بعض الحالات للتعامل مع إصدارات مختلفة من عناقيد Kubernetes لإجراء اختبار معين مثلًا، فيمكنك عندها التبديل بين هذه الإصدارات باستخدام الراية p- أو profile--.

وإذا كنت ستعمل مع عنقود معين لفترة طويلة أو أكثر من بقية العناقيد، فيمكنك تعيينه ليكون ملف التعريف الافتراضي في minikube بواسطة الأمر minikube profile، بدلًا من تحديده بعد الراية profile-- في كل أمر تنفذه.

لنُشغّل الآن Minikube مع ملف تعريفي جديد بتنفيذ الأمر minikube start مع الراية p-، وفق التالي:

$ minikube start -p new-profile

اضبط الآن هذا الملف التعريفي الجديد ليكون هو الملف الفعال أو الافتراضي في Minikube بكتابة الأمر minikube profile كما يلي:

$ minikube profile new-profile

سيظهر لك هذا الخرج:

  minikube profile was successfully set to new-profile

يمكنك معرفة الملف التعريفي الحالي الذي تستخدمه بواسطة الأمر get profile كما يلي:

$ minikube config get profile

وسيعيد لك الخرج اسم الملف التعريفي الحالي وهو في مثالنا:

new-profile

يُنشئ minikube ملفات الإعدادات لمنظومتك، ويخزنها هذه الملفات في مكانها الافتراضي المعروف للأداة kubectl ولغيرها من أدوات Kubernetes لتتمكن من الوصول إليها، ذلك سواء كنت تستخدم ملف تعريفي واحد أو عدة ملفات تعريفية، فمثلًا في كل مرة تنفذ فيها الأمر kubectl get nodes لاستعراض بيانات العقد nodes لعنقود minikube ستُحلل kubectl ملفات الإعدادات وتعطيك النتيجة، ألقِ نظرة على الأمر أدناه:

$ kubectl get nodes

وسيبين الخرج العقد الموجودة وهي عقدة واحدة فقط في حالتنا كما يلي:

NAME       STATUS   ROLES                  AGE     VERSION
minikube   Ready    control-plane,master   3h18m   v1.23.1

يمكنك اختيار أي ملف إعدادات تريده وإسناده للمُحَدِد kubeconfig لتقرأه kubectl عند بدء تشغيلها بدلًا من الملف الافتراضي الموجود في المجلد kube/confg. /~، وعندها ستستخدم بيانات اعتماد العنقود المذكورة في هذا الملف عوضًا عن تلك الموجودة في الملف الافتراضي. لنفترض أن لديك ملف إعدادات اسمه remote-kubeconfig.yaml مثلًا لعنقود Kubernetes آخر غير عنقودك في Minikube، وتريد استخراج العقد الموجودة فيه، فستكتب حينئذ الأمر التالي:

$ kubectl --kubeconfig=remote-kubeconfig.yaml get nodes

وستحصل في الخرج على العقد الموجودة في ذلك العنقود، والتي تعمل عن بعد خارج Minikube الخاص بك، وسيبدو الخرج كما يلي:

NAME                   STATUS   ROLES    AGE    VERSION
pool-xr6rvqbox-uha8f   Ready    <none>   2d2h   v1.21.9
pool-xr6rvqbox-uha8m   Ready    <none>   2d2h   v1.21.9
pool-xr6rvqbox-uha8q   Ready    <none>   2d2h   v1.21.9

صُمم Kubernetes في الأساس للعمل مع ملف إعدادات واحد لكل عنقود، يُمَرَر للأوامر مثل kubectl وغيره عند التشغيل، ومع ذلك يمكنك دمج عدة ملفات إعدادات مع بعضها، إلّا أنها ليست الطريقة المفضلة ولا تُعدّ ضرورية إذ سيضعب عليك بعدها تتبع أفضل الممارسات الموصى بها للعمل مع Kubernetes. ننصحك أيضًا بالتَعَرُّف على Krew مدير الحزم الخاص بإضافات Kubectl plugins.

الخلاصة

وضح هذا المقال كيفية تثبيت Minikube محليًا على الحاسوب الشخصي، واستخدام لوحة معلومات Kubernetes المُضمنة لمراقبة التطبيقات ونشرها، مع الإضاءة على الفكرة الأهم، وهي إمكانية العمل على نشر مثيل instance تجريبي للتطبيق واختباره محليًا ضمن minikube بالتزامن مع وجود مثيل Kubernetes بعيد نصل إليه بواسطة ملفات تعريف Minikube والراية kubectl --kubeconfig. خلاصة القول: يساعدك minikube على اختبار إعدادات Kubernetes وتقييمها محليًا قبل نشرها الفعلي، لتحدد كيف ومتى تصبح جاهزًا لنشر Kubernetes في بيئة الإنتاج.

ترجمة -وبتصرف- للمقال How To Use minikube for Local Kubernetes Development and Testing لصاحبه Alex Garnett.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...