تو پست قبلیِ مرتبط با داکر، در رابطه با نحوه ایزولهسازی حرف زدیم، که تو یکی از کامنتهای اون پست حسین سلیمانی(به نظر من حسین یکی از آدمهای باسواد جامعه IT کشورمونه) گفتش که اگر به فایل سیستمها تو داکر هم پرداخته بشه خوبه. پیشنهاد خوبی بود و استقبال کردم ازش، نتیجه اون هم شد این چیزی که میبینید( بحث بعدی این زنجیره میشه، شبکه تو داکر که اگر عمری باشه به امید خدا درباره اونم حرف میزنیم).
داکر برای مدیریت فایلها از چندتا driver میتونه استفاده کنه:
در بین driverهای گفته شده، تیم داکر overlay2 رو به عنوان درایور ترجیحی خودش انتخاب و داکر به صورت پیشفرض از اون استفاده میکنه(مگر اینکه اون سیستمعامل از overlay2 پشتیبانی نکنه و درایورهای دیگه استفاده بشه. به عنوان مثال اوبونتو ۱۴٫۰۴ با کرنل ۳٫۱۳ و داکر نسخه ۱۸٫۰۶ از aufs استفاده میکنه).
برای اینکه بفهمیم داکر ما از چه درایوری به صورت پیشفرض استفاده میکنه از دستور زیر کمک میگیریم:
docker info | grep Storage
که خروجی اون چیزی مثل عکس پایین میشه:
همونطور که از عنوان پست مشخصه، ما تو این پست قراره تنها درمورد overlay2 حرف بزنیم، دو دلیل اصلی این رویکرد: ۱− بقیه رو بلد نیستم!!! ۲− چون درایور ترجیحی داکر هستش و ما معمولا این مورد رو تغییر نمیدیم(مثل اون ماجرای metaclassهای پایتون، به نظرم اگر کسی نیاز داشته باشه خودش متوجه میشه و میره پی ماجرا).
تغییر درایور
اینکه overlay2 درایور پیشفرضه، به این معنی نیست که نمیشه اون رو عوض کرد. برای عوض کردن اون باید فایل daemon.json(این فایل موقع راه اندازی سرویس docker استفاده میشه) رو دستکاری یا سرویس docker را با فلگ --storage-driver مدنظرمون اجرا کنیم. به عنوان مثال فرض کنیم که میخوایم از aufs برای درایور خودمون انتخاب کنیم:
sudo vim /etc/docker/daemon.json
این فایل موقع راه اندازی سرویس docker استفاده میشه. فرض کنیم که این فایل خالی بوده و قبلا تنظیمات دیگهای نداشتیم که در این صورت محتویات زیر را داخلش مینویسیم:
{ "storage-driver": "aufs" }
در انتها هم سرویس داکر رو دوباره راهاندازی میکنیم.
systemctl restart docker.service یا service docker restart
شاید بگین که چطور میشه که یکی بیاد درایور رو عوض کنه؟ ما تو دنیای نرمافزار همیشه بر اساس نیازمون موارد مختلف رو انتخاب میکنیم. مثلا ممکنه تحت شرایطی ویندوز سرور برامون بهتر از لینوکس باشه(نیاین بگین لینوکس سیستمعامل نیست، اصل موضوع رو بچسبین)، به همین دلیل این مورد هم استثنا نیست.
به عنوان مثال گفته میشه که تو PaaS استفاده از aufs بهتر overlay2 هستش چون اشتراکگذاری بهتری تو لایهها دارد(نمیدونم این حرف در عمل چطوره! اما دوستان فندق یا همچین سرویسهایی بهتر میتونند جواب بدهند).
قبل از که به خود overlay2 برسیم بزارین یک قدم برگردیم عقب و ببینیم که اصلا کار storage driverها چیه و چرا وجود دارند؟ یک حامل(container) از یک image به وجود میاد که این image هم از یک سری لایههای فقط خواندنی(readonly) تشکیل شده، در واقع حامل میاد یک لایه نوشتنی(writable) روی آخرین لایه فقط خواندنی اون image میسازد. یک image هم معمولا از یک image پایه ساخته شده و اومدیم تغییرات خودمون رو روش اعمال کردیم، ممکنه که خود این image پایه هم که از یک image دیگه استفاده کرده باشه و تغییرات خودش رو داده باشه(سوال: تا حالا به این فکر کردین که imageهای خیلی پایه مثل alpine، چطوری ساخته میشوند؟). در واقع هی لایه روی لایه گذاشته میشه، این لایهها هم یا فایلی کم یا اضافه و یا تغییر میدهند. حالا storage driverها وظیفه مدیریت این فایلها بر عهده دارند. چیزهایی مثل، آیا فلان فایل رو داریم؟ اگر آره تو کدوم لایه؟ فلان فایل تغییر کرد؟ تغییرات چی بود؟ این تغییرات رو چطوری ذخیره کنم(فقط diff یا کل فایل)؟ تفاوت storage driverها هم تو نحوه رفتار اونهاست، از نحوه پیدا کردن و واکشی فایل تا ذخیره اونها.
به صورت مختصر این استراتژی میگه اگر فایل تغییر کرد یک کپی ازش بگیر. با یک مثال موضوع رو شفافتر میکنیم، فرض کنیم که یک فایل تنظیمات داریم که این فایل در لایهی پایینی قرار داره(فقط هم قصد خوندنش رو داریم)، وقتی که میخوایم اون فایل رو بخونیم، داکر دست ما رو میگیره، میبره میرسونه به فایل اصلی و محتوای اون رو مستقیم از خود فایل بهمون میده. تا اینجا همه چیز عادیه اما تصمیممون عوض میشه و میخوایم که تغییراتی رو تو این فایل تنظیمات داشته باشیم، اینجاست که داکر میاد، یک کپی از اون فایل میگیره و تو لایه فعلی قرارش میده و تغییرات رو اعمال میکنه(در واقع یک فایل جدید داریم). از این به بعد هم هر وقت بخوایم به اون فایل دسترسی داشته باشیم(حتی فقط برای خواندن) به این فایل جدید دسترسی پیدا میکنیم. به خاطر همین موضوع بازدهی و سرعت یک حامل میتونه در مواردی که تعداد فایلی که تغییر میکنند زیاد باشه، میتونه تحت تاثیر قرار بگیرد. نکته بعدی اینه که تا جایی که میدونم overlay2 در حد فایل کار میکنه نه بلاک(داکر تو مستندات گفته الان که دارم مینویسم یادم نیست). یعنی اگر یک بخش کوچک از یک فایل بسیار بزرگ تغییر کنه، کل اون فایل کپی میشه که این هم باید برای بازدهی و سرعت مدنظر قرار بدیم(به این کپی کردن از فایل از لایه پایینتر و آوردنش تو لایه بالاتر copy up گفته میشه).
شاید سوال بشه که چرا این استراتژی انتخاب شده و خوبی اون چیه؟ جواب کوتاه میشه: امکان استفاده همزمان چندین حامل یا image از یک فایل. یعنی در واقع فایل به اشتراک گذاشته میشه و این هم باعث میشه که حجم حامل کم بشه. چرا حجم حامل کم میشه؟ چون فایل کپی نمیشه و به لایه فعلی(نوشتنی) نمیاد. حتی جالبتر اینکه این فایل بین تمام حاملهایی که به این لایه دسترسی دارند به اشتراک گذاشته میشه. یعنی چی؟ گفتیم که یک image از چندین لایه تشکیل شده(اگر flat نکرده باشیم) خب؟ حالا ما مثلا سه تا image مختلف میسازیم که تو یک لایه با هم اشتراک دارند و اون لایه هم یک فایل رو تو خودش دارد. حالا اگر ما بیایم برای هر کدوم از این imageها، حاملهای مختلفی بسازیم مادامی که این حاملها اون فایل رو تغییر ندادند، همگی از اون فایل موجود در اون لایه مشترک استفاده میکنند و این زیبایی و مهندسی کاره( یک لحظه به خودم دیدم که چقدر دارم با این موضوع حال میکنم! در این حد geek :) ).
حجم حامل
(این پاراگراف صرفا جهت اطلاعات بیشتره!) حامل دوتا حجم دارد، یکی واقعی و یکی مجازی که به ترتیب به اسمهای size و virtual size شناخته میشوند. size یعنی میزان حجمی که حامل تو دیسک اشغال کرده، از اون ور virtual size هم میشه میزان فضایی که لایههای image اشغال کرده به همراه size. یعنی virtual size همیشه بزرگتر مساوی size خواهد بود. این اشتراکگذاری که تو مرحله قبل گفتیم هم میشه باعث میشه که این virtual size کم بشه.
نکته درمورد size: چیزهایی مثل فایل log، تنظیمات اون حامل، volumnها و چیزهایی از این دست شامل size نمیشه. در واقع size فقط نشاندهنده اندازه لایه نوشتنی(witable layer) هستش.
به طور کلی موقع بحث تو دامنه overlay2 با دو موضوع upper dir و lower dir زیاد سروکار داریم، که lowerdir همون لایه پایینی و جایی که فایلهای اصلی قبلی قرار گرفتند و upperdir هم میشه همین لایهای که تو اون قرار داریم و هر تغییری تو فایلهای قبلی تو این لایه ذخیره میشه. در ادامه با شکل این موارد رو مفصلتر توضیح میدیم.
توی تصویر بالا ما یک لایه پایین داریم که سه تا فایل دارد(lowerdir) و البته لایه فعلی که میخوایم تغییراتی رو تو فایلها داشته باشیم(upperdir). File 1 دچار تغییر محتوا و به خاطر همین یک کپی ازش گرفته میشه(توجه کنید که از این به بعد هر وقت میخوایم File 1 رو بخونیم، به فایلی که تو upperdir قرار دارد دسترسی خواهیم داشت). File 2 حذف شده و توی upperdir به عنوان حذف شده نشانهگذاری میشه(این فایل تو لایه پایینی هنوز وجود دارد ولی چون گفتیم حذف کن، میاد تو upperdir اون رو به عنوان حذف شده علامت میزنه). File 3 که هیچی تغییری نداشته و تو upperdir هم نداریمش. در نهایت هم File 4 که تازه ساخته شده و فقط تو upperdir داریمش.
توی تصویر بالا احتمالا merged براتون سواله و با خودتون میگید که اون چیزیه.
همون طور که از اسمش مشخصه merged چیزیه که از ترکیب این دو لایه بدست میاد(چه جواب فاخری)! در واقع فایلها تو خودش دارد. به عبارت دیگه upperdir تغییرات رو به ما میگه و merged فایلها رو دارد. اما برای اینکه موضوع ملموستر بشه با یک مثال میریم جلو!
برای شروع فرض کنید که همچین پوشهبندی رو داریم:
حالا میایم به lowerdir سه تا فایل اضافه میکنیم:
بعد میایم دوتا دستور زیر رو اجرا میکنیم:
# mount -t overlay overlay \ -o lowerdir=/root/virgool_overlay_example/lowerdir \ -o upperdir=/root/virgool_overlay_example/client_1/upperdir \ -o workdir=/root/virgool_overlay_example/client_1/workdir \ /root/virgool_overlay_example/merged/client_1 ------------------------------------------------------------- # mount -t overlay overlay \ -o lowerdir=/root/virgool_overlay_example/lowerdir \ -o upperdir=/root/virgool_overlay_example/client_2/upperdir \ -o workdir=/root/virgool_overlay_example/client_2/workdir \ /root/virgool_overlay_example/merged/client_2
که خروجی این دوتا دستور میشه:
حالا تصمیم میگیریم که تغییراتی رو تو client_1 داشته باشیم:
# cd merged/client_1 # echo "extra info to file_1" >> file_1.txt # rm file_2.txt # echo "creating new file 4" > file_4.txt
یعنی اگر دستور ls رو بگیریم نتیجه این طوری میشه(حواستون باشه بالا cd کرده بودیم به merged/client_1):
# ls file_1.txt file_3.txt file_4.txt
حالا بیاین tree بگیریم:
نکته: چون تغییراتی تو client_2 نداشتیم پس برای اون اتفاقی نیافتاده و همچنان رفرنس فایلها به lowerdir هستش. این موضوع برای فایل file_3.txt تو client_1 هم صادقه.
همه این چیزها تو داکر هم اتفاق میوفته یعنی این lowerdir و upperdir رو برای حاملهامون داریم. برای اینکه مطمئن بشیم، کافیه از یکی از حاملهامون inspect بگیریم و به بخش GraphDriver نگاه کنیم که همچین چیزی خواهد بود:
اگر این موضوع رو دوست دارید و علاقهمندید، یک نگاه به دایرکتوری پایین بندازین، چیزهای جالبی رو میبینید:
/var/lib/docker/overlay2/
اگر دایرکتوری بالا رو یک نگاه بندازید، میبینید که آخرین عضو این لیست یک دایرکتوریه به اسم l ( همون L کوچک انگلیسی) که لایههای imageهامون تو این دایرکتوری قرار دارند. نکته اینه که دایرکتوریهای داخل l، خودشون چیزی ندارند و فقط لینک شدند(دلیل این لینک بودن، محدودیت دستور mount تو پیچ سایز آرگومان ورودی هستش)، یعنی به این شکل دایرکتوریها به این شکل هستند:
به خاطر اینکه مطلب طولانی نشه، ریز نمیشم ولی حتما یک نگاهی به دایرکتوری /var/lib/docker/overlay2 بیندازید، چیزهای باحالی پیدا میکنید. مثلا یکی از اون چیزها دایرکتوری link هستش(این link با اون لینک پاراگراف قبلی فرق داره! اینجا اسم یک دایرکتوری هستش ولی اونجا منظور بهم وصل شدن بود) که تو پایینترین لایه وجود دارد و کارش اینکه که شناسه کوتاه شده(shortened identifier) رو مشخص کنه(همون اسمی که تو دایرکتوری l هستش).
همین! امیدوارم که مفید بوده باشه(به شخصِ از مهندسی این موضوع لذت بردم).
شاد باشید و لبخند بزنین لطفا :)
منابع: