DOCKER::ROOT الإقليم III · overlayfs · الصور الطبقية kernel live
REGION III البدائية الثالثة lower · upper · work

الجذر: مِمّ يتكوّن عالم العملية؟

عزلنا الرؤية والموارد. الآن: ما نظام الملفات الذي تراه العملية حين تنظر إلى /؟ في كونتينرك اليدوي نسخت bash بيدك — حلٌّ لا يتوسّع. هنا الحلّ الذي يتوسّع، ولماذا اتخذ شكل طبقات.

النبذة

الـ image ليس «ملفاً» ولا قرصاً افتراضياً. إنه كومةٌ من طبقات، كل طبقةٍ مجموعة فروقاتٍ على نظام ملفات: «أضِف هذه، عدّل تلك، احذف هذه». حين تشغّل كونتينراً، يكدّس الـ kernel الطبقات شفّافةً عبر union filesystem فتبدو نظام ملفٍ واحد. الطبقات كلها للقراءة فقط ومشتركة؛ ويُضاف فوقها طبقةٌ رقيقةٌ واحدة قابلة للكتابة خاصة بكل كونتينر. كل هذا وُلد من سؤالٍ اقتصاديٍّ واحد: كيف لا ننسخ؟

اللغز المستفزّ — نظام ملفٍ من العدم، بلا نسخ

عندك مجلدان يمثّلان «طبقتين»: base/ فيه app.txt=v1 وconfig.txt=default، وfeature/ فارغ. أنشئ merged/ بحيث:

  1. ترى فيها app.txt وconfig.txt من base/ (دون نسخهما).
  2. الكتابة في merged/app.txt = v2 لا تغيّر base/app.txt — يبقى v1.
  3. إنشاء merged/new.txt يظهر في «مكان التعديلات» لا في base/.
  4. حذف merged/config.txt يختفي من merged/ لكن يبقى في base/. (أصعب نقطة: كيف «تحذف» ملفاً من طبقةٍ للقراءة فقط دون تعديلها؟)
قلب اللغز

إن كانت الطبقة السفلى للقراءة فقط، فكيف يُسجَّل «هذا محذوف»؟ لا بدّ من علامةٍ في طبقةٍ أعلى تقول للـ union «تجاهل ما تحتك بهذا الاسم». ما شكلها؟ ابدأ من man mount وابحث عن نوع fs يقبل lowerdir, upperdir, workdir.

لحظة «آها»: «الحذف» ليس حذفاً، بل قناع (whiteout) — وهذا يفسّر لاحقاً لماذا حذف ملفٍ كبير في Dockerfile لا يصغّر الصورة.

الدرس — OverlayFS، وكيف صار image format

ليش الطبقات؟ الدافع الاقتصادي

بلا طبقات: كل صورة نسخةٌ كاملة، وكل كونتينر نسخة. عشرة كونتينرات = ١٠ جيجا من بتاتٍ مكرّرة، ومع كل تعديل سطر تعيد رفع جيجابايت. لا يتوسّع.

ليش — الحلّ المشترك بين كل أنظمة الحاويات

محتوى مُعنوَن بالمحتوى (content-addressable) + طبقاتٌ للقراءة فقط مشتركة. الطبقة تُعرَّف بـ digest (هاش SHA-256 لمحتواها). طبقتان متطابقتان = تُخزَّنان مرة واحدة. هذا يعطي: تكرار صفري على القرص، نقلٌ تفاضلي عند pull، وكاش بناء.

التكلفة المقابلة (trade-off)

الطبقات تتراكم ولا تُطرَح. إن أضفت ملف ١ جيجا في طبقة ثم «حذفته» أعلى، فالطبقة السفلى ما زالت تحمله — الحذف مجرد قناع فوقها. لهذا «نظّف في نفس الـ RUN»: الحذف في RUN لاحق يضيف طبقةً بقناع، ولا يحذف البايتات.

ليش OverlayFS تحديداً (وما سبقها)

جرّب Docker عدّة «storage drivers»، كلٌّ محاولةٌ لنفس المشكلة:

driverالفكرة / المشكلة
AUFSالأصلي، لم يدخل الـ kernel الرسمي قط (خارج الشجرة) فكان عبئاً.
devicemapperعلى مستوى الكتل لا الملفات؛ معقّد وبطيء كثيراً.
btrfs/zfsأنظمة ملفاتٍ كاملة بلقطاتها؛ قوية لكن تفرض نظام ملفٍ معيّناً.
overlay2الحاضر: في الـ kernel، على مستوى الملفات، بلا متطلبات. انتصر لأنه الأبسط الرسمي.

