Docker-in-Docker یک محیط شبح داکر مجازی است که در خود کانتینر برای ساخت تصاویر کانتینر اجرا می شود. هدف اصلی از ایجاد Docker-in-Docker کمک به توسعه خود Docker بود. بسیاری از مردم از آن برای اجرای Jenkins CI استفاده می کنند. این در ابتدا طبیعی به نظر می رسد، اما پس از آن مشکلاتی ایجاد می شود که می توان با نصب Docker در یک کانتینر Jenkins CI از آنها جلوگیری کرد. این مقاله به شما می گوید که چگونه این کار را انجام دهید. اگر به راه حل نهایی بدون جزئیات علاقه دارید، کافی است بخش آخر مقاله «حل مشکل» را مطالعه کنید.
Docker-in-Docker: "خوب"
بیش از دو سال پیش در Docker قرار دادم
- هک هک;
- ساختن؛
- متوقف کردن داکر دیمون در حال اجرا.
- راه اندازی Daemon جدید Docker.
- تست
- چرخه را تکرار کنید
اگر می خواستید یک مجموعه زیبا و قابل تکرار (یعنی در یک ظرف) بسازید، آن وقت پیچیده تر می شد:
- هک هک;
- مطمئن شوید که یک نسخه فعال Docker در حال اجرا است.
- ساخت داکر جدید با داکر قدیمی.
- توقف داکر دیمون.
- یک Daemon جدید Docker را شروع کنید.
- تست؛
- متوقف کردن داکر جدید.
- تکرار.
با ظهور Docker-in-Docker، این روند ساده تر شده است:
- هک هک;
- مونتاژ + راه اندازی در یک مرحله؛
- چرخه را تکرار کنید
اینجوری خیلی بهتر نیست؟
Docker-in-Docker: "بد"
با این حال، بر خلاف تصور عمومی، Docker-in-Docker 100٪ ستاره، پونی و تک شاخ نیست. منظور من این است که چندین مسئله وجود دارد که یک توسعه دهنده باید از آنها آگاه باشد.
یکی از آنها به LSM ها (ماژول های امنیتی لینوکس) مانند AppArmor و SELinux مربوط می شود: هنگام اجرای یک کانتینر، "Docker داخلی" ممکن است سعی کند پروفایل های امنیتی را اعمال کند که "Docker خارجی" را در تضاد یا گیج کنند. این مشکل ترین مشکل برای حل در هنگام تلاش برای ادغام اجرای اصلی -پرچم امتیاز است. تغییرات من جواب داد و همه آزمایشها روی ماشین دبیان و ماشینهای مجازی آزمایشی اوبونتو انجام میشد، اما در ماشین مایکل کراسبی خراب میشد و میسوختند. من نمی توانم علت دقیق مشکل را به خاطر بیاورم، اما ممکن است به این دلیل باشد که مایک مرد عاقلی است که با SELINUX=enforce کار می کند (من از AppArmor استفاده کردم) و تغییرات من پروفایل های SELinux را در نظر نگرفته است.
Docker-in-Docker: "Evil"
مشکل دوم مربوط به درایورهای ذخیره سازی Docker است. هنگامی که Docker-in-Docker را اجرا می کنید، داکر خارجی در بالای یک سیستم فایل معمولی (EXT4، BTRFS، یا هر چیز دیگری که دارید) و داکر داخلی بالای یک سیستم کپی در نوشتن (AUFS، BTRFS، Device Mapper) اجرا می شود. و غیره). بسته به اینکه چه چیزی برای استفاده از داکر خارجی پیکربندی شده است). این ترکیبات زیادی را ایجاد می کند که کارایی ندارند. به عنوان مثال، شما نمی توانید AUFS را در بالای AUFS اجرا کنید.
اگر BTRFS را بالای BTRFS اجرا کنید، ابتدا باید کار کند، اما وقتی زیرجلدهای تو در تو وجود داشته باشند، حذف زیرجلد والد ناموفق خواهد بود. ماژول Device Mapper هیچ فضای نامی ندارد، بنابراین اگر چندین نمونه Docker آن را روی یک دستگاه اجرا کنند، همه آنها میتوانند تصاویر روی یکدیگر و روی دستگاههای پشتیبان کانتینر را ببینند (و تحت تأثیر قرار دهند). این بد است.
راه حل هایی برای حل بسیاری از این مشکلات وجود دارد. به عنوان مثال، اگر می خواهید از AUFS در Docker داخلی استفاده کنید، فقط پوشه /var/lib/docker را به یک حجم تبدیل کنید و خوب خواهید بود. Docker برخی از فضای نام های پایه را به نام های هدف Device Mapper اضافه کرده است تا اگر چندین تماس Docker روی یک دستگاه اجرا می شود، روی یکدیگر پا نگذارند.
با این حال، همانطور که از اینها می توان فهمید، چنین تنظیمی اصلاً ساده نیست
Docker-in-Docker: بدتر می شود
در مورد کش ساخت چطور؟ این نیز می تواند بسیار دشوار باشد. مردم اغلب از من می پرسند "اگر من Docker-in-Docker را اجرا می کنم، چگونه می توانم از تصاویر میزبانی شده در میزبان خود به جای بازگرداندن همه چیز به Docker داخلی خود استفاده کنم"؟
برخی از افراد مبتکر سعی کرده اند /var/lib/docker را از میزبان به یک کانتینر Docker-in-Docker متصل کنند. گاهی اوقات آنها /var/lib/docker را با چندین کانتینر به اشتراک می گذارند.
آیا می خواهید داده های خود را خراب کنید؟ زیرا این دقیقا همان چیزی است که به داده های شما آسیب می رساند!
Daemon Docker به وضوح برای دسترسی انحصاری به /var/lib/docker طراحی شده است. هیچ چیز دیگری نباید هیچ فایل داکری را که در این پوشه قرار دارد، "لمس، بهم زدن، یا تحریک" کند.
چرا اینطور است؟ زیرا این نتیجه یکی از سخت ترین درس هایی است که در حین توسعه dotCloud آموخته ایم. موتور کانتینر dotCloud با داشتن چندین فرآیند که به طور همزمان به /var/lib/dotcloud دسترسی داشتند، کار کرد. ترفندهای حیلهآمیز مانند جایگزینی فایل اتمی (به جای ویرایش در محل)، ایجاد کد با قفلهای توصیهای و اجباری، و آزمایشهای دیگر با سیستمهای امن مانند SQLite و BDB همیشه کارساز نبودند. هنگامی که ما در حال طراحی مجدد موتور کانتینر خود بودیم، که در نهایت به Docker تبدیل شد، یکی از تصمیمات بزرگ طراحی این بود که همه عملیات کانتینر را تحت یک دیمون یکپارچه کنیم تا از همه مزخرفات همزمانی حذف شود.
اشتباه نکنید: ساختن چیزی خوب، قابل اعتماد و سریع که شامل فرآیندهای متعدد و کنترل موازی مدرن باشد کاملاً ممکن است. اما ما فکر می کنیم نوشتن و نگهداری کد با استفاده از Docker به عنوان تنها پخش کننده ساده تر و آسان تر است.
این بدان معناست که اگر دایرکتوری /var/lib/docker را بین چندین نمونه Docker به اشتراک بگذارید، با مشکل مواجه خواهید شد. البته، این می تواند کارساز باشد، به خصوص در مراحل اولیه آزمایش. "گوش کن، مادر، من می توانم اوبونتو را به عنوان داکر اجرا کنم!" اما چیز پیچیدهتری را امتحان کنید، مانند کشیدن یک تصویر از دو نمونه مختلف، و خواهید دید که جهان میسوزد.
این بدان معنی است که اگر سیستم CI شما ساخت و بازسازی را انجام دهد، هر بار که کانتینر Docker-in-Docker خود را مجددا راه اندازی می کنید، خطر انداختن یک هسته هسته ای در حافظه پنهان آن را دارید. این اصلا جالب نیست!
راه حل
بیایید یک قدم به عقب برگردیم. آیا واقعاً به Docker-in-Docker نیاز دارید یا فقط می خواهید بتوانید Docker را اجرا کنید و کانتینرها و تصاویر را از سیستم CI خود بسازید و اجرا کنید در حالی که خود سیستم CI در یک کانتینر است؟
من شرط می بندم که اکثر مردم گزینه دوم را می خواهند، به این معنی که می خواهند یک سیستم CI مانند جنکینز بتواند کانتینرها را اجرا کند. و ساده ترین راه برای انجام این کار این است که به سادگی یک سوکت Docker را در ظرف CI خود وارد کنید و آن را با پرچم -v مرتبط کنید.
به عبارت ساده، هنگامی که کانتینر CI خود را اجرا می کنید (جنکینز یا موارد دیگر)، به جای هک کردن چیزی همراه با Docker-in-Docker، آن را با خط شروع کنید:
docker run -v /var/run/docker.sock:/var/run/docker.sock ...
این کانتینر اکنون به سوکت Docker دسترسی خواهد داشت و بنابراین میتواند کانتینرها را اجرا کند. با این تفاوت که به جای اجرای کانتینرهای "کودک"، کانتینرهای "خواهر و برادر" را راه اندازی می کند.
این را با استفاده از تصویر رسمی docker (که حاوی باینری Docker است) امتحان کنید:
docker run -v /var/run/docker.sock:/var/run/docker.sock
-ti docker
به نظر می رسد و مانند Docker-in-Docker کار می کند، اما Docker-in-Docker نیست: وقتی این کانتینر کانتینرهای اضافی ایجاد می کند، آنها در Docker سطح بالا ایجاد می شوند. عوارض جانبی تودرتو را تجربه نخواهید کرد و حافظه پنهان اسمبلی در چندین تماس به اشتراک گذاشته خواهد شد.
توجه: نسخههای قبلی این مقاله به پیوند باینری Docker از میزبان به کانتینر توصیه میکردند. این در حال حاضر غیر قابل اعتماد شده است زیرا موتور داکر دیگر کتابخانه های ایستا یا نزدیک به استاتیک را پوشش نمی دهد.
بنابراین، اگر می خواهید از Docker از Jenkins CI استفاده کنید، 2 گزینه دارید:
نصب Docker CLI با استفاده از سیستم بسته بندی تصویر اولیه (به عنوان مثال اگر تصویر شما بر اساس Debian است، از بسته های .deb استفاده کنید)، با استفاده از Docker API.
چند تبلیغ 🙂
از اینکه با ما ماندید متشکرم آیا مقالات ما را دوست دارید؟ آیا می خواهید مطالب جالب تری ببینید؟ با ثبت سفارش یا معرفی به دوستان از ما حمایت کنید
Dell R730xd 2 برابر ارزان تر در مرکز داده Equinix Tier IV در آمستردام؟ فقط اینجا
منبع: www.habr.com