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

أمل عبدالله محمد الجنايني

الأعضاء
  • المساهمات

    10
  • تاريخ الانضمام

  • تاريخ آخر زيارة

1 متابع

آخر الزوار

لوحة آخر الزوار معطلة ولن تظهر للأعضاء

إنجازات أمل عبدالله محمد الجنايني

عضو مساهم

عضو مساهم (2/3)

1

السمعة بالموقع

  1. يمكن أن يكون نشر التطبيقات في Kubernetes، نظام تنسيق الحاويات القوي والشائع، أمرًا معقدا. قد يتضمن إعداد تطبيق واحد إنشاء التعامل مع موارد Kubernetes المترابطة، مثل كائنات Pod، والخدمات وعمليات النشر والنسخ المتماثلة، يتطلب كل منها كتابة ملف YAML مفصل. Helm هو مدير حزم يسمح للمطورين والمشغلين بتحزيم التطبيقات والخدمات وإعدادها ونشرها على عناقيد Kubernetes بسهولة أكبر؛ وهو الآن مشروع رسمي من مشاريع منصة Kubernetes الرسمية، ويمثّل جزءًا من مؤسسة الحوسبة السحابية الأصيلة (Cloud Native Computing Foundation)، وهي مؤسسة غير ربحية تدعم المشاريع مفتوحة المصدر التي تشكّل جزءًا من النظام البيئي لمنصة Kubernetes، والمشاريع التي تدور حولها. سنقدم في هذ المقال نظرة عامة على Helm والتجريدات المختلفة التي يستخدمها لتبسيط نشر التطبيقات في Kubernetes. إذا كنت جديدًا على Kubernetes، فقد يكون من المفيد قراءة مقدمة عن Kubernetes أولاً للتعرف على المفاهيم الأساسية. نظرة عامة على Helm تحوي غالبية لغات البرمجة وأنظمة التشغيل على مدير حزم خاص بها للمساعدة في تثبيت البرامج وصيانتها. يقدّم Helm مجموعة الميزات الأساسية نفسها التي يقدّمها الكثير من مديري الحزم المشهورين مثل apt في توزيعة دبيان و pip في بيئة تطوير بايثون. يستطيع Helm: تثبيت البرامج. تثبيت اعتماديّات البرامج تلقائيا. ترقية البرامج. إعداد عمليات نشر البرامج. إحضار حزم البرامج من المستودعات. يوفر Helm هذه الوظائف من خلال المكونات التالية: أداة سطر الأوامر، helm، التي توفر واجهة مستخدم لجميع وظائف Helm. مكوّن مرافق للخادم، tiller، يعمل على عنقود Kubernetes الخاصة بك، ويستمع إلى الأوامر من helm، ويتعامل مع إعدادات إصدارات البرامج وعمليّات نشرها على عنقود Kubernetes. صيغة تحزيم خاصّة بمدير الحزم Helm، وتسمى charts. مستودع رسمي لحزم Charts، مع حزم جاهزة لمشاريع برمجية مفتوحة المصدر شائعة. سنحقق في تنسيق الحزم بمزيد من التفاصيل بعد ذلك. سنفصّل في ما يلي حول صيغة التحزيم Charts. صيغة التحزيم Charts يُطلَق على حزم Helm اسم Charts، وتتكون من عدد قليل من ملفات الإعداد المكتوبة بلغة YAML وبعض القوالب التي تُصيَّر (Rendered) ضمن ملف التبيان (Manifest file) في Kubernetes. في ما يلي البنية القاعدية لمجلّد حزمة بصيغة Chart: package-name/ charts/ templates/ Chart.yaml LICENSE README.md requirements.yaml values.yaml هذه المجلدات والملفات لها الوظائف التالية: /charts: يمكن وضع اعتماديّات الحزم المُدارة يدويًا في هذا المجلد، على الرغم من أنه من الأفضل عادةً استخدام requirements.yaml لربط الاعتماديّات بالحزمة ديناميكيّا. /templates: يحتوي هذا المجلد على ملفات القوالب التي تُدمَج مع قيم الإعداد (من ملف values.yaml وسطر الأوامر) وتُصيّر ضمن ملفّات تبيان Kubernetes. تستخدم القوالب تنسيق القوالب في لغة برمجة Go. Chart.yaml: ملف YAML يحتوي على بيانات وصفية حول الحزم، مثل اسم الحزمة وإصدارها، ومعلومات المشرف، وموقع الويب ذي الصلة، والكلمات الرئيسية للبحث. LICENSE: ترخيص نص عادي للحزمة. README.md: ملف تمهيدي يحتوي على معلومات لمستخدمي الحزمة. requirements.yaml: ملف YAML يسرد اعتماديّات الحزمة. values.yaml: ملف YAML بقيم الإعدادات الافتراضية للحزمة. يمكن للأمر helm تثبيت حزمة من مُجلد محلي، أو من مجلّد بالبنية أعلاه مضغوط ضمن ملف tar.gz.. يمكن أيضًا تنزيل هذه الحزم وتثبيتها تلقائيًا من مستودعات حزم Helm. سنلقي في ما يلي نظرة على مستودعات حزم Helm. مستودعات الحزم يتمثّل مستودع حزم Helm في موقع ويب بسيط يقدّم ملفّ index.yaml وحزمة Helm مضغوطة في ملفtar.gz. . يحتوي الأمر helm على خيارات متاحة للمساعدة في التحزيم بصيغة Helm وإنشاء ملف index.yaml المطلوب. يمكن تقديم هذه الملفات بواسطة أي خادم ويب أو خدمة تخزين أو مضيف مواقع ثابتة مثل صفحات GitHub. يأتي Helm مهيأ مسبقًا بمستودع حزم مبدئي، يوصف بأنه مستقر (stable). يشير هذا المستودع إلى مجموعة تخزين Google على الرابط https://kubernetes-charts.storage.googleapis.com. يمكن العثور على مصدر المستودع المستقر على مستودع Git التالي. يمكن إضافة مستودعات بديلة باستخدام الأمر helm repo add. في ما يلي مستودعات بديلة شائعة: مستودع incubator الرسمي الذي يحتوي على حزم غير جاهزة بعد لتكون ضمن المستودع المستقر. يمكن العثور على تعليمات استخدام Incubator في صفحة Github التالية. مستودع Bitnami الذي يوفر حزمًا غير متوفّرة في المستودع الرسمي المستقر. سواء كنت تثبّت حزمة طوّرتها محليًا، أو حصلت عليها من مستودع، فستحتاج إلى تهيئتها لإعداداتك الخاصة، وهو ما سننظر فيه الفقرة التالية إعداد حزم Helm عادةً ما تأتي حزم Helm بقيم الإعدادات الافتراضية في ملف values.yaml. قد تكون بعض التطبيقات قابلة للنشر كاملةً باستخدام القيم الافتراضية، ولكن ستحتاج عادةً إلى إعادة تعيين قيم لتلبية احتياجاتك. يحدّد مطوّر الحزمة القيم التي تمكن إعادة تعيينها. تُستخدم بعض القيم لإعداد وظائف Kubernetes، في حين تُمرَّر أخرى عبر الحاويّة لإعداد التطبيق نفسه. فيما يلي مقتطف من مثال لملف قيم: service: type: ClusterIP port: 3306 هذه خيارات لإعداد مورد خدمة Kubernetes. يمكنك استخدام helm inspect values chart-name لتفريغ كل قيم الإعداد المتاحة للحزمة. يمكن تجاوز هذه القيم عن طريق كتابة ملف YAML خاص بك واستخدامه عند تشغيل الأمر helm install، أو من خلال تعيين الخيارات فرديًّا في سطر الأوامر مع الخيار set--. ما عليك سوى تحديد تلك القيم التي تريد تغييرها من الإعدادات الافتراضية. تُسمى حزم Helm المنشورة بإعداد معين بالإصدار (release). الإصدارات أثناء تثبيت الحزمة، يجمع Helm بين القوالب والإعدادات المُخصّصة من طرف المستخدم والقيم الافتراضية في value.yaml. تُحوّل تلك الإعدادات إلى ملف Manifest في Kubernetes تُنشَر بعد ذلك عبر واجهة تطبيقات Kubernetes. ينتج عن ذلك إنشاء إصدار، وهو إعداد ونشر محدّديْن لحزمة معينة. مفهوم الإصدارات هذا مهم، لأنك قد ترغب في نشر التطبيق نفسه أكثر من مرة على عنقود. على سبيل المثال، قد تحتاج إلى عدة خوادم MySQL بإعدادات مختلفة. قد ترغب أيضًا في ترقية نظائر مختلفة من الحزمة فرديّا. ربما يكون تطبيق جاهزًا لخادم MySQL مُحدَّث ولكن تطبيقًا آخر ليس كذلك. باستخدام Helm، يمكنك ترقية كل إصدار على حدة. يمكنك ترقية إصدار لأن الحزمة الخاصة به مُحدّثة، أو لأنك ترغب في تحديث إعدادات الإصدار. في كلتا الحالتين، ستُنشئ كل ترقية مراجعة جديدة للإصدار، وسيسمح لك Helm بالعودة بسهولة إلى المراجعات السابقة في حالة وجود مشكلة. إنشاء الحزم إذا لم تتمكن من العثور على حزمة موجود للبرنامج الذي تنشره، فقد ترغب في إنشاء الحزمة الخاص بك. يمكن لمدير الحزم Helm إنشاء بنية جاهزة لمجلد الحزمة باستخدام الأمر helm create chart-name. سيؤدي هذا إلى إنشاء مجلد يحتوي على الملفات والمجلدات التي ناقشناها في قسم ضيغة التحزيم أعلاه. من هناك، ستحتاج إلى ملء البيانات الوصفية للحزمة الخاصة بك في Chart.yaml ووضع ملفات تبيان Kubernetes في مجلد templates. ستحتاج بعد ذلك إلى استخلاص متغيرات الإعداد المناسبة من ملفات التبيان إلى ملف القيم values.yaml، ثم تضمينها مرة أخرى في ملفات التبيان باستخدام نظام القوالب. يحتوي الأمر helm على العديد من الخيارات المتاحة لمساعدتك في إنشاء حزم Helm واختبارها وتقديمها. لمزيد من المعلومات، يرجى قراءة وثائق Helm الرسمية حول تطوير الحزم. خاتمة في هذا المقال استعرضنا Helm، مدير حزم Kubernetes. ألقينا نظرة عامة على هندسة Helm، ومكوناته helm و tiller، وفصّلنا صيغة حزم Helm، وألقينا نظرة على مستودعات الحزم. تطرّقنا كذلك إلى كيفية إعداد حزم Helm، وكيف تُدمَج الإعدادات والحزم وتُنشَر كإصدارات على عناقيد Kubernetes. أخيرًا، تطرقنا إلى أساسيات إنشاء حزمة في حالة عدم توفر حزمة مناسب بالفعل. لمزيد من المعلومات حول Helm ، ألق نظرة على وثائق Helm الرسمية. تحقق من مستودع helm/charts على Github للعثور على الحزم الرسمية. ترجمة - وبتصرّف - للمقال An Introduction to Helm, the Package Manager for Kubernetes لصاحبه Brian Boucheron.
  2. عند تشغيل خدمات وتطبيقات متعددة على عنقود Kubernetes، يمكن أن يساعدك مكدس سجلات مركزي على مستوى العنقود على فرز الحجم الكبير لبيانات السجلات التي تنتجها كائنات Pod، وتحليل تلك السجلّات بسرعة. أحد حلول التسجيل المركزية الشائعة هو مكدس EFK الذي يضم التطبيقات الثلاث Elasticsearch، وFluentd وKibana. Elasticsearch هو محرك بحث فوري ومُوزّع وقابل للتحجيم يسمح بالبحث في النصوص والبيانات المنظَّمة وكذلك التحليلات. يُستخدم Elasticsearch عادةً لفهرسة كميات كبيرة من بيانات السجلّات والبحث فيها، ولكن يمكن استخدامه أيضًا للبحث في العديد من أنواع المستندات المختلفة. يُنشَر Elasticsearch عادة جنبًا إلى جنب مع Kibana، وهي واجهة أمامية ولوحة تحكم فعّالة لعرض البيانات. تتيح Kibana استكشاف بيانات سجل Elasticsearch الخاص بك من خلال واجهة ويب، وإنشاء لوحات تحكم واستعلامات للإجابة بسرعة على الأسئلة والحصول على نظرة مفصَّلة عن تطبيقات Kubernetes. سنستخدم في هذا الدرس Fluentd لجمع بيانات السجل وتحويلها وشحنها إلى سند Elasticsearch الخلفي. Fluentd هو مُجمِّع بيانات شائع الاستخدام، ومفتوح المصدر، سنعدّه على عُقد Kubernetes لتتبّع ملفات سجلّات الحاوية، وتصفية وتحويل بيانات السجل، وتسليمها إلى عنقود Elasticsearch، حيث تُفهرَس وتُخزَّن. سنبدأ بإعداد وتشغيل عنقود Elasticearch قابل للتحجيم، ثم ننشئ خدمة Kibana وعمليّة النشر الخاصّة بها على Kubernetes. في الختام، سنعدّ Fluentd بصيغة متحكّم DaemonSet حتى يُشغَّل على كل عقدة عاملة على Kubernetes. المتطلبات الأساسية قبل أن تبدأ بهذا الدليل، تأكد من توفر ما يلي لك: عنقود Kubernetes، الإصدار 1.10 فما فوق، مع تفعيل التحكّم في الوصول اعتمادًا على الدور (Role-Based Accedd Control, RBAC). تأكّد أن العنقود لديه موارد كافية لتشغيل مكدس EFK، وإن لم يكن، زد حجم العنقود بإضافة عقَد عاملة جديدة. سننشر Elasticsearch على ثلاث كائنات Pod (يمكنك تقليل العدد إلى 1 إنْ لزم الأمر)، بالإضافة إلى كائن Pod واحد لبيئة Kibana. ستشغّل كل عقدة عاملة كائن Pod لتطبيق Fluentd. يتكون العنقود في هذا الدليل من ثلاث عقد عاملة ومستوى تحكم مُدبَّر (Managed control plane) واحد. أداة سطر الأوامر kubectl مُثبّتة على جهازك المحلي، مُعدَّة للاتصال بالعنقود. يمكنك قراءة المزيد حول تثبيت kubectl في التوثيق الرسمي. بمجرد الانتهاء من إعداد هذه المكونات، تكون جاهزًا للبدء بهذا الدليل. الخطوة الأولى: إنشاء فضاء أسماء (Namespace) قبل إطلاق عنقود Elasticsearch، سنُنشئ أولاً فضاء أسماء نثبّت فيه جميع أدوات التسجيل الخاصة بنا. يتيح لك Kubernetes فصل الكائنات التي تعمل في العنقود باستخدام تجريد ينشئ "عنقودَا افتراضيًّا" يُسمّى فضاء الأسماء. سننشئ في هذا الدليل فضاء أسماء نطلق عليه kube-logging نثبّت فيه مكونات مكدس EFK. علاوةً على ذلك، سيسمح لنا فضاء الأسماء هذا بتنظيف مكدس EFK وإزالته بسرعة دون فقدان وظائف أخرى في عنقود Kubernetes. للبدء، تَحقَّق أولاً من فضاءات الأسماء الموجودة في العنقود باستخدام kubectl: $ kubectl get namespaces يجب أن ترى فضاءات الأسماء الثلاثة المبدئية التالية، والتي تأتي مُثبَّتة مسبقًا مع عنقود Kubernetes: NAME STATUS AGE default Active 5m kube-system Active 5m kube-public Active 5m يحتوي فضاء الأسماء default على كائنات أنشئت بدون تحديد فضاء أسماء لها. يحتوي فضاء الأسماء kube-system على كائنات أنشأها واستخدمها نظام Kubernetes، مثلkube-dns، kube-proxy، وkubernetes-dashboard. من الجيد الحفاظ على نظافة فضاءات الأسماء هذه وعدم تلويثها بأحمال عمل التطبيقات والأدوات الأخرى. فضاء الأسماء kube-public هو الآخرأنشئ تلقائيًا ويمكن استخدامه لتخزين الكائنات التي تريد لها أن تكون قابلة للقراءة ويمكن الوصول إليها عبر العنقود بأكمله، حتى للمستخدمين الذين لم تستوثق منهم (Unauthenticated users). لإنشاء فضاء الأسماء kube-logging، أولًا افتح ملفًّا يُسمى kube-logging.yaml باستخدام المحرر المفضل لديك، مثل nano: $ nano kube-logging.yaml داخل المحرر، الصق كائن فضاء الأسماء التالي المكتوب بصيغة YAML: kind: Namespace apiVersion: v1 metadata: name: kube-logging ثم احفظ الملف وأغلقه. هنا، نحدد نوع كائن Kubernetes بأنه فضاء أسماء. لمعرفة المزيد حول كائنات فضاء الأسماء، راجع إرشادات فاضاءات الأسماء في توثيق Kubernetes. نحدد أيضًا إصدار واجهة تطبيقات Kubernetes المستخدم لإنشاء الكائن (v1)، ونعيّن قيمة الحقل name (الاسم) لتكون kube-logging. أنشئ فضاء أسماء باستخدام الأمر kubectl create مع خيار اسم الملف f-، مباشرة بعد تحرير ملف كائن فضاء الأسماء kube-logging.yaml، وذلك على النحو التالي: $kubectl create -f kube-logging.yaml تظهر المُخرجات كالتالي: namespace/kube-logging created يمكنك بعد ذلك التأكد من إنشاء فضاء الأسماء بنجاح: $ kubectl get namespaces عند هذه النقطة، من المُفترض أن ترى فضاء الأسماء الجديد kube-logging : NAME STATUS AGE default Active 23m kube-logging Active 1m kube-public Active 23m kube-system Active 23m يمكننا الآن نشر عنقود Elasticsearch في فضاء الأسماء المعزول الخاص بالتسجيل. الخطوة 2 - إنشاء Elasticsearch StatefulSet الآن بعد أن أنشأنا فضاء أسماء لاستيعاب مكدس السجلّات، يمكننا البدء في طرح مكوناته المختلفة. سنبدأ أولاً بنشر عنقود Elasticsearch من ثلاث عُقد. في هذا الدليل نستخدم ثلاث كائنات Pod ليعمل عليها عنقود Elasticsearch، والهدف هو تجنب مشكلة "الدماغ المنصدع" (Split-brain)، التي قد تحدث في العنقود متعدّد العقد. تحدث مشكلة "الدماغ المنصدع" عندما يتعذر على عُقدة (أو عدة عقد) التواصل مع العقد الأخرى، فتُختار عدة عقد لتلعب دور القبطان (Master). مع ثلاث عقد، إذا انفصلت عقدة عن العنقود يمكن للعقدتين الأخريين انتخاب قبطان جديد ويمكن للعنقود أن يستمر في العمل بينما تحاول العقدة الضائعة الالتحاق. راجع المقالين A new era for cluster coordination in Elasticsearch وVoting configurations لمعرفة المزيد عن المشكلة المذكورة. إنشاء خدمة مجهولة العنوان (Headless Service) للبدء، سننشئ خدمة Kubernetes مجهولة العنوان تسمى elasticsearch تعرّف اسم نطاق لكائنات Pod الثلاثة الموجودة في عنقود Elasticsearch. لا توازن الخدمة مجهولة العنوان الحمل وليس لديها عنوان IP ثابت. راجع توثيق Kubernetes للمزيد عن هذه الخدمات. افتح الملف elasticsearch_svc.yaml بمحرّر النصوص المفضل لديك: $ nano elasticsearch_svc.yaml الصق فيه خدمة Kubernetes التالية (بصيغة YAML): elasticsearch_svc.yaml kind: Service apiVersion: v1 metadata: name: elasticsearch namespace: kube-logging labels: app: elasticsearch spec: selector: app: elasticsearch clusterIP: None ports: - port: 9200 name: rest - port: 9300 name: inter-node ثم احفظ الملف وأغلقه. نعيّن خدمة (service) تسمى elasticearch في فضاء الأسماء kube-logging ، ونعطيها اللصيقة app: elasticsearch. ثم نعيّن القيمة app: elasticsearch للحقل spec.selector. لكي تختار لخدمة كائن Pod عن طريق اللصيقة app: elasticsearch. عندما نربط متحكّم StatefulSet في عنقود Elasticsearch الخاص بنا مع هذه الخدمة، سترجع الخدمة سجل DNS من النوع A يشير إلى كائنات Pod ذات اللصيقة app: elasticsearch في عنقود Elasticsearch. ثم نعيّن الحقل clusterIP لتكون قيمته None، ممّا يجعل الخدمة مجهولة العنوان. أخيرًا، نُحدد المنافذ 9200 و9300 التي تُستخدَم على التوالي للتفاعل مع واجهة التطبيقات من النوع، وللتواصل الداخلي بين العقد. أنشئ الخدمة باستخدام kubectl: $ kubectl create -f elasticsearch_svc.yaml يجدر أن تظهر مُخرجات كالتالي: service/elasticsearch created أخيرًا، تحقق جيدًا من إنشاء الخدمة بنجاح باستخدام kubectl get: kubectl get services --namespace=kube-logging يجب أن ترى ما يلي: NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE elasticsearch ClusterIP None <none> 9200/TCP,9300/TCP 26s الآن بعد أن أعددنا خدمتنا المستقرة مجهولة العنوان والنطاق elasticsearch.kube-logging.svc.cluster.local. لكائنات Pod الخاصة بنا، يمكننا المضي قدمًا وإنشاء متحكّم المجموعة ذات الحالة (StatefulSet). إنشاء مجموعة ذات حالة (StatefulSet) من كائنات Pod يسمح لك كائن StatefulSet في Kubernetes بتعيين هوية ثابتة لكائنات Pod ومنحها تخزينًا مستقرًّا ودائما. يحتاج Elasticsearch لتخزين مستقر لضمان ديمومة البيانات عند إعادة جدولة كائنات Pod وإعادة تشغيلها. راجع التوثيق الرسمي للمزيد حول كائنات StatefulSet. افتح الملف المُسمّى elasticsearch_statefulset.yaml في المُحرر المفضل لديك: $ nano elasticsearch_statefulset.yaml سنمرّ خطوة خطوة على تعريف كائن StatefulSet من خلال لصق أجزاء من التعريف في الملف المذكور. نبدأ بنسخ ثم لصق الجزء التالي: apiVersion: apps/v1 kind: StatefulSet metadata: name: es-cluster namespace: kube-logging spec: serviceName: elasticsearch replicas: 3 selector: matchLabels: app: elasticsearch template: metadata: labels: app: elasticsearch نعرّف في الجزء أعلاه كائن StatefulSet باسم es-cluster في فضاء الأسماء kube-logging. ثم نربطه بخدمة elasticsearch التي أنشئت مسبقًا باستخدام حقل serviceName، ممّا يضمن إمكانية الوصول إلى كل كائن Pod في المجموعة باستخدام النطاق التالي: es-cluster-[0,1,2].elasticsearch.kube-logging.svc.cluster.local، حيث [0,1,2] يُشير إلى العدد الترتيبي لكائن Pod. نحدد ثلاث نسخ متطابقة (كائنات Pod) بتعيين القيمة 3 للحقل replicas، ونعيّن القيمة app: elasticseach للمُحدِّد matchLabels، ونكرّر الأمر مع الحقل spec.template.metadata.. يجب أن تتطابق قيمتا الحقلين .spec.selector.matchLabels و .spec.template.metadata.labels. يمكننا الآن الانتقال إلى مواصفات الكائن. ألصق الجزء التعريفي التالي، المكتوب بصيغة YAML، أسفل الجزء السابق: . . . spec: containers: - name: elasticsearch image: docker.elastic.co/elasticsearch/elasticsearch:7.2.0 resources: limits: cpu: 1000m requests: cpu: 100m ports: - containerPort: 9200 name: rest protocol: TCP - containerPort: 9300 name: inter-node protocol: TCP volumeMounts: - name: data mountPath: /usr/share/elasticsearch/data env: - name: cluster.name value: k8s-logs - name: node.name valueFrom: fieldRef: fieldPath: metadata.name - name: discovery.seed_hosts value: "es-cluster-0.elasticsearch,es-cluster-1.elasticsearch,es-cluster-2.elasticsearch" - name: cluster.initial_master_nodes value: "es-cluster-0,es-cluster-1,es-cluster-2" - name: ES_JAVA_OPTS value: "-Xms512m -Xmx512m" هنا نحدد كائنات Pod في المجموعة ذات الحالة (StatefulSet). نسمّي الحاويات elasticsearch ونختار صورة Docker من الرابط docker.elastic.co/elasticsearch/elasticsearch:7.2.0. في هذه المرحلة، يمكنك تعديل وسم الصورة هذا ليتوافق مع صورة Elasticsearch الداخلية الخاصة بك، أو مع إصدار مختلف. لاحظ أنه لأغراض هذا الدليل، اقتصر الااختبارعلى الإصدار 7.2.0 من Elasticsearch. ثم نستخدم الحقل resources لتحديد أن الحاوية تحتاج على الأقل لضمان 10% من وحدة المعالجة المركزية الافتراضية ( 0.1vCPU)، ويمكن أن يزيد الاستخدام ليصل إلى وحدة معالجة كاملة (1vCPU)، أي أننا نعيّن الحد الأقصى لاستهلاك المورد في حالات مثل استقبال كائن Pod لكمية أولية كبيرة من البيانات أو في أوقات الذروة). يجب تعديل هذه القيم اعتمادًا على الحمل المتوقع والموارد المتاحة. راجع توثيق Kubernetes للمزيد عن الطلبات على الموارد وحدوها. ثم نفتح المنفذين 9200 و 9300، مع تسميتهما، بغرض الاتصال بواجهة تطبيقات REST والاتصال الداخلي، على التوالي. نحدد نقطة لتركيب تجزئات التخزين (الحقل volumeMount) ونسمّيها data. وظيفة نقطة التركيب تلك هي تحميل التجزئة المستقرّة (كائن من النوع PersistentVolume) المُسماة data الموجودة ضمن الحاوية على المسار usr/share/elasticsearch/data/. سنحدد مطالب الحجم (VolumeClaims) لهذه المجموعة في جزء لاحق من التعريف. أخيرًا، نعيّن بعض متغيرات البيئة في الحاوية: cluster.name: اسم عنقود Elasticsearch، والذي في هذا الدليل هو k8s-logs. node.name: اسم العقدة، الذي عيّناه للحقل metadata.name. باستخدام valueFrom. ستُترجم هذه القيمة إلى [es-cluster-[0,1,2، اعتمادًا على العدد الترتيبي المعيّن للعقدة. discovery.seed_hosts: يعين هذا الحقل قائمة بالعقد المؤهلة لتؤدّي دور القبطان في العنقود الذي سيؤسس لعملية استكشاف العقد. في هذا الدليل، وبفضل الخدمة مجهولة العنوان التي أعددناها سابقًا، تأخذ كائنات Pod نطاقات بالصيغة es-cluster-[0,1,2].elasticsearch.kube-logging.svc.cluster.local، لذلك عيّنا هذا المتغير وفقًا لذلك. يمكننا تقصير النطاق إلى es-cluster-[0,1,2].elasticsearch باستخدام ترجمة أسماء النطاقات الداخلية في Kubernetes. راجع توثيق Elasticsearch للمزيد عن الاستكشاف في Elasticsearch. cluster.initial_master_nodes: يحدد هذا الحقل أيضًا قائمة عقد مؤهلة لتؤدّي دور القبطان في العنقود ستشارك في عملية انتخاب القبطان. لاحظ أنه في هذا الحقل، يجب تحديد العقد من خلال node.name وليس أسماء المضيفين. ES_JAVA_OPTS: ضبطنا المتغيّر على القيم Xms512m -Xmx512m- لإخبار آلة جافا الافتراضية (JVM) برغبتنا في استخدام 512 ميجابايت لتكون الحد الأدنى والحد الأقصى لحجم كومة الذاكرة المؤقتة. يجب ضبط هذه المعاملات بناءً على توفر الموارد للعنقود واحتياجاته. راجع المقال التالي للمزيد من التفاصيل. يظهر الجزء الموالي من التعريف على النحو التالي: . . . initContainers: - name: fix-permissions image: busybox command: ["sh", "-c", "chown -R 1000:1000 /usr/share/elasticsearch/data"] securityContext: privileged: true volumeMounts: - name: data mountPath: /usr/share/elasticsearch/data - name: increase-vm-max-map image: busybox command: ["sysctl", "-w", "vm.max_map_count=262144"] securityContext: privileged: true - name: increase-fd-ulimit image: busybox command: ["sh", "-c", "ulimit -n 65536"] securityContext: privileged: true في هذه الجزء، نحدد العديد من الحاويات الأولية التي تُشغَّب قبل حاوية تطبيق elasticearch الرئيسية. تُشغَّل هذه الحاويات الأولية (Init Containers) حتى إكمال عملها بالترتيب المحدد. راجع التوثيق الرسمي لمعرفة المزيد عن الحاويات الأولية. تنفّذ الحاوية الأولى، المُسمّاة fix-permissions، أمر chown لتغيير الحساب والمجموعة المالكيْن لمجلّد بيانات دليل Elasticsearch ليصبحا 1000: 1000، وهما معرّفا (UID) مستخدم ومجموعة Elasticsearch. يركّب Kubernetes مجلّد البيانات بالحساب الجذر (root)، مما يجعل وصول Elasticsearch إليه غير ممكن. راجع توثيق Elasticsearch لمزيد من المعلومات حول هذه الخطوة. تنفّذ الحاوية الثانية، المسمّاة increase-fd-ulimit، أمرًا لزيادة حدود استدعاءات mmap التي يمكن لنظام التشغيل إجراؤها، والتي قد يكون عددها افتراضيًا منخفضًا جدًا، مما ينتج عنه أخطاء في الذاكرة. راجع توثيق Elasticsearch لمزيد من المعلومات حول هذه الخطوة. الحاوية الأوليّة التالية هي increase-fd-ulimit والتي تنفّذ الأمر ulimit لزيادة الحد الأقصى لعدد واصفات الملفات المفتوحة (File descriptor). راجع توثيق Elasticsearch لمزيد من المعلومات حول هذه الخطوة. الآن بعد أن حددنا حاوية التطبيق الرئيسية والحاويات الأوليّة التي تُشغَّل قبلها لضبط نظام تشغيل الحاوية، يمكننا إضافة الجزء الأخير إلى ملف تعريف كائن StatefulSet، وهو الجزء المتعلّق بقوالب مطالب الحجم (volumeClaimTemplates). ألصق الجزء التالي المتعلّق بتعريف volumeClaimTemplate: . . . volumeClaimTemplates: - metadata: name: data labels: app: elasticsearch spec: accessModes: [ "ReadWriteOnce" ] storageClassName: do-block-storage resources: requests: storage: 100Gi في هذا الجزء، نحدد المجلد VolumeClaimTemplates الخاص بكائن StatefulSet. سيستخدم Kubernetes هذا المجلّد لإنشاء تجزئات دائمة (PersistentVolumes) لكائنات Pod. في التعريف أعلاه، نسميها data (وهو الاسم الذي نشير إليه في وحدة التخزين التي تم تعريفها سابقًا)، ونعطيها نفس اللصيقة (app: elasticsearch) اللتي أعطيناها لكائن StatefulSet سابقا. ثم نحدد وضع الوصول إلى التجزئات ليكون ReadWriteOnce، مما يعني أنه لا تمكن القراءة والكتابة منها إلّا بواسطة عُقدة واحدة. نحدد الصنف do-block-storage ليكون صنف التخزين إذ أننا نستخدم عنقود Kubernetes من DigitalOcean لأغراض الشرح. يجب تغيير هذه القيمة اعتمادًا على أين تشغّل عنقود Kubernetes الخاص بك. راجع توثيق Kubernetes للمزيد عن التخزين الدائم. أخيرًا، نحدد حجم كل مجلد دائم (PersistentVolume) ليكون 100 غيغابايت. يجب تعديل هذه القيمة حسب احتياجات بيئة الإنتاج الخاصة بك. تبدو المواصفات الكاملة لكائن StatefulSet كالتالي: apiVersion: apps/v1 kind: StatefulSet metadata: name: es-cluster namespace: kube-logging spec: serviceName: elasticsearch replicas: 3 selector: matchLabels: app: elasticsearch template: metadata: labels: app: elasticsearch spec: containers: - name: elasticsearch image: docker.elastic.co/elasticsearch/elasticsearch:7.2.0 resources: limits: cpu: 1000m requests: cpu: 100m ports: - containerPort: 9200 name: rest protocol: TCP - containerPort: 9300 name: inter-node protocol: TCP volumeMounts: - name: data mountPath: /usr/share/elasticsearch/data env: - name: cluster.name value: k8s-logs - name: node.name valueFrom: fieldRef: fieldPath: metadata.name - name: discovery.seed_hosts value: "es-cluster-0.elasticsearch,es-cluster-1.elasticsearch,es-cluster-2.elasticsearch" - name: cluster.initial_master_nodes value: "es-cluster-0,es-cluster-1,es-cluster-2" - name: ES_JAVA_OPTS value: "-Xms512m -Xmx512m" initContainers: - name: fix-permissions image: busybox command: ["sh", "-c", "chown -R 1000:1000 /usr/share/elasticsearch/data"] securityContext: privileged: true volumeMounts: - name: data mountPath: /usr/share/elasticsearch/data - name: increase-vm-max-map image: busybox command: ["sysctl", "-w", "vm.max_map_count=262144"] securityContext: privileged: true - name: increase-fd-ulimit image: busybox command: ["sh", "-c", "ulimit -n 65536"] securityContext: privileged: true volumeClaimTemplates: - metadata: name: data labels: app: elasticsearch spec: accessModes: [ "ReadWriteOnce" ] storageClassName: do-block-storage resources: requests: storage: 100Gi احفظ الملف ثم أغلقه بعد التأكد من محتواه. الآن، انشر ملف StatefulSet باستخدام kubectl: $ kubectl create -f elasticsearch_statefulset.yaml يجب أن تشاهد المُخرجات التالية: statefulset.apps/es-cluster created يمكنك مراقبة حالة StatefulSet أثناء إطلاقه باستخدام kubectl rollout status: $ kubectl rollout status sts/es-cluster --namespace=kube-logging يجب أن ترى المخرجات التالية أثناء إطلاق العنقود: Waiting for 3 pods to be ready... Waiting for 2 pods to be ready... Waiting for 1 pods to be ready... partitioned roll out complete: 3 new pods have been updated... بمجرد نشر كل كائنات Pod، يمكنك التحقق من أن عنقود Elasticsearch الخاص بك يعمل من خلال إرسال طلب إلى واجهة تطبيقات REST. لذا، أعد توجيه المنفذ المحلي 9200 إلى المنفذ 9200 على إحدى عُقد Elasticsearch وهي (es-cluster-0)، باستخدام kubectl port-forward: $ kubectl port-forward es-cluster-0 9200:9200 --namespace=kube-logging ثم، في نافذة طرفية منفصلة، نفذ طلب curl باتجاه واجهة REST: $ curl http://localhost:9200/_cluster/state?pretty يجب أن تظهر المخرجات التالية: { "cluster_name" : "k8s-logs", "compressed_size_in_bytes" : 348, "cluster_uuid" : "QD06dK7CQgids-GQZooNVw", "version" : 3, "state_uuid" : "mjNIWXAzQVuxNNOQ7xR-qg", "master_node" : "IdM5B7cUQWqFgIHXBp0JDg", "blocks" : { }, "nodes" : { "u7DoTpMmSCixOoictzHItA" : { "name" : "es-cluster-1", "ephemeral_id" : "ZlBflnXKRMC4RvEACHIVdg", "transport_address" : "10.244.8.2:9300", "attributes" : { } }, "IdM5B7cUQWqFgIHXBp0JDg" : { "name" : "es-cluster-0", "ephemeral_id" : "JTk1FDdFQuWbSFAtBxdxAQ", "transport_address" : "10.244.44.3:9300", "attributes" : { } }, "R8E7xcSUSbGbgrhAdyAKmQ" : { "name" : "es-cluster-2", "ephemeral_id" : "9wv6ke71Qqy9vk2LgJTqaA", "transport_address" : "10.244.40.4:9300", "attributes" : { } } }, ... تشير المخرجات إلى أنّ العنقود k8s-logs المكوَّن من ثلاث عقد ( es-cluster-1، es-cluster-0 وes-cluster-2.) قد أنشئ بنجاح. العقدة التي تؤدي دور القبطان هي حاليًّا es-cluster-0. الآن بعد أن أصبح عنقود Elasticsearch جاهزًا يمكنك الانتقال إلى إعداد واجهة Kibana الأمامية. الخطوة 3: إنشاء خدمة ونشر كيبانا لإطلاق Kibana على Kubernetes، سننشئ خدمة تسمى kibana، وننشئ أيضًا عملية نشر (Deployment) تتكوّن من كائن Pod واحد. يمكنك تحديد عدد النسخ التي تحتاجها بناءً على احتياجات بيئة الإنتاج الخاصة بك، وتحديد النوع LoadBalancer لتوزيع عبء بين كائنات Pod. هذه المرة ، سننشئ الخدمة والنشر في الملف نفسه. افتح ملفًا يسمى kibana.yaml في محررك المفضل: $ nano kibana.yaml ألصق المواصفات التالية للخدمة: apiVersion: v1 kind: Service metadata: name: kibana namespace: kube-logging labels: app: kibana spec: ports: - port: 5601 selector: app: kibana --- apiVersion: apps/v1 kind: Deployment metadata: name: kibana namespace: kube-logging labels: app: kibana spec: replicas: 1 selector: matchLabels: app: kibana template: metadata: labels: app: kibana spec: containers: - name: kibana image: docker.elastic.co/kibana/kibana:7.2.0 resources: limits: cpu: 1000m requests: cpu: 100m env: - name: ELASTICSEARCH_URL value: http://elasticsearch:9200 ports: - containerPort: 5601 ثم احفظ الملف وأغلقه. في هذه المواصفات، حددنا خدمة تسمى kibana في فضاء الأسماء kube-logging، وأعطيناها اللصيقة app: kibana. حددنا أيضًا أنه يجب الوصول إليها على المنفذ 5601 واستخدمنا اللصيقة app: kibana لتحديد كائنات Pod المُستهدَفة بالخدمة. في مواصفات النشر Deployment، نحدد عملية نشر تُسمى kibana ونحدد أننا نريد كائن Pod واحدا. نستخدم صورة Kibana من الرابط docker.elastic.co/kibana/kibana:7.2.0. عند هذه النقطة يمكنك وضع صورة كيبانا الخاصة أو العامة التي تودّ استخدامها. نحدد أن الحاوية تحتاج على الأقل لضمان 10% من وحدة المعالجة المركزية الافتراضية ( 0.1vCPU)، ويمكن أن يزيد الاستخدام ليصل إلى وحدة معالجة كاملة (1vCPU)، أي أننا نعيّن الحد الأقصى لاستهلاك المورد. يمكنك تغيير هذه المعاملات اعتمادًا على الحمل المتوقع والموارد المتاحة. بعد ذلك ، نستخدم متغير البيئة ELASTICSEARCH_URL لتعيين نقطة النهاية والمنفذ لعنقود Elasticsearch. باستخدام نطاقات Kubernetes نجد أن نقطة النهاية تتوافق مع اسم الخدمة الخاص بها elasticsearch. سيُترجم اسم النطاق إلى قائمة عناوين IP الخاصة بكائنات Pod الثلاث. راجع توثيق Kubernetes للمزيد عن إدارة أسماء النطاقات في Kubernetes. أخيرًا، عيّنا 5601 ليكون منفذ حاويات Kibana، حيث ستعيد خدمة Kibana توجيه الطلبات إليه. يمكنك - بعد التأكّد من إعدادات Kibana - إطلاق الخدمة وتشغيل عملية النشر باستخدام kubectl: $ kubectl create -f kibana.yaml يجب أن تشاهد المخرجات التالية: service/kibana created deployment.apps/kibana created يمكنك التحقق من نجاح إطلاق الخدمة عن طريق تشغيل الأمر التالي: $ kubectl rollout status deployment/kibana --namespace=kube-logging يجب أن تشاهد المخرجات التالية: deployment "kibana" successfully rolled out للوصول إلى واجهة Kibana، سنعيد توجيه منفذ محلي مرة أخرى إلى عُقدة Kubernetes التي تشغّل Kibana. احصل على تفاصيل كائن Pod الخاص بعقدة Kibana باستخدام kubectl get: $ kubectl get pods --namespace=kube-logging المخرجات: NAME READY STATUS RESTARTS AGE es-cluster-0 1/1 Running 0 55m es-cluster-1 1/1 Running 0 54m es-cluster-2 1/1 Running 0 54m kibana-6c9fb4b5b7-plbg2 1/1 Running 0 4m27s هنا نلاحظ أن كائن Pod الخاص بعقدة Kibana يُسمّى kibana-6c9fb4b5b7-plbg2. أعد توجيه المنفذ المحلي 5601 إلى المنفذ 5601 على ذلك الكائن: $ kubectl port-forward kibana-6c9fb4b5b7-plbg2 5601:5601 --namespace=kube-logging يجب أن تظهر المخرجات التالية: Forwarding from 127.0.0.1:5601 -> 5601 Forwarding from [::1]:5601 -> 5601 الآن، زر رابط URL التالي: http://localhost:5601 إذا رأيت صفحة ترحيب Kibana التالية، فقد نجحت في نشر Kibana في عنقود Kubernetes: يمكنك الآن الانتقال إلى إطلاق المكون النهائي لمكدس EFK وهو مُجمِّع السجلات Fluentd. الخطوة 4: إنشاء Fluentd DaemonSet في هذا الدليل، سنعدّ Fluentd بصيغة DaemonSet، وهو نوع من أعباء العمل في Kubernetes يشغّل نسخة من كائن Pod على كل عقدة في عنقود Kubernetes. باستخدام وحدة تحكم DaemonSet هذه، سنطلق وكيل تسجيل Fluentd عبارة عن كائن Pod على كل عقدة من العنقود. راجع هذا المقال من توثيق Kubernetes الرسمي لمعرفة المزيد حول بنية التسجيل هذه. تُلتَقط سجّلات المُخرجات والأخطاء المعيارية (stdout و stderr على التوالي) للتطبيقات العاملة ضمن حاويّات ويُعاد توجيهها إلى ملفات JSON على العقد. سيتتبّع وكيل Fluentd ملفّات السجلّات تلك، ويفرز الأحداث الموجودة فيها، ويحوّل بيانات السجلّات وفي الأخير يشحنها إلى السند الخلفي لحفظ السجلّات على Elasticsearch الذي نشرناه في الخطوة الثانية من هذا الدليل. بالإضافة إلى سجلات الحاوية، يتتبّع وكيل Fluentd سجلات مكونات نظام Kubernetes مثل سجلات kubelet وkube-proxy وDocker. راجع ملف kubernetes.conf المستخدم لإعداد وكيل التسجيل للاطلاع على قائمة كاملة بالمصادر التي يتتبعها وكيل تسجيل Fluentd. راجع توثيق Kubernetes لمعرفة المزيد حول تسجيل الأحداث على مستوى عقد Kubernetes. ابدأ بفتح الملف fluentd.yaml في محرر النصوص المفضل لديك: $ nano fluentd.yaml مرة أخرى ، سنلصق تعريفات الكائن جزءًا جزءًا، مع شرح سياق التعريفات في كل جزء. نستند في هذا الدليل على مواصفات متحكّم Fluentd DaemonSet التي يوفرها فريق صيانة Fluentd. مورد مفيد آخر يقدمه مشرفو Fluentd هو مقالات Flunetd ضمن توثيق Kuberentes. أولاً، الصق في تعريف ServiceAccount التالي: apiVersion: v1 kind: ServiceAccount metadata: name: fluentd namespace: kube-logging labels: app: fluentd هنا، قمنا بإنشاء حساب خدمة باسم flentd ستستخدمه كائنات Pod التابعة لخدمة Fluentd في الوصول إلى واجهة تطبيقات Kubernetes. ننشئ الحساب في فضاء الأسماء kube-logging ونعطيه مرة أخرى اللصيقة app: fluentd. راجع توثيق Kubernetes للمزيد عن حسابات الخدمة في Kubernetes. بعد ذلك، الصق الجزء التالي الذي يعرّف الكائن ClusterRole : . . . --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: fluentd labels: app: fluentd rules: - apiGroups: - "" resources: - pods - namespaces verbs: - get - list - watch هنا نحدد ClusterRole يسمى fluentd نمنحه صلاحيات get،وlist و watch على كائنات Pod وفضاءات الأسماء. تسمح الكائنات من النوع ClusterRoles بمنح الوصول إلى موارد Kubernetes في نطاق العنقود مثل العقَد. راجع [فصل استخدام تراخيص RBAC]() ضمن توثيق Kubernetes، للمزيد عن التحكم في الوصول المعتمد على الأدوار (RBAC). الآن، الصق الجزء التعريفي التالي المتعلق بكائن ClusterRoleBinding: . . . --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: fluentd roleRef: kind: ClusterRole name: fluentd apiGroup: rbac.authorization.k8s.io subjects: - kind: ServiceAccount name: fluentd namespace: kube-logging نعرّف كائنًا من النوع ClusterRoleBinding ونسمّيهfluentd. وظيفة هذا الكائن هي الربط بين الكائن من النوع ClusterRole وحساب خدمة fluentd، ممّا يمنح الخدمة الصلاحيّات المدرجة في الكائن من النوع ClusterRole الذي أسميناهfluentd . عند هذه النقطة يمكننا البدء بلصق التعريفات ضمن المواصفات الفعليّة لمتحكّم DaemonSet: . . . --- apiVersion: apps/v1 kind: DaemonSet metadata: name: fluentd namespace: kube-logging labels: app: fluentd هنا، نحدد متحكّم DaemonSet يُسمى fluentd في فضاء أسماء kube-logging ونعطيها اللصيقة app: fluentd. بعد ذلك، الصق الجزء التعريفي التالي: . . . spec: selector: matchLabels: app: fluentd template: metadata: labels: app: fluentd spec: serviceAccount: fluentd serviceAccountName: fluentd tolerations: - key: node-role.kubernetes.io/master effect: NoSchedule containers: - name: fluentd image: fluent/fluentd-kubernetes-daemonset:v1.4.2-debian-elasticsearch-1.1 env: - name: FLUENT_ELASTICSEARCH_HOST value: "elasticsearch.kube-logging.svc.cluster.local" - name: FLUENT_ELASTICSEARCH_PORT value: "9200" - name: FLUENT_ELASTICSEARCH_SCHEME value: "http" - name: FLUENTD_SYSTEMD_CONF value: disable هنا، نطابق اللصيقة app: fluentd المُعرَّفة في metadata.labels.، ثم نعيّن متحكّم DaemonSet لحساب خدمة fluentd. نختار أيضًا كائنات Pod التي تطابق المحدّد app: fluentd ليتولّى المتحكّم DaemonSet إدارتها. بعد ذلك، نحدد تسهيلاً بلا جدول زمني (NoSchedule toleration) لمطابقة الشائبة (Taint) المكافئة على عقد Kubernetes الرئيسية (القبطان). نضمن بذلك أنّ المتحكّم DaemonSet سيُطلَق أيضًا على العقد الرئيسيى في على سادة Kubernetes. أزل هذا التسهيل إذا كنت لا ترغب في تشغيل كائن Pod لتطبيق Fluentd على العقد الرئيسية الخاصة بك. راجع هذا الفصل في توثيق Kubernetes للمزيد عن التسهيلات والشوائب. بعد ذلك، نبدأ في تحديد حاوية كائن Pod، والتي نسميها fluentd. يستخدم هذا الدليل صورة رسمية من توزيعة دبيان يوفّرها فريق صيانة Fluentd. عدّل وسم image في مواصفات الحاوية إذا كنت ترغب في استخدام صورة Fluentd خاصة أو عامة، أو استخدام صورة بإصدار مختلف. يتوفر ملف Dockerfile ومحتويات هذه الصورة في مستودع fluentd-kubernetes-daemonset على Github. بعد ذلك، نضبط Fluentd باستخدام بعض متغيرات البيئة: FLUENT_ELASTICSEARCH_HOST: نعيّن قيمة هذا المتغيّر لتكون اسم نطاق خدمة Elasticsearch مجهولة العنوان المُحدَّد سابقًا elasticsearch.kube-logging.svc.cluster.local. يُترجم اسم النطاق هذا إلى قائمة عناوين IP لكائنات Pod الثلاث الخاصة بتطبيق Elasticsearch. من المُرجَّح أن يكون عنوان مضيف Elasticsearch الحالي هو عنوان IP الأول في القائمة المُرجَعة. لتوزيع السجلات على العنقود، ستحتاج إلى تعديل إعدادات مُلحَق Fluentd الخاص بمُخرجات Elasticsearch. راجع توثيق Fluentd للمزيد حول هذا الملحق. FLUENT_ELASTICSEARCH_PORT: نحدّد قيمة المتغيّر لتكون المنفذ الذي عيّناه سابقًا، 9200. FLUENT_ELASTICSEARCH_SCHEME: نعيّن القيمة http. FLUENTD_SYSTEMD_CONF: نعيّن القيمة disable لحذف مُخرجات systemd غير المضبوطة في الحاوية. أخيرًا، نلصق الجزء التالي من التعريف: . . . resources: limits: memory: 512Mi requests: cpu: 100m memory: 200Mi volumeMounts: - name: varlog mountPath: /var/log - name: varlibdockercontainers mountPath: /var/lib/docker/containers readOnly: true terminationGracePeriodSeconds: 30 volumes: - name: varlog hostPath: path: /var/log - name: varlibdockercontainers hostPath: path: /var/lib/docker/containers هنا نحدد القيمة 512 ميجابايت لتكون الحد الأقصى للذاكرة على كائن Pod الخاص بتطبيق Fluentd الذي نضمن له 0.1 من المعالج الافتراضي (0.1vCPU) و 200 ميجابايت من الذاكرة. يمكنك تدقيق حدود الموارد والطلبات بناءً على حجم السجلّات المتوقعة والموارد المتاحة. بعد ذلك، نقوم نركّب مسارات المضيف var/log/ و var/lib/docker/containers/ في الحاوية باستخدام نقطتيْ التركيب (volumeMounts) المذكورتيْن varlog و varlibdockercontainers . تُعرَّف التجزئات (volumes) في نهاية الجزء السابق. الوسيط الأخير الذي نعرّفه هو terminationGracePeriodSeconds، والذي يمنح 30 ثانية لإيقاف Fluentd بأمان عند استقبال إشارة SIGTERM. بعد 30 ثانية، تُرسَل إلى الحاويات إشارة SIGKILL. القيمة الافتراضية للوسيط terminationGracePeriodSeconds هي 30 ثانية، لذلك في معظم الحالات يمكن إغفال هذا الوسيط. راجع هذا المقال من غوغل عن أفضل الممارسات لإيقاف أعباء العمل على Kubernetes. يجب أن تبدو المواصفات الكاملة لتطبيق Fluentd كما يلي: apiVersion: v1 kind: ServiceAccount metadata: name: fluentd namespace: kube-logging labels: app: fluentd --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: fluentd labels: app: fluentd rules: - apiGroups: - "" resources: - pods - namespaces verbs: - get - list - watch --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: fluentd roleRef: kind: ClusterRole name: fluentd apiGroup: rbac.authorization.k8s.io subjects: - kind: ServiceAccount name: fluentd namespace: kube-logging --- apiVersion: apps/v1 kind: DaemonSet metadata: name: fluentd namespace: kube-logging labels: app: fluentd spec: selector: matchLabels: app: fluentd template: metadata: labels: app: fluentd spec: serviceAccount: fluentd serviceAccountName: fluentd tolerations: - key: node-role.kubernetes.io/master effect: NoSchedule containers: - name: fluentd image: fluent/fluentd-kubernetes-daemonset:v1.4.2-debian-elasticsearch-1.1 env: - name: FLUENT_ELASTICSEARCH_HOST value: "elasticsearch.kube-logging.svc.cluster.local" - name: FLUENT_ELASTICSEARCH_PORT value: "9200" - name: FLUENT_ELASTICSEARCH_SCHEME value: "http" - name: FLUENTD_SYSTEMD_CONF value: disable resources: limits: memory: 512Mi requests: cpu: 100m memory: 200Mi volumeMounts: - name: varlog mountPath: /var/log - name: varlibdockercontainers mountPath: /var/lib/docker/containers readOnly: true terminationGracePeriodSeconds: 30 volumes: - name: varlog hostPath: path: /var/log - name: varlibdockercontainers hostPath: path: /var/lib/docker/containers احفظ الملف وأغلقه بعد ضبط إعداد المتحكّم DaemonSet لتطبيق Fluentd. الآن، أطلق DaemonSet باستخدام kubectl: $ kubectl create -f fluentd.yaml يجب أن تشاهد الناتج التالي: serviceaccount/fluentd created clusterrole.rbac.authorization.k8s.io/fluentd created clusterrolebinding.rbac.authorization.k8s.io/fluentd created daemonset.extensions/fluentd created تحقق من نجاح إطلاق DaemonSet باستخدام kubectl: $ kubectl get ds --namespace=kube-logging يجب أن ترى الحالة الظاهرة في المُخرجات التالية: NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE fluentd 3 3 3 3 3 <none> 58s تشير النتيجة أعلاه إلى وجود ثلاث كائنات Pod قيد التشغيل ويعمل عليها المتحكّم fluentd. يتوافق هذا العدد مع عقد Kubernetes. يمكننا الآن النظر في Kibana للتحقق من أن بيانات السجلّات تُجمَع وتُشحَن كما نريد إلى Elasticsearch. استخدم المتصفّح لفتح العنوان التالي http://localhost:5601، مع استمرار تنفيذ الأمرkubectl port-forward. اضغط على زر Discover (اكتشف) في قائمة التنقل اليسرى: يجب أن تظهر نافذة الإعدادات التالية: تتيح لك هذه النافذة تحديد مؤشرات Elasticsearch التي ترغب في استكشافها في Kibana. لمعرفة المزيد، راجع المقال التالي في توثيق Kibana للمزيد عن أنماط الفهرسة في Kibana. في الوقت الحالي، سنستخدم نمط محارف البدل logstash-* لالتقاط جميع بيانات السجلّات في عنقود Elasticsearch الخاص بنا. اكتبlogstash-* في مربع النص ثم انقر على Next (التالي). ستنتقل بعد ذلك إلى الصفحة التالية. تسمح هذه النفاذة بإعداد الحقل الذي سيستخدمه Kibana لتصفية بيانات السجل حسب الوقت. في القائمة المنسدلة، حدد حقلtimestamp@، واضغط على Create index pattern (إنشاء نمط فهرس). الآن، اضغط على Discover (اكتشف) في قائمة التنقل اليسرى. يجب أن تشاهد مخطّطًا بيانيّا وبعض المُدخلات الأخيرة في السجلّات. بالوصول إلى هذه النقطة تكون قد نجحت في إعداد حزمة EFK وإطلاقها على عنقود Kubernetes. راجع دليل مستخدم Kibana لمعرفة كيفية استخدام Kibana لتحليل بيانات السجل. في القسم الاختياري التالي، سننشر كائن Pod لعدّاد بسيط يطبع أعدادًا على وحدة الإخراج المعيارية (stdout)، ونعثر على سجلاته في Kibana. الخطوة 5 (اختياري) - اختبار تسجيل الحاويات لتوضيح حالة بسيطة لاستخدام Kibana لاستكشاف أحدث السجلات على كائن Pod معيّن، سننشر كائن Pod محدود الإمكانيّات يطبع أعدادًا متسلسلة على وحدة stdout. لنبدأ بإنشاء كائن Pod. افتح ملفًا يسمى counter.yaml في محررك المفضل: $ nano counter.yaml ثم الصق مواصفات التالية لكائن Pod: apiVersion: v1 kind: Pod metadata: name: counter spec: containers: - name: count image: busybox args: [/bin/sh, -c, 'i=0; while true; do echo "$i: $(date)"; i=$((i+1)); sleep 1; done'] احفظ الملف ثم أغلقه. تعرّف المواصفات كائن Pod محدود يُسمى counter ينفّذ حلقة while تكرارية تطبع أعدادًا تسلسلية. ننشر counter باستخدامkubectl: $ kubectl create -f counter.yaml بمجرد إنشاء كائن Pod وتشغيله، انتقل مرة أخرى إلى لوحة معلومات Kibana. أدخل العبارة kubernetes.pod_name: counter في شريط البحث ضمن صفحة Discover (اكتشف). تصفّي العبارة السابقة بيانات السجلّات لعرض البيانات القادمة من كائن Pod المسمَّىcounter دون غيره. يمكنك النقر على أي من مُدخلات السجل لعرض بيانات وصفية إضافية مثل اسم الحاوية، وعقدة Kubernetes، وفضاء الأسماء، والمزيد. خاتمة أوضحنا في هذا الدليل كيفية إعداد وتهيئة Elasticsearch و Fluentd و Kibana على عنقود Kubernetes. استخدمنا الحد الأدنى لإنشاء بنية تسجيل تتكون من وكيل تسجيل واحد يعمل على كل عقدة Kubernetes عاملة. قبل نشر مكدس التسجيل هذا على عنقود Kubernetes ضمن بيئة إنتاج، من الأفضل ضبط متطلبات الموارد وحدودها كما هو موضح في هذا الدليل. قد تحتاج أيضًا إلى إعدادX-Pacl لتمكين ميزات المراقبة والأمان المضمنة. تتكون بنية التسجيل التي استخدمناها هنا من ثلاث كائنات Pod لتطبيق Elasticsearch، وكائن Pod واحد لتطبيق Kibana (بدون موازنة الحِمل)، ومجموعة من كائنات Pod لتطبيق Fluentd تُشغَّل على أنها متحكّمات DaemonSet. قد ترغب في تحجيم هذا الإعداد وفقًا لحالة استخدام بيئة الإنتاج الخاصة بك. راجع هذا المقال للمزيد عن حجيم Elasticsearch و Kibana. يسمح Kubernetes أيضًا ببُنى أكثر تغقيدًا لوكلاء التسجيل ممّا قد يتناسب أكثر مع استخداماتك. راجع هذا المقال على توثيق Kubernetes للمزيد عن بنية السجلّات في Kubernetes. ترجمة - وبتصرّف - للمقال How To Set Up an Elasticsearch, Fluentd and Kibana (EFK) Logging Stack on Kubernetes لصاحبه Hanif Jetha.
  3. يوفر هذا الدرس نظرة عامة على أساسيات نظام تنسيق العناقيد (Cluster orchestration) في Kubernetes. تحتوي كل وحدة على معلومات أساسية عن ميزات ومفاهيم Kubernetes الرئيسية، وتتضمن برنامجًا تعليميًا تفاعليًا عبر الإنترنت. تتيح لك هذه الدروس التفاعلية إدارة عنقود بسيط والتطبيقات العاملة على حاويّات فيه. باستخدام الدروس التفاعلية، يمكنك تعلم ما يلي: نشر تطبيق يعمل ضمن حاوية على عنقود. تحجيم النشر. تحديث التطبيق العامل ضمن حاوية بإصدار جديد من البرنامج. تنقيح التطبيقات العاملة ضمن حاويات. تستخدم البرامج التعليمية منصَّة Katacoda لتشغيل طرفية افتراضية في متصفح الويب يشغّل Minikube، وهو بيئة نشر Kubernetes محلية محدودة الحجم يمكن تشغيلها في أي مكان. لا داعي لتثبيت أي برنامج أو ضبط أي شيء؛ يعمل كل درس تفاعلي مباشرة من متصفح الويب. ما الذي يمكن أن يقدمه لك Kubernetes؟ مع خدمات الويب الحديثة، يتوقع المستخدمون أن تكون التطبيقات متاحة على مدار الساعة طوال أيام الأسبوع، ويتوقع المطورون نشر إصدارات جديدة من هذه التطبيقات عدة مرات في اليوم. يُساعد مفهوم "الحاويات" على تحزيم البرامج لخدمة هذه الأهداف، مما يتيح إصدار التطبيقات وتحديثها بطريقة سهلة وسريعة دون توقف. يساعدك Kubernetes على التأكد من تشغيل هذه التطبيقات الحاوية أينما ومتى تريد، ويساعد في العثور على الموارد والأدوات التي تحتاجها تلك الحاويّات للعمل. Kubernetes هي منصّة مفتوحة المصدر، جاهزة للإنتاج ومصممة بخبرة Google المتراكمة في تنسيق الحاويات، جنبًا إلى جنب مع أفضل الأفكار والممارسات التي يقترحها المجتمع. سننتقل الآن إلى التحدث عن وحدات Kubernetes الأساسية: إنشاء عنقود Kubernetes نشر التطبيق استكشاف التطبيق الإعلان عن التطبيق تحجيم التطبيق تحديث التطبيق 1. استخدام Minikube لإنشاء عنقود Kubernetes عناقيد Kubernetes تنسق Kubernetes عناقيد من الحواسيب عالية التوفّر المتصلة في ما بينها للعمل كوحدة منفردة. تسمح لك التجريدات (Abstractions) في Kubernetes بنشر تطبيقات تعمل ضمن حاويات على عنقود دون ربط الحاويّات بأجهزة مخصوصة. للاستفادة من هذا النموذج الجديد للنشر، يجب تحزيم التطبيقات بطريقة تفصلها عن المضيفات الفردية، أي أنه يجب وضعها في حاويات. التطبيقات العاملة ضمن حاويات أكثر مرونة وتوفّرًا مما كانت عليه في نماذج النشر السابقة، إذ كانت التطبيقات تثبّت مباشرة على أجهزة معينة بصيغة حزم مدمجة شديدة الارتباط بالمضيف. يؤتمت Kubernetes عمليات توزيع حاويّات التطبيقات وجدولتها على عنقود من المضيفات بطريقة أكثر كفاءة. Kubernetes هي منصة مفتوحة المصدر وجاهزة للإنتاج. يتكون عنقود Kubernetes من نوعين من الموارد: القبطان (The Master) الذي ينسق عمل العنقود. العُقَد (Nodes) وهي الموارد العاملة على تشغيل التطبيق. مخطط عنقود القبطان هو المسؤول عن إدارة الدفة. ينسّق القبطان جميع الأنشطة في العنقود، مثل جدولة التطبيقات، والحفاظ على الحالة المرغوبة، وتحجيم التطبيقات، وطرح تحديثات جديدة. العقدة هي آلة افتراضية (VM) أو حاسوب فيزيائي يُستخدم كآلة عاملة في عنقود Kubernetes. تحتوي كل عقدة على Kubelet، وهو وكيل لإدارة العقدة والتواصل مع القبطان. يجب أن تحتوي العقدة أيضًا على أدوات للتعامل مع عمليات الحاوية، مثل Docker أو rkt. يجب أن يحتوي عنقود Kubernetes الذي يتعامل مع حركة البيانات في بيئة إنتاج على ثلاث عقد على الأقل. عندما تنشر تطبيقات على Kubernetes، فأنت تطلب من القبطان تشغيل حاويات التطبيقات. يجدول القبطان الحاويات لتعمل على عقد العنقود. **تتواصل العُقَد مع القبطان باستخدام واجهة تطبيقات Kubernetes التي يبرزها القبطان. يمكن للمستخدمين النهائيين أيضًا استخدام واجهة تطبيقات Kubernetes مباشرةً للتفاعل مع العنقود. يمكن نشر عنقود Kubernetes على الأجهزة الفيزيائية أو الافتراضية على حد السواء. يمكنك بدء التطوير على Kubernetes باستخدام Minikube، وهو إصدار مخفّف من Kubernetes ينشئ آلة افتراضية على جهازك المحلي وينشر عنقودًا بسيطًا يحتوي على عقدة واحدة فقط. يتوفر Minikube لأنظمة لينكس و ماك وويندوز. توفّر طرفية Minikube عمليات التمهيد الأساسية للعمل مع العناقيد، بما في ذلك البدء (Start) والإيقاف (Stop) والحالة (Status) والحذف (Delete). توجد على هذه الصفحة طرفية جاهزة للاستخدام يُثبّت عليها Minikube مسبقا. 2. نشر تطبيق باستخدام kubectl عمليات النشر في Kubernetes يمكن نشر تطبيقات تعمل على حاويّات ضمن منصة Kubernetes بمجرّد توفر عنقود قيد التشغيل. لذا أنشئ إعدادات نشر (Deployment). يوجِّه كائن النشر Kubernetes إلى كيفية إنشاء نظائر (Instances) للتطبيق وتحديثها. يجدول قبطان Kubernetes، بعد إعداد كائن النشر، نظائر التطبيق للعمل على عقد العنقود. تراقب وحدة تحكم (Controller) باستمرار عمل النظائر بعد إنشائها. إنْ تعطلت عقدة مضيفة أو حذفت، فإن وحدة التحكم تستبدلها بعقدة أخرى من العنقود ليعمل عليها التطبيق، ممّا يوفّر آلية للإصلاح الذاتي لمعالجة إخفاق الآلة أو لصيانتها. في عالم ما قبل التنسيق، غالبًا ما كان تُشتخدَم سكربتات تثبيت لبدء التطبيقات، لكن لم توفر الحلول حينئذٍ لاستعادة عمل التطبيق عند إخفاق الآلة. من خلال إنشاء نظائر لتطبيقك والحفاظ على تشغيلها عبر العُقَد، توفر عمليات النشر في Kubernetes نهجًا مختلفًا جذريًا لإدارة التطبيقات. نشر تطبيقك الأول على Kubernetes يمكنك إنشاء عملية نشر وإدارتها باستخدام واجهة سطر أوامر Kubernetes ‏(Kubectl). يستخدم Kubectl واجهة برمجة تطبيقات Kubernetes للتفاعل مع العنقود. في هذا الجزء، ستتعلم أوامر Kubectl الأكثر شيوعًا اللازمة لإنشاء عمليات النشر التي تشغّل تطبيقاتك على عنقود Kubernetes. عندما تنشئ عملية نشر، ستحتاج إلى تحديد صورة حاوية التطبيق وعدد النظائر التي تريد تشغيلها. يمكنك تغيير هذه المعلومات لاحقًا عن طريق تحديث النشر. تناقش النقطتان 5 و 6 من هذا الدرس كيفية تحجيم عمليات النشر وتحديثها. بالنسبة إلى عملية النشر الأولى، ستستخدم تطبيق Node.js معبَّأ في حاوية Docker. (إذا لم تكن قد حاولت بالفعل إنشاء تطبيق Node.js ونشره باستخدام حاوية، فيمكنك القيام بذلك أولاً باتباع الإرشادات من مقال مدخل إلى Kubernetes. 3. استكشاف التطبيق: عرض العناقيد والعُقد الكائنات من نوع Pod عندما أنشأت عملية نشر في الجزء 2 أعلاه، أنشأ Kubernetes كائنًا من نوع Pod لاستضافة نظير من التطبيق. كائنات Pod هي تجريد Kubernetes لتمثيل مجموعة واحدة أو أكثر من حاويات التطبيقات (مثل Docker أو rkt)، إضافة إلى موارد مشتركة بين تلك الحاويات. تشمل تلك الموارد: التخزين المشترك، بصيغة تجزئات (Partitions). الشبكات، مثل عنوان IP فريد لكل عنقود. معلومات حول كيفية تشغيل كل حاوية، مثل إصدار صورة الحاوية أو منافذ معينة لاستخدامها. يصمّم كائن Pod "مضيفًا منطقيًّا" خاصًّا بالتطبيق، ويمكن أن يحتوي على حاويات تطبيق مختلفة مقترنة في ما بينها بإحكام نسبيًا. على سبيل المثال، قد يحتوي الكائن على حاوية تطبيق Node.js بالإضافة إلى حاوية أخرى تغذي البيانات التي سينشرها خادم الويب Node.js الذي يعمل في الحاوية الأولى. تشترك الحاويات الموجودة في كائن واحد عنوانَ IP وفضاء منافذ (Port Space)، كما أنها تشترك دائمًا العقدة والجدولة، وتعمل في سياق مشترك على العقدة نفسها. كائنات Pod هي أصغر وحدة على منصة Kubernetes. عندما تنشئ عملية نشر فإن هذا النشر ينشئ كائنات Pod مع حاويات بداخل الكائن (بدلًا من إنشاء حاويات مباشرة). يرتبط كل كائن Pod بالعقدة التي جُدولت عليها، وتظل هناك حتى الإنهاء (وفقًا لسياسة إعادة التشغيل) أو الحذف. في حالة إخفاق العقدة، تُجدول كائنات Pod متطابقة على العقد الأخرى المتاحة في العنقود. نظرة عامة على كائنات Pod عقد Kubernetes تُشَغَّل كائنات Pod دائمًا على عقدة. العقدة هي آلة عاملة في Kubernetes وقد تكون إما آلة افتراضية أو فيزيائية، حسب العنقود. يدير القبطان كل العُقد. يمكن أن تحتوي العُقدة على عدة كائنات Pod. يتولّى القبطان في Kubernetes جدولة كائنات Pod تلقائيًا على عقد العنقود. تأخذ الجدولة التلقائية من طرف القبطان في الاعتبار الموارد المتاحة لكل عقدة. تُشغِّل كل عقدة Kubernetes على الأقل: Kubelet، عملية مسؤولة عن التواصل بين القبطان و العُقدة؛ تُدير كائنات Pod والحاويات التي تعمل على الجهاز. بيئة تشغيل حاويات (مثل Docker وrkt) مسؤولة عن سحب صورة الحاوية من تقييد (Registry)، فك ضغط الحاوية، وتشغيل التطبيق. نظرة عامة على العقد استكشاف الأخطاء وإصلاحها باستخدام kubectl تحدّثنا في الجزء 2 أعلاه عن واجهة سطر الأوامر kubectl. سنستمر في الحديث عنه في هذا الجزء للحصول على معلومات حول التطبيقات المنشورة وبيئاتها. يمكن تنفيذ العمليات الأكثر شيوعًا باستخدام أوامر kubectl التالية: kubectl get - سرد الموارد. kubectl describe - عرض معلومات تفصيلية حول مورد. kubectl logs - طباعة السجلات من حاوية في كائن Pod. kubectl exec - تنفيذ أمر على حاوية في كائن Pod. يمكنك استخدام هذه الأوامر لمعرفة متى نُشرت التطبيقات، وما حالاتها الراهنة، وأين تعمل وما إعداداتها. الآن بعد أن عرفنا المزيد عن مكونات المجموعة لدينا وسطر الأوامر، دعنا نستكشف تطبيقنا. توجد على هذا الرابط بيئة تفاعلية لعرض العناقيد والعقد واستكشاف أوامر kubectl. 4. الإعلان عن التطبيق للعموم استخدام خدمة للإعلان عن التطبيق الخاص بك نظرة عامة على خدمات Kubernetes كائنات Pod في Kubernetes فانية. وهي في الواقع لها دورة حياة. عند توقّف عقدة عاملة تفقد كل كائنات Pod التي تعمل كانت تعمل على العقدة. قد تعيد وحدة التحكم ReplicaSet بعد ذلك العنقود ديناميكيًّا إلى الحالة المرغوبة من خلال إنشاء كائنات Pod جديدة للحفاظ على تشغيل التطبيق الخاص بك. مثال آخر، فلنفترض سندًا (Backend) لمعالجة الصور مع ثلاث حاويّات متماثلة. هذه النسخ المتماثلة قابلة للاستبدال؛ يجب ألا يهتم نظام الواجهة الأمامية بالنسخ المتماثلة للسند أو حتى في حالة فقد كائن Pod وإعادة إنشائه. ومع ذلك، فإن كل كائن Pod في عنقود Kubernetes له عنوان IP فريد، حتى الكائنات على العقدة نفسها. لذا يجب أن تكون هناك طريقة للتوفيق بين التغييرات تلقائيًا بين كائنات Pod حتى تستمر تطبيقاتك في العمل. الخدمة في Kubernetes عبارة عن تجريد يعرّف مجموعة منطقية من كائنات Pod وسياسة للوصول إليها. تتيح الخدمات اقترانًا فضفاضًا بين كائنات Pod المترابطة في ما بينها. تُعرّف الخدمة باستخدام YAML (وهي الوسيلة المفضّلة) أو JSON، مثل جميع كائنات Kubernetes. عادةً ما تُحدّذ مجموعة كائنات Pod التي تستهدفها الخدمة بواسطة كائن من النوع LabelSelector (انظر أدناه لمعرفة الحالات التي قد تدعوك لإنشاء خدمة بطريقة مغايرة). على الرغم من أن كل كائن Pod لديه عنوان IP فريد، إلّا أنّ تلك العناوين لا تُعرَض خارج العنقود بدون خدمة. تسمح الخدمات لتطبيقاتك بتلقي حركة المرور. يمكن الإعلان عن الخدمات بطرق مختلفة عن طريق تحديد النوع type في ServiceSpec: ClusterIP (قيمة افتراضية): يعرض الخدمة على عنوان IP داخلي في العنقود. يجعل هذا النوع الوصول للخدمة متاحًا فقط من داخل العنقود. NodePort: يعرض الخدمة على نفس المنفذ لكل عقدة محددة في العنقود باستخدام ترجمة عناوين الشبكة (NAT). يتيح الوصول إلى خدمة من خارج العنقود باستخدام عنوان IP العنقود ورقم المنفذ (<NodeIP>:<NodePort>). امتداد للطريقة السابقة (ClusterIP). LoadBalancer : ينشئ موازن حِمْل خارجي في السحابة الحالية (إذا كانت تدعم ذلك) ويعين عنوان IP ثابتًا خارجيًا للخدمة. امتداد للطريقة السابقة (NodePort). ExternalName: يُعلن عن الخدمة باستخدام اسم عشوائي (محدد بواسطة القيمة externalName في المواصفات spec) عن طريق إرجاع سجل CNAME يتضمّن الاسم. لا يُستخدَم أي وكيل (Proxy). يتطلب هذا النوع الإصدار v1.7 أو أعلى من حزمة kube-dns. يمكن العثور على مزيد من المعلومات حول الأنواع المختلفة من الخدمات في الدرس التالي، وأيضًا من خلال هذا المقال. بالإضافة إلى ذلك، يُرجى ملاحظة أن هناك حالات استخدام لا تتضمن فيها الخدمات تحديد حقل selector في المواصفات (spec). لن تنشئ الخدمة في تلك الحالة الكائن الطرفي (Endpoint object) المقابل، وهو ما يسمح للمستخدمين بالتعيين اليدوي للنقاط الطرفية للخدمة. توجد إمكانية أخرى لعدم وجود حقل selector وهي أنك تستخدم النوع ExternalName. الخدمات واللصائق (Labels) توجّه الخدمة البيانات عبر مجموعة من كائنات Pod. الخدمات هي طبقة تجريد تسمح بزوال كائنات Pod وتكرارها دون التأثير على عمل التطبيق في بيئة Kubernetes. تتولّى خدمات Kubernetes اكتشاف كائنات Pod المترابطة (مثل تطبيق يتكوّن من سند وواجهة أمامية) والتوجيه بينها. تتعرّف الخدمات على كائنات Pod المترابطة من خلال المحدّدات واللصائق (Selectors and Labels). المحدّدات هي دوال للتجميع تسمح بإجراء عمليات منطقية على كائنات Kubernetes، أمّا اللصائق فهي أزواج مفاتيح وقيم (Key/Value) مرفقة بالكائنات، ويمكن استخدامها بأي واحدة من الطرق التالية: تعيين كائنات للتطوير والاختبار والإنتاج، تضمين وسوم الإصدار، تصنيف كائن حسب الوسوم. يمكن إرفاق التسميات أو اللصائق (label) بالكائنات في وقت الإنشاء أو لاحقا. يمكن تعديلها في أي وقت. تجربة تفاعلية لعرض التطبيق للعموم باستخدام خدمات Kubernetes. 5. تحجيم التطبيق تشغيل نُسَخ متعددة للتطبيق رأينا أعلاه كيفية إنشاء عملية نشر (Deplyment)، ثم كيفية الإعلان عنها للعموم عبر خدمة. أنشأت عملية النشر كائن Pod واحدًا لتشغيل التطبيق. عندما تزداد حركة البيانات، سنحتاج إلى تحجيم (Scaling) التطبيق لمواكبة طلب المستخدم. يتحقّق التحجيم عن طريق تغيير عدد النظائر المنشورة. نظرة عامة على التحجيم سيضمن تحجيم النشر إنشاء كائنات Pod جديدة وجدولتها على العقد ذات الموارد المتاحة. سيؤدي التحجيم إلى زيادة عدد كائنات Pod إلى الحالة المرغوبة الجديدة. يدعم Kubernetes أيضًا التحجيم التلقائي Autoscaling لكائنات Pod، ولكنّ ذلك خارج نطاق هذا الدرس. التحجيم باتجاه الصفر ممكن أيضًا، وسيؤدي إلى إنهاء جميع كائنات Pod في عملية النشر المحددة. يتطلب تشغيل نسخ متعددة من تطبيق ما طريقة لتوزيع حركة البيانات عليها جميعا. تتضمّن الخدمات موازِن حِمل متكامل من شأنه أن يوزِّع البيانات المارة في الشبكة على جميع كائنات Pod الموجود ضمن نشر متاح للعموم. تراقب الخدماتُ كائنات Pod العاملة باستمرار باستخدام النقاط الطرفية لضمان إرسال البيانات إلى كائنات Pod المتاحة فقط. بمجرد أن يكون لديك نُسَخ متعددة للتطبيق قيد التشغيل، ستتمكن من دحرجة التحديثات دون توقف. سنغطي ذلك في الأجزاء التالية، وهو ما سنراه في الجزء اللاحق. يمكنك تجربة تحجيم التطبيق (بتوسيعه أو تقليصه) عبر هذه الطرفية التفاعلية. 6. تحديث التطبيق التحديث المتدحرج (Rolling update) يتوقع المستخدمون أن تكون التطبيقات متاحة طوال الوقت، ويُنتظر من المطورين أن ينشروا إصدارات جديدة منها عدة مرات في اليوم. في Kubernetes يتم ذلك عن طريق التحديثات المتدحرجة. تسمح التحديثات المتدحرجة بتحديث عمليات النشر دون توقف عن طريق التحديث التدريجي بإحلال كائنات Pod جديدة تدريجيًّا مكان الكائنات الحالية. تُجدول كائنات Pod الجديدة على العُقد التي لديها موارد متاحة. تحدّثنا في الجزء السابق عن تحجيم التطبيق لتشغيل نُسَخ متعددة. هذا الأمر مطلوب لإجراء التحديثات دون التأثير على توفر التطبيق. افتراضيًّا، الحد الأقصى لعدد كائنات Pod التي يمكن أن تكون غير متاحة ولعدد كائنات Pod الجديدة التي يمكن إنشاؤها أثناء التحديث، هو واحد. يمكن ضبط كل واحد من الخيارين إما بالأرقام أو بالنسب المئوية (من كائنات Pod). تؤصدر (Versioned) التحديثات في Kubernetes، ويمكن التراجع عن أي تحديث نشر إلى إصدار سابق (مستقر). نظرة عامة على التحديثات المتدحرجة على غرار تحجيم التطبيق، إذا كان النشر متاحًا للعموم، فإن الخدمة ستوازن حركة البيانات فقط بين كائنات Pod المتاحة أثناء التحديث. كائن Pod متاح هو نظير متوفّر لمستخدمي التطبيق. تسمح التحديثات المتدحرجة بالإجراءات التالية: نقل تطبيق من بيئة إلى أخرى (عبر تحديثات صورة الحاوية). التراجع إلى إصدارات سابقة. التكامل المستمر والتوصيل المستمر للتطبيقات بدون توقف. يمكن استخدام هذه النافذة التفاعلية لاختبار تحديث تطبيق إلى إصدار جديد والتراجع عن ذلك. ترجمة - وبتصرّف - لأجزاء kubernetes basics من توثيق Kubernetes.
  4. يعرّف هذا المقال منصَّة Kubernetes، نتناول فيه ثلاث نقاط مهمة، وهي: ما Kubernetes؟ كائنات Kubernetes Objects مرحبا Miniku ما Kubernetes؟ هذا الجزء عبارة عن نظرة عامة على Kubernetes، نتناول فيه ما يلي: نظرة على نشر التطبيقات قبل وجود Kubernetes. لماذا نحتاج Kubernetes وما الذي يمكنه فعله؟ الحالات التي لا ينفع فيها Kubernetes. Kubernetes هو منصة مفتوحة المصدر قابلة للنقل والتوسيع لإدارة أعباء العمل والخدمات الموجودة في حاويات، والتي تُسهِّل كلًا من الضبط التصريحي (Declarative) والأتمتة (Automation). تتوفّر منصة Kubernates على نظام بيئي غني وسريع النمو، كما أن دعم المنصة، وخدماتها، وأدواتها مُتاحة على نطاقٍ واسع. تنحدر كلمة Kubernetes من اليونانية، وتعني قائد الدّفة أو الطيار. جعلت غوغل مشروع Kubernetes مفتوح المصدر عام 2014. تستند Kubernetes على عقد ونصف من الخبرة التي تتمتع بها Google في تشغيل أعباء الإنتاج على نطاق واسع، جنبًا إلى جنب مع أفضل الأفكار والممارسات التي يقترحها المجتمع. نظرة على نشر التطبيقات قبل وجود Kubernetes دعنا نلق نظرة على الماضي لنفهم السبب الذي يجعل Kubernetes مفيدًا جدا. عصر النشر التقليدي في البداية، كانت المؤسسات تشغّل التطبيقات على خوادم فعلية (Physical Servers). لم تكن هناك طريقة لتعريف حدود على موارد التطبيقات في الخادم الفعلي، وقد تسبب ذلك في مشكلات تخصيص الموارد. على سبيل المثال، إذا شُغّلت تطبيقات متعددة على خادم فعلي، فقد تكون هناك حالات يستهلك فيها تطبيق واحد معظم الموارد، ونتيجة لذلك، يتراجع أداء التطبيقات الأخرى. قد يكون الحل لتلك المشكلة هو تشغيل كل تطبيق على خادم فعلي مختلف، إلّا ذلك لم يُحدِث فارقًا، إذ كانت الموارد غير مستغلة، وكان مكلفًا للمؤسسات استخدام الكثير من الخوادم الفعلية. عصر النشر الافتراضي قُدّمت الحوسبة الافتراضية (Virtualization) كحل يتيح لك تشغيل العديد من الأجهزة الافتراضية (Virtual machines، أو VM اختصارا) على وحدة المعالجة المركزية (CPU) لخادم فعلي واحد. تسمح المحاكاة الافتراضية بتقسيم التطبيقات بين الأجهزة الافتراضية وتوفِّر مستوى من الأمان إذ لا يمكن الوصول إلى معلومات أحد التطبيقات من خلال تطبيق آخر. تتيح المحاكاة الافتراضية استغلالًا أفضل للموارد في الخادم الفعلي وتتيح قابلية تطوير أفضل لأنه يمكن إضافة تطبيق أو تحديثه بسهولة، ويقلل من تكاليف العتاد (Hardware)، وأكثر من ذلك بكثير. يمكنك من خلال الحوسبة الافتراضية تقديم مجموعة من الموارد المادية على شكل مجموعة من الأجهزة الافتراضية القابلة للتدوير. كل آلة افتراضية عبارة عن آلة كاملة تقوم بتشغيل جميع المكونات، بما في ذلك نظام التشغيل الخاص بها، على الآلات الافتراضية. عصر النشر على الحاويات (Containers) تتشابه الحاويات مع الآلات الافتراضية، لكن لها خصائص عزل مريحة لمشاركة نظام التشغيل (OS) بين التطبيقات. لذلك، تعدّ الحاويات خفيفة. تحتوي الحاوية، على غرار الآلة الافتراضية ، على نظام ملفات (File System) خاص بها، ووحدة معالجة مركزية (CPU)، وذاكرة حية (Memory)، وفضاء معالجة (Process Space)، والمزيد. يمكن نقل الحاويات بين سحابات (Clouds) وتوزيعات نظم تشغيل مختلفة، نظرًا لكونها غير مقترنة بالبنية التحتية التي تعمل عليها. أصبحت "الحاويات" شائعة لأنها توفر فوائد إضافية، مثل: إنشاء ونشر تطبيقات مرنة (Agile Applications): سهولة وكفاءة إنشاء صور الحاويات مقارنة باستخدام صور الآلات الافتراضية. التطوير المستمر والتكامل والنشر (CD/CI): يُوفَّر بنية تحتية لبناء صور موثوقة ومتكررة للحاويات ونشرها مع إمكانية التراجع السريع والسهل (بسبب ثبات الصورة). فصل الاهتمامات بين التطوير وإدارة العمليات: إنشاء صور الحاويات عند إنشاء التطبيق أو أثناء إصداربدلاً من إنشائها عند نشر التطبيق، وبالتالي فصل التطبيقات عن البنية التحتية. سهولة الملاحظة: لا تقتصر الملاحظات على المعلومات والمقاييس على مستوى نظام التشغيل، بل تتعذّاه إلى سلامة التطبيق وإشارات أخرى. الاتساق البيئي عبر التطوير والاختبار والإنتاج: يعمل بنفس الطريقة على الحاسوب المحمول كما هو الحال في السحابة. قابلية التوزيع بغض النظر عن السحابة ونظام التشغيل: تعمل على أوبونتو، ردهات (RHEL)، CoreOS، لدى العميل أو على السحابات العامة الرئيسية، وفي أي مكان آخر. إدارة تتمحور حول التطبيقات: ترفع مستوى التجريد من تشغيل نظام تشغيل على آلات افتراضية إلى تشغيل تطبيق على نظام تشغيل باستخدام الموارد المنطقية. خدمات صغيرة (Micro services) موزعة، ومرنة، ومتحررة وذات اقتران محدود: تُقسَّم التطبيقات إلى قطع أصغرومستقلة يمكن نشرها وإدارتها ديناميكيًا، وليس مكدسًا (Stack) متجانسًا يعمل على آللة واحدة كبيرة أحادية الغرض. عزل الموارد: إمكانية التنبؤ بأداء التطبيقات. استغلال الموارد: كفاءة وكثافة عالية. لماذا تحتاج Kubernetes ومالذي يمكن أن يفعله تُعَد الحاويات طريقة جيدة لتجميع التطبيقات وتشغيلها. في بيئة الإنتاج، تحتاج إلى إدارة الحاويات التي تشغّل التطبيقات والتأكد من عدم وجود وقت تعطل. على سبيل المثال، في حالة تعطل الحاوية، يجب أن تبدأ حاوية أخرى. ألن يكون أسهل إذا كان النظام هو ما يتعامل مع هذا السلوك؟ هنا يأتي دور منصة Kubernates. يوفر لك Kubernetes إطار عمل لتشغيل أنظمة موزعة متكيّفة، ويعتني بتحجيم (Scaling) التطبيقات، وتجاوز إخفاقها (Failover)، ويوفر أنماطًا لنشرها، وأمور أخرى. على سبيل المثال، يمكن لمنصة Kubernetes إدارة نشر الكناري (ِCanary deployment)، وهو طريقة لنشر الإصدارات إلى مجموعة فرعية من المستخدمين أو الخوادم واختبارها أولًا ثم طرحها على بقية الخوادم، لنظامك بسهولة. يوفر لك Kubernetes ما يلي: اكتشاف الخدمات وموازنة الحِمل (Load balancing): يمكن لـ Kubernetes كشف حاوية باستخدام اسم نطاق أو باستخدام عنوان IP الخاص بها، كما يمكنه موازنة الحِمل إذا كانت حركة البيانات إلى الحاوية عالية، وتوزيع حركة البيانات عبرالشبكة لكي يكون النشر مستقرا. تنسيق التخزين (Storage orchestration): يسمح لك Kubernetes بتركيب نظام تخزين من اختيارك تلقائيًا، مثل المخازن المحلية، وموفري السحابة العامة والمزيد. أتمتة طرح الحاويات (Rollout) واستردادها (Rollback) : يمكنك وصف الحالة المرغوبة للحاويات التي تُنشَر باستخدام Kubernetes، ويمكنك تغيير الحالة الراهنة إلى الحالة المرغوبة بمعدل يُتحكّم فيه. على سبيل المثال، يمكنك إعداد Kubernetes لإنشاء حاويات جديدة للنشر آليًا، وإزالة الحاويات الموجودة وإسناد جميع مواردها في الحاوية الجديدة. التعبئة التلقائية للحاويات: تزوّد Kubernetes بمجموعة من العقد (الأجهزة) التي يمكن استخدامها لتشغيل المهام في حاويات، وتخبر المنصة بما تحتاجه كل حاوية من قدرة معالجة وذاكرة حية. يستطيع Kubernetes وضع الحاويات على العقد الخاصة بك لتحقيق أقصى استفادة من مواردك. التعالج الذاتي: يعيد Kubernetes تشغيل الحاويات التي أخفقت، ويستبدل الحاويات، ويوقف الحاويات التي لا تستجيب لمعايير التحقق التي يحددها المستخدم، ولا يعلن عن الحاويات للعملاء إلا إذا كانت جاهزة للخدمة. الإدارة السرية والتكوين: يتيح لك Kubernetes تخزين المعلومات الحساسة وإدارتها، مثل كلمات المرور ورموز OAuth ومفاتيح SSH. يمكنك نشر وتحديث هذه المعلومات الحساسة، وبناء التطبيق دون إعادة إنشاء صور الحاوية، وبدون الكشف عن الأسرار في تكوين المكدس (Stack) الخاص بك. الحالات التي لا ينفع فيها Kubernetes Kubernetes ليس نظامًا تقليديًّا وشاملًا لتقديم المنصات بصيغة خدمة (PaaS - Platform as a Service). نظرًا لأن Kubernetes يعمل على مستوى الحاوية بدلاً من مستوى الأجهزة، فإنه يوفر بعض الميزات القابلة للتطبيق عمومًا والشائع وجودها في المنصات المُقدّمة بصيغة خدمة، مثل النشر، والتحجيم، وموازنة الحِمل، والتسجيل، والمراقبة. ومع ذلك، فإن Kubernetes غير متجانس، وهذه الحلول الافتراضية اختيارية وقابلة للتوصيل بالمنصة. يوفر Kubernetes اللبنات الأساسية لبناء منصات للمطورين، ولكنه يحافظ على اختيار المستخدم والمرونة حيث يكون ذلك مهما. إنَّ Kubernetes: لا يحد من أنواع التطبيقات المدعومة. يهدف Kubernetes إلى دعم مجموعة متنوعة للغاية من أعباء العمل، بما في ذلك أعباء العمل عديمة الحالة (Stateless)، وذات الحالة (Stetful)، والتي تعالج البيانات. إذا كان يمكن تشغيل تطبيق في حاوية، فبالتأكيد سيعمل على نحو رائع على Kubernetes. لا ينشر الشفرة المصدرية ولا يبني تطبيقك. يُحدَّد سير عمل آليّات التكامل والتسليم والنشر المستمر (CI/CD) من خلال ثقافات المؤسسة وتفضيلاتها، بالإضافة إلى المتطلبات التقنية. لا يوفر خدمات على مستوى التطبيق، مثل البرامج الوسيطة (على سبيل المثال، قنوات الرسائل)، وأطر معالجة البيانات (Spark مثلًَا)، وقواعد البيانات (MySQL مثلًا)، وذاكرة التخزين المؤقت، وأنظمة التخزين العقدية (Ceph مثلًا) لا يقدّمها كخدمات مدمجة. يمكن تشغيل هذه المكونات على Kubernetes، مع الوصول إليها - أو عدمه - عن طريق التطبيقات التي تعمل على Kubernetes من خلال آليات محمولة، مثل Open Service Broker. لا يفرض حلول التسجيل أو المراقبة أو التنبيه. يوفر إرشادات إلى عمليات التكامل، وآليات جمع المقاييس وتصديرها. لا يوفر ولا يفرض لغة أو نظامًا للضبط (Jsonnet مثلا). يوفر واجهة تطبيقات برمجية تصريحية يمكن استهدافها من خلال أشكال عشوائية من المواصفات التصريحية. لا يوفر ولا يعتمد أي أنظمة ضبط أو صيانة أو إدارة أو معالجة ذاتية شاملة للأنظمة. بالإضافة إلى ذلك، Kubernetes ليس مجرد نظام تنسيق. في الواقع، يلغي Kubernetes الحاجة إلى التنسيق. التعريف الفني للتنسيق هو تنفيذ سير عمل محدد: أولاً قم بـ أ، ثم ب، ثم ج. على النقيض من ذلك، يشتمل Kubernetes على مجموعة من عمليات تحكم مستقلة قابلة للتوليف تدفع باستمرار الحالة الحالية نحو الحالة المطلوبة المقدمة. لا يهم كيف تنتقل من أ إلى ج. التحكم المركزي ليس مطلوبًا أيضًا. ينتج عن ذلك نظام أسهل استخدامًا، وأكثر قوة وصلابة وقابلية للتكيّف والتوسع. فهم كائنات Kubernetes يشرح هذا الجزء كيفية تمثيل الكائنات في واجهة برمجة تطبيقات Kubernetes ‏(Kubernetes API)، وكيف يمكنك التعبير عنها بتنسيق yaml. ونتناول به ما يلي: شرح كائنات Kubernetes. مواصفات الكائن وحالته وصف كائن Kubernetes الحقول المطلوبة شرح كائنات Kubernetes الكائنات (Kubernetes objects) هي كيانات مستديمة (Persistent) في نظام Kubernetes. تُستخدم هذه الكيانات لتمثيل حالة العنقود (Cluster) الخاصة بك. على وجه التحديد، يمكن للكائنات وصف: التطبيقات المُحتواة (Containerized) المشغَّلة الآن (وعلى أي عقدة Node). الموارد المتاحة لتلك التطبيقات. السياسات المتعلقة بكيفية تصرف هذه التطبيقات، مثل سياسات إعادة التشغيل، والتحديثات، والتسامح مع الأخطاء (Fualt tolerance). كائن Kubernetes هو "سجل نيات". بمجرد أن تُنشئ الكائن، سيعمل نظام Kubernetes باستمرار للتأكد من وجوده. من خلال إنشاء كائن، فأنت تخبر نظام Kubernetes عمليَّا بالكيفية التي تريد أن يبدو عليها عبء عمل العنقود. هذه هي الحالة المرغوبة (Desired state) للعنقود. للعمل مع كائنات Kubernetes، سواء لإنشائها أو تعديلها أو حذفها، ستحتاج إلى استخدام واجهة برمجة تطبيقات Kubernetes. عند استخدام واجهة سطر الأوامر، kubectl على سبيل المثال، تستدعي واجهة سطر الأوامر واجهةَ برمجة تطبيقات Kubernetes الضرورية. يمكنك أيضًا استخدام واجهة التطبيقات البرمجية مباشرة في برامجك الخاصة باستخدام إحدى المكتبات العميلة. مواصفات الكائن وحالته يحتوي كل كائن Kubernetes تقريبًا على حقلين مضمَّنيْن في الكائن يتحكمان في تكوينه، وهما مواصفات الكائن spec وحالتهstatus. بالنسبة للكائنات التي لها مواصفات (الحقل spec)، يجب عليك تعيين الحقل عند إنشاء الكائن، مع تقديم وصف للخصائص التي يجب توافرها في المورد: الحالة المرغوبة. يصف حقل status الحالة الراهنة للكائن. يتولّى Kubernetes ومكوّناته تبديل الحالة الراهنة للكائن وتحديثها. يدير مستوى التحكم (Controle plane) في Kubernetes باستمرار ونشاط، الحالة الفعلية لكل كائن لمطابقتها مع الحالة المرغوبة التي حدّدتها. على سبيل المثال: في Kubernetes ، النشر (Deployment) هو كائن يمكن أن يمثل تطبيقًا يعمل على عنقودك. عند إنشاء النشر، يمكنك تعيين مواصفات النشر spec لتحديد أنك تريد تشغيل ثلاث نسخ متماثلة من التطبيق. يقرأ نظام Kubernetes مواصفات النشر ويبدأ ثلاث نظائر من التطبيق المطلوب، مع تحديث الحالة لتتوافق مع المواصفات الخاصة بك. في حالة تعطّل واحدة من تلك النظائر (تغيير الحالة)، يستجيب نظام Kubernetes لوجود فرق بين المواصفات spec والحالة statusعن طريق إجراء تصحيح، في هذه الحالة، بدء نسخة بديلة من التطبيق. راجع اتفاقيّات واجهة برمجة تطبيقات Kubernetes ‏(Kubernetes API Conventions) لمزيد من المعلومات حول مواصفات الكائن وحالته والبيانات الوصفية. وصف كائن Kubernetes عندما تنشئ كائنًا في Kubernetes، يجب عليك تقديم مواصفات الكائن التي تصف حالته المرغوبة، بالإضافة إلى بعض المعلومات الأساسية حول الكائن (مثل الاسم). عند استخدام واجهة برمجة تطبيقات Kubernetes لإنشاء الكائن (إما مباشرة أو عبر kubectl)، يجب أن يتضمن طلب واجهة برمجة التطبيقات (API request) تلك المعلومات على أنها JSON في متن الطلب (Request body). في معظم الأحيان، تُقدَّم المعلومات إلى kubectl في ملف yaml. و يحوِّل kubectl المعلومات إلى JSON عند تقديم طلب واجهة برمجة التطبيقات. ها هو مثال لملف yaml. يعرض الحقول المطلوبة ومواصفات كائن نشر Kubernetes: الملف application/deployment.yaml apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 kind: Deployment metadata: name: nginx-deployment spec: selector: matchLabels: app: nginx replicas: 2 # tells deployment to run 2 pods matching the template template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.14.2 ports: - containerPort: 80 تتمثل إحدى طرق إنشاء النشر باستخدام ملف yaml.، مثل الملف أعلاه، في استخدام الأمر kubectl apply في واجهة سطر أوامر kubectl، وتمرير ملف yaml. كوسيط. إليك مثال: kubectl apply -f https://k8s.io/examples/application/deployment.yaml --record الناتج مشابه لهذا: deployment.apps/nginx-deployment created الحقول المطلوبة ستحتاج إلى تعيين قيم للحقول التالية في ملف yaml. لكائن Kubernetes الذي تريد إنشاءه: apiVersion: إصدار واجهة برمجة تطبيقات Kubernetes التي تستخدمها لإنشاء هذا الكائن. kind: نوع الكائن الذي تريد إنشاءه. metadata: البيانات الوصفية التي تساعد على تحديد الكائن على نحو فريد، بما في ذلك سلسلة محارف (String) الاسم (الحقل name)، والمعرّف الفريد (UID)، و فضاء أسماء (namespace) اختياري. spec: الحالة المرغوبة للكائن. تختلف الصيغة الدقيقة للمواصفات حسب كائنات Kubernetes، وتحتوي على حقول متداخلة خاصة بكل كائن. يمكن أن يساعدك مرجع واجهة برمجة تطبيقات Kubentes ‏(The Kubernetes API Reference) في العثور على صيغة المواصفات لجميع الكائنات التي يمكنك إنشاؤها باستخدام Kubernetes. على سبيل المثال، يمكن العثور على صيغة مواصفات الكائنات (الحقل spec) من نوع Pod على الرابط التالي. PodSpec v1 core، ويمكن العثور على صيغة المواصفات spec لكائن نشر (Deployment) هنا. مرحبا Minikube تشرح الفقرات التالية من الدرس كيفية تشغيل برنامج "مرحبا بالعالم!" بسيط في بيئة Node.js على منصة Kubernetes باستخدام Minikube و Katacoda الذي يوفر بيئة Kubernetes مجانية في المتصفح. ملحوظة: تصلح التعليمات المذكورة هنا في بيئات Kubernetes المثبَّتة محلّيا. سنتناول ما يلي: الأهداف المتطلبات إنشاء عنقود Minikube إنشاء كائن نشر إنشاء خدمة تمكين الإضافات تنظيف المخلفات الأهداف نشر برنامج "مرحبا بالعالم!" على MiniKube. تشغيل التطبيق. عرض سجلات التطبيق. المتطلبات يوفر هذا الدرس صورة حاوية أنشئت من الملفات التالية: الملف minikube/server.js var http = require('http'); var handleRequest = function(request, response) { console.log('Received request for URL: ' + request.url); response.writeHead(200); response.end('Hello World!'); }; var www = http.createServer(handleRequest); www.listen(8080); الملف minikube/Dockerfile FROM node:6.14.2 EXPOSE 8080 COPY server.js . CMD [ "node", "server.js" ] راجع توثيق Docker للمزيد من المعلومات عن أمر docker build. إنشاء عنقود Minikube اضغط على Launch Terminal. افتح لوحة معلومات Kubernetes في متصفح: minikube dashboard بالنسبة لمستخدمي بيئة Katacoda فقط: في الجزء العلوي من لوجة الطرفية، اضغط على علامة الجمع (+)، ثم اضغط على Select port to view on Host 1. بالنسبة لمستخدمي بيئة Katacoda فقط: اكتب 30000، ثم اضغط على Display Port. إنشاء كائن نشر (Deployment) كائنات Pod في Kubernetes عبارة عن حاوية واحدة أو مجموعة من الحاويات مرتبطة ببعضها البعض لأغراض الإدارة والتواصل. تحتوي كائنات Pod في هذا الدرس على حاوية واحدة فقط. يتحقق كائن النشر (Deployment) في Kubernetes من صحة Pod الخاص بك ويعيد تشغيل حاوية Pod إنْ توقفت عن العمل. عمليات النشر هي الطريقة الموصى بها لإدارة إنشاء وتحجيم مجموعات الحاويات المسمّاة Pods. استخدم الأمر create kubectl لإنشاء كائن نشر يدير Pod. يقوم Pod بتشغيل الحاوية بناءً على صورة Docker المتوفرة. kubectl create deployment hello-node --image=gcr.io/hello-minikube-zero-install/hello-node عرض كائن النشر: kubectl get deployments الناتج مشابه لما يلي: NAME READY UP-TO-DATE AVAILABLE AGE hello-node 1/1 1 1 1m عرض Pod: kubectl get pods الناتج مشابه لما يلي: NAME READY STATUS RESTARTS AGE hello-node-5f76cf6ccf-br9b5 1/1 Running 0 1m عرض أحداث العنقود (Cluster Events): kubectl get events عرض إعدادات kubectl: kubectl config view إنشاء خدمة لا يمكن الوصول إلى كائن Pod افتراضيَّا إلا من خلال عنوان IP الداخلي الخاص به ضمن عنقود Kubernetes. لإتاحة الوصول إلى حاوية hello-node من خارج شبكة Kubernetes الافتراضية، يجب عليك عرض كائن Pod بصيغة خدمة Kubernetes. استخدم الأمر kubectl expose لعرض كائن Pod للعموم: kubectl expose deployment hello-node --type=LoadBalancer --port=8080 يشير الخيار type=LoadBalancer-- إلى أنك تريد إتاحة الخدمة للوصول إليها من خارج العنقود. اعرض الخدمة التي أنشأتها للتو: kubectl get services الناتج مشابه لما يلي: NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE hello-node LoadBalancer 10.108.144.78 <pending> 8080:30369/TCP 21s kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 23m بالنسبة لمزوّدي السحابة الذين يدعمون موازنة الحمل، فسيتوفّر عنوان IP خارجي للوصول إلى الخدمة. في بيئة Minikube، يتيح النوع LoadBalancer الوصول إلى الخدمة من خلال الأمر minikube service. نفّذ الأمر التالي: minikube service hello-node بالنسبة لمستخدمي بيئة Katacoda: اضغط على علامة الجمع (+)، ثم اضغط على Select port to view on Host 1. بالنسبة لمستخدمي بيئة Katacoda: لاحظ رقم المنفذ المكون من 5 أرقام المعروض مقابل 8080 في مُخرجات الخدمات (Services Output). يُولّد رقم المنفذ هذا عشوائيًّا ويمكن أن يكون مختلفًا بالنسبة لك. اكتب رقمك في مربع نص رقم المنفذ، ثم اضغط على Display Port. باستخدام المثال السابق ، يمكنك كتابة 30369 يؤدي ذلك إلى فتح نافذة متصفح تخدم تطبيقك وتظهر رسالة "Hello World". تمكين الإضافات يحتوي Minikube على مجموعة من الإضافات المدمجة، وهي موارد تضيف وظائف جديدة إلى Kubernetes. يمكن تمكين الإضافات وتعطيلها وفتحها في بيئة Kubernetes المحلية. أظهر قائمة بالإضافات المدعومة حاليّا: minikube addons list الناتج مشابه لما يلي: addon-manager: enabled dashboard: enabled default-storageclass: enabled efk: disabled freshpod: disabled gvisor: disabled helm-tiller: disabled ingress: disabled ingress-dns: disabled logviewer: disabled metrics-server: disabled nvidia-driver-installer: disabled nvidia-gpu-device-plugin: disabled registry: disabled registry-creds: disabled storage-provisioner: enabled storage-provisioner-gluster: disabled تمكين إضافة، على سبيل المثال، metrics-server: minikube addons enable metrics-server الناتج مشابه لما يلي: metrics-server was successfully enabled عرض Pod والخدمة التي أنشأتها للتو: kubectl get pod,svc -n kube-system الناتج مشابه لما يلي: NAME READY STATUS RESTARTS AGE pod/coredns-5644d7b6d9-mh9ll 1/1 Running 0 34m pod/coredns-5644d7b6d9-pqd2t 1/1 Running 0 34m pod/metrics-server-67fb648c5 1/1 Running 0 26s pod/etcd-minikube 1/1 Running 0 34m pod/influxdb-grafana-b29w8 2/2 Running 0 26s pod/kube-addon-manager-minikube 1/1 Running 0 34m pod/kube-apiserver-minikube 1/1 Running 0 34m pod/kube-controller-manager-minikube 1/1 Running 0 34m pod/kube-proxy-rnlps 1/1 Running 0 34m pod/kube-scheduler-minikube 1/1 Running 0 34m pod/storage-provisioner 1/1 Running 0 34m NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/metrics-server ClusterIP 10.96.241.45 <none> 80/TCP 26s service/kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP 34m service/monitoring-grafana NodePort 10.99.24.54 <none> 80:30002/TCP 26s service/monitoring-influxdb ClusterIP 10.111.169.94 <none> 8083/TCP,8086/TCP 26s تعطيل metrics-server: minikube addons disable metrics-server والناتج مشابه لما يلي: metrics-server was successfully disabled التخلّص من الموارد يمكنك الآن التخلص من الموارد التي أنشأتها في العنقود: kubectl delete service hello-node kubectl delete deployment hello-node يوجد خيار آخر يتمثّل في إيقاف آلة Minikube الافتراضية: minikube stop كما يمكن حذف Minikube: minikube delete ترجمة وبتصرُّف للمقالات التالية: What is Kubernetes Understanding Kubernetes Objects Hello Minikube
  5. 1. مقدمة عن نظام بناء Gradle 1.1. ما هو نظام بناء Gradle Gradle هو نظام بناء إداري للأغراض العامة. يدعم Gradle التنزيل والضبط التلقائي للتبعيات Dependencies أو المكتبات الأخرى. ويدعم مستودعات Maven و Ivy لاستعادة هذه التبعيات. يدعم Gradle المشروع المُتعدد والبناءات ذات النواتج المتعددة. 1.2. المشاريع والمهام في Gradle يتم وصف بناءات جرادل (Gradle Builds) عبر ملف واحد أو عدة ملفات build.gradle متعددة. يوجد عادةً (ملف بناء Build File) واحد على الأقل في المجلد الأصلي للمشروع. يُعرِّف كل ملف بناء مشروعًا ومهامًا لهذا المشروع. تنقسم المشروعات إلى نوعين: شيء يجب بناؤه شيء يجب القيام به ويتكون المشروع من مهام Tasks. تمثل المهمة Task جزءًا من العمل الذي تؤديه أحد الأبنية Builds، على سبيل المثال، تجميع التعليمات البرمجية المصدر (Compile the source code) أو إنشاء Javadoc. تستند ملفات البناء هذه إلى لغة ذات مجال مخصص (Domain Specific Language - DSL).في هذا الملف، يمكنك استخدام مجموعة من التعليمات التصريحية والضرورية (declarative and imperative statements). تستطيع كتابة كود Groovy أو Kotlin متى احتجت إلى ذلك. يمكنك أيضًا إنشاء المهام وتوسيعها ديناميكيًا في وقت التشغيل Runtime. تمثل القائمة التالية ملف بناء بسيط للغاية. task hello { doLast { println 'Hello Gradle' } } لتنفيذ المهمة hello في ملف البناء، اكتب gradle hello في سطر الأوامر في مجلد ملف البناء. إذا اختفى ناتج جرادل Gradle Output، يُمكنك استخدام q- كـ (quiet parameter). gradle hello # alternative add the -q flag gradle -q hello 1.3 التعليقات في ملفات بناء Gradle يُمكنك استخدام التعليقات الفردية والمتعددة الأسطر في ملفات بناء Gradle كما يلي: // تعليق على سطر واحد /* تعليق على أسطر متعددة */ 1.4. إعدادات المشروع والوصف بشكل افتراضي، يستخدم Gradle اسم ال (Directory Name) كاسم مشروع. يمكنك تغيير هذا عن طريق إنشاء ملف settings.gradle في الدليل الذي يُحدد اسم المشروع. rootProject.name ='com.vogella.gradle.first' يمكنك أيضًا إضافة وصف لمشروعك عبر ملف build.gradle: description =""" Example project for a Gradle build Project name: ${project.name} More detailed information here... """ task hello { doLast { println 'Hello Gradle' } } استخدم الأمر gradle project للحصول على معلومات حول مشروعك. تظهر القائمة التالية الناتج. :projects ------------------------------------------------------------ Root project - Example project for a Gradle build Project name: com.vogella.gradle.first More detailed information here... ------------------------------------------------------------ Root project 'com.vogella.gradle.first' - Example project for a Gradle build Project name: com.vogella.gradle.first More detailed information here... No sub-projects To see a list of the tasks of a project, run gradle <project-path>:tasks For example, try running gradle :tasks BUILD SUCCESSFUL Total time: 1.048 secs 2. مكونات Gradle الإضافية (Gradle Plug-ins) يستخدم نظام بناء Gradle مكونات إضافية أو إضافات (Plug-ins) لزيادة وظائفه الأساسية. المكوّن الإضافي هو توسيع لعمل لـ Gradle يضيف عادة بعض المهام المُعدّة مسبقًا. يأتي Gradle مع عدد من المكونات الإضافية، ويمكنك تطوير مكونات إضافية مخصصة. مثال على ذلك هو مُكوِّن جافا الإضافي (Java Plug-in). يُضيف هذا المكوّن الإضافي مهامًا إلى مشروعك والتي تسمح بترجمة شيفرة جافا المصدرية (Java Source Code)، وتشغيل اختبارات الوحدة وإنشاء ملف JAR. يتواجد المكون الإضافي في ملف build.gradle مع التعليمة البرمجية 'apply plugin:'pluginname . على سبيل المثال، الأمر التالي يجعل المكون الإضافي لنظام Android متاحًا لإنشاء Gradle: apply plugin: 'com.android.application' يوفر Gradle أيضًا سجلًا للمكونات الإضافية عبر المكوِّن الإضافي الخاص بالبحث في Gradle والمُسمى بـ Gradle Plugin Search. 2.1. دعم بيئة تطوير متكاملة IDE لـ Gradle. تقوم شركة Gradleware بتطوير برنامج Eclipse Gradle التعليمي عبر مشروع Eclipse Buildship. تتضمن البيئات التطويرية المتكاملة الأخرى IDEs مثل IntelliJ و Android Studio دعمًا جيدًا لـ Gradle. تثبيت وتهيئة Gradle 2.2. المتطلبات يتطلب استخدام Gradle تثبيت عدّة تطوير جافا (JDK (Java Development Kit. 2.3. تحميل واستخراج Gradle يمكن العثور على أحدث إصدار من Gradle على صفحة تنزيل جرادل Gradle Download Page. قم بتنزيل أحدث توزيع متكامل. إنها gradle-${version}-all.zip، حيث تعبّر {version}$ عن الإصدار الحالي. استخرج محتويات الملف المضغوط الذي قمت بتنزيله في مجلد جديد. 2.4. تثبيت Gradle على الويندوز أضف هذا المُجلد الجديد، الذي استخرجت فيه محتويات Gradle في الخطوة السابقة، إلى مُتغير البيئة PATH. بالضغط على Win + Pause، يمكنك فتح إعدادات النظام. أولًا قم باختيار Advanced System Settings، ثم اضغط على زر Environment Variables كما هو موضّح. في نافذة متغيرات البيئة، يجب تعيين متغيرات المستخدم JAVA_HOME و GRADLE_HOME. بعد ذلك، اختر المدخل PATH في (متغيرات النظام System Variables). ثم اضغط على زر التعديل Edit لإضافة ملف bin الخاص بتثبيت Gradle إلى المسار PATH. 2.5. تثبيت Gradle على لينكس/ماك 2.5.1 التثبيت اليدوي يجب أن يشير متغير JAVA_HOME إلى jdk مناسب ويجب أن يكون JAVA_HOME/bin$ جزءًا من متغير بيئة PATH. أضف Gradle إلى المسار عن طريق تنفيذ السطر الآتي في Terminal: export PATH=/usr/local/gradle/FOLDER_TO_WHICH_YOU_EXTRACTED_GRADLE/bin:$PATH 2.5.2 التثبيت مع SDKMAN! SDKMAN! هي أداة سطر أوامر تتيح لك تثبيت إصدارات Gradle المتعددة والتبديل بينها. يتم تشغيله على أي نظام تشغيل يستند إلى UNIX. تثبيت SDKMAN! يمكنك تثبيته من سطر الأوامر. إذا كنت قد قمت بالفعل بتثبيت SDKMAN! يمكنك تخطي هذه الخطوة. curl -s "https://get.sdkman.io" | bash بعد تثبيت SDKMAN! يجب عليك إعادة تشغيل الجهاز قبل استخدامه. تثبيت Gradle وإعداد الإصدار الافتراضي sdk install gradle 6.2.1 sdk default gradle 6.2.1 gradle -v تبديل إصدار Gradle sdk install gradle 6.2.1 # use 6.2.1 for current terminal session sdk use gradle 6.2.1 gradle -v 2.5.3. تحقق من نجاح تثبيت Gradle افتح سطر أوامر واكتب gradle، وسيتم تشغيل المهمة المساعدة الخاصة بـ Gradle بشكل افتراضي. تنبيه: قد يختلف الإصدار المتوافر حاليًا أثناء قراءتك للمقال عن الإصدار المستعمل أثناء ترجمة المقال، لذا نرجو التأكد من هذه النقطة من موقع Gradle الرسمي قبل الاستمرار، فقد يكون هنالك بعض الإختلافات والتحديثات التي أضيفت على أحدث إصدار Gradle عن الإصدارات السابقة. 2.6. استخدام البرنامج الخفي Gradle لتحسين وقت بدء التشغيل يسمح Gradle لنفسه بالبدء كـبرنامج خفي لتجنب بدء تشغيل جهاز جافا الظاهري Java Virtual Machine لكل بنية. لتكوين ذلك: أنشئ ملف يسمى gradle.properties في HOME}/.gradle}$ وإضافة السطر التالي إليه: org.gradle.daemon=true يمكنك أيضًا وضع ملف gradle.properties في المجلد الأصلي لمشروعك وإلزامه بنظام التحكم في الإصدار Control Version System. إذا لم يتم استخدام Gradle لبضع ساعات ، فإن البرنامج الخفي يتوقف تلقائيًا. بتنفيذ Gradle مع الوسيط daemon-- في سطر الأوامر يبدأ البرنامج الخفي gradle. لإيقاف البرنامج الخفي بشكل تفاعلي، استخدم الأمر gradle --stop. 2.7. حدد إعدادات JVM المخصصة لـ Gradle يوفر متغير البيئة GRADLE_OPTS الفرصة لتعيين خيارات JVM محددة لـ Gradle. في استخدام البرنامج الخفي Gradle لتحسين وقت بدء التشغيل، تم تحسين أداء بدء التشغيل JVM، لكن (قاتل الأداء Performance Killer) الآخر للبنيات الكبيرة يمكن أن يكون الحد الأقصى لـ(مساحة كومة heap space) صغيرة جدا. لذلك من المنطقي زيادته في Gradle. يُعرِّف export GRADLE_OPTS=-Xmx1024m أن Gradle يمكنه استخدام 1 غيغابايت كحد أقصى لحجم الكومة. في نظام التشغيل Windows، عادةً ما يتم تعريف متغيرات البيئة عن طريق واجهة مستخدم خاصية النظام. إذا كنت ترغب في ضبط إعدادات JVM على المستوى العالمي ولكن على أساس كل مشروع، أضف إلى الملف ‎/gradle.properties السطر التالي: org.gradle.jvmargs=-Xms2g -Xmx4g -XX\:MaxHeapSize\=3g 2.8. ملف ‎.gitignore النموذجي لمشاريع Gradle إذا كنت تستخدم Git كنظام للتحكم في الإصدار، فيمكنك استخدام ملف gitignore. التالي كقالب لمشروع Gradle. # Android built artifacts *.apk *.ap_ *.dex # Java build artifacts class files *.class # other generated files bin/ gen/ build/ # local configuration file (for Android sdk path, etc) local.properties # OSX files .DS_Store # Eclipse project files .classpath .project # Android Studio *.iml .idea .gradle #NDK obj/ 3. تمرين: إنشاء مشروع Java باستخدام سطر أوامر Gradle يوفر Gradle دعمًا للسقالات Scaffolding Support لإنشاء مشاريع تستند إلى Gradle عبر سطر الأوامر. قم بإنشاء مجلد جديد new directory على نظام الملفات الخاص بك، وانتقل إليه وقم بتشغيل الأمر التالي. gradle init --type java-library --test-framework junit-jupiter يؤدي هذا إلى إنشاء مشروع Java يستند إلى Gradle والذي يستخدم JUnit Jupiter لاختبار الوحدة Unit Testing. يمكنك تشغيل البناء build عبر: gradle build والاختبار الناتج عن هذا البناء عبر: gradle test سيقوم Gradle بإنشاء تقرير اختبار في مجلد /build/reports/tests/test 4. تمرين: تكوين خصائص Gradle عند استخدام أمر gradle للمرة الأولى، يتم إنشاء مجلد gradle. في الدليل {USER_HOME}$. عادة ما يكون home/${yourUserName}/.gradle/ على نظام Linux. بينما يكون C:\Users\${yourUserName}.gradle على نظام Windows. أما على نظام MAC فيكون /Users/${yourUserName}/.gradle. داخل مجلد gradle. هذا، يجب إنشاء ملف gradle.properties مع المحتويات التالية: org.gradle.warning.mode=all الآن سيتم تجاوز الافتراض لخاصية org.gradle.warning.mode (الافتراض هو الملخص). 5. إدارة التبعية لمشاريع جافا 5.1. إدارة التبعيات باستخدام Gradle يسمح Gradle بإدارة classpath من المشاريع الخاصة بك. يمكنه إضافة ملفات JAR أو أدلة أو مشاريع أخرى إلى مسار إنشاء التطبيق الخاص بك. كما يدعم التنزيل التلقائي لتبعيات مكتبة Java. ما عليك سوى تحديد التبعية في ملف بناء Gradle الخاص بك. مما يجعل Gradle يقوم بتنزيل المكتبة بما في ذلك التبعيات العابرة لها أثناء الإنشاء. يتم تحديد مكتبة Java بواسطة Gradle عبر groupId:artifactId:version الخاص بمشروعها (ويُعرف أيضًا باسم GAV في Maven). تحدد هذه الـGAV مكتبة بشكل فريد في إصدار معين. يمكنك استخدام موقع Maven للبحث عن GAV المكتبة في Maven Central. لإضافة تبعية، قم بإضافة إدخال إلى قسم التبعية في ملف build.gradle الخاص بك كما هو موضح في القائمة التالية. dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.squareup.okhttp:okhttp:2.5.0' testCompile 'junit:junit:4.12' } 5.2. تحديد المستودعات للبحث عن التبعيات في ملف البناء الخاص بك، يمكنك تحديد المستودعات البعيدة للبحث عن التبعيات. يدعم Gradle مستودعات Maven و Ivy للبحث عن التبعيات. توضح القائمة التالية كيفية تكوين Maven central كمصدر تبعية. repositories { mavenCentral() } من الممكن أيضًا تكوين الهدف كعنوان URL: repositories { maven { url "http://repo.mycompany.com/maven2" } } توضح القائمة التالية كيفية تحديد تبعية Ivy: repositories { ivy { url "http://repo.mycompany.com/repo" } } يمكنك أيضًا إضافة مستودعات مختلفة مرة واحدة. repositories { maven ("https://repository-achartengine.forge.cloudbees.com/snapshot/") jcenter { url "http://jcenter.bintray.com/" } } يمكنك أيضًا الرجوع إلى الملفات التي ينتجها المشروع من نظام الملفات. apply plugin: 'java' repositories { mavenCentral() } dependencies { compile group: 'commons-collections', name: 'commons-collections', version: '3.2' testCompile group: 'junit', name: 'junit', version: '4.+' runtime files('libs/library1.jar', 'libs/library2.jar') runtime fileTree(dir: 'libs', include: '*.jar') compile fileTree(dir: "${System.properties['user.home']}/libs/cargo", include: '*.jar') } } 5.3. إظهار تبعيات المشروع (التبعيات العابرة أيضًا) يعرض الأمر التالي جميع التبعيات العابرة لمشروع Gradle: gradle dependencies 5.4. ذاكرة التخزين المؤقت لـ Gradle وحذف ذاكرة التخزين المؤقت يمكنك تحديث التبعيات في ذاكرة التخزين المؤقت مع خيار سطر الأوامر refresh-dependencies--. يمكنك أيضًا حذف الملفات المخزنة مؤقتًا ضمن gradle/cache./~ مع البناء القادم (next build)، يحاول Gradle تنزيل التبعيات مرة أخرى. 5.5.استثناء التبعيات المتعدية في بعض الأحيان يكون لديك تبعيات على الحزم Packages التي تحدد التبعيات العابرة المتعارضة. أحد الحلول هو استبعاد التبعية من وحدة نمطية محددة: compile 'org.springframework:spring-web:4.3.10.RELEASE' { exclude group: 'com.google.code.gson', module: 'gson' } إذا كان لديك تبعية متعددة تحدد تبعية تريد استبعادها، فيمكنك القيام بذلك على مستوى المشروع: configurations.all { exclude group: 'com.google.code.gson', module: 'gson' } باتباع نفس النهج، يمكننا استبعاد التبعية فقط أثناء وقت التشغيل runtim: configurations.runtime { exclude group: 'com.google.code.gson', module: 'gson' } 5.6. فرض إصدار محدد من تبعية متعدية من الممكن إجبار gradle على اختيار إصدار معين عندما يواجه التبعيات العابرة المتعارضة. ضع في اعتبارك أنه قد تضطر إلى تحديث هذا الإصدار يدويًا عندما تقوم بتحديث الحزم التي تعتمد عليه. configurations.all { resolutionStrategy.force 'com.google.code.gson:gson:2.8.1' } 6. تشغيل بناء Running a build عند بدء إنشاء Gradle عبر سطر الأوامر، تبحث أداة أمر gradle عن ملف يسمى build.gradle في الدليل الحالي. يدعم Gradle أيضًا اختصار المهام، على سبيل المثال، لبدء تشغيل المهمة lars، فإنّ استخدام أمر gradle l يكفي. يجب على الاختصار أن يحدد المهمة بشكل فريد، وإلا سيُظهر Gradle رسالة خطأ، توضح أن الاختصار غامض. يمكن أيضًا استخدام CamelCase للاختصار، على سبيل المثال، يمكن استدعاء المهمة gradle vogellaCompany باستخدام الأمر gradle vC. يمكن تشغيل بناء Gradle عبر الأمر gradle أو gradle -q. الوسائط q- أو quiet-- تجعل تنفيذ Gradle أقل طولًا. يمكن معالجة مهمة محددة كالتالي: gradle -q other، والتي تنفذ المهمة "الأخرى. يمكنك بالطبع أيضًا استخدام السكربت المجمع the Gradle wrapper script، إذا كان ذلك متاحًا. لتحديد ملف بناء مختلف، يمكن استخدام الخيار b buildFileName-. في السيناريوهات التي لا يتوفر فيها اتصال بشبكة، يمكن استخدام المعاملoffline--. يعمل هذا على تشغيل Gradle build دون الاتصال بالإنترنت، مما يعني أن Gradle لا يحاول الوصول إلى الموارد من الشبكة أثناء الإنشاء. على سبيل المثال، للتبعيات من مستودع إنتاج مثل Maven Central أو Bintray. للحصول على ناتج تفصيلي لما يقوم به Gradle، يمكنك تحديد المعامل info--. 7. مهام Gradle 7.1. مهام Gradle الافتراضية يقدم Gradle أيضًا مهامًا لاستكشاف Gradle نفسه، حيث يمكنك تحليل مشروع Gradle باستخدام مهام Gradle الافتراضية. مثال جيد هو مهمة المهام، والتي تعرض المهام المتاحة للمشروع. عند كتابة gradle -q tasks، يتم عرض قائمة بالمهام. يسرد هذا الأمر المهام الأساسية حتى بدون ملف build.gradle. يحاول Gradle أيضًا إعطاء بعض الإرشادات لاستخدام المهام التي تم استدعاؤها، كما هو موضح في الجزء السفلي من إخراج وحدة التحكم. سيقوم الأمر gradle tasks --all أيضًا بسرد المهام التابعة، والتي يتم استدعاؤها قبل المهمة الفعلية. عند تشغيل gradle tasks --all يبدو الناتج مشابهًا تمامًا للملف السابق، باستثناء مهمة init التي تعتمد على المهمة wrapper. 7.2. إنشاء مهام Gradle مخصصة في جزء gradle_runbuild_buildfile، تم إنشاء أول مهمة بسيطة في ملف build.gradle task hello { doLast { println 'Hello Gradle' } } عند تشغيل مهمة مهام gradle -q tasks مع ملف build.gradle هذا، سيتم سرد مهمة hello ضمن "مهام أخرى Other Tasks". تعتبر المهام بدون مجموعة مهام خاصة. على سبيل المثال، لا تُظهر طريقة العرض Gradle Task View الخاصة بـ Eclipse Gradle Plug-in مثل هذه المهام. ولكن يمكن إظهارها عن طريق تنشيط الإدخال الصحيح في قائمة العرض. يمكن تطبيق المجموعات عن طريق خاصية المجموعة group ويمكن تطبيق وصف باستخدام خاصية الوصفdescription. في حالة وجود المجموعة بالفعل، تتم إضافة مهمة hello إليها. إذا كانت المجموعة غير موجودة، فهي موجودة مُسبقًا. task hello { group 'vogella' description 'The hello task greets Gradle by saying "Hello Gradle"' doFirst { println 'Hello Gradle' } doLast { println 'Bye bye Gradle' } } 7.3. هيكل المهمة Task Structure Gradle له مراحل مختلفة، عند العمل مع المهام. بادئ ذي بدء، هناك مرحلة التكوين، حيث يتم تنفيذ الكود، المحدد مباشرة في إغلاق المهمة. يتم تنفيذ كتلة التكوين لكل مهمة متوفرة وليس فقط لتلك المهام، والتي يتم تنفيذها فعليًا فيما بعد. بعد مرحلة التكوين، تقوم مرحلة التنفيذ بعد ذلك بتشغيل الكود داخل عمليات إغلاق doFirst أو doLast لتلك المهام، والتي تم تنفيذها بالفعل. task onlySpecifiesCodeForConfigurationPhase { group 'vogella' description 'Configuration phase task example.' println 'I always get printed even though, I am not invoked' } task anotherUnrelatedTask { doLast { println 'I am in the doLast execution phase' } } عند تنفيذ gradle -q anotherUnrelatedTask، تتم طباعة ما يلي: I always get printed even though, I am not invoked I am in the doLast execution phase تأتي العبارة الأولى من مرحلة التكوين التي يتم فيها تقييم تعريف المهمة onlySpecifiesCodeForConfigurationPhase. 7.4. تبعيات مهمة Task Dependencies يسمح Gradle بتعريف المهام الافتراضية في ملف البناء. يتم تنفيذ هذه، إذا لم يتم تحديد مهام أخرى. يمكن للمهام أيضًا تحديد تبعياتها. كلا الإعدادات موضحة في ملف البناء التالي. defaultTasks 'clean', 'compile' task clean { doLast { println 'Executing the clean task' } } task compile { doLast { println 'Executing the compile task' } } task other(dependsOn: 'compile') { doLast { println "I'm not a default task!" } } task cleanOther { doLast { println "I want to clean up before running!" } } cleanOther.dependsOn clean, compile يمكن أيضًا تنفيذ ارتباطات تنفيذ المهام المحددة مسبقًا للمهام الافتراضية أو المهام من المكونات الإضافية Plug-ins باستخدام طريقة dependOn. على سبيل المثال، عندما يَتعيّن القيام ببعض الأشياء مباشرة بعد تجميع كود جافا: apply plugin: 'java' task invokedAfterCompileJava(dependsOn: 'compileJava') { doLast { println 'This will be invoked right after the compileJava task is done' } } كبديل لإنشاء مهمة جديدة، والتي تعتمد على مهمة 'compileJava'، يمكن أيضًا تطبيق كتلة تنفيذ جديدة مباشرة على مهمة موجودة، على سبيل المثال، المهمة 'compileJava': apply plugin: 'java' compileJava.doFirst { println 'Another action applied to the "compileJava" task' } compileJava.doLast { println 'Another doLast action is also applied' } عند تشغيل المهمة 'javaCompile'، يتم تنفيذ جميع الإجراءات، التي تم تطبيقها على المهمة javaCompile، واحدة تلو الأخرى وفقًا للترتيب الذي تم تطبيقه على المهمة. 7.5. تخطي مهام Skipping Tasks يمكن تخطي المهام عن طريق تمرير إغلاق مُسند predicate closure إلى طريقة onlyIf الخاصة بالمهمة أو عن طريق طرح StopExecutionException: task eclipse { doLast { println 'Hello Eclipse' } } // #1st approach - closure returning true, if the task should be executed, false if not. eclipse.onlyIf { project.hasProperty('usingEclipse') } // #2nd approach - alternatively throw an StopExecutionException() like this eclipse.doFirst { if(!usingEclipse) { throw new StopExecutionException() } } 7.5.1. الوصول إلى متغيرات النظام مثل دليل المستخدم الرئيسي يمكنك الوصول إلى متغيرات النظام. على سبيل المثال، للحصول على دليل المستخدم الرئيسي، استخدم ما يلي: def homePath = System.properties['user.home'] 8. تمرين: مهام Gradle 8.1. استخدام مهمة المهام في Gradle الهدف من هذا التمرين هو الحصول على نظرة عامة حول المهام الافتراضية، والتي يتم تسليمها افتراضيًا. افتح سطر الأوامر وقم بتنفيذ الأمر التالي: gradle -q tasks 8.2. استخدام المهمة help الهدف من هذا التمرين هو الاستفادة من المهمة help المساعدة للحصول على مزيد من المعلومات حول المهام الأخرى، مثل مهمة init. gradle -q help --task init 8.3. إنشاء مشروع Groovy التمرين السابق على علم باستخدام المهمة init: gradle -q init --type groovy-library 8.4. اختياري - إدخال مشروع Groovy جديد يمكن استخدام Eclipse Buildship لإدخال المشروع إلى Eclipse IDE. 8.5 . استخدام مهمة التبعيات من أجل رؤية تبعيات المشروع (بما في ذلك التبعيات)، يجب التذرع بمهمة التبعيات. ./gradlew dependencies إذا تم تنفيذ تمرين الإدخال Buildship الاختياري، فيمكن أيضًا استدعاء مهمة التبعيات باستخدام طريقة العرض Gradle Tasks. 9. استخدام Gradle Wrapper يتيح Gradle Wrapper إمكانية قيام المستخدم بتشغيل البناء باستخدام إصدار مُحدَّد مُسبقًا واستخدام إعدادات Gradle دون تثبيت Gradle محليًا. هذا المُغلِّفWrapper هو برنامج يتكون من سكربتات مرقِّعة-Patch Script على ويندوز، وسكربت للصدفة-Shell Script لأنظمة التشغيل الأخرى مثل لينكس. عند بدء تشغيل Gradle build عبر المجمع، يتم تنزيل الإصدار المحدد من Gradle تلقائيًا ويستخدم لتشغيل البناء. يعد الـWrapper الطريقة المفضلة لبدء إنشاء Gradle، حيث يجعل تنفيذ البناء مستقلًا عن إصدار Gradle المثبت. يمكن إنشاء البرنامج النصي المجمع عن طريق gradle wrapper. نتيجًة لذلك، يمكنك العثور على gradlew للأنظمة المستندة إلى يونكس (أنظمة التشغيل مفتوحة المصدر) و gradlew.bat لأنظمة الويندوز. يمكن استخدام هذه الملفات بدلاً من ذلك لأمر gradle، وإذا لم يتم تثبيت Gradle على الجهاز، فسيتم تنزيل Gradle وتثبيته تلقائيًا. من الممكن أيضًا تحديد مهمة تُحدِّد إصدار البرنامج. إذا تم تنفيذ هذه المهمة، فإنها تنشئ الـWrapper وتُحمِّل الإصدار الصحيح من Gradle. wrapper { gradleVersion = '4.9' } يمكن أيضًا تعريف إصدار Gradle Wrapper، عند إنشائه عبر سطر الأوامر الآتي: gradle wrapper --gradle-version 4.9 بدون هذا الإصدار الصريح، ستقوم Gradle تلقائيًا باختيار أحدث إصدار. 9.1. تكوين GRADLE_OPTS لبرنامج Gradle Wrapper يمكن أيضًا تحديد GRADLE_OPTS داخل ملف gradlew أو gradlew.bat. #!/usr/bin/env bash ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Add default JVM options here. # You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="-Xmx1024m" #... {more lines} @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal @rem Add default JVM options here. You can also use JAVA_OPTS # and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS=-Xmx1024m @rem ... {more lines} 10. تمرين: تكوين مهمة الـ Wrapper تتوفر مهام مثل مهام Gradle Wrapper افتراضيًا ويمكن تعيين خصائص معينة منها، على سبيل المثال، gradleVersion مثل هذا: wrapper { gradleVersion = '4.9' } 11. تمرين - إنشاء مهام Gradle مخصصة 11.1. تمرين: مهمة Hello Gradle قم بإنشاء مهمة helloGradle، والتي تطبع Hello Gradle مع ورشة عمل المجموعة والوصف المناسب. ثم استخدم أمر gradlew tasks/. لرؤية مهمة helloGradle الجديدة في وحدة التحكم أو استخدام Buildship في Eclipse IDE. ثم استدعِ مهمة helloGradle عن طريق استدعاء gradlew hG/. أو مرة أخرى استخدم Buildship في Eclipse IDE. 11.2. تمرين: التبعيات بين المهام قم بإنشاء مهمتين جديدتين الأولى تدعى LearnGroovy، تطبع Learn Groovy، والثانية learnGradle، تطبع 'Learn Gradle'. يجب أن يكون لهذه المهام تبعيات معقولة. 11.3. تمرين: عمل doFirst للمهام قم بتعديل مهمة learnGroovy بحيث تقوم بطباعة *Install Eclipse IDE with Buildship * قبل أن تقوم بطباعة Learn Groovy. 12. تمرين: إنشاء مهمة نسخ يمكن أيضًا إنشاء مهام جديدة واستخلاصها من مهمة أخرى وتحديد خصائص معينة لها. يمكن استخدام نوع المهمة Copy لتحديد هذه المهمة، والتي تكون قادرة على نسخ الملفات. إنشاء مشروع جديد باستخدام ملف build.gradle التالي: task copyFile(type: Copy) { from 'source' into 'destination' } قم بإنشاء مجلد مصدر داخل هذا المشروع وأضف ملف نصي إلى هذا المجلد. عند تشغيل مهمة copyFile، تقوم بنسخ الملف النصي إلى مجلد وجهة جديد. 13. تمرين: تحديد مهمة مخصصة في ملف gradle آخر إنشاء مشروع Gradle جديد، والذي يحتوي على الهيكل التالي. تبدو الفئة CheckWebsite.groovy كما يلي: package com.example import org.gradle.api.DefaultTask import org.gradle.api.tasks.TaskAction import java.io.IOException; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; class CheckWebsite extends DefaultTask { String url = 'http://www.vogella.com' @TaskAction void checkWebsite() { // check the given website by using the url try { Document doc = Jsoup.connect(url).get(); String title = doc.title(); println title println url } catch (IOException e) { e.printStackTrace(); } } } نظرًا لأن هذا الصنف-Class بها تبعيات خارجية لـ jsoup، يجب إنشاء ملف build.gradle لها. لذلك فإن build.gradle داخل مجلد buildSrc، المسؤول عن بناء فئة CheckWebsite، يبدو كما يلي: plugins { id 'groovy' } repositories { jcenter() } dependencies { compile 'org.jsoup:jsoup:1.8.3' } أخيرًا، يستخدم ملف build.gradle الرئيسي في المجلد الرئيسي نوع مهام جديدة مثل com.example.CheckWebsite. task defaultWebsiteCheck(type: com.example.CheckWebsite) task checkGradleWebsite(type: com.example.CheckWebsite) { url = 'https://docs.gradle.org/' } wrapper { gradleVersion = '4.9' } 14. تمرين: بناء Gradle Trigger من كود جافا يصف هذا التمرين كيفية تشغيل إنشاء gradle من تعليمات كود جافا. 14.1. إنشاء مشاريع جديدة لـGradle قم بإنشاء مشروعين gradle جديدين بأسماء BaseProject (يبدأ هذا المشروع إنشاء gradle) و TargetProject (تم بناء هذا المشروع بواسطة BaseProject). تأكد من أن BaseProject يطبق البرنامج المساعد جافا Java blugin. 14.2. إضافة التبعيات أضِف التبعية التالية إلى BaseProject. compile 'org.gradle:gradle-tooling-api:4.0-rc-2' 14.3. إنشاء TargetProject أُنشئ Class تحت إسم Application بطريقة static main كما يلي: import java.io.File; import org.gradle.tooling.BuildLauncher; import org.gradle.tooling.GradleConnector; import org.gradle.tooling.ProjectConnection; public class Application { public static void main(String[] args) { ProjectConnection connection = GradleConnector.newConnector().forProjectDirectory(new File("path/to/targetproject")).connect(); try { BuildLauncher build = connection.newBuild(); build.forTasks("build"); build.run(); } catch (Exception e) { e.printStackTrace(); } finally { connection.close(); } } } تقوم هذه الطريقة أولاً بإنشاء ProjectConnection للمشروع الذي يجب إنشاؤه ويتصل به. تأكد من استبدال path/to/targetproject بمسار TargetProject. من project connection، يمكن الحصول على BuildLauncher جديد. بمساعدة الدالة ()forTasks يمكنك تحديد مهام gradle التي يجب تنفيذها. يوفر BuildLauncher أيضًا بعض الطرق الأخرى لتكوين البناء. يمكنك، على سبيل المثال، تعيين عوامل بناء gradle أو تغيير إصدار Java لإنشاء المشروع بها. وعن طريق استدعاء الدالة ()run يتم تنفيذ البناء أخيرًا. تأكد من إغلاق الاتصال في البلوك الأخير. 15.بناء مشاريع جافا 15.1. البريمج التكميلي جافا Java plug-in يوفر البريمج التكميلي Java مهامًا لتجميع كود Java وتشغيل اختبارات الوحدة وإنشاء Javadoc وإنشاء ملف JAR. 15.2. تخطيط المشروع الافتراضي لمشاريع Java تفترض هذه المكونات الإضافية إعدادًا معينًا لمشروع Java الخاص بك (على غرار Maven). src/main/java يحتوي على كود جافا الأساسي. src/test/java يحتوي على اختبارات جافا. إذا اتبعت هذا الإعداد، فسيكون ملف البناء التالي كافياً لتجميع مشروع Java واختباره وحزمه. apply plugin: 'java' لبدء التنفيذ، اكتب gradle build في سطر الأوامر. يمكن استخدام SourceSets لتحديد بنية مشروع مختلفة، على سبيل المثال، يتم تخزين المصادر في مجلد src بدلاً من src/main/java. apply plugin: 'java' sourceSets { main { java { srcDir 'src' } } test { java { srcDir 'test' } } } 15.3. إنشاء مشروع Java باستخدام مهمة init لا يدعم Gradle بعد قوالب مشاريع متعددة (تسمى النماذج الأولية archetypes) مثل Maven. لكنه يوفر مهمة init لإنشاء هيكل مشروع Gradle جديد. بدون عوامل إضافية، تنشئ هذه المهمة مشروع Gradle، والذي يحتوي على ملفات gradle wrapper وملف build.gradle و settings.gradle. عند إضافة المعامل type-- مع 'java-library' كقيمة، يتم تنفيذ بناء مشروع java ويحتوي ملف build.gradle على قالب Java معين مع JUnit. سيبدو ملف build.gradle مشابهًا لهذا: /* * ... deleted the generated text for brevity */ // Apply the java plugin to add support for Java apply plugin: 'java' // In this section you declare where to find the dependencies of your project repositories { // Use 'jcenter' for resolving your dependencies. // You can declare any Maven/Ivy/file repository here. jcenter() } // In this section you declare the dependencies for your production and test code dependencies { // The production code uses the SLF4J logging API at compile time compile 'org.slf4j:slf4j-api:1.7.12' // Declare the dependency for your favourite test framework you want to use // TestNG is also supported by the Gradle Test task. Just change the // testCompile dependency to testCompile 'org.testng:testng:6.8.1' and add // 'test.useTestNG()' to your build script. testCompile 'junit:junit:4.12' } يوفر المشروع المستضاف على Github والذي يُدعى Gradle-Templates Project مزيدًا من القوالب تتجاوز مهمة init. يعمل فريق Gradle أيضًا على موضوع النموذج/القالب هذا. عادةً ما يحتوي مشروع Java على إصدار و JRE مُستهدف يتم تجميعه عليه. يمكن تعيين خاصية version و sourceCompatibility في ملف build.gradle. version = 0.1.0 sourceCompatibility = 1.8 عند تعيين خاصية الإصدار، سيتم تغيير اسم الناتج وفقًا لذلك، على سبيل المثال، my-lib-name} -0.1.0.jar} إذا كان الناتج عبارة عن تطبيق جافا قابل للتنفيذ، فيجب أن يكون ملف MANIFEST.MF على دراية بالصنف Class باستخدام الدالة main. apply plugin: 'java' jar { manifest { attributes 'Main-Class': 'com.example.main.Application' } } 16. بناء مشاريع Groovy 16.1. البريمج التكميلي جروفي Groovy Plugin يعمل البريمج التكميلي Groovy في Gradle على تمديد البريمج التكميلي Java ويوفر مهامًا لبرامج Groovy. apply plugin: 'groovy' repositories { mavenCentral() } dependencies { implementation 'org.codehaus.groovy:groovy-all:2.4.5' testImplementation 'junit:junit:4.12' } لكي تبدأ البناء، اكتب gradle build في سطر الأوامر. 16.2. الشكل الافتراضي لمشاريع Groovy تفترض هذه البريمجات التكميلية src/main/groovy يحتوي على الكود الأصلي لـ Groovy. src/test/groovy يحتوي على اختبارات Groovy. src/main/java يحتوي على الكود الأصلي لـ Java. src/test/java يحتوي على اختبارات Java. إذا تتبّعت هذا الإعداد، يكون ملف البناء التالي كافياً لتجميع مشروع Groovy واختباره وتعبئته. 17. اختبارات مع Gradle 17.1. تنفيذ 5 اختبارات JUnit مع Gradle لاستخدام 5 اختبارات جافا، أضف ما يلي إلى نهاية الـ dependencies في ملف 'build.gradle` الخاص بك. استخدم Gradle 6.0 على الأقل لهذا لتجنب المشكلات التي تم إصلاحها بالفعل. dependencies { // more stuff testImplementation(enforcedPlatform("org.junit:junit-bom:5.4.0")) // JUnit 5 BOM testImplementation("org.junit.jupiter:junit-jupiter") } 17.2. اختبار اصطلاحات التسمية لـ Gradle تفحص مهمة "test" في Gradle جميع الفئات المترجمة في المجلد المصدر للمشروع، على سبيل المثال، src/test/java أو /src/test/groovy/. يتم تحديد أصناف JUnit بواسطة: Class أو Super Class يوسِّع TestCase أو GroovyTestCase. الـ Class أو Super Class يُرمز أليهم بـ RunWith@. يحتوي الـ Class أو Super Class على دالة يُرمز إليها بـ Test@. يمكنك تعيين خاصية scanForTestClasses إلى false، إذا كنت لا تريد الكشف التلقائي عن فئة الاختبار. في هذه الحالة، إذا لم يتم تحديد أنماط تضمين/استبعاد إضافية، فإن الإعدادات الافتراضية للفئات المضمنة هي "Tests.class”, “/*Test.class/” والفئات المُستثناة الافتراضية هي"Abstract * .class /*". 17.3. تضمين واستبعاد اختبارات معينة يتم وصف تكوين الاختبار بشكل عام في وصف مهام اختبار جرادل Gradle Test Tasks Description. فئة الاختبار teast Class لديها دوال include وexclude. يمكن استخدام هذه الدوال لتحديد الاختبارات التي يجب تشغيلها بالفعل. تشغيل الاختبارات المضمنة included tests فقط: test { include '**my.package.name/*' } تخطي الاختبارات المستبعدة excluded tests: test { exclude '**my.package.name/*' } 17.4. إظهار كل نواتج الاختبار في الطرفية Terminal بشكل افتراضي، لا يقوم Gradle بطباعة الناتج القياسي لاختباراتك على الطرفية Terminal. لمشاهدة جميع مخرجات اختباراتك، أضف هذا إلى ملف البناء الخاص بك: test { testLogging.showStandardStreams = true } 18. بناء مشاريع متعددة مع Gradle 18.1. إنشاء هيكل بناء مشروع متعدد عادةً لا يتألف تطبيق الأعمال من مشروع / وحدة واحدة فقط ، ولكن يحتوي على العديد من المشاريع، والتي يجب أن يتم بناؤها. لدى Gradle مفهوم المشروع الأساسي The root project، والذي يمكن أن يحتوي على العديد من المشاريع الفرعية. يتم تحديد المشروع الأساسي بواسطة ملف build.gradle، مثل المشاريع الفردية من قبل. لتحديد، ما هي المشاريع التي تنتمي إلى البناء يتم استخدام ملف settings.gradle. على سبيل المثال، قد يكون هناك هيكل المشروع هذا: root_project core ui util settings.gradle وجود بنية المشروع هذا، سيبدو ملف الإعدادات settings.gradle هكذا: include 'core', 'ui', 'util' # altenative way would be #include 'core' #include 'ui' #include 'util' إلى جانب المهمة tasks، يوفر Gradle أيضًا المهمة المساعدة projects، والتي يمكن تنفيذها في المجلد root_project. > gradle projects 18.2. تحديد تكوين بناء عام في ملف build.gradle في التكوينات العامة root_project يمكن تطبيقها على جميع المشاريع أو على المشروعات الفرعية فقط. allprojects { group = 'com.example.gradle' version = '0.1.0' } subprojects { apply plugin: 'java' apply plugin: 'eclipse' } يحدد هذا مجموعة com.example.gradle مشتركة وإصدار 0.1.0 لجميع المشاريع. تُطبِّق نهاية المشروعات الفرعية تكوينات شائعة على جميع المشروعات الفرعية، ولكن ليس على المشروع الأصلي، كما هو الحال في نهاية all projects. 18.3. تكوينات محددة المشروع والتبعيات يمكن أن يكون للـ core و ui و util (هيكل الـ root project في المشروع كما وضحنا ذلك في الجزء 19.1) الخاصين بالمشروعات الفرعية ملف build.gradle خاص بها أيضًا. إذا كانت لديهم احتياجات محددة، والتي لم يتم تطبيقها بالفعل عن طريق التكوين العام للمشروع الأصلي. على سبيل المثال، عادةً ما يكون لمشروع واجهة المستخدم ui تبعية للمشروع الأساسي. لذا يحتاج مشروع واجهة المستخدم إلى ملف build.gradle الخاص به لتحديد هذه التبعية. dependencies { compile project(':core') compile 'log4j:log4j:1.2.17' } يتم تحديد تبعيات المشروع مع دالة المشروع project method. بدلاً من ذلك، يمكنك أيضًا تحديد تبعيات مشروع في ملف build.gradle الأصلي. ولكن يعتبر ممارسة جيدة لتحديد التبعيات في ملفات build.gradle المحدّدة الخاصة بالمشروع، وبالتالي يتم تضمين النهج التالي فقط لغرض العرض التوضيحي. allprojects { apply plugin: 'java' repositories { mavenCentral() } } project(':com.example.core').dependencies { compile project(':com.example.model') compile 'log4j:log4j:1.2.17' } 19. النشر مع Gradle 19.1. كيفية النشر باستخدام Gradle يوفر Gradle عدة طرق لنشر عناصر البناء في مستودعات الإنتاج، مثل Artifactory أو Sonatyp Nexus. 19.2. باستخدام المافن نشر البريمج التكميلي Using the maven-publish plugin الطريقة الأكثر شيوعًا هي استخدام المكون الإضافي maven-publish، والذي يوفره Gradle افتراضيًا. // other plug-ins apply plugin: 'maven-publish' publishing { publications { mavenJava(MavenPublication) { from components.java } } repositories { maven { url "$buildDir/repo" } } } هناك العديد من خيارات النشر ، عندما يتم تطبيق java والمكون الإضافي maven-publish. يمكن إجراء النشر إلى مستودع منفصل مثل هذا: apply plugin: 'groovy' apply plugin: 'maven-publish' group 'workshop' version = '1.0.0' publishing { publications { mavenJava(MavenPublication) { from components.java } } repositories { maven { // default credentials for a nexus repository manager credentials { username 'admin' password 'admin123' } // url to the releases maven repository url "http://localhost:8081/nexus/content/repositories/releases/" } } } يمكن العثور على مزيد من المعلومات حول النشر إلى مستودع Maven للإنتاج هنا: Publish to Maven repository with Gradle. 20. التكامل مع Ant يدعم Gradle تشغيل مهام Ant عبر برنامج Groovy AntBuilder. 21. تحويل مشاريع Groovy إلى Gradle يوفر Gradle مهمة init المحتضنة، والتي تساعد في إنشاء مشاريع Gradle جديدة. يمكن لهذه المهمة أيضًا تحويل ملفات Apache Maven pom.xml إلى ملفات بناء Gradle، إذا كانت جميع المكونات الإضافية المستخدمة في Maven معروفة لهذه المهمة. في هذا الجزء، سيتم تحويل Groovy pom.xml التالي إلى مشروع Gradle. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example.app</groupId> <artifactId>example-app</artifactId> <packaging>jar</packaging> <version>1.0.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> </dependencies> </project> يؤدي تشغيل gradle init --type pom في سطر الأوامر إلى تكوين Gradle الآتي. تعتمد مهمة init على مهمة Wrapper بحيث يتم أيضًا إنشاء Gradle Wrapper. يشبه ناتج ملف build.gradle الآتي: apply plugin: 'java' apply plugin: 'maven' group = 'com.example.app' version = '1.0.0-SNAPSHOT' description = """""" sourceCompatibility = 1.5 targetCompatibility = 1.5 repositories { maven { url "http://repo.maven.apache.org/maven2" } } dependencies { testImplementation group: 'junit', name: 'junit', version:'4.11' } 22. تطوير مكونات Gradle الإضافية Gradle Plug-ins 22.1. لماذا نُنشيء مكونات Gradle الإضافية؟ كقاعدة عامة، من المفيد أن يكون هناك بناءً تصريحيًا declarative build بأكبر قدر ممكن لأن هذا يُبسِّط التعديلات المستقبلية. لذلك يُنصح بتجنب التعليمات البرمجية المعقدة في ملف بناء Gradle الخاص بك. إذا كنت بحاجة إلى منطق مخصص، فيجب وضعه في مكون إضافي مخصص لـ Gradle. 22.2. Gradle DSL يأتي كل مكون إضافي من Gradle مزودًا بخدمة DSL. لمشاهدة جميع خصائص كائن Gradle، يمكنك استخدام مقتطف التالي من الكود: println variants.properties .sort{it.key} .collect{it} .findAll{!filtered.contains(it.key)} .join('\n') على سبيل المثال، لتحديد المهام التي تعرض جميع خصائص android.applicationVariants (في مشروع أندرويد)، استخدم: task showAndoidVariantsInformation { doLast { android.applicationVariants.all { variants -> println variants.properties .sort{it.key} .collect{it} .findAll{!filtered.contains(it.key)} .join('\n') } } } 23. تمرين: إنشاء برنامجًا مساعدًا بسيطًا Simple Gradle Plugin تعمل الأداة الإضافية java-gradle-plugin على تبسيط إنشاء مكونات إضافية مخصصة لـ Gradle. هذا البرنامج المساعد في طور الإعداد حاليًا. يفعل ما يأتي: تتم إضافة التابع ()gradleApi تلقائيًا. تتم إضافة التابع ()gradleTestKit تلقائيًا. تتم إضافة ملفات واصف المكونات الإضافية الضرورية تلقائيًا. 23.1. إنشاء مشروع Gradle حدِّد Gradle Project < Gradle < Other < new < File في Eclipse لإنشاء مشروع Gradle جديد. ضع com.vogella.gradleplugin ليكون اسم المشروع كما هو موضّح. التزم بالإعدادات الافتراضية للمعالج وقم بإنشاء المشروع. 23.2. قم بتطبيق المكون الإضافي "java-gradle-plugin" غيِّر ملف build.gradle إلى ما يلي: plugins { id 'java-gradle-plugin' } gradlePlugin { plugins { vogellaPlugin { id = 'com.vogella.gradleplugin' implementationClass = 'com.vogella.gradleplugin.MyPlugin' } } } repositories { jcenter() } dependencies { // No need to add gradleApi() here, because it is applied by the 'java-gradle-plugin' plug-in // We want to merge and parse SpotBugs xml files with XSLT compile('net.sf.saxon:Saxon-HE:9.8.0-12') // Use JUnit test framework testImplementation 'junit:junit:4.12' } wrapper { gradleVersion = '4.9' } في مجلد src/main/java/، قم بإنشاء الصنفين Classes التاليتين. package com.vogella.gradleplugin; import org.gradle.api.DefaultTask; import org.gradle.api.tasks.TaskAction; public class MyTask extends DefaultTask { @TaskAction public void myTask() { System.out.println("Hello from vogella task"); } } package com.vogella.gradleplugin; import org.gradle.api.Plugin; import org.gradle.api.Project; public class MyPlugin implements Plugin<Project> { @Override public void apply(Project project) { project.getTasks().create("myTask", MyTask.class); } } قم بتنفيذ المهمة build لإنشاء مكون إضافي وانظر التدريبات التالية حول كيفية نشر المكون الإضافي واستخدامه. 24. تمرين: قم بنشر المكونات الإضافية المخصصة لـ Gradle في مستودع Maven المحلي أضف المكوّن الإضافي Gradle-plugin ومغلِّف النشر publishing closur إلى ملف build.gradle. plugins { id 'java-gradle-plugin' id 'maven-publish' } group = 'com.vogella' version = '0.0.1-SNAPSHOT' sourceCompatibility = 1.8 // ... more code publishing { publications { mavenJava(MavenPublication) { from components.java } } } تتوفر الآن مهام نشر إضافية ويمكن استخدام مهمة publishToMavenLocal لإضافة مكون Gradle الإضافي إلى مستودع Maven المحلي. ./gradlew pTML 25. تمرين: استخدام المكون الإضافي الجديد لاستخدام مكون Gradle الجديد، يجب تحديد تبعية له. إذا دفعت مكونك الإضافي إلى مستودع Maven المحلي، فيجب على Gradle العثور عليه وإتاحته. buildscript { repositories { mavenLocal() } dependencies { classpath 'com.vogella:com.vogella.gradleplugin:0.0.1-SNAPSHOT' } } apply plugin: 'com.vogella.gradleplugin' الآن المهمة الجديدة من com.vogella.gradleplugin يجب أن تكون متاحة: ./gradlew tasks ./gradlew myTask 26. تمرين: -اختياري- نشر المكوّن الإضافي في مدخل المكوّن الإضافي جرادل Gradle Plug-in Portal لنشر مكون إضافي من نوع Gradle في مدخل Gradle Plug-in، يمكن استخدام com.gradle.plugin-publish. قبل تحميل مكونات Gradle الإضافية إلى البوابة ، يجب عليك التسجيل على https://plugins.gradle.org/user/register والحصول على مفاتيح api من ملفك الشخصي. يجب إضافة خصائص gradle.publish.key و gradle.publish.secret إلى gradle.properties. بعد ذلك ، يجب تعديل ملف build.gradle ليتمكن من تحميل مكونات Gradle الإضافية. plugins { id 'java-gradle-plugin' id 'maven-publish' id 'com.gradle.plugin-publish' version '0.9.10' } // more code ... pluginBundle { website = '${Web site for your plugin}' vcsUrl = 'https://github.com/${your-repo}' plugins { vogellaPlugin { id = 'com.vogella.gradleplugin' displayName = 'Vogella Sample Plug-in' description = 'Vogella Sample Plug-in for trying the ' tags = ['Vogella','Training','Gradle','Sample'] // Gradle's plug-in portal does not support SNAPSHOTs version = '0.0.1' } } } يمكن بعد ذلك استخدام المهمة التالية لتحميل المكوّن الإضافي Gradle. ./gradlew publishPlugins عند نشر المكون الإضافي، يمكن استخدام نهايات المكونات الإضافية للاستفادة من مكون Gradle الإضافي. plugins { id "com.vogella.gradleplugin" version "0.0.1" } لذلك يمكن حذف النهايات المطوّلة بشكل أكبر وأيضًا دالة المكوِّن الإضافي apply من الفصول السابقة. المزيد من التفاصيل تجدها هنا. 27. تصحيح إضافات غرادل Gradle Plug-ins 27.1. تنشيط تصحيح الأخطاء عن بُعد يجب تحديد الخصائص التالية في ملف gradle.properties لتمكين تصحيح الأخطاء عن بُعد: org.gradle.daemon=true org.gradle.jvmargs=-XX:+HeapDumpOnOutOfMemoryError -Xmx4g -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5006 عند تشغيل بناء Gradle محليًا باستخدام هذه الإعدادات، يمكن الوصول إلى تصحيح الأخطاء عن بُعد عبر مضيف محلي localhost على المنفذ 5006. 27.2. تصحيح الأخطاء عن بُعد في Eclipse IDE يجب عليك إدخال مكون إضافي معين إلى Eclipse IDE باستخدام أدوات Buildship. بعد ذلك، يمكنك إضافة نقاط التوقف break points إلى الملفات الأساسية للمكون الإضافي. بعد ذلك، افتح تكوين تصحيح الأخطاء Debug Configuration وانقر بزر الفأرة الأيمن فوق Remote Java Application لإنشاء Debug Configuration جديد باستخدام الإعدادات التالية: اضغط على الزر Debug لتشغيل مصحح الأخطاء عن بُعد remote debugger. بعد ذلك، يمكن تشغيل بناء Gradle، الذي يستخدم مكون Gradle الإضافي المطلوب، إما باستخدام طريقة عرض مهام Gradle لأداة Buildship داخل Eclipse IDE أو استدعاء بناء Gradle من سطر الأوامر. عند الوصول إلى نقطة توقف أثناء مهمة Gradle، فإن Eclipse IDE سوف يتوقف عند هذه النقطة. 28. استخدام أدوات تحليل الكود يوفر Gradle عدة مكونات إضافية لتحليل قاعدة الكود لمشروع Gradle. الجدول 1. أدوات تحليل كود Gradle الأداة Plug-in الوصف Checkstyle checkstyle التحقق من صحة قواعد checkstyle، والتي يتم تطبيقها على المشروع. Jacoco jacoco يتحقق من تغطية الاختبار للكود الذي يجري بناؤه. FindBugs findbugs تحليل الكود الثابت للجافا. CodeNarc codenarc تحليل الكود الثابت لـGroovy. PMD pmd يضيف اختبارات جودة الكود لعدة لغات برمجة. JDepend jdepend أداة تحليل التعليمات البرمجية الأخرى لتحليل التبعيات في كود الجافا. table { width: 100%; } thead { vertical-align: middle; text-align: center; } td, th { border: 1px solid #dddddd; text-align: right; padding: 8px; text-align: inherit; } tr:nth-child(even) { background-color: #dddddd; } 28.1. Jcoco لمشاريع Gradle لاستخدام Jacoco لتحليل مدى تغطية الاختبار-Code Coverage، يجب إضافة الكود التالي إلى ملف build.gradle ذي المستوى الأعلى. plugins { id 'jacoco' } jacocoTestReport { reports { xml.enabled true html.enabled true } } إذا كان لديك مشروع Gradle متعدد المشروعات، فأنت بحاجة إلى إضافة jacocoTestReport والمكون الإضافي jacoco إلى قسم المشاريع الفرعية في ملف build.gradle ذي المستوى الأعلى. plugins { id 'jacoco' } subprojects { apply plugin: 'jacoco' jacocoTestReport { reports { xml.enabled true html.enabled true } } } إذا كنت ترغب في الحصول على ملف xml مدمج لجميع المشروعات الفرعية، على سبيل المثال لاستخدام SonarQube، يمكنك إنشائه بالمهمة التالية في build.gradle ذي المستوى الأعلى. task generateMergedReport(type: JacocoReport) { dependsOn = subprojects.test additionalSourceDirs.setFrom files(subprojects.sourceSets.main.allSource.srcDirs) sourceDirectories.setFrom files(subprojects.sourceSets.main.allSource.srcDirs) classDirectories.setFrom files(subprojects.sourceSets.main.output) executionData.setFrom project.fileTree(dir: '.', include: '**/build/jacoco/test.exec') reports { xml.enabled true xml.destination file("../coverage-reports/coverage.xml") } } ستقوم هذه المهمة بحفظ ملف XML الموحد في المستوى العلوي من المشروع ضمن تقارير التغطية. أخيرًا لإنشاء ملف xml مدمج، قم بتشغيل Gradle باستخدام المهمة التي تم إنشاؤها generMergedReport. ./gradle clean build generateMergedReport 29. Gradle Gradle Goodness blog series Eclipse Gradle Tooling Publish to Maven repository حقوق النشر والترخيص والكود يتم منح الاستخدام المجاني لأمثلة البرامج بموجب شروط Eclipse Public License 2.0. المقال مترجم وبتصرف للمقال The Gradle build system- Tutorial
  6. هل تحتاج إلى تطبيق في متجر التطبيقات؟ هل يجب أن تكون أصلية (Native) أم مُهجّنة (Hybrid)؟ ما الفرق بين تطبيق جوال وتطبيق ويب وموقع متجاوب؟ هل تعتقد أن يكون الموقع متجاوبًا (Responsive Website)، فهذا يجعله متوافقًا مع الجوال (Mobile Friendly)؟ كل هذه الاسئلة عليك أن تُجيب عنها إذا ما أردت التعامل مع الأجهزة المحمولة كمُطوِّر، وهذا ما سنسلط عليه الضوء في هذا المقال. أنواع تطبيقات الجوال هناك الكثير من الالتباس بين المؤسسات حول كيفية التعامل مع الأجهزة المحمولة. ومع ذلك، عندما يتعلق الأمر بهذا فلديك 4 خيارات كالتالي: التطبيقات الأصلية أو الأصيلة (Native Applications). تطبيقات الويب (Web Application). التطبيقات المُهجّنة (Hybird Application). المواقع المُتجاوبة (Responsive Websites). التطبيقات الأصلية (Native Applications) التطبيقات الأصلية هي تطبيقات تعمل فعليًا على الجهاز المحمول ويتم ترميزها خصيصًا لنظام تشغيل الجهاز. هذه هي التطبيقات التي تجدها عادةً في متجر تطبيقات Google Play أو iOS. هذا هو أفضل نهج حيث السرعة والميزات الأصلية (Native Features) مطلوبة. تطبيقات الويب (Web Application) هناك خصائص مشتركة بين كلٍ من تطبيقات الويب، التطبيقات الأصلية، والمواقع المُتجاوبة. كما هو الحال مع الموقع المتجاوب، تُصمَّم تطبيقات الويب باستخدام HTML و CSS و Javascript ويكون بالكامل على الإنترنت. ومع ذلك، عندما يكون الموقع المُتجاوب موجَّهًا للمحتوى (Content Oriented)، فإن تطبيق الويب يركز على المهمة بنفس طريقة التطبيق الأصلي. ومن الأمثلة على ذلك تطبيق Blackpool Pleasure Beach للهاتف المحمول. التطبيق متاح عبر الإنترنت ولكنه ليس موقع ويب غني بالمحتوى. بدلاً من ذلك، إنه تطبيق حجز يسمح للمستخدمين بشراء تذاكرهم وبطاقاتهم. نظرًا لأنه يتطلب اتصالًا مستمرًا بالخادم(Server)، ولم يستخدمه المستخدمون بشكل منتظم ولا يحتاج إلى ميزات أصلية، فلا فائدة من جعله تطبيقًا أصليًا. التطبيقات الهجينة (Hybird Application) ربما يكون التطبيق المُهجّن أو الهجين هو الأصعب في شرح الخيارات. يُعَد التطبيق المُهجّن تطبيقًا أصليًا أُنشئ باستخدام HTML و CSS و Javascript. فمن خلال بناء هذه التطبيقات باستخدام تقنية الويب، يكون تطويرها أسرع وأسهل للنشر على منصات متعددة (مثل iOS أو Android). الجانب السلبي هو أن الأداء لا يميل إلى أن يكون جيدًا ويفتقر إلى نمط التصميم لكل منصة. مثال على هذا النوع من التطبيقات هو إثبات مفهوم تطبيق "فراشة العد butterfly counting" الذي طوّرته Headscape. تم بناء هذا التطبيق المُهجَّن في PhoneGap وسمح للمستخدمين بتحديد وحساب الفراشات في الحقل حتى مع عدم وجود اتصال بالانترنت. كان قرار إنشاء هذا كتطبيق هجين لأنه إثباتًا للمفهوم وأرادت الشركة إنتاجه بسرعة وبأقل تكلفة. المواقع المتجاوبة (Responsive Websites) الموقع المُتجاوب هو الذي يتكيف مع أي جهاز يتم عرضه عليه. سواء كان ذلك عبارة عن جهاز سطح مكتب أو جهاز لوحي أو جهاز محمول، سيعرض موقع الويب نفسه المحتوى نفسه باستخدام تصميم مرئي يناسب هذا الجهاز. خذ على سبيل المثال Macmillian English. فهو موقعٌ غنيٌّ جدًا بالمعلومات. يُنقِّب مستخدمو الموقع عن المعلومات بدلاً من إتمام المهام. يضمن موقع الويب المتجاوب أن يتمكنوا من العثور على نفس المعلومات مهما كان الجهاز الذي يستخدمونه. المواقع المُتجاوبة جيدة لـ : المواقع الغنيّة بالمعلومات. المستخدمين الذين يتطلعون إلى جمع المعلومات. إذا كنت لست متأكدًا من الخيار الذي تحتاجه، فعادةً ما يكون الموقع المُتجاوب نقطة انطلاقٍ جيدة. هذا ما نعتقده عادًًة، أن تكون المواقع مُتجاوبة، فهذا يجعلها متوافقة مع الجوال (Mobile Friendly)، لكن هذا ليس كل ما في الأمر. لا تزال هناك طرق كثيرة تُمكّننا من إفساد تجربة المُستخدم والإضرار بموضعنا في نتائج البحث. ألق نظرة على دورة تطوير تطبيقات الجوال باستخدام تقنيات الويب والتي ستتعلم فيها تطوير تطبيقات جوال لأنظمة أندرويد وأيفون و ويندوز فون باستخدام تقنيات الويب البسيطة JavaScript, HTML, CSS مما يمكنك من تطوير تطبيق واحد يعمل مباشرة على جميع المنصات. إنشاء موقع متجاوب متوافق تمامًا مع الجوال ندرك جيدًا أهمية أن يكون الموقع متوافقًا مع الجوال، ويميل معظمنا إلى التفكير في "التصميم المتجاوب Responsive Website" كحل لهذا. لا ريب أن "التصميم المُتجاوب" قد أحدث طفرةً في تطبيقات الويب على الهواتف الذكيّة. فبوجٍه عام، لقد حسّنَ "تجربة المُستخدم User Experience" وسهّل علينا إدارة المواقع وجنّبنا الحفاظ على إصدارات متعددة لنفس الموقع. دعني أقدم لك بعض الأمثلة عن "أين يمكن أن تسوء الأمور" بدءًا من المشكلة الأكبر على الإطلاق "المواقع شبه المتجاوبة". هل موقعك مُتجاوب بالكامل؟ عندما نقوم بإعادة تصميم موقع ويب من البداية، فإن بناءه حيث يصبح "موقعًا متجاوبًا" أمرًا منطقيًا تمامًا. لكن تعديل لموقع موجود بالفعل (خاصةً إذا كان موقعًا كبيرًا) حيث يصبح "موقعًا متجاوبًا"، يمكن لهذا أن يتحول إلى كابوس. العديد من العملاء واجهوا هذه المشكلة، تحتوي معظم المواقع الإلكترونية الخاصة بهم على عشرات الآلاف من الصفحات على الأقل، تم إنشاؤها على مدار سنوات. مما جعل أمر أن تكون هذه المواقع "صديقة للهواتف الذكيّة" تحديًا بشكل لا يُصدَّق. لتقليل العمل الذي ينطوي عليه الأمر، فهم يتخذون قرارًا بالتركيز على الصفحات الأساسية وجعلها سهلة الاستخدام على الهواتف الذكيّة ومٌتجاوبة أيضًا، متجاهلين البقية. بالرغم من أنني أستطيع فهم هذا القرار إلا أنه سيء جدًا من منظور "تجربة المستخدم". ليس هناك ما هو أكثر إحباطًا من التورط في التفكير بأنّك تتصفح موقعًا صديقًا للهاتف، فقط لتجد نفسك عالقًا في صفحة مُحسّنة لسطح المكتب لا يمكنك قراءتها أو تصفّحها. تحتاج هذه المنظمات إلى التفكير بتمعن ولفترة طويلة حول ما إذا كانت تحتاج لمثل هذه المواقع الكبيرة. من واقع خبرتي، نادرًا ما يقومون بذلك، ويقومون فقط بترحيل المحتوى بشكلٍ أعمى من إصدارٍ لآخر. هل قلصت وظائفية الموقع؟ يواجه المصممون مشكلة مماثلة عندما يصطدمون ببعض الوظائف الشاقة للموقع. شيء يبدو معقدًا جدًا لدرجة لا تمكّنه من صداقة الهواتف المحمولة ويكون التعامل مع الموقع على الهاتف شاقًا جدًا على المُستخدم. بدلًا من مضاعفة الجهود والتوصل لحل مُبتكر، نسهل الطريق الأقل صعوبة ومقاومة. نحن نقنع أنفسنا بأن مُستخدمي الهواتف لن يحتاجوا إلى هذه الوظائف وسيُزيلونها من الموقع. هذا بالطبع، أمرٌ ساذج بشكل لا يُصدّق. لا يختلف مُستخدمو الهواتف المحمولة بطريقة سحرية. هم نفس المُستخدمين الذين يستخدمون موقعك على "سطح المكتب Desktop". لقد بدّلوا الأجهزة فقط. لقد رأيتُ حتى أن الناس يستخدمون الهواتف المحمولة أثناء الجلوس في متناول اليد من أجهزة الحاسوب الخاصة بهم. لا يمكننا وضع افتراضات حول متطلباتهم بناءً على الجهاز. هل تؤيد إشارات اللمس؟ لا تتعلق الأخطاء الصديقة لأجهزة الهاتف فقط بإزالة الأشياء، يمكن أن يكون الفشل في إضافة شيء ما بنفس الخطورة. خذ على سبيل المثال "دعم إشارات اللمس". لا عجب أن كثيرًا من المُستخدمين يفضلون أحيانًا تطبيقات الأجهزة المحمولة عندما لا يُمكنهم التحرك والضغط على موقع إلكتروني على الهاتف. يجب حقًا على مصممي المواقع السماح للمستخدمين بتحريك الصور الدائرية والضغط عليها. فغالبًا ما يتم التغاضي عن هذا النوع من الوظائف لأننا نركز على تكييف التصميم لنقاط التوقف المختلفة. هل يتكيف المحتوى وكذلك التصميم؟ أن يكون الموقع صديقًا للهاتف المحمول، فهذا لا يعني تغيير التصميم فقط، إنّه يتعلق أيضًا بتغيير المحتوى نفسه. خذ "الجدول" على سبيل المثال. فقط لأننا نعرض البيانات كجدول بأحجام شاشات أكبر، فهذا لا يعني أن علينا أن نفعل ذلك على الهواتف أيضًا. قد نستنتج أن عرض "مخطط تفاعلي interactive chart" أو شكل من أشكال الآلة الحاسبة أكثر صداقة للهاتف "Mobile Friendly". نحتاج إلى تعديل المحتوى وليس فقط التصميم والجداول ومخططات المعلومات البيانية، إلى شيء يسهل التعامل معه على الهاتف ثم يأتي دور "الرسوم البيانية infographics"، التي تبدو رائعة على الشاشات الكبيرة لكنها تصبح غير قابلة للقراءة على أجهزة الهاتف. نستطيع بالتأكيد أن نسمح للمُستخدم بتكبير الشاشة وندّعي بذلك أننا قمنا بعملنا على أكمل وجه، أو يمكننا أن نعيد تخيلهم. ربما يجب علينا أن نُقسِّم هذه الرسوم البيانية إلى "لوحة رسوم Storyboard" أو نستخدم فيديو عوضًا عن ذلك. هل موقعك مقروءًا على الهاتف بقدر الإمكان؟ لا تقتصر مشاكل الوضوح فقط على الصور والجداول، إنما ينطبق الأمر بنفس القدر على النص. لن تُخلق تجربة ودية ومقروءة مع الهاتف بمجرد إضافة نقاط توقف إلى عناصر إعادة الموضع. انها غالبًا ما تُقصِّر أطوال الخطوط لدرجة تجعل القراءة صعبة للغاية. يبذل المصممون جهدًا في هذا الصدد بجعل حجم الخط يتناسب مع نقطة التوقف (breakpoint)، وبتقليص حجمه وفقًا لذلك. لكنني زرتُ العديد من المواقع على الأجهزة المحمولة حيث أصبحت أحجام الخطوط صغيرة جدًا مما يجعل النص غير مقروء. وأخيرًا، هناك قضية "اللون". غالبًا ما يفشل المصممون في أن يأخذوا بعين الاعتبار وهج الشاشة التي يعاني منها مستخدمو الهواتف المحمولة، ومن ثَمَّ يصنعون لوحات الألوان الدقيقة التي تُصعِّب تجربة القراءة على الهاتف وهذا على أقل تقدير. هل تركز بشكل كبير على أحدث وأفضل الهواتف الذكيّة؟ بالطبع، بعض مشاكل الوضوح هذه غير مرئية لنا حتى عند اختبارها على جهاز محمول. ذلك لأن لدينا أحدث وأكبر هاتف ذكي. إنه يحتوي على شاشة شبكية (retina screen)، ذات دقة عالية جدًا، وسطوع لا يمكن حتى للشمس أن تجاريها! ولكن ليس الجميع لديه أجهزة كهذه. حتى لو تجاهلت الهواتف المميزة كبندٍ للمقارنة، فقد تختلف التجارب بشكل كبير. ثم، بالطبع، هناك نقاط توقف. ما زلت أرى أن المصممين يقومون بتعيين نقاط التوقف استنادًا على الجهاز، بدلًا من المكان المناسب في المحتوى. لديهم تصميم بحجم iPad، وتصميم بحجم iPhone وما إلى ذلك. ولكن في الحقيقة، الأحجام متنوعة بشكل كبير، ونحتاج إلى التوقف عن التفكير في أجهزة معينة. هل أداؤك صديقًا للهاتف؟ الأداء هو ما يجب أن نفكر به. في الواقع، ربما تكون هذه هي المنطقة الأكبر الوحيدة حيث يخذلنا "التصميم المتجاوب". لا تسيئ فهمي أنا لا أقول أن "التصميم المتجاوب" يجعل مواقعنا أبطأ. إنه فقط لا يفعل شيئًا لتحسينه وهو شيء تحتاجه الأجهزة المحمولة. حجم الصورة بالتأكيد هو السبب. قد يؤدي استخدام "استعلامات الوسائط media queries" إلى تصغير الصورة بصريًا، لكنه لن يؤثر في تقليل حجم الصورة أو أوقات التحميل. هذا ليس جيدًا عند استخدام شبكة خلوية. أضف إلى ذلك الخطوط والمكتبات والأطر وجميع العناصر الأخرى التي تنتفخ في مواقع الويب الحالية ولديك أوقات تحميل سيئة. يمكن أن يؤدي اختبار أداء موقعك على الأجهزة المحمولة إلى قراءة محبطة! لكن هذه ليست مجرد مشكلة في حجم التنزيل وسرعة الشبكة الخلوية. الأداء هو مشكلة في الجهاز أيضًا. تفتقر العديد من الأجهزة المحمولة إلى قدرة معالجة أجهزة الحاسوب المحمولة أو أجهزة الحاسوب المكتبية أو الأجهزة اللوحية. والنتيجة هي أنها لا تستطيع التعامل مع قالب مُكثف من كود الجافاسكريبت "intensive javascript" موجود على العديد من المواقع الحديثة. هل ملء البيانات سهلًا على الهواتف؟ بعد ذلك، نأتي إلى مجموعة معينة من الأخطاء تحدث عندما نقوم بإدخال البيانات على الأجهزة المحمولة. فهل لك أن تتخيل كيف يمكن لأحد إدخال كم كبير من البيانات عبر لوحة مفاتيح هاتفه الصغير خلال فترة زمنية قصيرة؟ الأمر مريع حقًا، خصوصًا إن كان المستخدم مسنًا. كمصممي ويب، يبدو أننا نجعل المشكلة أسوأ بمئة مرة على مواقع الويب. عند إدخال بيانات رقمية، نفشل في عرض لوحة مفاتيح رقمية. عند إدخال كلمات المرور، نخفي ما يكتبه المستخدم، على الرغم من حقيقة أن الأخطاء المطبعية أمرًا شائعًا على الأجهزة المحمولة. في الواقع، لا ينبغي لنا أن نتوقع من مستخدمي الجوال ملء كلمات المرور على الإطلاق. هناك العديد من البدائل الأخرى مثل الإشعارات النصية أو روابط البريد الإلكتروني أو "معرف اللمس Touch ID". هناك العديد من المواقف التي يمكن فيها تجنب إدخال البيانات أو تبسيطها. سيكون تذكر اسم المستخدم لتسجيل الدخول بين الجلسات بداية جيدة. ولكن يجب علينا أيضًا تحسين تصميم النموذج وتجنب عناصر الأشكال المملوءة مثل منتقي التواريخ أو القوائم المنسدلة الطويلة. هل الروابط معبأة بإحكام أيضًا؟ عند الحديث عن التفاعلات المزعجة، أشعر بالدهشة إزاء قلة الاهتمام الذي يبديه المصممون للتحديات المحيطة باستخدام شاشة تعمل باللمس. أجد أن العديد من مواقع الويب التي تدّعي أنها صديقة للهاتف، ليست كذلك عندما تبدأ بالتفاعل معها. غالبًا ما يتم تجميع الارتباطات والأزرار معًا لزيادة حجم الشاشة إلى الحد الأقصى، بحيث يصبح من المستحيل النقر عليها. مرةً أخرى، مجرد تغيير موضع المحتوى لا يكفي. نحتاج إلى ضمان أن المساحة حول العناصر تتّسع للسماح بنقص الدقة الذي يحدث جرّاء استخدام الإصبع. من المسلَّم به أن المساحة هي ميزة استثنائية، ولكن إذا استخدمنا المساحة المتوفرة لدينا بحكمة، فلا يوجد سبب يمنع اختيار الروابط بسهولة أكبر. فقط إلقِ نظرة على متوسط التطبيق المحمول الخاص بك. هل يتعين على المستخدمين تحمُّل محتوى ثابت الموضع؟ بالحديث عن قلة المساحة، لماذا على الرغم من رغبة جميع المصممين في إنشاء موقع ويب سهل الاستخدام للهاتف المحمول، فإننا نعتبر أنه من المقبول إضافة محتوى موقع ثابت إلى الصفحة. هذا يقلل بشكل كبير من المساحة المتاحة لعناصر المحتوى الأخرى. فكر بتمعُّن قبل نقل تنقلاتك الثابتة إلى منظور موقعك الإليكتروني على الهاتف. بالمثل، قم بإبعاد تلك الطبقات ونوافذ الحوار، وكذلك رموز مواقع التواصل الاجتماعي الثابتة. ليس لديهم مكان على موقع إليكتروني على الهاتف. يكاد يكون من المستحيل قراءة المحتوى الموجود على Mashable على جهاز محمول نظرًا لحجم عناصر الموضع الثابت. ما المناسب لك؟ كلٌ من الخيارات الأربعة التي تمت مناقشتها في هذا المنشور له مكانه المناسب الذي سيختلف بناءً على الاحتياجات الخاصة بك. ومع ذلك، فإن نقطة الانطلاق الجيدة هي السؤال عمّا إذا كان المستخدمون يقومون في المقام الأول بإكمال مهمة أو الوصول إلى المعلومات. إذا كان الوصول إلى المعلومات هو الهدف، فمن المؤكّد أنّ المواقع المُتجاوبة هي الحل الأمثل. أمّا اذا كان "إكمال مهمة" هو الهدف، فعليك أن تسأل عمّا إذا كانت السرعة أم الوصول إلى الميزات الأصلية أيهما أهم. إذا كان الأمر كذلك، فستحتاج إلى تطبيق أصلي أو مُهجّن، وإلاّ سيكون تطبيق الويب مثاليًا. ترجمة -وبتصرف- للمقالين Is your site as mobile friendly as you think و Mobile app vs mobile website design: Your four options لصاحبهما Paul Boag
  7. سنتعرف في هذا المقال على إيجابيات وسلبيات تخزين البيانات في Go باستخدام المصفوفات Arrays والشرائح Slices ولماذا يكون أحدها أفضل من الآخر عادةً. هذا المقال جزء من سلسلة Go التي كتبها Mihalis Tsoukalos: الجزء 1: إنشاء كلمات مرور عشوائية وآمنة في Go الجزء 2: بناء خادم TCP متزامن في لغة البرمجة Go. الجزء 3: 3 طرائق لنسخ الملفات في لغة البرمجة Go. المصفوفات Arrays تعد المصفوفات واحدةً من أكثر هياكل البيانات شيوعًا بين لغات البرمجة لسببين رئيسيين: إنّها بسيطة وسهلة الفهم، ويمكنها تخزين أنواع مختلفة من البيانات فيها. يمكنك تعريف مصفوفة Array في لغة البرمجة Go، تحت اسم anArray مثلًا والتي تُخزِّن أربعة أعداد صحيحة كما يلي: anArray := [4]int{-1, 2, 0, -4} يُحدَّد حجم المصفوفة Array Size أولًا، ثم نوعها Array Type، وأخيرًا عناصرها Array Elements. تُساعدك الدّالة ()len في معرفة طول المصفوفة فحجم المصفوفة السابقة هو 4. إذا كنت على درايةٍ بلغات برمجة أخرى، فقد حاولت الوصول لجميع عناصر المصفوفة باستخدام حلقة for. ومع ذلك، كما سترى لاحقًا، أنّ الكلمة المفتاحية range الخاصة بلغة Go تُتيح لك الوصول لجميع عناصر المصفوفة أو الشريحة بسلاسة. وأخيرًا، إليك كيفية تحديد مصفوفة ذات بُعدين two dimentional array: twoD := [3][3]int{ {1, 2, 3}, {6, 7, 8}, {10, 11, 12}} الملف المصدر arrays.go يُوضِّح كيفية استخدام مصفوفات Go، ها هو الكود الأكثر أهمية في ملف arrays.go: for i := 0; i < len(twoD); i++ { k := twoD[i] for j := 0; j < len(k); j++ { fmt.Print(k[j], " ") } fmt.Println() } for _, a := range twoD { for _, j := range a { fmt.Print(j, " ") } fmt.Println() } يوضح هذا كيف يُمكنك المرور على عناصر المصفوفة باستخدام for loop والكلمة المُفتاحية range. توضّح باقي الكود الخاص بالملف arrays.go كيفية تمرير المصفوفة كمعامل دالّة. فيما يلي هو ناتج arrays.go: $ go run arrays.go Before change(): [-1 2 0 -4] After change(): [-1 2 0 -4] 1 2 3 6 7 8 10 11 12 1 2 3 6 7 8 10 11 12 يوضح هذا الناتج أنّ التغييرات التي تُجريها على مصفوفة داخل دالة تُفقَد بعد إنتهاء الدالة. عيوب ومساوئ المصفوفات arrays لدى مصفوفات Go العديد من المساوئ التي لابد أن تأخذها بعين الإعتبار حينما تستخدمها في مشاريع Go. أولًا، لا يُمكنك تغيير حجم المصفوفة بعد تعريفها، وهذا يعني أنّ مصفوفات Go ليست ديناميكية. بعبارة أبسط، إذا كنت بحاجة إلى إضافة عنصر إلى مصفوفة مُمتلئة، ستحتاج إلى إنشاء مصفوفة أكبر ونسخ جميع عناصر المصفوفة القديمة إلى الجديدة. ثانيًا، عندما تقوم بتمرير مصفوفة إلى دالة كمعامل لهذه الدالة، فإنّك في الواقع تُمرر نسخة من المصفوفة، مما يعني أن أي تغييرات تُجريها على المصفوفة داخل الدالة ستُفقد بعد إنتهاء هذه الدالة. أخيرًا، يمكن أن يكون تمرير مصفوفة كبيرة إلى دالة بطيئًا جدًا، خاصة وأن Go يجب أن تنشئ نسخة من هذه المصفوفة. الحل لجميع هذه المشاكل هو استخدام الشرائح Slices التي توفرها Go. الشرائح Slices تشبه شرائح Go مصفوفات Go لكن بدون أوجه القصور. أولاً، يمكنك إضافة عنصر إلى شريحة موجودة باستخدام الدالة ()append، علاوةً على ذلك، تم تنفيذ شرائح Go داخليًا باستخدام المصفوفات، مما يعني أن Go تستخدم مصفوفةً أساسيًة لكل شريحة. الشرائح لها خاصية سعة وخاصية طول، وهما ليستا نفس الشيء دائمًا. طول الشريحة هو نفس طول المصفوفة التي تحتوي على نفس عدد العناصر، ويمكن معرفتها باستخدام الدالة ()len. أمّا سعة الشريحة فهي الغرفة التي تم تخصيصها حاليًا للشريحة، ويمكن معرفتها باستخدام الدالة ()cap. نظرًا لأن الشرائح ديناميكية الحجم، ففي حالة نفاذ شريحة ما (مما يعني أن الطول الحالي للمصفوفة هو نفس السعة أثناء محاولة إضافة عنصر آخر إليها)، يعمل نظام Go تلقائيًا على مضاعفة قدرته الحالية على توفير مساحة لمزيد من العناصر ويضيف العنصر المطلوب إلى المصفوفة. بالإضافة إلى ذلك، يتم تمرير الشرائح للدوال حسب المرجع (Pass by reference)، مما يعني أن ما يتم نقله فعليًا إلى الدالة هو عنوان الذاكرة لمتغير الشريحة ، ولن تضيع أي تعديلات تجريها على الشريحة داخل إحدى الدوال بعد انتهائها. نتيجةً لذلك، فإن تمرير شريحة كبيرة إلى دالة يكون أسرع بكثير من تمرير مصفوفة بنفس عدد العناصر إلى نفس الدالة. وذلك لأن Go لن تضطر إلى عمل نسخة من الشريحة، إذ إنها ستُمرِّر فقط عنوان ذاكرة متغير الشريحة. يتم توضيح شرائح Go في ملف slice.go، والذي يحتوي على الكود التالي: package main import ( "fmt" ) func negative(x []int) { for i, k := range x { x[i] = -k } } func printSlice(x []int) { for _, number := range x { fmt.Printf("%d ", number) } fmt.Println() } func main() { s := []int{0, 14, 5, 0, 7, 19} printSlice(s) negative(s) printSlice(s) fmt.Printf("Before. Cap: %d, length: %d\n", cap(s), len(s)) s = append(s, -100) fmt.Printf("After. Cap: %d, length: %d\n", cap(s), len(s)) printSlice(s) anotherSlice := make([]int, 4) fmt.Printf("A new slice with 4 elements: ") printSlice(anotherSlice) } الفرق الأكبر بين تعريف الشريحة وتعريف المصفوفة هو أنّك لست بحاجة إلى تحديد حجم الشريحة، والذي يتم تحديده بعدد العناصر التي تريد وضعها فيها. بالإضافة إلى ذلك، تتيح لك الدالة ()append إضافة عنصر إلى شريحة موجودة؛ لاحظ أنه حتى إذا كانت سعة الشريحة تسمح لك بإضافة عنصر إلى هذه الشريحة فلن يتم تعديل طولها ما لم تستخدم ()append. الدالة ()printSlice هي دالة مُساعدة، تُستخدم لطباعة عناصر معامل الشريحة الخاص بها، في حين أنّ الدالة ()negative تقوم بعمل معالجة لعناصر مُعامل الشريحة الخاص بها. ها هو ناتج slice.go: $ go run slice.go 0 14 5 0 7 19 0 -14 -5 0 -7 -19 Before. Cap: 6, length: 6 After. Cap: 12, length: 7 0 -14 -5 0 -7 -19 -100 A new slice with 4 elements: 0 0 0 0 يُرجى ملاحظة أنه عند إنشاء شريحة جديدة وتخصيص مساحة ذاكرة لعدد معين من العناصر، فستعمل Go تلقائيًا على تهيئة جميع العناصر بقيمة الصفر من نوعها، والتي في هذه الحالة هي 0. الشرائح كمرجع للمصفوفات تُتيح لك Go الاشارة إلى مصفوفة موجودة بشريحة باستخدام الترميز [:]. في هذه الحالة، ستنعكس أي تغييرات تجريها على دالة شريحة إلى المصفوفة - وهذا موضّح في refArray.go. يرجى تذكُّر أن الترميز [:] لا يُنشئ نسخة من المصفوفة، بل فقط يُشير إليها. الجزء الأكثر إثارة للاهتمام في refArray.go هو: func main() { anArray := [5]int{-1, 2, -3, 4, -5} refAnArray := anArray[:] fmt.Println("Array:", anArray) printSlice(refAnArray) negative(refAnArray) fmt.Println("Array:", anArray) } ناتج refArray.go هو: $ go run refArray.go Array: [-1 2 -3 4 -5] -1 2 -3 4 -5 Array: [1 -2 3 -4 5] لذلك، تغيرت عناصر مجموعة anArray بسبب الإشارة إلى الشريحة. الملخص على الرغم من أن Go تدعم المصفوفات والشرائح، إلا أنه من الواضح إلى الآن أنك ستستخدم الشرائح على الأرجح لأنها أكثر تنوعًا وقوة من مصفوفات Go. لا يوجد سوى عدد قليل من الأحداث التي ستحتاج فيها إلى استخدام مصفوفة بدلاً من شريحة. الحدث الأكثر وضوحًا هو عندما تكون متأكدًا تمامًا من أنك ستحتاج إلى تخزين عدد محدد من العناصر. يمكنك العثور على كود Go الخاص بـ arrays.go و slice.go و refArray.go على GitHub. ترجمة -وبتصرف- للمقال An introduction to Go arrays and slices لصاحبه Mihalis Tsoukalos
  8. ستتعلم في هذا المقال ثلاث طرائق الأكثر شيوعًا لنسخ ملف عبر لغة البرمجة Go. هذا المقال جزء من سلسلة Go التي كتبها Mihalis Tsoukalos. اقرأ الجزء 1: إنشاء كلمات مرور عشوائية وآمنة في Go، والجزء 2: إنشاء خادم TCP متزامن في Go. بالرغم من وجود أكثر من ثلاث طرق لنسخ ملف في Go، يُقدم هذا المقال الطرق الثلاثة الأكثر شيوعًا: استخدام استدعاء دالة ()io.copy من مكتبة Go. قراءة ملف الإدخال مرة واحدة وكتابته إلى ملف آخر. نسخ الملف في قطع صغيرة باستخدام مخزن مؤقت. الطريقة الأولى: استخدام ()io.copy الإصدار الأول من هذه الطريقة يستخدم الدالة ()io.copy من مكتبة Go. يمكنك العثور على منطق هذه الطريقة من خلال الإطّلاع على كود الدالة ()copy، وهي كما يلي: func copy(src, dst string) (int64, error) { sourceFileStat, err := os.Stat(src) if err != nil { return 0, err } if !sourceFileStat.Mode().IsRegular() { return 0, fmt.Errorf("%s is not a regular file", src) } source, err := os.Open(src) if err != nil { return 0, err } defer source.Close() destination, err := os.Create(dst) if err != nil { return 0, err } defer destination.Close() nBytes, err := io.Copy(destination, source) return nBytes, err } بصرف النظر عن اختبار ما إذا كان الملف الذي سيتم نسخه موجودًا (عبر (os.Stat (src)، وهو ملف عادي (يمكن التأكد عبر sourceFileStat.Mode().IsRegular()‎)، حتى تتمكن من فتحه للقراءة، يتم تنفيذ كل العمل بواسطة الجملة التعريفية (io.copy(destination, Source السابقة. تُرجِع الدالة ()io.copy عدد البايتات المنسوخة ورسالة الخطأ الأولى التي حدثت أثناء عملية النسخ. في Go، إذا لم تكن هناك رسالة خطأ، فستكون قيمة متغير الخطأ صفر. يُمكنك معرفة المزيد عن ()io.copy بالإطّلاع على صفحة التوثيق الخاصة بـالـ io Package. سيؤدي تنفيذ cp1.go إلى إنشاء النوع التالي من النواتج: $ go run cp1.go Please provide two command line arguments! $ go run cp1.go fileCP.txt /tmp/fileCPCOPY Copied 3826 bytes! $ diff fileCP.txt /tmp/fileCPCOPY هذه التقنية بسيطة بقدر الإمكان ولكنها لا تعطي أي مرونة للمطور، وهذا ليس شيئًا سيئًا دائمًا. ومع ذلك، هناك أوقات يحتاج فيها المطور أو يريد أن يقرر كيف يريد قراءة الملف. الطريقة الثانية: استخدام ()ioutil.WriteFile و ()ioutil.ReadFile الطريقة الثانية لنسخ ملف هي استخدام الدوال ()ioutil.ReadFile و ()ioutil.WriteFile. تقرأ الدالة الأولى محتويات ملف بأكمله في شريحة بايت، بينما تقوم الدالة الثانية بكتابة محتويات شريحة بايت في ملف. يمكنك العثور على منطق هذه الطريقة في كود Go التالي: input, err := ioutil.ReadFile(sourceFile) if err != nil { fmt.Println(err) return } err = ioutil.WriteFile(destinationFile, input, 0644) if err != nil { fmt.Println("Error creating", destinationFile) fmt.Println(err) return } بصرف النظر عن كتلتي if، التي تُعد جزءًا من طريقة عمل Go، تستطيع أن ترى أنّ الأداء الوظيفي للبرنامج موجود في كلٍ من ()ioutil.ReadFile و ()ioutil.WriteFile. سيؤدي تنفيذ cp2.go إلى إنشاء نوع الناتج التالي: $ go run cp2.go Please provide two command line arguments! $ go run cp2.go fileCP.txt /tmp/copyFileCP $ diff fileCP.txt /tmp/copyFileCP يُرجى ملاحظة أنه على الرغم من أن هذه التقنية ستنسخ ملفًا، فقد لا تكون فعّالة عندما تريد نسخ ملفات ضخمة لأن شريحة البايت التي يتم إرجاعها بواسطة ()ioutil.ReadFile ستكون كبيرة هي الأخرى. الطريقة الثالثة: استخدام ()os.read و ()os.write الطريقة الثالثة لنسخ ملفات في Go، هي استخدام وسيلة cp3.go المُطوّرة في هذا القسم. تأخذ ثلاث معاملات: اسم ملف الإدخال. اسم ملف الإخراج. حجم المخزن المؤقت. يتواجد الجزء الأكثر أهمية من cp3.go في الحلقة for التالية، والذي يمكن العثور عليه في الدالة ()copy: buf := make([]byte, BUFFERSIZE) for { n, err := source.Read(buf) if err != nil && err != io.EOF { return err } if n == 0 { break } if _, err := destination.Write(buf[:n]); err != nil { return err } } تستخدم هذه التقنية الدالة ()os.read لقراءة أجزاء صغيرة من ملف الإدخال في المخزن المؤقت المُسمى buf. وتستخدم الدالة ()os.write لكتابة محتويات هذا المخزن المؤقت إلى ملف. تتوقف عملية النسخ عندما يكون هناك خطأ في القراءة أو عند الوصول إلى نهاية الملف (io.EOF). سيؤدي تنفيذ cp3.go إلى إنشاء نوع الناتج التالي: $ go run cp3.go usage: cp3 source destination BUFFERSIZE $ go run cp3.go fileCP.txt /tmp/buf10 10 Copying fileCP.txt to /tmp/buf10 $ go run cp3.go fileCP.txt /tmp/buf20 20 Copying fileCP.txt to /tmp/buf20 كما سترى، يؤثر حجم المخزن المؤقت بشكل كبير على أداء cp3.go. بعض من التقييم بهدف الموازنة سيحاول الجزء الأخير من هذ المقال موازنة البرامج الثلاثة بالإضافة إلى أداء cp3.go لمختلف أحجام المخزن المؤقت باستخدام الأداة المساعدة لسطر الأوامر (time (1. يُظهر الناتج التالي أداء cp1.go و cp2.go و cp3.go عند نسخ ملف بحجم 500 ميجابايت: $ ls -l INPUT -rw-r--r-- 1 mtsouk staff 512000000 Jun 5 09:39 INPUT $ time go run cp1.go INPUT /tmp/cp1 Copied 512000000 bytes! real 0m0.980s user 0m0.219s sys 0m0.719s $ time go run cp2.go INPUT /tmp/cp2 real 0m1.139s user 0m0.196s sys 0m0.654s $ time go run cp3.go INPUT /tmp/cp3 1000000 Copying INPUT to /tmp/cp3 real 0m1.025s user 0m0.195s sys 0m0.486s يُظهر الناتج أنّ أداء الأدوات المساعدة الثلاثة متشابه إلى حدٍ ما، مما يعني أن دوال مكتبة Go القياسية ذكية ومُحسَّنة تمامًا. الآن، دعنا نختبر كيف يؤثر حجم المخزن المؤقت على أداء cp3.go. سيؤدي تنفيذ cp3.go بحجم المخزن المؤقت 10 و 20 و 1000 بايت لنسخ ملف 500 ميجابايت على جهاز سريع إلى حد كبير لاستخراج النتائج التالية: $ ls -l INPUT -rw-r--r-- 1 mtsouk staff 512000000 Jun 5 09:39 INPUT $ time go run cp3.go INPUT /tmp/buf10 10 Copying INPUT to /tmp/buf10 real 6m39.721s user 1m18.457s sys 5m19.186s $ time go run cp3.go INPUT /tmp/buf20 20 Copying INPUT to /tmp/buf20 real 3m20.819s user 0m39.444s sys 2m40.380s $ time go run cp3.go INPUT /tmp/buf1000 1000 Copying INPUT to /tmp/buf1000 real 0m4.916s user 0m1.001s sys 0m3.986s يُظهر الناتج الذي تم إنشاؤه أنه كلما زاد حجم المخزن المؤقت، كلما كان أداء cp3.go أسرع، وهو أمر مُتوَقع إلى حدٍ ما. علاوةً على ذلك، يُعد استخدام أحجام المخازن المؤقتة أصغر من 20 بايت لنسخ الملفات الكبيرة عملية بطيئة للغاية ويجب تجنُّبها. يمكنك العثور على كود Go الخاص بـ cp1.go و cp2.go و cp3.go على Github. ترجمة وبتصرّف للمقال 3 ways to copy files in Go، لصاحبه Mihalis Tsoukalos.
  9. سنعمل في هذا المقال على تطوير خادم TCP متزامن يقوم بإنشاء أرقام عشوائية باستخدام حوالي 65 سطرًا من كود Go، إذ سأشرح كيفية تطوير خادم TCP متزامن، بلغة البرمجة Go، والتي تقوم بإرجاع أرقام عشوائية. إن لم تقرأ المقال السابق حول إنشاء كلمات مرور عشوائية وآمنة في Go، فننصحك بالرجوع إليه وقراءته أولًا. تعمل خوادم TCP و UDP بخدمة عملاء الشبكة في كل مكان عبر شبكات TCP / IP. لكل اتصال وارد من عميل TCP، سيقوم خادم TCP ببدء تشغيل goroutine جديد لمعالجة هذا الطلب. تستطيع إيجاد هذا المشروع concTCP.go على GitHub. التعامل مع اتصالات TCP يمكنك العثور على منطق البرنامج في دالة ()handleConnection بلغة Go، والذي يتم تنفيذه على النحو التالي: func handleConnection(c net.Conn) { fmt.Printf("Serving %s\n", c.RemoteAddr().String()) for { netData, err := bufio.NewReader(c).ReadString('\n') if err != nil { fmt.Println(err) return } temp := strings.TrimSpace(string(netData)) if temp == "STOP" { break } result := strconv.Itoa(random()) + "\n" c.Write([]byte(string(result))) } c.Close() } إذا أرسل عميل TCP سلسلة التعليمات "STOP"، فسيتم إنهاء برنامج Go أي goroutine الذي يخدم عميل TCP محدد؛ وإلا، سيرسل خادم TCP رقمًا عشوائيًا إلى عميل TCP. تضمن الحلقة for أن عميل TCP سيتم خدمته طالما يُتَطلب ذلك. تقرأ الحلقة for الموجودة في كود Go البيانات من عميل TCP سطرًا بسطر باستخدام ('bufio.NewReader(c).ReadString('\n وتُعيد إرسال البيانات باستخدام (((c.Write([]byte(string(result. التزامن تنفيذ دالة ()main، يُعطي أمرًا لخادم TCP لبدء تشغيل برنامج goroutine جديد في كل مرة يتعين عليه خدمة عميل TCP: func main() { arguments := os.Args if len(arguments) == 1 { fmt.Println("Please provide a port number!") return } PORT := ":" + arguments[1] l, err := net.Listen("tcp4", PORT) if err != nil { fmt.Println(err) return } defer l.Close() rand.Seed(time.Now().Unix()) for { c, err := l.Accept() if err != nil { fmt.Println(err) return } go handleConnection(c) } } أولاً، تتأكد ()main من أن البرنامج يحتوي على وسيطة سطر أوامر واحدة على الأقل. لاحظ أن الكود الموجود لا يتحقق مما إذا كانت وسيطة سطر الأوامر المحددة هي رقم مَنفذ TCP صالح أم لا. ومع ذلك، إذا لم تكن القيمة المحددة رقم منفذ TCP صالحًا، فسوف يفشل استدعاء ()net.Listen مع ظهور رسالة خطأ مشابهة لما يلي: $ go run concTCP.go 12a listen tcp4: lookup tcp4/12a: nodename nor servname provided, or not known $ go run concTCP.go -10 listen tcp4: address -10: invalid port يتم استخدام استدعاء ()net.Listen لإخبار برنامج Go بقبول اتصالات الشبكة وبالتالي كخادم. قيمة الإرجاع ()net.Listen هي من النوع net.Conn، والتي تنفذ واجهات io.Reader و io.Writer. تقوم الدالة ()main أيضًا باستدعاء الدالة ()rand.seed لتهيئة مُنشئ الأرقام العشوائية. أخيرًا، تُتيح حلقة for للبرنامج الحفاظ على قبول عملاء TCP الجدد باستخدام ()accept والتي سيتم معالجتها بواسطة نسخ الدالة ()handleConnection، والتي يتم تنفيذها على شكل goroutines. أول معامل للدالة ()net.Listen يُحدد المعامل الأول من الدالة ()net.Listen نوع الشبكة التي سيتم استخدامها، بينما يُحدد المعامل الثاني عنوان الخادم بالإضافة إلى رقم المَنفذ الذي سيستمع إليه الخادم. القيم الصالحة للمُعامل الأول هي: tcp, tcp4 (IPv4-only), tcp6 (IPv6-only), udp, udp4 (IPv4- only), udp6 (IPv6-only), ip, ip4 (IPv4-only), ip6 (IPv6-only), Unix (Unix sockets), Unixgram, Unixpacket فعالية خادم TCP المتزامن يتطلب concTCP.go وسيطة سطر أوامر واحدة، وهي رقم المَنفذ الذي سيستمع إليه. سيكون الناتج الذي ستحصل عليه من concTCP.go عند خدمة عملاء TCP مشابهًا لما يلي: $ go run concTCP.go 8001 Serving 127.0.0.1:62554 Serving 127.0.0.1:62556 يمكن لناتج (1)netStat التحقق من أن concTCP.go يخدم العديد من عملاء TCP أثناء الاستماع لمزيد من الاتصالات: $ netstat -anp TCP | grep 8001 tcp4 0 0 127.0.0.1.8001 127.0.0.1.62556 ESTABLISHED tcp4 0 0 127.0.0.1.62556 127.0.0.1.8001 ESTABLISHED tcp4 0 0 127.0.0.1.8001 127.0.0.1.62554 ESTABLISHED tcp4 0 0 127.0.0.1.62554 127.0.0.1.8001 ESTABLISHED tcp4 0 0 *.8001 *.* LISTEN يعلمنا السطر الأخير من ناتج الأمر السابق أن هناك عملية تستمع إلى المَنفذ 8001، مما يعني أنه لا يزال بإمكانك الاتصال بمنفذ TCP رقم 8001. يتحقق أول سطرين من وجود اتصال شبكة TCP ثابت يستخدم أرقام المنافذ 8001 و 62556. وبالمثل، يتحقق السطران الثالث والرابع من وجود اتصال TCP آخر يستخدم أرقام المنافذ 8001 و 62554. تُظهر هذه الصورة ناتج concTCP.go عند خدمة العديد من عملاء TCP: بشكل مشابه، تُظهر الصورة التالية الناتج من عميلين من TCP يتم تنفيذهما باستخدام (1)nc: يُمكنك ايجاد معلومات أكثر عن (1)nc، والتي تُدعى أيضًا (1)netcat على ويكيبيديا. الملخص لقد تعلمت للتو كيفية تطوير خادم TCP متزامن يقوم بإنشاء أرقام عشوائية باستخدام حوالي 65 سطرًا من كود Go، وهو أمر مثير للإعجاب جدًا! إذا كنت تريد أن يقوم خادم TCP بمهمة مختلفة، فقط قم بتغيير تنفيذ الدالة ()handleConnection. ترجمة وبتصرّف للمقال Build a concurrent TCP server in Go، لصاحبه Mihalis Tsoukalos.
  10. يعد مولد الأرقام العشوائية لـ Go طريقة رائعة لإنشاء كلمات مرور يصعب تخمينها. يمكنك استخدام مولد الأرقام العشوائية الذي توفره لغة البرمجة Go لإنشاء كلمات مرور يصعب تخمينها تتكون من رموز ASCII. على الرغم من أن الكود المقدَّم في هذا المقال سهل القراءة، إلا أنّه يُفضَّل أن تكون على معرفةٍ سابقة بأساسيات Go لفهمها. إذا كنت حديث العهد بلغات البرمجة، فاختر مدخل إلى لغة البرمجة Go لمعرفة المزيد حول Go، ثم عد إلى هنا. قبل التعمُّق في الأدوات المساعدة والكود الخاص بـ Go، ألقِ نظرة على هذه المجموعة الفرعية من جدول ASCII كما هو موجود في ناتج الأمر man ascii: 30 40 50 60 70 80 90 100 110 120 --------------------------------- 0: ( 2 < F P Z d n x 1: ) 3 = G Q [ e o y 2: * 4 > H R \ f p z 3: ! + 5 ? I S ] g q { 4: " , 6 @ J T ^ h r | 5: # - 7 A K U _ i s } 6: $ . 8 B L V ` j t ~ 7: % / 9 C M W a k u DEL 8: & 0 : D N X b l v 9: ' 1 ; E O Y c m w القيم العشرية لرموز ASCII تتراوح من 33 إلى 126؛ لا توجد قيم ASCII أخرى مناسبة لتتواجد ضمن كلمات المرور. لذلك، سوف تنتج الأدوات المساعدة المقدَّمة في هذا المقال أحرف ASCII في هذا النطاق. خلق أعداد صحيحة عشوائية الأداة المساعدة الأولى تُدعى random.go، وتقوم بإنشاء عدد محدد من الأعداد الصحيحة العشوائية ضمن نطاق معين. الجزء الأكثر أهمية في random.go هو هذه الدالة: func random(min, max int) int { return rand.Intn(max-min) + min } تقوم هذه الدالّة بإنشاء أعداد صحيحة عشوائية تنتمي إلى نطاق معين باستخدام ()rand.intn. لاحظ أنّ rand.intn()‎ تقوم بإرجاع رقم عشوائي غير سالب ينتمي إلى المجال [0,n)، إذ n هو العدد المُمرَّر إلى الدالة. ستخرب الدالة إذا كان مُعاملها عددًا سالبًا وستكون رسالة الخطأ كما يلي: panic: invalid argument to Intn يُمكنك إيجاد توثيق المجموعة math/rand في هذا الرابط: math/rand Documentation. تقبل الدالة random.go ثلاثًا من عوامل سطر الأوامر (Command Line Parameters): الحد الأدنى لقيمة الأعداد الصحيحة المراد توليدها. والقيمة القصوى. وعدد الأعداد الصحيحة التي سيتم توليدها. تجميع وتنفيذ random.go سيخلق هذا النوع من الناتج: $ go build random.go $ ./random Usage: ./random MIX MAX TOTAL $ ./random 1 3 10 2 2 1 2 2 1 1 2 2 1 إذا كنت ترغب في إنشاء أرقام عشوائية أكثر أمانًا في Go، فاستخدم حزمة crypto / rand من مكتبة Go. إنشاء كلمات مرور عشوائية الأداة المساعدة الثانية randomPass.go، تنشئ كلمات مرور عشوائية. يستخدم randomPass.go الدالة ()random لإنشاء أرقام عشوائية سيتم تحويلها إلى رموز ASCII باستخدام كود Go التالي: for { myRand := random(MIN, MAX) newChar := string(startChar[0] + byte(myRand)) fmt.Print(newChar) if i == LENGTH { break } i++ } قيمة MIN هي 0 وقيمة MAX هي 94، في حين أن قيمة startChar هي !، وهو أول حرف قابل للطباعة في جدول ASCII (برمز ASCII العشري وهو 33). لذلك، توجد جميع أحرف ASCII التي سيتم إنشاؤها بعد ! وقبل الحرف ~، الذي يحتوي على رمز ASCII العشري لـلرقم 126. لذلك، كل رقم عشوائي يتم إنشاؤه أكبر من MIN، وأصغر من MAX، ويتم تحويله إلى رمز ASCII. تستمر العملية حتى تصبح كلمة المرور التي تم إنشاؤها لها بالطول المطلوب. تقبل randomPass.go معامل سطر أوامر واحد (اختياري) يُحدد طول كلمة المرور التي تم إنشاؤها. القيمة الافتراضية هي ثمانية، وهو طول كلمة مرور شائع جدًا. سيؤدي تنفيذ randomPass.go إلى الناتج التالي: $ go run randomPass.go 1 Z $ go run randomPass.go 10 #Cw^a#IwkT $ go run randomPass.go Using default values! [PP8@'Ci تفصيل أخير: لا تنس استعمال ()rand.seed مع قيمة أولية لتهيئة مولد الأرقام العشوائية. إذا استخدمت نفس القيمة الأولية في كل وقت، فسيقوم مُولِّد الأرقام العشوائية بإنشاء نفس تسلسل الأعداد الصحيحة العشوائية. تستطيع إيجاد كلًا من random.go و randomPass.go على GitHub. وتستطيع أيضًا تنفيذ هذه الدوال على play.golang.org ترجمة -وبتصرف- للمقال Creating random, secure passwords in Go لصاحبه Mihalis Tsoukalos.
×
×
  • أضف...