دليل Kubernetes الشامل: تعلم Kubernetes للمبتدئين

دقائق القراءة: 65

يُعد Kubernetes، الذي يُعرف اختصارًا بـ K8s، منصة مفتوحة المصدر لتنسيق الحاويات (container orchestration) تُعنى بأتمتة عمليات نشر التطبيقات وإدارتها وتوسيع نطاقها وتوفير الشبكات اللازمة لها. طوّرته شركة Google باستخدام لغة البرمجة Go، وأصبح متاحًا كمشروع مفتوح المصدر منذ عام 2014. وفقًا لاستبيان مطوري Stack Overflow لعام 2020، يحتل Kubernetes المرتبة الثالثة كأكثر المنصات المحبوبة والمرغوبة.

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

  • فهم قوي للأساسيات.
  • إنشاء وإدارة مجموعات Kubernetes (clusters).
  • نشر (تقريبًا) أي تطبيق على مجموعة Kubernetes.

المتطلبات الأساسية

  • الإلمام بـ JavaScript
  • الإلمام بـ Linux Terminal
  • الإلمام بـ Docker (قراءة مقترحة: دليل Docker الشامل)

كود المشروع

يمكن العثور على الكود الخاص بالمشاريع التجريبية في المستودع التالي:

https://github.com/fhsinchy/kubernetes-handbook-projects

يمكنك العثور على الكود الكامل في فرع completed.

جدول المحتويات

  • مقدمة إلى تنسيق الحاويات و Kubernetes
  • تثبيت Kubernetes
  • تطبيق Hello World في Kubernetes
  • هندسة Kubernetes المعمارية
    • مكونات مستوى التحكم (Control Plane)
    • مكونات العقدة (Node)
  • كائنات Kubernetes
    • الـ Pods
    • الـ Services
  • الصورة الكاملة
  • التخلص من موارد Kubernetes
  • نهج النشر التصريحي (Declarative Deployment Approach)
    • كتابة أول مجموعة من التكوينات
  • لوحة تحكم Kubernetes (Dashboard)
  • العمل مع التطبيقات متعددة الحاويات
    • خطة النشر
    • وحدات التحكم بالنسخ المتماثلة (Replication Controllers) ومجموعات النسخ المتماثلة (Replica Sets) وعمليات النشر (Deployments)
    • إنشاء أول عملية نشر لك
    • فحص موارد Kubernetes
    • الحصول على سجلات الحاوية من الـ Pods
    • متغيرات البيئة
    • إنشاء نشر قاعدة البيانات
    • وحدات التخزين المستمرة (Persistent Volumes) ومطالبات وحدات التخزين المستمرة (Persistent Volume Claims)
    • التوفير الديناميكي لوحدات التخزين المستمرة
    • ربط وحدات التخزين مع الـ Pods
    • ربط كل شيء معًا
  • العمل مع وحدات التحكم بالـ Ingress
    • إعداد وحدة التحكم بـ NGINX Ingress
  • الـ Secrets والـ Config Maps في Kubernetes
  • تنفيذ عمليات طرح التحديثات في Kubernetes
  • دمج التكوينات
  • استكشاف الأخطاء وإصلاحها
  • الخلاصة

مقدمة إلى تنسيق الحاويات و Kubernetes

وفقًا لـ Red Hat، «تنسيق الحاويات هو عملية أتمتة مهام نشر الحاويات وإدارتها وتوسيع نطاقها وتوفير الشبكات اللازمة لها. يمكن استخدامه في أي بيئة تستخدم فيها الحاويات، ويمكن أن يساعدك في نشر نفس التطبيق عبر بيئات مختلفة دون الحاجة إلى أي إعادة تصميم.»

دعني أوضح لك بمثال. لنفترض أنك طوّرت تطبيقًا رائعًا يقترح على الأشخاص ما يجب عليهم تناوله بناءً على وقت اليوم. الآن، لنفترض أنك قمت بحوسبة التطبيق (containerized) باستخدام Docker ونشرته على AWS.

تطبيق اقتراح الطعام يعمل على مثيل واحد

إذا تعطل التطبيق لأي سبب، سيفقد المستخدمون الوصول إلى خدمتك فورًا. لحل هذه المشكلة، يمكنك إنشاء نسخ متعددة أو مثيلات (replicas) من نفس التطبيق لجعله عالي التوافر (highly available).

تطبيق اقتراح الطعام يعمل على مثيلات متعددة لتحقيق التوافر العالي

حتى لو تعطل أحد المثيلات، سيظل الاثنان الآخران متاحين للمستخدمين. الآن، لنفترض أن تطبيقك أصبح شائعًا بشكل كبير بين محبي السهر، وأن خوادمك تُغمر بالطلبات ليلًا بينما أنت نائم. ماذا لو تعطلت جميع المثيلات بسبب الحمل الزائد؟ من سيقوم بتوسيع النطاق (scaling)؟ حتى لو قمت بتوسيع النطاق وجعلت 50 نسخة من تطبيقك، من سيتولى فحص حالتها الصحية؟ كيف ستقوم بإعداد الشبكة بحيث تصل الطلبات إلى نقطة النهاية الصحيحة؟ موازنة التحميل (Load balancing) ستكون مصدر قلق كبير أيضًا، أليس كذلك؟

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

تمثيل لكيفية عمل Kubernetes في إدارة التطبيقات

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

تثبيت Kubernetes

تشغيل Kubernetes على جهازك المحلي يختلف كثيرًا عن تشغيله على السحابة. لتشغيل Kubernetes، تحتاج إلى برنامجين:

  • minikube: يقوم بتشغيل مجموعة Kubernetes ذات عقدة واحدة (single-node Kubernetes cluster) داخل جهاز افتراضي (VM) على جهاز الكمبيوتر المحلي الخاص بك.
  • kubectl: أداة سطر الأوامر (command-line tool) لـ Kubernetes، والتي تتيح لك تشغيل الأوامر ضد مجموعات Kubernetes.

بصرف النظر عن هذين البرنامجين، ستحتاج أيضًا إلى برنامج تشغيل افتراضي (hypervisor) ومنصة لحوسبة الحاويات (containerization platform). يُعد Docker الخيار الواضح لمنصة حوسبة الحاويات. برامج التشغيل الافتراضية الموصى بها هي كما يلي:

  • Hyper-V لنظام Windows
  • HyperKit لنظام Mac
  • Docker لنظام Linux

يأتي Hyper-V مدمجًا في Windows 10 (إصدارات Pro و Enterprise و Education) كميزة اختيارية ويمكن تشغيله من لوحة التحكم. يأتي HyperKit مدمجًا مع Docker Desktop لنظام Mac كمكون أساسي. وعلى نظام Linux، يمكنك تجاوز طبقة الـ hypervisor بالكامل باستخدام Docker مباشرة. إنها أسرع بكثير من استخدام أي hypervisor وهي الطريقة الموصى بها لتشغيل Kubernetes على Linux.

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

بمجرد تثبيت الـ hypervisor ومنصة حوسبة الحاويات، حان الوقت لتثبيت برنامجي minikube و kubectl. يأتي kubectl عادةً مجمعًا مع Docker Desktop على نظامي Mac و Windows. يمكن العثور على تعليمات التثبيت لنظام Linux هنا. أما minikube، فيجب تثبيته على جميع الأنظمة الثلاثة. يمكنك استخدام Homebrew على نظام Mac، و Chocolatey على نظام Windows لتثبيت minikube. يمكن العثور على تعليمات التثبيت لنظام Linux هنا.

بمجرد تثبيتهما، يمكنك اختبار كلا البرنامجين بتنفيذ الأوامر التالية:

minikube version
# minikube version: v1.12.1
# commit: 5664228288552de9f3a446ea4f51c6f29bbdd0e0
kubectl version
# Client Version: version.Info{Major:"1", Minor:"18", GitVersion:"v1.18.6", GitCommit:"dff82dc0de47299ab66c83c626e08b245ab19037", GitTreeState:"clean", BuildDate:"2020-07-16T00:04:31Z", GoVersion:"go1.14.4", Compiler:"gc", Platform:"darwin/amd64"}
# Server Version: version.Info{Major:"1", Minor:"18", GitVersion:"v1.18.3", GitCommit:"2e7996e3e2712684bc73f0dec0200d64eec7fe40", GitTreeState:"clean", BuildDate:"2020-05-20T12:43:34Z", GoVersion:"go1.13.9", Compiler:"gc", Platform:"linux/amd64"}

إذا قمت بتنزيل الإصدارات الصحيحة لنظام التشغيل الخاص بك وقمت بإعداد المسارات بشكل صحيح، فيجب أن تكون جاهزًا للبدء. كما ذكرت سابقًا، يقوم minikube بتشغيل مجموعة Kubernetes ذات عقدة واحدة داخل جهاز افتراضي (VM) على جهاز الكمبيوتر المحلي الخاص بك. سأشرح المجموعات (clusters) والعقد (nodes) بتفاصيل أكبر في قسم قادم. في الوقت الحالي، افهم أن minikube ينشئ جهازًا افتراضيًا عاديًا باستخدام مشغل الـ hypervisor الذي تختاره ويعامله كمجموعة Kubernetes.

إذا واجهت أي مشاكل في هذا القسم، يرجى إلقاء نظرة على قسم استكشاف الأخطاء وإصلاحها في نهاية هذا المقال. قبل البدء في تشغيل minikube، يجب عليك تعيين مشغل الـ hypervisor الصحيح الذي سيستخدمه. لتعيين VirtualBox كمشغل افتراضي، نفذ الأمر التالي:

minikube config set driver virtualbox
# ❗ These changes will take effect upon a minikube delete and then a minikube start

يمكنك استبدال virtualbox بـ hyperv أو hyperkit أو docker حسب تفضيلك. هذا الأمر ضروري في المرة الأولى فقط. لبدء تشغيل minikube، نفذ الأمر التالي:

minikube start
# ? minikube v1.12.1 on Ubuntu 20.04
# ✨ Using the virtualbox driver based on existing profile
# ? Starting control plane node minikube in cluster minikube
# ? Updating the running virtualbox "minikube" VM ...
# ? Preparing Kubernetes v1.18.3 on Docker 19.03.12 ...
# ? Verifying Kubernetes components...
# ? Enabled addons: default-storageclass, storage-provisioner
# ? Done! kubectl is now configured to use "minikube"

يمكنك إيقاف minikube بتنفيذ الأمر minikube stop.

تطبيق Hello World في Kubernetes

الآن بعد أن أصبح لديك Kubernetes على نظامك المحلي، حان الوقت لتطبيق ما تعلمته. في هذا المثال، ستقوم بنشر تطبيق بسيط جدًا على مجموعتك المحلية وتتعرف على الأساسيات. سيكون هناك مصطلحات مثل pod و service و load balancer وما إلى ذلك في هذا القسم. لا تقلق إذا لم تفهمها على الفور. سأشرح كل منها بالتفصيل في قسم الصورة الكاملة.

إذا كنت قد بدأت تشغيل minikube في القسم السابق، فأنت جاهز للمتابعة. وإلا، سيتعين عليك تشغيله الآن. بمجرد بدء تشغيل minikube، نفذ الأمر التالي في محطتك الطرفية (terminal):

kubectl run hello-kube --image=fhsinchy/hello-kube --port=80
# pod/hello-kube created

سترى رسالة pod/hello-kube created على الفور تقريبًا. يقوم الأمر run بتشغيل صورة الحاوية (container image) المحددة داخل pod. الـ Pods تشبه صندوقًا يغلف حاوية. للتأكد من إنشاء الـ pod وتشغيله، نفذ الأمر التالي:

kubectl get pod
# NAME READY STATUS RESTARTS AGE
# hello-kube 1/1 Running 0 3m3s

يجب أن ترى Running في عمود STATUS. إذا رأيت شيئًا مثل ContainerCreating، انتظر دقيقة أو دقيقتين وتحقق مرة أخرى. الـ Pods بشكل افتراضي غير قابلة للوصول من خارج المجموعة. لجعلها قابلة للوصول، يجب عليك كشفها (expose) باستخدام خدمة (service). لذا، بمجرد أن يكون الـ pod قيد التشغيل، نفذ الأمر التالي لكشف الـ pod:

kubectl expose pod hello-kube --type=LoadBalancer --port=80
# service/hello-kube exposed

للتأكد من إنشاء خدمة موازنة التحميل (load balancer service) بنجاح، نفذ الأمر التالي:

kubectl get service
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# hello-kube LoadBalancer 10.109.60.75 <pending> 80:30848/TCP 119s
# kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 7h47m

تأكد من رؤية خدمة hello-kube في القائمة. الآن بعد أن أصبح لديك pod قيد التشغيل ومكشوف، يمكنك المضي قدمًا والوصول إليه. نفذ الأمر التالي للقيام بذلك:

minikube service hello-kube
# |-----------|------------|-------------|-----------------------------|
# | NAMESPACE | NAME | TARGET PORT | URL |
# |-----------|------------|-------------|-----------------------------|
# | default | hello-kube | 80 | http://192.168.99.101:30848 |
# |-----------|------------|-------------|-----------------------------|
# ? Opening service default/hello-kube in default browser...

يجب أن يفتح متصفح الويب الافتراضي الخاص بك تلقائيًا ويجب أن ترى شيئًا كهذا:

تطبيق ويب بسيط يعرض رسالة Hello Kube

هذا تطبيق JavaScript بسيط جدًا قمت بإنشائه باستخدام Vite وقليل من CSS. لفهم ما قمت به للتو، يجب أن تكتسب فهمًا جيدًا لهندسة Kubernetes المعمارية.

هندسة Kubernetes المعمارية

في عالم Kubernetes، يمكن أن تكون العقدة (node) إما جهازًا ماديًا أو افتراضيًا له دور محدد. تُسمى مجموعة من هذه الأجهزة أو الخوادم التي تستخدم شبكة مشتركة للتواصل بين بعضها البعض بـ «المجموعة» (cluster).

مخطط يوضح مجموعة Kubernetes تتكون من عدة عقد

في إعدادك المحلي، minikube هو مجموعة Kubernetes ذات عقدة واحدة. لذا، بدلاً من وجود خوادم متعددة كما في الرسم البياني أعلاه، يحتوي minikube على خادم واحد فقط يعمل كخادم رئيسي (main server) وعقدة (node) في آن واحد.

مخطط يوضح minikube كمجموعة Kubernetes ذات عقدة واحدة

يُمنح كل خادم في مجموعة Kubernetes دورًا. هناك دوران محتملان:

  • control-plane (مستوى التحكم): يتخذ معظم القرارات الضرورية ويعمل كدماغ للمجموعة بأكملها. يمكن أن يكون هذا خادمًا واحدًا أو مجموعة من الخوادم في المشاريع الكبيرة.
  • node (العقدة): مسؤول عن تشغيل أعباء العمل (workloads). تتم إدارة هذه الخوادم عادةً بشكل دقيق بواسطة مستوى التحكم وتنفذ مهام مختلفة باتباع التعليمات المقدمة.

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

