محسن نوشته

یک Container تمام عیار بدون Docker
منتشر شده در: — Aug 14, 2019

اینکه Docker چه ابزار بالغیه و چقدر کامل و کار راه بندازیه شکی توش نیست. اما چیزی که توی این پست میخوام با هم تجربه کنیم اینه که واقعا Docker برای ساختن یه دونه Container چیکار میکنه. کسب اطلاع از این موضوع فواید زیادی داره و به تسلط ما توی تکنولوژی Containerization کمک شایانی میکنه.

توی پست های قبلی من درباره NameSpace ها توی Linux صحبت کردم و واقعیت امر اینه که Docker برای رسیدن به Containerها از همین قابلیت های Linux-Kernel استفاده میکنه. چیزی که توی این پست دنبالشیم اینه که ببینیم چطور این اتفاق میفته. برای اینکه بتونیم نکات کلیدی را دنبال کنیم یه مثال درنظر میگیریم: ساختن یه Container از Alpine طوریکه توی محیط ایزوله ای از فایل سیستم و شبکه قرار داشته باشه.

حواسمون باشه که یک سیستم عامل از دو بخش اصلی ساخته میشه: یک بخش هسته سیستم عامل هستش که این بخش معمولا بین اکثر توزیع های Linux مشترک هست. اما بخش دوم مجموعه ای از برنامه ها و کتابخانه ها هستند که کمک میکنند تا هدف انتشار یک توزیع بهینه شه. مثلا یکی از اهداف توزیع Mint کاربردهای Desktop هست. بنابراین مجموعه کاملی از ابزارها براش درنظر گرفته شده تا این هدف به بهترین شکل ممکن ارضا شه.

یکی از توزیع های دیگه Linux: توزیع Alpine هست که هم توی تکنولوژی Containerization خیلی استفاده میشه هم تنوع خیلی خوبی مطابق نیاز فعلی دنیای فن آوری اطلاعات در اختیار قرار میده. مثلا یک نسخه جالب Alpine: نسخه alpine-minirootfs هستش، که کمینه ای بهینه از ابزارهای لازم برای راه اندازی یک Container را بهمون میده و جالب اینه که این توزیع بدون هسته سیستم عامل یا همون Linux خودمون منتشر میشه. ما هم توی این پست ازش استفاده میکنیم تا ببینیم Container چیه و چطور ساخته میشه.

چنتا نکته:

خوب برای شروع کارمون اول یه دونه alpine-minirootfs دانلود می کنیم و یجای مناسب بازش میکنیم. برای این کار دستورات زیر را روی CC اجرا کنید:

1
2
3
4
cd /tmp
wget http://dl-cdn.alpinelinux.org/alpine/v3.10/releases/x86_64/alpine-minirootfs-3.10.1-x86_64.tar.gz
mkdir myContainer
tar -xzf alpine-minirootfs-3.10.1-x86_64.tar.gz -C myContainer

حالا وقتشه که Container را روشن کنیم. برای اینکار با استفاده از unshare یک پراسس ایجاد میکنیم که bash را اجرا کنه و با nmpf- بهش میگیم این پراسس توی NameSpace های جدیدی از نوع: Mount ،Network و PID قرار بگیره. سوئیچ f- هم تضمین میکنه PID پراسس های ایجاد شده تویPID-Namespace از شماره 1 شروع بشن. دستور زیر را روی CC وارد اجرا کنید تا توضیح بالا رخ بده:

1
 unshare -mpnf bash

حالا ببینیم چی داریم: ما الان یک پراسس bash داریم که توی سه تا NameSpace مختلف و جدید قرار داره. باید حواسمون باشه که این پراسس ویوی کاملی از سیستم فایل کامپیوترمون داره و این به این خاطره که File Descriptors Table کامپوتر ما را به ارث برده. از طرفی از اینجا به بعد هر فایلی که این پراسس ایجاد کنه متعلق به Mount-Namespace این پراسس خواهد بود. با این مقدمه اول باید مشکل File Descriptors Table را حل کنیم. تا پراسس جدید بتونه محیط ایزوله خوشه از سیستم فایل داشته باشه. برای این کار دستور زیر را روی CC اجرا کنید :

1
mount --bind myContainer myContainer

این دستور با تجربه های قبلی ما از mount سازکار نیست چرا که مبدا و مقصد یکی هستن. اما این توی ظاهر ماجراست. واقعا مبدا و مقصد اینجا متفاوتن! myContainer اول که مبدا هست از FileDescriptors Table به ارث برده شده میاد، اما myContainer دوم از دارایی های Container هستش. لذا این دستور، alpile-rootfs را برامون mount میکنه.

اما اگه حالا دستور ls را روی CC اجرا کنیم، می بینیم اونقدر هم FileSystem کانتینرمون از FileSystem هاستون ایزوله نیست. برای اینکار باید پراسسی که با unshare ایجاد کردیم را مجبور کنیم / (روت) متفاوتی (مطابق چیزی که ما میخواهیم: alpile-rootfs) را ببینه. به این کار میگیم: Root Pivot. دستورات زیر را روی کنسول CC وارد کنید:

