{"id":"l-dk-1-4","courseId":"docker-kubernetes","lang":"ar","kind":"lesson","summary":"شغّل notes-api في الخلفية مع المنافذ ومتغيّرات البيئة وحدود الموارد، واقرأ سجلاته، وافتح shell داخله، واجعله يتوقّف توقّفًا نظيفًا عند استقبال SIGTERM.","html":"<p>بناء الـ image نصف العمل؛ والنصف الآخر هو تشغيلها بالطريقة التي سيشغّلها بها الإنتاج. في العام الماضي، كانت إحدى خدمات Skylane تُسقط بضع مئات من الطلبات مع كل deploy. لم يكن في الكود أي خلل؛ كل ما في الأمر أن العملية كانت تتجاهل إشارة «من فضلك توقّف» المهذّبة، فتُقتل بعد عشر ثوانٍ في منتصف الطلبات. يتناول هذا الدرس جانب التشغيل، ويختم بمسألة الإيقاف تلك.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">شغّله في الخلفية</h2>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"0\"><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\">docker</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> run</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -d</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --name</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  -p</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 8080:3000</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  -e</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> PORT=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3000</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  -e</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> DATABASE_URL=postgres://notes:notes@host.docker.internal:5432/notes</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  --memory</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 512m</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --cpus</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 1</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">  notes-api:dev</span></span></code></pre></div>\n<ul>\n<li><code dir=\"ltr\">-d</code> يشغّل الـ container منفصلًا (detached)، فيعمل في الخلفية ويطبع Docker معرّفه.</li>\n<li><code dir=\"ltr\">--name notes</code> يعطيه اسمًا تكتبه بدلًا من المعرّف.</li>\n<li><code dir=\"ltr\">-p 8080:3000</code> يربط المنفذ 8080 على <strong>المضيف</strong> بالمنفذ 3000 في <strong>الـ container</strong>. اقرأه «خارج:داخل». ويمكن لاثنين من الـ containers أن يستمعا كلاهما على المنفذ 3000 في الداخل، ما داما منشورَين على منفذين مختلفين في المضيف.</li>\n<li><code dir=\"ltr\">-e</code> يضبط متغيّرات البيئة. استخدم <code dir=\"ltr\">--env-file .env.local</code> حين تكثر.</li>\n<li><code dir=\"ltr\">--memory</code> و<code dir=\"ltr\">--cpus</code> يضبطان حدود الـ cgroups. إذا احتاجت العملية إلى ذاكرة أكثر من ذلك (إضافةً إلى ما يسمح به Docker من swap على المضيفات التي فيها swap)، ينهيها الـ OOM killer في النواة.</li>\n</ul>\n<p>الاسم <code dir=\"ltr\">host.docker.internal</code> يُحلّ إلى عنوان جهازك من داخل الـ container على Docker Desktop؛ وعلى Linux أضف <code dir=\"ltr\">--add-host=host.docker.internal:host-gateway</code>. ستستبدل هذا بشبكة Compose حقيقية في القسم الثالث.</p>\n<aside class=\"callout callout-why\" 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\"/><circle cx=\"12\" cy=\"12\" r=\"6\"/><circle cx=\"12\" cy=\"12\" r=\"2\"/></svg><span><span class=\"callout-kind\">لماذا يهمّ هذا</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>image واحدة لكل البيئات</span></p>\n<p>الإعدادات تُمرَّر وقت التشغيل، ولا تُدمج داخل الـ image أبدًا. الـ image التي اختبرتها في بيئة staging هي البايتات نفسها التي تشغّلها في الإنتاج، مع قيم <code dir=\"ltr\">-e</code> مختلفة. إذا أعدت البناء لكل بيئة، فأنت تشحن شيئًا لم تختبره قط.</p>\n</aside>\n<h2 id=\"sec-2\" tabindex=\"-1\">انظر إلى ما يعمل وما يقوله</h2>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"1\"><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\">docker</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> ps</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">                      # running containers</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">docker</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> logs</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -f</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">           # follow stdout/stderr</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">docker</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> logs</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --since</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 10m</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">  # recent output only</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">docker</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> inspect</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --format</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> '{{.State.Status}} {{.NetworkSettings.Ports}}'</span></span></code></pre></div>\n<p>الأمر <code dir=\"ltr\">docker logs</code> يعرض كل ما كتبته العملية إلى stdout وstderr. هذا هو العقد مع الـ containers: <strong>اكتب السجلات إلى التدفّقات القياسية</strong>، سطرًا لكل حدث، ودع المنصّة تجمعها. الكتابة إلى ملف سجلات داخل الـ container تخفي السجلات عن كل الأدوات وتضيّعها حين يُحذف الـ container.</p>\n<p>ولإلقاء نظرة في الداخل:</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\">docker</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> exec</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -it</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> sh</span></span></code></pre></div>\n<p>الأمر <code dir=\"ltr\">exec</code> يبدأ عملية إضافية في الـ container الذي يعمل؛ و<code dir=\"ltr\">-it</code> يعطيك terminal تفاعليًا. هذا للنظر، لا للإصلاح. في القسم الثاني ستنتقل إلى base image لا تحتوي على shell أصلًا، وستتعلّم طرقًا أخرى للدخول.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">الإيقاف: SIGTERM ثم SIGKILL</h2>\n<p>الأمر <code dir=\"ltr\">docker stop notes</code> يرسل <code dir=\"ltr\">SIGTERM</code> إلى PID 1 وينتظر 10 ثوانٍ (غيّرها بـ <code dir=\"ltr\">-t</code>). إذا بقيت العملية حيّة، يرسل Docker إشارة <code dir=\"ltr\">SIGKILL</code>، التي لا يمكن التقاطها. رموز الخروج تخبرك بما حدث:</p>\n<div class=\"table-wrap\" role=\"region\" aria-label=\"جدول قابل للتمرير\" tabindex=\"0\"><table><thead>\n<tr>\n<th>رمز الخروج</th>\n<th>المعنى</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td>خرج التطبيق بنظافة من تلقاء نفسه</td>\n</tr>\n<tr>\n<td>143</td>\n<td>‏128 + 15: ماتت العملية بسبب SIGTERM دون أن تعالجها</td>\n</tr>\n<tr>\n<td>137</td>\n<td>‏128 + 9: قُتلت بـ SIGKILL (انتهاء مهلة الإيقاف أو نفاد الذاكرة)</td>\n</tr>\n</tbody>\n</table></div><p>يعامل Linux العملية ذات المعرّف PID 1 معاملة خاصة: الإشارة التي لم تسجّل لها العملية معالجًا تُتجاهل بدلًا من أن تقتلها. ولا يسجّل Node.js معالجًا لـ <code dir=\"ltr\">SIGTERM</code> افتراضيًا، لذا فإن <code dir=\"ltr\">node dist/server.js</code> في موقع PID 1 يتجاهل <code dir=\"ltr\">docker stop</code> ويُقتل بعد 10 ثوانٍ. وتموت معه الطلبات الجارية.</p>\n<p>عالِج الإشارة: توقّف عن قبول الاتصالات، وأنهِ ما هو جارٍ، وأغلق مجمّع اتصالات قاعدة البيانات، ثم اخرج.</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">src/server.ts</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\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> server</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> app.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">listen</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(port, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'0.0.0.0'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, () </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">`notes-api listening on ${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">port</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}`</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">));</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">process.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">on</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'SIGTERM'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, () </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'SIGTERM received, draining'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  server.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">close</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">async</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> () </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    await</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pool.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">end</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">();</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    process.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">exit</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  });</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">  // Safety net: don't hang forever on a stuck keep-alive connection</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  setTimeout</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(() </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> process.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">exit</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">), </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">8000</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">).</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">unref</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">();</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">});</span></span></code></pre></div>\n<p>صار <code dir=\"ltr\">docker stop</code> ينتهي الآن في أقل بكثير من ثانية، ويخرج الـ container بالرمز 0. وهذا المعالج أهمّ في Kubernetes، الذي يرسل الإشارة نفسها مع كل deploy، وكل تقليص لعدد النسخ (scale-down)، وكل تفريغ (drain) لأحد الـ nodes.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">خطأ شائع</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>الاعتماد على ‎–init وإهمال المعالج</span></p>\n<p>الأمر <code dir=\"ltr\">docker run --init</code> يضع عملية init صغيرة في موقع PID 1 تمرّر الإشارات وتنظّف العمليات الميتة (zombies). هذا مفيد، لكن تمرير SIGTERM إلى تطبيق بلا معالج لا يفعل سوى أن يجعله يموت فورًا بدلًا من أن يموت بعد 10 ثوانٍ. وتبقى الطلبات تسقط. المعالج في كودك هو ما يجعل الإيقاف آمنًا.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">سياسات إعادة التشغيل ونموّ السجلات</h2>\n<p>على خادم واحد دون منسّق، يطلب <code dir=\"ltr\">--restart unless-stopped</code> من Docker إعادة الـ container بعد انهياره أو بعد إعادة تشغيل المضيف، إلا إذا أوقفته أنت بنفسك. لـ Kubernetes وCompose إعدادات إعادة تشغيل خاصة بهما، لذا نادرًا ما ستضبط هذا على جهازك، لكنه أول ما تفحصه على تلك الآلة الافتراضية القديمة التي لا يريد أحد لمسها.</p>\n<p>على مضيفات Linux طويلة العمر، راقب حجم السجلات أيضًا. برنامج تشغيل السجلات (logging driver) الافتراضي <code dir=\"ltr\">json-file</code> يحتفظ بكل ما يطبعه الـ container ما لم تضبط <code dir=\"ltr\">max-size</code> و<code dir=\"ltr\">max-file</code> في إعدادات الـ daemon. وقد يملأ container ثرثار قرصًا كاملًا في عطلة نهاية أسبوع واحدة؛ وقد استُدعيت في مناوبة لهذا السبب بالضبط.</p>\n<h2 id=\"sec-5\" tabindex=\"-1\">التنظيف</h2>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"4\"><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\">docker</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> rm</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -f</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">          # stop and remove</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">docker</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> container</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> prune</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">      # remove all stopped containers</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">docker</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> image</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> prune</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">          # remove dangling images</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">docker</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> system</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> df</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">            # what's using disk</span></span></code></pre></div>\n<p>تستطيع الآن بناء container وتشغيله ومراقبته وإيقافه كما يجب. القسم الثاني يحوّل هذه الـ image إلى image ترتاح لشحنها: أصغر، وبمستخدم غير root، وذات إصدار واضح، ومفحوصة.</p>\n","toc":[{"id":"sec-1","text":"شغّله في الخلفية","level":2},{"id":"sec-2","text":"انظر إلى ما يعمل وما يقوله","level":2},{"id":"sec-3","text":"الإيقاف: SIGTERM ثم SIGKILL","level":2},{"id":"sec-4","text":"سياسات إعادة التشغيل ونموّ السجلات","level":2},{"id":"sec-5","text":"التنظيف","level":2}],"takeaways":["الخيار <code dir=\"ltr\">-p 8080:3000</code> يربط المنفذ 8080 على المضيف بالمنفذ 3000 في الـ container؛ الجهة اليسرى جهازك، والجهة اليمنى التطبيق.","مرّر الإعدادات وقت التشغيل بـ <code dir=\"ltr\">-e</code> أو <code dir=\"ltr\">--env-file</code> حتى تخدم image واحدة كل البيئات.","على الـ containers أن تكتب سجلاتها إلى stdout وstderr؛ فمن هناك يجمعها <code dir=\"ltr\">docker logs</code> وكل منسّق (orchestrator).","الأمر <code dir=\"ltr\">docker stop</code> يرسل SIGTERM، وينتظر 10 ثوانٍ، ثم يرسل SIGKILL؛ ورمز الخروج 137 يعني أن العملية قُتلت.","عملية Node.js في موقع PID 1 تحتاج إلى معالج SIGTERM خاص بها، وإلا فستتجاهل الإشارة وتُقتل في منتصف الطلبات."],"further":[{"title":"docker container run","url":"https://docs.docker.com/reference/cli/docker/container/run/"},{"title":"Publishing and exposing ports","url":"https://docs.docker.com/get-started/docker-concepts/running-containers/publishing-ports/"},{"title":"Process signal events in Node.js","url":"https://nodejs.org/api/process.html#signal-events"}],"quiz":[{"q":"<p>تشغّل <code dir=\"ltr\">docker run -d -p 8080:3000 notes-api:dev</code>. أي عنوان يصل إلى الـ API من جهازك؟</p>","options":[{"text":"<code dir=\"ltr\">http://localhost:8080</code>","why":"صحيح. الجهة اليسرى من <code dir=\"ltr\">-p</code> هي منفذ المضيف؛ ويحوّله Docker إلى المنفذ 3000 داخل الـ container."},{"text":"<code dir=\"ltr\">http://localhost:3000</code>","why":"المنفذ 3000 هو منفذ الـ container. لا شيء على المضيف يستمع عليه ما لم تنشره بوصفه منفذ المضيف."},{"text":"<code dir=\"ltr\">http://notes-api:8080</code>","why":"أسماء الـ containers تُحلّ داخل شبكات Docker، لا في DNS جهازك، و8080 ليس منفذ الـ container أصلًا."},{"text":"كلا المنفذين يعمل، لأن Docker يربط الاتجاهين.","why":"ربط المنافذ قاعدة واحدة من منفذ واحد على المضيف إلى منفذ واحد في الـ container."}],"answer":0},{"q":"<p>الأمر <code dir=\"ltr\">docker stop notes</code> يستغرق عشر ثوانٍ بالضبط، و<code dir=\"ltr\">docker ps -a</code> يعرض <code dir=\"ltr\">Exited (137)</code>. ماذا حدث؟</p>","options":[{"text":"انهار التطبيق باستثناء غير معالَج أثناء الإيقاف.","why":"الاستثناء غير المعالَج يخرج عادةً بالرمز 1. أما 137 فهي 128 + 9، أي رقم إشارة SIGKILL."},{"text":"نفدت ذاكرة Docker أثناء إيقاف الـ container.","why":"قد يأتي 137 من الـ OOM killer، لكن التأخير الدقيق لعشر ثوانٍ في <code dir=\"ltr\">docker stop</code> يشير إلى مهلة الإيقاف."},{"text":"تجاهل التطبيق SIGTERM، فأرسل Docker إشارة SIGKILL بعد مهلة السماح البالغة 10 ثوانٍ.","why":"صحيح. 137 = 128 + 9 ‏(SIGKILL). عملية Node في موقع PID 1 دون معالج SIGTERM تتجاهل الإشارة وتنتظر أن تُقتل."},{"text":"خرج الـ container خروجًا نظيفًا؛ و137 هو رمز الإيقاف العادي في Docker.","why":"الخروج النظيف هو 0، أو 143 إذا ماتت العملية بسبب SIGTERM غير معالَجة. أما 137 فيعني أنها قُتلت."}],"answer":2},{"q":"<p>أين يجب أن يكتب notes-api سجلات الطلبات داخل الـ container؟</p>","options":[{"text":"في <code dir=\"ltr\">/var/log/notes-api.log</code>، حتى تبقى بعد إعادة التشغيل.","why":"لن تبقى. الـ layer القابلة للكتابة تُحذف مع الـ container. ولا أحد يجمع الملفات من داخل الـ containers افتراضيًا."},{"text":"في stdout وstderr، حيث يجمعها <code dir=\"ltr\">docker logs</code> وKubernetes.","why":"صحيح. الـ runtime يلتقط التدفّقات القياسية (stdout وstderr)، وأدوات شحن السجلات تقرؤها من هناك."},{"text":"في ملف داخل volume، تدوّره مهمة cron داخل الـ container.","why":"هذه عملية ثانية ووظيفة ثانية داخل الـ container. اكتب إلى stdout ودع المنصّة تتولّى التخزين."},{"text":"مباشرة من التطبيق إلى خدمة السجلات عبر HTTP.","why":"هذا يربط التطبيق بمزوّد واحد ويضيّع السجلات عند أي انقطاع عابر في الشبكة. الـ stdout هو العقد الذي تدعمه كل منصّة."}],"answer":1},{"q":"<p>يريد زميلك أن يستخدم <code dir=\"ltr\">docker exec</code> لإصلاح خطأ إملائي في ملف إعدادات داخل container الإنتاج الذي يعمل الآن. ما المشكلة؟</p>","options":[{"text":"الأمر <code dir=\"ltr\">docker exec</code> يعمل فقط على الـ containers المتوقّفة.","why":"العكس تمامًا. ‏<code dir=\"ltr\">exec</code> يشغّل عملية جديدة داخل container يعمل."},{"text":"الملفات داخل الـ containers للقراءة فقط دائمًا.","why":"الـ layer القابلة للكتابة تقبل التغييرات، ما لم تضبط <code dir=\"ltr\">--read-only</code>. سينجح التغيير، لفترة قصيرة."},{"text":"الأمر <code dir=\"ltr\">docker exec</code> يعيد تشغيل الـ container، فيسبّب توقّفًا للخدمة.","why":"الأمر <code dir=\"ltr\">exec</code> يبدأ عملية إضافية؛ ولا يعيد تشغيل أي شيء."},{"text":"التغيير يعيش في الـ layer القابلة للكتابة لذلك الـ container، ويختفي مع الـ deploy التالي أو إعادة التشغيل.","why":"صحيح. أصلح الـ image أو الإعدادات الممرَّرة إلى الـ container، ثم أعِد النشر. التعديلات اليدوية لا يراها أحد غيرك."}],"answer":3}],"exercise":{"kind":"fill","language":"bash","template":"# Start notes-api in the background, reachable at localhost:8080\ndocker run {{0}} --name notes -p {{1}}:3000 notes-api:dev\n\n# Follow its output\ndocker {{2}} -f notes\n\n# Open a shell inside the running container\ndocker {{3}} -it notes sh\n\n# Stop it, allowing 30 seconds to drain\ndocker stop {{4}} 30 notes","blanks":[{"answers":["-d","--detach"]},{"answers":["8080"]},{"answers":["logs","container logs"]},{"answers":["exec","container exec"]},{"answers":["-t","--time","--timeout"]}],"title":"أوامر الـ containers اليومية","prompt":"<p>املأ الفراغات لتشغيل notes-api في الخلفية على المنفذ <strong>8080</strong> في المضيف، ومتابعة سجلاته، وفتح shell داخله، وإيقافه مع مهلة سماح مدّتها 30 ثانية.</p>\n","hints":["خيار واحد يشغّل الـ container منفصلًا (detached)؛ ومنفذ المضيف هو الجهة اليسرى من <code dir=\"ltr\">-p</code>.","الأوامر الفرعية هي التي تستخدمها لقراءة المخرجات ولتشغيل عملية إضافية داخل container يعمل.","يأخذ <code dir=\"ltr\">docker stop</code> خيارًا لعدد الثواني التي ينتظرها قبل SIGKILL."],"explanation":"<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"0\"><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\">docker</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> run</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -d</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --name</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -p</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 8080:3000</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes-api:dev</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">docker</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> logs</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -f</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">docker</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> exec</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -it</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> sh</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">docker</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> stop</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -t</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 30</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes</span></span></code></pre></div>\n<p>الخيار <code dir=\"ltr\">-d</code> يشغّل الـ container منفصلًا عن الطرفية. وفي <code dir=\"ltr\">-p 8080:3000</code> تأتي جهة المضيف أولًا. والأمر <code dir=\"ltr\">logs -f</code> يتابع stdout وstderr. و<code dir=\"ltr\">exec -it</code> يشغّل عملية تفاعلية داخل الـ container الذي يعمل. أما <code dir=\"ltr\">stop -t 30</code> (صيغته الطويلة <code dir=\"ltr\">--timeout</code>) فينتظر 30 ثانية بعد SIGTERM قبل إرسال SIGKILL، وهذا يعطي التطبيق الذي يفرّغ طلباته وقتًا لإنهاء ما هو جارٍ.</p>\n","checkLabels":{}},"words":702,"readMinutes":4}