لماذا Kubernetes ولماذا Docker Desktop

11 دقيقة
الجمهور المستهدف
مطوّر أو طالب سبق له أن كتب docker run وdocker compose up، لكنه لم يكتب kubectl قط
المدة
25 إلى 35 دقيقة
الوحدة
1/8
الكفاءة المستهدفة
شرح ما يضيفه Kubernetes إلى Docker، وتسمية مكوّنات control plane وعقدة (node)، وتبرير اختيار Docker Desktop للتعلّم

في صورة واحدة

تعرف منظّم الحرارة (thermostat) في صالة منزلك: تضبطه على 21 درجة مئوية ولا تعود تفكّر فيه. فهو يقيس درجة الحرارة، يقارنها بما ضبطته، يُشغّل التدفئة أو يوقفها، وإذا فتح أحدهم النافذة فإنه يستدرك الأمر دون أن تتحرّك أنت. يفعل Kubernetes الأمر نفسه تماماً مع حاوياتك (conteneurs). تُعلن «أريد ثلاث نسخ من واجهتي البرمجية (API)، متاحة دائماً على المنفذ 8080»؛ فيقارن باستمرار ما تريده بما يعمل فعلياً، ويصحّح: تموت حاوية في الساعة الثالثة صباحاً، فيُعيد تشغيل واحدة أخرى؛ تتعطّل آلة، فيُعيد وضع الحاويات في مكان آخر؛ تُغيّر إصدار الصورة، فيستبدل النسخ واحدة تلو الأخرى دون قطع الخدمة. مع docker run، أنت منظّم الحرارة: أنت من يراقب ويُعيد التشغيل. في هذا الدرس، سترى الفرق بعينيك: تقتل حاوية Docker وتلاحظ أنها تبقى ميتة، ثم تنظر إلى «قطع» Kubernetes التي تعمل بالفعل على جهازك، جاهزة لأداء هذا العمل بدلاً منك.

كيف يعمل الأمر

ثلاثة مواقف يعيشها الجميع في النهاية مع Docker وحده. الأول: تعمل واجهتك البرمجية داخل حاوية على خادم؛ في إحدى الأمسيات تتعطّل (تسريب ذاكرة، اعتمادية خارجية معطّلة) ولا يلاحظ أحد ذلك حتى الصباح. يُعيد --restart=always تشغيل العملية، لكنه لا يُخبرك بشيء، ولا يتحقق من أن التطبيق يستجيب فعلاً، ولا يفعل شيئاً إذا انطفأ الخادم كلياً. الثاني: لديك عشر حاويات لنفس الواجهة البرمجية خلف وكيل عكسي (reverse proxy) وعليك تسليم الإصدار 2 دون أن يرى المستخدمون أي خطأ؛ يدوياً، يعني ذلك إيقاف وإعادة تشغيل وإزالة كل حاوية من الوكيل بالترتيب الصحيح، عشر مرات، دون خطأ. الثالث: لم يعد حركة المرور تتّسع على آلة واحدة؛ عليك إضافة اثنتين أخريين، وتقرير أي حاوية تذهب إلى أين، وجعل الحاويات تجد بعضها بعضاً رغم أن عناوين IP الخاصة بها تتغيّر مع كل إعادة تشغيل.

هذه المشاكل الثلاث هي مهنة المُنسِّق (orchestrateur). يقترح Docker حلَّين جزئيَّين: يصف Compose عدة حاويات في ملف واحد لكنه يبقى على آلة واحدة ولا يراقب شيئاً؛ ويضيف Swarm تعدد الآلات والنسخ المتماثلة لكن منظومته بقيت صغيرة. أصبح Kubernetes (يُختصر K8s: «K»، ثم ثماني حروف، ثم «s»)، الذي وُلد لدى Google سنة 2014 وتديره اليوم مؤسسة CNCF، هو المعيار السائد: ستجده لدى AWS (باسم EKS)، وGoogle (باسم GKE)، وAzure (باسم AKS)، وOVH، وScaleway، وعلى خوادم معظم الشركات.

المعيارDocker ComposeDocker SwarmKubernetes
النطاقآلة واحدةعدة آلاتعدة آلات (بالآلاف)
إعادة التشغيل التلقائيrestart: فقطنعمنعم، مع مسابر صحة (readiness، liveness)
التحديث دون انقطاعلانعم، بشكل بسيطنعم، بشكل تدريجي، مع تراجع بأمر واحد
توزيع الحمل المدمجلانعمنعم (Services)، إضافة إلى Ingress وDNS داخلي
التحجيم التلقائيلالانعم (HPA، بناءً على المعالج أو الذاكرة أو مقاييس الأعمال)
المنظومة (Helm، المشغّلات (operators)، السحابة)صغيرةضخمة
منحنى التعلّمسهلمتوسطحاد، ومن هنا هذه الدورة

