{"id":"l-dk-5-4","courseId":"docker-kubernetes","lang":"ar","kind":"lesson","summary":"وجّه حركة HTTPS العامة إلى notes-api باستخدام Gateway API، وحصّن الـ Pods الخاصة به بـ securityContext، وشخّص الـ Pods المعطوبة بسرعة بالأوامر get وdescribe وlogs وevents وkubectl debug.","html":"<p>شيئان يقفان بين notes-api والمستخدمين الحقيقيين. لا يمكن الوصول إليه من الإنترنت بعد، وحين ينكسر شيء في الثالثة فجرًا، تحتاج إلى معرفة ما هو خلال دقائق. هذا الدرس يغطّي الأمرين، وينتهي بإعدادات الأمان التي تكمل صورة الإنتاج.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">إدخال حركة HTTP</h2>\n<p>‏Service من نوع <code dir=\"ltr\">LoadBalancer</code> لكل تطبيق يصلح لخدمة واحدة. أما مع عشر خدمات، فأنت تدفع ثمن عشرة موازنات أحمال، وتدير عشر شهادات، والتوجيه حسب اسم المضيف أو المسار مستحيل. ما تريده نقطة دخول واحدة مشتركة توجّه طلبات HTTP إلى الـ Service الصحيح.</p>\n<p>لسنوات كانت الإجابة <strong>Ingress</strong>. ما زال يعمل، لكن واجهة Ingress البرمجية مجمَّدة ولا تحصل على ميزات جديدة، والـ controller المجتمعي الواسع الانتشار ingress-nginx تقاعد في مارس 2026: لا إصدارات جديدة ولا إصلاحات أمنية. مشروع Kubernetes يوجّه العمل الجديد نحو <strong>Gateway API</strong>، المتاح رسميًا (GA) بالإصدار <code dir=\"ltr\">gateway.networking.k8s.io/v1</code>. يُثبَّت كإضافة (الـ CRDs الخاصة به مع أداة تنفيذ (implementation)، مثل controller موازن الأحمال في منصّتك السحابية، أو Envoy Gateway، أو Cilium، أو Istio)، وكثير من الـ clusters المُدارة توفّره جاهزًا.</p>\n<p>يقسّم Gateway API المهمّة حسب الأدوار:</p>\n<ul>\n<li><strong>GatewayClass</strong>: أي أداة تنفيذ تتولّى الحركة. يضبطه مرة واحدة من يشغّل الـ cluster.</li>\n<li><strong>Gateway</strong>: نقطة دخول فيها listeners ومنافذ وأسماء مضيفات وشهادات TLS. يملكها فريق المنصّة.</li>\n<li><strong>HTTPRoute</strong>: قواعد التوجيه لتطبيق واحد، مرتبطة بـ Gateway. يملكها فريق التطبيق.</li>\n</ul>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 220\" role=\"img\" aria-labelledby=\"l-dk-5-4-t1\"><defs><marker id=\"arrow-l-dk-5-4-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-5-4-t1\">مشغّل الـ cluster يختار الـ GatewayClass. الـ Gateway المسمّى public في الـ namespace ‏infra ينهي TLS على المنفذ 443. الـ HTTPRoute ‏notes-api في الـ namespace ‏notes يرتبط به ويرسل طلبات notes.skylane.example إلى الـ Service ‏notes-api، الذي يحوّلها إلى الـ Pods.</title>\n  <rect class=\"d-box\" x=\"10\" y=\"80\" width=\"120\" height=\"60\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"70\" y=\"106\" text-anchor=\"middle\">GatewayClass</text>\n  <text class=\"d-label-muted\" x=\"70\" y=\"126\" text-anchor=\"middle\">مشغّل الـ cluster</text>\n  <rect class=\"d-box-primary\" x=\"155\" y=\"80\" width=\"130\" height=\"60\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"220\" y=\"106\" text-anchor=\"middle\">Gateway :443</text>\n  <text class=\"d-label-muted\" x=\"220\" y=\"126\" text-anchor=\"middle\">فريق المنصّة</text>\n  <rect class=\"d-box-accent\" x=\"310\" y=\"80\" width=\"140\" height=\"60\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"380\" y=\"106\" text-anchor=\"middle\">HTTPRoute</text>\n  <text class=\"d-label-muted\" x=\"380\" y=\"126\" text-anchor=\"middle\">فريق notes</text>\n  <rect class=\"d-box-success\" x=\"475\" y=\"80\" width=\"100\" height=\"60\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"525\" y=\"115\" text-anchor=\"middle\">Service</text>\n  <rect class=\"d-box-success\" x=\"600\" y=\"80\" width=\"90\" height=\"60\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"645\" y=\"115\" text-anchor=\"middle\">Pods</text>\n  <path class=\"d-arrow\" d=\"M130 110 L153 110\" marker-end=\"url(#arrow-l-dk-5-4-1)\"/>\n  <path class=\"d-arrow\" d=\"M285 110 L308 110\" marker-end=\"url(#arrow-l-dk-5-4-1)\"/>\n  <path class=\"d-arrow\" d=\"M450 110 L473 110\" marker-end=\"url(#arrow-l-dk-5-4-1)\"/>\n  <path class=\"d-arrow\" d=\"M575 110 L598 110\" marker-end=\"url(#arrow-l-dk-5-4-1)\"/>\n  <text class=\"d-code\" x=\"380\" y=\"175\" text-anchor=\"middle\">notes.skylane.example → notes-api:80</text>\n</svg><figcaption>كل فريق يملك طبقته، والـ routes ترتبط بـ Gateway مشترك</figcaption></figure>\n<p>الـ Gateway الخاص بفريق المنصّة، مرة واحدة لكل cluster:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"yaml\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">infra/gateway.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\">gateway.networking.k8s.io/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\">Gateway</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\">public</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  namespace</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">infra</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\">  gatewayClassName</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">skylane-lb</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">        # provided by your implementation</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  listeners</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\">https</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      protocol</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">HTTPS</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\">443</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      hostname</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"*.skylane.example\"</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      tls</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">        mode</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">Terminate</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">        certificateRefs</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\">skylane-wildcard-tls</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      allowedRoutes</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">        namespaces</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">          from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">All</span></span></code></pre></div>\n<p>والـ route الخاص بك، في الـ namespace ‏<code dir=\"ltr\">notes</code>:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"yaml\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">k8s/httproute.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\">gateway.networking.k8s.io/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\">HTTPRoute</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\">  namespace</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\">spec</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  parentRefs</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\">public</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      namespace</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">infra</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  hostnames</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:#0A3069;--shiki-dark:#A5D6FF\">notes.skylane.example</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  rules</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\">matches</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\">path</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">            type</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">PathPrefix</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\">/</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      backendRefs</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\">notes-api</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></code></pre></div>\n<p>الأمر <code dir=\"ltr\">kubectl describe httproute notes-api</code> يُظهر تحت <code dir=\"ltr\">Status</code> هل قبل الـ Gateway هذا الـ route. وإذا كنت تنقل كائنات Ingress موجودة، فالأداة <code dir=\"ltr\">ingress2gateway</code> تحوّلها، بما في ذلك كثير من annotations الخاصة بـ ingress-nginx.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">دليل التشخيص</h2>\n<p>حين يسيء notes-api التصرّف، قاوم التخمين. اتّبع الترتيب نفسه في كل مرة، ودع كل خطوة تختار التالية:</p>\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\"> get</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 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\"> pod</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> &#x3C;</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">po</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">d</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">></span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> logs</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> &#x3C;</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">po</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">d</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">></span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --previous</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> events</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --for</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> pod/</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">&#x3C;</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">po</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">d</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">></span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --types=Warning</span></span></code></pre></div>\n<p>العمود <code dir=\"ltr\">STATUS</code> يخبرك أين تنظر:</p>\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\">Pending</code></td>\n<td>لا يوجد node يستطيع استقبال الـ Pod</td>\n<td>‏<code dir=\"ltr\">describe</code>: أحداث مثل <code dir=\"ltr\">Insufficient cpu</code> أو node selectors غير مطابقة</td>\n</tr>\n<tr>\n<td><code dir=\"ltr\">ImagePullBackOff</code></td>\n<td>لا يستطيع الـ kubelet سحب الـ image</td>\n<td>‏<code dir=\"ltr\">describe</code>: ‏tag خاطئ، أو بيانات اعتماد سحب مفقودة، أو معمارية خاطئة</td>\n</tr>\n<tr>\n<td><code dir=\"ltr\">CreateContainerConfigError</code></td>\n<td>مفتاح ConfigMap أو Secret مُشار إليه مفقود</td>\n<td>‏<code dir=\"ltr\">describe</code> يسمّي المفتاح</td>\n</tr>\n<tr>\n<td><code dir=\"ltr\">CrashLoopBackOff</code></td>\n<td>التطبيق يبدأ ويخرج، مرارًا</td>\n<td>‏<code dir=\"ltr\">logs --previous</code>، مع رمز الخروج في <code dir=\"ltr\">describe</code> ‏(137 = نفاد ذاكرة أو قتل)</td>\n</tr>\n<tr>\n<td><code dir=\"ltr\">Running</code> مع <code dir=\"ltr\">0/1</code> جاهز</td>\n<td>الـ readiness probe يفشل</td>\n<td>‏<code dir=\"ltr\">describe</code> يُظهر إخفاقات الـ probe؛ تحقّق مما يعتمد عليه <code dir=\"ltr\">/readyz</code></td>\n</tr>\n</tbody>\n</table></div><p>إذا كانت الـ Pods سليمة لكن الطلبات تفشل، فاعمل من الداخل إلى الخارج: ‏<code dir=\"ltr\">kubectl get endpointslices -l kubernetes.io/service-name=notes-api</code> (الفراغ يعني مشكلة في الـ selector أو الجاهزية)، ثم حالة الـ route، ثم الـ Gateway.</p>\n<p>الـ images من نوع distroless لا تحتوي على shell، فلا يفيد <code dir=\"ltr\">kubectl exec</code>. ألحِق <strong>ephemeral debug container</strong> (container تشخيص مؤقتًا) بدلًا من ذلك. ينضمّ إلى الـ network namespace الخاصة بالـ Pod، ومع <code dir=\"ltr\">--target</code> يستطيع رؤية عمليات التطبيق أيضًا:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"3\"><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\"> debug</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -it</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> &#x3C;</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">po</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">d</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">></span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --image=busybox:1.37</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --target=api</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\"># inside: nslookup notes-db, wget -qO- localhost:3000/readyz, ps</span></span></code></pre></div>\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>حذف الأدلّة</span></p>\n<p>ردّ الفعل التلقائي أثناء الحادث هو <code dir=\"ltr\">kubectl delete pod</code> «ليُعاد تشغيله نظيفًا». البديل ينهار بالطريقة نفسها، وسجلات الـ container السابق وأحداثه تذهب مع الـ Pod القديم. اقرأ <code dir=\"ltr\">describe</code> و<code dir=\"ltr\">logs --previous</code> أولًا. إعادة التشغيل تأتي بعد أن تعرف السبب.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">القطعة الأخيرة: الـ securityContext</h2>\n<p>في الدرس 2.2 شغّلت الـ image كمستخدم غير root ووعدت بفرض ذلك في الـ cluster. ها هو ذا، على container ‏notes-api:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"yaml\" data-index=\"4\"><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\">          securityContext</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">            runAsNonRoot</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">true</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">            allowPrivilegeEscalation</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">false</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">            readOnlyRootFilesystem</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">true</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">            capabilities</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">              drop</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"ALL\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]</span></span></code></pre></div>\n<p>الحقل <code dir=\"ltr\">runAsNonRoot</code> يجعل الـ kubelet يرفض بدء الـ container إذا كانت الـ image ستعمل بـ UID 0. إنه يفحص UID رقميًا، لذا فإن image يكون فيها <code dir=\"ltr\">USER</code> اسمًا مثل <code dir=\"ltr\">node</code> تفشل بـ <code dir=\"ltr\">CreateContainerConfigError</code>؛ اكتب <code dir=\"ltr\">USER 1000</code> في الـ Dockerfile أو أضف <code dir=\"ltr\">runAsUser: 1000</code> هنا. وأعطِ التطبيق volume من نوع <code dir=\"ltr\">emptyDir</code> في <code dir=\"ltr\">/tmp</code> إن احتاج إلى مساحة مؤقتة تحت نظام ملفات جذري للقراءة فقط.</p>\n<p>بهذا يكتمل مسار notes-api: بناء multi-stage يستفيد من الـ cache، وimage صغيرة بمستخدم غير root مثبّتة بالـ digest ومفحوصة، وبيئة Compose للتطوير، وDeployment مع probes وموارد وتوسّع تلقائي وrollouts آمنة وroute عبر Gateway وsecurity context محصّن. كل واحد من هذه الإعدادات موجود في الملف لأن حادثًا ما علّم أحدهم أنه ضروري. حافظ على هذه العادة: حين ينكسر شيء، اسأل أي إعداد كان سيجعله مستحيلًا، وأضِفه.</p>\n","toc":[{"id":"sec-1","text":"إدخال حركة HTTP","level":2},{"id":"sec-2","text":"دليل التشخيص","level":2},{"id":"sec-3","text":"القطعة الأخيرة: الـ securityContext","level":2}],"takeaways":["يقسّم Gateway API الحركة الخارجية إلى GatewayClass (أداة التنفيذ)، وGateway (نقطة الدخول وTLS)، وHTTPRoute (قواعد التوجيه لتطبيق ما)، فيملك كلٌّ من فريق المنصّة وفريق التطبيق جزأه.","الـ Ingress ما زال يعمل لكنه مجمَّد، والـ controller المجتمعي ingress-nginx تقاعد في مارس 2026؛ والإعدادات الجديدة يجب أن تستخدم Gateway API.","اقرأ حالة الـ Pod أولًا، ثم <code dir=\"ltr\">kubectl describe</code> للأحداث، ثم <code dir=\"ltr\">kubectl logs --previous</code> للانهيارات؛ فالحالة تخبرك بالأداة التي تلجأ إليها.","الأمر <code dir=\"ltr\">kubectl debug --target</code> يُلحق container مؤقتًا فيه أدوات بـ Pod يعمل، وهكذا تفحص الـ images من نوع distroless.","‏Pod الإنتاج يعمل كمستخدم غير root، دون تصعيد للصلاحيات، مع نظام ملفات جذري للقراءة فقط، وبعد إسقاط كل الـ capabilities."],"further":[{"title":"Gateway API","url":"https://kubernetes.io/docs/concepts/services-networking/gateway/"},{"title":"Debug Running Pods","url":"https://kubernetes.io/docs/tasks/debug/debug-application/debug-running-pod/"},{"title":"Configure a Security Context for a Pod or Container","url":"https://kubernetes.io/docs/tasks/configure-pod-container/security-context/"},{"title":"Ingress NGINX Retirement","url":"https://kubernetes.io/blog/2025/11/11/ingress-nginx-retirement/"}],"quiz":[{"q":"<p>يشغّل فريق المنصّة لديكم Gateway مشتركًا في الـ namespace ‏<code dir=\"ltr\">infra</code>. ماذا تنشئ أنت، بصفتك فريق notes-api، لعرض <code dir=\"ltr\">notes.skylane.example</code>؟</p>","options":[{"text":"‏GatewayClass جديدًا لـ notes-api.","why":"الـ GatewayClasses تصف أداة تنفيذ (implementation)، ويضبطها فريق المنصّة عادةً مرة واحدة لكل cluster."},{"text":"‏HTTPRoute في الـ namespace ‏<code dir=\"ltr\">notes</code> يشير إلى الـ Gateway المشترك في <code dir=\"ltr\">parentRefs</code> وإلى الـ Service ‏<code dir=\"ltr\">notes-api</code> في <code dir=\"ltr\">backendRefs</code>.","why":"صحيح. الـ Routes تخصّ فرق التطبيقات؛ والـ Gateway يقرّر (عبر <code dir=\"ltr\">allowedRoutes</code>) أي namespaces يمكنها الارتباط به."},{"text":"‏Gateway ثانيًا يستمع على المنفذ 443.","why":"هذا يكرّر نقطة الدخول الخاصة بالمنصّة وإعداد TLS فيها. اربط route بالـ Gateway الموجود."},{"text":"‏Service من نوع LoadBalancer لـ notes-api.","why":"هذا يتجاوز نقطة الدخول المشتركة ويعطيك عنوانًا عامًا منفصلًا عليك إدارته."}],"answer":1},{"q":"<p>‏Pod يُظهر الحالة <code dir=\"ltr\">ImagePullBackOff</code>. ما الخطوة التالية الأكثر فائدة؟</p>","options":[{"text":"<code dir=\"ltr\">kubectl logs &lt;pod&gt;</code>","why":"الـ container لم يبدأ أصلًا، فلا توجد سجلات لقراءتها."},{"text":"<code dir=\"ltr\">kubectl rollout restart deployment/notes-api</code>","why":"الـ Pods الجديدة ستفشل في سحب الـ image نفسها بالطريقة نفسها."},{"text":"<code dir=\"ltr\">kubectl debug -it &lt;pod&gt; --image=busybox:1.37</code>","why":"لا يوجد container يعمل لتلتحق به، والمشكلة في السحب، لا في تشغيل الـ Pod."},{"text":"الأمر <code dir=\"ltr\">kubectl describe pod &lt;pod&gt;</code> ثم قراءة خطأ السحب في الأحداث، كـ tag خاطئ، أو بيانات اعتماد registry مفقودة، أو عدم وجود image لهذه المعمارية.","why":"صحيح. الأحداث تذكر بالضبط لماذا لم يستطع الـ kubelet السحب."}],"answer":3},{"q":"<p>‏notes-api في حالة <code dir=\"ltr\">CrashLoopBackOff</code>. والأمر <code dir=\"ltr\">kubectl logs &lt;pod&gt;</code> لا يعرض إلا سطرًا أو اثنين من مخرجات البدء. ماذا تشغّل؟</p>","options":[{"text":"‏<code dir=\"ltr\">kubectl logs &lt;pod&gt; --previous</code>، لقراءة مخرجات نسخة الـ container التي انهارت.","why":"صحيح. النسخة الحالية ربما بدأت للتوّ. أما <code dir=\"ltr\">--previous</code> فيعرض مخرجات النسخة السابقة، بما فيها الخطأ الذي قتلها."},{"text":"‏<code dir=\"ltr\">kubectl delete pod &lt;pod&gt;</code>، حتى يبدأ من جديد.","why":"البديل يشغّل الـ image نفسها بالإعدادات نفسها وينهار بالطريقة نفسها، وتكون قد أضعت الأدلّة."},{"text":"<code dir=\"ltr\">kubectl scale deployment/notes-api --replicas=0</code>","why":"هذا يوقف حلقة الانهيار بإيقاف الخدمة، دون أن يخبرك بأي شيء."},{"text":"<code dir=\"ltr\">kubectl get nodes</code>","why":"صحة الـ nodes نادرًا ما تسبّب حلقة انهيار في تطبيق واحد؛ مخرجات التطبيق نفسه هي نقطة البداية."}],"answer":0},{"q":"<p>الـ image الخاصة بـ notes-api من نوع distroless، فيفشل <code dir=\"ltr\">kubectl exec -it &lt;pod&gt; -- sh</code>. كيف تتحقّق من تحليل DNS من داخل الـ Pod؟</p>","options":[{"text":"أعِد بناء الـ image مع shell، وانشرها، ثم حاول مجددًا.","why":"هذا يغيّر الشيء الذي تشخّصه ويستغرق دورة إصدار كاملة."},{"text":"استخدم <code dir=\"ltr\">kubectl port-forward</code> وشغّل <code dir=\"ltr\">nslookup</code> على حاسوبك.","why":"هذا يحلّ الأسماء بـ DNS حاسوبك، لا بـ DNS الـ Pod."},{"text":"‏<code dir=\"ltr\">kubectl debug -it &lt;pod&gt; --image=busybox:1.37 --target=api</code>، ثم شغّل <code dir=\"ltr\">nslookup notes-db</code> داخل container التشخيص.","why":"صحيح. الـ ephemeral container يتشارك الـ network namespace الخاصة بالـ Pod (ومع <code dir=\"ltr\">--target</code> يرى عملياته أيضًا)، ويجلب الأدوات التي تفتقر إليها الـ image."},{"text":"اقرأ <code dir=\"ltr\">/etc/resolv.conf</code> بـ <code dir=\"ltr\">kubectl logs</code>.","why":"السجلات تعرض ما طبعته العملية، لا الملفات داخل الـ container."}],"answer":2}],"exercise":{"kind":"choice","multiple":true,"options":["crash-logs","pending-describe","pull-logs","notready-probe","crash-delete"],"correct":["crash-logs","pending-describe","notready-probe"],"title":"الخطوة الأولى لكل عَرَض","prompt":"<p>أنت في المناوبة على notes-api. لكل عَرَض أدناه، يقترح زميلك خطوة أولى. اختر <strong>كل</strong> ربط تكون فيه الخطوة الأولى صحيحة.</p>\n","hints":["اسأل عن كل واحد: هل عمل container فعلًا حتى الآن؟ إن لم يعمل، فلا سجلات له.","أي الخطوات تجمع الأدلّة، وأيها يدمّرها؟","ثلاثة من الروابط صحيحة."],"explanation":"<p>حلقة الانهيار تترك خطأها في مخرجات الـ container السابق، لذا يأتي <code dir=\"ltr\">logs --previous</code> أولًا. والـ Pod العالق في Pending لم يُجدوَل أصلًا؛ و<code dir=\"ltr\">describe</code> يُظهر سبب الـ scheduler. والـ Pod الذي يعمل لكنه غير جاهز يفشل في الـ readiness probe؛ و<code dir=\"ltr\">describe</code> يُظهر الإخفاقات، و<code dir=\"ltr\">/readyz</code> يخبرك بالاعتمادية المتعطّلة. أما فشل سحب الـ image فلا سجلات له لأن أي container لم يعمل؛ اقرأ خطأ السحب في <code dir=\"ltr\">describe</code>. وحذف Pod ينهار يُضيّع سجلاته وأحداثه، والبديل ينهار بالطريقة نفسها.</p>\n","checkLabels":{},"optionText":{"crash-logs":{"text":"<code dir=\"ltr\">CrashLoopBackOff</code> ← <code dir=\"ltr\">kubectl logs &lt;pod&gt; --previous</code>","why":"صحيح. مخرجات النسخة المنهارة تحتوي عادةً على الخطأ الذي قتلها."},"pending-describe":{"text":"<code dir=\"ltr\">Pending</code> لخمس دقائق ← <code dir=\"ltr\">kubectl describe pod &lt;pod&gt;</code> وقراءة أحداث الجدولة","why":"صحيح. أحداث مثل <code dir=\"ltr\">Insufficient memory</code> أو node selector غير مطابق تشرح لماذا لم يقبله أي node."},"pull-logs":{"text":"<code dir=\"ltr\">ImagePullBackOff</code> ← <code dir=\"ltr\">kubectl logs &lt;pod&gt;</code>","why":"الـ container لم يبدأ أبدًا، فلا توجد سجلات. ‏<code dir=\"ltr\">describe</code> يُظهر خطأ السحب."},"notready-probe":{"text":"<code dir=\"ltr\">Running</code> لكن <code dir=\"ltr\">0/1</code> جاهز ← <code dir=\"ltr\">kubectl describe pod &lt;pod&gt;</code> لرؤية إخفاقات الـ probe، ثم التحقّق مما يعتمد عليه <code dir=\"ltr\">/readyz</code>","why":"صحيح. إخفاقات الـ readiness تُسجَّل كأحداث، واعتماديات ذلك الـ endpoint هي السبب عادةً."},"crash-delete":{"text":"<code dir=\"ltr\">CrashLoopBackOff</code> ← <code dir=\"ltr\">kubectl delete pod &lt;pod&gt;</code> حتى يُعاد تشغيله نظيفًا","why":"البديل يشغّل الـ image والإعدادات نفسها وينهار بالطريقة نفسها، وتذهب أدلّة الـ Pod القديم."}}},"words":651,"readMinutes":4}