Добре помислете, преди да използвате Docker-in-Docker за CI или тестова среда.

Добре помислете, преди да използвате Docker-in-Docker за CI или тестова среда.

Docker-in-Docker представлява виртуализирана среда Docker демон, стартирана в самия контейнер за изграждане на контейнерни образи. Основната цел на създаването на Docker-in-Docker беше да се помогне в разработката на самия Docker. Много хора го използват за пускане на Jenkins CI. В началото изглежда нормално, но след това възникват проблеми, които могат да бъдат избегнати, като инсталирате Docker в контейнера Jenkins CI. В тази статия ще разгледаме как да го направите. Ако се интересувате от окончателното решение без подробности, просто прочетете последния раздел на статията "Решение на проблема".

Добре помислете, преди да използвате Docker-in-Docker за CI или тестова среда.

Docker-in-Docker: "Добър"

Повече от две години назад, добавих в Docker флаг –privileged и написах първата версия dind. Целта беше да помогне на основния екип по-бързо да разработва Docker. Преди появата на Docker-in-Docker типичният цикъл на разработка беше такъв:

  • hackity hack;
  • изграждане (build);
  • спиране на работещия Docker демон;
  • старт на нов Docker демон;
  • тестове;
  • повторение на цикъла.

Ако обаче искате да направите красиво, воспроизводимо изграждане (в контейнер), то ставаше по-сложно:

  • hackity hack;
  • да се уверите, че работи версия на Docker;
  • да съберете нов Docker с помощта на стар Docker;
  • да спрете Docker демона;
  • да стартирате нов Docker демон;
  • да тествате;
  • да спрете новия Docker демон;
  • да повторите.

С появата на Docker-in-Docker процесът се опрости:

  • hackity hack;
  • изграждане + стартиране в един етап;
  • повторение на цикъла.

Не е ли много по-добре така?

Добре помислете, преди да използвате Docker-in-Docker за CI или тестова среда.

Docker-in-Docker: "Лош"

Обаче, въпреки общоприетото мнение, Docker-in-Docker не е изцяло съставен от звездички, понита и еднорози. Имам предвид, че съществуват няколко проблема, за които разработчикът трябва да знае.

Една от тях се отнася до LSM (Linux Security Modules), като AppArmor и SELinux: при стартиране на контейнер "вътрешният Docker" може да се опита да приложи профили за сигурност, които ще конфликтуват или объркат "външния Docker". Това е най-сложният проблем, който трябваше да реша при опитите да се обединят оригиналната имплементация на флага –privileged. Моите промени работеха и всички тестове също щяха да минат на моята Debian машина и тестовите виртуални машини с Ubuntu, но те щяха да се провалят и да се срутят на машината на Майкъл Кросби (доколкото си спомням, той имаше Fedora). Не мога да си спомня точната причина за проблема, но възможно е да е произходила от факта, че Майк е мъдър човек, който работи с SELINUX=enforce (аз използвах AppArmor), и моите промени не взимаха предвид профилите на SELinux.

Docker-in-Docker: "Злият"

Втората проблема е свързана с драйверите за съхранение на Docker. Когато стартирате Docker-in-Docker, външният Docker работи върху стандартна файлова система (EXT4, BTRFS или друга, която имате), а вътрешният Docker работи върху система за записващо копие (AUFS, BTRFS, Device Mapper и т.н., в зависимост от това, каквото външният Docker е конфигуриран да използва). Появяват се множество комбинации, които няма да работят. Например, не можете да стартирате AUFS върху AUFS.

Ако стартирате BTRFS върху BTRFS, първоначално трябва да работи, но веднъж след като се появят вложени подсекции, няма да можете да изтривате родителската подсекция. Модулът Device Mapper няма пространства от имена, така че, ако няколко инстанции на Docker използват него на едно устройство, всички те ще могат да виждат (и да влияят) на образите помежду си и на устройствата за резервно копиране на контейнери. Това е лошо.

Има обходни пътища за справяне с много от тези проблеми. Например, ако искате да използвате AUFS във вътрешния Docker, просто превърнете папката /var/lib/docker в том и всичко ще бъде наред. Docker добави някои основни пространства от имена за целите на Device Mapper, така че, ако няколко извиквания на Docker се извършват на едно устройство, те няма да "настъпват" помежду си.

Въпреки това, тази конфигурация не е толкова проста, колкото може да се види от тези статиите в хранилището dind в GitHub.

Docker-in-Docker: става все по-лошо