تتكوّن كتلة Kubernetes من جزأين. control plane يقرّر؛ العقد (nœuds) تنفّذ. على Docker Desktop، يعيش الجزآن على نفس الآلة الافتراضية، لكن الأدوار تبقى نفسها كما في بيئة الإنتاج.

المكوّنأينالدور في جملة واحدة
kube-apiservercontrol planeيستقبل كل الأوامر (kubectl، المتحكّمات، kubelet) عبر HTTPS على المنفذ 6443؛ لا يحدث شيء دون المرور به.
etcdcontrol planeقاعدة بيانات مفتاح-قيمة تخزّن الحالة المرغوبة والحالة الملاحظة لكامل الكتلة؛ فقدانها بدون نسخة احتياطية يعني فقدان الكتلة.
kube-schedulercontrol planeيختار على أي عقدة يضع كل Pod جديدة (الموارد المتاحة، القيود). لا يُشغّل شيئاً بنفسه.
kube-controller-managercontrol planeيجمع حلقات التوفيق: «تنقص نسخة متماثلة؟ أُنشئ واحدة»، «عقدة توقّفت عن الاستجابة؟ أُعلّم الـ Pods الخاصة بها لإعادة وضعها».
kubeletكل عقدةالوكيل الذي يقرأ من الـ API الـ Pods المُسندة إليه، ويطلب من runtime تشغيل الحاويات، ويرفع حالتها.
kube-proxyكل عقدةيبرمج قواعد الشبكة كي يصل عنوان الـ Service إلى الـ Pod الصحيحة.
runtimeكل عقدةالبرنامج الذي يُشغّل الحاويات فعلياً: containerd أو CRI-O عموماً، وdocker://29.3.1 على Docker Desktop.

جوهر النموذج هو إعلان الحالة المرغوبة. أنت لا تأمر بـ«شغّل حاوية»؛ بل تكتب (أو تُولّد) كائناً يقول «أريد Deployment باسم api، بصورة traefik/whoami:v1.10، بثلاث نسخ متماثلة». يُسجّل الـ API ذلك في etcd. تقارن المتحكّمات (controllers) بعد ذلك، في حلقة دائمة ومستمرة، هذه الحالة المرغوبة بـالحالة الملاحظة التي ترفعها الـ kubelets؛ وكل انحراف يُطلق إجراءً. لهذا السبب تُبعث Pod محذوفة من جديد: لم يُعِد أحد «تشغيلها»، بل لاحظ المتحكّم ببساطة 2 بدلاً من 3 وصحّح الأمر. سترى الحلقة تعمل في الدرس 04.

لماذا التعلّم على Docker Desktop بدلاً من minikube أو kind أو كتلة سحابية؟ لأنك على الأرجح تملكه بالفعل، ولأن نقرة واحدة تُفعّل كتلة كاملة، ولأن الصور التي تبنيها بأمر docker build مرئية مباشرة من طرف Kubernetes (نفس مخزن الصور، لا حاجة لتثبيت سجلّ صور)، ولأن Services من نوع LoadBalancer تحصل على عنوان localhost: تفتح متصفّحك ويستجيب، تماماً كما في الإنتاج خلف موازن حمل حقيقي. حدوده واضحة أيضاً: عقدة واحدة فقط (لا وجود لـ«آلة تتعطّل» لمحاكاتها)، ولا NetworkPolicy مطبَّقة افتراضياً (يتجاهلها CNI المدمج)، ولا تخزين شبكي ولا عنوان IP عمومي. كل ما ستكتبه هنا سيُطبَّق كما هو على كتلة حقيقية؛ فقط طريقة الوصول إليها ستتغيّر.

الخيارالتثبيتالعقدLoadBalancer على localhostصور محلية بدون سجلّلمن
Docker Desktop (هذه الدورة)نقرة واحدة في التطبيق1 (kubeadm) أو أكثر (kind، 4.38+)نعمنعمالتعلّم والتطوير على جهازك
minikubeملف تنفيذي + برنامج تشغيل (Docker، VM)1، تعدد ممكنعبر minikube tunnelعبر minikube image loadالتعلّم، إضافات جاهزة
kindملف تنفيذي، العقد = حاويات Dockerعدة عقدلا (port-forward)عبر kind load docker-imageالاختبارات الآلية، CI
السحابة (EKS، GKE، AKS…)حساب، فوترة، شبكةبقدر ما تشاءعنوان IP عمومي حقيقيلا، يلزم سجلّ صورالإنتاج