1
2
3
cd myContainer
mkdir old_fs
pivot_root . old_fs

دستور pivot_root فایل سیستم قبلی را به old_fs منتقل میکنه و فایل سیستم جاری (.) با alpile-rootfs را بعنوان فایل سیستم اصلی یا rootfs مونت می کنه.

حالا اگه دستور زیر را روی کنسول CC اجرا کنید میبینید که فایل سیستم Container ما ایزوله شده:

1
ls /

تنظیم بعدی مربوط به فایل سیستم proc هستش. اینکه این فایل سیستم چیه و چه کاربردی داره خارج حوصله این متنه اما یادمون باشه که Kernel اطلاعات همه پراسس ها را توش نگهداری و بروز میکنه. و مثلا وقتی ما دستوراتی مثل top یا ps را اجرا میکنیم این اطلاعات مورد استفاده قرار میگیره. اگه الان روی کنسول دستور ps را اجرا کنید خواهید دید که همه پراسس های مربوط به کامپیوتر شما لیست میشه. برای اینکه فایل سیستم proc مختص خود Container ایجاد شه باید دستور زیر را روی کنسول CC اجرا کنیم:

1
mount -t proc none /proc

در آخر هم لازمه old_fs را umount کنیم لذا دستور زیر را روی CC اجرا کنید:

1
umount -l /old_fs

تا اینجا Container ما فایل سیستم ایزوله خودشو داره و مثلا اجرای دستور */ rm -rf هیچ اثری روی فایل سیستم هاستمون نداره. حالا وقتشه که Container را به اینترنت وصل کنیم. در ادامه این کار را انجام میدیم.

درباره Network-Namespace ها توی پست قبلی توضیح دادم. اگه شناخت کافی درباره این موضوع ندارید پیشنها می کنم قبل از ادامه دادن، پست قبلی را بخونید. چیزی که میخواهیم اینه که Container ما یک کارت شبکه داشته باشه که از طریق اون بتونه با اینترنت ارتباط بگیره. برای اینکار لازمه ما به Namespace پراسس unshare یه کارت شبکه اضافه کنیم. برای اینکار دستورات زیر را روی HC اجرا کنید:

1
2
3
4
5
CPID=$(pidof unshare)
ip link add name cable-a type veth peer name eth0
ip link set eth0 netns $CPID
ip a add 192.168.1.1/24 dev cable-a
ip link set cable-a up

حالا لازمه فایروال هاستمونو تنطیم کنیم تا Container بتونه اینترنت داشته باشه. حواسمون باشه این کار را روی سیستم های لایه Production هیچوقت انجام ندیم. برای این کار دستورات زیر را روی HC اجرا کنید:

1
2
sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A POSTROUTING -j MASQUERADE

حالا بریم Container را تنظیم کنیم. دستورات زیر را روی CC اجرا کنید:

1
2
3
ip addr add 192.168.1.2/24 dev eth0
ip link set eth0 up
route add default gw 192.168.1.1 eth0

این دستورات به کارت شبکه eth0 کانتینر IP میده و روشنش میکنه. لازمه DNS هم برای Container تنظیم شه. بنابراین فایل resolv.conf را روی CC باز کنید و مطابق زیر DNS را تنظیم کنید:

1
2
vi /etc/resolv.conf
   nameserver 4.2.2.4

قبل اینکه بخواهیم از ‍‍‍Container‍ ایجاد شده استفاده کنیم یه نکته را یادآوری کنم: یادتونه ما با استفاده از unshare پراسسی ایجاد کردیم که bash را ران کرد. تا الان ما هر کاری انجام دادیم روی این bash بود اما باید بدونیم که Alipine اصلا bash نداره. بنابراین آخرین کاری که لازمه انجام بدیم اینه که بجای bash که به هاستمون تعلق داره از sh که به Container مون تعلق داره استفاده کنیم. برای اینکار دستور زیر را روی CC وارد کنید:

1
/usr/sbin/chroot / /bin/sh

خوب حالا Container ما هم FileSystem ایزوله و هم Network ایزوله خودشو داره. مثلا میتونیم دستورات زیر را راوی CC اجرا کنیم:

1
2
3
ping google.com
apk add bmon
bmon

لازمه تاکید کنم توی متن من بعضی جاها از واژگانی مثل: کارت شبکه و … استفاده کردم و یا گاها خیلی انتزاعی صحبت کردم. حواسمون باشه اینکار صرفا برای ساده و روان شدن موضوع هستش تا همه یتونیم روی موضوع اصلی تمرکز بگیریم. علاوه بر این تاکید میکنم با این روش برای لایه Production هیچوقت Container نسازید، نیت من از ارائه این روش صرفا اینه که با هم ببینیم یه Container ساده چطور ایجاد میشه و کار میکنه.

پیروز باشید.