Docker والحاويات

الصور والطبقات والسجل

18 دقيقة الدرس 2 من 30

الصور والطبقات والسجل

كل حاوية تُشغّلها تبدأ من صورة. إن فهم كيفية بناء الصور وتخزينها وتوزيعها ليس معرفةً اختياريةً، بل يؤثر مباشرةً على سرعة البناء، وموثوقية النشر، وأمان النظام في الإنتاج. في شركات مثل Google وFacebook وAmazon، تستثمر الفرق بثقل في جودة الصور، لأن نموذج الطبقات غير المفهوم يتسبب في حوادث حقيقية: إخفاقات التخزين المؤقت التي تُضاعف زمن البناء ثلاث مرات، وطبقات بحجم غيغابايتات تُبطئ الإقلاع الباردة بدقائق طويلة، وصور أساسية قديمة تنقل ثغرات أمنية غير مُرقّعة إلى ملايين المستخدمين.

نظام الملفات الموحد ونموذج الطبقات

صورة Docker ليست أرشيفاً أحادياً، بل هي مجموعة مرتبة من الطبقات للقراءة فقط، حيث تمثل كل طبقة الفرق في نظام الملفات الناتج عن تعليمة واحدة في Dockerfile. يدمج وقت تشغيل الحاوية هذه الطبقات باستخدام نظام ملفات موحد (OverlayFS على Linux الحديث) في واجهة واحدة متكاملة. فوق هذا المجموعة للقراءة فقط، يُضيف وقت التشغيل طبقةً واحدةً رقيقةً قابلةً للكتابة — طبقة الحاوية — التي تحوي جميع التعديلات أثناء التشغيل.

لماذا يهم هذا؟ صورتان تشتركان في نفس القاعدة — مثلاً debian:12-slim — تُخزِّنان تلك القاعدة مرةً واحدةً فقط على القرص وفي السجل. حين تسحب عشر خدمات مصغّرة مشتقة من نفس القاعدة، تنتقل عبر الشبكة الطبقات الفريدة العليا فقط. هكذا تُبقي الأساطيل الكبيرة تكاليف نقل بيانات السجل في حدود معقولة، وكيف يحصل المطورون على عمليات سحب شبه فورية عند التغييرات التدريجية.

Docker Image Layer Stack and Shared Base Image A (app-server) Container Layer (writable) Runtime changes only — ephemeral COPY ./app /app Layer 4 — app source (unique) RUN pip install -r requirements.txt Layer 3 — Python deps (unique) RUN apt-get install python3 Layer 2 — shared FROM debian:12-slim Layer 1 — base OS (shared) Image B (worker) Container Layer (writable) Runtime changes only — ephemeral COPY ./worker /worker Layer 4 — worker source (unique) RUN pip install celery Layer 3 — Celery deps (unique) RUN apt-get install python3 Layer 2 — shared FROM debian:12-slim Layer 1 — base OS (shared) pulled once طبقات مشتركة (مخزّنة محلياً وفي السجل) طبقات فريدة لكل صورة طبقة الحاوية (قابلة للكتابة، مؤقتة)
تشترك الصورتان في طبقتَي نظام التشغيل الأساسي وpython3. تُنقل الطبقات الفريدة العليا فقط عند السحب.

التخزين المعتمد على المحتوى: معرّف الطبقة والبصمة

تُعرَّف كل طبقة بـبصمة SHA-256 لأرشيف tar المضغوط الخاص بها. يسرد بيان الصورة — وهو مستند JSON — هذه البصمات بالترتيب. عند تشغيل docker pull، يجلب Docker البيان أولاً ويقارن بصمة كل طبقة بما هو موجود على القرص، ثم يُنزّل الطبقات المفقودة فقط. هذا ليس تحسيناً، بل ضماناً للصحة: أي عدم تطابق في البصمة يعني تلاعباً أو تلفاً، ويرفض Docker استخدام تلك الطبقة.

للصورة ذاتها أيضاً بصمة، محسوبة من البيان. هذا ما تراه عند إلحاق @sha256:<hash> بمرجع الصورة. الوسم — مثل nginx:1.27 — مجرد مؤشر قابل للتغيير؛ أما البصمة فثابتة لا تتغير. في عمليات النشر الإنتاجية (Kubernetes وArgo CD)، يجب تثبيت صورك بالبصمة لا بالوسم، لضمان قابلية التكرار عبر البيئات ومنع فئة حوادث "يعمل في التدريج" التي تحدث حين يُرفع وسم جديد بين اختبارك ونشرك الإنتاجي.

الوسم مقابل البصمة: قد يشير nginx:latest إلى صورة مختلفة تماماً غداً. أما nginx@sha256:a3ed... فيشير إلى بيان واحد ثابت لا يتغير أبداً. تُثبّت خطوط CI/CD في الشركات الكبرى دائماً البصمات في بيانات النشر التي تُحفظ في Git، ثم تترك لروبوت آلي (Dependabot أو Renovate) اقتراح الترقيات عبر طلبات سحب.

سحب الصور: ما يحدث فعلياً

تتبع عملية سحب حقيقية لترى جميع الأجزاء المتحركة:

# سحب وسم محدد ومراقبة تحليل الطبقات docker pull nginx:1.27-alpine # فحص بصمة البيان التي حلّلها Docker docker inspect nginx:1.27-alpine --format '{{index .RepoDigests 0}}' # nginx@sha256:a1b2c3d4... # السحب بالبصمة — صورة مطابقة مضمونة على كل مضيف docker pull nginx@sha256:a1b2c3d4e5f6... # سرد الطبقات المخزّنة محلياً docker image ls --digests nginx # تفاصيل كل طبقة (الحجم غير المضغوط والبصمة) docker image inspect nginx:1.27-alpine \ --format '{{range .RootFS.Layers}}{{println .}}{{end}}'