مكونات مستوى التحكم (Control Plane)

يتكون مستوى التحكم في مجموعة Kubernetes من خمسة مكونات. وهي كالتالي:

  • kube-api-server: يعمل كمدخل إلى مستوى التحكم في Kubernetes، وهو مسؤول عن التحقق من صحة ومعالجة الطلبات المقدمة باستخدام مكتبات العميل مثل برنامج kubectl.
  • etcd: هذا متجر قيم-مفتاح موزع (distributed key-value store) يعمل كمصدر وحيد للحقيقة حول مجموعتك. يحتفظ ببيانات التكوين ومعلومات حول حالة المجموعة. etcd هو مشروع مفتوح المصدر تم تطويره بواسطة فريق Red Hat. كود المشروع متاح على مستودع GitHub الخاص بـ etcd-io/etcd.
  • kube-controller-manager: وحدات التحكم (controllers) في Kubernetes مسؤولة عن التحكم في حالة المجموعة. عندما تُعلم Kubernetes بما تريده في مجموعتك، تتأكد وحدات التحكم من تلبية طلبك. kube-controller-manager هو جميع عمليات وحدات التحكم مجمعة في عملية واحدة.
  • kube-scheduler: يُعرف تعيين المهام لعقدة معينة مع الأخذ في الاعتبار مواردها المتاحة ومتطلبات المهمة بـ «الجدولة» (scheduling). يقوم مكون kube-scheduler بمهمة الجدولة في Kubernetes، مما يضمن عدم تحميل أي من الخوادم في المجموعة فوق طاقتها.
  • cloud-controller-manager: في بيئة سحابية حقيقية، يتيح لك هذا المكون ربط مجموعتك بواجهة برمجة تطبيقات (API) لمزود الخدمة السحابية الخاص بك (مثل GKE / EKS). بهذه الطريقة، تظل المكونات التي تتفاعل مع تلك المنصة السحابية معزولة عن المكونات التي تتفاعل فقط مع مجموعتك. في مجموعة محلية مثل minikube، لا يوجد هذا المكون.

مكونات العقدة (Node)

مقارنة بمستوى التحكم، تحتوي العقد على عدد قليل جدًا من المكونات. هذه المكونات هي كما يلي:

  • kubelet: تعمل هذه الخدمة كبوابة بين مستوى التحكم وكل عقدة في المجموعة. تمر جميع التعليمات من مستوى التحكم إلى العقد عبر هذه الخدمة. كما تتفاعل مع متجر etcd للحفاظ على معلومات الحالة محدثة.
  • kube-proxy: تعمل هذه الخدمة الصغيرة على كل خادم عقدة وتحافظ على قواعد الشبكة عليها. أي طلب شبكة يصل إلى خدمة داخل مجموعتك يمر عبر هذه الخدمة.
  • Container Runtime (بيئة تشغيل الحاويات): Kubernetes هي أداة لتنسيق الحاويات، وبالتالي فهي تشغل التطبيقات في حاويات. هذا يعني أن كل عقدة تحتاج إلى بيئة تشغيل حاويات مثل Docker أو rkt أو cri-o.

كائنات Kubernetes

وفقًا لوثائق Kubernetes الرسمية، «الكائنات هي كيانات دائمة في نظام Kubernetes. يستخدم Kubernetes هذه الكيانات لتمثيل حالة مجموعتك. على وجه التحديد، يمكنها وصف التطبيقات المحوسبة التي تعمل، والموارد المتاحة لها، والسياسات المحيطة بسلوكها.»

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

الـ Pods

وفقًا لوثائق Kubernetes الرسمية، «الـ Pods هي أصغر وحدات الحوسبة القابلة للنشر التي يمكنك إنشاؤها وإدارتها في Kubernetes.» يغلف الـ pod عادةً حاوية واحدة أو أكثر تكون مرتبطة ارتباطًا وثيقًا، وتتشارك دورة حياة وموارد قابلة للاستهلاك.

مخطط يوضح Pods تحتوي على حاوية واحدة أو أكثر

على الرغم من أن الـ pod يمكن أن يستضيف أكثر من حاوية واحدة، إلا أنه لا ينبغي عليك وضع الحاويات في الـ pod بشكل عشوائي. يجب أن تكون الحاويات في الـ pod مرتبطة ارتباطًا وثيقًا لدرجة أنها يمكن أن تُعامل كتطبيق واحد. على سبيل المثال، قد تعتمد واجهة برمجة التطبيقات الخلفية (back-end API) الخاصة بك على قاعدة البيانات، ولكن هذا لا يعني أنك ستضع كليهما في نفس الـ pod. طوال هذا المقال، لن ترى أي pod يحتوي على أكثر من حاوية واحدة تعمل. عادةً، لا يجب عليك إدارة الـ pod مباشرة. بدلاً من ذلك، يجب عليك العمل مع كائنات عالية المستوى يمكن أن توفر لك قابلية إدارة أفضل بكثير. ستتعرف على هذه الكائنات عالية المستوى في الأقسام اللاحقة.

الـ Services

وفقًا لوثائق Kubernetes الرسمية، «الخدمة (Service) في Kubernetes هي طريقة مجردة لكشف تطبيق يعمل على مجموعة من الـ pods كخدمة شبكة.» تتميز الـ pods في Kubernetes بطبيعتها العابرة (ephemeral). يتم إنشاؤها وبعد فترة، عندما يتم تدميرها، لا يتم إعادة تدويرها. بدلاً من ذلك، تحل pods جديدة متطابقة محل القديمة. بعض كائنات Kubernetes عالية المستوى قادرة حتى على إنشاء وتدمير الـ pods ديناميكيًا.

يتم تعيين عنوان IP جديد لكل pod وقت إنشائه. ولكن في حالة الكائنات عالية المستوى التي يمكنها إنشاء وتدمير وتجميع عدد من الـ pods، فإن مجموعة الـ pods التي تعمل في لحظة معينة قد تختلف عن مجموعة الـ pods التي تشغل هذا التطبيق بعد لحظة. يؤدي هذا إلى مشكلة: إذا كانت مجموعة من الـ pods في مجموعتك تعتمد على مجموعة أخرى من الـ pods داخل مجموعتك، فكيف تكتشف وتتتبع عناوين IP لبعضها البعض؟

تقول وثائق Kubernetes: «الخدمة (Service) هي تجريد يحدد مجموعة منطقية من الـ Pods وسياسة للوصول إليها.» وهذا يعني أساسًا أن الخدمة تجمع عددًا من الـ pods التي تؤدي نفس الوظيفة وتقدمها ككيان واحد. بهذه الطريقة، يزول الارتباك الناتج عن تتبع الـ pods المتعددة حيث تعمل تلك الخدمة الواحدة الآن كنوع من وسيط الاتصال لجميعها. في مثال hello-kube، قمت بإنشاء خدمة من نوع LoadBalancer تسمح للطلبات من خارج المجموعة بالاتصال بالـ pods التي تعمل داخل المجموعة.

مخطط يوضح خدمة LoadBalancer توجه حركة المرور إلى Pods

في أي وقت تحتاج فيه إلى منح الوصول إلى pod واحد أو أكثر لتطبيق آخر أو لشيء خارج المجموعة، يجب عليك إنشاء خدمة. على سبيل المثال، إذا كان لديك مجموعة من الـ pods التي تشغل خوادم ويب ويجب أن تكون قابلة للوصول من الإنترنت، فستوفر الخدمة التجريد الضروري.

الصورة الكاملة

الآن بعد أن أصبح لديك فهم صحيح لمكونات Kubernetes الفردية، إليك تمثيل مرئي لكيفية عملها معًا خلف الكواليس:

مخطط تفصيلي لمكونات Kubernetes وكيفية تفاعلها

المصدر: kubernetes.io/docs/concepts/overview/components/

قبل أن أتعمق في شرح التفاصيل الفردية، ألقِ نظرة على ما تقوله وثائق Kubernetes: «للعمل مع كائنات Kubernetes – سواء لإنشائها أو تعديلها أو حذفها – ستحتاج إلى استخدام واجهة برمجة تطبيقات Kubernetes (Kubernetes API). عندما تستخدم واجهة سطر الأوامر kubectl، تقوم الـ CLI بإجراء استدعاءات Kubernetes API الضرورية نيابة عنك.»

كان أول أمر قمت بتشغيله هو أمر run. وكان كالتالي:

kubectl run hello-kube --image=fhsinchy/hello-kube --port=80

الأمر run مسؤول عن إنشاء pod جديد يشغل الصورة المحددة. بمجرد إصدار هذا الأمر، تحدث مجموعة الأحداث التالية داخل مجموعة Kubernetes:

  • يستقبل مكون kube-api-server الطلب، ويتحقق من صحته ويعالجه.
  • ثم يتصل kube-api-server بمكون kubelet على العقدة ويوفر التعليمات اللازمة لإنشاء الـ pod.
  • ثم يبدأ مكون kubelet العمل على جعل الـ pod قيد التشغيل ويحافظ أيضًا على تحديث معلومات الحالة في متجر etcd.

الصيغة العامة لأمر run هي كالتالي:

kubectl run <pod name> --image=<image name> --port=<port to expose>

يمكنك تشغيل أي صورة حاوية صالحة داخل pod. تحتوي صورة Docker المسماة fhsinchy/hello-kube على تطبيق JavaScript بسيط جدًا يعمل على المنفذ 80 داخل الحاوية. يسمح الخيار --port=80 للـ pod بكشف المنفذ 80 من داخل الحاوية.

مخطط يوضح Pod يعمل داخل minikube ويعرض المنفذ 80

يعمل الـ pod الذي تم إنشاؤه حديثًا داخل مجموعة minikube ولا يمكن الوصول إليه من الخارج. لكشف الـ pod وجعله قابلاً للوصول، كان الأمر الثاني الذي أصدرته كالتالي:

kubectl expose pod hello-kube --type=LoadBalancer --port=80

الأمر expose مسؤول عن إنشاء خدمة Kubernetes من نوع LoadBalancer تسمح للمستخدمين بالوصول إلى التطبيق الذي يعمل داخل الـ pod. تمامًا مثل أمر run، يمر تنفيذ أمر expose بنفس الخطوات داخل المجموعة. ولكن بدلاً من الـ pod، يوفر kube-api-server التعليمات اللازمة لإنشاء خدمة في هذه الحالة لمكون kubelet.

الصيغة العامة لأمر expose هي كالتالي:

kubectl expose <resource kind to expose> <resource name> --type=<type of service to create> --port=<port to expose>

يمكن أن يكون نوع الكائن (object type) أي نوع كائن Kubernetes صالح. يجب أن يتطابق الاسم مع اسم الكائن الذي تحاول كشفه. يشير --type إلى نوع الخدمة التي تريدها. هناك أربعة أنواع مختلفة من الخدمات المتاحة للشبكات الداخلية أو الخارجية. أخيرًا، --port هو رقم المنفذ الذي تريد كشفه من الحاوية قيد التشغيل.

مخطط يوضح خدمة LoadBalancer تربط Pods بالخارج

بمجرد إنشاء الخدمة، كانت القطعة الأخيرة من اللغز هي الوصول إلى التطبيق الذي يعمل داخل الـ pod. للقيام بذلك، كان الأمر الذي نفذته كالتالي:

minikube service hello-kube

على عكس الأوامر السابقة، لا يذهب هذا الأمر الأخير إلى kube-api-server. بدلاً من ذلك، يتصل بالمجموعة المحلية باستخدام برنامج minikube. يعيد أمر service الخاص بـ minikube عنوان URL كاملاً لخدمة معينة. عندما أنشأت hello-kube pod باستخدام الخيار --port=80، أصدرت تعليمات لـ Kubernetes للسماح للـ pod بكشف المنفذ 80 من داخل الحاوية، لكنه لم يكن قابلاً للوصول من خارج المجموعة. ثم عندما أنشأت خدمة LoadBalancer باستخدام الخيار --port=80، قامت بربط المنفذ 80 من تلك الحاوية بمنفذ عشوائي في النظام المحلي، مما جعله قابلاً للوصول من خارج المجموعة.

على نظامي، يعيد أمر service عنوان URL التالي للـ pod: 192.168.99.101:30848. عنوان IP في هذا الـ URL هو في الواقع عنوان IP للجهاز الافتراضي minikube. يمكنك التحقق من ذلك بتنفيذ الأمر التالي:

minikube ip
# 192.168.99.101

للتحقق من أن المنفذ 30848 يشير إلى المنفذ 80 داخل الـ pod، يمكنك تنفيذ الأمر التالي:

kubectl get service hello-kube
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# hello-kube LoadBalancer 10.109.60.75 <pending> 80:30848/TCP 119s

في عمود PORT(S)، يمكنك أن ترى أن المنفذ 80 يرتبط بالفعل بالمنفذ 30484 على النظام المحلي. لذا، بدلاً من تشغيل أمر service، يمكنك فقط فحص عنوان IP والمنفذ ثم وضعهما في متصفحك يدويًا للوصول إلى تطبيق hello-kube.

تطبيق Hello Kube يعمل في المتصفح عبر عنوان IP والمنفذ

الآن، يمكن تصور الحالة النهائية للمجموعة كالتالي:

مخطط يوضح خدمة LoadBalancer تربط Pods بالإنترنت

إذا كنت قادمًا من عالم Docker، فقد يبدو لك أهمية استخدام الخدمة لكشف الـ pod مطولًا بعض الشيء في الوقت الحالي. ولكن عندما تنتقل إلى الأمثلة التي تتعامل مع أكثر من pod واحد، ستبدأ في تقدير كل ما يقدمه Kubernetes.

التخلص من موارد Kubernetes

الآن بعد أن عرفت كيفية إنشاء موارد Kubernetes مثل الـ pods والـ Services، تحتاج إلى معرفة كيفية التخلص منها. الطريقة الوحيدة للتخلص من مورد Kubernetes هي حذفه. يمكنك القيام بذلك باستخدام الأمر delete لـ kubectl. الصيغة العامة للأمر هي كالتالي:

kubectl delete <resource type> <resource name>

لحذف pod باسم hello-kube، سيكون الأمر كالتالي:

kubectl delete pod hello-kube
# pod "hello-kube" deleted

ولحذف خدمة باسم hello-kube، سيكون الأمر كالتالي:

kubectl delete service hello-kube
# service "hello-kube" deleted

أو إذا كنت في مزاج تدميري، يمكنك حذف جميع الكائنات من نوع معين دفعة واحدة باستخدام الخيار --all لأمر delete. الصيغة العامة للخيار هي كالتالي:

kubectl delete <object type> --all

لذا لحذف جميع الـ pods والـ services، يجب عليك تنفيذ kubectl delete pod --all و kubectl delete service --all على التوالي.

نهج النشر التصريحي (Declarative Deployment Approach)

