{"id":"l-dk-1-1","courseId":"docker-kubernetes","lang":"ar","kind":"intro","summary":"اشرح ما هو الـ container فعلًا (عملية Linux عادية مع namespaces وcgroups ونظام ملفات قادم من image)، وكيف ترتبط الـ images بالـ containers.","html":"<p>في الساعة 2:40 فجرًا ذات ليلة، بدأت إحدى خدمات Skylane تفشل في فحوص الصحة على كل الـ nodes دفعة واحدة، مع أن الكود لم يتغيّر. تبيّن أن مكتبة أساسية على خادم البناء رُقّيت بصمت، فلم يعد البرنامج الذي شحنّاه مطابقًا للبرنامج الذي اختبرناه. وُجدت الـ containers (الحاويات) لتضع حدًّا لمثل هذه الليالي: تشحن البرنامج <em>ومعه</em> كل ما يحتاج إليه في حزمة واحدة، فيكون ما اختبرته هو نفسه ما يعمل.</p>\n<p>أنا Amara Diallo. أتولّى المناوبة (on-call) على الـ clusters في Skylane، وكل ما في هذه الدورة إمّا شيء كسرته بنفسي أو شيء رأيته ينكسر أمامي. سنعمل على خدمة واحدة من البداية إلى النهاية.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">المشروع: notes-api</h2>\n<p>notes-api واجهة HTTP API صغيرة لإدارة الملاحظات، مكتوب بـ TypeScript ويعمل على Node.js 24. يخزّن بياناته في Postgres ويعرض أربعة مسارات: <code dir=\"ltr\">GET /notes</code> و<code dir=\"ltr\">POST /notes</code> و<code dir=\"ltr\">GET /healthz</code> (هل العملية حيّة؟) و<code dir=\"ltr\">GET /readyz</code> (هل تصل إلى قاعدة البيانات؟). يستمع على المنفذ (port) 3000، ويقرأ إعداداته من متغيّرات البيئة مثل <code dir=\"ltr\">DATABASE_URL</code>.</p>\n<p>بنهاية الدورة ستكون قد بنيت الـ image الخاصة به، وشغّلته مع Postgres على جهازك، ونشرته على Kubernetes مع فحوص الصحة والتوسّع التلقائي وخطة للتراجع.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">الـ container عملية</h2>\n<p>لا يوجد داخل نواة Linux كائن اسمه «container». حين تشغّل container، يبدأ الـ runtime عملية عادية ويغلّفها بثلاثة أشياء:</p>\n<ul>\n<li><strong>الـ Namespaces</strong> تغيّر ما تستطيع العملية <em>رؤيته</em>: شجرة عمليات خاصة بها (تطبيقك هو PID 1)، وواجهات شبكة خاصة، واسم مضيف خاص، ورؤية خاصة لأنظمة الملفات المركّبة.</li>\n<li><strong>مجموعات التحكّم (cgroups)</strong> تحدّ ما تستطيع <em>استهلاكه</em>: وقت المعالج، والذاكرة، والإدخال والإخراج.</li>\n<li><strong>الـ image (صورة الحاوية)</strong> تقدّم نظام الملفات الجذري: ملف Node.js التنفيذي، وكودك المترجَم، و<code dir=\"ltr\">node_modules</code>، وبعض مكتبات النظام.</li>\n</ul>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 680 300\" role=\"img\" aria-labelledby=\"l-dk-1-1-t1\"><defs><marker id=\"arrow-l-dk-1-1-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-1-1-t1\">إلى اليسار: ثلاث آلات افتراضية، لكل منها نواة ضيف خاصة فوق الـ hypervisor. إلى اليمين: ثلاثة containers، كل منها عملية معزولة، تتشارك نواة المضيف مباشرة.</title>\n  <text class=\"d-label-strong\" x=\"170\" y=\"24\" text-anchor=\"middle\">آلات افتراضية</text>\n  <text class=\"d-label-strong\" x=\"510\" y=\"24\" text-anchor=\"middle\">Containers</text>\n  <rect class=\"d-box-accent\" x=\"20\" y=\"40\" width=\"92\" height=\"56\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"66\" y=\"73\" text-anchor=\"middle\">تطبيق</text>\n  <rect class=\"d-box-accent\" x=\"124\" y=\"40\" width=\"92\" height=\"56\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"170\" y=\"73\" text-anchor=\"middle\">تطبيق</text>\n  <rect class=\"d-box-accent\" x=\"228\" y=\"40\" width=\"92\" height=\"56\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"274\" y=\"73\" text-anchor=\"middle\">تطبيق</text>\n  <rect class=\"d-box-warn\" x=\"20\" y=\"104\" width=\"92\" height=\"44\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"66\" y=\"131\" text-anchor=\"middle\">نظام ضيف</text>\n  <rect class=\"d-box-warn\" x=\"124\" y=\"104\" width=\"92\" height=\"44\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"170\" y=\"131\" text-anchor=\"middle\">نظام ضيف</text>\n  <rect class=\"d-box-warn\" x=\"228\" y=\"104\" width=\"92\" height=\"44\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"274\" y=\"131\" text-anchor=\"middle\">نظام ضيف</text>\n  <rect class=\"d-box\" x=\"20\" y=\"158\" width=\"300\" height=\"44\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"170\" y=\"185\" text-anchor=\"middle\">hypervisor</text>\n  <rect class=\"d-box\" x=\"20\" y=\"212\" width=\"300\" height=\"44\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"170\" y=\"239\" text-anchor=\"middle\">نواة المضيف + العتاد</text>\n  <rect class=\"d-box-primary\" x=\"360\" y=\"40\" width=\"92\" height=\"108\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"406\" y=\"88\" text-anchor=\"middle\">عملية</text>\n  <text class=\"d-label-muted\" x=\"406\" y=\"108\" text-anchor=\"middle\">+ image</text>\n  <rect class=\"d-box-primary\" x=\"464\" y=\"40\" width=\"92\" height=\"108\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"510\" y=\"88\" text-anchor=\"middle\">عملية</text>\n  <text class=\"d-label-muted\" x=\"510\" y=\"108\" text-anchor=\"middle\">+ image</text>\n  <rect class=\"d-box-primary\" x=\"568\" y=\"40\" width=\"92\" height=\"108\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"614\" y=\"88\" text-anchor=\"middle\">عملية</text>\n  <text class=\"d-label-muted\" x=\"614\" y=\"108\" text-anchor=\"middle\">+ image</text>\n  <rect class=\"d-box-success\" x=\"360\" y=\"158\" width=\"300\" height=\"44\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"510\" y=\"185\" text-anchor=\"middle\">namespaces + cgroups</text>\n  <rect class=\"d-box\" x=\"360\" y=\"212\" width=\"300\" height=\"44\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"510\" y=\"239\" text-anchor=\"middle\">نواة المضيف + العتاد</text>\n  <text class=\"d-label-muted\" x=\"170\" y=\"286\" text-anchor=\"middle\">ثوانٍ للإقلاع، عزل قوي</text>\n  <text class=\"d-label-muted\" x=\"510\" y=\"286\" text-anchor=\"middle\">أجزاء من الثانية للبدء، نواة مشتركة</text>\n</svg><figcaption>الـ Containers تتشارك نواة واحدة، وكل آلة افتراضية تأتي بنواتها</figcaption></figure>\n<p>ولأنه لا يوجد نظام تشغيل ضيف يحتاج إلى الإقلاع، يبدأ الـ container بسرعة البرنامج الذي بداخله. والثمن أن كل الـ containers على المضيف تتشارك نواة واحدة. الآلة الافتراضية جدار أقوى؛ أما الـ container فباب محكم القفل. لهذا تُصرّ الدروس اللاحقة على المستخدمين غير الـ root وعلى الـ images الصغيرة: فأنت تقلّص ما يستطيع المهاجم فعله إن تجاوز ذلك الباب.</p>\n<p>على macOS وWindows، يشغّل Docker Desktop آلة Linux افتراضية خفيفة واحدة ويضع الـ containers داخلها. النموذج هو نفسه، كل ما في الأمر أن هناك طبقة إضافية واحدة في الأسفل.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">الـ image مقابل الـ container</h2>\n<p>الـ <strong>image</strong> لقطة لنظام ملفات مقسّمة إلى layers (طبقات)، للقراءة فقط، مع بيانات وصفية: أي أمر يُشغَّل، وأي منفذ يتوقّعه التطبيق، وبأي مستخدم يعمل. أما الـ <strong>container</strong> فنسخة قيد التشغيل من الـ image، فوقها layer رقيقة قابلة للكتابة.</p>\n<p>ينطبق تشبيه الـ class والـ object هنا جيدًا: image واحدة، وcontainers كثيرة. حين نوسّع notes-api إلى ست نسخ في Kubernetes، فهذه image واحدة وستة containers. والـ layer القابلة للكتابة في كل container خاصة به وقابلة للتخلّص منها، لذا فالقاعدة من اليوم الأول: <strong>الـ containers قطيع يُستبدل، والبيانات تعيش في مكان آخر</strong>. سيحفظ Postgres ملفاته في volume (وحدة تخزين دائمة)، وليس أبدًا في الـ layer القابلة للكتابة داخل container.</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>معاملة الـ container كأنه خادم</span></p>\n<p>كثيرًا ما تدخل الفِرق حديثة العهد بالـ containers إلى container قيد التشغيل بـ <code dir=\"ltr\">docker exec</code>، فتعدّل ملف إعدادات ثم تمضي. يأتي الـ deploy التالي فيستبدل الـ container ويختفي الإصلاح، وغالبًا ما يحدث ذلك في خضمّ حادث (incident). غيّر الـ image أو الإعدادات التي تمرّرها إلى الـ container، ولا تعدّل الـ container وهو يعمل أبدًا.</p>\n</aside>\n<p>في الدرس التالي ستكتب أول Dockerfile لـ notes-api، وتحوّل الكود المصدري إلى image تستطيع تشغيلها.</p>\n","toc":[{"id":"sec-1","text":"المشروع: notes-api","level":2},{"id":"sec-2","text":"الـ container عملية","level":2},{"id":"sec-3","text":"الـ image مقابل الـ container","level":2}],"takeaways":["الـ container عملية Linux عادية، تعزلها النواة بالـ namespaces وتحدّ مواردها بالـ cgroups.","تتشارك الـ containers نواة المضيف، ولهذا تبدأ خلال أجزاء من الثانية، ولهذا أيضًا يكون عزلها أضعف من عزل الآلة الافتراضية.","الـ image قالب للقراءة فقط مكوّن من layers لنظام الملفات مع بيانات وصفية؛ والـ container نسخة واحدة قيد التشغيل منها، فوقها layer رقيقة قابلة للكتابة.","كل ما يكتبه الـ container في نظام ملفاته الخاص يختفي عند حذف الـ container، إلا إذا وضعته في volume."],"further":[{"title":"What is a container?","url":"https://docs.docker.com/get-started/docker-concepts/the-basics/what-is-a-container/"},{"title":"What is an image?","url":"https://docs.docker.com/get-started/docker-concepts/the-basics/what-is-an-image/"},{"title":"Kubernetes Overview","url":"https://kubernetes.io/docs/concepts/overview/"}],"quiz":[{"q":"<p>تشغّل الأمر <code dir=\"ltr\">ps aux</code> على مضيف Linux بينما يشغّل container خادم Node.js. ماذا ترى؟</p>","options":[{"text":"لا شيء، لأن الـ container يعمل داخل آلة افتراضية صغيرة خاصة به.","why":"لا يشغّل Docker على Linux آلة افتراضية لكل container. صحيح أن Docker Desktop على macOS وWindows يشغّل آلة Linux افتراضية واحدة مشتركة، لكن الـ containers داخلها تبقى عمليات عادية."},{"text":"عملية <code dir=\"ltr\">dockerd</code> فقط، وخادم Node.js مخفيّ بداخلها.","why":"الـ daemon يُطلق الـ containers لكنه لا يحتضنها. عملية الـ container لها سطرها المستقل في جدول عمليات المضيف."},{"text":"عملية <code dir=\"ltr\">node</code> تظهر في القائمة مثل أي عملية أخرى على المضيف.","why":"صحيح. الـ container عملية على المضيف. الـ namespaces تخفي بقية المضيف عنها، لا العكس."},{"text":"نسخة من نظام التشغيل الكامل للـ container تُقلع في الخلفية.","why":"الـ containers لا تُقلع نظام تشغيل. إنها تعيد استخدام نواة المضيف ولا تشغّل إلا الأمر الذي طلبته."}],"answer":2},{"q":"<p>أي ميزة في النواة تمنع container خرج عن السيطرة من استهلاك كل ذاكرة المضيف؟</p>","options":[{"text":"مجموعات التحكّم (cgroups)، التي تضع سقفًا لاستهلاك المعالج والذاكرة والإدخال والإخراج وتحسبه.","why":"صحيح. الـ cgroups تفرض حدود الموارد، وستجد لاحقًا في الدورة أن الـ limits في Kubernetes تُترجَم في النهاية إلى هذه الإعدادات نفسها."},{"text":"الـ PID namespace، التي تعطي الـ container شجرة عمليات خاصة به.","why":"الـ namespaces تتحكّم فيما تراه العملية، لا في مقدار ما تستهلكه. عملية في PID namespace خاصة بها تستطيع مع ذلك التهام كل بايت من الذاكرة."},{"text":"الـ layers المخصّصة للقراءة فقط في الـ image، لأنها لا تكبر أثناء التشغيل.","why":"الـ layers المخصّصة للقراءة فقط تحدّ ما على القرص، لا استهلاك الذاكرة. العملية تحجز الذاكرة بغضّ النظر عن طريقة تخزين ملفاتها."},{"text":"الـ layer القابلة للكتابة في الـ container، لأن حجمها مضبوط على حدّ الذاكرة.","why":"الـ layer القابلة للكتابة مساحة على القرص لتغييرات الملفات، ولا علاقة لها بالذاكرة."}],"answer":0},{"q":"<p>تشغّل ثلاثة containers من الـ image ‏<code dir=\"ltr\">notes-api:1.0.0</code>. يكتب أحدها ملفًا في <code dir=\"ltr\">/tmp/cache.json</code>. ماذا يرى الاثنان الآخران؟</p>","options":[{"text":"الملف الجديد، لأن الثلاثة تتشارك نظام ملفات الـ image.","why":"إنها تتشارك layers الـ image المخصّصة للقراءة فقط، لكن كل container يكتب في layer خاصة به قابلة للكتابة."},{"text":"الملف الجديد بعد إعادة التشغيل، حين يزامن Docker الـ layers.","why":"لا يزامن Docker أبدًا الـ layers القابلة للكتابة بين الـ containers. هذا بالضبط دور الـ volumes."},{"text":"خطأ، لأن أنظمة ملفات الـ images للقراءة فقط.","why":"يحصل كل container على layer قابلة للكتابة فوق الـ image، فتنجح الكتابة؛ لكنها خاصة ومؤقتة."},{"text":"لا شيء جديد؛ الملف موجود فقط في الـ layer القابلة للكتابة لذلك الـ container وحده.","why":"صحيح. كل container نسخة لها layer رقيقة قابلة للكتابة خاصة بها. احذف الـ container فيختفي الملف معه."}],"answer":3},{"q":"<p>لماذا يُعدّ الـ container حدًّا أمنيًا أضعف من الآلة الافتراضية؟</p>","options":[{"text":"لأن الـ containers لا تستطيع تقييد الوصول إلى الشبكة إطلاقًا.","why":"الـ network namespaces تعطي كل container حزمة شبكة خاصة به، ويمكنك تقييدها أكثر. الشبكة ليست هي الثغرة."},{"text":"لأن كل الـ containers على المضيف تتشارك نواة واحدة، فثغرة في النواة قد تعبر بينها.","why":"صحيح. للآلة الافتراضية نواتها الخاصة خلف الـ hypervisor، أما الـ container فيتحدّث مباشرة مع نواة المضيف. هذا ثمن البدء خلال أجزاء من الثانية."},{"text":"لأن الـ images لا يمكن توقيعها أو التحقّق منها.","why":"يمكن توقيع الـ images وتثبيتها بالـ digest؛ لكن هذا من أمن سلسلة توريد الـ images، وليس حدًّا للعزل."},{"text":"لأن الـ containers تعمل دائمًا كمستخدم root على المضيف.","why":"كثير منها يفعل ذلك افتراضيًا، وهذا سيئ، لكنك تستطيع، بل يجب، أن تشغّلها كمستخدم غير root. الفرق البنيوي هو النواة المشتركة."}],"answer":1}],"exercise":{"kind":"choice","multiple":true,"options":["shared-kernel","own-writable","vm-each","cgroups-see","image-many"],"correct":["shared-kernel","own-writable","image-many"],"title":"صح أم خطأ: ما هو الـ container","prompt":"<p>يكتب زميلك دليلًا تعريفيًا للمنضمّين الجدد إلى notes-api، ويطلب منك مراجعة خمس عبارات عن الـ containers. اختر <strong>كل</strong> عبارة صحيحة.</p>\n","hints":["اسأل نفسك عن كل عبارة: هل تتحدّث عمّا تراه العملية، أم عمّا تستهلكه، أم عمّا على قرصها؟","الـ namespaces تخصّ الرؤية؛ والـ cgroups تخصّ حدود الموارد.","ثلاث من العبارات الخمس صحيحة."],"explanation":"<p>الـ containers عمليات على المضيف تتشارك نواته، ولهذا تبدأ بسرعة، ولهذا أيضًا يكون عزلها أضعف من عزل الآلة الافتراضية. يحصل كل container على layer خاصة قابلة للكتابة فوق الـ image المشتركة المخصّصة للقراءة فقط، ويمكن لـ image واحدة أن تكون أساسًا لأي عدد من الـ containers. لا ينشئ Docker على Linux آلة افتراضية لكل container، والـ cgroups تحدّ <em>استهلاك</em> الموارد؛ أما ما تستطيع العملية <em>رؤيته</em> فتتحكّم فيه الـ namespaces.</p>\n","checkLabels":{},"optionText":{"shared-kernel":{"text":"كل container على مضيف Linux يستخدم نواة المضيف نفسها.","why":"صحيح. لا توجد نواة ضيف. ومن هنا تأتي السرعة ويأتي العزل الأضعف معًا."},"own-writable":{"text":"يحصل كل container على layer خاصة قابلة للكتابة؛ والتغييرات فيها تختفي عند حذف الـ container.","why":"صحيح. ولهذا توضع البيانات التي يجب أن تبقى في volume."},"vm-each":{"text":"ينشئ Docker آلة افتراضية صغيرة لكل container حتى لا يؤثّر بعضها في بعض.","why":"ليس على Linux. يستخدم Docker Desktop آلة افتراضية واحدة مشتركة على macOS وWindows، لكن الـ containers داخلها تبقى عمليات على نواة واحدة."},"cgroups-see":{"text":"الـ cgroups تحدّد الملفات والعمليات التي يُسمح للـ container برؤيتها.","why":"هذا عمل الـ namespaces. الـ cgroups تضع سقفًا لاستهلاك المعالج والذاكرة والإدخال والإخراج وتحسبه."},"image-many":{"text":"يمكن لـ image واحدة أن تكون قالبًا لعدّة containers تعمل في الوقت نفسه.","why":"صحيح. التوسّع إلى ست نسخ يعني ستة containers من image واحدة."}}},"words":543,"readMinutes":3}