openshift namespace что это
Механизмы контейнеризации: namespaces
Последние несколько лет отмечены ростом популярности «контейнерных» решений для ОС Linux. О том, как и для каких целей можно использовать контейнеры, сегодня много говорят и пишут. А вот механизмам, лежащим в основе контейнеризации, уделяется гораздо меньше внимания.
Все инструменты контейнеризации — будь то Docker, LXC или systemd-nspawn,— основываются на двух подсистемах ядра Linux: namespaces и cgroups. Механизм namespaces (пространств имён) мы хотели бы подробно рассмотреть в этой статье.
Начнём несколько издалека. Идеи, лежащие в основе механизма пространств имён, не новы. Ещё в 1979 году в UNIX был добавлен системный вызов chroot() — как раз с целью обеспечить изоляцию и предоставить разработчикам отдельную от основной системы площадку для тестирования. Нелишним будет вспомнить, как он работает. Затем мы рассмотрим особенности функционирования механизма пространств имён в современных Linux-системах.
Chroot(): первая попытка изоляции
Название chroot представляет собой сокращение от change root, что дословно переводится как «изменить корень». С помощью системного вызова chroot() и соответствующей команды можно изменить корневой каталог. Программе, запущенной с изменённым корневым каталогом, будут доступны только файлы, находящиеся в этом каталоге.
Файловая система UNIX представляет собой древовидную иерархию:
Вершиной этой иерархии является каталог /, он же root. Все остальные каталоги — usr, local, bin и другие, — связаны с ним.
С помощью chroot в систему можно добавить второй корневой каталог, который с точки зрения пользователя ничем не будет отличаться от первого. Файловую систему, в которой присутствует изменённый корневой каталог, можно схематично представить так:
Файловая система разделена на две части, и они никак не влияют друг на друга. Как работает chroot? Сначала обратимся к исходному коду. В качестве примера рассмотрим реализацию chroot в OC 4.4 BSD-Lite.
Системный вызов chroot описан в файле vfs_syscall.c :
Самое главное происходит в предпоследней строке приведённого нами фрагмента: текущая директория становится корневой.
В ядре Linux системный вызов chroot реализован несколько сложнее (фрагмент кода взят отсюда ):
Рассмотрим особенности работы chroot в Linux на практических примерах.
Выполним следующие команды:
В результате выполнения второй команды мы получим сообщение об ошибке:
Ошибка заключается в следующем: не была найдена командная оболочка. Обратим внимание на этот важный момент: с помощью chroot мы создаём новую, изолированную файловую систему, которая не имеет никакого доступа к текущей. Попробуем снова:
Опять ошибка — несмотря на идентичное сообщение, совсем не такая, как в прошлый раз. Прошлое сообщение было выдал шелл, так как не нашёл нужного исполняемого файла. В примере выше об ошибке сообщил динамический линковщик: он не нашёл необходимых библиотек. Чтобы получить к ним доступ, их тоже нужно копировать в chroot. Посмотреть, какие именно динамические библиотеки требуется скопировать, можно так:
После этого выполним следующие команды:
Теперь получилось! Попробуем выполнить в новой файловой системе, например, команду ls:
В ответ мы получим сообщение об ошибке:
Причина понятна: в новой файловой системе команда ls отсутствует. Нужно опять копировать исполняемый файл и динамические библиотеки, как это уже было показано выше. В этом и заключается серьёзный недостаток chroot: все необходимые файлы нужно дублировать. Есть у chroot и ряд недостатков с точки зрения безопасности.
Механизм пространств имён
Пространство имён (англ. namespace) — это механизм ядра Linux, обеспечивающий изоляцию процессов друг от друга. Работа по его реализации была начата в версии ядра 2.4.19.
На текущий момент в Linux поддерживается шесть типов пространств имён:
Пространство имён | Что изолирует |
---|---|
PID | PID процессов |
NETWORK | Сетевые устройства, стеки, порты и т.п. |
USER | ID пользователей и групп |
MOUNT | Точки монтирования |
IPC | SystemV IPC, очереди сообщений POSIX |
UTS | Имя хоста и доменное имя NIS |
Все эти типы используются современными системами контейнеризации (Docker, LXC и другими) при запуске программ.
PID: изоляция PID процессов
Исторически в ядре Linux поддерживалось только одно дерево процессов. Дерево процессов представляет собой иерархическую структуру, подобную дереву каталогов файловой системы.
С появлением механизма namespaces стала возможной поддержка нескольких деревьев процессов, полностью изолированных друг от друга.
При загрузке в Linux сначала запускается процесс с идентификационным номером (PID) 1. В дереве процессов он является корневым. Он запускает другие процессы и службы. Механизм namespaces позволяет создавать отдельное ответвление дерева процессов с собственным PID 1. Процесс, который создаёт такое ответвление, являются частью основного дерева, но его дочерний процесс уже будет корневым в новом дереве.
Процессы в новом дереве никак не взаимодействуют с родительским процессом и даже не «видят» его. В то же время процессам в основном дереве доступны все процессы дочернего дерева. Наглядно это показано на следующей схеме:
Можно создавать несколько вложенных пространств имён PID: один процесс запускает дочерний процесс в новом пространстве имён PID, a тот в свою очередь порождает новый процесс в новом пространстве и т.п.
Один и тот же процесс может иметь несколько идентификаторов PID (отдельный идентификатор для отдельного пространства имён).
Для создания новых пространств имён PID используется системный вызов clone() c флагом CLONE_NEWPID. С помощью этого флага можно запускать новый процесс в новом пространстве имён и в новом дереве. Рассмотрим в качестве примере небольшую программу на языке C (здесь и далее примеры кода взяты отсюда и незначительно нами изменены):
Скомпилируем и запустим эту программу. По завершении её выполнения мы увидим следующий вывод:
Во время выполнения такой маленькой программы в системе произошло много интересных вещей. Функция clone() создала новый процесс, клонировав текущий, и начала его выполнение. При этом она отделила новый процесс от основного дерева и создала для него отдельное дерево процессов.
Попробуeм теперь изменить код программы и узнать родительский PID с точки зрения изолированного процесса:
Вывод изменённой программы будет выглядет так:
Строка «Родительский PID: 0» означает, что у рассматриваемого нами процесса родительского процесса нет. Внесём в программу ещё одно изменение и уберём флаг CLONE_NEWPID из вызова clone():
Системный вызов clone в этом случае сработал практически так же, как fork() и просто создал новый процесс. Между fork() и clone(), однако, есть существенное отличие, которое следует разобрать детально.
Fork() создаёт дочерний процесс, который представляет копию родительского. Родительский процесс копируется вместе со всем контекстом исполнения: выделенной памятью, открытыми файлами и т.п.
В отличие от fork() вызов clone() не просто создаёт копию, но позволяет разделять элементы контекста выполнения между дочерним и родительским процессами. В приведённом выше примере кода с функцией clone используется аргумент child_stack, который задаёт положение стека для дочернего процесса. Как только дочерний и родительский процессы могут разделять память, дочерний процесс не может выполняться в том же стеке, что и родительский. Поэтому родительский процесс должен установить пространство памяти для дочернего и передать указатель на него в вызове clone(). Ещё один аргумент, используемый с функцией clone() — это флаги, которые указывают, что именно нужно разделять между родительским и дочерним процессами. В приведённом нами примере использован флаг CLONE_NEWPID, который указывает, что дочерний процесс должен быть создан в новом пространстве имён PID. Примеры использования других флагов будут приведены ниже.
Итак, изоляцию на уровне процессов мы рассмотрели. Но это — всего лишь первый шаг. Запущенный в отдельном пространстве имён процесс все равно будет иметь доступ ко всем системным ресурсам. Если такой процесс будет слушать, например, 80-й порт, это этот порт будет заблокирован для всех остальных процессов. Избежать таких ситуаций помогают другие пространства имён.
NET: изоляция сетей
Благодаря пространству имён NET мы можем выделять для изолированных процессов собственные сeтевые интерфейсы. Даже loopback-интерфейс для каждого пространства имён будет отдельным.
Сетевые пространства имён можно создавать с помощью системного вызова clone() с флагом CLONE_NEWNET. Также это можно сделать с помощью iproute2:
Воспользуемся strace и посмотрим, что произошло в системе во время приведённой команды:
Как можно помещать процессы в новое сетевое пространство имён?
Во-первых, процесс, создавший новое пространство имён, может порождать другие процессы, и каждый из этих процессов будет наследовать сетевое пространство имён родителя.
/ns/net. Открыв этот файл, мы можем передать файловый дескриптор функции setns().
Можно пойти и другим путём. При создании нового пространства имён с помощью команды ip создаётся файл в директории /var/run/netns/ (см. в выводе трассировки выше). Чтобы получить файловый дескриптор, достаточно просто открыть этот файл.
Сетевое пространство имён нельзя удалить при помощи какого-либо системного вызова. Оно будет существовать, пока его использует хотя бы один процесс.
MOUNT: изоляция файловой системы
Об изоляции на уровне файловой системы мы уже упоминали выше, когда разбирали системный вызов chroot (). Мы отметили, что системный вызов chroot() не обеспечивает надёжной изоляции. С помощью же пространств имён MOUNT можно создавать полностью независимые файловые системы, ассоциируемые с различными процессами:
Для изоляции файловой системы используется системный вызов clone() c флагом CLONE_NEWNS:
Сначала дочерний процесс «видит» те же точки монтирования, что и родительский. Как только дочерний процесс перенесён в отдельное пространство имён, к нему можно примонтировать любую файловую системы, и это никак не затронет ни родительский процесс, ни другие пространства имён.
Другие пространства имён
Вывод этой программы будет выглядеть так:
Как видим, функция child_fn() выводит имя узла, изменяет его, а затем выводит уже новое имя. Изменение происходит только внутри нового пространства имён.
Заключение
В этой статье мы в общих чертах рассмотрели, как работает механизм namespaces. Надеемся, она поможет вам лучше понять принципы работы контейнеров.
По традиции приводим ссылки на интересные дополнительные материалы:
Рассмотрение механизмов контейнеризации мы обязательно продолжим. В следующей публикации мы расскажем о механизме cgroups.
Projects and Users
Users
Interaction with OpenShift is associated with a user. An OpenShift user object represents an actor which may be granted permissions in the system by adding roles to them or to their groups.
Several types of users can exist:
Regular users
This is the way most interactive OpenShift users will be represented. Regular users are created automatically in the system upon first login, or can be created via the API. Regular users are represented with the User object. Examples: joe alice
System users
Many of these are created automatically when the infrastructure is defined, mainly for the purpose of enabling the infrastructure to interact with the API securely. They include a cluster administrator (with access to everything), a per-node user, users for use by routers and registries, and various others. Finally, there is an anonymous system user that is used by default for unauthenticated requests. Examples: system:admin system:openshift-registry system:node:node1.example.com
Service accounts
These are special system users associated with projects; some are created automatically when the project is first created, while project administrators can create more for the purpose of defining access to the contents of each project. Service accounts are represented with the ServiceAccount object. Examples: system:serviceaccount:default:deployer system:serviceaccount:foo:builder
Every user must authenticate in some way in order to access OpenShift. API requests with no authentication or invalid authentication are authenticated as requests by the anonymous system user. Once authenticated, policy determines what the user is authorized to do.
Namespaces
A Kubernetes namespace provides a mechanism to scope resources in a cluster. In OpenShift, a project is a Kubernetes namespace with additional annotations.
Namespaces provide a unique scope for:
Named resources to avoid basic naming collisions.
Delegated management authority to trusted users.
The ability to limit community resource consumption.
Most objects in the system are scoped by namespace, but some are excepted and have no namespace, including nodes and users.
The Kubernetes documentation has more information on namespaces.
Projects
A project is a Kubernetes namespace with additional annotations, and is the central vehicle by which access to resources for regular users is managed. A project allows a community of users to organize and manage their content in isolation from other communities. Users must be given access to projects by administrators, or if allowed to create projects, automatically have access to their own projects.
The mandatory name is a unique identifier for the project and is most visible when using the CLI tools or API. The maximum name length is 63 characters.
The optional displayName is how the project is displayed in the web console (defaults to name ).
The optional description can be a more detailed description of the project and is also visible in the web console.
Each project scopes its own set of:
Objects
Pods, services, replication controllers, etc.
Policies
Rules for which users can or cannot perform actions on objects.
Constraints
Quotas for each kind of object that can be limited.
Service accounts
Service accounts act automatically with designated access to objects in the project.
Cluster administrators can create projects and delegate administrative rights for the project to any member of the user community. Cluster administrators can also allow developers to create their own projects.
Developers and administrators can interact with projects using the CLI or the web console.
Тестирование в Openshift: Внутреннее устройство кластера
Это продолжение серии из трех статей (первая статья, третья статья) об автоматизированном тестировании программных продуктов в Openshift Origin. В данной статье будут описаны основные объекты Openshift, а также описаны принципы работы кластера. Я осознано не делаю попытку описать все возможные объекты и их особенности, так как это очень трудоемкая задача, которая выходит за рамки данной статьи.
Кластер:
В целом работа кластера Openshift Origin не сильно отличается от других решений. Поступающие задачи распределяются по рабочим узлам на основе их загруженности, данное распределение берет на себя планировщик.
Для запуска контейнеров требуется Docker образа, которые могут быть загружены из внутреннего или внешнего регистра. Непосредственно запуск контейнеров происходит в различных контекстах безопасности (политики безопасности, которые ограничивают доступ контейнера к различным ресурсам).
По умолчанию контейнеры из разных проектов могут коммуницировать друг с другом с помощью overlay сети (выделяется одна большая подсеть, которая разбивается на более мелкие подсети для всех рабочих узлов кластера). Запущенному на рабочем узле контейнеру выделяется IP-адрес из той подсети, которая была назначена данному узлу. Сама overlay сеть построена на базе Open vSwitch, который использует VXLAN для связи между рабочими узлами.
На каждом рабочем узле запускается выделенныё экземпляр Dnsmasq, который перенаправляет все DNS запросы контейнеров на SkyDNS во внутреннюю сервисную подсеть.
Если контейнер аварийно завершил свою работу или просто не может быть проинициализирован, то задача по его развертыванию передается на другой рабочий узел.
Стоит отметить что:
SELinux не является строгим условием работы кластера. Отключение оного (не рекомендуется по соображениям безопасности) привнесет некое увелечение скорости (равно как и отключение мониторинга, кстати) при работе с контейнерами. Если SELinux мешает работе приложения в контейнере, присутствует возможность добавления исключения SELinux непосредственно на рабочем узле кластера.
По умолчанию используется LVM в качестве хранилища Docker Engine. Это далеко не самое быстрое решение, но можно использовать любой другой тип хранилища (BTRFS, например).
Стоит иметь ввиду, что название сервиса (см. Service) — это DNS имя, которое влечет за собой ограничения на длину и допустимые символы.
Чтобы сократить временные и аппаратные издержки при сборке Docker образов можно использовать так называемый «слоистый» подход (multi-stage в Docker). В данном подходе используются базовые и промежуточные образа, которые дополняют друг друга. Имеем базовый образ «centos:7» (полностью обновлен), имеем промежуточный образ «centos:7-tools» (установлены иструменты), имеем финальный образ «centos:7-app» (содержит «centos:7» и «centos:7-tools»). То есть вы можете создавать задачи сборки, которые основываются на других образах (см. BuildConfig).
Достаточно гибким решением является подход, когда существует один проект, который занимается только сборкой Docker образов с последующей «линковкой» данных образов в другие проекты (см. ImageStream). Это позволяет не плодить лишних сущностей в каждом проекте и приведет к некой унификации.
Большинству объектов в кластере можно присвоить произвольные метки, с помощью которых можно совершать массовые операции над данными объектами (удаление определенных контейнеров в проекте, например).
Стоит сразу побеспокоиться об удалении старых образов и забытых окружений. Если первое решается с помощью сборщика мусора/oadm prune, то второе требует проработки и ознакомлении всех участников с правилами совместной работы в Openshift Origin.
Любой кластер ограничен ресурсами, поэтому очень желательно организовать мониторинг хотя бы на уровне рабочих узлов (возможен мониторинг на уровне приложения в контейнере). Сделать это можно как с помощью готового решения Openshift Metrics, так и с помощью сторонних решений (Sysdig, например). При наличии метрик загруженности кластера (в целом или по проектно) можно организовать гибкую диспетчерезацию поступающих задач.
Особенно хочется отметить тот факт, что рабочие узлы могут быть динамически проинициализированы, а это значит, что вы можете расширить свой кластер Openshift Origin на существующих мощностях IaaS. То есть, во время предрелизного тестирования вы можете существенно расширить свои мощности и сократить время тестирования.
Объекты:
Project — объект является Kubernetes namespace. Верхний уровень абстракции, который содержит другие объекты. Созданные в проекте объекты не пересекаются с объектами в других проектах. На проект могут быть выставлены квоты, привилегии, метки узлов кластера и т.д. Вложенная иерархия и наследование между проектами отсутствуют, доступна «плоская» структура проектов. Существуюет несколько системных проектов (kube-system, openshift, openshift-infra), которые предназначены для нормального функционирования кластера.
Создание нового проекта:
Редактирование настроек проекта:
Pod — объект, который стал одним из решающих факторов, так как позволяет запускать произвольные команды внутри контейнера с помощью специальных хуков (и не только). Pod является основной рабочей единицей в кластере. Любой запущенный в кластере контйенер — Pod. По своей сути — группа из одного и более контейнеров, которые работают в единых для этих контейнеров namespaces (network, ipc, uts, cgroup), используют общее хранилище данных, секреты. Контейнеры, из которых состоит Pod, всегда запущены на одном узле кластера, а не распределены в одинаковых пропорциях по всем узлам (если Pod будет состоять из 10 контейнеров, все 10 будут работать на одном узле).
Secret — может являться строкой или файлом, предназначен для проброса чувствительной (хранится в открытом виде в etcd (поддержка шифрования в Kubernetes 1.7)) информации в Pod. Один Secret может содержать множество значений.
Использование Secret в BuildConfig:
ServiceAccount — специальный тип объекта, который предназначен для взаимодействия с ресурсам кластера. По своей сути является системным пользователем.
По умолчанию новый проект содержит три ServiceAccount:
Перечисленные служебные аккаунты:
Добавление прав администратора проекта ServiceAccount:
DeploymentConfig — это объект, который оперирует всё теми же Pod, но при этом привносит ряд дополнительных механизмов для управления жизненным циклом запущенных приложений, а именно:
ImageStream — по своей сути является «контейнером» для «ссылок» (ImageStreamTag), которые указывают на Docker образа или другие ImageStream.
Создание тага/ссылки на Docker образ между проектами:
Создание тага/ссылки на Docker образ, который расположен на Docker Hub:
BuildConfig — объект является сценарием того, как будет собран Docker образ и куда он будет помещен. Сборка нового образа может базироваться на других образах, за это отвечает секция «from:»
Источники сборки (то место, где размещены исходные данные для сборки):
Стратегии сборки (каким образом следует интерпретировать источник данных):
Назначение сборки (куда будет выгружен собранный образ):
Какие операции выполнит данный BuildConfig:
Service — объект, который стал одним из решающих факторов при выборе системы запуска сред, так как он позволяет гибко настраивать коммуникации между средами (что очень важно в тестировании). В случаях с использованием других систем требовались подготовительные манипуляции: выделить диапазоны IP-адресов, зарегистрировать DNS имена, осуществить проброс портов и т.д. и т.п. Service может быть объявлен до фактического развертывания приложения.
Что происходит во время публикации сервиса в проекте:
Разрешение DNS имени:
Заключение:
Все объекты кластера можно описать с помощью YAML, это, в свою очередь, дает возможность полностью автоматизировать любые процессы, которые протекают в Openshift Origin. Вся сложность в работе с кластером заключается в знании приципов работы и механизмов взаимодействия объектов. Такие рутинные операции как инициализация новых рабочих узлов берут на себя сценарии Ansible. Доступность API открывает возможность работать с кластером напрямую минуя посредников.
Pods and Services
OpenShift Enterprise leverages the Kubernetes concept of a pod, which is one or more containers deployed together on one host, and the smallest compute unit that can be defined, deployed, and managed.
Pods are the rough equivalent of OpenShift Enterprise v2 gears, with containers the rough equivalent of v2 cartridge instances. Each pod is allocated its own internal IP address, therefore owning its entire port space, and containers within pods can share their local storage and networking.
Pods have a lifecycle; they are defined, then they are assigned to run on a node, then they run until their container(s) exit or they are removed for some other reason. Pods, depending on policy and exit code, may be removed after exiting, or may be retained to enable access to the logs of their containers.
OpenShift Enterprise treats pods as largely immutable; changes cannot be made to a pod definition while it is running. OpenShift Enterprise implements changes by terminating an existing pod and recreating it with modified configuration, base image(s), or both. Pods are also treated as expendable, and do not maintain state when recreated. Therefore manage pods with higher-level controllers, rather than directly by users.
The following example definition of a pod provides a long-running service, which is actually a part of the OpenShift Enterprise infrastructure: the private Docker registry. It demonstrates many features of pods, most of which are discussed in other topics and thus only briefly mentioned here.
Pods can be «tagged» with one or more labels, which can then be used to select and manage groups of pods in a single operation. The labels are stored in key-value format in the metadata hash. One label in this example is docker-registry=default. |
Pods must have a unique name within their namespace. A pod definition may specify the basis of a name with the generateName attribute and random characters will be added automatically to generate a unique name. |
containers specifies an array of container definitions; in this case (as with most), just one. |
Environment variables can be specified to pass necessary values to each container. |
Each container in the pod is instantiated from its own Docker image. |
The container can bind to ports which will be made available on the pod’s IP. |
OpenShift Enterprise defines a security context for containers which specifies whether they are allowed to run as privileged containers, run as a user of their choice, and more. The default context is very restrictive but administrators can modify this as needed. |
The container specifies where external storage volumes should be mounted within the container. In this case, there is a volume for storing the registry’s data, and one for access to credentials the registry needs for making requests against the OpenShift Enterprise API. |
Pods making requests against the OpenShift Enterprise API is a common enough pattern that there is a serviceAccount field for specifying which service account user the pod should authenticate as when making the requests. This enables fine-grained access control for custom infrastructure components. |
The pod defines storage volumes that are available to its container(s) to use. In this case, it provides an ephemeral volume for the registry storage and a secret volume containing the service account credentials. |
This pod definition does not include attributes that are filled by OpenShift Enterprise automatically after the pod is created and its lifecycle begins. The Kubernetes API documentation has complete details of the pod REST API object attributes, and the Kubernetes pod documentation has details about the functionality and purpose of pods.
Services
A Kubernetes service serves as an internal load balancer. It identifies a set of replicated pods in order to proxy the connections it receives to them. Backing pods can be added to or removed from a service arbitrarily while the service remains consistently available, enabling anything that depends on the service to refer to it at a consistent internal address.
Services are assigned an IP address and port pair that, when accessed, proxy to an appropriate backing pod. A service uses a label selector to find all the containers running that provide a certain network service on a certain port.
Like pods, services are REST objects. The following example shows the definition of a service for the pod defined above:
The service name docker-registry is also used to construct an environment variable with the service IP that is inserted into other pods in the same namespace. The maximum name length is 63 characters. |
The label selector identifies all pods with the docker-registry=default label attached as its backing pods. |
Virtual IP of the service, allocated automatically at creation from a pool of internal IPs. |
Port the service listens on. |
Port on the backing pods to which the service forwards connections. |
The Kubernetes service documentation has more information about services.
Labels
Labels are used to organize, group, or select API objects. For example, pods are «tagged» with labels, and then services use label selectors to identify the pods they proxy to. This makes it possible for services to reference groups of pods, even treating pods with potentially different Docker containers as related entities.
Most objects can include labels in their metadata. Labels can be used to group arbitrarily-related objects; for example, all of the pods, services, replication controllers, and deployment configurations of a particular application can be grouped.
Labels are simple key-value pairs, as in the following example:
A pod consisting of an nginx Docker container, with the label role=webserver.
A pod consisting of an Apache httpd Docker container, with the same label role=webserver.
A service or replication controller that is defined to use pods with the role=webserver label treats both of these pods as part of the same group.
The Kubernetes labels documentation has more information about labels.