بصراحة، مثال hello-kube الذي رأيته للتو في القسم السابق ليس طريقة مثالية لتنفيذ النشر باستخدام Kubernetes. النهج الذي اتبعته في ذلك القسم هو نهج إلزامي (imperative approach) مما يعني أنه كان عليك تنفيذ كل أمر واحدًا تلو الآخر يدويًا. اتباع نهج إلزامي يتنافى مع الغرض الكامل من Kubernetes.

النهج المثالي للنشر باستخدام Kubernetes هو النهج التصريحي (declarative approach). فيه، أنت كمطور، تُعلم Kubernetes بالحالة التي ترغب أن تكون عليها خوادمك، ويقوم Kubernetes باكتشاف طريقة لتنفيذ ذلك. في هذا القسم، ستقوم بنشر نفس تطبيق hello-kube بنهج تصريحي. إذا لم تكن قد استنسخت (cloned) مستودع الكود المرتبط أعلاه، فافعل ذلك الآن. بمجرد حصولك عليه، انتقل إلى دليل hello-kube. يحتوي هذا الدليل على كود تطبيق hello-kube بالإضافة إلى ملف Dockerfile لبناء الصورة.

├── Dockerfile
├── index.html
├── package.json
├── public
└── src
2 directories, 3 files

يعيش كود JavaScript داخل مجلد src ولكن هذا ليس ذا أهمية لك. الملف الذي يجب أن تنظر إليه هو Dockerfile لأنه يمكن أن يمنحك نظرة ثاقبة حول كيفية التخطيط لنشرك. محتويات Dockerfile هي كالتالي:

FROM node as builder
WORKDIR /usr/app
COPY ./package.json ./
RUN npm install
COPY . .
RUN npm run build
EXPOSE 80

FROM nginx
COPY --from=builder /usr/app/dist /usr/share/nginx/html

كما ترى، هذه عملية بناء متعددة المراحل (multi-staged build process). تستخدم المرحلة الأولى صورة node كصورة أساسية وتقوم بتجميع تطبيق JavaScript في مجموعة من الملفات الجاهزة للإنتاج. تنسخ المرحلة الثانية الملفات التي تم بناؤها خلال المرحلة الأولى، وتلصقها داخل جذر مستند NGINX الافتراضي. نظرًا لأن الصورة الأساسية للمرحلة الثانية هي nginx، فإن الصورة الناتجة ستكون صورة nginx تقدم الملفات التي تم بناؤها خلال المرحلة الأولى على المنفذ 80 (المنفذ الافتراضي لـ nginx). الآن لنشر هذا التطبيق على Kubernetes، سيتعين عليك إيجاد طريقة لتشغيل الصورة كحاوية وجعل المنفذ 80 قابلاً للوصول من العالم الخارجي.

كتابة أول مجموعة من التكوينات

في النهج التصريحي، بدلاً من إصدار الأوامر الفردية في المحطة الطرفية، تقوم بكتابة التكوين الضروري في ملف YAML وتغذية Kubernetes به. في دليل مشروع hello-kube، أنشئ دليلًا آخر باسم k8s. k8s هو اختصار لـ k(ubernete = 8 character)s. لست بحاجة إلى تسمية المجلد بهذه الطريقة، يمكنك تسميته بما تريد. ليس من الضروري حتى الاحتفاظ به داخل دليل المشروع. يمكن أن تعيش ملفات التكوين هذه في أي مكان على جهاز الكمبيوتر الخاص بك، حيث لا علاقة لها بكود مصدر المشروع.

الآن داخل دليل k8s هذا، أنشئ ملفًا جديدًا باسم hello-kube-pod.yaml. سأقوم بكتابة الكود للملف أولاً ثم سأشرحه لك سطرًا بسطر. محتوى هذا الملف هو كالتالي:

apiVersion: v1
kind: Pod
metadata:
  name: hello-kube-pod
  labels:
    component: web
spec:
  containers:
    - name: hello-kube
      image: fhsinchy/hello-kube
      ports:
        - containerPort: 80

يحتوي كل ملف تكوين Kubernetes صالح على أربعة حقول مطلوبة. وهي كالتالي:

  • apiVersion: أي إصدار من واجهة برمجة تطبيقات Kubernetes تستخدمه لإنشاء هذا الكائن. قد تتغير هذه القيمة اعتمادًا على نوع الكائن الذي تقوم بإنشائه. لإنشاء Pod، الإصدار المطلوب هو v1.
  • kind: نوع الكائن الذي تريد إنشاءه. يمكن أن تكون الكائنات في Kubernetes من أنواع عديدة. بينما تتقدم في المقال، ستتعرف على الكثير منها، ولكن في الوقت الحالي، افهم أنك تقوم بإنشاء كائن Pod.
  • metadata: بيانات تساعد في تحديد الكائن بشكل فريد. تحت هذا الحقل، يمكنك الحصول على معلومات مثل name و labels و annotation وما إلى ذلك. ستظهر سلسلة metadata.name في المحطة الطرفية وستُستخدم في أوامر kubectl. لا يجب أن يكون زوج المفتاح والقيمة تحت حقل metadata.labels هو components: web. يمكنك إعطائه أي تسمية مثل app: hello-kube. ستُستخدم هذه القيمة كـ selector عند إنشاء خدمة LoadBalancer قريبًا جدًا.
  • spec: يحتوي على الحالة التي ترغب فيها للكائن. يحتوي الحقل الفرعي spec.containers على معلومات حول الحاويات التي ستعمل داخل هذا الـ Pod. قيمة spec.containers.name هي ما ستعيّنه بيئة تشغيل الحاويات داخل العقدة للحاوية التي تم إنشاؤها حديثًا. spec.containers.image هي صورة الحاوية التي ستُستخدم لإنشاء هذه الحاوية. ويحتوي حقل spec.containers.ports على تكوين بخصوص تكوينات المنافذ المختلفة. يشير containerPort: 80 إلى أنك تريد كشف المنفذ 80 من الحاوية.

إذا كنت تستخدم جهاز Raspberry Pi، فاستخدم raed667/hello-kube كصورة بدلاً من fhsinchy/hello-kube.

الآن لتغذية ملف التكوين هذا إلى Kubernetes، ستستخدم الأمر apply. الصيغة العامة للأمر هي كالتالي:

kubectl apply -f <configuration file>

لتغذية ملف تكوين باسم hello-kube-pod.yaml، سيكون الأمر كالتالي:

kubectl apply -f hello-kube-pod.yaml
# pod/hello-kube-pod created

للتأكد من أن الـ Pod قيد التشغيل، نفذ الأمر التالي:

kubectl get pod
# NAME READY STATUS RESTARTS AGE
# hello-kube 1/1 Running 0 3m3s

يجب أن ترى Running في عمود STATUS. إذا رأيت شيئًا مثل ContainerCreating، انتظر دقيقة أو دقيقتين وتحقق مرة أخرى. بمجرد أن يكون الـ Pod قيد التشغيل، حان الوقت لكتابة ملف التكوين لخدمة LoadBalancer. أنشئ ملفًا آخر داخل دليل k8s باسم hello-kube-load-balancer-service.yaml وضع فيه الكود التالي:

apiVersion: v1
kind: Service
metadata:
  name: hello-kube-load-balancer-service
spec:
  type: LoadBalancer
  ports:
    - port: 80
      targetPort: 80
  selector:
    component: web

مثل ملف التكوين السابق، تخدم حقول apiVersion و kind و metadata نفس الغرض هنا. كما ترى، لا يوجد حقل labels داخل metadata هنا. هذا لأن الخدمة تختار كائنات أخرى باستخدام labels، ولا تختار الكائنات الأخرى خدمة. تذكر، الخدمات تُعد سياسة وصول للكائنات الأخرى، ولا تُعد الكائنات الأخرى سياسة وصول لخدمة.

داخل حقل spec، يمكنك رؤية مجموعة جديدة من القيم. على عكس الـ Pod، تحتوي الخدمات على أربعة أنواع. وهي ClusterIP و NodePort و LoadBalancer و ExternalName. في هذا المثال، أنت تستخدم النوع LoadBalancer، وهي الطريقة القياسية لكشف خدمة خارج المجموعة. ستمنحك هذه الخدمة عنوان IP يمكنك استخدامه بعد ذلك للاتصال بالتطبيقات التي تعمل داخل مجموعتك.

مخطط يوضح خدمة LoadBalancer تربط Pods بالخارج عبر منفذ

يتطلب نوع LoadBalancer قيمتي منفذ للعمل بشكل صحيح. تحت حقل ports، قيمة port هي للوصول إلى الـ pod نفسه ويمكن أن تكون أي شيء تريده. قيمة targetPort هي من داخل الحاوية ويجب أن تتطابق مع المنفذ الذي تريد كشفه من داخل الحاوية. لقد ذكرت بالفعل أن تطبيق hello-kube يعمل على المنفذ 80 داخل الحاوية. لقد قمت أيضًا بكشف هذا المنفذ في ملف تكوين الـ Pod، لذا ستكون targetPort هي 80.

يُستخدم حقل selector لتحديد الكائنات التي ستتصل بهذه الخدمة. يجب أن يتطابق زوج المفتاح والقيمة component: web مع زوج المفتاح والقيمة تحت حقل labels في ملف تكوين الـ Pod. إذا كنت قد استخدمت زوج مفتاح وقيمة آخر مثل app: hello-kube في ملف التكوين هذا، فاستخدمه بدلاً من ذلك. لتغذية هذا الملف إلى Kubernetes، ستستخدم مرة أخرى الأمر apply. سيكون الأمر لتغذية ملف باسم hello-kube-load-balancer-service.yaml كالتالي:

kubectl apply -f hello-kube-load-balancer-service.yaml
# service/hello-kube-load-balancer-service created

للتأكد من إنشاء موازن التحميل بنجاح، نفذ الأمر التالي:

kubectl get service
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# hello-kube-load-balancer-service LoadBalancer 10.107.231.120 <pending> 80:30848/TCP 7s
# kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 21h

تأكد من رؤية اسم hello-kube-load-balancer-service في القائمة. الآن بعد أن أصبح لديك pod قيد التشغيل ومكشوف، يمكنك المضي قدمًا والوصول إليه. نفذ الأمر التالي للقيام بذلك:

minikube service hello-kube-load-balancer-service
# |-----------|----------------------------------|-------------|-----------------------------|
# | NAMESPACE | NAME | TARGET PORT | URL |
# |-----------|----------------------------------|-------------|-----------------------------|
# | default | hello-kube-load-balancer-service | 80 | http://192.168.99.101:30848 |
# |-----------|----------------------------------|-------------|-----------------------------|
# ? Opening service default/hello-kube-load-balancer in default browser...

يجب أن يفتح متصفح الويب الافتراضي الخاص بك تلقائيًا ويجب أن ترى شيئًا كهذا:

تطبيق Hello Kube يعمل في المتصفح بعد النشر التصريحي

يمكنك أيضًا تغذية كلا الملفين معًا بدلاً من تغذيتهما بشكل فردي. للقيام بذلك، يمكنك استبدال اسم الملف باسم الدليل كالتالي:

kubectl apply -f k8s
# service/hello-kube-load-balancer-service created
# pod/hello-kube-pod created

في هذه الحالة، تأكد من أن محطتك الطرفية في الدليل الأب لدليل k8s. إذا كنت داخل دليل k8s، يمكنك استخدام نقطة (.) للإشارة إلى الدليل الحالي. عند تطبيق التكوينات بشكل جماعي، قد يكون من الجيد التخلص من الموارد التي تم إنشاؤها مسبقًا. بهذه الطريقة، تقل احتمالية حدوث تعارضات بشكل كبير. النهج التصريحي هو النهج المثالي عند العمل مع Kubernetes، باستثناء بعض الحالات الخاصة التي ستراها قرب نهاية المقال.

لوحة تحكم Kubernetes (Dashboard)

في قسم سابق، استخدمت الأمر delete للتخلص من كائن Kubernetes. في هذا القسم، ومع ذلك، اعتقدت أن تقديم لوحة التحكم سيكون فكرة رائعة. لوحة تحكم Kubernetes هي واجهة مستخدم رسومية (graphical UI) يمكنك استخدامها لإدارة أعباء العمل والخدمات والمزيد.

لتشغيل لوحة تحكم Kubernetes، نفذ الأمر التالي في محطتك الطرفية:

minikube dashboard
# ? Verifying dashboard health ...
# ? Launching proxy ...
# ? Verifying proxy health ...
# ? Opening http://127.0.0.1:52393/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/ in your default browser...

يجب أن تفتح لوحة التحكم تلقائيًا في متصفحك الافتراضي:

واجهة لوحة تحكم Kubernetes الرسومية

الواجهة سهلة الاستخدام ويمكنك التجول فيها بحرية. على الرغم من أنه من الممكن تمامًا إنشاء الكائنات وإدارتها وحذفها من هذه الواجهة، إلا أنني سأستخدم واجهة سطر الأوامر (CLI) لبقية هذا المقال. هنا في قائمة الـ Pods، يمكنك استخدام قائمة النقاط الثلاث على الجانب الأيمن لحذف الـ Pod. يمكنك فعل الشيء نفسه مع خدمة LoadBalancer أيضًا. في الواقع، قائمة الخدمات موضوعة بشكل ملائم مباشرة بعد قائمة الـ Pods. يمكنك إغلاق لوحة التحكم بالضغط على مجموعة مفاتيح Ctrl + C أو إغلاق نافذة المحطة الطرفية.

العمل مع التطبيقات متعددة الحاويات

حتى الآن، عملت مع تطبيقات تعمل داخل حاوية واحدة. في هذا القسم، ستعمل مع تطبيق يتكون من حاويتين. ستتعرف أيضًا على Deployment و ClusterIP و PersistentVolume و PersistentVolumeClaim وبعض تقنيات تصحيح الأخطاء. التطبيق الذي ستعمل معه هو واجهة برمجة تطبيقات (API) بسيطة للملاحظات باستخدام Express مع وظائف CRUD كاملة. يستخدم التطبيق PostgreSQL كنظام قاعدة بيانات له. لذا لن تقوم بنشر التطبيق فحسب، بل ستقوم أيضًا بإعداد الشبكات الداخلية بين التطبيق وقاعدة البيانات.

يوجد كود التطبيق داخل دليل notes-api داخل مستودع المشروع.

.
├── api
├── docker-compose.yaml
└── postgres
2 directories, 1 file

يوجد كود مصدر التطبيق داخل دليل api، ويحتوي دليل postgres على ملف Dockerfile لإنشاء صورة postgres مخصصة. يحتوي ملف docker-compose.yaml على التكوين الضروري لتشغيل التطبيق باستخدام docker-compose. تمامًا مثل المشروع السابق، يمكنك البحث في ملف Dockerfile الفردي لكل خدمة للحصول على فكرة عن كيفية تشغيل التطبيق داخل الحاوية. أو يمكنك فقط فحص ملف docker-compose.yaml والتخطيط لنشر Kubernetes الخاص بك باستخدام ذلك.

