{"id":"l-dk-4-3","courseId":"docker-kubernetes","lang":"ar","kind":"lesson","summary":"أعطِ notes-api وقاعدة بياناته أسماء وعناوين ثابتة بالـ Services، واربط <code dir=\"ltr\">port</code> و<code dir=\"ltr\">targetPort</code> و<code dir=\"ltr\">containerPort</code> بشكل صحيح، واختر بين ClusterIP وNodePort وLoadBalancer.","html":"<p>شغّل <code dir=\"ltr\">kubectl get pods -o wide</code> مرتين، مع rollout بينهما، وستجد كل عنوان IP مختلفًا. الـ Pods تُستبدل باستمرار: إصدارات جديدة، وإعادة جدولة، وتوسّع. كل ما كان يتذكّر عنوان IP لـ Pod صار يشير إلى لا شيء. الـ Service هو الشيء الثابت الذي تشير إليه بدلًا من ذلك.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">Service لـ notes-api</h2>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"yaml\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">k8s/service.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\">Service</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\">  selector</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\">  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\">      port</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">80</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      targetPort</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">http</span></span></code></pre></div>\n<p>هذا ينشئ عنوان IP افتراضيًا واسم DNS، ‏<code dir=\"ltr\">notes-api</code>، يدومان ما دام الـ Service موجودًا. الاتصالات إليه تُوزَّع على الـ Pods التي يطابقها الـ <code dir=\"ltr\">selector</code> الخاص به، وهو استعلام الـ labels نفسه الذي يستخدمه الـ Deployment. يتتبّع Kubernetes المجموعة الحالية من الـ Pods <em>الجاهزة</em> المطابقة في <strong>EndpointSlices</strong> ويحدّثها كلما جاءت Pods وذهبت. والعملاء لا يلاحظون شيئًا.</p>\n<p>الـ ClusterIP ليس جهازًا، ولا تستمع عليه أي عملية. طبقة الشبكة في كل node ‏(kube-proxy، أو بديل مبني على eBPF في كثير من الـ clusters) تبرمج قواعد تعيد كتابة الحزم المرسلة إلى عنوان IP الخاص بالـ Service ومنفذه لتذهب إلى أحد الـ Pods الجاهزة. لهذا لا تستطيع تنفيذ <code dir=\"ltr\">ping</code> على عنوان IP الخاص بـ Service، ولا داعي للقلق حين لا يجيب: المنافذ المصرَّح بها هي وحدها الموجودة. ولهذا أيضًا لا يكلّف الـ Service شيئًا تقريبًا، فأنشئ واحدًا لكل حمل عمل يستقبل حركة.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">ثلاثة منافذ، وثلاثة معانٍ</h2>\n<p>هنا تعيش معظم أخطاء الـ Services.</p>\n<ul>\n<li><strong><code dir=\"ltr\">containerPort</code></strong> في مواصفات الـ Pod هو المنفذ الذي تستمع عليه عمليتك: 3000. إنه معلوماتي، مثل <code dir=\"ltr\">EXPOSE</code>، لكن تسميته (<code dir=\"ltr\">name: http</code>) تتيح للكائنات الأخرى الإشارة إليه.</li>\n<li><strong><code dir=\"ltr\">targetPort</code></strong> في الـ Service هو المكان الذي تُسلَّم إليه الحركة على الـ Pod. يجب أن يطابق المكان الذي يستمع عليه التطبيق فعلًا. وهنا يستخدم الاسم <code dir=\"ltr\">http</code>، الذي يُحلّ إلى 3000.</li>\n<li><strong><code dir=\"ltr\">port</code></strong> في الـ Service هو ما يتّصل به العملاء: 80.</li>\n</ul>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 250\" role=\"img\" aria-labelledby=\"l-dk-4-3-t1\"><defs><marker id=\"arrow-l-dk-4-3-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-3-t1\">‏Pod عميل يستدعي notes-api على المنفذ 80. الـ Service يحوّل إلى targetPort ‏http، وهو containerPort ‏3000، على أحد ثلاثة Pods جاهزة من notes-api يختارها الـ label.</title>\n  <rect class=\"d-box\" x=\"20\" y=\"95\" width=\"130\" height=\"60\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"85\" y=\"121\" text-anchor=\"middle\">Pod عميل</text>\n  <text class=\"d-code\" x=\"85\" y=\"143\" text-anchor=\"middle\">notes-api:80</text>\n  <path class=\"d-arrow\" d=\"M150 125 L228 125\" marker-end=\"url(#arrow-l-dk-4-3-1)\"/>\n  <rect class=\"d-box-primary\" x=\"230\" y=\"80\" width=\"190\" height=\"90\" rx=\"12\"/>\n  <text class=\"d-label-strong\" x=\"325\" y=\"106\" text-anchor=\"middle\">Service notes-api</text>\n  <text class=\"d-code\" x=\"325\" y=\"130\" text-anchor=\"middle\">port: 80</text>\n  <text class=\"d-code\" x=\"325\" y=\"152\" text-anchor=\"middle\">targetPort: http</text>\n  <path class=\"d-arrow\" d=\"M420 110 L508 52\" marker-end=\"url(#arrow-l-dk-4-3-1)\"/>\n  <path class=\"d-arrow\" d=\"M420 125 L508 125\" marker-end=\"url(#arrow-l-dk-4-3-1)\"/>\n  <path class=\"d-arrow\" d=\"M420 140 L508 198\" marker-end=\"url(#arrow-l-dk-4-3-1)\"/>\n  <rect class=\"d-box-success\" x=\"510\" y=\"28\" width=\"170\" height=\"48\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"595\" y=\"57\" text-anchor=\"middle\">pod :3000</text>\n  <rect class=\"d-box-success\" x=\"510\" y=\"101\" width=\"170\" height=\"48\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"595\" y=\"130\" text-anchor=\"middle\">pod :3000</text>\n  <rect class=\"d-box-success\" x=\"510\" y=\"174\" width=\"170\" height=\"48\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"595\" y=\"203\" text-anchor=\"middle\">pod :3000</text>\n  <text class=\"d-label-muted\" x=\"325\" y=\"200\" text-anchor=\"middle\">selector: app.kubernetes.io/name=notes-api</text>\n  <text class=\"d-label-muted\" x=\"595\" y=\"242\" text-anchor=\"middle\">containerPort http = 3000</text>\n</svg><figcaption>العملاء يستخدمون منفذ الـ Service؛ والـ Service يحوّل إلى targetPort على Pod جاهز</figcaption></figure>\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>‏targetPort يشير إلى المنفذ الخطأ</span></p>\n<p>الإعداد <code dir=\"ltr\">port: 80, targetPort: 80</code> هو الخطأ الكلاسيكي. يبدو متناظرًا ومرتّبًا، وللـ Service ‏endpoints، وكل طلب يفشل بـ <code dir=\"ltr\">connection refused</code>، لأن لا شيء في الـ Pod يستمع على 80. وجّه <code dir=\"ltr\">targetPort</code> إلى المنفذ المسمّى في الـ container، فلا يمكن أن يفترق الرقمان أبدًا.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">إيجاد الـ Services بالاسم</h2>\n<p>يعطي DNS الخاص بالـ cluster كل Service اسمًا بالشكل <code dir=\"ltr\">&lt;service&gt;.&lt;namespace&gt;.svc.cluster.local</code>. من Pod في الـ namespace نفسه يكفي الاسم القصير <code dir=\"ltr\">notes-api</code>؛ ومن namespace آخر استخدم <code dir=\"ltr\">notes-api.notes</code> أو الاسم الكامل.</p>\n<p>يحتاج notes-api إلى قاعدة بيانات، فأعطه واحدة بالطريقة نفسها. للتدرّب في kind، شغّل Postgres كـ Deployment بنسخة واحدة مع Service من نوع ClusterIP:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"yaml\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">k8s/db.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-db</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\">1</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-db</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-db</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\">postgres</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\">postgres:17</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\">POSTGRES_USER</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\">notes</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\">POSTGRES_PASSWORD</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\">notes</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">          # moved to a Secret in the next lesson</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\">POSTGRES_DB</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\">notes</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\">postgres</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\">5432</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">---</span></span>\n<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\">Service</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-db</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\">  selector</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-db</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\">postgres</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      port</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">5432</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      targetPort</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">postgres</span></span></code></pre></div>\n<p>المضيف في <code dir=\"ltr\">DATABASE_URL</code> الخاص بـ notes-api صار الآن <code dir=\"ltr\">notes-db</code>، تمامًا كما كان <code dir=\"ltr\">db</code> في Compose. والفاصل <code dir=\"ltr\">---</code> يفصل بين كائنين في ملف واحد.</p>\n<aside class=\"callout callout-note\" 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\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 16v-4\"/><path d=\"M12 8h.01\"/></svg><span><span class=\"callout-kind\">ملاحظة</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>قواعد البيانات في الإنتاج</span></p>\n<p>قاعدة بيانات التدريب هذه تحفظ بياناتها داخل الـ Pod، فتضيع كلما استُبدل الـ Pod. في الإنتاج، استخدم قاعدة بيانات مُدارة مثل Amazon RDS، أو شغّل Postgres بـ StatefulSet وpersistent volumes تحت operator يصونه أناس يعرفون Postgres جيدًا. التطبيقات عديمة الحالة (stateless) هي حيث يكون Kubernetes سهلًا؛ أما ذات الحالة (stateful) فتحتاج إلى تصميم متعمَّد.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">أنواع الـ Services</h2>\n<div class=\"table-wrap\" role=\"region\" aria-label=\"جدول قابل للتمرير\" tabindex=\"0\"><table><thead>\n<tr>\n<th>النوع</th>\n<th>يمكن الوصول إليه من</th>\n<th>استخدمه لـ</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code dir=\"ltr\">ClusterIP</code> (الافتراضي)</td>\n<td>داخل الـ cluster</td>\n<td>الحركة بين الخدمات، مثل notes-api ← notes-db</td>\n</tr>\n<tr>\n<td><code dir=\"ltr\">NodePort</code></td>\n<td>عنوان IP لكل node على منفذ ضمن 30000–32767</td>\n<td>الاختبارات السريعة، أو كلبنة بناء لموازنات الأحمال الخارجية</td>\n</tr>\n<tr>\n<td><code dir=\"ltr\">LoadBalancer</code></td>\n<td>عنوان موازن أحمال سحابي</td>\n<td>عرض خدمة واحدة للعموم على cluster مُدار</td>\n</tr>\n</tbody>\n</table></div><p>وهناك أيضًا الـ Service من نوع <strong>headless</strong>، مع <code dir=\"ltr\">clusterIP: None</code>. لا يحصل على عنوان IP افتراضي؛ بل يُرجع اسم الـ DNS الخاص به عناوين IP للـ Pods المفردة. تستخدمه قواعد البيانات والأنظمة العنقودية الأخرى حين يحتاج كل عضو إلى أن يُخاطَب مباشرة. لن تحتاج إليه لـ notes-api.</p>\n<p>يُبنى <code dir=\"ltr\">LoadBalancer</code> فوق النوعين الآخرين: ينشئ مزوّد السحابة موازن أحمال يحوّل في العادة إلى منافذ الـ nodes، وهذه تحوّل إلى الـ Service (وبعض الـ controllers يرسل الحركة مباشرة إلى عناوين IP الخاصة بالـ Pods بدلًا من ذلك). واحد لكل خدمة عامة يصبح مكلفًا ومتكرّرًا حين تكون لديك خدمات HTTP كثيرة؛ والدرس 5.4 يغطّي Gateway API، الذي يضع مسارات كثيرة خلف نقطة دخول واحدة.</p>\n<h2 id=\"sec-5\" 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\">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/</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\"> svc</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\"> endpointslices</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -l</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> kubernetes.io/service-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\"> port-forward</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> svc/notes-api</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 8080:80</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">curl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> localhost:8080/readyz</span></span></code></pre></div>\n<p>الأمر <code dir=\"ltr\">port-forward</code> يحفر نفقًا من منفذ محلي إلى الـ Service عبر الـ API server. إنه للتشخيص، لا للحركة الحقيقية. إذا لم يسرد الـ EndpointSlice أي عناوين، فالـ Service لا يطابق أي Pods جاهزة: قارن الـ selector بمخرجات <code dir=\"ltr\">kubectl get pods --show-labels</code>، ثم تحقّق من الجاهزية.</p>\n<p>مشكلتان صارتا ظاهرتين الآن في الـ YAML: كلمة مرور مكتوبة نصًّا صريحًا، وإعدادات مخلوطة داخل الـ Deployment. الدرس التالي يُخرج الاثنتين.</p>\n","toc":[{"id":"sec-1","text":"Service لـ notes-api","level":2},{"id":"sec-2","text":"ثلاثة منافذ، وثلاثة معانٍ","level":2},{"id":"sec-3","text":"إيجاد الـ Services بالاسم","level":2},{"id":"sec-4","text":"أنواع الـ Services","level":2},{"id":"sec-5","text":"الوصول إليه من حاسوبك","level":2}],"takeaways":["الـ Service يعطي مجموعة متغيّرة من الـ Pods عنوان IP افتراضيًا واسم DNS ثابتين، ويوزّع الاتصالات على الـ Pods الجاهزة التي يطابقها الـ selector الخاص به.","الحقل <code dir=\"ltr\">port</code> هو ما يتّصل به العملاء على الـ Service؛ و<code dir=\"ltr\">targetPort</code> هو وجهة الحركة على الـ Pod، ويجب أن يطابق المنفذ الذي يستمع عليه الـ container.","داخل الـ cluster، تصل الـ Pods إلى الـ Service باسمه، مثل <code dir=\"ltr\">notes-db</code> في الـ namespace نفسه أو <code dir=\"ltr\">notes-db.notes.svc.cluster.local</code> من أي مكان.","استخدم ClusterIP للحركة الداخلية، وLoadBalancer لعرض خدمة واحدة عبر موازن أحمال سحابي، وNodePort للاختبار أساسًا.","الـ EndpointSlice الفارغ يعني أن الـ Service لا يطابق أي Pods جاهزة؛ تحقّق من الـ selector والجاهزية أولًا."],"further":[{"title":"Service","url":"https://kubernetes.io/docs/concepts/services-networking/service/"},{"title":"DNS for Services and Pods","url":"https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/"},{"title":"Use Port Forwarding to Access Applications in a Cluster","url":"https://kubernetes.io/docs/tasks/access-application-cluster/port-forward-access-application-cluster/"}],"quiz":[{"q":"<p>يستمع notes-api على 3000. الـ Service فيه <code dir=\"ltr\">port: 80</code> و<code dir=\"ltr\">targetPort: 3000</code>. أي عنوان تستخدمه الـ Pods الأخرى في الـ namespace نفسه؟</p>","options":[{"text":"<code dir=\"ltr\">http://notes-api:3000</code>","why":"المنفذ 3000 منفذ الـ Pod. عملاء الـ Service يتّصلون بالـ <code dir=\"ltr\">port</code> الخاص بالـ Service، وهو 80."},{"text":"<code dir=\"ltr\">http://&lt;pod-ip&gt;:80</code>","why":"عناوين IP الخاصة بالـ Pods تتغيّر مع كل استبدال، ولا شيء في الـ Pod يستمع على 80."},{"text":"<code dir=\"ltr\">http://notes-api</code> (المنفذ 80)","why":"صحيح. اسم الـ Service يُحلّ عبر DNS الخاص بالـ cluster، والمنفذ 80 يُحوَّل إلى 3000 على Pod جاهز."},{"text":"<code dir=\"ltr\">http://localhost:80</code>","why":"‏<code dir=\"ltr\">localhost</code> هو الـ Pod المتّصِل نفسه، لا الـ Service."}],"answer":2},{"q":"<p>الأمر <code dir=\"ltr\">kubectl get endpointslices -l kubernetes.io/service-name=notes-api</code> لا يعرض أي endpoints، لكن ثلاثة Pods من notes-api في حالة Running. ما الذي تتحقّق منه أولًا؟</p>","options":[{"text":"هل يطابق الـ selector الخاص بالـ Service الـ labels الخاصة بالـ Pods، وهل الـ Pods في حالة Ready.","why":"صحيح. الـ endpoints تسرد الـ Pods التي تطابق الـ selector وتجتاز فحص الجاهزية. خطأ إملائي في label أو readiness probe فاشل يتركها فارغة."},{"text":"هل نوع الـ Service هو LoadBalancer.","why":"النوع يغيّر طريقة وصول الحركة إلى الـ Service من الخارج، لا الـ Pods التي تقف خلفه."},{"text":"هل الـ tag الخاص بالـ image هو <code dir=\"ltr\">latest</code>.","why":"الـ tags لا تؤثّر في مطابقة الـ Service."},{"text":"هل DNS الخاص بالـ cluster يعمل.","why":"الـ DNS يحلّ اسم الـ Service، لكن الـ endpoints تُحسب من الـ labels والجاهزية، باستقلال عن الـ DNS."}],"answer":0},{"q":"<p>تحتاج إلى أن يكون notes-api قابلًا للوصول من الإنترنت على cluster مُدار في AWS، وهو الخدمة العامة الوحيدة. أي نوع Service يناسب؟</p>","options":[{"text":"ClusterIP","why":"الـ ClusterIP لا يمكن الوصول إليه إلا من داخل الـ cluster."},{"text":"NodePort، مع اتّصال المستخدمين بعنوان IP لأحد الـ nodes على المنفذ 30080","why":"هذا يعرض كل node على منفذ عالٍ وينكسر حين تُستبدل الـ nodes. إنه لبنة بناء، لا نقطة وصول عامة."},{"text":"Headless ‏(<code dir=\"ltr\">clusterIP: None</code>)","why":"الـ Services من نوع headless تعطي سجلات DNS لكل Pod، لأشياء مثل قواعد البيانات ذات الهويات الثابتة. لا تعرض شيئًا للخارج."},{"text":"LoadBalancer، الذي يجهّز موازن أحمال سحابيًا أمام الـ Service","why":"صحيح. لخدمة عامة واحدة هو الخيار الأبسط. ومع خدمات HTTP كثيرة ستتشارك نقطة دخول واحدة عبر Gateway API بدلًا من ذلك."}],"answer":3},{"q":"<p>أي <code dir=\"ltr\">targetPort</code> صحيح لـ container يصرّح بـ <code dir=\"ltr\">ports: [{ name: http, containerPort: 3000 }]</code>؟</p>","options":[{"text":"<code dir=\"ltr\">targetPort: 80</code>، ليطابق منفذ الـ Service.","why":"الحقل <code dir=\"ltr\">targetPort</code> هو المكان الذي تصل إليه الحركة داخل الـ Pod. لا شيء يستمع على 80 هناك."},{"text":"<code dir=\"ltr\">targetPort: http</code>، اسم منفذ الـ container (أو <code dir=\"ltr\">3000</code>).","why":"صحيح. المنفذ المسمّى يستمر في العمل حتى لو غيّرت الرقم لاحقًا في مكان واحد."},{"text":"<code dir=\"ltr\">targetPort: notes-api</code>، اسم الـ Deployment.","why":"الحقل <code dir=\"ltr\">targetPort</code> يأخذ رقمًا أو اسم منفذ container، لا اسم كائن."},{"text":"<code dir=\"ltr\">targetPort: 30000</code>، من نطاق NodePort.","why":"نطاق NodePort يخصّ <code dir=\"ltr\">nodePort</code>، وهو حقل مختلف على جهة الـ node."}],"answer":1}],"exercise":{"kind":"spot-bug","language":"yaml","code":"apiVersion: v1\nkind: Service\nmetadata:\n  name: notes-api\nspec:\n  selector:\n    app.kubernetes.io/name: notes-api\n  ports:\n    - port: 80\n      targetPort: 80\n---\n# excerpt from the Deployment's Pod template\ncontainers:\n  - name: api\n    image: ghcr.io/skylane/notes-api:1.4.0\n    ports:\n      - containerPort: 3000","bugLines":[10],"title":"الـ endpoints موجودة، والطلبات تفشل","prompt":"<p>الـ Service أدناه له ثلاثة endpoints، والـ Pods في حالتَي Running وReady، ومع ذلك يحصل <code dir=\"ltr\">curl notes-api</code> من Pod آخر على <code dir=\"ltr\">connection refused</code> في كل مرة. اختر السطر المسبّب.</p>\n","hints":["وجد الـ Service الـ Pods، فالـ selector سليم. تتبّع أرقام المنافذ.","أين يستمع التطبيق داخل الـ Pod فعلًا؟"],"explanation":"<p>السطر 10 يرسل الحركة إلى المنفذ 80 على الـ Pods، لكن notes-api يستمع على 3000، فيُرفض كل اتصال. وجّه <code dir=\"ltr\">targetPort</code> إلى منفذ الـ container، ويفضَّل بالاسم حتى لا ينحرف أحدهما عن الآخر:</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:#6E7781;--shiki-dark:#8B949E\"># Service</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\">port</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">80</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      targetPort</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:#6E7781;--shiki-dark:#8B949E\"># Pod template</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></code></pre></div>\n","checkLabels":{}},"words":700,"readMinutes":4}