Вы наверное уже много читали о контейнерах и вам не терпится изучить технологию в деталях. Однако, перед тем как углубиться в рассуждения об архитектуре и развертывании контейнеров в производственном окружении, есть три важные вещи, которые разработчики, архитекторы и системные администраторы должны знать :
Все приложения, включая контейнеры, опираются на ядро операционной системы .
Ядро обеспечивает API (интерфейс прикладного программирования) для этих приложений через системные вызовы (system calls).
Управление версиями API важно, так как они обеспечивают прямую связь между пространствами ядра и пользователя .
Все процессы осуществляют системные вызовы:
Обращение процесса к ядру
Так как контейнеры являются процессами, они тоже совершают системные вызовы:
Обращение контейнера к ядру
Хорошо, мы разобрались с процессами и поняли, что контейнер – тоже процесс. Но что насчет файлов и программ внутри образа контейнера? Эти файлы и программы составляют так называемое пространство пользователя . Когда запускается контейнер, программа загружается в память из образа контейнера. После запуска программы в контейнере, она выполняет системные вызовы в пространство ядра. Возможность пространства пользователя взаимодействовать с ядром имеет критическое значение.
Пространство пользователя
Пространство пользователя относится ко всему коду в операционной системе, находящемуся вне ядра. Большинство UNIX-подобных систем (включая Linux) поставляются с разными видами предустановленных утилит, языков программирования и графических инструментов – все это приложения пространства пользователя.
Пользовательские приложения могут включать программы, написанные на C, Java, Python, Ruby и других языках. В мире контейнеров эти программы обычно доставляются в формате образа докер (docker).
Когда вы извлекаете образ контейнера RHEL7 (Red Hat Enterprise Linux 7) из официального докер-репозитория Red Hat, вы используете минимальное предустановленное пространство пользователя. Туда входят базовые утилиты, такие как bash, awk, grep и yum (чтобы вы могли установить остальной софт).
docker run -i -t rhel7 bash
Все пользовательские программы (в контейнерах или нет) работают, управляя данными, но где эти данные находятся? Они могут поступать из регистров центрального процессора и внешних устройств, но чаще всего они хранятся в памяти и на диске. Пользовательские программы получают доступ к данным, через специальные запросы к ядру, которые называются системными вызовами. Например аллоцирование памяти или открытие файла. В памяти и файлах часто содержится конфиденциальная информация, принадлежащая разным пользователям. Так что доступ должен запрашиваться у ядра через «syscall».
Пространство ядра
Ядро обеспечивает уровень абстракции для безопасности, оборудования и внутренних структур данных. Системный вызов open()
обычно используется для получения файлового дескриптора в Python, C, Ruby и других языках. Вам не понравится, если программы смогут вносить изменения в файловую систему XFS, поэтому ядро предоставляет системный вызов и обрабатывает драйвера. Этот системный вызов настолько распространен, что включен в библиотеку POSIX .
Заметьте, на следующем изображении bash совершает вызов getpid()
, который запрашивает свой идентификатор процесса. Также, обратите внимание, что команда cat()
запрашивает доступ к /etc/hosts через вызов open()
. В следующей статье мы подробно разберемся, как это работает в контейнерной среде, но отметим, что часть кода находится в пространстве пользователя, а часть в ядре.
Различные системные вызовы от программ пространства пользователя
Обычные пользовательские программы постоянно создают системные вызовы, чтобы выполнить свою задачу, например, ls, ps, top и bash.
А вот некоторые пользовательские программы, которые почти напрямую соответствуют системным вызовам: chroot, sync, mount/umount, swapon/swapoff.
Если копнуть глубже, то вот еще примеры системных вызовов, которые совершают перечисленные программы: open, getpid, socket. Обычно эти функции вызываются через библиотеки, такие как glibc, или интерпретатор Ruby, Python, Java Virtual Machine.
Стандартная программа получает доступ к ресурсам ядра через уровни абстракции, похожие на следующую диаграмму:
Пример взаимодействия программы с ядром
Чтобы понять, какие системные вызовы есть в ядре Linux, советуем посмотреть справку man syscalls. Мы выполняем эту команду на RHEL7, но используем образ контейнера RHEL6, чтобы увидеть, какие системные вызовы были добавлены или вырезаны в прошлой версии ядра.
docker run -t -i rhel6-base man syscalls
Пример мануала по системным вызовам
Обратите внимание, что некоторые системные вызовы были добавлены или удалены в разных версиях ядра. Линус Торвальдс и другие разработчики позаботились, чтобы их поведение было стабильным и прозрачно понятным. В RHEL7 (версия ядра 3.10) доступно 382 системных вызова. Время от времени появляются новые, другие устаревают – это стоит учитывать при планировании вашей контейнерной инфраструктуры и приложений в ней.
Заключение
Ключевые выводы для понимания разницы между пространством ядра и пользователя:
Приложения содержат бизнес-логику, но зависят от системных вызовов.
После компиляции приложения набор используемых вызовов встраивается в бинарный файл (в языках более высокого уровня – в интерпретатор или виртуальную машину).
Контейнер не абстрагируется от необходимости пространств пользователя и ядра в использовании общего набора системных вызовов.
В мире контейнеров, пользовательское пространство упаковывается и доставляется до разных хостов, от ноутбуков до промышленных серверов.
В ближайшие годы это вызовет проблемы.
Спустя время трудно будет гарантировать, что созданный сегодня контейнер запустится на хостах в будущем. Представьте, что в 2024 году у вас на промышленном сервере все еще будет контейнерное приложение, работающее в окружении RHEL7. Как безопасно обновить инфраструктуру под этим контейнером? Будет ли это приложение также хорошо работать на новейших доступных хостах?
В следующей части статьи мы рассмотрим как взаимодействие пространств ядра/пользователя влияет на архитектурные решения, и что можно сделать для уменьшения проблем.