version: "3.8"
services:
  db:
    build:
      context: ./postgres
      dockerfile: Dockerfile.dev
    volumes:
      - db-data:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: 63eaQB9wtLqmNBpg
      POSTGRES_DB: notesdb
  api:
    build:
      context: ./api
      dockerfile: Dockerfile.dev
    ports:
      - 3000:3000
    volumes:
      - /home/node/app/node_modules
      - ./api:/home/node/app
    environment:
      DB_CONNECTION: pg
      DB_HOST: db
      DB_PORT: 5432
      DB_USER: postgres
      DB_DATABASE: notesdb
      DB_PASSWORD: 63eaQB9wtLqmNBpg
volumes:
  db-data:
    name: notes-db-dev-data

بالنظر إلى تعريف خدمة api، يمكنك أن ترى أن التطبيق يعمل على المنفذ 3000 داخل الحاوية. كما يتطلب مجموعة من متغيرات البيئة للعمل بشكل صحيح. يمكن تجاهل وحدات التخزين (volumes) لأنها كانت ضرورية لأغراض التطوير فقط، وتكوين البناء خاص بـ Docker. لذا، مجموعتا المعلومات اللتان يمكنك نقلهما إلى ملفات تكوين Kubernetes الخاصة بك دون تغيير تقريبًا هما كالتالي:

  • تعيينات المنافذ (Port mappings): لأنك ستحتاج إلى كشف نفس المنفذ من الحاوية.
  • متغيرات البيئة (Environment variables): لأن هذه المتغيرات ستكون هي نفسها عبر جميع البيئات (القيم ستتغير، بالطبع).

خدمة db أبسط. كل ما لديها هو مجموعة من متغيرات البيئة. يمكنك حتى استخدام صورة postgres الرسمية بدلاً من صورة مخصصة. ولكن السبب الوحيد لوجود صورة مخصصة هو إذا كنت تريد أن يأتي مثيل قاعدة البيانات مع جدول notes الذي تم إنشاؤه مسبقًا. هذا الجدول ضروري للتطبيق. إذا نظرت داخل دليل postgres/docker-entrypoint-initdb.d، سترى ملفًا باسم notes.sql يُستخدم لإنشاء قاعدة البيانات أثناء التهيئة.

خطة النشر

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

مخطط يوضح ثلاثة مثيلات من Notes API وخدمة LoadBalancer

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

مخطط يوضح خدمة ClusterIP تربط Pods داخليًا

في هذا المشروع، يجب الوصول إلى قاعدة البيانات بواسطة واجهة برمجة تطبيقات الملاحظات فقط، لذا فإن كشف خدمة قاعدة البيانات داخل المجموعة هو خيار مثالي. لقد ذكرت بالفعل في قسم سابق أنه لا يجب عليك إنشاء الـ pods مباشرة. لذا في هذا المشروع، ستستخدم Deployment بدلاً من Pod.

وحدات التحكم بالنسخ المتماثلة (Replication Controllers) ومجموعات النسخ المتماثلة (Replica Sets) وعمليات النشر (Deployments)

وفقًا لوثائق Kubernetes الرسمية، «في Kubernetes، وحدات التحكم (controllers) هي حلقات تحكم تراقب حالة مجموعتك، ثم تُجري أو تطلب تغييرات عند الحاجة. تحاول كل وحدة تحكم تحريك حالة المجموعة الحالية أقرب إلى الحالة المطلوبة. حلقة التحكم هي حلقة غير منتهية تنظم حالة النظام.»

ReplicationController، كما يوحي الاسم، يتيح لك إنشاء نسخ متماثلة متعددة بسهولة بالغة. بمجرد إنشاء العدد المطلوب من النسخ المتماثلة، ستتأكد وحدة التحكم من بقاء الحالة على هذا النحو. إذا قررت بعد بعض الوقت تقليل عدد النسخ المتماثلة، فسيتخذ ReplicationController إجراءات فورية ويتخلص من الـ pods الزائدة. وإلا، إذا أصبح عدد النسخ المتماثلة أقل مما كنت تريده (ربما تعطلت بعض الـ pods)، فسيقوم ReplicationController بإنشاء نسخ جديدة لتتناسب مع الحالة المطلوبة.

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

Deployment يشبه امتدادًا لواجهة برمجة تطبيقات ReplicaSet الرائعة بالفعل. لا يسمح لك Deployment بإنشاء نسخ متماثلة في وقت قصير فحسب، بل يتيح لك أيضًا إصدار تحديثات أو العودة إلى وظيفة سابقة باستخدام أمر أو أمرين فقط من أوامر kubectl.

ReplicationController ReplicaSet Deployment
يسمح بإنشاء pods متعددة بسهولة يسمح بإنشاء pods متعددة بسهولة يسمح بإنشاء pods متعددة بسهولة
الطريقة الأصلية للنسخ المتماثل في Kubernetes يحتوي على محددات أكثر مرونة يمتد على ReplicaSets مع طرح وتراجع سهل للتحديثات

في هذا المشروع، ستستخدم Deployment للحفاظ على مثيلات التطبيق.

إنشاء أول عملية نشر لك

لنبدأ بكتابة ملف التكوين لنشر واجهة برمجة تطبيقات الملاحظات. أنشئ دليل k8s داخل دليل مشروع notes-api. داخل هذا الدليل، أنشئ ملفًا باسم api-deployment.yaml وضع المحتوى التالي فيه:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      component: api
  template:
    metadata:
      labels:
        component: api
    spec:
      containers:
        - name: api
          image: fhsinchy/notes-api
          ports:
            - containerPort: 3000

في هذا الملف، تخدم حقول apiVersion و kind و metadata و spec نفس الغرض كما في المشروع السابق. التغييرات الملحوظة في هذا الملف عن الملف الأخير هي كالتالي:

  • لإنشاء Pod، كان apiVersion المطلوب هو v1. ولكن لإنشاء Deployment، الإصدار المطلوب هو apps/v1. يمكن أن تكون إصدارات Kubernetes API مربكة بعض الشيء في بعض الأحيان، ولكن مع استمرارك في العمل مع Kubernetes، ستعتاد عليها. يمكنك أيضًا الرجوع إلى الوثائق الرسمية للحصول على أمثلة لملفات YAML.
  • النوع (kind) هو Deployment وهو واضح بذاته.
  • يحدد spec.replicas عدد النسخ المتماثلة قيد التشغيل. تعيين هذه القيمة على 3 يعني أنك تُعلم Kubernetes أنك تريد ثلاثة مثيلات من تطبيقك تعمل في جميع الأوقات.
  • spec.selector هو المكان الذي تُعلم فيه الـ Deployment أي الـ pods يجب التحكم فيها. لقد ذكرت بالفعل أن الـ Deployment هو امتداد لـ ReplicaSet ويمكنه التحكم في مجموعة من كائنات Kubernetes. تعيين selector.matchLabels على component: api يعني أن هذا الـ Deployment سيتحكم في الـ pods التي تحتوي على تسمية component: api. هذا السطر يُعلم Kubernetes أنك تريد أن يتحكم هذا الـ Deployment في جميع الـ pods التي تحتوي على تسمية component: api.
  • spec.template هو القالب لتكوين الـ pods. إنه مطابق تقريبًا لملف التكوين السابق.

إذا كنت تستخدم جهاز Raspberry Pi، فاستخدم raed667/notes-api بدلاً من fhsinchy/notes-api كصورة.

الآن لرؤية هذا التكوين عمليًا، طبق الملف تمامًا كما في المشروع السابق:

kubectl apply -f api-deployment.yaml
# deployment.apps/api-deployment created

للتأكد من إنشاء الـ Deployment، نفذ الأمر التالي:

kubectl get deployment
# NAME READY UP-TO-DATE AVAILABLE AGE
# api-deployment 0/3 3 0 2m7s

إذا نظرت إلى عمود READY، سترى 0/3. هذا يعني أن الـ pods لم يتم إنشاؤها بعد. انتظر بضع دقائق وحاول مرة أخرى.

kubectl get deployment
# NAME READY UP-TO-DATE AVAILABLE AGE
# api-deployment 0/3 3 0 28m

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

فحص موارد Kubernetes

قبل أن تتمكن من حل مشكلة، يجب عليك أولاً معرفة مصدرها. نقطة البداية الجيدة هي الأمر get. أنت تعرف بالفعل الأمر get الذي يطبع جدولاً يحتوي على معلومات مهمة حول مورد Kubernetes واحد أو أكثر. الصيغة العامة للأمر هي كالتالي:

kubectl get <resource type> <resource name>

لتشغيل الأمر get على api-deployment الخاص بك، نفذ سطر الكود التالي في محطتك الطرفية:

kubectl get deployment api-deployment
# NAME READY UP-TO-DATE AVAILABLE AGE
# api-deployment 0/3 3 0 15m

يمكنك حذف اسم api-deployment للحصول على قائمة بجميع عمليات النشر المتاحة. يمكنك أيضًا تشغيل الأمر get على ملف تكوين. إذا كنت ترغب في الحصول على معلومات حول عمليات النشر الموصوفة في ملف api-deployment.yaml، فيجب أن يكون الأمر كالتالي:

kubectl get -f api-deployment
# NAME READY UP-TO-DATE AVAILABLE AGE
# api-deployment 0/3 3 0 18m

بشكل افتراضي، يعرض الأمر get كمية صغيرة جدًا من المعلومات. يمكنك الحصول على المزيد منه باستخدام الخيار -o. يحدد الخيار -o تنسيق الإخراج لأمر get. يمكنك استخدام تنسيق الإخراج wide لرؤية المزيد من التفاصيل.

kubectl get -f api-deployment.yaml -o wide
# NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
# api-deployment 0/3 3 0 19m api fhsinchy/notes-api component=api

كما ترى، تحتوي القائمة الآن على معلومات أكثر من ذي قبل. يمكنك معرفة المزيد عن خيارات الأمر get من الوثائق الرسمية. تشغيل get على الـ Deployment لا يعرض أي شيء مثير للاهتمام، بصراحة. في مثل هذه الحالات، يجب عليك الانتقال إلى موارد المستوى الأدنى. ألقِ نظرة على قائمة الـ pods وانظر إذا كان يمكنك العثور على شيء مثير للاهتمام هناك:

kubectl get pod
# NAME READY STATUS RESTARTS AGE
# api-deployment-d59f9c884-88j45 0/1 CrashLoopBackOff 10 30m
# api-deployment-d59f9c884-96hfr 0/1 CrashLoopBackOff 10 30m
# api-deployment-d59f9c884-pzdxg 0/1 CrashLoopBackOff 10 30m

الآن هذا مثير للاهتمام. جميع الـ pods لديها حالة STATUS من نوع CrashLoopBackOff وهي جديدة. في السابق، رأيت فقط حالتي ContainerCreating و Running. قد ترى Error بدلاً من CrashLoopBackOff أيضًا. بالنظر إلى عمود RESTARTS، يمكنك أن ترى أن الـ pods قد أعيد تشغيلها 10 مرات بالفعل. هذا يعني لسبب ما أن الـ pods تفشل في البدء. الآن للحصول على نظرة أكثر تفصيلاً على أحد الـ pods، يمكنك استخدام أمر آخر يسمى describe. إنه يشبه إلى حد كبير الأمر get. الصيغة العامة للأمر هي كالتالي:

kubectl describe <resource type> <resource name>

للحصول على تفاصيل الـ pod الذي يحمل اسم api-deployment-d59f9c884-88j45، يمكنك تنفيذ الأمر التالي:

kubectl describe pod api-deployment-d59f9c884-88j45
# Name:         api-deployment-d59f9c884-88j45
# Namespace:    default
# Priority:     0
# Node:         minikube/172.28.80.217
# Start Time:   Sun, 09 Aug 2020 16:01:28 +0600
# Labels:       component=api
#               pod-template-hash=d59f9c884
# Annotations:  <none>
# Status:       Running
# IP:           172.17.0.4
# IPs:
#   IP:         172.17.0.4
# Controlled By:  ReplicaSet/api-deployment-d59f9c884
# Containers:
#   api:
#     Container ID:   docker://d2bc15bda9bf4e6d08f7ca8ff5d3c8593655f5f398cf8bdd18b71da8807930c1
#     Image:          fhsinchy/notes-api
#     Image ID:       docker-pullable://fhsinchy/notes-api@sha256:4c715c7ce3ad3693c002fad5e7e7b70d5c20794a15dbfa27945376af3f3bb78c
#     Port:           3000/TCP
#     Host Port:      0/TCP
#     State:          Waiting
#       Reason:       CrashLoopBackOff
#     Last State:     Terminated
#       Reason:       Error
#       Exit Code:    1
#       Started:      Sun, 09 Aug 2020 16:13:12 +0600
#       Finished:     Sun, 09 Aug 2020 16:13:12 +0600
#     Ready:          False
#     Restart Count:  10
#     Environment:    <none>
#     Mounts:
#       /var/run/secrets/kubernetes.io/serviceaccount from default-token-gqfr4 (ro)
# Conditions:
#   Type              Status
#   Initialized       True
#   Ready             False
#   ContainersReady   False
#   PodScheduled      True
# Volumes:
#   default-token-gqfr4:
#     Type: Secret (a volume populated by a Secret)
#     SecretName: default-token-gqfr4
#     Optional: false
# QoS Class:       BestEffort
# Node-Selectors:  <none>
# Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
#                  node.kubernetes.io/unreachable:NoExecute for 300s
# Events:
#   Type     Reason     Age                  From               Message
#   ----     ------     ----                 ----               -------
#   Normal   Scheduled  <unknown>            default-scheduler  Successfully assigned default/api-deployment-d59f9c884-88j45 to minikube
#   Normal   Pulled     2m40s (x4 over 3m47s)  kubelet, minikube  Successfully pulled image "fhsinchy/notes-api"
#   Normal   Created    2m40s (x4 over 3m47s)  kubelet, minikube  Created container api
#   Normal   Started    2m40s (x4 over 3m47s)  kubelet, minikube  Started container api
#   Normal   Pulling    107s (x5 over 3m56s)   kubelet, minikube  Pulling image "fhsinchy/notes-api"
#   Warning  BackOff    <invalid> (x44 over 3m32s)  kubelet, minikube  Back-off restarting failed container

الجزء الأكثر إثارة للاهتمام في هذا النص الطويل هو قسم Events. ألقِ نظرة فاحصة:

Events:
  Type     Reason     Age                  From               Message
  ----     ------     ----                 ----               -------
  Normal   Scheduled  <unknown>            default-scheduler  Successfully assigned default/api-deployment-d59f9c884-88j45 to minikube
  Normal   Pulled     2m40s (x4 over 3m47s)  kubelet, minikube  Successfully pulled image "fhsinchy/notes-api"
  Normal   Created    2m40s (x4 over 3m47s)  kubelet, minikube  Created container api
  Normal   Started    2m40s (x4 over 3m47s)  kubelet, minikube  Started container api
  Normal   Pulling    107s (x5 over 3m56s)   kubelet, minikube  Pulling image "fhsinchy/notes-api"
  Warning  BackOff    <invalid> (x44 over 3m32s)  kubelet, minikube  Back-off restarting failed container