Как стои въпросът за кеширането на изграждането? Това също може да бъде доста сложно. Хората често ме питат: "ако стартирам Docker-in-Docker, как мога да използвам изображенията, разположени на хоста ми, вместо отново да тегля всичко в моя вътрешен Docker?"

Някои предприемчиви хора се опитвали да свържат /var/lib/docker от хоста в контейнера Docker-in-Docker. Понякога те споделят /var/lib/docker между няколко контейнера.

Добре помислете, преди да използвате Docker-in-Docker за CI или тестова среда.
Искате ли да повредите данните? Защото точно това ще повреди вашите данни!

Docker демонът явно е проектиран, за да има изключителен достъп до /var/lib/docker. Нищо друго не трябва да "пипа, пъха или изследва" файловете на Docker, намиращи се в тази папка.

Защо е така? Защото това е резултат от един от най-трудните уроци, получени при разработването на dotCloud. Контейнерният двигател на dotCloud работеше, имайки няколко процеса, които едновременно се обръщаха към /var/lib/dotcloud. Хитри трикове, като атомарна смяна на файлове (вместо редактиране на място), "подправяне" на кода с препоръчителни и задължителни заключвания и други експерименти със безопасни системи, като SQLite и BDB, не работеха винаги. Когато преосмисляхме нашия контейнерен двигател, който в крайна сметка стана Docker, едно от основните дизайнерски решения беше да съберем всичките операции с контейнери под един демонт, за да сложим край на цялата тази бъркотия с едновременния достъп.

Не ме разбирайте погрешно: напълно е възможно да се направи нещо добро, надеждно и бързо, което да включва няколко процеса и съвременно паралелно управление. Но мислим, че е по-просто и по-лесно да се пише и поддържа код, използвайки Docker като единствен играч.

Това означава, че ако споделяте директорията /var/lib/docker между няколко екземпляра на Docker, ще имате проблеми. Разбира се, това може да сработи, особено на ранни етапи на тестване. "Слушай, мама, мога да 'докера' стартирам ubuntu!" Но опитайте да направите нещо по-сложно, например да изтеглите същото изображение от два различни екземпляра, и ще видите как светът изгори.

Това означава, че ако вашата CI система извършва компилации и повторни компилации, при всяко рестартиране на контейнера Docker-in-Docker рискувате да изтриете ядрена бомба от кеша му. Това определено не е добре!

Решение на проблема

Нека направим крачка назад. Наистина ли имате нужда от Docker-in-Docker или просто искате да можете да стартирате Docker, а именно да компилирате и стартирате контейнери и образи от вашата CI система, докато самата тази CI система е в контейнер?

Заложете, че повечето хора искат последния вариант, т.е. те искат CI система като Jenkins да може да стартира контейнери. И най-простият начин да направите това е просто да свържете Docker сокета с вашия CI контейнер с флага -v.

По-просто казано, когато стартирате вашия CI контейнер (Jenkins или друг), вместо да хаквате нещо с 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 вече не важи за статични или почти статични библиотеки.

Така че, ако искате да използвате Docker от Jenkins CI, имате 2 опции:
инсталиране на Docker CLI с използване на базовата система за опаковане на образи (т.е. ако образът ви е базиран на Debian, използвайте .deb пакети), използване на Docker API.

Малко реклама 🙂

Благодарим ви, че оставате с нас. Харесвате ли нашите статии? Искате ли да виждате повече интересни материали? Подкрепете ни, като направите поръчка или препоръчате на познати, облачни VPS за разработчици от $4.99, уникален аналог на entry-level сървъри, който е създаден от нас за вас: Цялата истина за VPS (KVM) E5-2697 v3 (6 ядра) 10GB DDR4 480GB SSD 1Gbps от $19 или как да делите правилно сървър? (с налични опции за RAID1 и RAID10, до 24 ядра и до 40GB DDR4).

Dell R730xd на половин цена в дата центъра Equinix Tier IV в Амстердам? Всичко това само при нас 2 х Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64GB DDR4 4x960GB SSD 1Gbps 100TB от 199 $ в Нидерландия! Dell R420 — 2x E5-2430 2.2GHz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB — от 99 $! Чете се за това Как да построим инфраструктура от корпоративен клас с помощта на сървъри Dell R730xd E5-2650 v4 на стойност 9000 евро за малко пари?

Източник: habr.com

Купете надежден хостинг за сайтове с защита от DDoS, VPS VDS сървъри 🔥 Купете надежден хостинг за сайтове с защита от DDoS, VPS VDS сървъри | ProHoster