<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Infrastructure on kenji.blog</title><link>http://kenji.blog/ar/categories/infrastructure/</link><description>Recent content in Infrastructure on kenji.blog</description><generator>Hugo -- gohugo.io</generator><language>ar</language><copyright>kenjinote</copyright><lastBuildDate>Sun, 13 Sep 2026 07:00:00 +0900</lastBuildDate><atom:link href="http://kenji.blog/ar/categories/infrastructure/index.xml" rel="self" type="application/rss+xml"/><item><title>Hyper-V مقابل WSL2: مقارنة تقنيات المحاكاة الافتراضية على Windows</title><link>http://kenji.blog/ar/p/hyper-v-vs-wsl2-windows-virtualization/</link><pubDate>Sun, 13 Sep 2026 07:00:00 +0900</pubDate><guid>http://kenji.blog/ar/p/hyper-v-vs-wsl2-windows-virtualization/</guid><description>&lt;img src="http://kenji.blog/p/hyper-v-vs-wsl2-windows-virtualization/img/eyecatch.jpg" alt="Featured image of post Hyper-V مقابل WSL2: مقارنة تقنيات المحاكاة الافتراضية على Windows" />&lt;h2 id="1-مقدمة-تطور-المحاكاة-الافتراضية-في-windows">1. مقدمة: تطور المحاكاة الافتراضية في Windows
&lt;/h2>&lt;p>لقد تطورت تقنيات المحاكاة الافتراضية على منصة Windows بشكل كبير خلال العقود القليلة الماضية. في الماضي، كانت برامج الهايبرفايزر (Hypervisor) من النوع الثاني (Type 2) التابعة لجهات خارجية (مثل VMware Workstation و VirtualBox) هي السائدة، ولكن منذ أن قدمت Microsoft &amp;ldquo;Hyper-V&amp;rdquo; في Windows Server 2008، تم دمج برامج الهايبرفايزر من النوع الأول (Type 1) في أنظمة تشغيل سطح المكتب Windows 10/11.&lt;/p>
&lt;p>وفي السنوات الأخيرة، أصبح &amp;ldquo;WSL2 (Windows Subsystem for Linux 2)&amp;rdquo; يحظى بأكبر قدر من الاهتمام بين المطورين. بينما كان WSL1 يعتمد على ترجمة استدعاءات النظام (System Calls)، يستخدم WSL2 &amp;ldquo;جهازاً افتراضياً خفيفاً للأدوات (Lightweight Utility VM)&amp;rdquo; يعتمد على تقنية Hyper-V، مما يوفر توافقاً كاملاً مع نظام Linux وتحسناً هائلاً في الأداء.&lt;/p>
&lt;p>في هذه المقالة، سنقوم بمقارنة وشرح هاتين التقنيتين القويتين للمحاكاة الافتراضية ―― &amp;ldquo;Hyper-V&amp;rdquo; كامل الميزات، و &amp;ldquo;WSL2&amp;rdquo; المتخصص في تجربة المطورين ―― من حيث البنية، الأداء (وحدة المعالجة المركزية، الذاكرة، وإدخال/إخراج القرص (Disk I/O))، تكوين الشبكة، وأفضل حالات الاستخدام، إلى جانب تفاصيل فنية عميقة.&lt;/p>
&lt;hr>
&lt;h2 id="2-النظرية-الأساسية-للهايبرفايزر-ومقارنة-البنية-architecture">2. النظرية الأساسية للهايبرفايزر ومقارنة البنية (Architecture)
&lt;/h2>&lt;p>لفهم تقنيات المحاكاة الافتراضية، من الضروري فهم تصنيف أنواع الهايبرفايزر (مراقب الأجهزة الافتراضية: VMM).&lt;/p>
&lt;h3 id="21-الفرق-بين-الهايبرفايزر-من-النوع-الأول-type-1-والنوع-الثاني-type-2">2.1. الفرق بين الهايبرفايزر من النوع الأول (Type 1) والنوع الثاني (Type 2)
&lt;/h3>&lt;p>الهايبرفايزر عبارة عن طبقة برمجية تجرد الوصول إلى الأجهزة وتسمح لأنظمة تشغيل متعددة (أنظمة التشغيل الضيف) بالعمل في وقت واحد على جهاز مادي واحد.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>النوع الأول (المعدن المجرد - Bare-metal)&lt;/strong>: يعمل مباشرة على الأجهزة. لا يوجد مفهوم لنظام التشغيل المضيف (على الرغم من أنه بالمعنى الدقيق للكلمة قد يكون هناك نظام تشغيل إدارة له امتيازات)، وله حمل زائد (Overhead) منخفض للغاية ويوفر أداءً وأماناً عاليين. أمثلة: Hyper-V و VMware ESXi و Xen.&lt;/li>
&lt;li>&lt;strong>النوع الثاني (المستضاف - Hosted)&lt;/strong>: يعمل كتطبيق على نظام التشغيل المضيف (مثل Windows أو macOS). نظراً لأن جميع عمليات الوصول إلى الأجهزة تتم عبر نظام التشغيل المضيف، فإن الحمل الزائد يكون كبيراً. أمثلة: VMware Workstation و Oracle VirtualBox.&lt;/li>
&lt;/ul>
&lt;p>يعد Hyper-V الخاص بـ Windows &lt;strong>هايبرفايزر نقياً من النوع الأول (Type 1)&lt;/strong>. عندما تقوم بتمكين Hyper-V، فإن نظام تشغيل Windows الفعلي الذي يتفاعل معه المستخدم عادة سيعمل أيضاً داخل جهاز افتراضي خاص يسمى &amp;ldquo;قسم الجذر (Root Partition)&amp;rdquo;.&lt;/p>
&lt;h3 id="22-تفاصيل-بنية-hyper-v">2.2. تفاصيل بنية Hyper-V
&lt;/h3>&lt;p>تتبنى بنية Hyper-V تصميم الميكروكيرنل (Microkernel)، وتستند إلى وحدات عزل منطقية تسمى الأقسام (Partitions).&lt;/p>
&lt;pre class="mermaid">
graph TD
A[&amp;#34;الأجهزة (وحدة المعالجة المركزية، ذاكرة الوصول العشوائي، القرص، بطاقة الشبكة)&amp;#34;] --&amp;gt; B[&amp;#34;هايبرفايزر ويندوز (Ring -1)&amp;#34;]
B --&amp;gt; C[&amp;#34;قسم الجذر (نظام تشغيل ويندوز)&amp;#34;]
B --&amp;gt; D[&amp;#34;القسم الفرعي 1 (جهاز افتراضي ويندوز)&amp;#34;]
B --&amp;gt; E[&amp;#34;القسم الفرعي 2 (جهاز افتراضي لينكس)&amp;#34;]
C --&amp;gt; F[&amp;#34;ناقل الجهاز الافتراضي (VMBus)&amp;#34;]
D --&amp;gt; F
E --&amp;gt; F
C --&amp;gt; G[&amp;#34;برنامج تشغيل البنية التحتية للمحاكاة الافتراضية (VID)&amp;#34;]
C --&amp;gt; H[&amp;#34;عملية العامل (VMWP.exe)&amp;#34;]
&lt;/pre>
&lt;ul>
&lt;li>&lt;strong>هايبرفايزر ويندوز (Windows Hypervisor)&lt;/strong>: يعمل بأعلى مستوى امتياز لوحدة المعالجة المركزية (Ring -1 أو VMX Root Mode)، ويكون مسؤولاً فقط عن تخصيص الذاكرة وجدولة وحدة المعالجة المركزية. لا يتضمن برامج تشغيل الأجهزة.&lt;/li>
&lt;li>&lt;strong>قسم الجذر (Root Partition)&lt;/strong>: القسم الذي يعمل فيه نظام تشغيل Windows المضيف. يمتلك جميع برامج تشغيل الأجهزة ويتحكم في الأجهزة مباشرةً. يوفر أيضاً وظائف الإدارة للأقسام الفرعية (مثل مزود WMI و VMWP.exe).&lt;/li>
&lt;li>&lt;strong>القسم الفرعي (Child Partition)&lt;/strong>: القسم الذي يعمل فيه نظام التشغيل الضيف. لا يُسمح له بالوصول المباشر إلى الأجهزة، ويرسل طلبات الإدخال/الإخراج إلى قسم الجذر (Synthetic I/O) عبر ناقل مشاركة ذاكرة منطقي يسمى &amp;ldquo;VMBus&amp;rdquo;.&lt;/li>
&lt;/ul>
&lt;h3 id="23-آلية-عمل-wsl2-وجهاز-utility-vm-الخفيف">2.3. آلية عمل WSL2 وجهاز Utility VM الخفيف
&lt;/h3>&lt;p>يستخدم WSL2 نفس تقنية الأساس للهايبرفايزر من النوع الأول (Type 1) مثل Hyper-V، ولكنه يستخدم مجموعة فرعية تسمى &amp;ldquo;منصة الأجهزة الافتراضية (Virtual Machine Platform: VMP)&amp;rdquo; والتي تختلف عن الأجهزة الافتراضية كاملة الميزات لـ Hyper-V.&lt;/p>
&lt;p>يلغي &amp;ldquo;الجهاز الافتراضي الخفيف للأدوات (Lightweight Utility VM)&amp;rdquo; المستخدم في WSL2 تماماً المحاكاة (Emulation) للأجهزة القديمة التي تمتلكها الأجهزة الافتراضية التقليدية (مثل BIOS الافتراضي واللوحة الأم الافتراضية).&lt;/p>
&lt;pre class="mermaid">
graph TD
A[&amp;#34;نظام تشغيل ويندوز المضيف (مساحة المستخدم)&amp;#34;]
B[&amp;#34;نظام ملفات NTFS&amp;#34;]
C[&amp;#34;خادم بروتوكول 9P (Plan 9)&amp;#34;]
D[&amp;#34;الجهاز الافتراضي الخفيف للأدوات (نواة لينكس)&amp;#34;]
E[&amp;#34;ext4.vhdx (القرص الافتراضي)&amp;#34;]
F[&amp;#34;مساحة مستخدم لينكس (توزيعات WSL2)&amp;#34;]
A --&amp;gt; C
C --&amp;gt;| مشاركة الملفات عبر أنظمة التشغيل | D
D --&amp;gt; E
D --&amp;gt; F
&lt;/pre>
&lt;p>أكبر ميزات WSL2 هي &lt;strong>سرعة بدء التشغيل&lt;/strong> و &lt;strong>التكامل السلس مع نظام التشغيل المضيف&lt;/strong>. تعمل نواة Linux في أقل من ثانية، ويتم الوصول إلى نظام الملفات (NTFS) على جانب Windows عبر بروتوكول نظام ملفات الشبكة &lt;code>9P&lt;/code> الخاص بـ Plan 9.&lt;/p>
&lt;hr>
&lt;h2 id="3-التحليل-الشامل-للأداء-موارد-الحوسبة-والإدخالالإخراج">3. التحليل الشامل للأداء: موارد الحوسبة والإدخال/الإخراج
&lt;/h2>&lt;p>يتم التعبير عن أداء الجهاز الافتراضي كمجموع الأحمال الزائدة (Overheads) في كل مكون: وحدة المعالجة المركزية، الذاكرة، وإدخال/إخراج القرص (Disk I/O).&lt;/p>
&lt;h3 id="31-وحدة-المعالجة-المركزية-وتبديل-السياق-context-switch">3.1. وحدة المعالجة المركزية وتبديل السياق (Context Switch)
&lt;/h3>&lt;p>يستخدم كل من Hyper-V و WSL2 المحاكاة الافتراضية المدعومة بالأجهزة (Intel VT-x / AMD-V). يتم تنفيذ تعليمات وحدة المعالجة المركزية بشكل أساسي بالسرعة الأصلية، ولكن عند تنفيذ التعليمات ذات الامتيازات أو معالجة الإدخال/الإخراج، تحدث مقاطعة تسمى &amp;ldquo;VM Exit&amp;rdquo;، ويتم تبديل السياق (Context Switch) إلى الهايبرفايزر.&lt;/p>
&lt;p>يمكن التعبير عن الحمل الزائد لوحدة المعالجة المركزية $T_{overhead}$ في هذا الوقت بالنموذج الرياضي التالي:&lt;/p>
$$ T_{overhead} = \sum_{i=1}^{N} (t_{vm\_exit} + t_{hypercall\_process} + t_{vm\_entry}) $$&lt;p>حيث:&lt;/p>
&lt;ul>
&lt;li>$N$: عدد مرات حدوث VM Exit لكل وحدة زمنية&lt;/li>
&lt;li>$t_{vm\_exit}$: وقت الانتقال من الضيف إلى الهايبرفايزر&lt;/li>
&lt;li>$t_{hypercall\_process}$: وقت معالجة الإدخال/الإخراج أو المقاطعات عبر VMBus&lt;/li>
&lt;li>$t_{vm\_entry}$: وقت العودة من الهايبرفايزر إلى الضيف&lt;/li>
&lt;/ul>
&lt;p>نظراً لعدم وجود محاكاة (Emulation) للأجهزة القديمة في WSL2، تم تحسين $t_{hypercall\_process}$ ليكون صغيراً للغاية. لذلك، في العمليات الحسابية البحتة لوحدة المعالجة المركزية (على سبيل المثال، تجميع النواة أو استنتاج نماذج التعلم الآلي)، يقتصر تدهور الأداء على بضع نسب مئوية مقارنة ببيئة المعدن المجرد (Bare-metal).&lt;/p>
&lt;h3 id="32-آلية-تخصيص-الذاكرة">3.2. آلية تخصيص الذاكرة
&lt;/h3>&lt;p>هناك اختلاف واضح في فلسفة التصميم بين الاثنين من حيث طرق إدارة الذاكرة.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Hyper-V (الذاكرة الديناميكية)&lt;/strong>: بناءً على طلب الذاكرة من الجهاز الافتراضي الضيف، يقوم قسم الجذر بتخصيص واستعادة الذاكرة ديناميكياً. ومع ذلك، لا يتم تحرير الذاكرة المحجوزة كذاكرة تخزين مؤقت للصفحات (Page Cache) داخل نظام التشغيل الضيف بسهولة ما لم يكن النظام تحت ضغط.&lt;/li>
&lt;li>&lt;strong>WSL2 (الاستعادة الديناميكية للذاكرة)&lt;/strong>: يمتلك WSL2 آلية خاصة به حيث يقوم بانتظام بإرجاع (Reclaim) الذاكرة التي لم يعد هناك حاجة إليها داخل جهاز Linux الافتراضي (بما في ذلك ذاكرة التخزين المؤقت) إلى مضيف Windows. في الإصدارات الأولى من WSL2، كانت هناك مشكلة حيث تستهلك ذاكرة التخزين المؤقت للصفحات في Linux ذاكرة Windows (تضخم عملية Vmmem)، ولكن تم تحسين ذلك الآن من خلال تصحيحات النواة (Kernel Patches).&lt;/li>
&lt;/ul>
&lt;h3 id="33-خصائص-إدخالإخراج-القرص-vhdx-مقابل-ext4vhdx">3.3. خصائص إدخال/إخراج القرص (VHDX مقابل ext4.vhdx)
&lt;/h3>&lt;p>غالبًا ما يكون إدخال/إخراج القرص هو العائق (Bottleneck) الأكبر في أداء الجهاز الافتراضي.&lt;/p>
&lt;p>يتم حساب زمن انتقال (Latency) الإدخال/الإخراج $L_{total}$ على النحو التالي:&lt;/p>
$$ L_{total} = L_{guest\_fs} + L_{vmbus} + L_{host\_fs} + L_{physical\_disk} $$&lt;p>&lt;strong>في حالة Hyper-V&lt;/strong>:
تستخدم الأجهزة الضيفة النموذجية في Hyper-V أقراصاً افتراضية بتنسيق &lt;code>VHDX&lt;/code>. يتم معالجة طلبات الإدخال/الإخراج الصادرة من نظام الملفات (ext4 أو NTFS) داخل نظام التشغيل الضيف عبر برنامج تشغيل تخزين الجهاز الكتلي لـ VMBus (storvsc)، ويتم التعامل معها كوصول إلى ملف VHDX على نظام NTFS في جانب Windows.&lt;/p>
&lt;p>&lt;strong>في حالة WSL2&lt;/strong>:
تعمل توزيعات Linux الخاصة بـ WSL2 على نظام ملفات ext4 الأصلي المبني داخل ملف &lt;code>ext4.vhdx&lt;/code> مخصص. توفر عمليات الملفات داخل Linux (مثل داخل دليل &lt;code>~&lt;/code>) أداءً أصلياً مشابهاً لأداء Hyper-V المذكور أعلاه.
ومع ذلك، &lt;strong>عند الوصول إلى ملفات على جانب Windows (مثل &lt;code>/mnt/c/&lt;/code>) من Linux في WSL2&lt;/strong>، أو العكس، تختلف المعالجة بشكل كبير. يتم استخدام &lt;code>9P (بروتوكول نظام ملفات Plan 9)&lt;/code> للوصول عبر أنظمة التشغيل هذه.&lt;/p>
$$ L_{cross\_os} = L_{9p\_client} + L_{socket\_transfer} + L_{9p\_server} + L_{ntfs} $$&lt;p>نظراً لأن الوصول عبر بروتوكول 9P يترتب عليه حمل زائد كبير بسبب عملية التسلسل (Serialization)، فإن الأداء ينخفض بشكل ملحوظ (أحياناً بتأخير يزيد عن 10 أضعاف) للاستخدامات التي تقرأ وتكتب عدداً كبيراً من الملفات الصغيرة (مثال: &lt;code>npm install&lt;/code> في مشروع Node.js أو عمليات Git الموجودة في دليل على جانب Windows).
لذلك، &lt;strong>القاعدة الذهبية عند استخدام WSL2 هي وضع ملفات المشروع دائماً في نظام الملفات الأصلي لنظام Linux (تحت &lt;code>~/&lt;/code>)&lt;/strong>.&lt;/p>
&lt;hr>
&lt;h2 id="4-هيكل-الشبكة-nat-والمفتاح-الافتراضي-default-switch-والشبكة-المدمجة-bridged">4. هيكل الشبكة: NAT، والمفتاح الافتراضي (Default Switch)، والشبكة المدمجة (Bridged)
&lt;/h2>&lt;p>تعد مرونة ميزات الشبكات أحد الاختلافات الرئيسية بين Hyper-V و WSL2.&lt;/p>
&lt;h3 id="41-شبكة-wsl2-القائمة-على-nat">4.1. شبكة WSL2 (القائمة على NAT)
&lt;/h3>&lt;p>تم تكوين شبكة WSL2 افتراضياً لتكون شبكة &amp;ldquo;NAT (ترجمة عنوان الشبكة)&amp;rdquo; باستخدام تقنية التبديل الافتراضي (Virtual Switch) الخاصة بـ Hyper-V.
يتم تلقائياً تخصيص عنوان IP خاص (مثال: &lt;code>172.20.x.x&lt;/code>) لجهاز Linux الافتراضي يختلف عن مضيف Windows. توجد آلية مدمجة تقوم بتوجيه الطلبات من مضيف Windows عبر &lt;code>localhost&lt;/code> إلى الخدمات (المنافذ) التي تم تشغيلها داخل WSL2، مما يسمح للمطورين باختبار خوادم الويب وغيرها دون القلق بشأن الشبكة.&lt;/p>
&lt;p>في الآونة الأخيرة، تم تقديم وضع شبكة جديد يسمى &amp;ldquo;وضع النسخ المتطابق (Mirrored Mode)&amp;rdquo; في إصدار المعاينة لـ WSL2. يهدف هذا إلى دعم IPv6 وتحسين التوافق مع اتصالات VPN (يمكن تكوينه في &lt;code>.wslconfig&lt;/code>).&lt;/p>
&lt;h3 id="42-المفتاح-الافتراضي-لـ-hyper-v-virtual-switch">4.2. المفتاح الافتراضي لـ Hyper-V (Virtual Switch)
&lt;/h3>&lt;p>يسمح Hyper-V ببناء شبكات متقدمة على مستوى المؤسسات. من خلال &amp;ldquo;مدير المفتاح الافتراضي (Virtual Switch Manager)&amp;quot;، فإنه يوفر ثلاثة أوضاع رئيسية:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>خارجي (External)&lt;/strong>: يربط بطاقة الشبكة الفعلية للجهاز المضيف بالمفتاح الافتراضي، ويسمح للجهاز الافتراضي الضيف بالانضمام مباشرة إلى الشبكة المادية (اتصال جسر - Bridged). يحصل الجهاز الافتراضي على عنوان IP من خادم DHCP من نفس الشبكة الفرعية للشبكة المادية.&lt;/li>
&lt;li>&lt;strong>داخلي (Internal)&lt;/strong>: يسمح فقط بالاتصال بين نظام التشغيل المضيف والجهاز الافتراضي، وبين الأجهزة الافتراضية وبعضها البعض. لا يمكن الوصول المباشر إلى الشبكات الخارجية.&lt;/li>
&lt;li>&lt;strong>خاص (Private)&lt;/strong>: يسمح فقط بالاتصال بين الأجهزة الافتراضية، ويحظر حتى الاتصال بنظام التشغيل المضيف. يستخدم لبناء بيئات اختبار معزولة.&lt;/li>
&lt;/ol>
&lt;h3 id="43-بناء-شبكة-hyper-v-المتقدمة-باستخدام-powershell">4.3. بناء شبكة Hyper-V المتقدمة باستخدام PowerShell
&lt;/h3>&lt;p>في بيئات التطوير والاختبار، إذا كنت ترغب في إنشاء شبكة NAT مخصصة للأجهزة الافتراضية، يمكنك الحصول على تحكم دقيق باستخدام PowerShell. فيما يلي مثال لبرنامج نصي يقوم بإنشاء مفتاح افتراضي داخلي وتكوين NAT عليه لتوفير وصول الأجهزة الافتراضية إلى الإنترنت.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># 1. إنشاء مفتاح افتراضي داخلي&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$SwitchName&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;HyperV-NatSwitch&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">New-VMSwitch&lt;/span> &lt;span class="n">-SwitchName&lt;/span> &lt;span class="nv">$SwitchName&lt;/span> &lt;span class="n">-SwitchType&lt;/span> &lt;span class="n">Internal&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># 2. تعيين عنوان IP لبطاقة الشبكة الافتراضية على جانب المضيف (عنوان IP الذي سيكون البوابة)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$GatewayIP&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;192.168.100.1&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$NetPrefix&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mf">24&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$InterfaceAlias&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;vEthernet (&lt;/span>&lt;span class="nv">$SwitchName&lt;/span>&lt;span class="s2">)&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">New-NetIPAddress&lt;/span> &lt;span class="n">-IPAddress&lt;/span> &lt;span class="nv">$GatewayIP&lt;/span> &lt;span class="n">-PrefixLength&lt;/span> &lt;span class="nv">$NetPrefix&lt;/span> &lt;span class="n">-InterfaceAlias&lt;/span> &lt;span class="nv">$InterfaceAlias&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># 3. تكوين شبكة NAT&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$NatName&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;HyperV-NatNetwork&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$NatSubnet&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;192.168.100.0/24&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">New-NetNat&lt;/span> &lt;span class="n">-Name&lt;/span> &lt;span class="nv">$NatName&lt;/span> &lt;span class="n">-InternalIPInterfaceAddressPrefix&lt;/span> &lt;span class="nv">$NatSubnet&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># أمر للتحقق&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">Get-NetNat&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>من خلال هذا التكوين، ومن خلال تعيين عنوان IP يدوياً مثل &lt;code>192.168.100.x&lt;/code> والبوابة &lt;code>192.168.100.1&lt;/code> لضيف Hyper-V المحدد، يمكنك بناء مقطع NAT خاص يمكنه الاتصال بالعالم الخارجي عبر المضيف.&lt;/p>
&lt;hr>
&lt;h2 id="5-حالات-الاستخدام-ودليل-الاختيار-العملي">5. حالات الاستخدام ودليل الاختيار العملي
&lt;/h2>&lt;p>بناءً على الاختلافات في البنية والأداء التي تمت مناقشتها حتى الآن، سنحدد في أي المواقف يجب اعتماد أي تقنية.&lt;/p>
&lt;h3 id="51-السيناريوهات-التي-يجب-فيها-اختيار-wsl2">5.1. السيناريوهات التي يجب فيها اختيار WSL2
&lt;/h3>&lt;p>تم تصميم WSL2 خصيصاً لـ &amp;ldquo;تحسين إنتاجية المطورين&amp;rdquo;. إنه مثالي للاستخدامات التالية:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>تطوير الويب وتطوير السحابة الأصلية (Cloud-Native)&lt;/strong>: تطوير الحاويات باستخدام Docker Desktop (نهاية WSL2 الخلفية - Backend) أو Podman.&lt;/li>
&lt;li>&lt;strong>استخدام الأدوات المخصصة لنظام Linux&lt;/strong>: إذا كنت تستخدم بشكل يومي bash و grep و awk و sed، أو مجمعات GCC أو Clang لنظام Linux.&lt;/li>
&lt;li>&lt;strong>تطبيقات واجهة المستخدم الرسومية (WSLg)&lt;/strong>: عندما تريد تشغيل تطبيقات X11/Wayland الخاصة بـ Linux بسلاسة على سطح مكتب Windows.&lt;/li>
&lt;li>&lt;strong>التعلم الآلي وتطوير الذكاء الاصطناعي&lt;/strong>: التدريب السريع للنماذج باستخدام TensorFlow أو PyTorch مع الاستفادة من ميزة التمرير المباشر لوحدة معالجة الرسومات (GPU Passthrough) (مثل NVIDIA CUDA على WSL).&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>ملاحظة&lt;/strong>: قد تواجه قيوداً إذا كنت ترغب في تخصيص النواة بدقة، أو إذا كنت تبني خدمات معقدة تعتمد بشكل كبير على systemd (على الرغم من أن systemd مدعوم حالياً، إلا أنه قد يكون معطلاً أو مقيداً بشكل افتراضي).&lt;/p>
&lt;h3 id="52-السيناريوهات-التي-يجب-فيها-اختيار-hyper-v">5.2. السيناريوهات التي يجب فيها اختيار Hyper-V
&lt;/h3>&lt;p>يهدف Hyper-V إلى &amp;ldquo;المحاكاة الافتراضية للبنية التحتية والعزل الكامل&amp;rdquo;. وهو ضروري للاستخدامات التالية:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>تشغيل أجهزة Windows الافتراضية&lt;/strong>: عند تشغيل إصدارات مختلفة من Windows (مثل Windows Server أو إصدار أقدم من Windows 10) كبيئة اختبار.&lt;/li>
&lt;li>&lt;strong>المحاكاة الافتراضية المتداخلة (Nested Virtualization)&lt;/strong>: عندما تريد تشغيل أجهزة افتراضية (Hyper-V أو KVM) داخل جهاز افتراضي آخر. لا غنى عنه لبيئات التحقق الخاصة بمهندسي البنية التحتية.&lt;/li>
&lt;li>&lt;strong>متطلبات الشبكة المتقدمة&lt;/strong>: عندما تحتاج إلى التحكم الصارم في تكوين الشبكة، مثل اتصالات الجسر الخارجية (الانضمام إلى نفس الشبكة المحلية LAN)، ووضع علامات VLAN (VLAN Tagging)، وتعيين بطاقات شبكة (NICs) متعددة.&lt;/li>
&lt;li>&lt;strong>اللقطات (نقاط الفحص - Checkpoints)&lt;/strong>: القدرة على حفظ حالة الجهاز الافتراضي في نقطة زمنية معينة والتراجع (Rollback) إليها على الفور في أي وقت. مفيد للغاية للاختبارات المدمرة للبرامج أو تحليل البرامج الضارة.&lt;/li>
&lt;li>&lt;strong>تخصيص الموارد الثابتة&lt;/strong>: عندما تريد تحديد عدد أنوية وحدة المعالجة المركزية وسعة الذاكرة بدقة، وتقليل التأثير على نظام التشغيل المضيف.&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="6-دراسة-إنتاجية-الإدخالالإخراج-باستخدام-النموذج-الرياضي-الملحق">6. دراسة إنتاجية الإدخال/الإخراج باستخدام النموذج الرياضي (الملحق)
&lt;/h2>&lt;p>كمهندس أنظمة، عند تقييم حدود أداء الإدخال/الإخراج لكليهما، من المهم أن تفهم نظرياً العلاقة بين الإنتاجية (Throughput) $S$ وحجم الكتلة (Block Size) $B$.&lt;/p>
&lt;p>إنتاجية نقل البيانات $S$ هي كمية البيانات المنقولة لكل وحدة زمنية، ويمكن تمثيلها بالنموذج التالي:&lt;/p>
$$ S(B) = \frac{B}{L_{setup} + \frac{B}{R_{max}}} $$&lt;ul>
&lt;li>$B$: حجم الكتلة (بايت - Bytes)&lt;/li>
&lt;li>$L_{setup}$: الكمون الثابت (Fixed Latency) المرتبط بإعداد طلب الإدخال/الإخراج وتبديل السياق&lt;/li>
&lt;li>$R_{max}$: أقصى عرض نطاق ترددي للأجهزة في النسخ ونقل الأجهزة&lt;/li>
&lt;/ul>
&lt;p>في الوصول إلى الملفات عبر بروتوكول 9P في WSL2، يكون $L_{setup}$ كبيراً جداً (بسبب اتصال المآخذ (Socket) والتسلسل/إلغاء التسلسل للبروتوكول). لذلك، عندما يكون حجم الكتلة $B$ صغيراً (قراءة وكتابة كميات كبيرة من الملفات الصغيرة بحجم عدة كيلوبايتات)، يصبح تأثير $L_{setup}$ في المقام هو السائد، وتنخفض الإنتاجية $S$ بشكل كبير.
على العكس من ذلك، في الوصول إلى VHDX عبر VMBus في Hyper-V، يتم تحسين $L_{setup}$ إلى مستوى قريب من مقاطعات الأجهزة، مما يتيح الحفاظ على IOPS عالي حتى مع الكتل الصغيرة.&lt;/p>
&lt;p>هذا الواقع الرياضي هو الأساس المنطقي لأفضل الممارسات القائلة: &amp;ldquo;في WSL2، يجب ألا تضع ملفات مشروعك على جانب Windows&amp;rdquo;.&lt;/p>
&lt;hr>
&lt;h2 id="7-الخلاصة-تقنيتا-محاكاة-افتراضية-تتعايشان-معا">7. الخلاصة: تقنيتا محاكاة افتراضية تتعايشان معاً
&lt;/h2>&lt;p>لا يعني Hyper-V و WSL2 أن أحدهما أفضل من الآخر، بل هما &lt;strong>&amp;ldquo;حلان ذوا أهداف مختلفة&amp;rdquo;&lt;/strong>.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>WSL2&lt;/strong> هو &amp;ldquo;أداة التكامل الأفضل&amp;rdquo; لكسر قوقعة نظام التشغيل Windows وتقديم نظام Linux البيئي بسلاسة وسرعة إلى مستخدمي Windows. ليس من المبالغة القول إنها بيئة واجهة سطر الأوامر (CLI) المثلى للمطورين.&lt;/li>
&lt;li>&lt;strong>Hyper-V&lt;/strong> هو &amp;ldquo;هايبرفايزر متكامل&amp;rdquo; يجلب قدرات العزل والإدارة القوية المزروعة في مراكز بيانات المؤسسات إلى سطح المكتب. لا يوجد مثيل له في بناء الشبكات واختبار نظام تشغيل Windows ومحاكاة بيئات البنية التحتية.&lt;/li>
&lt;/ul>
&lt;p>في بيئات Windows الحديثة، لا تتنافس هاتان التقنيتان مع بعضهما البعض، بل تتعايشان بشكل جميل على نفس منصة الأجهزة الافتراضية. من خلال استخدامهما في المكان المناسب حسب الغرض، سيصبح Windows أقوى محطة عمل هندسية وأكثرها مرونة في العالم.&lt;/p></description></item><item><title>خطوات بناء بيئة تطوير محلية قابلة لإعادة الإنتاج باستخدام Docker</title><link>http://kenji.blog/ar/p/docker-reproducible-local-dev-environment/</link><pubDate>Sun, 13 Sep 2026 01:00:00 +0900</pubDate><guid>http://kenji.blog/ar/p/docker-reproducible-local-dev-environment/</guid><description>&lt;img src="http://kenji.blog/p/docker-reproducible-local-dev-environment/img/eyecatch.jpg" alt="Featured image of post خطوات بناء بيئة تطوير محلية قابلة لإعادة الإنتاج باستخدام Docker" />&lt;h2 id="1-مقدمة-التخلص-من-مشكلة-إنها-تعمل-على-جهازي">1. مقدمة: التخلص من مشكلة &amp;ldquo;إنها تعمل على جهازي&amp;rdquo;
&lt;/h2>&lt;p>في مجال تطوير البرمجيات، لطالما كانت مشكلة &amp;ldquo;إنها تعمل على جهازي (It works on my machine)&amp;rdquo; الناتجة عن اختلاف البيئات بين المطورين سبباً في إهدار الكثير من الوقت في العديد من المشاريع. فالاختلافات في أنظمة التشغيل، وإصدارات اللغات المثبتة، واعتماديات المكتبات، وتعارض الأدوات المثبتة عالمياً، تجعل البيئة المحلية دائماً عرضة لـ &amp;ldquo;عدم يقين الحالة&amp;rdquo;.&lt;/p>
&lt;p>الحل الجذري لهذه التحديات يكمن في تقنية الحاويات مثل &lt;strong>Docker&lt;/strong>، ونموذج &lt;strong>البنية التحتية ككود (IaC)&lt;/strong>. من خلال تحويل بيئة التطوير المحلية إلى حاويات، يمكن تحقيق العزل على مستوى نظام التشغيل، مما يسمح بإدارة إصدارات البيئة نفسها جنباً إلى جنب مع قاعدة الكود.&lt;/p>
&lt;p>في هذا المقال، سنشرح بالتفصيل وبشكل شامل الخطوات اللازمة لبناء &lt;strong>&amp;ldquo;بيئة تطوير محلية قابلة لإعادة الإنتاج بحيث تكون متطابقة تماماً بغض النظر عمن يقوم بتشغيلها، أو متى، أو على أي جهاز&amp;rdquo;&lt;/strong> باستخدام Docker، و Docker Compose، و VSCode DevContainers. كما سنتناول الآليات التقنية العميقة الكامنة وراءها، مع دمج وجهات نظر رياضية.&lt;/p>
&lt;hr>
&lt;h2 id="2-التوافق-بين-البنية-التحتية-ككود-iac-وتقنية-الحاويات">2. التوافق بين البنية التحتية ككود (IaC) وتقنية الحاويات
&lt;/h2>&lt;h3 id="مبادئ-iac-وتطبيقها-على-البيئة-المحلية">مبادئ IaC وتطبيقها على البيئة المحلية
&lt;/h3>&lt;p>البنية التحتية ككود (IaC) هي نهج لإدارة وتجهيز إعدادات البنية التحتية من خلال ملفات تعريف قابلة للقراءة آلياً، بدلاً من العمليات اليدوية. تشمل المبادئ الأساسية لـ IaC العناصر التالية:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>النهج التعريفي (Declarative Approach)&lt;/strong>: تحديد &amp;ldquo;ما يجب أن تكون عليه الحالة النهائية&amp;rdquo; بدلاً من &amp;ldquo;كيفية تغيير الحالة&amp;rdquo;.&lt;/li>
&lt;li>&lt;strong>الاستقواء (Idempotency)&lt;/strong>: ضمان الحصول على نفس النتيجة (الحالة) دائماً بغض النظر عن عدد مرات تشغيل السكربت.&lt;/li>
&lt;li>&lt;strong>التحكم في الإصدارات (Version Control)&lt;/strong>: حفظ حالة البنية التحتية ككود في أنظمة التحكم في الإصدارات (VCS) مثل Git، مما يتيح تتبع سجل التغييرات والمراجعة من قبل النظراء.&lt;/li>
&lt;/ol>
&lt;p>تطبيق IaC في بيئة التطوير المحلية يعني كتابة &amp;ldquo;الحالة المثالية&amp;rdquo; لبيئة التطوير ككود باستخدام ملفات مثل &lt;code>Dockerfile&lt;/code>، و &lt;code>docker-compose.yml&lt;/code>، و &lt;code>devcontainer.json&lt;/code>. وهذا يحقق تجربة تأهيل (Onboarding) سلسة للأعضاء الجدد في الفريق، حيث يمكنهم بدء التطوير فوراً بمجرد استنساخ المستودع وتشغيل أمر واحد.&lt;/p>
&lt;h3 id="ميزات-النواة-kernel-التي-تدعم-تقنية-الحاويات">ميزات النواة (Kernel) التي تدعم تقنية الحاويات
&lt;/h3>&lt;p>تختلف تقنية الحاويات عن المحاكاة الافتراضية القائمة على الهايبرفايزر (مثل الأجهزة الافتراضية VM)، حيث إنها تقنية محاكاة افتراضية خفيفة الوزن تقوم بعزل العمليات (Isolation) مع مشاركة نواة نظام التشغيل المضيف. ولتحقيق ذلك، يتم استخدام الميزات التالية من نواة Linux بشكل أساسي:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>مساحات الأسماء (Namespaces)&lt;/strong>: توفر عرضاً مستقلاً لموارد النظام (مثل PID، والشبكة، ونقاط التركيب (Mount points)، والمستخدمين) لكل عملية.&lt;/li>
&lt;li>&lt;strong>مجموعات التحكم (Cgroups - Control Groups)&lt;/strong>: تقوم بتقييد وتخصيص الموارد المادية (مثل المعالج CPU، والذاكرة، وإدخال/إخراج القرص I/O) التي يمكن للعملية استخدامها.&lt;/li>
&lt;li>&lt;strong>نظام ملفات الاتحاد (UnionFS - Union File System)&lt;/strong>: تقنية تقوم بدمج عدة أشجار أدلة (طبقات) بشكل شفاف لتبدو كنظام ملفات واحد. تعتمد طبقات صور Docker على هذه التقنية.&lt;/li>
&lt;/ul>
&lt;p>دعونا ننظر في النموذج الرياضي لتقييد الموارد. بفرض أن السعة الإجمالية لذاكرة الجهاز المضيف هي $M_{\text{total}}$، وحد الذاكرة لكل حاوية من الحاويات الـ $n$ التي تعمل على المضيف هو $m_i$. فإن الشرط الضروري لعمل النظام باستقرار، مع أخذ الذاكرة الأساسية $M_{\text{os}}$ التي يستهلكها نظام التشغيل المضيف والعمليات الأخرى في الاعتبار، يمكن التعبير عنه بالمتراجحة التالية:&lt;/p>
$$ \sum_{i=1}^{n} m_i \le M_{\text{total}} - M_{\text{os}} $$&lt;p>من خلال الاستخدام الصارم لـ Cgroups لتعريف $m_i$ لكل حاوية، يمكننا منع انهيار الحاويات الأخرى أو النظام المضيف بأكمله بواسطة OOM (Out Of Memory) Killer عندما تتسبب حاوية معينة في تسرب للذاكرة.&lt;/p>
&lt;hr>
&lt;h2 id="3-التصميم-الفعال-لـ-dockerfile-إتقان-البناء-متعدد-الطبقات-multi-stage-build">3. التصميم الفعال لـ Dockerfile: إتقان البناء متعدد الطبقات (Multi-stage Build)
&lt;/h2>&lt;p>الخطوة الأولى نحو بيئة قابلة لإعادة الإنتاج هي تصميم &lt;code>Dockerfile&lt;/code> الذي يحدد بيئة تشغيل التطبيق. هنا، باستخدام Python (FastAPI) كمثال، سنشرح أفضل الممارسات لكتابة Dockerfile آمن وخفيف الوزن بالاستفادة من &lt;strong>البناء متعدد الطبقات (Multi-stage Build)&lt;/strong>.&lt;/p>
&lt;p>البناء متعدد الطبقات هو تقنية تستخدم تعليمات &lt;code>FROM&lt;/code> متعددة داخل &lt;code>Dockerfile&lt;/code> واحد لفصل بيئة البناء (التي تحتوي على بيئة ثقيلة مثل المترجمات وأدوات التطوير) عن بيئة التشغيل (بيئة خفيفة الوزن تحتوي فقط على المخرجات الضرورية).&lt;/p>
&lt;h3 id="مثال-عملي-لـ-dockerfile-لتطبيق-python-fastapi">مثال عملي لـ Dockerfile لتطبيق Python FastAPI
&lt;/h3>&lt;p>الكود أدناه هو مثال متقدم لـ &lt;code>Dockerfile&lt;/code> يجمع بين إدارة الاعتماديات باستخدام Poetry والبناء متعدد الطبقات.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;span class="lnt">50
&lt;/span>&lt;span class="lnt">51
&lt;/span>&lt;span class="lnt">52
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-dockerfile" data-lang="dockerfile">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># ---------------------------------------------------------&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># Stage 1: Builder (بيئة البناء)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># ---------------------------------------------------------&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="s"> python:3.11-slim AS builder&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># إعداد متغيرات البيئة الضرورية&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ENV&lt;/span> &lt;span class="nv">PYTHONUNBUFFERED&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="nv">PYTHONDONTWRITEBYTECODE&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="nv">POETRY_VERSION&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span>.6.1 &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="nv">POETRY_HOME&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;/opt/poetry&amp;#34;&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="nv">POETRY_VIRTUALENVS_IN_PROJECT&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">true&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="nv">POETRY_NO_INTERACTION&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># تثبيت الحزم المعتمدة&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">RUN&lt;/span> apt-get update &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> apt-get install -y --no-install-recommends &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> curl build-essential &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> curl -sSL https://install.python-poetry.org &lt;span class="p">|&lt;/span> python3 - &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> apt-get clean &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> rm -rf /var/lib/apt/lists/*&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ENV&lt;/span> &lt;span class="nv">PATH&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$POETRY_HOME&lt;/span>&lt;span class="s2">/bin:&lt;/span>&lt;span class="nv">$PATH&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">WORKDIR&lt;/span>&lt;span class="s"> /app&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># نسخ ملفات الاعتماديات وتثبيتها&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">COPY&lt;/span> pyproject.toml poetry.lock ./&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">RUN&lt;/span> poetry install --no-root --only main&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># ---------------------------------------------------------&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># Stage 2: Runtime (بيئة التشغيل)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># ---------------------------------------------------------&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="s"> python:3.11-slim AS runtime&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ENV&lt;/span> &lt;span class="nv">PYTHONUNBUFFERED&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="nv">PYTHONDONTWRITEBYTECODE&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="nv">PATH&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;/app/.venv/bin:&lt;/span>&lt;span class="nv">$PATH&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># إنشاء مستخدم غير متميز بحد أدنى من الصلاحيات&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">RUN&lt;/span> groupadd -r appuser &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> useradd -r -g appuser appuser&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">WORKDIR&lt;/span>&lt;span class="s"> /app&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># نسخ البيئة الافتراضية (الاعتماديات) فقط من بيئة البناء&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">COPY&lt;/span> --from&lt;span class="o">=&lt;/span>builder --chown&lt;span class="o">=&lt;/span>appuser:appuser /app/.venv /app/.venv&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># نسخ كود التطبيق&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">COPY&lt;/span> --chown&lt;span class="o">=&lt;/span>appuser:appuser ./src /app/src&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># التبديل إلى المستخدم غير المتميز&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">USER&lt;/span>&lt;span class="s"> appuser&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># الأمر الافتراضي عند تشغيل الحاوية&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ENTRYPOINT&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;uvicorn&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;src.main:app&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;--host&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;0.0.0.0&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;--port&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;8000&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="التقييم-الرياضي-لحجم-الصورة-بسبب-البناء-متعدد-الطبقات">التقييم الرياضي لحجم الصورة بسبب البناء متعدد الطبقات
&lt;/h3>&lt;p>لنفترض أن حجم الصورة عند بنائها في مرحلة واحدة هو $S_{\text{single}}$، وحجم الصورة عند تطبيق البناء متعدد الطبقات هو $S_{\text{multi}}$. يتم حساب معدل تقليل الحجم $R$ على النحو التالي:&lt;/p>
$$ R = \left( 1 - \frac{S_{\text{multi}}}{S_{\text{single}}} \right) \times 100 \ (\%) $$&lt;p>على سبيل المثال، لنفترض أن $S_{\text{single}}$ يحتوي على الصورة الأساسية لنظام التشغيل (حوالي 110 ميجابايت)، وحزم التطوير (مثل gcc حوالي 150 ميجابايت)، وبرنامج Poetry (حوالي 40 ميجابايت)، والمكتبات المعتمدة للمشروع (حوالي 80 ميجابايت)، والكود المصدري (حوالي 5 ميجابايت)، ليصبح المجموع 385 ميجابايت.
في المقابل، في $S_{\text{multi}}$، يتم نسخ المكتبات المعتمدة (80 ميجابايت) والكود المصدري (5 ميجابايت) فقط إلى الصورة الأساسية (110 ميجابايت)، ليصبح المجموع 195 ميجابايت.&lt;/p>
$$ R = \left( 1 - \frac{195}{385} \right) \times 100 \approx 49.35\% $$&lt;p>بهذه الطريقة، من خلال اعتماد البناء متعدد الطبقات، يمكن تقليل حجم الصورة إلى النصف تقريباً. هذا الانخفاض في الحجم يؤدي مباشرة إلى تسريع وقت السحب (Pull) من السجل (Registry)، وتوفير مساحة القرص، وتحسين الأمان من خلال تقليص مساحة الهجوم (Attack Surface).&lt;/p>
&lt;hr>
&lt;h2 id="4-تنسيق-حاويات-متعددة-باستخدام-docker-compose">4. تنسيق حاويات متعددة باستخدام Docker Compose
&lt;/h2>&lt;p>في تطوير تطبيقات الويب الحديثة، من الشائع استخدام بنية الخدمات المصغرة (Microservices Architecture) حيث تتعاون مكونات متعددة مثل خوادم الويب، وقواعد البيانات، وخوادم التخزين المؤقت (Cache). نستخدم &lt;code>docker-compose.yml&lt;/code> لإدارة هذه المكونات بشكل مركزي في البيئة المحلية.&lt;/p>
&lt;p>في هذا المثال، سنقوم ببناء نظام محلي يتكون من 3 طبقات: &amp;ldquo;الويب (FastAPI)&amp;quot;، و &amp;ldquo;قاعدة البيانات (PostgreSQL)&amp;quot;، و &amp;ldquo;التخزين المؤقت (Redis)&amp;rdquo;.&lt;/p>
&lt;h3 id="مخطط-البنية-mermaid">مخطط البنية (Mermaid)
&lt;/h3>&lt;p>يوضح المخطط التالي العلاقة بين الحاويات، والشبكة، ووحدات التخزين (Volumes) على الجهاز المحلي.&lt;/p>
&lt;pre class="mermaid">
graph TD
User[&amp;#34;الجهاز المضيف (المتصفح/curl)&amp;#34;] --&amp;gt;|Localhost:8000| Web[&amp;#34;حاوية FastAPI للويب&amp;#34;]
subgraph &amp;#34;شبكة Docker Bridge (app-network)&amp;#34;
Web --&amp;gt;|Port 5432| DB[&amp;#34;حاوية PostgreSQL&amp;#34;]
Web --&amp;gt;|Port 6379| Redis[&amp;#34;حاوية Redis&amp;#34;]
end
DB --&amp;gt; Volume1[&amp;#34;وحدة تخزين مسماة (postgres_data)&amp;#34;]
Redis --&amp;gt; Volume2[&amp;#34;وحدة تخزين مسماة (redis_data)&amp;#34;]
HostDir[&amp;#34;كود المصدر في المضيف (./src)&amp;#34;] -.-&amp;gt;|Bind Mount| Web
&lt;/pre>
&lt;h3 id="تنفيذ-وشرح-تفصيلي-لـ-docker-composeyml">تنفيذ وشرح تفصيلي لـ docker-compose.yml
&lt;/h3>&lt;p>فيما يلي مثال على &lt;code>docker-compose.yml&lt;/code> متين وقابل للاستخدام في بيئات التطوير العملية.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;span class="lnt">50
&lt;/span>&lt;span class="lnt">51
&lt;/span>&lt;span class="lnt">52
&lt;/span>&lt;span class="lnt">53
&lt;/span>&lt;span class="lnt">54
&lt;/span>&lt;span class="lnt">55
&lt;/span>&lt;span class="lnt">56
&lt;/span>&lt;span class="lnt">57
&lt;/span>&lt;span class="lnt">58
&lt;/span>&lt;span class="lnt">59
&lt;/span>&lt;span class="lnt">60
&lt;/span>&lt;span class="lnt">61
&lt;/span>&lt;span class="lnt">62
&lt;/span>&lt;span class="lnt">63
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">version&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;3.8&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">services&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">web&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">build&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">context&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">target&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">runtime&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">container_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">dev_web&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;8000:8000&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">./src:/app/src:ro &lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># تركيب كود المضيف بصلاحية القراءة فقط (لإعادة التحميل السريع)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">environment&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">REDIS_URL=redis://redis:6379/0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">env_file&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">.env&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">depends_on&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">db&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">condition&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">service_healthy&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">redis&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">condition&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">service_started&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">networks&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">app-network&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;uvicorn&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;src.main:app&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;--host&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;0.0.0.0&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;--port&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;8000&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;--reload&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">db&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">postgres:15-alpine&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">container_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">dev_db&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;5432:5432&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">environment&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">POSTGRES_USER&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">postgres&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">POSTGRES_PASSWORD&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">${POSTGRES_PASSWORD}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">POSTGRES_DB&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">${POSTGRES_DB}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">postgres_data:/var/lib/postgresql/data&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">networks&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">app-network&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">healthcheck&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">test&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;CMD-SHELL&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;pg_isready -U postgres -d ${POSTGRES_DB}&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">interval&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">5s&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">timeout&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">5s&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">retries&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">5&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">redis&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">redis:7-alpine&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">container_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">dev_redis&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;6379:6379&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">redis_data:/data&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">networks&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">app-network&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;redis-server&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;--appendonly&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;yes&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">postgres_data&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">redis_data&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">networks&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app-network&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">driver&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">bridge&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="وحدات-التخزين-volumes-والاحتفاظ-بالبيانات">وحدات التخزين (Volumes) والاحتفاظ بالبيانات
&lt;/h3>&lt;p>تعتبر الحاويات من حيث المبدأ &amp;ldquo;عديمة الحالة (Stateless)&amp;rdquo; و &amp;ldquo;قصيرة العمر (Ephemeral)&amp;rdquo;. عند تدمير الحاوية، تضيع البيانات الموجودة بداخلها. للحفاظ على بيانات قاعدة البيانات أو التخزين المؤقت، يجب تركيب (Mount) مساحة من نظام ملفات الجهاز المضيف داخل الحاوية.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>تركيب الربط (Bind Mount)&lt;/strong>: يندرج تحت ذلك &lt;code>./src:/app/src:ro&lt;/code> في خدمة &lt;code>web&lt;/code> المذكورة أعلاه. يقوم بتعيين دليل معين على المضيف مباشرة داخل الحاوية. يُستخدم لعكس تعديلات الكود المحلية على الفور في الحاوية (إعادة التحميل السريع). من منظور الأمان، يُعتبر إضافة خيار &lt;code>:ro&lt;/code> (Read-Only) ممارسة مثلى لمنع الحاوية من تعديل كود المصدر الموجود على المضيف.&lt;/li>
&lt;li>&lt;strong>وحدة التخزين المسماة (Named Volume)&lt;/strong>: يندرج تحت ذلك &lt;code>postgres_data&lt;/code> و &lt;code>redis_data&lt;/code>. هي مساحات تديرها Docker داخلياً (مثل &lt;code>/var/lib/docker/volumes/&lt;/code>)، وتتميز بأداء إدخال/إخراج أفضل من تركيب الربط، وتستوعب اختلافات نظام الملفات بين أنظمة التشغيل المختلفة. يجب استخدامها دائماً لحفظ بيانات قواعد البيانات.&lt;/li>
&lt;/ul>
&lt;h3 id="الشبكات-networking-واكتشاف-الخدمات">الشبكات (Networking) واكتشاف الخدمات
&lt;/h3>&lt;p>بشكل افتراضي، يقوم Docker Compose بإنشاء شبكة Bridge خاصة لكل مشروع. هذه هي &lt;code>app-network&lt;/code> المذكورة أعلاه.
يمكن للحاويات التي تنتمي إلى نفس الشبكة تحليل الأسماء (DNS Resolution) لبعضها البعض باستخدام &amp;ldquo;اسم الخدمة (مثل: &lt;code>db&lt;/code>, &lt;code>redis&lt;/code>)&amp;rdquo; كاسم مضيف بدلاً من عنوان IP.
على سبيل المثال، من حاوية الويب، يمكن الوصول إلى قاعدة البيانات باستخدام عنوان URL &lt;code>postgresql://postgres:password@db:5432/mydb&lt;/code>. يتيح ذلك التبديل السلس لوجهة الاتصال بين البيئة المحلية وبيئة الإنتاج باستخدام متغيرات البيئة دون تعديل كود البنية التحتية.&lt;/p>
&lt;h3 id="فحص-الصحة-healthcheck-والتحكم-في-ترتيب-بدء-التشغيل">فحص الصحة (Healthcheck) والتحكم في ترتيب بدء التشغيل
&lt;/h3>&lt;p>يتحكم توجيه &lt;code>depends_on&lt;/code> في ترتيب بدء تشغيل الحاويات، ولكن بمجرد تحديد &lt;code>depends_on&lt;/code> فقط، ستبدأ حاوية الويب بمجرد &amp;ldquo;بدء تشغيل&amp;rdquo; حاوية قاعدة البيانات. في الواقع، قد تستغرق عملية تهيئة قاعدة البيانات (بدء عملية PostgreSQL وتجهيز الجداول) بضع ثوانٍ، مما قد يؤدي إلى حدوث خطأ عند محاولة حاوية الويب الاتصال بها.
لتجنب ذلك، يمكن تعريف &lt;code>healthcheck&lt;/code> واستخدام &lt;code>condition: service_healthy&lt;/code>، مما يسمح بالتأكد من &amp;ldquo;استعداد قاعدة البيانات لاستقبال طلبات الاتصال&amp;rdquo; قبل بدء تشغيل حاوية الويب.&lt;/p>
&lt;hr>
&lt;h2 id="5-إدارة-متغيرات-البيئة-والأمان-env">5. إدارة متغيرات البيئة والأمان (.env)
&lt;/h2>&lt;p>يعتبر تشفير المعلومات الحساسة مثل كلمات مرور قاعدة البيانات ومفاتيح API برمجياً (Hardcoding) داخل &lt;code>docker-compose.yml&lt;/code> ممارسة سيئة يجب تجنبها تماماً. بدلاً من ذلك، نستخدم ملف متغيرات البيئة &lt;code>.env&lt;/code> لحقن هذه القيم.&lt;/p>
&lt;p>قم بإنشاء ملف &lt;code>.env&lt;/code> في جذر المشروع.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-ini" data-lang="ini">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ملف .env (يجب إضافته إلى .gitignore لاستبعاده من إدارة Git)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">POSTGRES_PASSWORD&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">supersecretpassword&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">POSTGRES_DB&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">devdb&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">API_SECRET_KEY&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">dev_secret_key_12345&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>بشكل افتراضي، يقرأ Docker Compose ملف &lt;code>.env&lt;/code> الموجود في دليل التنفيذ، ويستبدل العناصر النائبة بالشكل &lt;code>${VAR_NAME}&lt;/code> في ملف YAML. تتيح هذه الطريقة إدارة إعدادات مختلفة للبيئات المتعددة مثل البيئة المحلية، وبيئة الاختبار (Staging)، وبيئة الإنتاج (Production) بشكل آمن دون تغيير كود البنية التحتية.&lt;/p>
&lt;hr>
&lt;h2 id="6-تجربة-التطوير-القصوى-باستخدام-vscode-devcontainers">6. تجربة التطوير القصوى باستخدام VSCode DevContainers
&lt;/h2>&lt;p>حتى الآن، قمنا ببناء بيئة خلفية (Backend) متينة باستخدام Docker. ولكن يمكننا اتخاذ خطوة أبعد من ذلك. من خلال استخدام ميزة &lt;strong>VSCode DevContainers (Remote - Containers)&lt;/strong>، يصبح من الممكن تشغيل الواجهة الخلفية للمحرر (VSCode) نفسه داخل الحاوية.&lt;/p>
&lt;p>بهذا، لم يعد من الضروري تثبيت Python أو Node.js على الجهاز المحلي، ويمكن تحديد كل شيء بدءاً من أدوات الفحص (Linter مثل flake8/eslint) وأدوات التنسيق (Formatter مثل black/prettier) وصولاً إلى إضافات بيئة التطوير المتكاملة (IDE) داخل قاعدة الكود لمشاركتها مع جميع أعضاء الفريق.&lt;/p>
&lt;h3 id="إعداد-devcontainerjson">إعداد devcontainer.json
&lt;/h3>&lt;p>قم بإنشاء دليل &lt;code>.devcontainer&lt;/code> في جذر المشروع، وضع بداخله ملف التكوين.&lt;/p>
&lt;p>&lt;code>.devcontainer/devcontainer.json&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Python FastAPI Dev Environment&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;dockerComposeFile&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;../docker-compose.yml&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;service&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;web&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;workspaceFolder&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;/app&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;customizations&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;vscode&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;settings&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;python.defaultInterpreterPath&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;/app/.venv/bin/python&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;python.formatting.provider&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;black&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;editor.formatOnSave&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;extensions&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ms-python.python&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ms-python.vscode-pylance&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ms-python.black-formatter&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;tamasfe.even-better-toml&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;forwardPorts&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">8000&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">5432&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">6379&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;remoteUser&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;appuser&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;postCreateCommand&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;poetry install&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>بمجرد تضمين هذا الملف في المستودع، عند فتح المشروع في VSCode، ستظهر رسالة &amp;ldquo;Reopen in Container&amp;rdquo;. وبنقرة واحدة، سيتم تشغيل جميع الحاويات اللازمة، وتثبيت الإضافات، لتكون جاهزاً لبدء البرمجة على الفور. إنها تجربة سحرية حقاً.&lt;/p>
&lt;hr>
&lt;h2 id="7-تسلسل-معالجة-الطلبات-ونمذجة-الأداء">7. تسلسل معالجة الطلبات ونمذجة الأداء
&lt;/h2>&lt;p>سنراجع دورة حياة معالجة الطلب لتطبيق الويب في بيئة التطوير المحلية المبنية من خلال مخطط التسلسل (Sequence Diagram)، وسنحلل النموذج الرياضي لأدائها.&lt;/p>
&lt;h3 id="مخطط-التسلسل-تدفق-الطلب">مخطط التسلسل (تدفق الطلب)
&lt;/h3>&lt;pre class="mermaid">
sequenceDiagram
participant Client as &amp;#34;المتصفح / VSCode&amp;#34;
participant Web as &amp;#34;FastAPI (ويب)&amp;#34;
participant Redis as &amp;#34;تخزين Redis المؤقت&amp;#34;
participant DB as &amp;#34;PostgreSQL&amp;#34;
Client-&amp;gt;&amp;gt;Web: &amp;#34;GET /api/users/123&amp;#34;
activate Web
Web-&amp;gt;&amp;gt;Redis: &amp;#34;التحقق من التخزين المؤقت للمستخدم:123&amp;#34;
activate Redis
alt &amp;#34;إصابة التخزين المؤقت Cache Hit (البيانات موجودة)&amp;#34;
Redis--&amp;gt;&amp;gt;Web: &amp;#34;إرجاع بيانات المستخدم المخزنة&amp;#34;
Web--&amp;gt;&amp;gt;Client: &amp;#34;200 OK (استجابة سريعة)&amp;#34;
else &amp;#34;خطأ التخزين المؤقت Cache Miss (البيانات غير موجودة)&amp;#34;
Redis--&amp;gt;&amp;gt;Web: &amp;#34;فارغ Null (غير موجود)&amp;#34;
deactivate Redis
Web-&amp;gt;&amp;gt;DB: &amp;#34;SELECT * FROM users WHERE id = 123&amp;#34;
activate DB
DB--&amp;gt;&amp;gt;Web: &amp;#34;إرجاع صف من قاعدة البيانات&amp;#34;
deactivate DB
Web-&amp;gt;&amp;gt;Redis: &amp;#34;تعيين بيانات user:123 (فترة الصلاحية TTL: 60 ثانية)&amp;#34;
activate Redis
Redis--&amp;gt;&amp;gt;Web: &amp;#34;موافق OK&amp;#34;
deactivate Redis
Web--&amp;gt;&amp;gt;Client: &amp;#34;200 OK (استجابة قياسية)&amp;#34;
end
deactivate Web
&lt;/pre>
&lt;h3 id="النموذج-الرياضي-لتأخير-المعالجة-latency">النموذج الرياضي لتأخير المعالجة (Latency)
&lt;/h3>&lt;p>سنقوم بنمذجة متوسط وقت معالجة الطلب $T_{\text{total}}$ رياضياً في النظام المذكور أعلاه.
نعرّف تأخير (Latency) كل عملية على النحو التالي:&lt;/p>
&lt;ul>
&lt;li>$T_{\text{net}}$: تأخير الشبكة بين العميل وحاوية الويب&lt;/li>
&lt;li>$T_{\text{app}}$: وقت المعالجة الخالص من جانب التطبيق (مثل التسلسل Serialization)&lt;/li>
&lt;li>$T_{\text{cache}}$: الوقت المستغرق في القراءة/الكتابة من وإلى Redis&lt;/li>
&lt;li>$T_{\text{db}}$: الوقت المستغرق لتنفيذ الاستعلام على PostgreSQL&lt;/li>
&lt;li>$p_{\text{miss}}$: معدل فشل التخزين المؤقت (Cache Miss Rate) ($0 \le p_{\text{miss}} \le 1$)&lt;/li>
&lt;/ul>
&lt;p>عندئذٍ، يتم تمثيل متوسط وقت الاستجابة من خلال معادلة القيمة المتوقعة التالية:&lt;/p>
$$ T_{\text{total}} = T_{\text{net}} + T_{\text{app}} + T_{\text{cache}} + p_{\text{miss}} \times (T_{\text{db}} + T_{\text{cache\_write}}) $$&lt;p>في بيئة التطوير المحلية (داخل Docker)، يكون $T_{\text{net}}$ قريباً جداً من 0، ولكن ما يستحق الانتباه هو &lt;strong>أداء الإدخال/الإخراج (I/O) أثناء تركيب الربط (Bind Mount)&lt;/strong>. خاصة عند استخدام Docker Desktop على أنظمة Windows أو macOS، يميل $T_{\text{app}}$ (وقت قراءة الكود وما إلى ذلك) إلى التضخم بسبب العبء الإضافي لمشاركة الملفات بين نظام التشغيل المضيف والجهاز الافتراضي (الحاوية). للتغلب على عنق الزجاجة هذا، يوصى بشدة باستخدام DevContainers المذكورة أعلاه لوضع كود المصدر بالكامل داخل وحدة تخزين مسماة (Named Volume)، أو استخدام بنية يعمل فيها محرك Docker أصلياً على بيئة WSL2 (Windows Subsystem for Linux 2).&lt;/p>
&lt;hr>
&lt;h2 id="8-تحسين-أداء-بناء-docker-استراتيجية-التخزين-المؤقت-للطبقات-layer-cache">8. تحسين أداء بناء Docker: استراتيجية التخزين المؤقت للطبقات (Layer Cache)
&lt;/h2>&lt;p>يتغير وقت البناء بشكل كبير اعتماداً على ما إذا كنت تفهم آلية &amp;ldquo;التخزين المؤقت للطبقات&amp;rdquo; عند كتابة Dockerfile.
تقوم Docker بإنشاء فرق لنظام الملفات (طبقة) لكل تعليمة في Dockerfile (مثل &lt;code>FROM&lt;/code>، و &lt;code>RUN&lt;/code>، و &lt;code>COPY&lt;/code>)، وتحتفظ بها كنسخة مخبأة. عند إعادة البناء، يتم إعادة استخدام النسخ المخبأة للطبقات التي لم تتغير.&lt;/p>
&lt;p>المبدأ الأساسي هو &lt;strong>&amp;ldquo;كتابة التعليمات بالترتيب من الأقل تغييراً إلى الأكثر تغييراً&amp;rdquo;&lt;/strong>.&lt;/p>
&lt;p>دعونا ننمذج تأثير تغيير كود المصدر على وقت البناء. ليكن إجمالي وقت البناء هو $T_{\text{build}}$، ووقت تنفيذ كل خطوة هو $T_{\text{layer}_i}$، ووجود أو عدم وجود إصابة للنسخة المخبأة (Cache Hit) هو قيمة منطقية $c_i \in \{0, 1\}$ (حيث 1 تعني إصابة النسخة المخبأة).&lt;/p>
$$ T_{\text{build}} = T_{\text{init}} + \sum_{i=1}^{n} (1 - c_i) \times T_{\text{layer}_i} $$&lt;p>بمجرد حدوث خطأ في النسخة المخبأة (Cache Miss) ($c_k = 0$) في الطبقة $k$، يتم إبطال التخزين المؤقت ($c_j = 0$) في جميع الطبقات اللاحقة $j > k$.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-dockerfile" data-lang="dockerfile">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># مثال سيء (نسخ كود المصدر أولاً)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">COPY&lt;/span> ./src /app/src&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">COPY&lt;/span> pyproject.toml poetry.lock ./&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">RUN&lt;/span> poetry install&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>في الحالة أعلاه، يؤدي تعديل سطر واحد فقط من الكود إلى فشل النسخة المخبأة لأمر &lt;code>COPY&lt;/code> الأول، مما يتسبب في تنفيذ &lt;code>RUN poetry install&lt;/code> الذي يستغرق وقتاً طويلاً في كل مرة.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-dockerfile" data-lang="dockerfile">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># مثال جيد (حل الاعتماديات أولاً)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">COPY&lt;/span> pyproject.toml poetry.lock ./&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">RUN&lt;/span> poetry install&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">COPY&lt;/span> ./src /app/src&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>بكتابتها بهذه الطريقة، حتى لو قمت بتغيير كود المصدر، يظل التخزين المؤقت لطبقة &lt;code>poetry install&lt;/code> فعالاً ($c_i = 1$)، مما يقلل وقت البناء بشكل كبير من عدة دقائق إلى بضع ثوانٍ.&lt;/p>
&lt;hr>
&lt;h2 id="9-استكشاف-الأخطاء-وإصلاحها-ونصائح-tips">9. استكشاف الأخطاء وإصلاحها ونصائح (Tips)
&lt;/h2>&lt;p>فيما يلي بعض المشاكل الشائعة والحلول عند تشغيل البيئة المحلية.&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>خطأ تعارض المنافذ&lt;/strong>
إذا ظهر خطأ مثل &lt;code>Bind for 0.0.0.0:8000 failed: port is already allocated&lt;/code>، فهذا يعني أن عملية أخرى على الجهاز المحلي تستخدم هذا المنفذ. يمكن تجنب ذلك بتغيير رقم المنفذ على الجانب المضيف إلى شيء مثل &lt;code>ports: - &amp;quot;8080:8000&amp;quot;&lt;/code>.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>نفاد مساحة القرص&lt;/strong>
مع استخدام Docker لفترات طويلة، تتراكم الصور ووحدات التخزين غير المستخدمة (Dangling Images / Volumes)، مما قد يستهلك عشرات الجيجابايت من مساحة القرص. يُنصح بتنظيف النظام دورياً باستخدام الأمر التالي.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker system prune -a --volumes
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;/li>
&lt;li>
&lt;p>&lt;strong>مشكلة صلاحيات الملفات&lt;/strong>
عند استخدام تركيب الربط (Bind Mount) في بيئة Linux، قد يصبح المالك (Owner) للملفات المنشأة داخل الحاوية هو &lt;code>root&lt;/code>، مما يمنع تحريرها من الجانب المضيف. يمكن حل هذه المشكلة عن طريق إنشاء مستخدم غير متميز داخل Dockerfile ومطابقة المعرف الخاص به (UID/GID) (مثل 1000:1000) مع الخاص بنظام التشغيل المضيف.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="10-الخاتمة-كيف-يساهم-التكرار-وقابلية-إعادة-الإنتاج-في-تحسين-سرعة-التطوير">10. الخاتمة: كيف يساهم التكرار وقابلية إعادة الإنتاج في تحسين سرعة التطوير
&lt;/h2>&lt;p>من خلال الجمع بين Docker، و Docker Compose، و VSCode DevContainers، يمكننا تحقيق بيئة تطوير محلية قوية &amp;ldquo;تكون متطابقة تماماً بغض النظر عمن يقوم بإعدادها&amp;rdquo;.&lt;/p>
&lt;p>إن جلب نموذج IaC إلى البيئة المحلية لا يقتصر فقط على تقليل وقت الإعداد الأولي؛ بل يزيل القلق بشأن تغييرات إعدادات البنية التحتية، ويسهل تجربة تقنيات جديدة، ويتيح الانتقال السلس إلى خطوط أنابيب التكامل المستمر والتسليم المستمر (CI/CD)، مما يؤدي إلى تحسين سرعة وجودة دورة التطوير بأكملها بشكل كبير.&lt;/p>
&lt;p>ندعوك لتطبيق أفضل الممارسات التي شرحناها في هذا المقال، مثل تحسين حجم الصورة باستخدام البناء متعدد الطبقات، والتحكم في الاعتماديات من خلال فحوصات الصحة، وكتابة Dockerfile مع مراعاة التخزين المؤقت للطبقات، لتوفير أفضل تجربة تطوير (DX: Developer Experience) في مشاريعك الخاصة.&lt;/p></description></item></channel></rss>