من هذه الأحداث، يمكنك أن ترى أن صورة الحاوية تم سحبها بنجاح. تم إنشاء الحاوية أيضًا، ولكن من الواضح من رسالة Back-off restarting failed container أن الحاوية فشلت في البدء. أمر describe مشابه جدًا لأمر get وله نفس النوع من الخيارات. يمكنك حذف اسم api-deployment-d59f9c884-88j45 للحصول على معلومات حول جميع الـ pods المتاحة. أو يمكنك أيضًا استخدام الخيار -f لتمرير ملف تكوين إلى الأمر. قم بزيارة الوثائق الرسمية لمعرفة المزيد.

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

الحصول على سجلات الحاوية من الـ Pods

يوجد أمر kubectl آخر يسمى logs يمكن أن يساعدك في الحصول على سجلات الحاوية من داخل الـ pod. الصيغة العامة للأمر هي كالتالي:

kubectl logs <pod>

لعرض السجلات داخل الـ pod الذي يحمل اسم api-deployment-d59f9c884-88j45، يجب أن يكون الأمر كالتالي:

kubectl logs api-deployment-d59f9c884-88j45
# > api@1.0.0 start /usr/app
# > cross-env NODE_ENV=production node bin/www
# /usr/app/node_modules/knex/lib/client.js:55
# throw new Error(`knex: Required configuration option 'client' is missing.`);
# ^
# Error: knex: Required configuration option 'client' is missing.
#     at new Client (/usr/app/node_modules/knex/lib/client.js:55:11)
#     at Knex (/usr/app/node_modules/knex/lib/knex.js:53:28)
#     at Object.<anonymous> (/usr/app/services/knex.js:5:18)
#     at Module._compile (internal/modules/cjs/loader.js:1138:30)
#     at Object.Module._extensions..js (internal/modules/cjs/loader.js:1158:10)
#     at Module.load (internal/modules/cjs/loader.js:986:32)
#     at Function.Module._load (internal/modules/cjs/loader.js:879:14)
#     at Module.require (internal/modules/cjs/loader.js:1026:19)
#     at require (internal/modules/cjs/helpers.js:72:18)
#     at Object.<anonymous> (/usr/app/services/index.js:1:14)
# npm ERR! code ELIFECYCLE
# npm ERR! errno 1
# npm ERR! api@1.0.0 start: `cross-env NODE_ENV=production node bin/www`
# npm ERR! Exit status 1
# npm ERR! 
# npm ERR! Failed at the api@1.0.0 start script.
# npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
# npm ERR! A complete log of this run can be found in:
# npm ERR!     /root/.npm/_logs/2020-08-09T10_28_52_779Z-debug.log

الآن هذا ما تحتاجه لتصحيح المشكلة. يبدو أن مكتبة knex.js تفتقر إلى قيمة مطلوبة، مما يمنع التطبيق من البدء. يمكنك معرفة المزيد عن أمر logs من الوثائق الرسمية. يحدث هذا لأنك تفتقد بعض متغيرات البيئة المطلوبة في تعريف النشر. إذا ألقيت نظرة أخرى على تعريف خدمة api داخل ملف docker-compose.yaml، يجب أن ترى شيئًا كهذا:

api:
  build:
    context: ./api
    dockerfile: Dockerfile.dev
  ports:
    - 3000:3000
  volumes:
    - /home/node/app/node_modules
    - ./api:/home/node/app
  environment:
    DB_CONNECTION: pg
    DB_HOST: db
    DB_PORT: 5432
    DB_USER: postgres
    DB_DATABASE: notesdb
    DB_PASSWORD: 63eaQB9wtLqmNBpg

متغيرات البيئة هذه مطلوبة لكي يتواصل التطبيق مع قاعدة البيانات. لذا، يجب أن يؤدي إضافة هذه المتغيرات إلى تكوين النشر إلى حل المشكلة.

متغيرات البيئة

إضافة متغيرات البيئة إلى ملف تكوين Kubernetes أمر مباشر للغاية. افتح ملف api-deployment.yaml وحدث محتواه ليبدو كالتالي:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      component: api
  template:
    metadata:
      labels:
        component: api
    spec:
      containers:
        - name: api
          image: fhsinchy/notes-api
          ports:
            - containerPort: 3000
          # these are the environment variables
          env:
            - name: DB_CONNECTION
              value: pg

يحتوي حقل containers.env على جميع متغيرات البيئة. إذا نظرت عن كثب، سترى أنني لم أضف جميع متغيرات البيئة من ملف docker-compose.yaml. لقد أضفت واحدًا فقط. يشير DB_CONNECTION إلى أن التطبيق يستخدم قاعدة بيانات PostgreSQL. يجب أن يؤدي إضافة هذا المتغير الوحيد إلى حل المشكلة. الآن طبق ملف التكوين مرة أخرى بتنفيذ الأمر التالي:

kubectl apply -f api-deployment.yaml
# deployment.apps/api-deployment configured

على عكس المرات الأخرى، يقول الإخراج هنا إن المورد قد تم configured (تكوينه). هذا هو جمال Kubernetes. يمكنك فقط إصلاح المشكلات وإعادة تطبيق نفس ملف التكوين على الفور. الآن استخدم الأمر get مرة أخرى للتأكد من أن كل شيء يعمل بشكل صحيح.

kubectl get deployment
# NAME READY UP-TO-DATE AVAILABLE AGE
# api-deployment 3/3 3 3 68m
kubectl get pod
# NAME READY STATUS RESTARTS AGE
# api-deployment-66cdd98546-l9x8q 1/1 Running 0 7m26s
# api-deployment-66cdd98546-mbfw9 1/1 Running 0 7m31s
# api-deployment-66cdd98546-pntxv 1/1 Running 0 7m21s

جميع الـ pods الثلاثة تعمل، والـ Deployment يعمل بشكل جيد أيضًا.

إنشاء نشر قاعدة البيانات

الآن بعد أن أصبحت واجهة برمجة التطبيقات (API) قيد التشغيل، حان الوقت لكتابة التكوين لمثيل قاعدة البيانات. أنشئ ملفًا آخر باسم postgres-deployment.yaml داخل دليل k8s وضع المحتوى التالي فيه:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      component: postgres
  template:
    metadata:
      labels:
        component: postgres
    spec:
      containers:
        - name: postgres
          image: fhsinchy/notes-postgres
          ports:
            - containerPort: 5432
          env:
            - name: POSTGRES_PASSWORD
              value: 63eaQB9wtLqmNBpg
            - name: POSTGRES_DB
              value: notesdb

إذا كنت تستخدم جهاز Raspberry Pi، فاستخدم raed667/notes-postgres بدلاً من fhsinchy/notes-postgres كصورة. التكوين نفسه مشابه جدًا للتكوين السابق. لن أشرح كل شيء في هذا الملف – نأمل أن تفهمه بنفسك بالمعرفة التي اكتسبتها من هذا المقال حتى الآن. يعمل PostgreSQL على المنفذ 5432 بشكل افتراضي، ومتغير POSTGRES_PASSWORD مطلوب لتشغيل حاوية postgres. سيتم استخدام كلمة المرور هذه أيضًا للاتصال بقاعدة البيانات هذه بواسطة واجهة برمجة التطبيقات. متغير POSTGRES_DB اختياري. ولكن بسبب طريقة هيكلة هذا المشروع، فهو ضروري هنا – وإلا ستفشل التهيئة. يمكنك معرفة المزيد عن صورة Docker الرسمية لـ postgres من صفحتهم على Docker Hub. لغرض التبسيط، سأبقي عدد النسخ المتماثلة على 1 في هذا المشروع. لتطبيق هذا الملف، نفذ الأمر التالي:

kubectl apply -f postgres-deployment.yaml
# deployment.apps/postgres-deployment created

استخدم الأمر get للتأكد من أن النشر والـ pods تعمل بشكل صحيح:

kubectl get deployment
# NAME READY UP-TO-DATE AVAILABLE AGE
# postgres-deployment 1/1 1 1 13m
kubectl get pod
# NAME READY STATUS RESTARTS AGE
# postgres-deployment-76fcc75998-mwnb7 1/1 Running 0 13m

على الرغم من أن النشر والـ pods تعمل بشكل صحيح، إلا أن هناك مشكلة كبيرة في نشر قاعدة البيانات. إذا كنت قد عملت مع أي نظام قاعدة بيانات من قبل، فقد تعرف بالفعل أن قواعد البيانات تخزن البيانات في نظام الملفات. في الوقت الحالي، يبدو نشر قاعدة البيانات كالتالي:

مخطط يوضح حاوية PostgreSQL داخل Pod

حاوية postgres مغلفة بـ pod. أي بيانات يتم حفظها تبقى داخل نظام الملفات الداخلي للحاوية. الآن، إذا تعطلت الحاوية لأي سبب، أو تعطل الـ pod الذي يغلف الحاوية، فستفقد جميع البيانات المخزنة داخل نظام الملفات. عند التعطل، سيقوم Kubernetes بإنشاء pod جديد للحفاظ على الحالة المطلوبة، ولكن لا توجد آلية لنقل البيانات بين الـ pods على الإطلاق. لحل هذه المشكلة، يمكنك تخزين البيانات في مساحة منفصلة خارج الـ pod داخل المجموعة.

مخطط يوضح تخزين البيانات في وحدة تخزين منفصلة خارج Pod

تُعد إدارة مثل هذا التخزين مشكلة مميزة عن إدارة مثيلات الحوسبة. يوفر النظام الفرعي PersistentVolume في Kubernetes واجهة برمجة تطبيقات للمستخدمين والمسؤولين تجرد تفاصيل كيفية توفير التخزين من كيفية استهلاكه.

وحدات التخزين المستمرة (Persistent Volumes) ومطالبات وحدات التخزين المستمرة (Persistent Volume Claims)

وفقًا لوثائق Kubernetes الرسمية، «PersistentVolume (PV) هو جزء من التخزين في المجموعة تم توفيره بواسطة مسؤول أو تم توفيره ديناميكيًا باستخدام StorageClass. إنه مورد في المجموعة تمامًا مثل العقدة مورد في المجموعة.» وهذا يعني أساسًا أن PersistentVolume هو طريقة لأخذ شريحة من مساحة التخزين الخاصة بك وحجزها لـ pod معين. يتم استهلاك وحدات التخزين دائمًا بواسطة الـ pods وليس بواسطة كائن عالي المستوى مثل الـ deployment. إذا كنت ترغب في استخدام وحدة تخزين مع deployment يحتوي على عدة pods، فسيتعين عليك المرور ببعض الخطوات الإضافية.

أنشئ ملفًا جديدًا باسم database-persistent-volume.yaml داخل دليل k8s وضع المحتوى التالي في هذا الملف:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: database-persistent-volume
spec:
  storageClassName: manual
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: "/mnt/data"

تخدم حقول apiVersion و kind و metadata نفس الغرض كما في أي ملف تكوين آخر. ومع ذلك، يحتوي حقل spec على بعض الحقول الجديدة.

  • spec.storageClassName: يشير إلى اسم الفئة لوحدة التخزين هذه. لنفترض أن مزود خدمة سحابية لديه ثلاثة أنواع من التخزين المتاحة. يمكن أن تكون هذه الأنواع slow و fast و very fast. سيعتمد نوع التخزين الذي تحصل عليه من المزود على المبلغ الذي تدفعه. إذا طلبت تخزينًا سريعًا جدًا، فسيتعين عليك دفع المزيد. هذه الأنواع المختلفة من التخزين هي الفئات. أنا أستخدم manual كمثال هنا. يمكنك استخدام ما تريد في مجموعتك المحلية.
  • spec.capacity.storage: هو مقدار التخزين الذي ستحتوي عليه وحدة التخزين هذه. أنا أمنحه 5 جيجابايت من التخزين في هذا المشروع.
  • spec.accessModes: يحدد وضع الوصول لوحدة التخزين. هناك ثلاثة أوضاع وصول ممكنة. ReadWriteOnce يعني أنه يمكن تحميل وحدة التخزين للقراءة والكتابة بواسطة عقدة واحدة. ReadWriteMany، من ناحية أخرى، يعني أنه يمكن تحميل وحدة التخزين للقراءة والكتابة بواسطة العديد من العقد. ReadOnlyMany يعني أنه يمكن تحميل وحدة التخزين للقراءة فقط بواسطة العديد من العقد.
  • spec.hostPath: هو شيء خاص بالتطوير. يشير إلى الدليل في مجموعتك المحلية ذات العقدة الواحدة الذي سيتم التعامل معه كوحدة تخزين مستمرة. /mnt/data يعني أن البيانات المحفوظة في وحدة التخزين المستمرة هذه ستعيش داخل دليل /mnt/data في المجموعة.

لتطبيق هذا الملف، نفذ الأمر التالي:

kubectl apply -f database-persistent-volume.yaml
# persistentvolume/database-persistent-volume created

الآن استخدم الأمر get للتحقق من إنشاء وحدة التخزين:

kubectl get persistentvolume
# NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
# database-persistent-volume 5Gi RWO Retain Available manual 58s

الآن بعد إنشاء وحدة التخزين المستمرة، تحتاج إلى طريقة للسماح لـ pod قاعدة البيانات بالوصول إليها. هذا هو المكان الذي يأتي فيه PersistentVolumeClaim (PVC). PersistentVolumeClaim هو طلب تخزين بواسطة pod. لنفترض أن لديك عددًا كبيرًا من وحدات التخزين في مجموعة. سيحدد هذا الطلب الخصائص التي يجب أن تفي بها وحدة التخزين لتكون قادرة على تلبية احتياجات الـ pods. مثال واقعي يمكن أن يكون شراء قرص SSD من متجر. تذهب إلى المتجر ويعرض عليك البائع النماذج التالية:

النموذج 1 النموذج 2 النموذج 3
128 جيجابايت 256 جيجابايت 512 جيجابايت
SATA NVME SATA

الآن، أنت تطلب نموذجًا بسعة تخزين لا تقل عن 200 جيجابايت ويكون من نوع NVME. الأول أقل من 200 جيجابايت وهو SATA، لذا لا يتطابق مع طلبك. الثالث أكثر من 200 جيجابايت، لكنه ليس NVME. الثاني، ومع ذلك، أكثر من 200 جيجابايت وهو أيضًا NVME. لذا هذا هو ما تحصل عليه. نماذج SSD التي عرضها عليك البائع تعادل وحدات التخزين المستمرة، ومتطلباتك تعادل مطالبات وحدات التخزين المستمرة.

أنشئ ملفًا جديدًا آخر باسم database-persistent-volume-claim.yaml داخل دليل k8s وضع المحتوى التالي في هذا الملف:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: database-persistent-volume-claim
spec:
  storageClassName: manual
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 2Gi