خطوة بخطوة

تُكتب كل الأوامر في PowerShell (على Windows) أو في طرفية (على macOS وLinux)؛ وهي متطابقة. لا شيء مما يلي يُعدّل كتلتك: أنت تُلاحظ فقط.

  1. شغّل حاوية باستخدام Docker، كما تعرف فعله بالفعل. traefik/whoami تطبيق ويب صغير يعرض اسم الآلة التي تستجيب: إنها الصورة الخيط الناظم للوحدات الثلاث الأولى.

    bash
    docker run -d --name essai-whoami -p 8089:80 traefik/whoami:v1.10
    docker ps --filter name=essai-whoami --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}"

    المخرجات الحقيقية:

    text
    a088124875835ed375ddb25a4e4246faf62416a9772751ec7eedecc48bb758f8
    NAMES          IMAGE                  STATUS                  PORTS
    essai-whoami   traefik/whoami:v1.10   Up Less than a second   0.0.0.0:8089->80/tcp, [::]:8089->80/tcp

    ما يجب ملاحظته: سطر واحد، Up، والمنفذ 8089 من جهازك موجَّه نحو المنفذ 80 داخل الحاوية.

  2. اسأله. المعرّف Hostname المُعاد هو معرّف الحاوية: احتفظ بهذا التفصيل، فسيصبح اسم Pod في الدرس 04.

    bash
    curl -s http://localhost:8089

    على Windows، اكتب curl.exe (فـcurl بدون امتداد هو اسم مستعار في PowerShell لـInvoke-WebRequest). المخرجات الحقيقية (أول سطرين):

    text
    Hostname: a08812487583
    IP: 127.0.0.1
  3. انتبه جيداً لما سيحدث: قتل الحاوية. إنه عطل الساعة الثالثة صباحاً، بشكل مُسرَّع.

    bash
    docker rm -f essai-whoami
    docker ps --filter name=essai-whoami --format "table {{.Names}}\t{{.Status}}"

    المخرجات الحقيقية:

    text
    essai-whoami
    NAMES     STATUS

    ما يجب ملاحظته: الجدول فارغ. لن يُعيد أحد تشغيل هذه الحاوية؛ يفشل الآن curl http://localhost:8089 (curl: (52) Empty reply from server، أو (56) Recv failure، أو Connection refused حسب اللحظة التي يُحرّر فيها Docker Desktop المنفذ). فعل Docker ما طُلب منه، لا أكثر ولا أقل. احتفظ بهذه الصورة الذهنية: في الدرس 04، ستفعل نفس الشيء بـ Pod وستعود قبل أن تنتهي من إعادة قراءة السطر.

  4. تحقّق من وجود Kubernetes. إذا لم تُفعّل بعد Kubernetes في Docker Desktop، انتقل إلى الدرس 02 ثم عُد؛ وإلا، انظر إلى كتلتك.

    bash
    kubectl get nodes

    المخرجات الحقيقية:

    text
    NAME             STATUS   ROLES           AGE   VERSION
    docker-desktop   Ready    control-plane   18d   v1.34.1

    ما يجب ملاحظته: عقدة واحدة، Ready، بدور control-plane. على Docker Desktop، الآلة التي تقرّر وتلك التي تنفّذ هما نفس الآلة. AGE هو عمر كتلتك، وVERSION إصدار Kubernetes (1.34 هنا).

  5. المس control plane بإصبعك. المكوّنات الأربعة «التي تقرّر» تعمل هي نفسها كـ Pods، في namespace محجوز باسم kube-system، بعلامة (label) tier=control-plane.

    bash
    kubectl get pods -n kube-system -l tier=control-plane

    المخرجات الحقيقية:

    text
    NAME                                     READY   STATUS    RESTARTS   AGE
    etcd-docker-desktop                      1/1     Running   0          18d
    kube-apiserver-docker-desktop            1/1     Running   0          18d
    kube-controller-manager-docker-desktop   1/1     Running   0          18d
    kube-scheduler-docker-desktop            1/1     Running   0          18d

    ما يجب ملاحظته: etcd، وواجهة الـ API، ومدير المتحكّمات، والمُجدوِل، كل واحد 1/1 Running، بدون أي إعادة تشغيل (0). ينتهي اسم كل منها باسم العقدة التي تستضيفه. قارن مع جدول «كيف يعمل الأمر»: أصبح لديك الآن وجه لكل دور.

  6. اطّلع على بقية kube-system. بدون المرشّح (filter)، تكتشف مكوّنات العقدة والإضافات الخاصة بـ Docker Desktop.

    bash
    kubectl get pods -n kube-system

    المخرجات الحقيقية على آلة الدورة (سطر metrics-server يأتي من مكوّن سنُثبّته في الوحدة 6: من الطبيعي ألا يكون لديك بعد):

    text
    NAME                                     READY   STATUS    RESTARTS   AGE
    coredns-66bc5c9577-6gwhl                 1/1     Running   0          18d
    coredns-66bc5c9577-9p89d                 1/1     Running   0          18d
    etcd-docker-desktop                      1/1     Running   0          18d
    kube-apiserver-docker-desktop            1/1     Running   0          18d
    kube-controller-manager-docker-desktop   1/1     Running   0          18d
    kube-proxy-2ddzv                         1/1     Running   0          18d
    kube-scheduler-docker-desktop            1/1     Running   0          18d
    metrics-server-7c5fdf4664-6882s          1/1     Running   0          26m
    storage-provisioner                      1/1     Running   0          18d
    vpnkit-controller                        1/1     Running   0          18d

    ما يجب ملاحظته: kube-proxy (قواعد شبكة العقدة)، وcoredns مرّتين (DNS الداخلي الذي سيسمح لـfrontend باستدعاء api باسمه)، وقطعتان خاصتان بـ Docker Desktop: storage-provisioner (يُنشئ أحجام (volumes) من نوع hostpath عند الطلب، الوحدة 5) وvpnkit-controller (وهو من يمنح localhost لـ Services من نوع LoadBalancer، الدرس 04). أما الـ kubelet، فلا يظهر: فهو ليس Pod بل خدمة نظام على مستوى العقدة.

  7. اختياري: شاهد أن Kubernetes ما هو «إلا» حاويات. إذا كان خيار «Show system containers» مفعّلاً في Docker Desktop (الدرس 02)، يُظهر لك Docker الحاويات التي تحمل control plane. إنه نفس Docker الذي في الخطوة 1.

    bash
    docker ps --filter "name=k8s_kube-apiserver_" --filter "name=k8s_etcd_" --filter "name=k8s_kube-scheduler_" --filter "name=k8s_kube-controller-manager_" --format "table {{.Names}}\t{{.Status}}"

    المخرجات الحقيقية:

    text
    NAMES                                                                                                               STATUS
    k8s_etcd_etcd-docker-desktop_kube-system_0b753cb7812d40a401f3a8f63b18f779_0                                         Up 44 minutes
    k8s_kube-apiserver_kube-apiserver-docker-desktop_kube-system_647244f1c75810d936baf4253b7903ef_0                     Up 44 minutes
    k8s_kube-scheduler_kube-scheduler-docker-desktop_kube-system_b44739859c757a4712b786569a89a1f3_0                     Up 44 minutes
    k8s_kube-controller-manager_kube-controller-manager-docker-desktop_kube-system_10b0d524eef4b9a12d5827ba17a36f4f_0   Up 44 minutes

    ما يجب ملاحظته: اسم Docker مبنيّ على الصيغة k8s_<حاوية>_<pod>_<namespace>_<uid>_<n>. إذا لم يكن الخيار مفعّلاً، يكون الجدول فارغاً: لا شيء خطير، إنه مجرد إعداد عرض. لا يتناقض AGE البالغ 18 يوماً في Kubernetes مع Up 44 minutes في Docker: فالكتلة موجودة منذ 18 يوماً، وأُعيد تشغيل Docker Desktop قبل 44 دقيقة وأعاد إنشاء حاوياته دون فقدان الحالة، المخزّنة في etcd.

  8. أين نتحدّث؟ أمر أخير لتحديد بوابة الدخول التي ستمرّ منها كل الأوامر الأخرى.

    bash
    kubectl cluster-info

    المخرجات الحقيقية:

    text
    Kubernetes control plane is running at https://kubernetes.docker.internal:6443
    CoreDNS is running at https://kubernetes.docker.internal:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
    
    To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

    ما يجب ملاحظته: تستمع واجهة الـ API عبر HTTPS على المنفذ 6443 من kubernetes.docker.internal، وهو اسم يجعله Docker Desktop يُشير إلى جهازك. عندما يكون Docker Desktop متوقفاً، فإن هذا المنفذ هو من «يرفض الاتصال» (الدرس 02، «إذا واجهت مشكلة»). لا شيء لتنظيفه: حُذفت الحاوية essai-whoami في الخطوة 3 ولم تُنشئ شيئاً في Kubernetes.