كل سطر في مخرجات السحب يمثل طبقة واحدة: Pulling from library/nginx هو جلب البيان؛ ثم أسطر لكل طبقة إما Already exists (إصابة التخزين المؤقت) أو Pull complete. راقب نسبة الإصابات لفهم حجم نقل بيانات السجل الفعلي في أسطولك.

رفع الصور إلى السجل

سير العمل للرفع إلى Docker Hub أو AWS ECR أو أي سجل متوافق مع OCI واحد: المصادقة، ثم الوسم باسم كامل التأهيل، ثم الرفع. تُرفع الطبقات الغائبة على الخادم فقط — يُطبَّق نفس إلغاء التكرار المعتمد على المحتوى في مسار الرفع أيضاً.

# المصادقة على Docker Hub docker login -u myusername # المصادقة على AWS ECR (تنتهي صلاحية الرمز كل 12 ساعة — آلِّ هذا في CI) aws ecr get-login-password --region us-east-1 \ | docker login --username AWS --password-stdin \ 123456789012.dkr.ecr.us-east-1.amazonaws.com # وسم صورة محلية لـ ECR docker tag myapp:build-abc123 \ 123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp:1.4.2 # الرفع — يُرفع فقط ما هو جديد من الطبقات docker push 123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp:1.4.2 # رفع إصدار دلالي ثابت وتحديث وسم متحرك في آنٍ واحد docker tag 123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp:1.4.2 \ 123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp:stable docker push 123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp:stable
استراتيجية الوسم على نطاق واسع: سِمْ كل بناء CI ببصمة Git (مثل myapp:git-a1b2c3d) لإمكانية التتبع، وبإصدار دلالي (مثل myapp:1.4.2) للنشر، وبوسم متحرك مريح (مثل myapp:stable) لبيئات الكناري. لا تعتمد على latest وحده في أي نظام آلي.

فحص الطبقات باستخدام dive

تمنحك أداة المصدر المفتوح dive تفصيلاً طبقةً بطبقة لما تغيّر في كل خطوة، بما يشمل المساحة المهدرة من الملفات التي أُضيفت ثم حُذفت في تعليمات RUN منفصلة. في بيئات بمقياس Google، تُشغّل الفرق dive --ci في كل خط CI وتُفشل البناء إذا تجاوزت المساحة المهدرة حدًّا معيناً. شغّلها محلياً قبل كل ترقية للصورة لاكتشاف الضخامة الواضحة مبكراً.

# تثبيت dive (macOS) brew install dive # تحليل صورة تفاعلياً dive myapp:1.4.2 # التشغيل في CI — يفشل إذا كانت الكفاءة أقل من 90% CI=true dive --ci myapp:1.4.2

أنماط الفشل الإنتاجي الواجب معرفتها

ثلاثة أخطاء نموذجية في نموذج الطبقات تُحرق المهندسين مراراً:

  1. كسر الكاش على الطبقة الخاطئة. إذا نسخت شجرة المصدر كاملةً قبل تشغيل RUN pip install، فإن أي تغيير في ملف واحد يُبطل طبقة التبعيات. انسخ ملفات القفل أولاً، ثم ثبّت التبعيات، ثم انسخ المصدر. قد يُحوّل هذا بناءً من 4 دقائق إلى 12 ثانية.
  2. الأسرار المدمجة في الطبقات. خطوة RUN تطبع كلمة مرور تُنشئ طبقةً تحتوي على ذلك السر. حذفه في خطوة RUN لاحقة لا يُزيله من الطبقة أدناه — بل يظل مرئياً عبر docker history. استخدم أسرار البناء (--secret)، أو البناء متعدد المراحل، أو مديري الأسرار الخارجيين.
  3. قابلية تغيير الوسم تُسبب تباين النشر. إذا أُطلقت نسختان من خدمة في أوقات مختلفة وتغيّر الوسم بينهما، فإنهما تُشغّلان ثنائيات مختلفة. يجب دائماً تثبيت البصمات في بيانات Kubernetes التي تصل إلى الإنتاج.
لا تخزّن بيانات اعتماد في طبقات الصورة أبداً. حتى لو أضفت خطوة RUN rm /secrets فوراً بعدها، يُحفظ السر بشكل دائم في الطبقة السابقة ويمكن لأي شخص لديه صلاحية السحب الوصول إليه. استخدم أسرار BuildKit المركّبة (RUN --mount=type=secret,id=mytoken ...) أو وسائط البناء التي لا تُطبَع أبداً.

توافق OCI والنظام البيئي الأوسع

شعبّت Docker تنسيق الصورة، لكن مبادرة الحاوية المفتوحة (OCI) تملك الآن المواصفات: مواصفة الصورة، ومواصفة التوزيع (كيفية عمل السجلات)، ومواصفة وقت التشغيل. أي أداة متوافقة مع OCI — Podman وBuildah وcontainerd وKaniko — تستطيع بناء وتخزين وتشغيل نفس الصور. في Kubernetes، يُعدّ containerd وقت تشغيل الحاوية المعياري (أُزيل Docker كافتراضي في kubelet في الإصدار 1.24)، لكنه يقرأ ويُشغّل نفس صور OCI التي ينتجها docker build. فهم هذا يمنع الاعتقاد الخاطئ بأن "صور Docker تعمل مع Docker فقط."