مرة أخرى، تخدم حقول apiVersion و kind و metadata نفس الغرض كما في أي ملف تكوين آخر. يشير spec.storageClass في ملف تكوين المطالبة إلى نوع التخزين الذي تريده هذه المطالبة. هذا يعني أن أي PersistentVolume يحتوي على spec.storageClass مضبوطًا على manual مناسب للاستهلاك بواسطة هذه المطالبة. إذا كان لديك وحدات تخزين متعددة بفئة manual، فستحصل المطالبة على أي منها، وإذا لم يكن لديك أي وحدة تخزين بفئة manual، فسيتم توفير وحدة تخزين ديناميكيًا.

يحدد spec.accessModes مرة أخرى وضع الوصول هنا. يشير هذا إلى أن هذه المطالبة تريد تخزينًا له accessMode من نوع ReadWriteOnce. لنفترض أن لديك وحدتي تخزين مع ضبط الفئة على manual. إحداهما لديها accessModes مضبوطًا على ReadWriteOnce والأخرى على ReadWriteMany. ستحصل هذه المطالبة على تلك التي لديها ReadWriteOnce.

resources.requests.storage هو مقدار التخزين الذي تريده هذه المطالبة. 2Gi لا يعني أن وحدة التخزين المعطاة يجب أن تحتوي على سعة تخزين 2 جيجابايت بالضبط. بل يعني أنها يجب أن تحتوي على 2 جيجابايت على الأقل. آمل أن تتذكر أنك قمت بتعيين سعة وحدة التخزين المستمرة لتكون 5 جيجابايت، وهي أكثر من 2 جيجابايت. لتطبيق هذا الملف، نفذ الأمر التالي:

kubectl apply -f database-persistent-volume-claim.yaml
# persistentvolumeclaim/database-persistent-volume-claim created

الآن استخدم الأمر get للتحقق من إنشاء وحدة التخزين:

kubectl get persistentvolumeclaim
# NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
# database-persistent-volume-claim Bound database-persistent-volume 5Gi RWO manual 37s

انظر إلى عمود VOLUME. هذه المطالبة مرتبطة بوحدة التخزين المستمرة database-persistent-volume التي أنشأتها سابقًا. انظر أيضًا إلى CAPACITY. إنها 5Gi، لأن المطالبة طلبت وحدة تخزين بسعة تخزين 2 جيجابايت على الأقل.

التوفير الديناميكي لوحدات التخزين المستمرة

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

kubectl delete persistentvolumeclaim --all
# persistentvolumeclaim "database-persistent-volume-claim" deleted
kubectl delete persistentvolume --all
# persistentvolume "database-persistent-volume" deleted

افتح ملف database-persistent-volume-claim.yaml وحدث محتواه ليكون كالتالي:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: database-persistent-volume-claim
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 2Gi

لقد أزلت حقل spec.storageClass من الملف. الآن أعد تطبيق ملف database-persistent-volume-claim.yaml دون تطبيق ملف database-persistent-volume.yaml:

kubectl apply -f database-persistent-volume-claim.yaml
# persistentvolumeclaim/database-persistent-volume-claim created

الآن استخدم الأمر get للنظر في معلومات المطالبة:

kubectl get persistentvolumeclaim
# NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
# database-persistent-volume-claim Bound pvc-525ae8af-00d3-4cc7-ae47-866aa13dffd5 2Gi RWO standard 2s

كما ترى، تم توفير وحدة تخزين باسم pvc-525ae8af-00d3-4cc7-ae47-866aa13dffd5 وسعة تخزين 2Gi وربطها بالمطالبة ديناميكيًا. يمكنك استخدام وحدة تخزين مستمرة ثابتة أو ديناميكية التوفير لبقية هذا المشروع. سأستخدم واحدة تم توفيرها ديناميكيًا.

ربط وحدات التخزين مع الـ Pods

الآن بعد أن أنشأت وحدة تخزين مستمرة ومطالبة، حان الوقت للسماح لـ pod قاعدة البيانات باستخدام وحدة التخزين هذه. يمكنك القيام بذلك عن طريق ربط الـ pod بمطالبة وحدة التخزين المستمرة التي أنشأتها في القسم الفرعي السابق. افتح ملف postgres-deployment.yaml وحدث محتواه ليكون كالتالي:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      component: postgres
  template:
    metadata:
      labels:
        component: postgres
    spec:
      # volume configuration for the pod
      volumes:
        - name: postgres-storage
          persistentVolumeClaim:
            claimName: database-persistent-volume-claim
      containers:
        - name: postgres
          image: fhsinchy/notes-postgres
          ports:
            - containerPort: 5432
          # volume mounting configuration for the container
          volumeMounts:
            - name: postgres-storage
              mountPath: /var/lib/postgresql/data
              subPath: postgres
          env:
            - name: POSTGRES_PASSWORD
              value: 63eaQB9wtLqmNBpg
            - name: POSTGRES_DB
              value: notesdb

لقد أضفت حقلين جديدين في ملف التكوين هذا. يحتوي حقل spec.volumes على المعلومات الضرورية لكي يجد الـ pod مطالبة وحدة التخزين المستمرة. يمكن أن يكون spec.volumes.name أي شيء تريده. يجب أن يتطابق spec.volumes.persistentVolumeClaim.claimName مع قيمة metadata.name من ملف database-persistent-volume-claim.yaml.

يحتوي containers.volumeMounts على المعلومات الضرورية لتركيب وحدة التخزين داخل الحاوية. يجب أن يتطابق containers.volumeMounts.name مع القيمة من spec.volumes.name. يشير containers.volumeMounts.mountPath إلى الدليل الذي سيتم تركيب وحدة التخزين فيه. /var/lib/postgresql/data هو دليل البيانات الافتراضي لـ PostgreSQL. يشير containers.volumeMounts.subPath إلى دليل سيتم إنشاؤه داخل وحدة التخزين. لنفترض أنك تستخدم نفس وحدة التخزين مع pods أخرى أيضًا. في هذه الحالة، يمكنك وضع البيانات الخاصة بالـ pod داخل دليل آخر داخل وحدة التخزين هذه. ستذهب جميع البيانات المحفوظة داخل دليل /var/lib/postgresql/data داخل دليل postgres داخل وحدة التخزين.

الآن أعد تطبيق ملف postgres-deployment.yaml بتنفيذ الأمر التالي:

kubectl apply -f postgres-deployment.yaml
# deployment.apps/postgres-deployment configured

الآن لديك نشر قاعدة بيانات صحيح مع مخاطر أقل بكثير لفقدان البيانات. شيء واحد أود ذكره هنا هو أن نشر قاعدة البيانات في هذا المشروع يحتوي على نسخة متماثلة واحدة فقط. إذا كان هناك أكثر من نسخة متماثلة واحدة، لكانت الأمور مختلفة. يمكن أن يؤدي وصول عدة pods إلى نفس وحدة التخزين دون علم بعضها البعض إلى نتائج كارثية. في مثل هذه الحالات، قد يكون إنشاء أدلة فرعية للـ pods داخل وحدة التخزين فكرة جيدة.

ربط كل شيء معًا

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

مخطط يوضح ربط Notes API بقاعدة بيانات PostgreSQL عبر خدمات Kubernetes

  • سيتم كشف قاعدة البيانات فقط داخل المجموعة باستخدام خدمة ClusterIP. لن يُسمح بحركة المرور الخارجية.
  • نشر واجهة برمجة التطبيقات، ومع ذلك، سيتم كشفه للعالم الخارجي. سيتواصل المستخدمون مع واجهة برمجة التطبيقات، وستتواصل واجهة برمجة التطبيقات مع قاعدة البيانات.

لقد عملت سابقًا مع خدمة LoadBalancer التي تكشف تطبيقًا للعالم الخارجي. أما ClusterIP، من ناحية أخرى، فيكشف تطبيقًا داخل المجموعة ولا يسمح بحركة المرور الخارجية.

مخطط يوضح خدمة ClusterIP تربط Pods داخليًا

نظرًا لأن خدمة قاعدة البيانات يجب أن تكون متاحة فقط داخل المجموعة، فإن خدمة ClusterIP هي الأنسب لهذا السيناريو. أنشئ ملفًا جديدًا باسم postgres-cluster-ip-service.yaml داخل دليل k8s وضع المحتوى التالي فيه:

apiVersion: v1
kind: Service
metadata:
  name: postgres-cluster-ip-service
spec:
  type: ClusterIP
  selector:
    component: postgres
  ports:
    - port: 5432
      targetPort: 5432

كما ترى، ملف التكوين لـ ClusterIP مطابق لملف LoadBalancer. الشيء الوحيد الذي يختلف هو قيمة spec.type. يجب أن تكون قادرًا على تفسير هذا الملف دون أي مشكلة الآن. 5432 هو المنفذ الافتراضي الذي يعمل عليه PostgreSQL. لهذا السبب يجب كشف هذا المنفذ. ملف التكوين التالي هو لخدمة LoadBalancer، المسؤولة عن كشف واجهة برمجة التطبيقات للعالم الخارجي. أنشئ ملفًا آخر باسم api-load-balancer-service.yaml وضع المحتوى التالي فيه:

apiVersion: v1
kind: Service
metadata:
  name: api-load-balancer-service
spec:
  type: LoadBalancer
  ports:
    - port: 3000
      targetPort: 3000
  selector:
    component: api

هذا التكوين مطابق للتكوين الذي كتبته في قسم سابق. تعمل واجهة برمجة التطبيقات على المنفذ 3000 داخل الحاوية وهذا هو السبب في ضرورة كشف هذا المنفذ. آخر شيء يجب فعله هو إضافة بقية متغيرات البيئة إلى نشر واجهة برمجة التطبيقات. لذا افتح ملف api-deployment.yaml وحدث محتواه كالتالي:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      component: api
  template:
    metadata:
      labels:
        component: api
    spec:
      containers:
        - name: api
          image: fhsinchy/notes-api
          ports:
            - containerPort: 3000
          env:
            - name: DB_CONNECTION
              value: pg
            - name: DB_HOST
              value: postgres-cluster-ip-service
            - name: DB_PORT
              value: '5432'
            - name: DB_USER
              value: postgres
            - name: DB_DATABASE
              value: notesdb
            - name: DB_PASSWORD
              value: 63eaQB9wtLqmNBpg

في السابق، كان هناك فقط متغير DB_CONNECTION تحت spec.containers.env. المتغيرات الجديدة هي كالتالي:

  • DB_HOST: يشير إلى عنوان المضيف لخدمة قاعدة البيانات. في بيئة غير محوسبة، تكون القيمة عادةً 127.0.0.1. ولكن في بيئة Kubernetes، لا تعرف عنوان IP لـ pod قاعدة البيانات. وبالتالي، تستخدم فقط اسم الخدمة التي تكشف قاعدة البيانات بدلاً من ذلك.
  • DB_PORT: هو المنفذ المكشوف من خدمة قاعدة البيانات، وهو 5432.
  • DB_USER: هو المستخدم للاتصال بقاعدة البيانات. postgres هو اسم المستخدم الافتراضي.
  • DB_DATABASE: هي قاعدة البيانات التي ستتصل بها واجهة برمجة التطبيقات. يجب أن يتطابق هذا مع قيمة spec.containers.env.DB_DATABASE من ملف postgres-deployment.yaml.
  • DB_PASSWORD: هي كلمة المرور للاتصال بقاعدة البيانات. يجب أن يتطابق هذا مع قيمة spec.containers.env.DB_PASSWORD من ملف postgres-deployment.yaml.

مع الانتهاء من ذلك، أنت الآن جاهز لاختبار واجهة برمجة التطبيقات. قبل القيام بذلك، أقترح تطبيق جميع ملفات التكوين مرة أخرى بتنفيذ الأمر التالي:

kubectl apply -f k8s
# deployment.apps/api-deployment created
# service/api-load-balancer-service created
# persistentvolumeclaim/database-persistent-volume-claim created
# service/postgres-cluster-ip-service created
# deployment.apps/postgres-deployment created

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

kubectl get deployment
# NAME READY UP-TO-DATE AVAILABLE AGE
# api-deployment 3/3 3 3 106s
# postgres-deployment 1/1 1 1 106s

كما ترى من عمود READY، جميع الـ pods قيد التشغيل. للوصول إلى واجهة برمجة التطبيقات، استخدم الأمر service لـ minikube.

minikube service api-load-balancer-service
# |-----------|---------------------------|-------------|-----------------------------|
# | NAMESPACE | NAME                      | TARGET PORT | URL                         |
# |-----------|---------------------------|-------------|-----------------------------|
# | default   | api-load-balancer-service | 3000        | http://172.19.186.112:31546 |
# |-----------|---------------------------|-------------|-----------------------------|
# * Opening service default/api-load-balancer-service in default browser...

يجب أن تفتح واجهة برمجة التطبيقات تلقائيًا في متصفحك الافتراضي:

واجهة برمجة تطبيقات Notes API تعرض رسالة ترحيب

هذه هي الاستجابة الافتراضية لواجهة برمجة التطبيقات. يمكنك أيضًا استخدام http://172.19.186.112:31546/ مع أداة اختبار واجهة برمجة التطبيقات مثل Insomnia أو Postman لاختبار واجهة برمجة التطبيقات. تحتوي واجهة برمجة التطبيقات على وظائف CRUD كاملة. يمكنك رؤية الاختبارات التي تأتي مع كود مصدر واجهة برمجة التطبيقات كوثائق. ما عليك سوى فتح ملف api/tests/e2e/api/routes/notes.test.js. يجب أن تكون قادرًا على فهم الملف دون عناء كبير إذا كان لديك خبرة في JavaScript و Express.

العمل مع وحدات التحكم بالـ Ingress

حتى الآن في هذا المقال، استخدمت ClusterIP لكشف تطبيق داخل المجموعة و LoadBalancer لكشف تطبيق خارج المجموعة. على الرغم من أنني ذكرت LoadBalancer كنوع الخدمة القياسي لكشف تطبيق خارج المجموعة، إلا أن له بعض السلبيات. عند استخدام خدمات LoadBalancer لكشف التطبيقات في بيئة سحابية، سيتعين عليك الدفع مقابل كل خدمة مكشوفة على حدة، مما قد يكون مكلفًا في المشاريع الضخمة.

يوجد نوع آخر من الخدمات يسمى NodePort يمكن استخدامه كبديل لخدمات LoadBalancer.

مخطط يوضح خدمة NodePort تكشف Pods عبر منفذ على العقدة

يفتح NodePort منفذًا محددًا على جميع العقد في مجموعتك، ويتعامل مع أي حركة مرور تأتي عبر هذا المنفذ المفتوح. كما تعلم بالفعل، تجمع الخدمات عددًا من الـ pods، وتتحكم في طريقة الوصول إليها. لذا، فإن أي طلب يصل إلى الخدمة عبر المنفذ المكشوف سينتهي به المطاف في الـ pod الصحيح. يمكن أن يكون ملف التكوين المثال لإنشاء NodePort كالتالي:

apiVersion: v1
kind: Service
metadata:
  name: hello-kube-node-port