إذا واجهت مشكلة

  • docker: Error response from daemon: failed to set up container networking: … Bind for 0.0.0.0:8089 failed: port is already allocated ← حاوية أخرى تنشر المنفذ 8089 بالفعل (رسالة تظهر عند تشغيل الخطوة 1 مرّتين؛ إذا كان برنامج خارج Docker يشغل المنفذ، تقول الرسالة ports are not available). غيّر المنفذ على جهازك (-p 8090:80) أو حدّد من يشغله: Get-NetTCPConnection -LocalPort 8089 (PowerShell) / lsof -i :8089 (macOS، Linux).
  • kubectl : Le terme 'kubectl' n'est pas reconnu (أو kubectl: command not found) ← Kubernetes غير مفعّل، أو أن الطرفية فُتحت قبل التفعيل ولم تُعِد تحميل PATH. الدرس 02 لتفعيله؛ ثم أغلق الطرفية وأعد فتحها.
  • Unable to connect to the server: dial tcp 127.0.0.1:6443: connectex: No connection could be made because the target machine actively refused it. ← لا أحد يستمع على المنفذ 6443: Docker Desktop متوقف أو Kubernetes معطّل. شغّل Docker Desktop، انتظر ظهور «Kubernetes is up and running» في واجهة Kubernetes، ثم أعد المحاولة.
  • kubectl get pods -n kube-system -l tier=control-plane يُعيد No resources found ← من المحتمل أنك على كتلة أخرى (سياق minikube، kind-…) حيث لا يحمل control plane نفس العلامة أو غير مرئي. يجب أن يجيب kubectl config current-context بـdocker-desktop؛ يشرح الدرس 03 كيفية التصحيح.
  • الأمر docker ps --filter "name=k8s_…" لا يُظهر شيئاً رغم أن Kubernetes يعمل ← خيار «Show system containers (advanced)» غير مفعّل في إعدادات Kubernetes الخاصة بـ Docker Desktop. ليس عطلاً؛ الخطوة 7 اختيارية.

