{"checkpoints":{"s-dk-1":[{"q":"<p>تشغّل notes-api بالأمر <code dir=\"ltr\">docker run --memory 256m</code>. تحت اختبار حمل يموت الـ container ويعرض <code dir=\"ltr\">docker ps -a</code> الحالة <code dir=\"ltr\">Exited (137)</code>. ما الذي فرض الحدّ؟</p>","options":[{"text":"الـ PID namespace، التي تضع سقفًا لما تستطيع شجرة العمليات حجزه.","why":"الـ namespaces تتحكّم فيما تراه العملية، لا في مقدار ما تستهلكه."},{"text":"‏Node.js نفسه، الذي يقرأ <code dir=\"ltr\">--memory</code> ويخرج بالرمز 137.","why":"لا يقرأ Node خيارات Docker. الحدّ يُفرض من خارج العملية."},{"text":"حدّ ذاكرة في الـ cgroups؛ أنهى الـ OOM killer في النواة العملية بإشارة SIGKILL ‏(128 + 9 = 137).","why":"صحيح. الـ cgroups تفرض حدود الموارد، والقتل بسبب نفاد الذاكرة يظهر كرمز الخروج 137."},{"text":"الـ layers المخصّصة للقراءة فقط في الـ image، التي لا تتّسع لأكثر من 256 ميغابايت.","why":"الـ layers على القرص ولا علاقة لها بمقدار الذاكرة الذي تستخدمه العملية."}],"answer":2},{"q":"<p>تغيّر سطرًا واحدًا في <code dir=\"ltr\">src/server.ts</code> وتعيد بناء هذه الـ image. كم يستغرق البناء تقريبًا مقارنة بإعادة بناء دون تغييرات؟</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"dockerfile\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">Dockerfile</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:#CF222E;--shiki-dark:#FF7B72\">FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> node:24-slim</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">WORKDIR</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> /app</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">COPY</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> . .</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">RUN</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> npm ci</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">RUN</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> npm run build</span></span></code></pre></div>\n","options":[{"text":"أطول بكثير، لأن <code dir=\"ltr\">COPY . .</code> تُخفق في الـ cache، فتُعاد كل خطوة بعدها، بما فيها <code dir=\"ltr\">npm ci</code> الكامل.","why":"صحيح. تغيير الكود يُبطل <code dir=\"ltr\">COPY</code>، والتثبيت المكلف يأتي بعدها. انسخ ملفات الحزم أولًا."},{"text":"المدة نفسها تقريبًا، لأن <code dir=\"ltr\">npm run build</code> وحده يحتاج إلى الإعادة.","why":"كان هذا سيصحّ لو ثُبّتت الاعتماديات قبل نسخ الكود. وهذا ليس الحال هنا."},{"text":"أسرع، لأن Docker يعيد استخدام مجلد <code dir=\"ltr\">node_modules</code> السابق.","why":"حالما تُخفق <code dir=\"ltr\">COPY . .</code>، يُعاد <code dir=\"ltr\">npm ci</code> من الصفر على الـ layer الجديدة."},{"text":"يفشل، لأن الملفات المتغيّرة لا يمكن نسخها فوق layers مخزّنة.","why":"الملفات المتغيّرة تنتج ببساطة layer جديدة. لا شيء يفشل؛ الأمر بطيء فقط."}],"answer":0},{"q":"<p>يعمل notes-api بـ <code dir=\"ltr\">CMD [&quot;node&quot;, &quot;dist/server.js&quot;]</code> ولا معالج SIGTERM فيه. ماذا يفعل <code dir=\"ltr\">docker stop</code>؟</p>","options":[{"text":"يخرج Node فورًا بالرمز 143، لأن الإجراء الافتراضي لـ SIGTERM هو إنهاء العملية.","why":"هذا ما يحدث مع العملية العادية. أما في موقع PID 1 فلا يُطبَّق الإجراء الافتراضي، فتُتجاهل الإشارة."},{"text":"يعيد Docker تشغيل الـ container بدلًا من إيقافه.","why":"الأمر <code dir=\"ltr\">docker stop</code> لا يعيد التشغيل أبدًا. قد تعيد سياسة إعادة التشغيل تشغيله لاحقًا، لكن ليس كجزء من الإيقاف."},{"text":"ينهي Node الطلبات الجارية ويخرج بالرمز 0.","why":"التفريغ الآمن لا يحدث إلا إذا عالج كودك SIGTERM. ودون معالج لا يوجد تفريغ."},{"text":"تُتجاهل الإشارة لأن Node في موقع PID 1 دون معالج، فبعد 10 ثوانٍ يرسل Docker إشارة SIGKILL ويخرج الـ container بالرمز 137.","why":"صحيح. أضف معالج SIGTERM يغلق الخادم ومجمّع اتصالات قاعدة البيانات، فيستغرق الإيقاف أقل بكثير من ثانية."}],"answer":3},{"q":"<p>تشغّل <code dir=\"ltr\">docker run -d -p 3000:3000 notes-api:dev</code>. السجلات تقول <code dir=\"ltr\">listening on 3000</code>، لكن <code dir=\"ltr\">curl localhost:3000</code> من حاسوبك يحصل على <code dir=\"ltr\">connection reset</code>. ما الذي تتحقّق منه أولًا؟</p>","options":[{"text":"هل في الـ image سطر <code dir=\"ltr\">EXPOSE 3000</code>.","why":"التعليمة <code dir=\"ltr\">EXPOSE</code> توثيق؛ والخيار <code dir=\"ltr\">-p</code> نشر المنفذ أصلًا."},{"text":"هل يرتبط التطبيق بـ <code dir=\"ltr\">127.0.0.1</code> داخل الـ container بدلًا من <code dir=\"ltr\">0.0.0.0</code>.","why":"صحيح. الحركة المنشورة تصل إلى واجهة الشبكة الخاصة بالـ container، والمستمع على الـ loopback وحده لا يراها أبدًا."},{"text":"هل المنفذ 3000 مفتوح في جدار الحماية على المضيف.","why":"المنفذ المحجوب سيرفض الاتصال أو تنتهي مهلته من البداية؛ أما إعادة الضبط بعد الاتصال فتشير إلى أن لا شيء يستمع على تلك الواجهة داخل الـ container."},{"text":"هل Docker Desktop محدّث.","why":"مشاكل الإصدارات نادرًا ما تكون السبب؛ وعنوان الاستماع متّهم أكثر شيوعًا بكثير."}],"answer":1},{"q":"<p>أي عبارة عن الـ images والـ containers صحيحة؟</p>","options":[{"text":"يحصل كل container على نسخة كاملة خاصة به من ملفات الـ image على القرص.","why":"الـ containers تتشارك layers الـ image المخصّصة للقراءة فقط، ولا يضيف كل منها إلا layer رقيقة قابلة للكتابة."},{"text":"حذف الـ container يحذف الـ image الخاصة به أيضًا.","why":"الـ images تبقى حتى تحذفها بـ <code dir=\"ltr\">docker image rm</code> أو بعملية prune."},{"text":"يمكن تشغيل containers كثيرة من image واحدة؛ ولكل منها layer خاصة قابلة للكتابة تُحذف مع الـ container.","why":"صحيح. ولهذا توضع البيانات التي يجب أن تبقى في volume."},{"text":"تغييرات الـ container الذي يعمل تُكتب عائدةً إلى الـ image حين يتوقّف.","why":"الـ images غير قابلة للتغيير. التغييرات تبقى في الـ layer القابلة للكتابة في الـ container، والتي يمكن أن يلتقطها <code dir=\"ltr\">docker commit</code>، لكن لا ينبغي لك فعل ذلك."}],"answer":2}],"s-dk-2":[{"q":"<p>مرحلة التشغيل في notes-api فيها <code dir=\"ltr\">COPY --from=build /app/dist ./dist</code> و<code dir=\"ltr\">COPY --from=deps /app/node_modules ./node_modules</code>، لكن بدء الـ container يفشل برسالة <code dir=\"ltr\">Cannot use import statement outside a module</code>. ما الناقص؟</p>","options":[{"text":"‏TypeScript، الذي يجب تثبيته في مرحلة التشغيل.","why":"بيئة التشغيل تشغّل JavaScript مترجَمة. ولا حاجة إلى TypeScript إلا في مرحلة البناء."},{"text":"الملف <code dir=\"ltr\">package.json</code>، الذي يخبر الحقل <code dir=\"ltr\">&quot;type&quot;: &quot;module&quot;</code> فيه Node بمعاملة ملفات <code dir=\"ltr\">.js</code> كـ ES modules.","why":"صحيح. انسخ <code dir=\"ltr\">package.json</code> إلى مرحلة التشغيل بجوار <code dir=\"ltr\">dist</code> و<code dir=\"ltr\">node_modules</code>."},{"text":"المجلد <code dir=\"ltr\">src/</code>، الذي يقرؤه Node عند البدء.","why":"يشغّل Node الملف <code dir=\"ltr\">dist/server.js</code>؛ ولا يقرأ مصادر TypeScript أبدًا."},{"text":"تعليمة <code dir=\"ltr\">ENTRYPOINT</code>.","why":"‏<code dir=\"ltr\">CMD</code> وحدها تبدأ التطبيق. والخطأ يأتي من طريقة تحليل Node للملف، لا من طريقة تشغيله."}],"answer":1},{"q":"<p>يشترط فريق الأمن لديكم ألا تستطيع عملية notes-api تعديل كودها حتى لو حصل مهاجم على تنفيذ كود عن بُعد. أي خيار في الـ Dockerfile يحقّق ذلك؟</p>","options":[{"text":"استخدم <code dir=\"ltr\">COPY --chown=node:node</code> لكل الملفات حتى يملكها التطبيق.","why":"هذا يعطي مستخدم التطبيق صلاحية الكتابة على كوده، وهو عكس المطلوب."},{"text":"شغّل كـ root لكن ركّب نظام الملفات للقراءة فقط.","why":"الجذر المخصّص للقراءة فقط يساعد، لكن التشغيل كـ root يُضعف كل ما عداه. اجمع بين المستخدم غير الـ root والقراءة فقط، ولا تقايض أحدهما بالآخر."},{"text":"استخدم الـ image الكاملة <code dir=\"ltr\">node:24</code> حتى تتحقّق أدوات النظام من سلامة الملفات.","why":"أدوات أكثر تعني مساحة هجوم أكبر، لا حماية."},{"text":"اترك الملفات المنسوخة مملوكة لـ root وانتقل إلى <code dir=\"ltr\">USER node</code> في النهاية، فيستطيع التطبيق قراءتها لا الكتابة فيها.","why":"صحيح. ملفات مملوكة لـ root وقابلة للقراءة من الجميع، مع مستخدم بلا صلاحيات، تعني أن الكود للقراءة فقط بالنسبة للعملية."}],"answer":3},{"q":"<p>مهمّة في الـ CI تبني <code dir=\"ltr\">notes-api</code> على runner من نوع amd64 وتنشرها على cluster من nodes ‏arm64. تفشل الـ Pods برسالة <code dir=\"ltr\">exec format error</code>. ما الحل؟</p>","options":[{"text":"ابنِ بـ <code dir=\"ltr\">docker buildx build --platform linux/amd64,linux/arm64</code> وادفع، حتى يشير الـ tag إلى فهرس يضمّ المعماريّتين.","why":"صحيح. كل node يسحب الـ manifest الخاص بمعماريّته من الـ tag نفسه."},{"text":"ثبّت الـ base image بالـ digest.","why":"التثبيت بالـ digest يثبّت البايتات، لكن إن كانت تلك البايتات لـ amd64 وحدها، فلن تستطيع nodes ‏arm64 تشغيلها."},{"text":"غيّر الـ base image إلى <code dir=\"ltr\">node:24-alpine</code>.","why":"images ‏Alpine أيضًا لكل معمارية على حدة. والبناء ما زال ينتج amd64 فقط."},{"text":"أضف <code dir=\"ltr\">imagePullPolicy: Always</code>.","why":"يسحب الـ image نفسها التي لا تدعم إلا amd64 مرة أخرى."}],"answer":0},{"q":"<p>في الـ registry لديك <code dir=\"ltr\">notes-api:1.4.0</code>. إصلاح عاجل جاهز. أي tag تعطيه؟</p>","options":[{"text":"‏<code dir=\"ltr\">1.4.0</code> مرة أخرى، حتى لا تحتاج الـ manifests إلى تغيير.","why":"إعادة دفع tag منشور تعني أن الـ nodes تشغّل كودًا مختلفًا تحت الاسم نفسه حسب وقت سحبها. لا تُعِد استخدامه أبدًا."},{"text":"‏<code dir=\"ltr\">latest</code>، لأنه أحدث بناء.","why":"‏<code dir=\"ltr\">latest</code> مجرد اسم افتراضي؛ لا يعطي أي قابلية للتتبّع ويتغيّر من تحتك."},{"text":"‏<code dir=\"ltr\">1.4.1</code> مع tag ‏<code dir=\"ltr\">sha-&lt;commit&gt;</code>، وتحديث الـ manifest (ويفضَّل إلى الـ digest الجديد).","why":"صحيح. المحتوى الجديد يحصل على إصدار جديد؛ وtag الـ SHA والـ digest يجعلانه قابلًا للتتبّع ودقيقًا."},{"text":"‏<code dir=\"ltr\">1.4.0-fixed</code>، مع الكتابة فوقه إن جاء إصلاح آخر.","why":"الكتابة فوق tag لها المشكلة نفسها التي لإعادة استخدام <code dir=\"ltr\">1.4.0</code>."}],"answer":2},{"q":"<p>يُبلغ <code dir=\"ltr\">docker scout cves</code> عن CVE حرجة في حزمة npm يستخدمها notes-api، ويوجد إصدار مُصلَح. ما الاستجابة الصحيحة؟</p>","options":[{"text":"غيّر الـ base image إلى distroless.","why":"‏distroless تزيل حزم نظام التشغيل، لكن حزمة npm المعرّضة في <code dir=\"ltr\">node_modules</code> لديك وتُشحن في الحالتين."},{"text":"ارفع الاعتمادية إلى الإصدار المُصلَح، وأعِد البناء، وأعِد الفحص، وأبقِ بوّابة الـ CVE في الـ CI.","why":"صحيح. الاعتمادية القابلة للإصلاح تُصلَح، والبوّابة تمنع عودتها."},{"text":"أضف الـ CVE إلى قائمة التجاهل حتى ينجح البناء.","why":"تجاهل نتيجة حرجة قابلة للإصلاح يخفي خطرًا حقيقيًا؛ قوائم التجاهل للحالات الموثّقة التي لا يمكن الوصول إليها أو لا يمكن إصلاحها."},{"text":"أعِد البناء بـ <code dir=\"ltr\">--no-cache</code> حتى يرى الماسح layers جديدة.","why":"البناء الجديد يثبّت الإصدار المقفل نفسه ويحصل على النتيجة نفسها."}],"answer":1}],"s-dk-3":[{"q":"<p>في Compose، قيمة <code dir=\"ltr\">DATABASE_URL</code> لخدمة <code dir=\"ltr\">api</code> هي <code dir=\"ltr\">postgres://notes:notes@db:5432/notes</code>. تعيد تسمية الخدمة من <code dir=\"ltr\">db</code> إلى <code dir=\"ltr\">postgres</code> وتشغّل <code dir=\"ltr\">docker compose up -d --remove-orphans</code>. ماذا يحدث؟</p>","options":[{"text":"لا شيء يتغيّر؛ يحتفظ Compose باسم الـ DNS القديم كاسم بديل.","why":"أسماء الـ DNS تأتي من أسماء الخدمات الحالية. الخيار <code dir=\"ltr\">--remove-orphans</code> يزيل الـ container القديم <code dir=\"ltr\">db</code>، فيذهب اسمه معه. (ودون هذا الخيار يبقى الـ container اليتيم يعمل ويجيب على الاسم <code dir=\"ltr\">db</code>، فيُخفي الخلل.)"},{"text":"يعيد Compose كتابة <code dir=\"ltr\">DATABASE_URL</code> ليطابق.","why":"لا يعدّل Compose قيم متغيّرات البيئة نيابة عنك أبدًا."},{"text":"يُحلّ الاسمان كلاهما، لأن الـ volume ما زال اسمه <code dir=\"ltr\">pgdata</code>.","why":"أسماء الـ volumes لا علاقة لها بـ DNS الشبكة."},{"text":"لم يعد الـ API يستطيع حلّ <code dir=\"ltr\">db</code> ويفشل في الاتصال حتى تحدّث المضيف في العنوان إلى <code dir=\"ltr\">postgres</code>.","why":"صحيح. الخدمات يجد بعضها بعضًا باسم الخدمة، فيجب أن يتبع نصّ الاتصال إعادة التسمية."}],"answer":3},{"q":"<p>تشغّل <code dir=\"ltr\">docker compose down</code>، وتعدّل <code dir=\"ltr\">db/init/002-seed.sql</code>، ثم تشغّل <code dir=\"ltr\">docker compose up -d</code>. لا تظهر البيانات الأولية. لماذا؟</p>","options":[{"text":"‏<code dir=\"ltr\">down</code> حذف الـ bind mount.","why":"الـ bind mounts تشير إلى مجلد على مضيفك؛ ولا شيء يحذفه."},{"text":"سكربتات التهيئة تعمل بترتيب معكوس.","why":"إنها تعمل بترتيب الأسماء، وليست هذه هي المشكلة هنا."},{"text":"الـ volume ‏<code dir=\"ltr\">pgdata</code> صمد بعد <code dir=\"ltr\">down</code>، فقاعدة البيانات مهيّأة أصلًا وتُتخطّى سكربتات التهيئة.","why":"صحيح. استخدم migration للتغييرات على قاعدة بيانات موجودة، أو احذف volume التطوير عن قصد بـ <code dir=\"ltr\">down -v</code>."},{"text":"لا يقرأ Postgres ملفات <code dir=\"ltr\">.sql</code> إلا وقت بناء الـ image.","why":"سكربتات التهيئة تعمل عند بدء الـ container، لكن فقط على مجلد بيانات فارغ."}],"answer":2},{"q":"<p>خدمة <code dir=\"ltr\">migrate</code> تشغّل migrations المخطّط ثم تخرج. تريد ألا تبدأ <code dir=\"ltr\">api</code> إلا بعد أن يصبح Postgres سليمًا وتنجح الـ migration. أي <code dir=\"ltr\">depends_on</code> صحيح لـ <code dir=\"ltr\">api</code>؟</p>","options":[{"text":"‏<code dir=\"ltr\">db</code> مع <code dir=\"ltr\">condition: service_healthy</code>، و<code dir=\"ltr\">migrate</code> مع <code dir=\"ltr\">condition: service_completed_successfully</code>.","why":"صحيح. الأول ينتظر الـ healthcheck؛ والثاني ينتظر خروج المهمّة بالرمز 0."},{"text":"‏<code dir=\"ltr\">db</code> و<code dir=\"ltr\">migrate</code> بصيغة القائمة المختصرة.","why":"الصيغة المختصرة لا تنتظر إلا بدء الـ containers، لا الجاهزية ولا الاكتمال."},{"text":"‏<code dir=\"ltr\">migrate</code> مع <code dir=\"ltr\">condition: service_healthy</code>.","why":"المهمّة التي تخرج لا تصبح سليمة أبدًا، فلن تبدأ <code dir=\"ltr\">api</code> أبدًا."},{"text":"‏<code dir=\"ltr\">db</code> مع <code dir=\"ltr\">condition: service_started</code> و<code dir=\"ltr\">restart: true</code>.","why":"هذا ما زال لا ينتظر أن يقبل Postgres الاتصالات ولا أن تنتهي الـ migration."}],"answer":0},{"q":"<p>ملف <code dir=\"ltr\">.env</code> في مشروعك يحتوي على <code dir=\"ltr\">POSTGRES_PASSWORD=devpass</code>. والملف <code dir=\"ltr\">compose.yaml</code> يضبط <code dir=\"ltr\">POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}</code> تحت <code dir=\"ltr\">db.environment</code>. تشغّل <code dir=\"ltr\">POSTGRES_PASSWORD=other docker compose up -d</code> على volume جديد. أي كلمة مرور يحصل عليها Postgres؟</p>","options":[{"text":"‏<code dir=\"ltr\">devpass</code>، لأن <code dir=\"ltr\">.env</code> يفوز دائمًا.","why":"في الاستبدال، المتغيّر المضبوط أصلًا في الـ shell له الأولوية على <code dir=\"ltr\">.env</code>."},{"text":"‏<code dir=\"ltr\">other</code>، لأن متغيّرات بيئة الـ shell لها الأولوية على <code dir=\"ltr\">.env</code> في الاستبدال.","why":"صحيح. مفيد للتجاوزات لمرة واحدة، وسبب لفحص <code dir=\"ltr\">docker compose config</code> حين تفاجئك القيم."},{"text":"الاثنتان، موصولتين بفاصلة.","why":"للمتغيّرات قيمة واحدة؛ ولا يُوصل شيء بشيء."},{"text":"ولا واحدة؛ يُخرج Compose خطأ عند تعارض القيم.","why":"لا يوجد خطأ تعارض؛ قواعد الأولوية هي التي تقرّر."}],"answer":1},{"q":"<p>إعداد التطوير لدى زميلك يربط المشروع كاملًا (<code dir=\"ltr\">.:/app</code>) بـ bind mount داخل container الـ API على macOS. يفشل التطبيق بخطأ في وحدة أصلية (native module)، وتبدو تغييرات الملفات بطيئة. بماذا توصي؟</p>","options":[{"text":"أضف <code dir=\"ltr\">:ro</code> إلى الـ bind mount.","why":"القراءة فقط لا تمنع <code dir=\"ltr\">node_modules</code> المبني لـ macOS على المضيف من حجب نظيره المبني لـ Linux في الـ container."},{"text":"انتقل إلى named volume للمشروع كاملًا.","why":"الـ named volume لن يرى تعديلاته إطلاقًا."},{"text":"شغّل <code dir=\"ltr\">npm ci</code> على جهاز Mac قبل كل بدء.","why":"هذا ما زال ينتج ملفات تنفيذية لـ macOS، لا يستطيع Linux تحميلها."},{"text":"استخدم Compose watch لمزامنة <code dir=\"ltr\">./src</code> إلى الـ container وإعادة البناء عند تغيّر الـ lockfile، مع إبقاء <code dir=\"ltr\">node_modules</code> داخل الـ image.","why":"صحيح. ملفات الكود وحدها تعبر حدود الآلة الافتراضية، وتبقى الاعتماديات مبنية لـ Linux."}],"answer":3}],"s-dk-4":[{"q":"<p>تطبّق Deployment بـ 3 نسخ، ثم تحذف الـ ReplicaSet الخاص به بالأمر <code dir=\"ltr\">kubectl delete rs notes-api-6c8f9d7b5</code>. ماذا يحدث؟</p>","options":[{"text":"ينشئ الـ controller الخاص بالـ Deployments كائن ReplicaSet جديدًا للقالب نفسه، فينشئ هذا 3 Pods من جديد.","why":"صحيح. ما زال الـ Deployment يصرّح بـ 3 نسخ من ذلك القالب، فيعيد الـ controller الخاص به الـ ReplicaSet المفقود."},{"text":"يُحذف الـ Deployment أيضًا، لأنه كان يملك الـ ReplicaSet.","why":"الملكية تسري إلى الأسفل: حذف الابن لا يحذف مالكه."},{"text":"تستمر الـ Pods في العمل دون مالك إلى الأبد.","why":"حذف الـ ReplicaSet يحذف الـ Pods الخاصة به افتراضيًا، والـ Deployment يعيد إنشاء الاثنين على أي حال."},{"text":"لا شيء، لأن الـ ReplicaSets لا يمكن حذفها مباشرة.","why":"تستطيع حذفها؛ والـ controller ببساطة يستعيد ما يصرّح به الـ Deployment."}],"answer":0},{"q":"<p>تحتاج الـ Pods في الـ namespace ‏<code dir=\"ltr\">web</code> إلى استدعاء notes-api، الذي يعيش الـ Service ‏<code dir=\"ltr\">notes-api</code> (المنفذ 80) الخاص به في الـ namespace ‏<code dir=\"ltr\">notes</code>. أي عنوان يعمل؟</p>","options":[{"text":"‏<code dir=\"ltr\">http://notes-api</code> (المنفذ 80)","why":"الاسم القصير لا يُحلّ إلا داخل الـ namespace نفسه، فمن <code dir=\"ltr\">web</code> لا يجد الـ Service الموجود في <code dir=\"ltr\">notes</code>."},{"text":"‏<code dir=\"ltr\">http://notes-api.notes</code> (المنفذ 80)، أو الاسم الكامل <code dir=\"ltr\">notes-api.notes.svc.cluster.local</code>","why":"صحيح. إضافة الـ namespace إلى الاسم تجعله قابلًا للحلّ من أي namespace."},{"text":"<code dir=\"ltr\">http://notes:3000</code>","why":"‏<code dir=\"ltr\">notes</code> اسم الـ namespace، لا Service، و3000 منفذ الـ Pod لا منفذ الـ Service."},{"text":"‏<code dir=\"ltr\">http://localhost:80</code> مع NodePort","why":"‏<code dir=\"ltr\">localhost</code> هو الـ Pod المتّصِل نفسه."}],"answer":1},{"q":"<p>بعد تغيير ما، صارت Pods ‏notes-api في حالتَي Running وReady، لكن الـ EndpointSlice الخاص بالـ Service فارغ. ما الذي تغيّر على الأرجح؟</p>","options":[{"text":"رُفع الـ tag الخاص بالـ image.","why":"الـ image الجديدة تنشئ Pods جديدة بالـ labels نفسها؛ والـ endpoints تتبعها."},{"text":"تغيّر نوع الـ Service من ClusterIP إلى LoadBalancer.","why":"تغيير النوع يضيف عرضًا خارجيًا؛ ولا يغيّر الـ Pods التي تقف خلف الـ Service."},{"text":"تغيّرت الـ labels في قالب الـ Pod ولم تعد تطابق الـ selector الخاص بالـ Service.","why":"صحيح. الـ Pods الجاهزة دون labels مطابقة غير مرئية للـ Service. قارن مخرجات <code dir=\"ltr\">kubectl get pods --show-labels</code> بالـ selector."},{"text":"حُدّث الـ ConfigMap.","why":"قيم الإعدادات لا تؤثّر في مطابقة الـ labels."}],"answer":2},{"q":"<p>تحدّث الـ Secret ‏<code dir=\"ltr\">notes-db</code> بكلمة مرور جديدة، وتُغيَّر كلمة مرور Postgres لتطابقها. يقرأ notes-api قيمة <code dir=\"ltr\">DATABASE_URL</code> عبر <code dir=\"ltr\">secretKeyRef</code>. تبدأ الـ Pods الموجودة بالفشل في المصادقة. لماذا، وما الحل؟</p>","options":[{"text":"الـ Secrets تُخزَّن مؤقتًا لمدة 24 ساعة؛ انتظر انتهاء الصلاحية.","why":"لا توجد قاعدة تخزين مؤقت كهذه لمتغيّرات البيئة. إنها لا تتغيّر أبدًا في container يعمل."},{"text":"يجب حذف الـ Secret وإعادة إنشائه، لا تحديثه.","why":"التحديث لا بأس به؛ المشكلة في طريقة استهلاك الـ Pods العاملة له."},{"text":"يجب تغيير الـ selector في الـ Deployment ليلتقط الـ Secret الجديد.","why":"الـ selectors تختار الـ Pods، لا الإعدادات. وتغييرها سيكسر الـ Deployment."},{"text":"متغيّرات البيئة تُقرأ عند بدء الـ container، فالـ Pods العاملة ما زالت تستخدم القيمة القديمة؛ شغّل <code dir=\"ltr\">kubectl rollout restart deployment/notes-api</code>.","why":"صحيح. الـ Pods الجديدة تقرأ الـ Secret المحدَّث. ولتدوير دون توقّف، اجعل قاعدة البيانات تقبل كلمتَي المرور خلال التبديل."}],"answer":3},{"q":"<p>لماذا تشغّل الفرق <code dir=\"ltr\">kubectl diff -f k8s/</code> قبل <code dir=\"ltr\">kubectl apply -f k8s/</code>؟</p>","options":[{"text":"لترى بالضبط أي الحقول على الـ cluster الحيّ ستتغيّر، فتلتقط المفاجآت مثل إلغاء زيادة طارئة في عدد النسخ.","why":"صحيح. إنها خطوة المراجعة للـ cluster، مثل قراءة pull request قبل الـ merge."},{"text":"لأن <code dir=\"ltr\">apply</code> يفشل ما لم يُشغَّل <code dir=\"ltr\">diff</code> أولًا.","why":"‏<code dir=\"ltr\">apply</code> يعمل وحده؛ و<code dir=\"ltr\">diff</code> عادة أمان، لا شرط."},{"text":"للتحقّق من صياغة YAML فقط.","why":"إنه يحتاج فعلًا إلى YAML صالح، لكن قيمته في مقارنة ملفاتك بالكائنات الحيّة."},{"text":"لتنزيل الـ manifests الحالية إلى مجلد <code dir=\"ltr\">k8s/</code>.","why":"‏<code dir=\"ltr\">diff</code> لا يكتب ملفات؛ إنه يطبع الفروق."}],"answer":0}],"s-dk-5":[{"q":"<p>الـ readiness probe لـ notes-api يستدعي <code dir=\"ltr\">/readyz</code> (الذي يفحص Postgres)، والـ liveness يستدعي <code dir=\"ltr\">/healthz</code>. يتعطّل Postgres لمدة دقيقتين. ماذا يحدث؟</p>","options":[{"text":"تُعاد تشغيل كل الـ Pods مرارًا حتى يعود Postgres.","why":"الـ liveness لا يفحص إلا <code dir=\"ltr\">/healthz</code>، الذي لا يمسّ Postgres، فلا تحدث أي إعادة تشغيل."},{"text":"يعيد Kubernetes جدولة الـ Pods على nodes أخرى.","why":"الـ probes لا تنقل الـ Pods بين الـ nodes أبدًا."},{"text":"تصبح كل الـ Pods غير جاهزة وتغادر endpoints الـ Service؛ ثم تعود دون إعادة تشغيل حين يعود Postgres.","why":"صحيح. تفشل الطلبات سريعًا عند الـ Service بدلًا من أن تعلق في التطبيق، ولا يحتاج التعافي إلى إعادات تشغيل."},{"text":"لا شيء، لأن الـ probes تتوقّف مؤقتًا حين تتعطّل اعتمادية.","why":"لا يعرف Kubernetes شيئًا عن الاعتماديات؛ إنه يشغّل الـ probes كما ضُبطت."}],"answer":2},{"q":"<p>‏node فيه 4 أنوية معالج، ومجموع requests المعالج للـ Pods عليه 3.8 نواة، مع أن الاستهلاك الفعلي نواة واحدة فقط. ‏Pod جديد من notes-api يطلب 250m. ماذا يحدث؟</p>","options":[{"text":"يُجدوَل هناك، لأن الاستهلاك الفعلي منخفض.","why":"يضع الـ scheduler الـ Pods حسب الـ requests، لا حسب الاستهلاك الحالي."},{"text":"يُجدوَل هناك وتُخنق Pods أخرى لإفساح المجال.","why":"الجدولة لا تضغط الـ requests الخاصة بالـ Pods الموجودة أبدًا."},{"text":"يُجدوَل بـ request مخفّض إلى 200m.","why":"الـ requests لا تُعدَّل أبدًا لتناسب node."},{"text":"لا يُجدوَل على ذلك الـ node، لأنه لم يتبقَّ إلا 200m من المعالج القابل للطلب؛ فيذهب إلى مكان آخر أو يبقى Pending.","why":"صحيح. الـ requests حجوزات. والمبالغة في الطلب تهدر السعة حتى حين تبدو الـ nodes خاملة."}],"answer":3},{"q":"<p>أثناء rollout إلى 1.6.0 مع <code dir=\"ltr\">maxUnavailable: 0</code>، يعلق <code dir=\"ltr\">kubectl rollout status</code> ثم يُبلغ <code dir=\"ltr\">exceeded its progress deadline</code>. والـ readiness probe للـ Pod الجديد يفشل. ما حالة الخدمة؟</p>","options":[{"text":"يحصل المستخدمون على أخطاء من Pod الإصدار 1.6.0.","why":"الـ Pod غير الجاهز ليس ضمن endpoints الـ Service، فلا يستقبل أي حركة."},{"text":"ما زالت Pods الإصدار 1.5.x القديمة تخدم بكامل السعة؛ والـ rollout عالق ويحتاج إلى undo أو إصلاح.","why":"صحيح. مع عدم السماح بأي نقص في الإتاحة ومع بوّابة الـ readiness، لم يُقلَّص الـ ReplicaSet القديم أبدًا."},{"text":"تراجع Kubernetes فعلًا إلى الإصدار السابق.","why":"يُبلغ Kubernetes عن الفشل لكنه لا يتراجع من تلقاء نفسه."},{"text":"تقلّص الـ Deployment إلى صفر.","why":"لا شيء في التحديث المتدرّج يقلّص الـ Deployment بأكمله إلى صفر."}],"answer":1},{"q":"<p>الإنتاج يستخدم Gateway مشتركًا. الـ HTTPRoute الخاص بفريق notes يُظهر <code dir=\"ltr\">Accepted: False</code> مع السبب <code dir=\"ltr\">NotAllowedByListeners</code>. ما السبب المرجّح؟</p>","options":[{"text":"الحقل <code dir=\"ltr\">allowedRoutes</code> في listener الـ Gateway لا يسمح بـ routes من الـ namespace ‏<code dir=\"ltr\">notes</code>.","why":"صحيح. الـ Gateways تختار الـ namespaces التي يمكنها ربط routes؛ اطلب من فريق المنصّة السماح لـ <code dir=\"ltr\">notes</code> أو استخدم selector يطابقه."},{"text":"الـ Service الخاص بـ notes-api ليس له endpoints.","why":"الـ endpoints تؤثّر في وصول الحركة إلى الـ Pods، لا في قبول الـ Gateway للـ route."},{"text":"الـ HTTPRoute يستخدم <code dir=\"ltr\">apiVersion: gateway.networking.k8s.io/v1</code>.","why":"هذا هو الإصدار الحالي الصحيح."},{"text":"الـ Pods تفتقر إلى readiness probe.","why":"الـ probes لا تؤثّر في ارتباط الـ route."}],"answer":0},{"q":"<p>‏Pod من notes-api مبني على distroless في حالة Running، لكن كل طلب إليه تنتهي مهلته. يفشل <code dir=\"ltr\">kubectl exec</code> لعدم وجود shell. أي أمر يتيح لك أن تختبر، من داخل شبكة الـ Pod، هل يجيب التطبيق على المنفذ 3000؟</p>","options":[{"text":"<code dir=\"ltr\">kubectl logs &lt;pod&gt; --previous</code>","why":"السجلات تعرض المخرجات السابقة؛ ولا تستطيع اختبار الاتصال، والـ container لم ينهَر أصلًا."},{"text":"‏<code dir=\"ltr\">kubectl port-forward pod/&lt;pod&gt; 3000:3000</code> يليه <code dir=\"ltr\">kubectl events</code>","why":"الـ port-forward يختبر من حاسوبك عبر الـ API server، لا من داخل شبكة الـ Pod، والأحداث لن تُظهر انتهاء مهل الطلبات."},{"text":"‏<code dir=\"ltr\">kubectl debug -it &lt;pod&gt; --image=busybox:1.37 --target=api</code>، ثم <code dir=\"ltr\">wget -qO- localhost:3000/healthz</code>","why":"صحيح. الـ ephemeral container يتشارك الـ network namespace الخاصة بالـ Pod، فيكون <code dir=\"ltr\">localhost:3000</code> هو التطبيق نفسه."},{"text":"<code dir=\"ltr\">kubectl describe node</code>","why":"تفاصيل الـ node لن تخبرك هل يجيب التطبيق داخل الـ Pod."}],"answer":2}]},"final":[{"q":"<p>يريد مهندس في Skylane بناءً أسرع لـ notes-api في الـ CI. البناء يعمل على runners جديدة بـ cache فارغ لـ Docker، والـ Dockerfile مرتّب بشكل صحيح أصلًا. ما الذي يساعد أكثر؟</p>","options":[{"text":"التحويل من <code dir=\"ltr\">npm ci</code> إلى <code dir=\"ltr\">npm install</code>.","why":"ليس أسرع بشكل ملموس مع cache بارد، ويتخلّى عن التثبيت القابل للتكرار."},{"text":"تصدير الـ build cache واستيراده بـ <code dir=\"ltr\">--cache-to</code> و<code dir=\"ltr\">--cache-from</code> (مثلًا إلى registry).","why":"صحيح. عندها تستطيع الـ runners الجديدة إعادة استخدام الـ layers التي بنتها مهام سابقة."},{"text":"البناء بـ <code dir=\"ltr\">--no-cache</code> من أجل الاتّساق.","why":"هذا يضمن تنفيذ كل خطوة، وهو أبطأ بناء ممكن."},{"text":"إضافة المزيد من خطوات <code dir=\"ltr\">RUN</code> حتى توجد نقاط cache أكثر.","why":"الـ layers الإضافية لا تفيد حين لا يوجد cache تقرأ منه أصلًا."}],"answer":1},{"q":"<p>أي سطر في الـ Dockerfile يعرّض بيانات اعتماد للخطر رغم حذفها لاحقًا في البناء نفسه؟</p>","options":[{"text":"<code dir=\"ltr\">RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci</code>","why":"الـ secret mount لا يوجد إلا لتلك الخطوة ولا يُكتب أبدًا في layer. هذا هو النمط الآمن."},{"text":"<code dir=\"ltr\">COPY package.json package-lock.json ./</code>","why":"ملفات التعريف والـ lockfiles لا تحتوي على بيانات اعتماد."},{"text":"<code dir=\"ltr\">USER node</code>","why":"تغيير المستخدم لا يكشف أي سرّ."},{"text":"‏<code dir=\"ltr\">COPY .npmrc .</code> يليه لاحقًا <code dir=\"ltr\">RUN rm .npmrc</code>","why":"صحيح. الملف يبقى حيًّا في layer الـ <code dir=\"ltr\">COPY</code>؛ وحذفه في layer لاحقة يخفيه فقط."}],"answer":3},{"q":"<p>الـ image الخاصة بـ notes-api تعمل بـ <code dir=\"ltr\">USER node</code>، والـ securityContext في الـ Deployment يضبط <code dir=\"ltr\">runAsNonRoot: true</code>. تفشل الـ Pods بـ <code dir=\"ltr\">CreateContainerConfigError</code>. ما أنظف حل؟</p>","options":[{"text":"اضبط مستخدمًا رقميًا، إمّا <code dir=\"ltr\">USER 1000</code> في الـ Dockerfile أو <code dir=\"ltr\">runAsUser: 1000</code> في الـ securityContext، حتى يستطيع الـ kubelet التحقّق من أنه ليس root.","why":"صحيح. لا يستطيع الـ kubelet إثبات أن <em>اسم</em> مستخدم ليس root، فيرفض بدء الـ container."},{"text":"احذف <code dir=\"ltr\">runAsNonRoot</code> من الـ securityContext.","why":"هذا يزيل فحص أمان بدلًا من تلبيته."},{"text":"أعِد الـ image إلى التشغيل كـ root.","why":"عندها يرفضها <code dir=\"ltr\">runAsNonRoot</code> مباشرة، وتكون قد خسرت الحماية على أي حال."},{"text":"أضف <code dir=\"ltr\">allowPrivilegeEscalation: true</code>.","why":"لا علاقة له بالتحقّق من المستخدم، وهو إعداد أضعف."}],"answer":0},{"q":"<p>بيئة Compose تبدأ بشكل موثوق على حاسوبك، لكن في الـ CI تعمل اختبارات التكامل أحيانًا قبل أن يجهز الـ API. أي تغيير يحلّ ذلك دون تخمين؟</p>","options":[{"text":"أضف <code dir=\"ltr\">sleep 30</code> قبل الاختبارات.","why":"قصيرة جدًا على runner بطيء، ومهدرة على runner سريع."},{"text":"استخدم <code dir=\"ltr\">restart: always</code> على الـ API.","why":"إعادة التشغيل لا تخبر خطوة الاختبار متى يكون الـ API جاهزًا."},{"text":"أعطِ الـ API وقاعدة البيانات healthchecks، وشغّل البيئة بـ <code dir=\"ltr\">docker compose up -d --wait</code>.","why":"صحيح. ‏<code dir=\"ltr\">--wait</code> لا يعود إلا حين تكون الخدمات سليمة، ويفشل إن صارت إحداها غير سليمة."},{"text":"انشر منفذ قاعدة البيانات حتى تستطيع الاختبارات استطلاعه.","why":"كون قاعدة البيانات تعمل لا يثبت أن الـ API جاهز."}],"answer":2},{"q":"<p>ترث Deployment بلا readiness probe ومع إعدادات التحديث المتدرّج الافتراضية. يخرج إصدار فيه عنوان قاعدة بيانات معطوب. ماذا يحدث؟</p>","options":[{"text":"يتوقّف الـ rollout وتستمر الـ Pods القديمة في الخدمة.","why":"دون readiness probe تُعدّ الـ Pods الجديدة جاهزة حالما تعمل، فلا شيء يتوقّف."},{"text":"يكتشف Kubernetes العنوان المعطوب ويرفض الإصدار.","why":"لا يعرف Kubernetes شيئًا عن قيم إعدادات تطبيقك."},{"text":"يُستبدل Pod واحد فقط، بسبب <code dir=\"ltr\">maxSurge</code>.","why":"‏<code dir=\"ltr\">maxSurge</code> يحدّد وتيرة الـ rollout؛ ولا يوقفه."},{"text":"تُعلَّم الـ Pods الجديدة جاهزة حالما تبدأ الـ containers الخاصة بها، فيستبدل الـ rollout كل Pod يعمل بآخر يفشل.","why":"صحيح. الـ readiness هو بوّابة الـ rollouts. ودونه يأخذ الإصدار المعطوب كل الحركة."}],"answer":3},{"q":"<p>لدى notes-api ‏request للمعالج قدره 500m ولا حدّ للمعالج، وrequest وحدّ للذاكرة قدرهما 512Mi. تحت قفزة في الحركة يصل استهلاكه للمعالج إلى 1.5 نواة على node فيه سعة خاملة. ماذا يحدث؟</p>","options":[{"text":"يُقتل بسبب نفاد الذاكرة لتجاوزه الـ request.","why":"القتل بسبب نفاد الذاكرة يتعلّق بحدود الذاكرة، لا المعالج، والـ requests لا تُفرض أبدًا كسقوف."},{"text":"يستخدم المعالج الخامل، لأنه لا يوجد حدّ للمعالج يخنقه؛ والـ request الخاص به ما زال يضمن 500m عند التزاحم.","why":"صحيح. الـ requests تحجز؛ والـ limits وحدها تضع سقفًا. وهذا سبب ترك بعض الفرق لحدود المعالج."},{"text":"يُخنق إلى 500m.","why":"الخنق يحدث عند <em>حدّ</em> المعالج. أما الـ request فحجز أدنى، لا سقف."},{"text":"ينقله الـ scheduler إلى node أكبر.","why":"الـ scheduler لا ينقل الـ Pods العاملة أبدًا؛ الـ Pods الجديدة وحدها تحصل على مواضع جديدة."}],"answer":1},{"q":"<p>أي manifest صالح في Kubernetes الحالي؟</p>","options":[{"text":"‏<code dir=\"ltr\">apiVersion: extensions/v1beta1</code> مع <code dir=\"ltr\">kind: Deployment</code>","why":"تلك المجموعة وذلك الإصدار للـ Deployments أُزيلا منذ زمن بعيد."},{"text":"‏<code dir=\"ltr\">apiVersion: autoscaling/v1</code> مع HPA يستخدم <code dir=\"ltr\">metrics</code> و<code dir=\"ltr\">behavior</code>","why":"‏<code dir=\"ltr\">metrics</code> و<code dir=\"ltr\">behavior</code> حقلان من <code dir=\"ltr\">autoscaling/v2</code>."},{"text":"‏<code dir=\"ltr\">apiVersion: networking.k8s.io/v1beta1</code> مع <code dir=\"ltr\">kind: Ingress</code>","why":"الـ Ingress يُقدَّم بالإصدار <code dir=\"ltr\">networking.k8s.io/v1</code>؛ والإصدار التجريبي أُزيل."},{"text":"‏<code dir=\"ltr\">apiVersion: apps/v1</code> مع <code dir=\"ltr\">kind: Deployment</code>","why":"صحيح. الـ Deployments والـ ReplicaSets والـ StatefulSets تعيش في <code dir=\"ltr\">apps/v1</code>."}],"answer":3},{"q":"<p>أضاف notes-api 1.5.0 عمودًا، وكوده يكتب في العمودين القديم والجديد معًا. في 1.5.0 خلل، فتشغّل <code dir=\"ltr\">kubectl rollout undo</code>. لماذا هذا التراجع آمن؟</p>","options":[{"text":"لأن <code dir=\"ltr\">rollout undo</code> يستعيد لقطة من قاعدة البيانات.","why":"لا يمسّ Kubernetes قاعدة بياناتك أبدًا."},{"text":"لأن تغيير المخطّط كان إضافيًا، فما زال 1.4.0 يجد كل ما يتوقّعه.","why":"صحيح. تغييرات التوسعة ثم التقليص (expand-then-contract) تُبقي الإصدارات المتجاورة متوافقة، وهذا يجعل التراجع مملًّا بالمعنى الجيد."},{"text":"لأن Postgres يتجاهل الأعمدة المجهولة في جمل UPDATE.","why":"لا يتجاهل Postgres أي شيء؛ ببساطة لا يشير 1.4.0 إلى العمود الجديد أبدًا."},{"text":"لأن التراجع آمن دائمًا في Kubernetes.","why":"التراجع عن الكود لا يكون آمنًا إلا حين تكون البيانات والمخطّط متوافقة مع الكود الأقدم."}],"answer":1},{"q":"<p>الـ HPA الخاص بـ notes-api يستهدف استخدامًا للمعالج بنسبة 60% مع request للمعالج قدره 200m. أربعة Pods يبلغ متوسط كل منها 240m. كم نسخة تقريبًا سيريد الـ HPA؟</p>","options":[{"text":"4، لأن الـ HPA لا يوسّع إلا حين يتجاوز الاستهلاك الـ request.","why":"الهدف 60% من الـ request ‏(120m). و240m ضعف ذلك."},{"text":"5","why":"المطلوب = 4 × 240 / 120 = 8، لا 5."},{"text":"8","why":"صحيح. الاستهلاك 200% من الهدف البالغ 120m لكل Pod، فيضاعف الـ HPA النسخ (ضمن <code dir=\"ltr\">maxReplicas</code>)."},{"text":"2، لأنه يأخذ متوسط الـ request والاستهلاك.","why":"يوسّع الـ HPA حسب نسبة الاستهلاك إلى الهدف؛ ولا يأخذ متوسطهما."}],"answer":2},{"q":"<p>الـ controller ‏ingress-nginx الذي كان الـ cluster لديك يستخدمه تقاعد ولم يعد يتلقّى إصلاحات أمنية. ما الاتجاه الموصى به لإعدادات توجيه HTTP الجديدة؟</p>","options":[{"text":"اعرض كل خدمة بـ NodePort خاص بها.","why":"الـ NodePorts ليست نقطة دخول عامة؛ وستخسر إنهاء TLS والتوجيه."},{"text":"أبقِ على الـ controller المتقاعد؛ ستصونه نواة Kubernetes.","why":"التقاعد يعني لا إصدارات ولا إصلاحات أمنية."},{"text":"استبدل الـ Ingress بـ Service من نوع LoadBalancer لكل تطبيق.","why":"يصلح لخدمة واحدة؛ لكنه مكلف ولا يمكن التوجيه فيه حسب المضيف أو المسار مع خدمات كثيرة."},{"text":"‏Gateway API: ‏Gateway يملكه فريق المنصّة، وHTTPRoutes يملكها فريق كل تطبيق.","why":"صحيح. ‏Gateway API هو الخليفة الموصى به من المشروع، بموارد موجّهة حسب الأدوار وأدوات تنفيذ (implementations) كثيرة."}],"answer":3},{"q":"<p>‏Pod من notes-api يُظهر <code dir=\"ltr\">CrashLoopBackOff</code> و<code dir=\"ltr\">Last State: Terminated, Exit Code: 137</code>. والأمر <code dir=\"ltr\">kubectl logs --previous</code> يعرض سجلات طلبات عادية، ثم لا شيء. ما السبب المرجّح والحل؟</p>","options":[{"text":"تجاوز الـ container حدّ ذاكرته فقُتل بسبب نفاد الذاكرة؛ تحقّق في <code dir=\"ltr\">describe</code> من <code dir=\"ltr\">OOMKilled</code>، ثم ارفع الحدّ أو أصلح التسرّب وضع سقفًا للـ heap في V8.","why":"صحيح. الخروج بالرمز 137 دون مخرجات خطأ يشير إلى SIGKILL، والذاكرة هي السبب المعتاد في حلقة الانهيار."},{"text":"استثناء JavaScript أسقط العملية.","why":"الاستثناء غير الملتقَط يطبع stack trace ويخرج بالرمز 1، لا 137."},{"text":"لا يمكن سحب الـ image.","why":"فشل السحب لا يصل أبدًا إلى تشغيل التطبيق أو تسجيل الطلبات."},{"text":"فشل الـ readiness probe.","why":"إخفاقات الـ readiness لا تقتل الـ containers إطلاقًا."}],"answer":0},{"q":"<p>أي مجموعة تعطي notes-api عمليات نشر دون توقّف؟</p>","options":[{"text":"استراتيجية <code dir=\"ltr\">Recreate</code> مع liveness probe على <code dir=\"ltr\">/readyz</code>.","why":"‏<code dir=\"ltr\">Recreate</code> يوقف كل شيء أولًا، والـ liveness probe الذي يفحص الاعتماديات يضيف عواصف إعادة تشغيل."},{"text":"لا probes، و<code dir=\"ltr\">maxUnavailable: 25%</code>، وقيمة عالية لـ <code dir=\"ltr\">terminationGracePeriodSeconds</code>.","why":"دون readiness تأخذ الـ Pods الجديدة الحركة قبل أن تجهز، وتنخفض السعة أثناء الـ rollout."},{"text":"‏readiness probe، و<code dir=\"ltr\">maxUnavailable: 0</code>، وتوقّف <code dir=\"ltr\">preStop</code> قصير، ومعالج SIGTERM يفرّغ الطلبات الجارية.","why":"صحيح. الـ Pods الجديدة لا تأخذ الحركة إلا حين تجهز، والسعة لا تنخفض أبدًا، والـ Pods القديمة تغادر دون إسقاط الطلبات."},{"text":"‏<code dir=\"ltr\">replicas: 1</code> مع <code dir=\"ltr\">kubectl rollout restart</code> بعد كل تغيير.","why":"النسخة الواحدة تعني أن أي إعادة تشغيل توقّف للخدمة."}],"answer":2}],"glossary":[{"id":"container","term":"Container","definition":"عملية عادية تعزلها نواة Linux بالـ namespaces وتحدّ مواردها بالـ cgroups، وتعمل من نظام ملفات image مع layer خاصة قابلة للكتابة يمكن التخلّص منها.","lesson":"l-dk-1-1"},{"id":"image","term":"Image","definition":"لقطة لنظام ملفات مقسّمة إلى layers وللقراءة فقط، مع بيانات وصفية مثل الأمر الافتراضي والمستخدم. يمكن لـ image واحدة أن تكون قالبًا لأي عدد من الـ containers.","lesson":"l-dk-1-1"},{"id":"namespaces-cgroups","term":"Namespaces وcgroups","definition":"ميزتان في النواة تقوم عليهما الـ containers: الـ namespaces تتحكّم فيما تراه العملية (العمليات والشبكة والـ mounts)، والـ cgroups تحدّ ما تستهلكه (المعالج والذاكرة والإدخال والإخراج).","lesson":"l-dk-1-1"},{"id":"build-context","term":"Build context","definition":"مجموعة الملفات التي يرسلها <code dir=\"ltr\">docker build</code> إلى أداة البناء، وهي عادةً المجلد الذي تمرّره كآخر معامل، مطروحًا منه كل ما يطابق <code dir=\"ltr\">.dockerignore</code>. ولا تقرأ <code dir=\"ltr\">COPY</code> إلا منه.","lesson":"l-dk-1-2"},{"id":"exec-form","term":"صيغة exec ‏(Exec form)","definition":"كتابة <code dir=\"ltr\">CMD</code> أو <code dir=\"ltr\">ENTRYPOINT</code> كمصفوفة JSON، مثل <code dir=\"ltr\">[&quot;node&quot;, &quot;dist/server.js&quot;]</code>، حتى يعمل البرنامج كـ PID 1 دون shell ويستقبل إشارات الإيقاف مباشرة.","lesson":"l-dk-1-2"},{"id":"layer-cache","term":"Build cache","definition":"نتائج مخزّنة لخطوات بناء سابقة. تُعاد الخطوة من الـ cache حين لا تتغيّر تعليمتها ومُدخلاتها؛ أما أول خطوة متغيّرة وكل خطوة بعدها فيُعاد بناؤها.","lesson":"l-dk-1-3"},{"id":"sigterm","term":"SIGTERM","definition":"إشارة الإيقاف المهذّبة التي يرسلها Docker وKubernetes إلى PID 1 قبل SIGKILL القسرية. على التطبيقات معالجتها بإنهاء العمل الجاري والخروج بنظافة.","lesson":"l-dk-1-4"},{"id":"multi-stage-build","term":"Multi-stage build","definition":"‏Dockerfile فيه عدّة مراحل <code dir=\"ltr\">FROM</code> لا تُشحن منها إلا الأخيرة، فتبقى المترجِمات واعتماديات التطوير والمصادر في المراحل السابقة وخارج image الإنتاج.","lesson":"l-dk-2-1"},{"id":"dockerignore","term":".dockerignore","definition":"ملف يسرد أنماطًا تُستبعد من الـ build context، مثل <code dir=\"ltr\">node_modules</code> و<code dir=\"ltr\">.git</code> وملفات <code dir=\"ltr\">.env</code>، فيسرّع البناء ويُبقي الأسرار خارج الـ images.","lesson":"l-dk-2-1"},{"id":"distroless","term":"Distroless image","definition":"‏base image مصغّرة لا تحتوي إلا على بيئة تشغيل اللغة ومكتباتها، دون shell أو مدير حزم، فتقلّص مساحة الهجوم وتقرير الثغرات.","lesson":"l-dk-2-2"},{"id":"digest","term":"البصمة (Image digest)","definition":"‏hash من نوع SHA-256 يحدّد المحتوى الدقيق للـ image. على عكس الـ tag، لا يمكن أن يشير أبدًا إلى بايتات مختلفة، فالتثبيت بالـ digest يضمن أن كل node يشغّل الـ image نفسها.","lesson":"l-dk-2-3"},{"id":"secret-mount","term":"Secret mount","definition":"ميزة في BuildKit، هي <code dir=\"ltr\">RUN --mount=type=secret</code>، تتيح بيانات اعتماد لخطوة بناء واحدة كملف أو كمتغيّر بيئة دون كتابتها في أي layer من الـ image.","lesson":"l-dk-2-4"},{"id":"compose-service","term":"خدمة Compose ‏(Compose service)","definition":"مكوّن واحد من تطبيق Compose، مثل <code dir=\"ltr\">api</code> أو <code dir=\"ltr\">db</code>، معرَّف في <code dir=\"ltr\">compose.yaml</code>، وتصل إليه الخدمات الأخرى على شبكة المشروع باسم الخدمة.","lesson":"l-dk-3-1"},{"id":"named-volume","term":"Named volume","definition":"تخزين يديره Docker ويوجد باستقلال عن الـ containers، يُستخدم لبيانات مثل ملفات قاعدة البيانات التي يجب أن تبقى بعد حذف الـ container. والأمر <code dir=\"ltr\">docker compose down -v</code> يحذفه.","lesson":"l-dk-3-2"},{"id":"healthcheck","term":"فحص الصحة (Healthcheck)","definition":"أمر يشغّله Docker داخل الـ container وفق جدول زمني ليقرّر هل هو سليم. ويستطيع Compose جعل الخدمات الأخرى تنتظره بالشرط <code dir=\"ltr\">condition: service_healthy</code>.","lesson":"l-dk-3-3"},{"id":"compose-watch","term":"Compose watch","definition":"وضع للتطوير يبدأ بـ <code dir=\"ltr\">docker compose up --watch</code>، يزامن الملفات المعدّلة إلى الـ containers العاملة أو يعيد بناء الـ image حين تتغيّر ملفات مثل الـ lockfile.","lesson":"l-dk-3-4"},{"id":"control-loop","term":"حلقة التحكّم (Control loop)","definition":"النمط الذي تقوم عليه الـ controllers في Kubernetes: مراقبة الحالة الفعلية باستمرار، ومقارنتها بالحالة المرغوبة المخزّنة في الـ API، والتصرّف لسدّ الفرق.","lesson":"l-dk-4-1"},{"id":"pod","term":"Pod","definition":"أصغر وحدة يُجدوِلها Kubernetes: ‏container واحد أو أكثر تتشارك هوية شبكية وvolumes. الـ Pods يمكن التخلّص منها، فهي تُستبدل ولا تُصلَح.","lesson":"l-dk-4-2"},{"id":"deployment","term":"Deployment","definition":"كائن في Kubernetes يُبقي مجموعة من الـ Pods المتطابقة قيد التشغيل عبر الـ ReplicaSets، ويستبدلها تدريجيًا حين يتغيّر قالب الـ Pod.","lesson":"l-dk-4-2"},{"id":"label-selector","term":"Label selector","definition":"استعلام على labels من أزواج مفتاح-قيمة تستخدمه كائنات Kubernetes ليجد بعضها بعضًا، مثل Service يختار الـ Pods التي يرسل إليها الحركة.","lesson":"l-dk-4-2"},{"id":"service","term":"Service","definition":"عنوان IP افتراضي واسم DNS ثابتان أمام مجموعة متغيّرة من الـ Pods الجاهزة يختارها label selector. من أنواعه ClusterIP وNodePort وLoadBalancer.","lesson":"l-dk-4-3"},{"id":"configmap-secret","term":"ConfigMap وSecret","definition":"كائنان يحفظان إعدادات الـ Pods، ويُحقنان كمتغيّرات بيئة أو كملفات. الـ Secrets لبيانات الاعتماد لكنها مرمّزة بـ base64 فقط، لذا يهمّ التحكّم في الوصول إليها.","lesson":"l-dk-4-4"},{"id":"namespace","term":"Namespace","definition":"نطاق في Kubernetes لأسماء الكائنات والصلاحيات والحصص، يكون عادةً واحدًا لكل تطبيق أو فريق. لا يحجب حركة الشبكة دون NetworkPolicy.","lesson":"l-dk-4-4"},{"id":"readiness-probe","term":"Readiness probe","definition":"فحص دوري يقرّر هل يجب أن يستقبل الـ Pod حركة. فشله يزيل الـ Pod من endpoints الـ Service دون إعادة تشغيله، وهو بوّابة للتحديثات المتدرّجة.","lesson":"l-dk-5-1"},{"id":"liveness-probe","term":"Liveness probe","definition":"فحص دوري يقرّر هل الـ container عالق. فشله يعيد تشغيل الـ container، لذا يجب ألا يعتمد على قواعد البيانات أو الخدمات الأخرى.","lesson":"l-dk-5-1"},{"id":"requests-limits","term":"Requests وlimits","definition":"إعدادات موارد لكل container: الـ requests هي ما يحجزه الـ scheduler، والـ limits سقوف مفروضة. تجاوز حدّ المعالج يسبّب الخنق (throttling)؛ وتجاوز حدّ الذاكرة يسبّب القتل بسبب نفاد الذاكرة (OOM).","lesson":"l-dk-5-2"},{"id":"hpa","term":"HorizontalPodAutoscaler","definition":"‏controller ‏(<code dir=\"ltr\">autoscaling/v2</code>) يعدّل عدد نسخ حمل العمل ليُبقي مقياسًا ما، مثل متوسط استخدام المعالج نسبةً إلى الـ requests، عند هدف محدّد أو حوله.","lesson":"l-dk-5-2"},{"id":"rolling-update","term":"التحديث المتدرّج (Rolling update)","definition":"الاستراتيجية الافتراضية للـ Deployment: يتوسّع ReplicaSet جديد بينما يتقلّص القديم، بوتيرة يضبطها <code dir=\"ltr\">maxSurge</code> و<code dir=\"ltr\">maxUnavailable</code>، ولا يتقدّم إلا بقدر ما تسمح به الـ readiness.","lesson":"l-dk-5-3"},{"id":"helm-chart","term":"Helm chart","definition":"حزمة من manifests ‏Kubernetes على شكل قوالب مع قيم افتراضية. الأمر <code dir=\"ltr\">helm upgrade --install</code> ينشرها كـ release revision مرقّمة يستطيع <code dir=\"ltr\">helm rollback</code> العودة إليها.","lesson":"l-dk-5-3"},{"id":"gateway-api","term":"Gateway API","definition":"خليفة Ingress في Kubernetes لتوجيه الحركة الخارجية، مقسّم إلى موارد GatewayClass وGateway وHTTPRoute حتى يملك كلٌّ من فريق المنصّة وفريق التطبيق جزأه.","lesson":"l-dk-5-4"}]}