spec:
  type: NodePort
  ports:
    - port: 8080
      targetPort: 8080
      nodePort: 31515
  selector:
    component: web

يجب أن تكون قيمة حقل spec.ports.nodePort هنا بين 30000 و 32767. هذا النطاق خارج المنافذ المعروفة التي تستخدمها عادةً الخدمات المختلفة، ولكنه أيضًا غير عادي. أعني، كم مرة ترى منفذًا به الكثير من الأرقام؟ يمكنك محاولة استبدال خدمات LoadBalancer التي أنشأتها في الأقسام السابقة بخدمة NodePort. لا ينبغي أن يكون هذا صعبًا ويمكن اعتباره اختبارًا لما تعلمته حتى الآن.

لحل المشكلات التي ذكرتها، تم إنشاء واجهة برمجة تطبيقات Ingress. لكي نكون واضحين جدًا، Ingress ليس نوعًا من الخدمات في الواقع. بدلاً من ذلك، فإنه يجلس أمام خدمات متعددة ويعمل كنوع من الموجه (router). يلزم وجود IngressController للعمل مع موارد Ingress في مجموعتك. يمكن العثور على قائمة بوحدات التحكم بالـ ingress المتاحة في وثائق Kubernetes.

إعداد وحدة التحكم بـ NGINX Ingress

في هذا المثال، ستقوم بتوسيع واجهة برمجة تطبيقات الملاحظات عن طريق إضافة واجهة أمامية (front end) إليها. وبدلاً من استخدام خدمة مثل LoadBalancer أو NodePort، ستستخدم Ingress لكشف التطبيق. وحدة التحكم التي ستستخدمها هي NGINX Ingress Controller لأن NGINX سيُستخدم لتوجيه الطلبات إلى خدمات مختلفة هنا. يجعل NGINX Ingress Controller العمل مع تكوينات NGINX في مجموعة Kubernetes سهلاً للغاية.

يوجد كود المشروع داخل دليل fullstack-notes-application.

.
├── api
├── client
├── docker-compose.yaml
├── k8s
│   ├── api-deployment.yaml
│   ├── database-persistent-volume-claim.yaml
│   ├── postgres-cluster-ip-service.yaml
│   └── postgres-deployment.yaml
├── nginx
└── postgres
5 directories, 1 file

سترى دليل k8s هناك. يحتوي على جميع ملفات التكوين التي كتبتها في القسم الفرعي الأخير، باستثناء ملف api-load-balancer-service.yaml. والسبب في ذلك هو أنه في هذا المشروع، سيتم استبدال خدمة LoadBalancer القديمة بـ Ingress. أيضًا، بدلاً من كشف واجهة برمجة التطبيقات، ستقوم بكشف تطبيق الواجهة الأمامية للعالم. قبل البدء في كتابة ملفات التكوين الجديدة، ألقِ نظرة على كيفية عمل الأمور خلف الكواليس.

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

يزور المستخدم تطبيق الواجهة الأمامية ويقدم البيانات الضرورية. ثم يقوم تطبيق الواجهة الأمامية بإعادة توجيه البيانات المقدمة إلى واجهة برمجة التطبيقات الخلفية. ثم تقوم واجهة برمجة التطبيقات بحفظ البيانات في قاعدة البيانات وإرسالها مرة أخرى إلى تطبيق الواجهة الأمامية. ثم يتم تحقيق توجيه الطلبات باستخدام NGINX. يمكنك إلقاء نظرة على ملف nginx/production.conf لفهم كيفية إعداد هذا التوجيه. الآن الشبكات الضرورية لجعل هذا يحدث هي كالتالي:

مخطط يوضح Ingress كمدخل وموجه للطلبات إلى الخدمات المختلفة

يمكن شرح هذا الرسم البياني كالتالي:

  • سيعمل Ingress كنقطة دخول وموجه لهذا التطبيق. هذا Ingress من نوع NGINX، لذا سيكون المنفذ هو منفذ nginx الافتراضي وهو 80.
  • سيتم توجيه كل طلب يأتي إلى / نحو تطبيق الواجهة الأمامية (الخدمة على اليسار). لذا إذا كان عنوان URL لهذا التطبيق هو https://kube-notes.test، فإن أي طلب يأتي إلى https://kube-notes.test/foo أو https://kube-notes.test/bar سيتعامل معه تطبيق الواجهة الأمامية.
  • سيتم توجيه كل طلب يأتي إلى /api نحو واجهة برمجة التطبيقات الخلفية (الخدمة على اليمين). لذا إذا كان عنوان URL مرة أخرى هو https://kube-notes.test، فإن أي طلب يأتي إلى https://kube-notes.test/api/foo أو https://kube-notes.test/api/bar سيتعامل معه واجهة برمجة التطبيقات الخلفية.

كان من الممكن تمامًا تكوين خدمة Ingress للعمل مع النطاقات الفرعية بدلاً من المسارات مثل هذه، لكنني اخترت النهج القائم على المسار لأن هذا هو كيفية تصميم تطبيقي. في هذا القسم الفرعي، سيتعين عليك كتابة أربعة ملفات تكوين جديدة:

  1. تكوين ClusterIP لنشر واجهة برمجة التطبيقات.
  2. تكوين Deployment لتطبيق الواجهة الأمامية.
  3. تكوين ClusterIP لتطبيق الواجهة الأمامية.
  4. تكوين Ingress للتوجيه.

سأمر على الملفات الثلاثة الأولى بسرعة كبيرة دون قضاء الكثير من الوقت في شرحها. الأول هو تكوين api-cluster-ip-service.yaml ومحتويات الملف هي كالتالي:

apiVersion: v1
kind: Service
metadata:
  name: api-cluster-ip-service
spec:
  type: ClusterIP
  selector:
    component: api
  ports:
    - port: 3000
      targetPort: 3000

على الرغم من أنك في القسم الفرعي السابق كشفت واجهة برمجة التطبيقات مباشرة إلى العالم الخارجي، إلا أنك في هذا القسم ستدع Ingress يقوم بالعمل الشاق بينما تكشف واجهة برمجة التطبيقات داخليًا باستخدام خدمة ClusterIP القديمة الجيدة. يجب أن يكون التكوين نفسه واضحًا بذاته في هذه المرحلة، لذا لن أقضي أي وقت في شرحه.

بعد ذلك، أنشئ ملفًا باسم client-deployment.yaml مسؤولاً عن تشغيل تطبيق الواجهة الأمامية. محتويات الملف هي كالتالي:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: client-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      component: client
  template:
    metadata:
      labels:
        component: client
    spec:
      containers:
        - name: client
          image: fhsinchy/notes-client
          ports:
            - containerPort: 8080
          env:
            - name: VUE_APP_API_URL
              value: /api

إنه مطابق تقريبًا لملف api-deployment.yaml وأفترض أنك ستكون قادرًا على تفسير ملف التكوين هذا بنفسك. يشير متغير البيئة VUE_APP_API_URL هنا إلى المسار الذي يجب إعادة توجيه طلبات واجهة برمجة التطبيقات إليه. ستتعامل Ingress بدورها مع هذه الطلبات المعاد توجيهها. لكشف تطبيق العميل هذا داخليًا، يلزم وجود خدمة ClusterIP أخرى. أنشئ ملفًا جديدًا باسم client-cluster-ip-service.yaml وضع المحتوى التالي فيه:

apiVersion: v1
kind: Service
metadata:
  name: client-cluster-ip-service
spec:
  type: ClusterIP
  selector:
    component: client
  ports:
    - port: 8080
      targetPort: 8080

كل ما يفعله هذا هو كشف المنفذ 8080 داخل المجموعة الذي يعمل عليه تطبيق الواجهة الأمامية بشكل افتراضي. الآن بعد الانتهاء من التكوينات القديمة المملة، فإن التكوين التالي هو ملف ingress-service.yaml ومحتوى الملف هو كالتالي:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-service
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
  rules:
    - http:
        paths:
          - pathType: Prefix
            path: "/?(.*)"
            backend:
              service:
                name: client-cluster-ip-service
                port:
                  number: 8080
          - pathType: Prefix
            path: "/api/?(.*)"
            backend:
              service:
                name: api-cluster-ip-service
                port:
                  number: 3000

قد يبدو ملف التكوين هذا غير مألوف بعض الشيء بالنسبة لك ولكنه في الواقع مباشر تمامًا. لا تزال واجهة برمجة تطبيقات Ingress في مرحلة البيتا، وبالتالي فإن apiVersion هو networking.k8s.io/v1 (ملاحظة: النص الأصلي ذكر extensions/v1 ولكن networking.k8s.io/v1 هو الإصدار المستقر الحالي). تخدم حقلا kind و metadata.name نفس الغرض كما في أي من التكوينات التي كتبتها سابقًا. يمكن أن تحتوي metadata.annotations على معلومات بخصوص تكوين Ingress. يشير kubernetes.io/ingress.class: nginx إلى أن كائن Ingress يجب أن تتحكم فيه وحدة التحكم ingress-nginx. يشير nginx.ingress.kubernetes.io/rewrite-target إلى أنك تريد إعادة كتابة هدف URL في أماكن. يحتوي spec.rules.http.paths على تكوين بخصوص توجيهات المسار الفردية التي رأيتها سابقًا داخل ملف nginx/production.conf. يتوافق paths.pathType مع نوع المسار. بشكل افتراضي هو Prefix في NGINX. يشير حقل paths.path إلى المسار الذي يجب توجيهه. backend.service.name هي الخدمة التي يجب توجيه المسار المذكور إليها و backend.service.port.number هو المنفذ المستهدف داخل تلك الخدمة. /?(.*) و /api/?(.*) هي تعبيرات منتظمة بسيطة تعني أن جزء ?(.*) سيتم توجيهه نحو الخدمات المعينة. يمكن أن تتغير طريقة تكوين إعادة الكتابة من وقت لآخر، لذا فإن التحقق من الوثائق الرسمية سيكون فكرة جيدة.

قبل تطبيق التكوينات الجديدة، سيتعين عليك تفعيل إضافة ingress لـ minikube باستخدام الأمر addons. الصيغة العامة هي كالتالي:

minikube addons <option> <addon name>

لتفعيل إضافة ingress، نفذ الأمر التالي:

minikube addons enable ingress
# ? Verifying ingress addon...
# ? The 'ingress' addon is enabled

يمكنك استخدام خيار disable لأمر addon لتعطيل أي إضافة. يمكنك معرفة المزيد عن أمر addon في الوثائق الرسمية. بمجرد تفعيل الإضافة، يمكنك تطبيق ملفات التكوين. أقترح حذف جميع الموارد (الخدمات، عمليات النشر، ومطالبات وحدات التخزين المستمرة) قبل تطبيق الموارد الجديدة.

kubectl delete ingress --all
# ingress.networking.k8s.io "ingress-service" deleted
kubectl delete service --all
# service "api-cluster-ip-service" deleted
# service "client-cluster-ip-service" deleted
# service "kubernetes" deleted
# service "postgres-cluster-ip-service" deleted
kubectl delete deployment --all
# deployment.apps "api-deployment" deleted
# deployment.apps "client-deployment" deleted
# deployment.apps "postgres-deployment" deleted
kubectl delete persistentvolumeclaim --all
# persistentvolumeclaim "database-persistent-volume-claim" deleted
kubectl apply -f k8s
# service/api-cluster-ip-service created
# deployment.apps/api-deployment created
# service/client-cluster-ip-service created
# deployment.apps/client-deployment created
# persistentvolumeclaim/database-persistent-volume-claim created
# ingress.networking.k8s.io/ingress-service created
# service/postgres-cluster-ip-service created
# deployment.apps/postgres-deployment created

انتظر حتى يتم إنشاء جميع الموارد. يمكنك استخدام الأمر get للتأكد من ذلك. بمجرد تشغيل جميعها، يمكنك الوصول إلى التطبيق على عنوان IP لمجموعة minikube. للحصول على عنوان IP، يمكنك تنفيذ الأمر التالي:

minikube ip
# 172.17.0.2

يمكنك أيضًا الحصول على عنوان IP هذا عن طريق فحص Ingress:

kubectl get ingress
# NAME CLASS HOSTS ADDRESS PORTS AGE
# ingress-service <none> * 172.17.0.2 80 2m33s

كما ترى، عنوان IP والمنفذ مرئيان تحت عمودي ADDRESS و PORTS. بالوصول إلى 127.17.0.2:80، يجب أن تهبط مباشرة على تطبيق الملاحظات.

تطبيق الملاحظات الكامل المكدس يعمل عبر Ingress

يمكنك إجراء عمليات CRUD بسيطة في هذا التطبيق. المنفذ 80 هو المنفذ الافتراضي لـ NGINX، لذا لا تحتاج إلى كتابة رقم المنفذ في عنوان URL. يمكنك فعل الكثير باستخدام وحدة التحكم بالـ ingress هذه إذا كنت تعرف كيفية تكوين NGINX. بعد كل شيء، هذا هو الغرض من وحدة التحكم هذه – تخزين تكوينات NGINX على ConfigMap في Kubernetes، والذي ستتعلم عنه في القسم الفرعي التالي.

الـ Secrets والـ Config Maps في Kubernetes

حتى الآن في عمليات النشر الخاصة بك، قمت بتخزين معلومات حساسة مثل POSTGRES_PASSWORD كنص عادي، وهي ليست فكرة جيدة جدًا. لتخزين مثل هذه القيم في مجموعتك، يمكنك استخدام Secret وهي طريقة أكثر أمانًا لتخزين كلمات المرور والرموز المميزة وما إلى ذلك. قد لا تعمل الخطوة التالية بنفس الطريقة في سطر أوامر Windows. يمكنك استخدام git bash أو cmder للمهمة. لتخزين المعلومات في Secret، يجب عليك أولاً تمرير بياناتك عبر base64. إذا كانت كلمة المرور النصية العادية هي 63eaQB9wtLqmNBpg، فنفذ الأمر التالي للحصول على نسخة مشفرة بـ base64:

echo -n "63eaQB9wtLqmNBpg" | base64
# NjNlYVFCOXd0THFtTkJwZw==

هذه الخطوة ليست اختيارية، يجب عليك تشغيل السلسلة النصية العادية عبر base64. الآن أنشئ ملفًا باسم postgres-secret.yaml داخل دليل k8s وضع المحتوى التالي فيه:

apiVersion: v1
kind: Secret
metadata:
  name: postgres-secret
data:
  password: NjNlYVFCOXd0THFtTkJwZw==