الدرس المتكرّر: ما يدخل الـ kernel الرسمي وبأبسط نموذج يفوز على الأقوى الخارجي.

كيف تعمل OverlayFS — النموذج الدقيق

lowerdir — طبقة (أو طبقات) سفلى، للقراءة فقط. تُكدّس: lowerdir=L3:L2:L1 upperdir — الطبقة العليا، قابلة للكتابة. كل تعديل يذهب هنا. workdir — مجلد عملٍ داخلي للعمليات الذرّية. على نفس fs الذي عليه upperdir. merged — نقطة التركيب الناتجة: العرض الموحّد.
bash
$ mount -t overlay overlay \ -o lowerdir=base,upperdir=feature,workdir=work \ merged

قواعد الدمج (استنتجها لا تحفظها):

افحص بنفسك بعد اللغز: بعد حذف config.txt، نفّذ ls -l feature/ وابحث عن ملفٍ بنوعٍ غريب — هذا الـ whiteout. رؤيته بعينك تثبّت المفهوم.

من OverlayFS إلى OCI image

الوصلة الكبرى: طبقة الكونتينر = lowerdir. الصورة كومة طبقات overlay محزومةٌ للنقل. مواصفة OCI Image Spec تعرّف:

FIG 1 شجرة الصورة = شجرة Merkle
tag → index manifest (arch) config (Cmd/Env) [ layers ] sha256: tar…
تغيير بايتٍ في طبقة يغيّر هاشها → يغيّر الـ manifest → يغيّر هوية الصورة. الثبات والكاش والمشاركة من تصميمٍ واحد.

أين يخزّنها Docker؟ تحت /var/lib/docker/overlay2/ (الطبقات جاهزةً للتركيب) وimage/ (الـ manifests والـ configs). استكشفه في اللغز.

اللغز — فكّك صورةً حقيقية وأعِد تركيبها

  1. التشريح: docker pull alpine ثم docker save alpine -o alpine.tar. فُكّ وتجوّل: index.json → manifest → config + الطبقات. افتح طبقةً (هي tar). ارسم بيدك الشجرة index→manifest→config+layers مع الـ digest المختصر بجانب كل عقدة. لا تكمل قبل أن ترى السلسلة كاملة بعينيك.
  2. إعادة التركيب: ركّب طبقات alpine المفكوكة بـ mount -t overlay، والناتج merged/ rootfs صالح. ثم — وصلٌ بالإقليم I — استخدمه جذراً لكونتينرك اليدوي عبر pivot_root. صنعت كونتينر Alpine حقيقياً من طبقاتٍ حقيقية، بلا Docker. ذروة الإقليم.
  3. اللاتوسّع المعكوس: أنشئ ملف ١٠٠ميجا في طبقة، قِس الحجم، «احذفه» أعلى، أعد القياس. أثبت أن الحجم لم ينقص. اربطه بقاعدة الـ Dockerfile.
المصدر — لا تلخّصه لنفسك

مواصفة OCI Image Spec (opencontainers/image-spec): اقرأ manifest.md لبنية الـ manifest، config.md لـ rootfs.diff_ids، layer.md لتمثيل الـ whiteouts (.wh. و.wh..wh..opq).

الخلاصة — الأرجل الثلاث، والصورة تتّضح

namespaces
ماذا ترى · I
cgroups
كم تستهلك · II
overlay
مِمّ تتكوّن · III

من هذه الثلاث يُشتقّ مفهوم الكونتينر بالكامل. كل ما بقي (المعمارية، الشبكة، البناء، التوزيع) تنظيمٌ وأتمتة لها، لا مفاهيم جديدة من الجذر. هذه لحظة الصورة الكبرى: النقاط المتفرّقة بدأت تلتحم.

بذرة الغموض

content-addressing (شجرة Merkle) سيعود بقوة في التوزيع (VII): كيف يتشارك الـ registry الطبقات عبر الشبكة بلا تكرار = نفس فكرة الـ digest. لكن أولاً: namespaces عزلت الرؤية، cgroups الموارد، overlay الملفات... فماذا يُسمح للعملية أن تفعل حين تكون «root» بداخلها؟ كلمة «root» ليست مفتاحاً واحداً، بل حزمةٌ من ~٤٠ مفتاحاً منفصلاً — Docker يرمي معظمها قبل أن يسلّمك الكونتينر.