Чего я не понимаю в докере (процессы, и как докер работает на низком уровне)
Уважаемые господа, я уже несколько раз задавал вопросы на тему докера, и вроде бы научился им пользоваться - на уровне базовых задач: построение и запуск моих приложений в контейнерах, прокидывание томов в контейнеры (для хранения состояния), простейшая оркестрация при помощи docker compose и т.п.
Также, я понимаю общую картину: есть "вызовы уровня ядра". Как я понимаю, docker server транслирует любые вызовы из запущенного контейнера в эти "вызовы уровня ядра". Благодаря этому, я могу на одной машине запустить внутри одного контейнера centos, внутри другого - ubuntu, а внутри третьего - alpain, и всё это будет работать.
При этом если я спрошу версию ядра командой uname -r на хосте и в контейнере - то мне покажется одна и та же версия.
А если я прошу "версию OS" командой cat /etc/os-release - то информация на хосте и в контейнере может быть разной, так как контейнер содержит специфические версии программ, библиотек и т.п, то есть всего, что идёт уже "выше ядра".
Также, я понимаю кардинальные отличия докер-контейнеров от виртуальных машин.
Но вот что мне остаётся непонятным.
Процессы.
Я считаю кол-во запущенных процессов командой ps -A --no-headers | wc -l
Когда я на своём игрушечном сервере под убунтой считаю кол-во запущенны процессов, получается цифра 184.
Я возьму простейший Dockerfile, состоящий из одной строки FROM Ubuntu 20.04
Запущу его командой
docker run --entrypoint "/bin/bash" --name u20 -it ubuntu20-img
просто запущу убунту в контейнере, а контейнер - в интерактивном режиме
Счетчик процессов на хосте покажет после этого 187 процессов - на три больше. Сетчик процессов внутри контейнера показывает... 3 процесса. И я даже понимаю, какие именно: это bash, ps и wc
Вопрос: а почему так мало? Ведь у меня, как-никак, целая убунта?
Почему она, будучи запущена в контейнере, довольствуется одним единственным процессом?
Логику и соотвествие идологии докера "один контейнер - одна функция - один процесс" я здесь вижу.
Но не понимаю, чем именно запущенная в контейнере убунта - в смысле своих процессов - так отличается от той, которая работает на хосте.
Вообще, хотелось бы немного подробнее прочитать про то, как докер работает на низком уровне -как происходит запуск контейнера, что происходит при выполнении docker exec и т.п.
Я попрбовал книжку "McKendrick R., Gallagher S. - Mastering Docker. Third Edition", посмотрел несколько видео по докеру и прочитал штук двадцать статей. Но они все построены по одному и тому же образцу "разница с VM - преимущества - команды создания контейнеров" - и погнали, погнали в сторону сборки, оркестрации и использования. В сторону того, как использовать docker. Про то, как он устроен и что просиходит на уровне системы при выполнении команд - никто не пишет.
Не могли бы Вы направить меня к правильным источникам, в которых эти моменты объяснены?
Ответы (2 шт):
Тут само понимание хромает, докер это не виртуальная машина. С родни докер ближе к jail (https://ru.wikipedia.org/wiki/FreeBSD_Jail) чем к VM. Просто оно симулирует не окружение 1 приложения, а целую экосистему определённой OS. Если пояснять на пальцах, то Docker это по сути своей эмулятор который через hypervisor симулирует OS.
Если хочется понять как под капотом?! - то стоит почить документацию на тот-же jail. Просто что-бы понять основу основ. Потом уже читать документации на hypervisor. И тогда всё само по себе раскроется.
На основе базовой ОС (Тип 2, V) https://ru.wikipedia.org/wiki/Гипервизор
Так прям на пальцах не пояснишь, это не разработка одной группы учёных, это скорее достояние человечества. В 90х люди научились сажать в Jail отдельные программы, потом появились всякие Poket Edition, потом началась эра эмуляторов, после полноценных виртуальных машин, потом людям захотелось что-бы виртуальные машины смогли взаимодействовать с основной ОС и появилась Hyper-V, и на её основе уже построили Docker.
Рассмотрим простой пример, когда запускается один контейнер:
docker run -ti ubuntu /bin/bash
С точки зрения ОС - это обычный процесс, к которому применены определенные настройки с целью:
- ограничить доступные этому процессу ресурсы (т.е. память, время CPU, IO и т.п.)
- изолировать процесс от других процессов запущенных в ОС
- изолировать файловую систему ОС от нашего процесса
Для этого используются механизмы реализованные ядром линукс:
- cgroups (control groups): позволяет настраивать для процесса ограничения по потребляемым ресурсам, а еще ставить процесс на паузу и заново запускать.
- namespaces: эта фича позволяет создавать пространства имен для системных ресурсов таких как pid-ы (идентификаторы процессов), сетевые интерфейсы, порты, идентификаторы пользователей/групп и др. Подробнее как это работает - ниже.
- ufs (union file system): позволяет несколько файловых систем объединять в одну. Используется, чтоб можно было "поверх" read-only образа файловой системы, скажем того же образа ubuntu, добавить новый "слой", который можно изменять.
Все эти возможности доступны в ядре линукса и их можно использовать в своих целях. docker - это такой фронтенд, который дает удобный интерфейс для того, чтоб всеми этими возможностями изоляции и контроля пользоваться удобно.
Пространства имен (namespaces)
Пространство имен позволяет изолировать системные ресурсы. Например, pid-ы, если процесс запущен в отдельном пространстве имен pid-ов, то он может видеть только процессы из этого же пространства имен. Т.е. при системном вызове к ядру "дай мне список всех процессов" он увидит только те процессы, которые запущены в том же пространстве имен. Системный вызов тот же, он в себе реализовывает эту логику фильтрации.
Также важный момент, что идентификаторы в каждом пространстве свои, т.е. и в пространстве "по-умолчанию" и в созданных дополнительно могут быть процессы с идентификатором 1 и это будут разные процессы.
Аналогично есть пространства имен для пользователей. Т.е. процессу может быть назначено отдельное пространство имен, к котором существуют свои пользователи (и в том пространстве другой пользователь будет root-ом).
Так же и для портов. Процессу может быть назначено свое пространство имен, в котором процесс может использовать порт 80, при том, что в ОС порт 80 может уже быть использован другим процессом, но эти порты не пересекаются, так как они в разных пространствах имен.
почему так мало? Ведь у меня, как-никак, целая убунта?
В том контейнере у вас нет "целой убунты". Т.е. у вас нет всего функционала, что дают те 184 процесса. Например, там нет процессов, которые следят за тем, чтоб процессы-демоны были запущены. Обычно в этом нет необходимости в силу специфики того, для чего используется контейнеры. Но если это будет необходимо, то вам придется самим запустить их внутри контейнера.
не понимаю, чем именно запущенная в контейнере убунта - в смысле своих процессов - так отличается от той, которая работает на хосте
Тут все зависит от того, что вы называете "убунтой". В примере выше с запуском bash, у вас в контейнере запущен один процесс. С точки зрения ОС ("хост" системы - пишу в кавычках, потому что это я поясняю для лучшего понимания, на самом деле тут нет никакой хост и гостевой системы, как в VM) это просто один процесс, но которому назначены свои пространства имен, чтоб его изолировать от других процессов и файлов.
По сути убунту в контейнере это файлы, которые находятся в файловой системе образа, из которого запущен контейнер. Т.е. там есть программы именно из дистрибутива ubuntu (а не, скажем, из alpine) и их можно запускать внутри контейнера.