{"id":"l-dk-4-2","courseId":"docker-kubernetes","lang":"ar","kind":"lesson","summary":"اكتب manifest لـ Deployment خاص بـ notes-api، وافهم كيف يملك الـ ReplicaSets والـ Pods عبر الـ labels والـ selectors، وطبّقه وافحصه ووسّعه انطلاقًا من الملفات.","html":"<p>الأمر المباشر <code dir=\"ltr\">kubectl create deployment</code> من الدرس السابق كان جيدًا لعرض توضيحي، وبلا فائدة لفريق. لا أحد يستطيع مراجعته، ولا أحد يعرف أي خيارات استُخدمت، وفي الأسبوع القادم لا أحد يستطيع تكراره. من الآن فصاعدًا، تعيش إعدادات Kubernetes الخاصة بـ notes-api في ملفات YAML تحت <code dir=\"ltr\">k8s/</code>، بجوار الـ Dockerfile، وتُراجع مثل الكود.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">الـ Pods: الوحدة التي يشغّلها Kubernetes</h2>\n<p>الـ <strong>Pod</strong> هو container واحد أو أكثر تُجدوَل معًا على node واحد، وتتشارك network namespace واحدة (عنوان IP واحد، و<code dir=\"ltr\">localhost</code> واحد) وأي volumes تصرّح بها. معظم الـ Pods لها container رئيسي واحد. والـ container الثاني يكون لشيء مرتبط بالأول ارتباطًا وثيقًا، مثل أداة شحن سجلات أو proxy يجب أن يعيش ويموت معه.</p>\n<p>يبدو manifest الـ Pod الأدنى هكذا:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"yaml\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">YAML</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">apiVersion</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">v1</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">kind</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">Pod</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">metadata</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">notes-api-test</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">spec</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  containers</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    - </span><span style=\"color:#116329;--shiki-dark:#7EE787\">name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">api</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      image</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">ghcr.io/skylane/notes-api:1.4.0</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      ports</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        - </span><span style=\"color:#116329;--shiki-dark:#7EE787\">containerPort</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3000</span></span></code></pre></div>\n<p>لن تنشئ Pods بهذه الطريقة عمليًا. الـ Pod المجرّد لا يُستبدل إذا انهار على node ميت، ولا يمكن تغيير عدد نسخه، ولا يمكن تحديثه إلى image جديدة تدريجيًا. الـ Pods قطيع يُستبدل: كل واحد منها يمكن التخلّص منه، ويحصل على اسم وIP جديدين حين يُستبدل، ويديره شيء فوقه.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">الـ Deployments والـ ReplicaSets والـ Pods</h2>\n<p>الـ <strong>Deployment</strong> هو ذلك الشيء. إنه يملك <strong>ReplicaSet</strong>، ومهمّته الوحيدة إبقاء N من الـ Pods المتطابقة قيد التشغيل. أما مهمّة الـ Deployment فهي إدارة الـ ReplicaSets عبر الزمن: حين تغيّر قالب الـ Pod، ينشئ ReplicaSet جديدًا وينقل الـ Pods إليه، وهذا هو التحديث المتدرّج (rolling update) الذي ستدرسه في القسم الخامس.</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"yaml\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">k8s/deployment.yaml</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">apiVersion</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">apps/v1</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">kind</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">Deployment</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">metadata</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">notes-api</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  labels</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    app.kubernetes.io/name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">notes-api</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">spec</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  replicas</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  selector</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    matchLabels</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      app.kubernetes.io/name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">notes-api</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  template</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    metadata</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      labels</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">        app.kubernetes.io/name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">notes-api</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    spec</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      containers</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        - </span><span style=\"color:#116329;--shiki-dark:#7EE787\">name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">api</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">          image</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">ghcr.io/skylane/notes-api:1.4.0</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">          ports</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">            - </span><span style=\"color:#116329;--shiki-dark:#7EE787\">name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">http</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">              containerPort</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3000</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">          env</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">            - </span><span style=\"color:#116329;--shiki-dark:#7EE787\">name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">PORT</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">              value</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"3000\"</span></span></code></pre></div>\n<p>اقرأه على نصفين. النصف العلوي هو الـ Deployment نفسه: اسمه، وعدد النسخ، والـ Pods التي يملكها (<code dir=\"ltr\">selector</code>). وكل ما تحت <code dir=\"ltr\">template</code> هو مواصفات Pod، المخطّط الذي تُطبع منه كل نسخة. لاحظ أن <code dir=\"ltr\">apiVersion</code> يختلف حسب النوع: الـ Pods في <code dir=\"ltr\">v1</code> الأساسية، والـ Deployments في <code dir=\"ltr\">apps/v1</code>.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 260\" role=\"img\" aria-labelledby=\"l-dk-4-2-t1\"><defs><marker id=\"arrow-l-dk-4-2-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-dk-4-2-t1\">الـ Deployment ‏notes-api يملك ReplicaSet مسمّى بـ hash القالب. الـ ReplicaSet يملك ثلاثة Pods، لكل منها الـ label ‏app.kubernetes.io/name=notes-api، الذي يطابقه الـ selector.</title>\n  <rect class=\"d-box-primary\" x=\"240\" y=\"16\" width=\"220\" height=\"56\" rx=\"10\"/>\n  <text class=\"d-label-strong\" x=\"350\" y=\"40\" text-anchor=\"middle\">Deployment</text>\n  <text class=\"d-code\" x=\"350\" y=\"60\" text-anchor=\"middle\">notes-api</text>\n  <path class=\"d-arrow\" d=\"M350 72 L350 98\" marker-end=\"url(#arrow-l-dk-4-2-1)\"/>\n  <rect class=\"d-box-accent\" x=\"220\" y=\"100\" width=\"260\" height=\"56\" rx=\"10\"/>\n  <text class=\"d-label-strong\" x=\"350\" y=\"124\" text-anchor=\"middle\">ReplicaSet</text>\n  <text class=\"d-code\" x=\"350\" y=\"144\" text-anchor=\"middle\">notes-api-6c8f9d7b5</text>\n  <path class=\"d-arrow\" d=\"M300 156 L140 188\" marker-end=\"url(#arrow-l-dk-4-2-1)\"/>\n  <path class=\"d-arrow\" d=\"M350 156 L350 188\" marker-end=\"url(#arrow-l-dk-4-2-1)\"/>\n  <path class=\"d-arrow\" d=\"M400 156 L560 188\" marker-end=\"url(#arrow-l-dk-4-2-1)\"/>\n  <rect class=\"d-box-success\" x=\"40\" y=\"190\" width=\"200\" height=\"50\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"140\" y=\"220\" text-anchor=\"middle\">pod …-x2k4q</text>\n  <rect class=\"d-box-success\" x=\"250\" y=\"190\" width=\"200\" height=\"50\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"350\" y=\"220\" text-anchor=\"middle\">pod …-p8mzr</text>\n  <rect class=\"d-box-success\" x=\"460\" y=\"190\" width=\"200\" height=\"50\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"560\" y=\"220\" text-anchor=\"middle\">pod …-c4tq9</text>\n  <text class=\"d-label-muted\" x=\"350\" y=\"256\" text-anchor=\"middle\">كل Pod: app.kubernetes.io/name=notes-api</text>\n</svg><figcaption>الـ Deployment يملك ReplicaSets، والـ ReplicaSet يملك Pods، والـ labels تربطها معًا</figcaption></figure>\n<h2 id=\"sec-3\" tabindex=\"-1\">الـ Labels والـ Selectors</h2>\n<p>كائنات Kubernetes لا يشير بعضها إلى بعض بالاسم. إنها تجد بعضها عبر <strong>الـ labels</strong> (الوسوم)، وهي أزواج مفتاح-قيمة في <code dir=\"ltr\">metadata.labels</code>، وعبر <strong>الـ selectors</strong> (المحدِّدات)، وهي استعلامات على تلك الـ labels. يعدّ الـ ReplicaSet الـ Pods الخاصة به بتشغيل الـ selector الخاص به؛ وفي الدرس التالي سيجد Service الـ Pods التي يرسل إليها الحركة بالطريقة نفسها.</p>\n<p>لهذا يجب أن يطابق <code dir=\"ltr\">selector.matchLabels</code> في الـ Deployment الحقل <code dir=\"ltr\">template.metadata.labels</code>: يجب أن يستطيع الـ Deployment إيجاد الـ Pods التي ينشئها قالبه. يتحقّق الـ API server من ذلك ويرفض عدم التطابق. والـ selector أيضًا <strong>غير قابل للتغيير</strong> (immutable) بعد وجود الـ Deployment، فاختره بعناية. المفتاح <code dir=\"ltr\">app.kubernetes.io/name</code> أحد الـ labels الموصى بها في Kubernetes؛ تفهمه الأدوات ولوحات المتابعة، واستخدامه باتّساق يؤتي ثماره.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">خطأ شائع</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>نسخ manifest قديم</span></p>\n<p>ما زالت نتائج البحث تُظهر manifests للـ Deployments بـ <code dir=\"ltr\">apiVersion: extensions/v1beta1</code> أو <code dir=\"ltr\">apps/v1beta2</code>. هذه الإصدارات أُزيلت منذ سنوات، ويفشل <code dir=\"ltr\">kubectl apply</code> برسالة <code dir=\"ltr\">no matches for kind &quot;Deployment&quot; in version …</code>. استخدم <code dir=\"ltr\">apps/v1</code>، وتحقّق من أي حقل لست متأكّدًا منه بـ <code dir=\"ltr\">kubectl explain deployment.spec</code>.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">طبّق وافحص ووسّع</h2>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kind</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> load</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> docker-image</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> ghcr.io/skylane/notes-api:1.4.0</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --name</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> apply</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -f</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> k8s/deployment.yaml</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> get</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> deploy,rs,pods</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -l</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> app.kubernetes.io/name=notes-api</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> describe</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> deployment</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes-api</span></span></code></pre></div>\n<p>الأمر <code dir=\"ltr\">kind load</code> ينسخ الـ image إلى الـ cluster باسمها الكامل، فيستطيع الـ manifest استخدام المرجع نفسه الذي سيستخدمه الإنتاج. و<code dir=\"ltr\">-l</code> يصفّي حسب الـ label، وهي الآلية نفسها التي تستخدمها الـ controllers. انظر إلى أسماء الـ Pods: ‏<code dir=\"ltr\">notes-api-6c8f9d7b5-x2k4q</code> هو اسم الـ Deployment، ثم الـ <code dir=\"ltr\">pod-template-hash</code> الخاص بالـ ReplicaSet، ثم لاحقة عشوائية. غيّر أي شيء في القالب وستحصل على ReplicaSet جديد بـ hash جديد.</p>\n<p>لزيادة عدد النسخ، غيّر <code dir=\"ltr\">replicas: 3</code> إلى <code dir=\"ltr\">5</code> في الملف وطبّق مجددًا. يقارن <code dir=\"ltr\">kubectl apply</code> ملفك بما هو مخزّن ولا يرسل إلا الفرق. والأمر <code dir=\"ltr\">kubectl diff -f k8s/</code> يعرض ذلك الفرق قبل أن تلتزم به، ويجب أن تعتاد تشغيله.</p>\n<p>الأمر <code dir=\"ltr\">kubectl scale deployment/notes-api --replicas=5</code> يعمل أيضًا، وله مكانه في حالات الطوارئ. لكن الملف ما زال يقول 3، فيُرجع الـ <code dir=\"ltr\">apply</code> الروتيني التالي العدد إلى 3 بصمت. كل ما تغيّره يدويًا، غيّره في Git مباشرة بعده.</p>\n<aside class=\"callout callout-tip\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">نصيحة</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>شخّص الـ Pod، لا الـ Deployment</span></p>\n<p>حين يكون هناك خلل، لا يخبرك الـ Deployment إلا بالأعداد. أما <code dir=\"ltr\">kubectl describe pod &lt;name&gt;</code> فيعرض أحداث ذلك الـ Pod: قرارات الجدولة، وسحب الـ images، وإعادات تشغيل الـ containers وأسبابها. إنه أول أمر تلجأ إليه، والقسم الخامس يبني دليل عمل كاملًا حوله.</p>\n</aside>\n<p>الـ Pods لديك تعمل، لكن كلًّا منها يملك عنوان IP خاصًا يتغيّر في كل مرة يُستبدل فيها. لا شيء يستطيع الوصول إليها بشكل موثوق بعد. وهذا ما وُجدت الـ Services من أجله.</p>\n","toc":[{"id":"sec-1","text":"الـ Pods: الوحدة التي يشغّلها Kubernetes","level":2},{"id":"sec-2","text":"الـ Deployments والـ ReplicaSets والـ Pods","level":2},{"id":"sec-3","text":"الـ Labels والـ Selectors","level":2},{"id":"sec-4","text":"طبّق وافحص ووسّع","level":2}],"takeaways":["الـ Pod هو container واحد أو أكثر تتشارك هوية شبكية وتخزينًا؛ إنه أصغر شيء يُجدوِله Kubernetes، ويمكن التخلّص منه.","نادرًا ما تنشئ Pods مجرّدة؛ فالـ Deployment يدير ReplicaSet، وهذا الأخير يُبقي العدد الصحيح من الـ Pods قيد التشغيل.","يجب أن يطابق <code dir=\"ltr\">spec.selector.matchLabels</code> في الـ Deployment الـ labels الموجودة في <code dir=\"ltr\">spec.template.metadata.labels</code>، ولا يمكن تغيير الـ selector بعد الإنشاء.","احفظ الـ manifests في Git وغيّرها بـ <code dir=\"ltr\">kubectl apply -f</code>، حتى تبقى الملفات مصدر الحقيقة.","استخدم إصدارات الـ API الحالية مثل <code dir=\"ltr\">apps/v1</code>؛ فالـ manifests المنسوخة من مقالات قديمة بـ <code dir=\"ltr\">extensions/v1beta1</code> لم تعد تعمل."],"further":[{"title":"Pods","url":"https://kubernetes.io/docs/concepts/workloads/pods/"},{"title":"Deployments","url":"https://kubernetes.io/docs/concepts/workloads/controllers/deployment/"},{"title":"Labels and Selectors","url":"https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/"},{"title":"Recommended Labels","url":"https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/"}],"quiz":[{"q":"<p>لماذا نشغّل notes-api عبر Deployment بدلًا من إنشاء ثلاثة Pods مباشرة؟</p>","options":[{"text":"لأن الـ Pods لا يمكن إنشاؤها من ملفات YAML.","why":"يمكن ذلك. ‏manifest الـ Pod المجرّد صالح؛ لكن لا شيء يديره."},{"text":"لأن الـ Pods المنشأة مباشرة لا تستطيع استخدام images من registry.","why":"سحب الـ images يعمل بالطريقة نفسها لأي Pod. الإدارة هي الفرق."},{"text":"لأن الـ Deployments تشغّل الـ containers أسرع من الـ Pods المجرّدة.","why":"سرعة البدء متطابقة. الـ containers تعمل بالطريقة نفسها."},{"text":"لأن الـ Pod المجرّد لا يُستبدل إذا مات هو أو الـ node الخاص به، ولا يمكن نشر إصدار جديد له تدريجيًا؛ والـ Deployment يتولّى الأمرين.","why":"صحيح. الـ Deployment، عبر الـ ReplicaSet الخاص به، يُبقي العدد صحيحًا ويدير التحديثات."}],"answer":3},{"q":"<p>يرفض <code dir=\"ltr\">kubectl apply</code> كائن Deployment بالرسالة: <code dir=\"ltr\">selector does not match template labels</code>. ما الخطأ؟</p>","options":[{"text":"اسم الـ container لا يطابق اسم الـ Deployment.","why":"أسماء الـ containers مستقلة عن اسم الـ Deployment وعن الـ labels."},{"text":"الحقل <code dir=\"ltr\">spec.selector.matchLabels</code> يطلب labels لا يضبطها <code dir=\"ltr\">spec.template.metadata.labels</code>.","why":"صحيح. يجب أن يستطيع الـ Deployment إيجاد الـ Pods التي ينشئها قالبه، لذا يتحقّق الـ API server من تطابقهما."},{"text":"الـ Deployment في namespace مختلف عن الـ Pods الخاصة به.","why":"الـ Pods الخاصة بـ Deployment تُنشأ دائمًا في الـ namespace الخاص به."},{"text":"الـ tag الخاص بالـ image ليس قيمة label صالحة.","why":"الـ image ليست label أصلًا؛ ولا تشارك في الاختيار."}],"answer":1},{"q":"<p>تشغّل <code dir=\"ltr\">kubectl get pods</code> فترى <code dir=\"ltr\">notes-api-6c8f9d7b5-x2k4q</code>. ما هو <code dir=\"ltr\">6c8f9d7b5</code>؟</p>","options":[{"text":"الـ commit في Git للـ image التي تعمل.","why":"لا يعرف Kubernetes شيئًا عن Git. القيمة تأتي من قالب الـ Pod."},{"text":"الـ node الذي يعمل عليه الـ Pod.","why":"أسماء الـ nodes ليست في أسماء الـ Pods. استخدم <code dir=\"ltr\">kubectl get pods -o wide</code> لرؤية الـ nodes."},{"text":"‏hash لقالب الـ Pod، يحدّد الـ ReplicaSet الذي أنشأ هذا الـ Pod.","why":"صحيح. كل نسخة من القالب تحصل على ReplicaSet خاص بها، وتحمل الـ Pods التابعة له الـ <code dir=\"ltr\">pod-template-hash</code> الخاص به. سترى لماذا يهمّ هذا في التراجع."},{"text":"لاحقة عشوائية بلا معنى.","why":"الجزء الأخير وحده (<code dir=\"ltr\">x2k4q</code>) عشوائي. أما الجزء الأوسط فمقصود."}],"answer":2},{"q":"<p>شغّل أحدهم <code dir=\"ltr\">kubectl scale deployment/notes-api --replicas=6</code> أثناء حادث. ماذا يحدث عند <code dir=\"ltr\">kubectl apply -f k8s/</code> التالي والملف يضبط <code dir=\"ltr\">replicas</code> على 3؟</p>","options":[{"text":"يعود الـ Deployment إلى 3 نسخ، لأن الملف يضبطها على 3.","why":"صحيح. ‏apply يجعل الـ cluster مطابقًا للملف. حدّث الملف حين يجب أن يبقى التغيير، وإلا فسيلغيه الـ deploy التالي."},{"text":"يبقى على 6، لأن التغييرات المباشرة تفوز دائمًا.","why":"التغييرات المباشرة مجرد تحديثات عبر الـ API؛ والـ apply التالي يكتب فوق الحقول التي يديرها الملف."},{"text":"يفشل apply بتعارض حتى يحلّه أحد.","why":"الـ apply من جهة العميل يضبط الحقل ببساطة. لا تحصل على أي تحذير، وهنا الخطر."},{"text":"يأخذ Kubernetes متوسط القيمتين ويشغّل 4 أو 5.","why":"لا يوجد متوسط؛ لكل حقل قيمة واحدة في كل لحظة."}],"answer":0}],"exercise":{"kind":"spot-bug","language":"yaml","code":"apiVersion: extensions/v1beta1\nkind: Deployment\nmetadata:\n  name: notes-api\nspec:\n  replicas: 3\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: notes-api\n  template:\n    metadata:\n      labels:\n        app.kubernetes.io/name: notes\n    spec:\n      containers:\n        - name: api\n          image: ghcr.io/skylane/notes-api:1.4.0\n          ports:\n            - containerPort: 3000","bugLines":[1,13],"title":"‏Deployment يرفضه الـ API server","prompt":"<p>اقتبس زميلك هذا الـ Deployment من مقالة في مدوّنة. يفشل <code dir=\"ltr\">kubectl apply</code>، وبعد إصلاح الخطأ الأول يفشل مجددًا بخطأ مختلف. اختر السطرين <strong>الاثنين</strong> اللذين يجب إصلاحهما.</p>\n","hints":["أي مجموعة API وأي إصدار تستخدمهما الـ Deployments في Kubernetes الحالي؟","قارن الـ labels في الـ selector بالـ labels التي يضبطها قالب الـ Pod، حرفًا بحرف."],"explanation":"<p>السطر 1 يستخدم <code dir=\"ltr\">extensions/v1beta1</code>، الذي أُزيل منذ زمن بعيد؛ فيفشل apply برسالة <code dir=\"ltr\">no matches for kind &quot;Deployment&quot;</code>. استخدم <code dir=\"ltr\">apps/v1</code>.</p>\n<p>السطر 13 يعطي الـ Pods الـ label ‏<code dir=\"ltr\">notes</code>، بينما يطلب الـ selector ‏<code dir=\"ltr\">notes-api</code>، فيرفض الـ API server الـ Deployment لأن الـ selector الخاص به لا يطابق قالبه. اجعلهما متطابقين:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"yaml\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">YAML</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">apiVersion</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">apps/v1</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\"># …</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  selector</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    matchLabels</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      app.kubernetes.io/name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">notes-api</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  template</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    metadata</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      labels</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">        app.kubernetes.io/name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">notes-api</span></span></code></pre></div>\n","checkLabels":{}},"words":674,"readMinutes":4}