تعتبر حقول apiVersion و kind و metadata واضحة بذاتها. يحتوي حقل data على السر الفعلي. كما ترى، لقد أنشأت زوجًا من المفتاح والقيمة حيث المفتاح هو password والقيمة هي NjNlYVFCOXd0THFtTkJwZw==. ستستخدم قيمة metadata.name لتحديد هذا الـ Secret في ملفات التكوين الأخرى والمفتاح للوصول إلى قيمة كلمة المرور. الآن لاستخدام هذا السر داخل تكوين قاعدة البيانات الخاصة بك، حدث ملف postgres-deployment.yaml كالتالي:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      component: postgres
  template:
    metadata:
      labels:
        component: postgres
    spec:
      volumes:
        - name: postgres-storage
          persistentVolumeClaim:
            claimName: database-persistent-volume-claim
      containers:
        - name: postgres
          image: fhsinchy/notes-postgres
          ports:
            - containerPort: 5432
          volumeMounts:
            - name: postgres-storage
              mountPath: /var/lib/postgresql/data
              subPath: postgres
          env:
            # not putting the password directly anymore
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: postgres-secret
                  key: password
            - name: POSTGRES_DB
              value: notesdb

كما ترى، الملف بأكمله هو نفسه باستثناء حقل spec.template.spec.containers.env. كان متغير البيئة name المستخدم لتخزين قيمة كلمة المرور نصًا عاديًا من قبل. ولكن الآن يوجد حقل valueFrom.secretKeyRef جديد. يشير حقل name هنا إلى اسم الـ Secret الذي أنشأته قبل لحظات، وتشير قيمة key إلى المفتاح من زوج المفتاح والقيمة في ملف تكوين الـ Secret هذا. سيتم فك تشفير القيمة المشفرة إلى نص عادي داخليًا بواسطة Kubernetes.

بصرف النظر عن تكوين قاعدة البيانات، سيتعين عليك أيضًا تحديث ملف api-deployment.yaml كالتالي:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      component: api
  template:
    metadata:
      labels:
        component: api
    spec:
      containers:
        - name: api
          image: fhsinchy/notes-api
          ports:
            - containerPort: 3000
          env:
            - name: DB_CONNECTION
              value: pg
            - name: DB_HOST
              value: postgres-cluster-ip-service
            - name: DB_PORT
              value: '5432'
            - name: DB_USER
              value: postgres
            - name: DB_DATABASE
              value: notesdb
            # not putting the password directly anymore
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: postgres-secret
                  key: password

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

kubectl apply -f k8s
# service/api-cluster-ip-service created
# deployment.apps/api-deployment created
# service/client-cluster-ip-service created
# deployment.apps/client-deployment created
# persistentvolumeclaim/database-persistent-volume-claim created
# secret/postgres-secret created
# ingress.networking.k8s.io/ingress-service created
# service/postgres-cluster-ip-service created
# deployment.apps/postgres-deployment created

اعتمادًا على حالة مجموعتك، قد ترى مجموعة مختلفة من المخرجات. في حال واجهت أي مشكلة، احذف جميع موارد Kubernetes وأعد إنشائها عن طريق تطبيق التكوينات. استخدم الأمر get للفحص والتأكد من أن جميع الـ pods قيد التشغيل. الآن لاختبار التكوين الجديد، ادخل إلى تطبيق الملاحظات باستخدام minikube IP وحاول إنشاء ملاحظات جديدة. للحصول على عنوان IP، يمكنك تنفيذ الأمر التالي:

minikube ip
# 172.17.0.2

بالوصول إلى 127.17.0.2:80، يجب أن تهبط مباشرة على تطبيق الملاحظات.

تطبيق الملاحظات يعمل مع كلمات مرور آمنة باستخدام Secrets

هناك طريقة أخرى لإنشاء الـ secrets بدون أي ملف تكوين. لإنشاء نفس الـ Secret باستخدام kubectl، نفذ الأمر التالي:

kubectl create secret generic postgres-secret --from-literal=password=63eaQB9wtLqmNBpg
# secret/postgres-secret created

هذا نهج أكثر ملاءمة حيث يمكنك تخطي خطوة تشفير base64 بالكامل. سيتم تشفير السر في هذه الحالة تلقائيًا. ConfigMap مشابه لـ Secret ولكنه مخصص للاستخدام مع المعلومات غير الحساسة. لوضع جميع متغيرات البيئة الأخرى في نشر واجهة برمجة التطبيقات داخل ConfigMap، أنشئ ملفًا جديدًا باسم api-config-map.yaml داخل دليل k8s وضع المحتوى التالي فيه:

apiVersion: v1
kind: ConfigMap
metadata:
  name: api-config-map
data:
  DB_CONNECTION: pg
  DB_HOST: postgres-cluster-ip-service
  DB_PORT: '5432'
  DB_USER: postgres
  DB_DATABASE: notesdb

تعتبر حقول apiVersion و kind و metadata واضحة بذاتها مرة أخرى. يمكن أن يحتوي حقل data على متغيرات البيئة كأزواج مفتاح-قيمة. على عكس الـ Secret، يجب أن تتطابق المفاتيح هنا مع المفتاح الدقيق المطلوب بواسطة واجهة برمجة التطبيقات. وبالتالي، لقد قمت بنسخ المتغيرات من ملف api-deployment.yaml ولصقها هنا مع تعديل طفيف في الصيغة. لاستخدام هذا السر في نشر واجهة برمجة التطبيقات، افتح ملف api-deployment.yaml وحدث محتواه كالتالي:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      component: api
  template:
    metadata:
      labels:
        component: api
    spec:
      containers:
        - name: api
          image: fhsinchy/notes-api
          ports:
            - containerPort: 3000
          # not putting environment variables directly
          envFrom:
            - configMapRef:
                name: api-config-map
          env:
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: postgres-secret
                  key: password

الملف بأكمله لم يتغير تقريبًا باستثناء حقل spec.template.spec.containers.env. لقد نقلت متغيرات البيئة إلى ConfigMap. يُستخدم spec.template.spec.containers.envFrom للحصول على البيانات من ConfigMap. يشير configMapRef.name هنا إلى ConfigMap الذي سيتم سحب متغيرات البيئة منه. الآن طبق جميع هذه التكوينات الجديدة بتنفيذ الأمر التالي:

kubectl apply -f k8s
# service/api-cluster-ip-service created
# configmap/api-config-map created
# deployment.apps/api-deployment created
# service/client-cluster-ip-service created
# deployment.apps/client-deployment created
# persistentvolumeclaim/database-persistent-volume-claim created
# ingress.networking.k8s.io/ingress-service configured
# service/postgres-cluster-ip-service created
# deployment.apps/postgres-deployment created
# secret/postgres-secret created

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

minikube ip
# 172.17.0.2

بالوصول إلى 127.17.0.2:80، يجب أن تهبط مباشرة على تطبيق الملاحظات.

تطبيق الملاحظات يعمل مع ConfigMaps لمتغيرات البيئة

يحتوي كل من Secret و ConfigMap على بعض الحيل الأخرى التي لن أتعمق فيها الآن. ولكن إذا كنت فضوليًا، يمكنك مراجعة الوثائق الرسمية.

تنفيذ عمليات طرح التحديثات في Kubernetes

الآن بعد أن قمت بنشر تطبيق يتكون من حاويات متعددة بنجاح على Kubernetes، حان الوقت للتعرف على تنفيذ التحديثات. على الرغم من أن Kubernetes قد يبدو سحريًا لك، إلا أن تحديث حاوية إلى إصدار صورة أحدث يمثل بعض الصعوبة. هناك طرق متعددة يتخذها الأشخاص غالبًا لتحديث حاوية، لكنني لن أتناولها جميعًا. بدلاً من ذلك، سأنتقل مباشرة إلى النهج الذي أتبعه غالبًا في تحديث حاوياتي. إذا فتحت ملف client-deployment.yaml ونظرت إلى حقل spec.template.spec.containers، ستجد شيئًا يبدو كالتالي:

containers:
  - name: client
    image: fhsinchy/notes-client

كما ترى، في حقل image لم أستخدم أي وسم (tag) للصورة. الآن إذا كنت تعتقد أن إضافة :latest في نهاية اسم الصورة سيضمن أن النشر يسحب دائمًا أحدث صورة متاحة، فستكون مخطئًا تمامًا. النهج الذي أتبعه عادة هو نهج إلزامي (imperative one). لقد ذكرت بالفعل في قسم سابق أنه في بعض الحالات، يكون استخدام نهج إلزامي بدلاً من نهج تصريحي فكرة جيدة. إنشاء Secret أو تحديث حاوية هو إحدى هذه الحالات.

الأمر الذي يمكنك استخدامه لتنفيذ التحديث هو الأمر set، والصيغة العامة هي كالتالي:

kubectl set image <resource type>/<resource name> <container name>=<image name with tag>

نوع المورد هو deployment واسم المورد هو client-deployment. يمكن العثور على اسم الحاوية تحت حقل containers داخل ملف client-deployment.yaml، وهو client في هذه الحالة. لقد قمت بالفعل ببناء إصدار من صورة fhsinchy/notes-client بوسم edge سأستخدمه لتحديث هذا النشر. لذا يجب أن يكون الأمر النهائي كالتالي:

kubectl set image deployment/client-deployment client=fhsinchy/notes-client:edge
# deployment.apps/client-deployment image updated

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

minikube ip
# 172.17.0.2

بالوصول إلى 127.17.0.2:80، يجب أن تهبط مباشرة على تطبيق الملاحظات.

تطبيق الملاحظات يعمل بعد تحديث الصورة

نظرًا لأنني لم أجرِ أي تغييرات فعلية على كود التطبيق، سيبقى كل شيء كما هو. يمكنك التأكد من أن الـ pods تستخدم الصورة الجديدة باستخدام الأمر describe.

kubectl describe pod client-deployment-849bc58bcc-gz26b | grep 'Image'
# Image: fhsinchy/notes-client:edge
# Image ID: docker-pullable://fhsinchy/notes-client@sha256:58bce38c16376df0f6d1320554a56df772e30a568d251b007506fd3b5eb8d7c2

الأمر grep متاح على Mac و Linux. إذا كنت تستخدم Windows، فاستخدم git bash بدلاً من سطر أوامر Windows. على الرغم من أن عملية التحديث الإلزامية مملة بعض الشيء، إلا أنه يمكن جعلها أسهل بكثير باستخدام سير عمل CI/CD جيد.

دمج التكوينات

كما رأيت بالفعل، فإن عدد ملفات التكوين في هذا المشروع كبير جدًا على الرغم من أنه يحتوي على ثلاث حاويات فقط. يمكنك في الواقع دمج ملفات التكوين كالتالي:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: client-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      component: client
  template:
    metadata:
      labels:
        component: client
    spec:
      containers:
        - name: client
          image: fhsinchy/notes-client
          ports:
            - containerPort: 8080
          env:
            - name: VUE_APP_API_URL
              value: /api
---
apiVersion: v1
kind: Service
metadata:
  name: client-cluster-ip-service
spec:
  type: ClusterIP
  selector:
    component: client
  ports:
    - port: 8080
      targetPort: 8080

كما ترى، لقد قمت بدمج محتويات ملفي client-deployment.yaml و client-cluster-ip-service.yaml باستخدام فاصل (---). على الرغم من أن هذا ممكن ويمكن أن يساعد في المشاريع التي يكون فيها عدد الحاويات مرتفعًا جدًا، إلا أنني أوصي بإبقائها منفصلة ونظيفة وموجزة.

استكشاف الأخطاء وإصلاحها

في هذا القسم، سأدرج بعض المشكلات الشائعة التي قد تواجهها أثناء عملك مع Kubernetes:

  • إذا كنت تستخدم نظام Windows أو Mac وتستخدم مشغل Docker لـ minikube، فلن تعمل إضافة Ingress.
  • إذا كان لديك Laravel Valet يعمل على Mac وتستخدم مشغل HyperKit لـ minikube، فسيفشل في الاتصال بالإنترنت. سيؤدي إيقاف خدمة dnsmasq إلى حل المشكلة.
  • إذا كان لديك جهاز كمبيوتر بمعالج Ryzen (معالجي هو R5 1600) وتشغل Windows 10، فقد يفشل مشغل VirtualBox في البدء بسبب نقص الدعم للـ nested virtualization (المحاكاة الافتراضية المتداخلة). سيتعين عليك استخدام مشغل Hyper-V على Windows 10 (إصدارات Pro و Enterprise و Education). بالنسبة لمستخدمي إصدار Home، للأسف لا يوجد خيار آمن على هذا الجهاز.
  • إذا كنت تشغل Windows 10 (إصدارات Pro و Enterprise و Education) مع مشغل Hyper-V لـ minikube، فقد يفشل الجهاز الافتراضي في البدء برسالة تتعلق بذاكرة غير كافية. لا داعي للذعر، ونفذ الأمر minikube start مرة أخرى لبدء تشغيل الجهاز الافتراضي بشكل صحيح.
  • إذا رأيت بعض الأوامر المنفذة في هذا المقال مفقودة أو تعمل بشكل غير صحيح في سطر أوامر Windows، فاستخدم git bash أو cmder بدلاً من ذلك.
  • إذا كنت تستخدم جهاز Raspberry Pi، فلن تعمل صوري. استخدم الصور التي بناها رائد شمام. يمكن العثور على جميع الصور الثلاث على ملفه الشخصي على Docker Hub. يمكن العثور على تعليمات بخصوص بناء الصور لـ Raspberry Pi في مشكلة GitHub هذه.
  • أقترح تثبيت توزيعة Linux جيدة على نظامك واستخدام مشغل Docker لـ minikube. هذا هو أسرع وأكثر الإعدادات موثوقية حتى الآن.

الخلاصة التقنية

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

أود أن أشكرك من أعماق قلبي على الوقت الذي قضيته في قراءة هذا المقال. آمل أن تكون قد استمتعت بوقتك وتعلمت جميع أساسيات Kubernetes. بصرف النظر عن هذا الدليل، لقد كتبت أدلة شاملة حول مواضيع معقدة أخرى متاحة مجانًا على freeCodeCamp. هذه الأدلة هي جزء من مهمتي لتبسيط التقنيات الصعبة الفهم للجميع. يستغرق كل من هذه الأدلة الكثير من الوقت والجهد في الكتابة. إذا استمتعت بكتاباتي وتريد أن تبقيني متحفزًا، فكر في ترك نجوم على GitHub وادعمني للمهارات ذات الصلة على LinkedIn. أقبل أيضًا الرعاية، لذا يمكنك التفكير في شراء لي قهوة إذا أردت. أنا دائمًا منفتح على الاقتراحات والمناقشات على Twitter أو LinkedIn. راسلني مباشرة. في النهاية، فكر في مشاركة الموارد مع الآخرين، لأن مشاركة المعرفة هي أسمى أفعال الصداقة. لأنها طريقة يمكنك بها أن تعطي شيئًا دون أن تخسر شيئًا. — ريتشارد ستالمان. حتى المرة القادمة، ابقَ آمنًا واستمر في التعلم.

فرحان حسين شودري، مطور برامج يتمتع بمهارة في تعلم أشياء جديدة والكتابة عنها. إذا كان هذا المقال مفيدًا، فشاركه. تعلم البرمجة مجانًا. ساعد منهج freeCodeCamp مفتوح المصدر أكثر من 40,000 شخص في الحصول على وظائف كمطورين. ابدأ.

إعلان

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *