Когда дело доходит до технологии виртуализации, мы должны в первую очередь думать о Docker.После четырех лет быстрой разработки Docker стал стандартной конфигурацией многих компаний, и это уже не игрушка, которую можно использовать только на этапе разработки. Как продукт, широко используемый в производственных средах, Docker имеет очень зрелое сообщество и большое количество пользователей, а содержимое базы кода стало очень большим.
Опять же, из-за развития проекта, разделения функций и различных странных изменений имени. PR, снова усложняет понимание общей архитектуры Docker.
Хотя в настоящее время Docker состоит из множества компонентов и его реализация очень сложна, в этой статье мы не хотим слишком много рассказывать о конкретных деталях реализации Docker, а поговорим об основных технологиях, поддерживающих появление Docker, технологии виртуализации.
Во-первых, Докер Он должен появиться, потому что текущая серверная часть действительно нуждается в технологии виртуализации на этапах разработки, эксплуатации и обслуживания, чтобы решить проблему согласованности между средой разработки и производственной средой.Через Docker мы также можем включить среду, в которой программа работает с контролем версий, устраняя необходимость в возможности различных результатов работы из-за среды. Однако, хотя вышеуказанные требования способствовали развитию технологии виртуализации, если нет подходящей базовой технической поддержки, мы все равно не сможем получить идеальный продукт. В оставшейся части этой статьи будут представлены несколько основных технологий, используемых Docker.Если мы поймем, как и как они используются, мы сможем понять, как реализован Docker.
В этом случае, как только определенная служба на сервере подвергнется вторжению, злоумышленник может получить доступ ко всем службам и файлам на текущей машине, чего мы также не хотим видеть, и Docker фактически использует пространства имен Linux для разных контейнеров для достижения изоляции.
Механизм пространства имен Linux предоставляет следующие семь различных пространств имен, включая CLONE_NEWCGROUP, CLONE_NEWIPC, CLONE_NEWNET, CLONE_NEWNS, CLONE_NEWPID, CLONE_NEWUSER и CLONE_NEWUTS.С помощью этих семи параметров мы можем указать, в каких ресурсах должен находиться новый процесс при создании нового процесса. с хост-машины.
На текущей машине выполняется много процессов.Среди вышеупомянутых процессов есть два очень особенных, один процесс /sbin/init с pid 1, а другой процесс kthreadd с pid 2, оба из которых управляется Linux.Созданный богом процесс idle в , первый отвечает за выполнение части работы по инициализации и системной конфигурации ядра, а также создает некоторые процессы регистрации, подобные getty, в то время как последний отвечает за управление и планирование других процессы ядра.
Если мы запустим новый Docker-контейнер под текущей операционной системой Linux, выполним его внутренний bash и напечатаем все процессы в нем, мы получим следующие результаты:
Выполнение команды ps внутри нового контейнера выводит очень чистый список процессов, только три процесса, включая текущий ps -ef, десятки процессов на хост-компьютере исчезли.
Текущий контейнер Docker успешно изолирует процесс в контейнере от процесса на хост-компьютере.Если мы напечатаем все текущие процессы на хост-компьютере, мы получим следующие три результата, связанные с Docker:
На текущем хост-компьютере может быть дерево процессов, состоящее из различных процессов, упомянутых выше:
Это достигается путем передачи CLONE_NEWPID при использовании clone(2) для создания нового процесса, то есть с использованием пространства имен Linux для достижения изоляции процесса, и любой процесс внутри контейнера Docker ничего не знает о процессе хост-машины.
Контейнер Docker использует описанную выше технологию для достижения изоляции процесса от хост-компьютера.Когда мы запускаем docker run или docker start каждый раз, мы создадим Spec в следующем методе, чтобы установить изоляцию процесса:
В методе setNamespaces задаются не только пространства имен, связанные с процессом, но также пространства имен, связанные с пользователем, сетью, IPC и UTS:
Все настройки, связанные с пространством имён, Spec окончательно будут заданы как параметры функции Create при создании нового контейнера:
Все настройки, связанные с пространством имен, выполняются в двух вышеупомянутых функциях, и Docker успешно завершает изоляцию от хост-процесса и сети через пространство имен.
Каждый контейнер, запущенный с помощью docker run, фактически имеет отдельное сетевое пространство имен.Docker предоставляет нам четыре различных режима сети: Host, Container, None и Bridge.
В этой части мы представим Режим настройки сети Docker по умолчанию: режим моста. В этом режиме, помимо выделения изолированных сетевых пространств имен, Docker также устанавливает IP-адреса для всех контейнеров. При запуске сервера Docker на хосте создается новый виртуальный мост docker0, и все сервисы, запускаемые впоследствии на хосте, по умолчанию подключаются к этому мосту.
По умолчанию каждый контейнер при создании создает пару виртуальных сетевых карт, две виртуальные сетевые карты образуют канал данных, одна из которых будет помещена в созданный контейнер и добавлена в мост с именем docker0. Мы можем использовать следующую команду для просмотра интерфейса текущего моста:
docker0 назначит новый IP-адрес каждому контейнеру и установит IP-адрес docker0 в качестве шлюза по умолчанию. Мост docker0 подключен к сетевой карте на хост-машине через конфигурацию в iptables.Все квалифицированные запросы будут перенаправлены на docker0 через iptables и распределены по мосту на соответствующую машину.
Мы используем команду docker run -d -p 6379:6379 redis для запуска нового контейнера Redis на текущей машине После этого, когда мы посмотрим на текущую конфигурацию NAT iptables, мы увидим, что в DOCKER появляется новый. цепочка. правило:
Приведенное выше правило будет пересылать TCP-пакеты, отправленные из любого источника, на порт 6379 текущей машины на адрес, где находится 192.168.0.4:6379.
Этот адрес на самом деле является IP-адресом, назначенным Docker службе Redis.Если мы пропингуем этот IP-адрес непосредственно на текущей машине, мы обнаружим, что он доступен:
Из приведенного выше ряда явлений мы можем сделать вывод о том, как Docker раскрывает внутренние порты контейнера и пересылает пакеты данных; когда есть контейнер Docker, которому необходимо предоставить службу хост-компьютеру, он будет выделен контейнеру. IP-адрес и добавляет новое правило в iptables.
Когда мы используем redis-cli для доступа к адресу 127.0.0.1:6379 в командной строке хост-компьютера, IP-адрес направляется на 192.168.0.4 через NAT PREROUTING iptables, а перенаправленные пакеты могут проходить ФИЛЬТР в iptables. Конфигурация и, наконец, замаскируйте IP-адрес под 127.0.0.1 на этапе NAT POSTROUTING.Хотя это выглядит так, будто мы запрашиваем 127.0.0.1:6379 извне, на самом деле мы запрашиваем порт, открытый контейнером Docker.
Docker реализует сетевую изоляцию через пространства имен Linux и перенаправляет пакеты через iptables, позволяя контейнерам Docker изящно предоставлять услуги хост-компьютерам или другим контейнерам.
В модели контейнерной сети каждый контейнер содержит песочницу, в которой хранится конфигурация сетевого стека текущего контейнера, включая интерфейс контейнера, таблицу маршрутизации и настройки DNS.Linux использует сетевое пространство имен для реализации этой песочницы, и каждая песочница содержит может быть одна или несколько конечных точек, которая представляет собой виртуальную сетевую карту в Linux.Песочница добавляется в соответствующую сеть через конечные точки.Сеть здесь может быть мостом Linux или VLAN, о которых мы упоминали выше.
Чтобы создать пространство имен изолированной точки монтирования в новом процессе, вам нужно передать CLONE_NEWNS в функцию клонирования, чтобы дочерний процесс мог получить копию точки монтирования родительского процесса.Если этот параметр не передан, дочерний процесс может читать и запись в файловую систему будет синхронизирована с родительским процессом и всей файловой системой хоста.
Если контейнер необходимо запустить, он должен предоставить корневую файловую систему (rootfs), контейнер должен использовать эту файловую систему для создания нового процесса, и все выполнение двоичных файлов должно выполняться в этой корневой файловой системе.
Для нормального запуска контейнера необходимо смонтировать указанные выше каталоги в rootfs.Помимо указанных выше каталогов, которые необходимо смонтировать, нам также необходимо установить некоторые символические ссылки, чтобы гарантировать отсутствие проблем с системным вводом-выводом.
Чтобы убедиться, что текущий процесс-контейнер не имеет доступа к другим каталогам на хост-компьютере, нам также необходимо использовать функцию pivor_root или chroot, предоставляемую libcotainer, для изменения корневого узла, к которому процесс может получить доступ к файловому каталогу.
На этом этапе мы монтируем в контейнер необходимые каталоги, а также запрещаем текущему процессу контейнера доступ к другим каталогам на хост-машине, обеспечивая изоляцию разных файловых систем.
Если один из контейнеров выполняет задачи, интенсивно использующие ЦП, это повлияет на производительность и эффективность выполнения задач в других контейнерах, в результате чего несколько контейнеров будут влиять друг на друга и вытеснять ресурсы. Как ограничить использование ресурсов несколькими контейнерами стало основной проблемой после решения проблемы изоляции виртуальных ресурсов процесса, а группы управления (сокращенно CGroups) могут изолировать физические ресурсы на хост-машине, такие как ЦП, память, дисковый ввод/вывод. O и пропускная способность сети.
Каждая CGroup представляет собой группу процессов, ограниченных одними и теми же стандартами и параметрами, между разными CGgroups существует иерархическая связь, то есть они могут наследовать некоторые стандарты и параметры ограничения использования ресурсов от родительского класса.
CGgroups для Linux Он может выделять ресурсы группе процессов, то есть ЦП, память, пропускную способность сети и другие ресурсы, о которых мы упоминали выше.Благодаря распределению ресурсов CGroup может выполнять следующие функции:
Большинство дистрибутивов Linux имеют очень похожие подсистемы, а перечисленные выше вещи, такие как набор процессоров, процессор и т. д., называются подсистемами, потому что они могут распределять ресурсы по соответствующим контрольным группам и ограничивать использование ресурсов.
Если мы хотим создать новую контрольную группу, просто создайте новую папку под подсистемой, которую мы хотим выделить или ограничить ресурсы, тогда в этой папке автоматически будет много содержимого, если у вас установлен Docker в Linux, вы найдете папку с именем Docker в каталоге всех подсистем:
9c3057xxx на самом деле является контейнером Docker, который мы запускаем. При запуске контейнера Docker создаст CGroup для контейнера с тем же идентификатором контейнера. На текущем хосте CGroup будет иметь следующие иерархические отношения:
Под каждой CGroup есть файл tasks, в котором хранятся идентификаторы процессов всех процессов, принадлежащих к текущей контрольной группе.Как подсистема, отвечающая за ЦП, содержимое файла cpu.cfs_quota_us может ограничивать использование ЦП.Если содержимое текущего файла. Если оно равно 50000, использование ЦП всеми процессами в текущей контрольной группе не может превышать 50%.
Если системный администратор хочет контролировать использование ресурсов контейнера в Docker, он может найти соответствующую подгруппу управления в родительской группе управления Docker и изменить содержимое соответствующих файлов.Конечно, мы также можем использовать параметры непосредственно во время работы программы. , Пусть Процесс Docker для изменения содержимого соответствующего файла.
Когда мы используем Docker для закрытия работающего контейнера, папка, соответствующая подгруппе управления Docker, также будет удалена процессом Docker.Когда Docker использует CGroup, он фактически выполняет только некоторые файловые операции для создания папок и изменения содержимого Тем не менее, использование CGroup действительно решает проблему, заключающуюся в том, что мы ограничиваем использование ресурсов подконтейнерами.Системный администратор может разумно распределять ресурсы для нескольких контейнеров без проблемы, когда несколько контейнеров вытесняют ресурсы друг друга.
Что такое образ, как он составлен и организован — вопрос, который озадачил автора в течение некоторого времени с тех пор, как он использовал Docker.Мы можем использовать docker run, чтобы загрузить образ Docker из удаленного места и запустить его локально.
Образ Docker на самом деле является сжатым пакетом, мы можем использовать следующую команду для Экспорт файла в образ Docker:
Вы можете видеть, что структура каталогов в этом образе busybox не сильно отличается от содержимого корневого каталога операционной системы Linux.Можно сказать, что образ Docker — это файл.
Чтобы понять драйверы хранилища, используемые Docker, нам сначала нужно понять, как Docker создает и хранит образы и как образы Docker используются каждым контейнером; каждый образ в Docker состоит только из серии Каждая команда в Dockerfile создает новый слой поверх существующего слоя только для чтения:
Каждый слой в контейнере вносит очень небольшие изменения в текущий контейнер.Вышеупомянутый Dockerfile создаст образ с четырьмя слоями:
Когда образ создается с помощью команды запуска docker, к верхнему слою образа добавляется доступный для записи слой, то есть слой контейнера.Все модификации контейнера среды выполнения фактически являются модификациями слоя чтения-записи контейнера.
Разница между контейнерами и образами в том, что все образы доступны только для чтения, и каждый контейнер фактически равен изображению плюс слой чтения-записи, то есть одному и тому же образу может соответствовать несколько контейнеров.
Как объединенная файловая система, AUFS может объединять слои в разных папках в одну папку.Эти папки называются ветками в AUFS.Весь процесс "объединения" называется объединением монтирования (Union).Mount:
Каждый слой образа или слой контейнера представляет собой подпапку в каталоге /var/lib/docker/; в Docker содержимое всех слоев образа и слоев контейнера хранится в /var/lib/docker/aufs/diff/ В каталоге:
И /var/lib/docker/aufs/layers/ хранит метаданные зеркального слоя, каждый файл сохраняет метаданные зеркального слоя, а последний /var/lib/docker/aufs/mnt/ содержит зеркало или контейнер. точка монтирования слоя в конечном итоге будет собрана Docker федеративным образом.
На картинке выше очень хорошо показан процесс сборки Каждый слой изображения строится поверх другого слоя изображения При этом все слои изображения доступны только для чтения, только самый верхний слой контейнера каждого контейнера Все контейнеры строятся на некотором базовые службы (ядро), включая пространства имен, группы управления, rootfs и т. д. Эта сборка контейнера обеспечивает большую гибкость, доступна только для чтения. Зеркальный уровень также может сократить использование диска за счет совместного использования.
Различные драйверы хранилища также имеют совершенно разные реализации при хранении образов и файлов-контейнеров.Заинтересованные читатели могут найти соответствующий контент в официальном документе Docker Select a storage driver.
Чтобы узнать, какой драйвер хранилища используется в Docker текущей системы, просто используйте следующую команду для получения соответствующей информации:
Поскольку в Ubuntu автора нет драйвера хранилища overlay2, aufs используется в качестве драйвера хранилища по умолчанию для Docker.
Автор проконсультировался с большим количеством информации в процессе изучения принципа реализации Docker, а также получил много знаний, связанных с операционной системой Linux.Однако, поскольку текущая кодовая база Docker слишком велика, я хочу полностью понять с точки зрения исходного кода понять Детали реализации Docker уже очень сложны, но если вас действительно интересуют детали его реализации, вы можете начать с исходного кода Docker CE, чтобы понять принципы Docker.
Оригинальная ссылка:draveness.me/docker
Опять же, из-за развития проекта, разделения функций и различных странных изменений имени. PR, снова усложняет понимание общей архитектуры Docker.
Хотя в настоящее время Docker состоит из множества компонентов и его реализация очень сложна, в этой статье мы не хотим слишком много рассказывать о конкретных деталях реализации Docker, а поговорим об основных технологиях, поддерживающих появление Docker, технологии виртуализации.
Во-первых, Докер Он должен появиться, потому что текущая серверная часть действительно нуждается в технологии виртуализации на этапах разработки, эксплуатации и обслуживания, чтобы решить проблему согласованности между средой разработки и производственной средой.Через Docker мы также можем включить среду, в которой программа работает с контролем версий, устраняя необходимость в возможности различных результатов работы из-за среды. Однако, хотя вышеуказанные требования способствовали развитию технологии виртуализации, если нет подходящей базовой технической поддержки, мы все равно не сможем получить идеальный продукт. В оставшейся части этой статьи будут представлены несколько основных технологий, используемых Docker.Если мы поймем, как и как они используются, мы сможем понять, как реализован Docker.
Namespaces
Пространства имен — это то, как Linux предоставляет нам такие ресурсы, как деревья процессов, сетевые интерфейсы, точки монтирования и межпроцессное взаимодействие. При повседневном использовании Linux или macOS нам не нужно запускать несколько совершенно отдельных серверов, но если мы запустим несколько служб на сервере, эти службы фактически будут влиять друг на друга, и каждая служба может видеть процесс других служб, а также может получать доступ к ним. любой файл на хост-компьютере, который часто не хотят видеть.Мы предпочитаем, чтобы разные службы, работающие на одной машине, могли быть полностью изолированы, точно так же, как несколько разных служб работают на одной машине.В этом случае, как только определенная служба на сервере подвергнется вторжению, злоумышленник может получить доступ ко всем службам и файлам на текущей машине, чего мы также не хотим видеть, и Docker фактически использует пространства имен Linux для разных контейнеров для достижения изоляции.
Механизм пространства имен Linux предоставляет следующие семь различных пространств имен, включая CLONE_NEWCGROUP, CLONE_NEWIPC, CLONE_NEWNET, CLONE_NEWNS, CLONE_NEWPID, CLONE_NEWUSER и CLONE_NEWUTS.С помощью этих семи параметров мы можем указать, в каких ресурсах должен находиться новый процесс при создании нового процесса. с хост-машины.
процесс
Процесс — очень важная концепция в Linux, а теперь и в операционных системах, представляющая собой исполняемую программу, а также единицу работы в современных системах с разделением времени. В каждой операционной системе * nix мы можем распечатать процессы, выполняющиеся в текущей операционной системе, с помощью команды ps.Например, в Ubuntu с помощью этой команды можно получить следующие результаты:$ ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 Apr08 ? 00:00:09 /sbin/init
root 2 0 0 Apr08 ? 00:00:00 [kthreadd]
root 3 2 0 Apr08 ? 00:00:05 [ksoftirqd/0]
root 5 2 0 Apr08 ? 00:00:00 [kworker/0:0H]
root 7 2 0 Apr08 ? 00:07:10 [rcu_sched]
root 39 2 0 Apr08 ? 00:00:00 [migration/0]
root 40 2 0 Apr08 ? 00:01:54 [watchdog/0]
...
На текущей машине выполняется много процессов.Среди вышеупомянутых процессов есть два очень особенных, один процесс /sbin/init с pid 1, а другой процесс kthreadd с pid 2, оба из которых управляется Linux.Созданный богом процесс idle в , первый отвечает за выполнение части работы по инициализации и системной конфигурации ядра, а также создает некоторые процессы регистрации, подобные getty, в то время как последний отвечает за управление и планирование других процессы ядра.
Если мы запустим новый Docker-контейнер под текущей операционной системой Linux, выполним его внутренний bash и напечатаем все процессы в нем, мы получим следующие результаты:
root@iZ255w13cy6Z:~# docker run -it -d ubuntu
b809a2eb3630e64c581561b08ac46154878ff1c61c6519848b4a29d412215e79
root@iZ255w13cy6Z:~# docker exec -it b809a2eb3630 /bin/bash
root@b809a2eb3630:/# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 15:42 pts/0 00:00:00 /bin/bash
root 9 0 0 15:42 pts/1 00:00:00 /bin/bash
root 17 9 0 15:43 pts/1 00:00:00 ps -ef
Выполнение команды ps внутри нового контейнера выводит очень чистый список процессов, только три процесса, включая текущий ps -ef, десятки процессов на хост-компьютере исчезли.
Текущий контейнер Docker успешно изолирует процесс в контейнере от процесса на хост-компьютере.Если мы напечатаем все текущие процессы на хост-компьютере, мы получим следующие три результата, связанные с Docker:
UID PID PPID C STIME TTY TIME CMD
root 29407 1 0 Nov16 ? 00:08:38 /usr/bin/dockerd --raw-logs
root 1554 29407 0 Nov19 ? 00:03:28 docker-containerd -l unix:///var/run/docker/libcontainerd/docker-containerd.sock --metrics-interval=0 --start-timeout 2m --state-dir /var/run/docker/libcontainerd/containerd --shim docker-containerd-shim --runtime docker-runc
root 5006 1554 0 08:38 ? 00:00:00 docker-containerd-shim b809a2eb3630e64c581561b08ac46154878ff1c61c6519848b4a29d412215e79 /var/run/docker/libcontainerd/b809a2eb3630e64c581561b08ac46154878ff1c61c6519848b4a29d412215e79 docker-runc
На текущем хост-компьютере может быть дерево процессов, состоящее из различных процессов, упомянутых выше:
Это достигается путем передачи CLONE_NEWPID при использовании clone(2) для создания нового процесса, то есть с использованием пространства имен Linux для достижения изоляции процесса, и любой процесс внутри контейнера Docker ничего не знает о процессе хост-машины.
containerRouter.postContainersStart
└── daemon.ContainerStart
└── daemon.createSpec
└── setNamespaces
└── setNamespace
Контейнер Docker использует описанную выше технологию для достижения изоляции процесса от хост-компьютера.Когда мы запускаем docker run или docker start каждый раз, мы создадим Spec в следующем методе, чтобы установить изоляцию процесса:
func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
s := oci.DefaultSpec()
// ...
if err := setNamespaces(daemon, &s, c); err != nil {
return nil, fmt.Errorf("linux spec namespaces: %v", err)
}
return &s, nil
}
В методе setNamespaces задаются не только пространства имен, связанные с процессом, но также пространства имен, связанные с пользователем, сетью, IPC и UTS:
func setNamespaces(daemon *Daemon, s *specs.Spec, c *container.Container) error {
// user
// network
// ipc
// uts
// pid
if c.HostConfig.PidMode.IsContainer() {
ns := specs.LinuxNamespace{Type: "pid"}
pc, err := daemon.getPidContainer(c)
if err != nil {
return err
}
ns.Path = fmt.Sprintf("/proc/%d/ns/pid", pc.State.GetPID())
setNamespace(s, ns)
} else if c.HostConfig.PidMode.IsHost() {
oci.RemoveNamespace(s, specs.LinuxNamespaceType("pid"))
} else {
ns := specs.LinuxNamespace{Type: "pid"}
setNamespace(s, ns)
}
return nil
}
Все настройки, связанные с пространством имён, Spec окончательно будут заданы как параметры функции Create при создании нового контейнера:
daemon.containerd.Create(context.Background(), container.ID, spec, createOptions)
Все настройки, связанные с пространством имен, выполняются в двух вышеупомянутых функциях, и Docker успешно завершает изоляцию от хост-процесса и сети через пространство имен.
Интернет
Если контейнер Docker завершает сетевую изоляцию от хост-процесса через пространство имен Linux, но нет возможности подключиться ко всему Интернету через хост-сеть, будет много ограничений, поэтому, хотя Docker может создать изоляцию через пространство имен network среде, но службы в Docker по-прежнему должны быть подключены к внешнему миру, чтобы функционировать.Каждый контейнер, запущенный с помощью docker run, фактически имеет отдельное сетевое пространство имен.Docker предоставляет нам четыре различных режима сети: Host, Container, None и Bridge.
В этой части мы представим Режим настройки сети Docker по умолчанию: режим моста. В этом режиме, помимо выделения изолированных сетевых пространств имен, Docker также устанавливает IP-адреса для всех контейнеров. При запуске сервера Docker на хосте создается новый виртуальный мост docker0, и все сервисы, запускаемые впоследствии на хосте, по умолчанию подключаются к этому мосту.
По умолчанию каждый контейнер при создании создает пару виртуальных сетевых карт, две виртуальные сетевые карты образуют канал данных, одна из которых будет помещена в созданный контейнер и добавлена в мост с именем docker0. Мы можем использовать следующую команду для просмотра интерфейса текущего моста:
$ brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.0242a6654980 no veth3e84d4f
veth9953b75
docker0 назначит новый IP-адрес каждому контейнеру и установит IP-адрес docker0 в качестве шлюза по умолчанию. Мост docker0 подключен к сетевой карте на хост-машине через конфигурацию в iptables.Все квалифицированные запросы будут перенаправлены на docker0 через iptables и распределены по мосту на соответствующую машину.
$ iptables -t nat -L
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
DOCKER all -- anywhere anywhere ADDRTYPE match dst-type LOCAL
Chain DOCKER (2 references)
target prot opt source destination
RETURN all -- anywhere anywhere
Мы используем команду docker run -d -p 6379:6379 redis для запуска нового контейнера Redis на текущей машине После этого, когда мы посмотрим на текущую конфигурацию NAT iptables, мы увидим, что в DOCKER появляется новый. цепочка. правило:
DNAT tcp -- anywhere anywhere tcp dpt:6379 to:192.168.0.4:6379
Приведенное выше правило будет пересылать TCP-пакеты, отправленные из любого источника, на порт 6379 текущей машины на адрес, где находится 192.168.0.4:6379.
Этот адрес на самом деле является IP-адресом, назначенным Docker службе Redis.Если мы пропингуем этот IP-адрес непосредственно на текущей машине, мы обнаружим, что он доступен:
$ ping 192.168.0.4
PING 192.168.0.4 (192.168.0.4) 56(84) bytes of data.
64 bytes from 192.168.0.4: icmp_seq=1 ttl=64 time=0.069 ms
64 bytes from 192.168.0.4: icmp_seq=2 ttl=64 time=0.043 ms
^C
--- 192.168.0.4 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 999ms
rtt min/avg/max/mdev = 0.043/0.056/0.069/0.013 ms
Из приведенного выше ряда явлений мы можем сделать вывод о том, как Docker раскрывает внутренние порты контейнера и пересылает пакеты данных; когда есть контейнер Docker, которому необходимо предоставить службу хост-компьютеру, он будет выделен контейнеру. IP-адрес и добавляет новое правило в iptables.
Когда мы используем redis-cli для доступа к адресу 127.0.0.1:6379 в командной строке хост-компьютера, IP-адрес направляется на 192.168.0.4 через NAT PREROUTING iptables, а перенаправленные пакеты могут проходить ФИЛЬТР в iptables. Конфигурация и, наконец, замаскируйте IP-адрес под 127.0.0.1 на этапе NAT POSTROUTING.Хотя это выглядит так, будто мы запрашиваем 127.0.0.1:6379 извне, на самом деле мы запрашиваем порт, открытый контейнером Docker.
$ redis-cli -h 127.0.0.1 -p 6379 ping
PONG
Docker реализует сетевую изоляцию через пространства имен Linux и перенаправляет пакеты через iptables, позволяя контейнерам Docker изящно предоставлять услуги хост-компьютерам или другим контейнерам.
Libnetwork
Функции всей сетевой части реализует libnetwork, отделенный от Docker, который предоставляет реализацию для подключения различных контейнеров, а также предоставляет модель контейнерной сети, которая может обеспечить согласованный программный интерфейс и абстракцию сетевого уровня для приложений.The goal of libnetwork is to deliver a robust Container Network Model that provides a consistent programming interface and the required network abstractions for applications.Наиболее важная концепция в libnetwork, модель контейнерной сети, состоит из следующих основных компонентов, а именно Sandbox, Endpoint и Сеть:
В модели контейнерной сети каждый контейнер содержит песочницу, в которой хранится конфигурация сетевого стека текущего контейнера, включая интерфейс контейнера, таблицу маршрутизации и настройки DNS.Linux использует сетевое пространство имен для реализации этой песочницы, и каждая песочница содержит может быть одна или несколько конечных точек, которая представляет собой виртуальную сетевую карту в Linux.Песочница добавляется в соответствующую сеть через конечные точки.Сеть здесь может быть мостом Linux или VLAN, о которых мы упоминали выше.
Дополнительные сведения о libnetwork или модели контейнерной сети см.Design · libnetworkДля получения дополнительной информации, конечно, вы можете прочитать исходный код, чтобы понять различные реализации модели контейнерной сети в разных ОС.
точка крепления
Хотя мы решили проблему изоляции процессов и сети с помощью пространств имен Linux, у нас нет возможности получить доступ к другим процессам на хост-компьютере и ограничить сетевой доступ в процессе Docker, но к процессам в контейнере Docker по-прежнему можно получить доступ или изменить их. Другие каталоги на хост-компьютере, которые мы не хотим видеть.Чтобы создать пространство имен изолированной точки монтирования в новом процессе, вам нужно передать CLONE_NEWNS в функцию клонирования, чтобы дочерний процесс мог получить копию точки монтирования родительского процесса.Если этот параметр не передан, дочерний процесс может читать и запись в файловую систему будет синхронизирована с родительским процессом и всей файловой системой хоста.
Если контейнер необходимо запустить, он должен предоставить корневую файловую систему (rootfs), контейнер должен использовать эту файловую систему для создания нового процесса, и все выполнение двоичных файлов должно выполняться в этой корневой файловой системе.
Для нормального запуска контейнера необходимо смонтировать указанные выше каталоги в rootfs.Помимо указанных выше каталогов, которые необходимо смонтировать, нам также необходимо установить некоторые символические ссылки, чтобы гарантировать отсутствие проблем с системным вводом-выводом.
Чтобы убедиться, что текущий процесс-контейнер не имеет доступа к другим каталогам на хост-компьютере, нам также необходимо использовать функцию pivor_root или chroot, предоставляемую libcotainer, для изменения корневого узла, к которому процесс может получить доступ к файловому каталогу.
// pivor_root
put_old = mkdir(...);
pivot_root(rootfs, put_old);
chdir("/");
unmount(put_old, MS_DETACH);
rmdir(put_old);
// chroot
mount(rootfs, "/", NULL, MS_MOVE, NULL);
chroot(".");
chdir("/");
На этом этапе мы монтируем в контейнер необходимые каталоги, а также запрещаем текущему процессу контейнера доступ к другим каталогам на хост-машине, обеспечивая изоляцию разных файловых систем.
Содержимое этой части принадлежит автору в libcontainer.SPEC.mdНайденный в файле файл содержит описание файловой системы, используемой Docker.У автора нет точного ответа, действительно ли Docker использует chroot для обеспечения того, чтобы текущий процесс не мог получить доступ к каталогу хост-машины.Во-первых, код проекта Docker слишком уж огромный, не знаю с чего начать, автор пытался использовать гугл Найти похожие результаты, но оба найдены без ответавопрос, также получил противоречивые описания в SPECОтвечать, если у читателей есть четкий ответ, вы можете оставить сообщение под блогом, большое спасибо.
Chroot
Здесь я должен кратко представить chroot (изменить корень). В системах Linux системный каталог по умолчанию начинается с /, который является корневым каталогом. Использование chroot может изменить текущую структуру корневого каталога системы. Путем изменения текущей системы В корневом каталоге старой системы мы можем ограничить права пользователей, а структура и файлы корневого каталога старой системы не могут быть доступны в новом корневом каталоге, а структура каталогов полностью изолирована от исходной система установлена.Часть контента, связанного с chroot, взята из "Понимание chroot", читатели могут прочитать эту статью для получения более подробной информации.
CGroups
Мы изолируем файловую систему, сеть и процесс от хост-машины для вновь созданного процесса через пространство имен Linux, но пространство имен не обеспечивает нам изоляцию физических ресурсов, таких как ЦП или память, если в поле " контейнеры», работающие на одной машине, которые ничего не знают друг о друге и хост-машине, но эти контейнеры в совокупности занимают физические ресурсы хост-машины.Если один из контейнеров выполняет задачи, интенсивно использующие ЦП, это повлияет на производительность и эффективность выполнения задач в других контейнерах, в результате чего несколько контейнеров будут влиять друг на друга и вытеснять ресурсы. Как ограничить использование ресурсов несколькими контейнерами стало основной проблемой после решения проблемы изоляции виртуальных ресурсов процесса, а группы управления (сокращенно CGroups) могут изолировать физические ресурсы на хост-машине, такие как ЦП, память, дисковый ввод/вывод. O и пропускная способность сети.
Каждая CGroup представляет собой группу процессов, ограниченных одними и теми же стандартами и параметрами, между разными CGgroups существует иерархическая связь, то есть они могут наследовать некоторые стандарты и параметры ограничения использования ресурсов от родительского класса.
CGgroups для Linux Он может выделять ресурсы группе процессов, то есть ЦП, память, пропускную способность сети и другие ресурсы, о которых мы упоминали выше.Благодаря распределению ресурсов CGroup может выполнять следующие функции:
В CGroup все задачи это процесс системы, а CGroup это группа процессов разделенных по определенному стандарту.В механизме CGroup все управление ресурсами реализуется CGroup как единым целым.Каждый процесс Вы можете присоединиться к CGroup в любое время и выйти из CGroup в любое время.линукс Используя файловую систему для реализации CGroup, мы можем напрямую использовать следующую команду, чтобы просмотреть, какие подсистемы находятся в текущей CGroup:
——Введение в CGroup, примеры применения и описание принципа
$ lssubsys -m
cpuset /sys/fs/cgroup/cpuset
cpu /sys/fs/cgroup/cpu
cpuacct /sys/fs/cgroup/cpuacct
memory /sys/fs/cgroup/memory
devices /sys/fs/cgroup/devices
freezer /sys/fs/cgroup/freezer
blkio /sys/fs/cgroup/blkio
perf_event /sys/fs/cgroup/perf_event
hugetlb /sys/fs/cgroup/hugetlb
Большинство дистрибутивов Linux имеют очень похожие подсистемы, а перечисленные выше вещи, такие как набор процессоров, процессор и т. д., называются подсистемами, потому что они могут распределять ресурсы по соответствующим контрольным группам и ограничивать использование ресурсов.
Если мы хотим создать новую контрольную группу, просто создайте новую папку под подсистемой, которую мы хотим выделить или ограничить ресурсы, тогда в этой папке автоматически будет много содержимого, если у вас установлен Docker в Linux, вы найдете папку с именем Docker в каталоге всех подсистем:
$ ls cpu
cgroup.clone_children
...
cpu.stat
docker
notify_on_release
release_agent
tasks
$ ls cpu/docker/
9c3057f1291b53fd54a3d12023d2644efe6a7db6ddf330436ae73ac92d401cf1
cgroup.clone_children
...
cpu.stat
notify_on_release
release_agent
tasks
9c3057xxx на самом деле является контейнером Docker, который мы запускаем. При запуске контейнера Docker создаст CGroup для контейнера с тем же идентификатором контейнера. На текущем хосте CGroup будет иметь следующие иерархические отношения:
Под каждой CGroup есть файл tasks, в котором хранятся идентификаторы процессов всех процессов, принадлежащих к текущей контрольной группе.Как подсистема, отвечающая за ЦП, содержимое файла cpu.cfs_quota_us может ограничивать использование ЦП.Если содержимое текущего файла. Если оно равно 50000, использование ЦП всеми процессами в текущей контрольной группе не может превышать 50%.
Если системный администратор хочет контролировать использование ресурсов контейнера в Docker, он может найти соответствующую подгруппу управления в родительской группе управления Docker и изменить содержимое соответствующих файлов.Конечно, мы также можем использовать параметры непосредственно во время работы программы. , Пусть Процесс Docker для изменения содержимого соответствующего файла.
$ docker run -it -d --cpu-quota=50000 busybox
53861305258ecdd7f5d2a3240af694aec9adb91cd4c7e210b757f71153cdd274
$ cd 53861305258ecdd7f5d2a3240af694aec9adb91cd4c7e210b757f71153cdd274/
$ ls
cgroup.clone_children cgroup.event_control cgroup.procs cpu.cfs_period_us cpu.cfs_quota_us cpu.shares cpu.stat notify_on_release tasks
$ cat cpu.cfs_quota_us
50000
Когда мы используем Docker для закрытия работающего контейнера, папка, соответствующая подгруппе управления Docker, также будет удалена процессом Docker.Когда Docker использует CGroup, он фактически выполняет только некоторые файловые операции для создания папок и изменения содержимого Тем не менее, использование CGroup действительно решает проблему, заключающуюся в том, что мы ограничиваем использование ресурсов подконтейнерами.Системный администратор может разумно распределять ресурсы для нескольких контейнеров без проблемы, когда несколько контейнеров вытесняют ресурсы друг друга.
UnionFS
Пространство имен и контрольная группа Linux решают проблему изоляции различных ресурсов соответственно.Первое решает изоляцию процесса, сети и файловой системы, а второе реализует изоляцию таких ресурсов, как ЦП и память, но в Docker есть еще одна очень важная вещь. Проблема должна быть решена - это зеркалирование.Что такое образ, как он составлен и организован — вопрос, который озадачил автора в течение некоторого времени с тех пор, как он использовал Docker.Мы можем использовать docker run, чтобы загрузить образ Docker из удаленного места и запустить его локально.
Образ Docker на самом деле является сжатым пакетом, мы можем использовать следующую команду для Экспорт файла в образ Docker:
$ docker export $(docker create busybox) | tar -C rootfs -xvf -
$ ls
bin dev etc home proc root sys tmp usr var
Вы можете видеть, что структура каталогов в этом образе busybox не сильно отличается от содержимого корневого каталога операционной системы Linux.Можно сказать, что образ Docker — это файл.
накопитель
Docker использует ряд различных драйверов хранилища для управления файловой системой внутри образа и запуска контейнера.Эти драйверы хранилища несколько отличаются от тома (тома) Docker, а механизм хранилища управляет хранилищем, которое может совместно использоваться несколькими контейнерами.Чтобы понять драйверы хранилища, используемые Docker, нам сначала нужно понять, как Docker создает и хранит образы и как образы Docker используются каждым контейнером; каждый образ в Docker состоит только из серии Каждая команда в Dockerfile создает новый слой поверх существующего слоя только для чтения:
FROM ubuntu:15.04
COPY . /app
RUN make /app
CMD python /app/app.py
Каждый слой в контейнере вносит очень небольшие изменения в текущий контейнер.Вышеупомянутый Dockerfile создаст образ с четырьмя слоями:
Когда образ создается с помощью команды запуска docker, к верхнему слою образа добавляется доступный для записи слой, то есть слой контейнера.Все модификации контейнера среды выполнения фактически являются модификациями слоя чтения-записи контейнера.
Разница между контейнерами и образами в том, что все образы доступны только для чтения, и каждый контейнер фактически равен изображению плюс слой чтения-записи, то есть одному и тому же образу может соответствовать несколько контейнеров.
AUFS
UnionFS на самом деле является службой файловой системы, разработанной для операционной системы Linux для «присоединения» нескольких файловых систем к одной и той же точке монтирования. AUFS или Advanced UnionFS на самом деле является обновленной версией UnionFS, которая обеспечивает более высокую производительность и эффективность.Как объединенная файловая система, AUFS может объединять слои в разных папках в одну папку.Эти папки называются ветками в AUFS.Весь процесс "объединения" называется объединением монтирования (Union).Mount:
Каждый слой образа или слой контейнера представляет собой подпапку в каталоге /var/lib/docker/; в Docker содержимое всех слоев образа и слоев контейнера хранится в /var/lib/docker/aufs/diff/ В каталоге:
$ ls /var/lib/docker/aufs/diff/00adcccc1a55a36a610a6ebb3e07cc35577f2f5a3b671be3dbc0e74db9ca691c 93604f232a831b22aeb372d5b11af8c8779feb96590a6dc36a80140e38e764d8
00adcccc1a55a36a610a6ebb3e07cc35577f2f5a3b671be3dbc0e74db9ca691c-init 93604f232a831b22aeb372d5b11af8c8779feb96590a6dc36a80140e38e764d8-init
019a8283e2ff6fca8d0a07884c78b41662979f848190f0658813bb6a9a464a90 93b06191602b7934fafc984fbacae02911b579769d0debd89cf2a032e7f35cfa
...
И /var/lib/docker/aufs/layers/ хранит метаданные зеркального слоя, каждый файл сохраняет метаданные зеркального слоя, а последний /var/lib/docker/aufs/mnt/ содержит зеркало или контейнер. точка монтирования слоя в конечном итоге будет собрана Docker федеративным образом.
На картинке выше очень хорошо показан процесс сборки Каждый слой изображения строится поверх другого слоя изображения При этом все слои изображения доступны только для чтения, только самый верхний слой контейнера каждого контейнера Все контейнеры строятся на некотором базовые службы (ядро), включая пространства имен, группы управления, rootfs и т. д. Эта сборка контейнера обеспечивает большую гибкость, доступна только для чтения. Зеркальный уровень также может сократить использование диска за счет совместного использования.
Другие накопители
AUFS — это всего лишь один из драйверов хранилища, используемых Docker.Помимо AUFS, Docker также поддерживает различные драйверы хранилища, включая aufs, devicemapper, overlay2, zfs и vfs и т. д. В последней версии Docker overlay2 заменил aufs в качестве рекомендуемого хранилища. драйвер, но по-прежнему использовать aufs в качестве драйвера по умолчанию для Docker на машинах без драйвера overlay2.Различные драйверы хранилища также имеют совершенно разные реализации при хранении образов и файлов-контейнеров.Заинтересованные читатели могут найти соответствующий контент в официальном документе Docker Select a storage driver.
Чтобы узнать, какой драйвер хранилища используется в Docker текущей системы, просто используйте следующую команду для получения соответствующей информации:
$ docker info | grep Storage
Storage Driver: aufs
Поскольку в Ubuntu автора нет драйвера хранилища overlay2, aufs используется в качестве драйвера хранилища по умолчанию для Docker.
Суммировать
Docker стал очень популярной технологией и использовался в производственной среде многих зрелых компаний, но основная технология Docker имеет многолетнюю историю.Три технологии: пространство имен Linux, группа управления и UnionFS поддерживают текущий Docker. Docker также является самой важной причиной появления Docker.Автор проконсультировался с большим количеством информации в процессе изучения принципа реализации Docker, а также получил много знаний, связанных с операционной системой Linux.Однако, поскольку текущая кодовая база Docker слишком велика, я хочу полностью понять с точки зрения исходного кода понять Детали реализации Docker уже очень сложны, но если вас действительно интересуют детали его реализации, вы можете начать с исходного кода Docker CE, чтобы понять принципы Docker.
Оригинальная ссылка:draveness.me/docker