از وقتی که Firecracker توسط آمازون منتشر شد همیشه دوست داشتم یه نگاه مفهومی بهش بندازم و ببینم چرا تصمیم گرفتن چرخ رو از اول اختراع کنن!؟ هرچیزی که توسط غولی مثل آمازون قراره ارائه بشه باید بسیار سریع و امن باشه و طبیعتا وقتی تصمیم میگیرن به جای استفاده از راهحل های موجود روش خودشون رو پیاده سازی کنن، موضوع جذاب تر هم میشه.
به طور خلاصه، این ابزار موتور AWS Lambda هست که برای رایانش بیسرور استفاده میشه. مزیتش اینه که دقیقا بر اساس منابعی که توسط کد شما مصرف شده هزینه پرداخت میکنید و به حضور یک sysadmin توی تیم نیاز نیست چونکه تمام قضیه از بالا آوردن سیستم عامل به صورت مجازی و تنظیم فایروال و ... همشون توسط شرکت ارائه دهنده این خدمت انجام میشه. -- تاحالا توی ایران سرویسی برای FaaS ندیدم.
ابزاری که آمازون ارائه کرده میتونه لینوکس رو توی 125 میلی ثانیه اجرا کنه و جالب اینجاست که محیطهای lambda به روش «مجازی سازی سختافزار» ازهم جدا شدن. -- با شنیدن این عبارت شاید یاد Virtual Box بیوفتین، بله! منظورم همونه! اما خیلی جذاب تر.
قبل از اینکه بخوام Firecracker رو توضیح بدم اولش لازمه که بفهمیم با مجازی ساز چطوری ویندوز رو توی گنو/لینوکس اجرا میکنن؟ یا چطوری میتونن MacOS رو اجرا کنن بدون اینکه صاحب مک بوک باشن.
اینها رو عمدا مثال زدم چون سورس کد سیستم عامل در دسترس عموم نیست. درواقع چطوری میتونیم درایورها، دستگاهها و غیره رو برای یک سیستم عاملی که عملا نمیتونیم دقیقا بفهمیم به چی نیاز داره رو فراهم کنیم؟
ابزاری مثل QEMU یا Virtual Box برای اجرای همچین چیزی باید روتینهای BIOS یا ماژولهای قدیمی، درایور کارت صدا، گرافیک، شبکه و ... رو شبیه سازی کنه.
درواقع شبیه سازی BIOS و حضور boot loader در این شرایط الزامیه؛ بدون این مرحله مجازی سازها نمیتونن خیلی از این سیستمعاملها رو اجرا کنن.
سکوی Firecracker فقط برای اجرای کرنل لینوکس پیاده سازی شده و برای این کار به KVM و بخش زیادی از سورس کد پروژه crosvm تکیه میکنه. درواقع KVM خودش به تنهایی سخت افزارهای چندانی رو شبیهسازی نمیکنه برای همین انتخاب بهینهای هست.
چیزی که از این مفاهیم باید برداشت کنید اینه که یک مجازی ساز «معمولا» سعی میکنه تا جایی که ممکنه به یک رایانه واقعی نزدیک باشه برای همین مجبوره گستره متنوعی از سخت افزارها رو شبیه سازی کنه و این کار خیلی سرعت اجرا رو میاره پایین! (حتی اگه از SSD هم استفاده کنید خیلی ربطی نداره هنوزم قضیه کند هست قطعا نه به اندازه وقتی که HDD دارین اما بازم 125 میلی ثانیه نیست !!!)
اما آیا لازمه «همیشه» boot loader و BIOS حضور داشته باشن؟
خب وقتی که یک رایانه معمولی اجرا میشه (من مثالم برای رایانههایی هست که همه داریم، برای معماریهای دیگه طبیعتا فرقهایی داره) پردازنده توی یک حالت 16 بیتی شروع به کار میکنه.
بایوس هم POST رو انجام میده و بعدش boot loader رو توی RAM فراخوانی میکنه و کنترل همهچیز رو میده بهش. بعد boot loader از روتینهای تعریف شده توی BIOS استفاده میکنه که متنی روی صفحه نمایش بده یا فضای ذخیره سازی رو بخونه، مشخصات سیستم رو به دست بیاره و «با کرنل ارتباط برقرار کنه». پس درواقع حضور BIOS برای خود لینوکس نیاز نیست و این boot loader هست که بهش نیاز داره.
یه جورایی حتی boot loader هم نیاز نیست. کرنل یه چیزی داره به اسم Boot Protocol که «هرپردازشی» که در زمان بوت درحال اجراست میتونه باهاش ارتباط برقرار کنه و از طریق این پروتکل بفهمه چطوری اونو اجرا کنه و ساختار دادهها رو توی RAM بچینه و در نهایت چطوری کنترل همه چیز به کرنل سپرده بشه.
پس، Firecracker خودش کرنل رو لود میکنه از طریق همین پروتکل (layout.rs - mod.rs) در نتیجه دیگه نیازی به BIOS نداریم و حضور یک boot loader عمومی که کلی edge case رو باید رعایت کنه هم الزامی نیست وقتی «هرپردازشی» بتونه با کرنل حرف بزنه. همین یک قدم در گفتار چیز سادهای به نظر میاد اما وقتی قابلیتهای Virtual Box رو ببینید درک میکنید که این مرحله چقدر در سبک شدن ماجرا تاثیر داره.
بعلاوه اینها، یک سری عملیات / دستورات وجود دارن که منجر به رخ دادن رویدادی تحت عنوان VM exit میشن. هرچی این اتفاق کمتر بیوفته مجازی ساز سریعتر عمل میکنه. به عنوان مثال دستوراتی که برای انجام عملیاتهای I/O نیاز هست مثل OUT منجر به VM exit میشن.
اما توی Firecracker برای انجام اکثر عملیاتها از MMIO استفاده میکنه به زبان ساده یعنی به جای استفاده از دستوراتی مثل IN و OUT برای انجام عملیاتهای I/O از دستورات عادی تر مثل mov استفاده میکنه. درواقع یک سری مقدار خاص توی یک قسمت ویژه از RAM قرار گرفتن که با mov کردن این مقادیر عملیاتهای I/O رو انجام میده.
وقتی آدرسهای MMIO دستکاری بشن رویداد KVM exit اتفاق میوفته. مزیتش اینه که VMM میتونه مشخص کنه کی این اتفاق انجام بشه پس کنترل بیشتری روی «زمان و تعداد دفعات رخ دادن رویداد» وجود داره.
اگر بخوام وارد جزئیات بشم به این صورته که هروقت VM exit اتفاق بیوفته، VMM بر اساس مقادیری که توی MMIO دستکاری شدن میفهمه که ورودی و خروجی چه دستگاهی بوده و بعد داده مربوطه رو به دستگاه موردنظر وصل میکنه. مثلا برای کنترل ماشین مجازی و دریافت خروجی (که به صورت متن هست) از یک کنسول سریال استفاده میکنه و اون رو به کرنل داخل ماشین مجازی وصل میکنه. (سورس کد)
مثلا اینجا رو ببینید این صفحه مربوط به job log پروسهای هست که توسط CI انجام شده این «دقیقا» خروجی کنسول هست! [من به AWS دسرسی ندارم برای همین از travis-ci مثال اوردم ولی این قسمت نمایش کنسول مفهومی یکسان هست بین مجازی سازها]. یادتون باشه کنسول در اینجا به معنی خط فرمان هست و همیشه خروجی اون صفحه نمایش نیست، قدیم کاغذ بود! این ویدیو جذاب رو ببینید.
یک قسمت جالب دیگه تعداد محدود دستگاههایی هست که Firecracker توی هر ماشین مجازی پشتیبانی میکنه. لینوکس توی معماری x86 فرض میکنه که interrupt controller و interval timer وجود دارن. این دو قطعه بخش هایی هستن که یک CPU به صورت پیشفرض اونها رو نداره پس باید شبیه سازی بشن.
شبیه به مثال کنسول، VMM میتونه این دوتا دستگاه رو به دوتا دستگاه موجود روی سیستم میزبان وصل کنه ولیکن این کار منجر به VM exit میشه. یادتونه؟ هرچقدر بیشتر توی کرنل بمونیم و یا دستورات ماشین مجازی اجرا بشن، سرعت و کارایی بالاتره تا اینکه که مجبور به VM exit بشیم.
خوشبختانه KVM یه راهی داره که میتونه چیپهای i8259 و i8254 رو داخل کرنل شبیه سازی کنه! یعنی اصلا نیازی به VM exit نیست. خودمم نمیدونم چطوری، درکش برام سخته فقط با دنبال کردن سورس کد Firecracker به KVM_CREATE_IRQCHIP و KVM_CREATE_PIT2 رسیدم که توی مستندات خودش کلی توضیحات داده دربارشون. اولی برای interrupt controller هست، دومی برای interval timer
یه سری چیزای دیگه هست که دوست دارم بدونید مثلا i8042.rs شبیه ساز کنترلر ماوس و کیبورد PS/2 هست. یا serial.rs تمام چیزیه که نیاز داره برای گرفتن ورودی و خروجی. هرچند هنوزم به کارت شبکه نیازه که برای این کار از VirtIO Net استفاده میکنه. و VirtIO Block هم که مشخصه دیگه برای دیسک و فضای ذخیره سازی لازمه. همین ! این تمام چیزاییه که نیازه برای اجرای ماشین مجازی.
چرا آمازون اصرار داشته به اینکه از ماشین مجازی استفاده کنه؟ درحالی که گوگل، یک غول دیگه تمام ناوگان cloud console رو با کانتینر اداره میکنه؟
دلیلش سادست، وقتی که از حصار یک container فرار کنید تمام API لینوکس در اختیار شماست! ولی با فرار از حصار یک ماشین مجازی دسترسی بسیار محدود تره.
این ویدیو رو ببینید درواقع یک نفر تونسته با فرار از حصار کانتینری که cloud console ارائه میکنه به سیستم عامل میزبان دسترسی پیدا کنه. اما این مسئله برای گوگل باگ امنیتی نیست چون اونها کانتینرها رو روی یک ماشین مجازی اجرا میکنن (ویدیو رو کامل ببینید قضیه طولانیه، یه کانتینر داکر در اختیار کاربر نهایی قرار داره که خود اون کانتینر توی یه کانتینر دیگه قرار گرفته !!! و بعد کل این کانتینر روی یه ماشین مجازی اجرا میشه همش به خاطر اینکه «وقتی/اگه» یکی از namespace تعریف شده فرار کرد، سطوح دیگهای برای جلوگیری وجود داشته باشه)
آمازون عاقلانه تر عمل کرده [به نظر من] و به جای درست کردن قلعه با بالش، وقت گذاشته و قضیه رو از ریشه کنترل کرده.
ناگفته نماند، توی یه کانتینر داکر با دسترسی root میتونید هر دستگاه ورودی / خروجی یا مسیر ذخیره سازی رو mount کنید. اما Firecracker کلا از دستگاههای محدودی پشتیبانی میکنه.
ولی قضیه به اینجا ختم نمیشه. تیم آمازون خیلی جدی تر به این مسئله فکر کرده، مثلا از chroot و cgroups استفاده میکنه و علاوه بر اون یک سری قوانین Seccomp هم تعریف کرده یعنی حتی پروسه Firecracker هم فقط به چیزایی که «دقیقا» لازمه دسترسی داره نه بیشتر و این حیرت انگیزه.
خب با توجه به تلاشهای آمازون و راه حلی که پیاده شده در نهایت کاملا واضحه که اختراع چرخ واقعا لازم بوده. البته ابزارهای دیگه ای هستن که کار مشابه رو انجام میدن مثلا katacontainers یه رابط Kubernetes ارائه میکنه ولیکن در عین حال هر کانتینر به صورت سخت افزاری مجزا شده. یا تلاش اینتل برای لخت کردن QEMU منجر به NEMU شد ایدش اینه که فقط از لینوکس پشتیبانی کنه برای همین خیلی از درایورها و چیزای دیگه که نیازه تا «هر سیستمعاملی» اجرا بشه رو برداشتن ولی هنوزم بوت لودر و بایوس وجود دارن منتها بهینه شدن. [البته NEMU ممکنه در آینده از windows server هم پشتیبانی کنه].
به هرحال شاید نشه گفت مخترع این قضیه آمازون هست اما تمیز ترین محصول رو قطعا اونها پیاده سازی کردن.