ما يجب تذكّره

  • ينفّذ Docker حاوية ويتوقّف عند ذلك؛ أما Kubernetes فيحافظ على حالة مرغوبة (كم عدد النسخ، أي صورة، أي منفذ) ويصحّح باستمرار الفرق مع الحالة الملاحظة.
  • control plane = kube-apiserver (بوابة الدخول، المنفذ 6443)، وetcd (ذاكرة الكتلة)، وkube-scheduler (يختار العقدة)، وkube-controller-manager (حلقات التوفيق).
  • العقدة = kubelet (الوكيل)، وkube-proxy (شبكة الـ Services)، وruntime (docker://29.3.1 على Docker Desktop)؛ يُظهر kubectl get pods -n kube-system -l tier=control-plane الأربعة الأولى وهي تعمل.
  • Docker Desktop: نقرة واحدة، عقدة docker-desktop بإصدار v1.34.1، صور محلية مشتركة مع Kubernetes، LoadBalancer على localhost؛ الحدود: عقدة واحدة فقط، ولا NetworkPolicy مطبَّقة، ولا سحابة حقيقية.
  • الملفات البيانية (manifests) التي ستكتبها هنا تعمل دون تغيير على minikube أو kind أو EKS أو GKE أو AKS.
  • الدرس القادم: تثبيت Docker Desktop على نظامك، وتفعيل Kubernetes، ورؤية ظهور السياق docker-desktop.

لمزيد من التعمّق

تُخفي العقدة الوحيدة في Docker Desktop فرقاً مهماً عن كتلة حقيقية: في الإنتاج، يعيش control plane على آلات مخصّصة (غالباً ثلاث، كي تحافظ etcd على النصاب القانوني (quorum))، ولا يستضيف أي Pod تطبيقي، بفضل «taint» باسم node-role.kubernetes.io/control-plane:NoSchedule. هنا، يُظهر kubectl describe node docker-desktop القيمة Taints: <none>: كل شيء يعمل في المكان نفسه. تُفصّل صفحة Kubernetes Components من التوثيق الرسمي كل مكوّن رأيناه في هذا الدرس، مع الاحتمالات الممكنة (cloud-controller-manager، runtimes أخرى).