Хорошо подумайте, прежде чем использовать Docker-in-Docker для CI или тестовой среды

Автор оригинала: Джером Петаццони aka ~jpetazzo
  • Перевод


Docker-in-Docker представляет собой виртуализированную среду Docker-демон, запущенную в самом контейнере для сборки образов контейнера. Основной целью создания Docker-in-Docker была помощь в разработке самого Docker. Многие люди используют его для запуска Jenkins CI. Поначалу это кажется нормальным, но затем возникают проблемы, которых можно избежать, установив Docker в контейнер Jenkins 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: «Плохой»


Однако, вопреки распространенному мнению, Docker-in-Docker не состоит на 100% из звездочек, пони и единорогов. Я имею в виду, что существует несколько проблем, о которых разработчику нужно знать.

Одна из них касается LSM (модулей безопасности Linux), таких как 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, сначала это должно работать, но как только появятся вложенные подразделы, удалить родительский подраздел parent subvolume не удастся. Модуль 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-демон явно был разработан для того, чтобы иметь эксклюзивный доступ к /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 Cores) 10GB DDR4 480GB SSD 1Gbps от $19 или как правильно делить сервер? (доступны варианты с RAID1 и RAID10, до 24 ядер и до 40GB DDR4).

Dell R730xd в 2 раза дешевле в дата-центре Equinix Tier IV в Амстердаме? Только у нас 2 х Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64GB DDR4 4x960GB SSD 1Gbps 100 ТВ от $199 в Нидерландах! Dell R420 — 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB — от $99! Читайте о том Как построить инфраструктуру корп. класса c применением серверов Dell R730xd Е5-2650 v4 стоимостью 9000 евро за копейки?
ua-hosting.company
Хостинг-провайдер: серверы в NL / US до 100 Гбит/с

Комментарии 13

    +4

    Статья безнадежно устарела. А проброс сокета докера, ну, явно в разы менее безопасно, чем dind.
    Для кубернетеса и пр — вообще придумали daemonless сборщики образов. Kaniko, buildah, umoci, img и пр. Тулинг все ещё активно развивается


    AUFS, кстати, уже давно нет — везде overlay2

      +2

      В чем устарела статья?
      Kaniko, buildah, umoci, img — они мало популярны.
      Люди инертны и до сих пор используют docker без Kaniko, buildah, umoci, img.
      Не везде overlay2. У нас сервера на CentOS 6-7, а там storage-driver=devicemapper

        +1

        Ну, device-mapper — это все равно не aufs.


        Kaniko, buildah, umoci, img — они мало популярны.

        Ага, только вот другого нормального способа собирать образы докер в кубернетес не придумали. Нормальный — с кэшем, повторяемыми сборками, безопасный.


        Если же мы говорим именно про docker на выделенных узлах, то действительно докер через сокет позволяет пользоваться кэшем сборок на хосте, но это порождает дополнительные проблемы — вроде невозможности конкуретнных сборок, очень жирный минус в безопасности (т.к. по факту контейнеры запущены в рамках одного демона на хосте). Проблемы со скоростью AUFS уже сейчас вроде как решены.
        В общем, я предлагаю не слепо следовать рекомендациям из этой статьи, а думать своей головой. Ну, и самая мякотка — оригинал от 2015 года!!! Уже 2020 на дворе так-то

          +1

          Поэтому я не пользуюсь Docker-in-Docker.
          Некоторые компании по уровню инфраструктурных утилит застряли на уровне 2015 года.
          Для таких компаний пост актуальный

            +2

            Для компаний из 2015 вообще в целом использование докера, кхм, наверное, нельзя рекомендовать.

      +1
      Тег #перевод был бы не лишним, по-моему
        +1

        Было бы не лишним удаление статьи модераторами. Потому что это плохой, некачественный, совершенно невычитанный гуглоперевод.

        0
        У нас в компании еще в 2017 году прекратили использование privileged, я почему-то думал, что это deprecated фича, но мануал говорит, что ошибался. Но на корпоративном хосте запустить докер с повышенными привилегиями банально было нельзя, мы достаточно сложно обходили данные ограничения.
          0
          Читал эту статью в оригинале еще 5 лет назад, когда она вышла. С тех пор многое изменилось, не вижу смысла заниматься некромантией.
            0
            эти подходы до сих пор используются, например я недавно столкнулся с этим при работе с Gitlab CI
              0

              Пожалуйста, расскажите отдельной статьей.

              +4

              Я сам активно "запилил" ci/cd на dind (gitlab runner) — он мне делает docker image и заливает в регистри гитлаб. Судя по комментам, я "устарел".
              Скажите или опубликуйте пожалуйста, как делают сегодня, в 2020.


              Спасибо

                0
                Так же делаем)

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

              Самое читаемое