في هذه السلسلة من المنشورات ، سننظر بعناية في أحد المكونات الرئيسية في الحاوية - مساحات الأسماء. في هذه العملية ، سنقوم بإنشاء استنساخ أبسط docker run
- برنامجنا الخاص ، والذي سيأخذ الأمر (إلى جانب الحجج الخاصة به ، إن وجدت) عند الإدخال وسع الحاوية لتنفيذه ، بمعزل عن بقية النظام ، على غرار الطريقة التي ستنفذ بها docker run
لتشغيل من صورة .
ما هي مساحة الاسم؟
مساحة اسم Linux هي عبارة عن مجموعة من الموارد في نظام التشغيل. يمكننا التفكير في مساحة الاسم كمربع. يحتوي هذا المربع على موارد النظام التي تعتمد على نوع المربع (مساحة الاسم). يوجد حاليًا سبعة أنواع من مساحات الأسماء: Cgroups و IPC و Network و Mount و PID و User و UTS.
على سبيل المثال ، تتضمن مساحة اسم الشبكة موارد النظام المرتبطة بالشبكة ، مثل واجهات الشبكة (مثل wlan0
، eth0
) ، وجداول التوجيه ، وما إلى ذلك ، تتضمن مساحة اسم Mount الملفات والدلائل في النظام ، ويحتوي PID على معرفات العملية ، وما إلى ذلك. . وبالتالي ، يمكن أن يحتوي wlan0
من مساحة اسم الشبكة A و B (المطابقان لمربعين من نفس النوع في القياس لدينا) على موارد مختلفة - ربما يحتوي A على wlan0
، بينما يحتوي B على eth0
ونسخة منفصلة من جدول التوجيه.
لا تعتبر مساحات الأسماء بعض الميزات أو المكتبات الإضافية التي تحتاج إلى تثبيتها ، على سبيل المثال ، باستخدام مدير الحزمة apt. يتم توفيرها بواسطة kernel Linux نفسها وهي بالفعل ضرورة لتشغيل أي عملية على النظام. في أي نقطة زمنية محددة ، تنتمي أي عملية P إلى مثيل واحد تمامًا من مساحة كل نوع. لذلك ، عندما يحتاج إلى قول "تحديث جدول التوجيه في النظام" ، فإن Linux يعرض عليه نسخة من جدول توجيه مساحة الاسم الذي ينتمي إليه في تلك اللحظة.
ما هذا؟
بالتأكيد من أجل لا شيء ... بالطبع ، كنت أمزح فقط. واحدة من الخصائص الرائعة للمربعات هي أنه يمكنك إضافة وإزالة أشياء من المربع ، وهذا لن يؤثر على محتويات الصناديق الأخرى. هذه هي نفس الفكرة مع مساحات الأسماء - فقد تتعرض عملية P "للجنون" وتنفذ sudo rm –rf /
، ولكن لن تتأثر عملية Q الأخرى التي تنتمي إلى مساحة أخرى من أسماء Mount ، لأنها تستخدم نسخًا منفصلة من هذه الملفات.
لاحظ أن المورد الموجود في مساحة الاسم ليس بالضرورة نسخة فريدة. في بعض الحالات التي حدثت عن قصد أو بسبب خرق أمني ، ستحتوي مسحتان أو أكثر على نفس النسخة ، على سبيل المثال ، نفس الملف. وبالتالي ، فإن التغييرات التي تم إجراؤها على هذا الملف في مساحة اسم Mount واحدة ستكون في الواقع مرئية في جميع مساحات أسماء Mount الأخرى ، والتي تشير إليها أيضًا. لذلك ، سنتخلى عن تشبيه الدرج ، حيث لا يمكن أن يكون العنصر في صندوقين مختلفين في نفس الوقت.
تقييد هو مصدر قلق
يمكننا أن نرى مساحات الأسماء التي تنتمي إليها العملية! عادةً لنظام Linux ، تظهر كملفات في الدليل /proc/$pid/ns
لهذه العملية مع معرف العملية $pid
:
$ ls -l /proc/$$/ns total 0 lrwxrwxrwx 1 iffy iffy 0 May 18 12:53 cgroup -> cgroup:[4026531835] lrwxrwxrwx 1 iffy iffy 0 May 18 12:53 ipc -> ipc:[4026531839] lrwxrwxrwx 1 iffy iffy 0 May 18 12:53 mnt -> mnt:[4026531840] lrwxrwxrwx 1 iffy iffy 0 May 18 12:53 net -> net:[4026531957] lrwxrwxrwx 1 iffy iffy 0 May 18 12:53 pid -> pid:[4026531836] lrwxrwxrwx 1 iffy iffy 0 May 18 12:53 user -> user:[4026531837] lrwxrwxrwx 1 iffy iffy 0 May 18 12:53 uts -> uts:[4026531838]
يمكنك فتح محطة أخرى ، وتنفيذ نفس الأمر وهذا ينبغي أن يوفر لك نفس النتيجة. هذا لأنه ، كما ذكرنا سابقًا ، يجب أن تنتمي العملية إلى مساحة اسم معينة (مساحة الاسم) وإلى أن نحدد بشكل واضح أي منها ، يضيفها Linux إلى مساحات الأسماء بشكل افتراضي.
دعنا نشارك قليلا في هذا. في المحطة الثانية ، يمكننا أن نفعل شيئًا مثل هذا:
$ hostname iffy $ sudo unshare -u bash $ ls -l /proc/$$/ns lrwxrwxrwx 1 root root 0 May 18 13:04 cgroup -> cgroup:[4026531835] lrwxrwxrwx 1 root root 0 May 18 13:04 ipc -> ipc:[4026531839] lrwxrwxrwx 1 root root 0 May 18 13:04 mnt -> mnt:[4026531840] lrwxrwxrwx 1 root root 0 May 18 13:04 net -> net:[4026531957] lrwxrwxrwx 1 root root 0 May 18 13:04 pid -> pid:[4026531836] lrwxrwxrwx 1 root root 0 May 18 13:04 user -> user:[4026531837] lrwxrwxrwx 1 root root 0 May 18 13:04 uts -> uts:[4026532474] $ hostname iffy $ hostname coke $ hostname coke
يقوم أمر unshare
بتشغيل البرنامج (اختياري) في مساحة الاسم الجديدة. يخبرها العلم -u
بتشغيل bash
في مساحة الاسم UTS الجديدة. لاحظ أن عملية bash
الجديدة تشير إلى ملف uts
آخر ، بينما تظل جميع العناصر الأخرى كما هي.
عادةً ما يتطلب إنشاء مساحات أسماء جديدة وصولاً للمستخدم الخارق. unshare
، سوف نفترض أن كلا من المشاركة غير المنفذة sudo
يتم تنفيذهما باستخدام sudo
.
أحد عواقب ما قمنا به للتو هو أنه يمكننا الآن تغيير اسم مضيف النظام من عملية bash الجديدة الخاصة بنا ولن يؤثر هذا على أي عملية أخرى في النظام. يمكنك التحقق من ذلك عن طريق تشغيل hostname
في أول محطة ورؤية أن اسم المضيف لم يتغير هناك.
ولكن ما ، على سبيل المثال ، حاوية؟
نأمل الآن أن يكون لديك فكرة عما يمكن أن تفعله مساحة الاسم. يمكنك افتراض أن الحاويات هي عمليات عادية بشكل أساسي مع مساحات أسماء مختلفة عن العمليات الأخرى ، وسوف تكون على حق. في الواقع ، هذه حصة. لا يشترط وجود حاوية بدون حصص في مساحة اسم فريدة لكل نوع - يمكنها مشاركة بعضها.
على سبيل المثال ، عندما تكتب docker run --net=host redis
، كل ما عليك فعله هو إخبار العامل بأن لا ينشئ مساحة اسم شبكة جديدة لعملية redis. وكما رأينا ، سيضيف Linux هذه العملية كمشارك في مساحة اسم الشبكة الافتراضية ، مثل أي عملية عادية أخرى. وبالتالي ، من وجهة نظر الشبكة ، فإن عملية redis هي نفسها تمامًا مثل أي شخص آخر. هذا ليس مجرد خيار لتكوين الشبكة ، حيث يتيح لك docker run
إجراء مثل هذه التغييرات لمعظم مساحات الأسماء الموجودة. هذا يطرح السؤال ، ما هو الحاوية؟ هل هناك حاوية تستخدم عملية تستخدم كل مساحة الاسم الشائعة ما عدا واحدة؟ containers \ _ (ツ) _ / ¯ نموذجياً ، تأتي الحاويات مع مفهوم العزل الذي يتم تحقيقه من خلال مساحات الأسماء: كلما قل عدد مساحات الأسماء والموارد التي تشاركها العملية مع الآخرين ، كلما كانت معزولة وكل ما يهم حقًا.
عزل
في الجزء المتبقي من هذا المنشور ، سنضع الأساس لبرنامجنا ، والذي isolate
عليه isolate
. تأخذ isolate
الأمر كوسيطات وتبدأ تشغيله في عملية جديدة ، معزولة عن بقية النظام ومحدودة بمساحات الأسماء الخاصة بها. في المنشورات التالية ، سننظر في إضافة دعم لمساحات الأسماء الفردية لأمر العملية الذي يبدأ isolate
.
وفقًا للتطبيق ، سنركز على مساحات أسماء المستخدمين والشبكات ومعرف PID والشبكة. سيكون الباقي سهل التنفيذ بعد الانتهاء (في الحقيقة ، سنضيف دعم UTS هنا في التنفيذ الأولي للبرنامج). والنظر ، على سبيل المثال ، من Cgroups ، هو خارج نطاق هذه السلسلة (دراسة cgroups ، مكون آخر من الحاويات المستخدمة للتحكم في مقدار الموارد التي يمكن أن تستخدمها العملية).
يمكن أن تصبح مساحات الأسماء سريعة جدًا وهناك العديد من الطرق المختلفة التي يمكنك استخدامها عند استكشاف كل مساحة اسم ، لكن لا يمكننا تحديدها جميعًا مرة واحدة. سنناقش فقط الطرق ذات الصلة بالبرنامج الذي نقوم بتطويره. سيبدأ كل منشور ببعض التجارب في وحدة التحكم على مساحة الاسم المعنية لفهم الخطوات المطلوبة لتكوين مساحة الاسم هذه. نتيجة لذلك ، سيكون لدينا بالفعل فكرة عما نريد تحقيقه ، ومن ثم سيتبعه التنفيذ المقابل isolate
.
لتجنب التحميل الزائد للكود على المشاركات ، لن ندرج أشياء مثل الوظائف الإضافية غير الضرورية لفهم التنفيذ. يمكنك العثور على شفرة المصدر الكاملة هنا على جيثب .
تطبيق
يمكن العثور على الكود المصدري لهذا المنشور هنا . سيكون تطبيقنا المعزول برنامجًا بسيطًا يقرأ سطرًا بأمر من stdin ويقوم باستنساخ عملية جديدة تنفذها باستخدام الوسائط المحددة. سيتم تشغيل العملية المستنسخة باستخدام الأمر في مساحة الاسم الخاصة بـ UTS بنفس الطريقة التي قمنا بها مع unshare
. في المنشورات التالية ، سنرى أن مساحات الأسماء لا تعمل بالضرورة (أو على الأقل توفر عزلة) من المربع وسنحتاج إلى إجراء بعض التكوينات بعد إنشائها (ولكن قبل تشغيل الأمر فعليًا) ، بحيث يعمل الأمر بالفعل بمعزل.
تتطلب تركيبة تكوين مساحة الاسم هذه بعض التفاعل بين عملية isolate
الرئيسية والعملية الفرعية للأمر المطلوب تشغيله. نتيجة لذلك ، سيكون جزء من العمل الرئيسي هنا هو تكوين قناة الاتصال بين كلتا العمليتين - في حالتنا ، سنستخدم توجيه Linux بسبب بساطته.
نحن بحاجة إلى القيام بثلاثة أشياء:
- قم بإنشاء عملية
isolate
أساسية تقوم بقراءة البيانات من stdin. - استنساخ عملية جديدة ستقوم بتشغيل الأمر في مساحة اسم UTS الجديدة.
- قم بتكوين توجيه الإخراج بحيث تبدأ عملية تنفيذ الأمر في التشغيل فقط بعد تلقي إشارة من العملية الرئيسية بأن تكوين مساحة الاسم قد اكتمل.
هذه هي العملية الأساسية:
int main(int argc, char **argv) { struct params params; memset(¶ms, 0, sizeof(struct params)); parse_args(argc, argv, ¶ms);
لاحظ clone_flags
التي clone_flags
إلى مكالمة clone
لدينا. ترى كم هو سهل لإنشاء عملية في مساحة الاسم الخاصة بها؟ كل ما نحتاج إلى القيام به هو تعيين علامة لنوع مساحة الاسم (علامة CLONE_NEWUTS
تتوافق مع مساحة اسم UTS) ، وسيقوم Linux بالباقي.
بعد ذلك ، تتوقع عملية الأمر إشارة قبل أن تبدأ:
static int cmd_exec(void *arg) {
أخيرًا ، يمكننا محاولة تشغيل هذا:
$ ./isolate sh ===========sh============ $ ls isolate isolate.c isolate.o Makefile $ hostname iffy $ hostname coke $ hostname coke
يعد isolate
الآن أكثر من مجرد برنامج يقوم ببساطة بتشكيل الفريق (لدينا UTS يعمل لصالحنا). في المنشور التالي ، سنتخذ خطوة أخرى عن طريق فحص مساحات أسماء المستخدمين وجعل isolate
تنفيذ الأمر في مساحة اسم المستخدم الخاصة به. هناك سنرى أننا في الواقع نحتاج إلى القيام ببعض الأعمال من أجل الحصول على مساحة اسم قابلة للاستخدام حيث يمكن تنفيذ الأمر.