Перевод сделан автором сайта goxpert.ru
Краткое содежание перевода
- Пространство имен в Linux является абстракцией от ресурсов операционной системы.
- Пространства имен инкапсулируют системные ресурсы, связанные с различными аспектами системы.
- Пространства имен являются необходимым условием для запуска любого процесса в системе.
- Коробки позволяют добавлять и удалять содержимое без влияния на другие коробки.
- Пространства имен могут содержать одну и ту же копию ресурсов, что может привести к изменениям, видимым во всех других пространствах имен.
- Контейнеры являются обычными процессами с разными пространствами имен, отличными от других процессов.
- Изоляция в контейнерах достигается с помощью пространств имен, чем меньше количество совместно используемых ресурсов, тем более изолированным является процесс.
- В оставшейся части поста будет рассмотрена реализация программы isolate, которая запускает командный процесс в новом процессе, изолированном от остальной системы.
Глубокое погружение в пространства имен Linux
В этой серии постов мы внимательно рассмотрим один из основных компонентов контейнера - пространства имен. В процессе мы создадим более простой клон docker run команды - нашу собственную программу, которая примет в качестве входных данных команду (вместе с ее аргументами, если таковые имеются) и запустит процесс в контейнере для ее запуска, изолированный от остальной системы, подобно тому, как вы сделали бы docker run это из image - образа.
Что такое пространство имен?(What is a namespace?)
Пространство имен Linux - это абстракция над ресурсами операционной системы. Мы можем представить пространство имен как коробку. Внутри этого блока находятся системные ресурсы, которые в точности зависят от типа блока (пространства имен - namespaces). В настоящее время существует 7 типов пространств имен Cgroup, IPC, Network, Mount, PID, User, UTS.
Например, Network пространство имен инкапсулирует системные ресурсы, связанные с сетью, такие как сетевые интерфейсы (например, wlan0, eth0), таблицы маршрутов(route tables) и т.д., Mount пространство имен инкапсулирует файлы и каталоги в системе, PID содержит идентификаторы процессов и так далее. Итак, два экземпляра Network пространства имен A и B (соответствующие двум блокам одного типа в нашей аналогии) могут содержать разные ресурсы - возможно, A содержит wlan0, тогда как B содержит eth0 и другую копию таблицы маршрутов(route table).
Пространства имен - это не какая-то дополнительная функция или библиотека, которую вам нужно установить apt, они предоставляются самим ядром Linux и уже являются необходимым условием для запуска любого процесса в системе. В любой данный момент любой процесс P принадлежит ровно одному экземпляру каждого типа пространства имен - поэтому, когда ему нужно сказать, обновить таблицу маршрутов в системе, Linux показывает ему копию таблицы маршрутов пространства имен, к которому он принадлежит в этот момент.
Для чего это нужно? (What is it good for?)
Абсолютно ни для чего… шучу. Одна хорошая вещь с ящиками заключается в том, что вы можете добавлять и удалять содержимое из одного ящика, и это не повлияет на содержимое других ящиков. Здесь та же идея с пространствами имен - процесс P может сойти с ума и выплнить sudo rm -rf / но другой процесс, Q который принадлежит другому Mount пространству имен, не пострадает, поскольку они используют разные копии этих файлов.
Однако обратите внимание, что ресурс, инкапсулированный в пространство имен, не обязательно означает, что это уникальная копия. В ряде случаев, либо по замыслу, либо из-за бреши в системе безопасности, два или более пространств имен будут содержать одну и ту же копию, например, одного и того же файла, так что изменения, внесенные в этот файл в одном Mount пространстве имен, фактически будут видны во всех других Mount пространствах имен, которые также ссылаются на него. По этой причине мы удаляем нашу аналогию с ящиками, поскольку элемент не может одновременно существовать в двух разных ящиках 😞.
Неразделенность - это забота (Unsharing is caring)
Мы можем видеть пространства имен, к которым принадлежит процесс! В типичном для Linux виде они отображаются в виде файлов в каталоге /proc/$pid/ns для данного процесса с идентификатором процесса $pid:
1 | $ ls -l /proc/$$/ns |
Вы можете открыть второй терминал и запустить ту же команду, и она должна выдать вам точно такой же результат - это потому, что, как мы упоминали ранее, процесс должен принадлежать некоторому пространству имен, и если мы явно не укажите, какие именно, Linux добавит их в качестве элемента в пространства имен по умолчанию.
Давайте немного разберемся в этом. Во втором терминале мы можем запустить что-то вроде:
1 | $ hostname |
Команда unshare запускает программу (необязательно) в новом пространстве имен. -u Флаг сообщает ей о запуске bash в новом UTS пространстве имен. Обратите внимание, как наш новый bash процесс указывает на другой uts файл, в то время как все остальные остаются прежними.
- Для создания новых пространств имен обычно требуется доступ суперпользователя. С этого момента мы будем предполагать, что оба unshare или наша реализация выполняются с sudo.
Одно из следствий того, что мы только что сделали, заключается в том, что теперь мы можем изменять систему hostname изнутри нашего нового bash процесса, и это не повлияет ни на один другой процесс в системе. Вы можете убедиться в этом, запустив hostname в первой оболочке или новой и увидев, что имя хоста там не изменилось.
Но, например, что такое контейнер? (But like, what is a container though?)
Надеюсь, теперь у вас есть некоторое представление о том, что может делать пространство имен. Вы могли бы предположить, что контейнеры - это в основе своей обычные процессы с пространствами имен, отличными от других процессов, и вы были бы правы. На самом деле контейнер без кавычек(квот) не обязательно должен принадлежать уникальному пространству имен для каждого типа - он может использовать некоторые из них совместно.
Например, когда вы docker run –net=host redis, все, что вы делаете, это говорите docker не создавать новое Network пространство имен для redis процесса, и, как мы видели, Linux добавит этот процесс в качестве члена пространства имен по умолчанию Network, как и любой другой обычный процесс. Итак, процесс redis точно такой же, как и все остальные, с точки зрения сетевого взаимодействия. Сетевое взаимодействие здесь не является чем-то особенным,docker run давайте сделаем эту настройку для большинства пространств имен. Возникает вопрос о том, что вообще такое контейнер? Является ли процесс, который разделяет все пространства имен, кроме одного, все еще контейнером? _(ツ)_/
Обычно контейнеры поставляются с концепцией изоляции, достигаемой с помощью пространств имен - чем меньше количество пространств имен и ресурсов, совместно используемых процессом, тем более изолированным является процесс, и это все, что действительно имеет значение.
Программа Isolate (Isolate)
В оставшейся части этого поста мы заложим основу для нашей программы, которую мы назовем isolate. isolate принимает команду в качестве аргументов и выполняет эту команду в новом процессе, изолированном от остальной системы и находящемся в своих собственных пространствах имен. В следующих публикациях мы рассмотрим добавление поддержки отдельных пространств имен, когда isolate запустим в процессе управления.
С точки зрения области применения мы сосредоточимся на User, Mount, PID и Network пространствах имен. Остальные относительно тривиальны для реализации после того, как мы закончим (фактически, здесь мы добавляем UTS поддержку в начальной реализации) и, Cgroup например, интересны только с точки зрения, выходящей за рамки этой серии (изучение cgroups - другого ингредиента в контейнерах, который используется для контроля того, какой объем ресурсов разрешено использовать процессу).
Пространства имен могут очень быстро усложняться, поэтому при изучении каждого пространства имен мы можем использовать множество различных путей, но мы не можем использовать их все. Мы обсудим только те пути, которые имеют отношение к программе, которую мы создаем. Каждый пост будет начинаться с некоторых экспериментов с данным пространством имен в терминале в попытке понять взаимодействия, связанные с настройкой этого пространства имен. После этого у нас уже будет представление о том, чего мы хотим достичь, и затем мы приступим к соответствующей реализации в isolate.
Чтобы не засорять посты кодом, мы не будем включать такие вещи, как вспомогательные функции, которые не являются необходимыми для понимания реализации. Вы можете найти полный исходный код здесь, на Github.
Реализация (Implementation)
Исходный код этого поста можно найти здесь.
Наша isolate реализация изначально будет представлять собой простую программу, которая считывает путь к команде из stdin и клонирует новый процесс, который выполняет команду с указанными аргументами. Клонированный командный процесс будет выполняться в своем собственном UTS пространстве имен точно так же, как мы делали с unshare ранее. В последующих публикациях мы увидим, что пространства имен не обязательно работают (или даже обеспечивают изоляцию) “из коробки”, и нам нужно будет выполнить некоторые настройки после их создания (но перед выполнением фактической команды), чтобы команда действительно выполнялась изолированно.
Эта комбинация создания и настройки пространства имен потребует некоторого взаимодействия между основным isolate процессом и дочерним командным процессом. В результате частью основной работы здесь будет настройка канала связи между обоими процессами - мы будем использовать Linux pipe из-за его простоты с учетом нашего варианта использования.
Нам нужно сделать три вещи:
- Создайте основной isolate процесс, который считывает данные из stdin.
- Клонируйте новый процесс, который выполнит команду в новом UTS пространстве имен.
- Настройте канал таким образом, чтобы командный процесс начинал выполнение команды только после получения сигнала от основного процесса о том, что настройка пространства имен завершена.
Вот основной процесс:
1 |
|
Обратите внимание на clone_flags, которые мы передаем в наш вызов clone. Видите, как просто создать процесс в его собственном namespace? Всё, что нам нужно сделать, это установить флаг для типа namespace (CLONE_NEWUTS флаг соответствует UTS namespace), а Linux позаботится об остальном.
Далее процесс команды ожидает сигнала перед её запуском:
1 | static int cmd_exec(void *arg) |
Наконец, мы может попробовать это запустить:
1 | $ ./isolate sh |
В настоящее время isolate - это немного больше, чем программа, которая просто разветвляет команду (у нас есть UTS то, что работает для нас). В следующем посте мы сделаем еще один шаг вперед, рассмотрев User пространства имен и заставим isolate запустить команду в ее собственном User пространстве имен. Там мы увидим, что нам действительно нужно проделать некоторую работу, чтобы иметь полезное пространство имен, в котором может выполняться команда.