Эволюция концепций, положенных в основу Docker, шла постепенно; идеи созревали в течение длительного времени. На самом деле существует несколько весьма эффективных подходов к платформе, и в частности к защите контейнера Docker (https://docs.docker.com/engine/security/security/), позволяющих сгруппировать процессы и заставить их работать, при этом не теряя над ними контроля.
В вычислительной технике реализован принцип, согласно которому операционная система делится на привилегированную и непривилегированную области. В Linux, наследнице UNIX, это соответствует пользователю root и пользователю с ограниченными правами. Несанкционированное овладение привилегиями root часто приводит к тому, что нарушитель заставляет приложения с привилегиями root совершать некоторые непозволительные действия для менее привилегированного пользователя.
Идея найти способ построить крепость вокруг процессов или поместить их в метафорическую песочницу, в которой они могут резвиться, гораздо старше, чем сами контейнеры. В некоторых разновидностях UNIX первые стены был возведены путем превращения непривилегированного процесса в изолированный.
Эти первые варианты изоляции, запускаемые командой chroot в версии BSD (Berkeley Software Distribution, подразделение AT&T UNIX), позволяли администратору ограничить область, в которой могла действовать вызванная команда. Непривилегированный процесс мог вызвать процесс root, но область, в которой мог работать этот непривилегированный процесс, была изолирована. Это простой и достаточно эффективный метод. Затем разработчики FreeBSD, ответвления BSD, предложили концепцию камеры (jail). Камеры были начальными контейнерами, реализующими концепцию размещения программного кода, данных и стены между всеми этими процессами и процессами root.
В операционных системах SunOS и Solaris компании Sun Microsystems (ныне Oracle) камеры были преобразованы в зоны. В зонах Solaris программный код, данные и хранилища превратились в более атомарный элемент, то есть объект. Зону можно перемещать, назначить ей групповой доступ и рассматривать как набор объектов, возможно скрепленный другим программным кодом. Зоны были переносимы, но только если на всех ваших серверах или системах использовалась операционная система Solaris.
С появлением программы Virtuozzo концепция была перенесена на более распространенные платформы (отличные от Solaris и SunOS), где она применялась в основном для виртуализации ресурсов — в качестве первых виртуальных машин. В Virtuozzo были реализованы элементы гипервизора, позволяющие представлять разнообразные ресурсы операционной системы и даже оборудования. Примерно в то же время появился продукт Xen и началось распространение гипервизоров, поддерживающих многочисленные функции операционных систем со стенами, обеспечиваемыми функциями управления памятью процессора.
Развитие гипервизоров породило SuSE (теперь SUSE) Xen, Citrix Xen и VMware. Эти системы рассчитаны на значительную нагрузку в виде процессов, включая целые операционные системы, благодаря модели памяти 64-разрядных процессоров Intel и AMD. В результате серверы могут выделять большое пространство памяти операционным системам, существующим на одном сервере параллельно.
Гипервизоры предоставляют аппаратные службы для операционных систем, а характеристики этих аппаратных служб определяются по выбору администратора.
Однако концепция контейнера была скромнее. Вместо того чтобы поддерживать несколько операционных систем, каждая из которых полагала бы, что владеет всем компьютером через механизм гипервизора, программный код контейнеров предназначен для выполнения немногих функций. Он использует приложения и ресурсы в одном экземпляре операционной системы сервера виртуальных машин. Экземпляры на основе гипервизора — полноценные операционные системы: существующие на одном компьютере Windows, Linux, BSD и в некоторых случаях даже macOS. Экземплярам ничего не известно друг о друге, они полагают, что владеют собственными аппаратными средствами сервера, выделенными административными компонентами гипервизора. Структуры же контейнера существуют в модели с сокращенными привилегиями, контролируемыми модулем chroot и новыми группами управления, cgroups.
Контейнеры процессов появились в Linux, и Linux стала платформой для экспериментов как с контейнерами (минималистский подход), так и с гипервизорами (полноценная операционная система). Компания Google развивала контейнеры процессов в соответствии с концепцией Linux, назвав их cgroups (от control groups). В них реализованы многие возможности выполнения программного кода с использованием ресурсов компьютера и одного общего ядра операционной системы.
Кроме того, в одной команде cgroups задаются различные характеристики: размер памяти, который можно задействовать; выделенное время процессора; ресурсы файловой системы; ресурсы диска и памяти и параметры сети. Cgroups ограждают ресурсы, которые выделяются приложению и одновременно предоставляют метод контроля над тем, какая доля этих ресурсов действительно используется. Та же концепция применялась к гипервизорам, чтобы одна виртуальная машина не доминировала над ресурсами сервера в ущерб другим процессам. Концепция cgroups устанавливала границы и была встроена в ядро Linux.
В пространствах имен происходит дальнейшее группирование объектов как сущности. Каждый объект существует с более низким уровнем разрешений, не может затрагивать другие выполняемые процессы, но все же способен выполнять работу и может быть идентифицирован как уникальная сущность и при этом использует общее ядро Linux или BSD. У пространств имен могут быть дочерние пространства имен, что обеспечивает возможность изолировать характеристики объекта, дочернего объекта и внучатого объекта друг от друга. Благодаря изоляции процессов работа может быть выполнена дочерними процессами без помех со стороны родительского или других дочерних процессов. Сетевые подключения, связь между процессами и, что важно, процессы, которым назначены идентификаторы в Linux, могут быть переведены на более высокий или низкий уровень. Кроме того, их доступностью можно управлять через определение и развертывание пространств имен.
В контейнерах LXC (LinuX Containers) получила дальнейшее развитие идея сосуществования нескольких операционных систем благодаря концептуализации понижения привилегий пользователя и группы с применением chroot, в зависимости от ограничений ресурсов cgroup. LXC был в меньшей степени похож на гипервизор и ориентирован строго на Linux. Было предпринято несколько попыток его размещения в Windows, но гипервизоры предоставляют ядра, лучше обеспечивающие потребности в ресурсах операционной системы Windows.
LXC может создавать целые экземпляры сред операционных систем с общим ядром для программных рабочих нагрузок с помощью единственной командной строки. В свою очередь, эти экземпляры могут работать, не будучи осведомленными, и поэтому с минимальной возможностью повлиять на запросы ресурсов других параллельных экземпляров операционной системы и связанных с ними рабочих нагрузок. Экземпляры операционных систем могут быть «сведены к минимуму» в смысле установленного программного обеспечения, в соответствии с нуждами приложений.
В случае с LXC несколько рабочих приложений может быть размещено на одном сервере — они работают автономно, при тех же ограничениях по ресурсам, с административным управлением нужными параметрами.
Далее встал вопрос об объединении этих концепций, и появилось несколько пакетов для управления группами cgroup и пространствами имен рабочих приложений, коммуникации между рабочими приложениями в иерархиях объектов и клонирования этих элементов контейнеров. Такие инфраструктуры, наряду с использованием ими процессора, памяти, хранилищ данных, сетевых подключений и взаимосвязей, можно рассматривать как стеки.
Появление Docker
На данном этапе, примерно в 2012 году, появилось несколько стеков контейнеров, в том числе Docker (https://www.docker.com/).
Docker помещает все параметры и области управления в переносимый формат образа, обеспечивая целостность, и указывает взаимозависимости, сетевые подключения, хранилища и связи базового образа. Docker предоставляет типовой набор интерфейсов и элементов управления и обеспечивает их взаимодействие друг с другом (возможно, по иерархии). При этом он изолирует рабочие нагрузки друг от друга в нужной степени и регулирует использование контейнерами ресурсов (процессор, хранилища, сетевые подключения). Кроме того, система управляет элементами безопасности и настройками, предоставляя переносимый формат файла для упаковки всех компонентов в один образ Docker.
Docker отличается чрезвычайной простотой использования: после того, как вы загрузите систему Docker для данной операционной системы сервера виртуальных машин, направляется запрос на загрузку из репозитория образов Docker. Доступны сотни тысяч образов Docker с компонентами для выполнения любых задач, от обслуживания базы данных или веб-сайта Wordpress до запуска экземпляра операционной системы. Образы Docker представлены в сотнях языков, версий и настроек. Как правило, образы Docker бесплатны и почти всегда относятся к открытому исходному коду. К тому же их легко настроить для повторного использования.
Обычно образы компактны, и Docker обеспечивает шлюз к ресурсам сервера; ограничения на ресурсы для любого контейнера или группы контейнеров могут контролироваться администраторами.
Обеспечение безопасности контейнеров Docker
Однако при всем разнообразии и глубине настроек существует проблема: в прошлом происхождение образов Docker часто оставалось неизвестным, если только не был проверен каждый элемент контейнера. Это практически невозможно для обычного человека, поэтому достоверность образов было сложно подтвердить. Отчасти дело в широком разнообразии путей изменения, исправления или обновления образов, что вызывает опасения в отношении уязвимости элементов образа. Следствием уязвимости может быть остановка контейнеров, утечка данных (в случае компрометации) и ошибочное или вредоносное поведение.
По мере роста популярности разработки контейнеров все больше компаний начинают выпускать «официальные» образы экземпляров в формате Docker. Например, компания Canonical предоставляет ряд базовых образов, относительно происхождения компонентов которых нет никаких вопросов. В образы можно вносить изменения в соответствии с основными профилями использования, в частности образы для стека LAMP, базы данных и web.
Образы, одобренные поставщиком и разработчиком, способствовали росту популярности Docker. Например, образы, созданные для различных языков, предоставляют дополнительные варианты для официальных репозиториев образов, и многие из этих вариантов присутствуют в «официальном» репозитории Docker.
Однако проблема не была решена, поскольку отсутствовал инструментарий для проверки целостности составляющих образа. Это приходилось делать вне рамок контроля за Docker, то есть образ мог именоваться официальным, но в нем должна была быть подпись, удостоверяющая, что его содержимое не менялось. Изменение может указывать на перемены в настройках, иные, нежели ожидалось, версии программного обеспечения и даже внедрение вредоносных программ или опасные изменения в файле параметров.
В прошлом даже использование «официальных» образов было сопряжено с риском, поскольку было трудно определить цепочку ответственности за целостность образов, и пользователю приходилось брать на себя функции администратора.
Тем временем собственно Docker выполняется как процесс root на сервере. Благодаря нескольким степеням защиты механизм Docker использует планировщик базового сервера для создания экземпляров, запуска, изменения и удаления образов, то есть обслуживания всего жизненного цикла развертывания Docker. Можно ли управлять Docker с помощью порождаемых им образов? Об этом речь пойдет в следующей части статьи.