Что такое Docker и технология контейнеров Linux
Как и FreeBSD Jails и Solaris Zones, контейнеры Linux — независимые среды выполнения с собственными центральным процессором, памятью, блоком ввода-вывода и сетевыми ресурсами, которые используют ядро гостевой ОС. На выходе вы получаете что-то вроде виртуальной машины, которая работает как надстройка гостевой ОС.
В широкомасштабной системе, когда вас у работают виртуальные серверы, это обычно значит, что вы используете множество дубликатов одной и той же ОС и много лишних загрузочных томов.
В среде приложений с требованиями веб-масштабирования контейнеры — более привлекательная среда, чем традиционная серверная виртуализация.
Чтобы понять, что такое контейнеры, нужно начать с контрольных групп и пространства имен Linux, — функциями ядра Linux, которые создают стены между контейнерами и другими процессами, проходящими на хосте. Пространства имен Linux создают оболочку для набора системных ресурсов и представляют их процессу.
Контрольные группы Linux управляют изолированием и использованием системных ресурсов — центральным процессором, памятью, — для группы процессов. Например, если ваше приложение занимает много циклов ЦП и памяти, вы можете поместить его в контрольную группу, чтобы ограничить использование памяти и ЦП.
Пространства имен занимаются изолированием ресурсов для одного процесса, а контрольные группы управляют ресурсами для группы процессов.
От LXC до Docker
Оригинальная технология контейнеров Linux называется Linux Containers, или LXC.
LXC — это метод виртуализации на уровне ОС предназначенный для того, чтобы запускать множество изолированных систем Linux на одном хосте.
Контейнеры отделяют приложения от операционных систем. Это значит, что у пользователей есть чистая минимальная Linux ОС, и можно запускать все процессы в одном или нескольких изолированных контейнерах.
Так как операционная система отделена от контейнеров, вы можете перемещать контейнер на любой Linux-сервер, который поддерживает операционную среду контейнера.
Docker, который начался как проект, чтобы строить LXC-контейнеры под одно приложение, серьезно изменил LXC и сделал контейнеры более портативными и гибкими.
Docker — это открытая платформа для разработки, доставки и эксплуатации приложений. Используя контейнеры Docker, вы можете развертывать, копировать, переносить и делать резервные копии информации быстрее и легче, чем при помощи виртуальной машины. В принципе Docker привносит облакоподобную гибкость в любую инфраструктуру, которая может работать на контейнерах.
Давайте рассмотрим Docker детальнее и посмотрим, чем Docker отличается от LXC в разделе ниже.
Технология контейнеризации Docker
Хотя Docker начался как проект с открытым кодом для того, чтобы строить специализированную LXC, он позже превратился в собственную контейнерную среду исполнения. Docker — это инструмент Linux, который эффективно создает, отправляет и запускает контейнеры.
Контейнеры Docker и LXC — легковесные механизмы виртуализации в пользовательском пространстве, которые применяют контрольные группы и пространства имен, чтобы управлять изолированием ресурсов. Но между Docker и LXC есть несколько фундаментальных отличий — рассмотрим их.
Единственный процесс vs множество процессов
Docker ограничивает контейнеры, заставляя их работать как единый процесс. Если ваша среда приложения состоит из X одновременных процессов, Docker запустит X контейнеров, каждый со своим процессом. В отличие от Docker, LXC контейнеры могут запускать множество процессов.
Чтобы запустить простое многоуровневое веб-приложение в Docker, вам понадобится PHP контейнер, Nginx контейнер (веб-сервер), MySQL контейнер (для процесса базы данных) и несколько контейнеров данных для того, чтобы хранить таблицы баз данных и другую информацию приложения.
У однопроцессных контейнеров много преимуществ, включая простые и более мелкие обновления. Вам не нужно убивать процесс баз данных, когда вы хотить обновить только веб-сервер. Также у однопроцессных контейнеров эффективная архитектура для того, чтобы строить приложения, основанные на микросервисах.
У однопроцессных контейнеров также есть ограничения. Например, вы не можете запускать агенты, скрипты регистрации или автоматически запускаемые SSH-процессы внутри контейнера. Также нелегко незначительно обновлять однопроцессный контейнер на уровне приложения. Вам придется запускать новый обновленный контейнер.
Бесструктурность vs структурность
Контейнеры Docker сделаны так, чтобы быть более бесструктурными, чем LXC.
Во-первых, Docker не поддерживает внешнее хранилище. Docker обходит это тем, что позволяет вам подключать хранилище хоста в качестве тома Docker из ваших контейнеров. Так как тома подключаются, они не считаются частью среды контейнера.
Во-вторых, контейнеры Docker состоят из слоев в режиме чтения. Это значит, что как только создается образ контейнера, он не меняется. Во время выполнения программы, если процесс в контейнере меняет свое внутреннее состояние, создается разница между внутренним состоянием и образом, из которого был создан контейнер.
Если вы выполняете команду docker commit, разница между двумя версиями становится частью нового образа — не оригинального, а нового, из которого вы можете создавать новые контейнеры. Если вы удалите контейнер, разница версий исчезнет.
Бесструктурный контейнер — интересная сущность. Вы можете обновлять контейнер, но серия обновлений создаст серию новых образов контейнеров, поэтому в системе так легко откатываться.
Портативность
Пожалуй, это самое важное преимущество Docker над LXC. Docker больше отделяет сетевые ресурсы, хранилище и детали ОС, чем LXC. С Docker приложение действительно не зависит от настроек этих низкоуровневых ресурсов. Когда вы перемещаете контейнер Docker от одного хоста Docker к другой машине с Docker, Docker гарантирует, что среда для приложения останется неизменной.
Прямое преимущество этого подхода — это то, что Docker помогает программистам создавать локальные среды разработки, которые выглядят как продакшн-сервер. Когда программист заканчивает писать и начинает тестировать код, он может обернуть его в контейнер, опубликовать напрямую на сервере или в приватном облаке, и он сразу будет работать, так как это одна и та же среда.
СLXC программист может запустить что-то на своей машине, но обнаружить, что код работает неправильно при разворачивании на сервере. Среда сервера будет другой, и программисту придется потратить много времени, чтобы починить эту разницу и исправить проблему. С Docker этих проблем нет.
Архитектура, разработанная для программистов
Отделение приложений от аппаратного обеспечения, лежащего в их основе, — фундаментальный концепт виртуализации. Контейнеры идут еще дальше и отделяют приложения от ОС. Благодаря этой особенности программисты получают гибкость и масштабирование при разработке.
Как Docker помог нам достичь (почти) невозможного
С тех пор как мы начали работать над Iron.io, мы пытались решить проблему поддержания наших IronWorker-контейнеров в актуальном состоянии относительно новых сред выполнения и пакетов Linux. В течение последних двух лет IronWorker использовал одну и ту же среду выполнения без изменений. Пока, несколько недель назад, мы не выпустили в продакшен различные окружения для языков программирования.
С момента создания нашего сервиса, мы использовали только один контейнер, который содержал набор языковых сред и бинарных пакетов — Ruby, Python, PHP, Java, .NET и другие языки, а также библиотеки такие как ImageMagick, SoX и другие.
Этот контейнер и стратегия его использования начали устаревать, равно как и Ruby 1.9.1, Node 0,8, Mono 2 и прочие языки со старыми версиями, которые использовались в стеке по умолчанию. Со временем проблема стала ещё острее, поскольку люди начали использовать новые вещи, но были вынуждены изменять свой код для работы со старыми версиями языков.
Ограниченный одним LXC контейнером
IronWorker использует LXC контейнеры для изоляции ресурсов и обеспечения безопасности во время выполнения задач. LXC прекрасно работал в качестве компонента исполнения, но падал время от времени, когда дело доходило до интеграции с разного рода окружениями, необходимыми для обработки задач. Мы были в тупике, когда дело дошло до создания сред выполнения. С одной стороны, мы не могли просто обновить версии в существующем контейнере, иначе мы бы рисковали уничтожить миллион с лишними задач, которые выполняются каждый день. (Мы пробовали это однажды в начале запуска сервиса, и ни к чему хорошему это не привело)
Мы также не могли хранить различные LXC-контейнеры c разными версиями языков, поскольку они содержат полные копии операционной системы и библиотек (то есть
2 ГБ на каждый образ). На самом деле это прекрасно работало бы в PaaS среде, такой как Heroku где процессы идут бесконечно, и можно просто получить правильный контейнер перед началом запуска процесса. В такой ситуации, были бы большие пользовательские образы для каждого клиента, но в случае с IronWorker всё иначе.
IronWorker это большая многопользовательская система обработки задач, где пользователи добавляют задачи в очередь, и эти задачи выполняются в тысячах обработчиков. Он может быть использован для разгрузки основного потока исполнения путем запуска в фоновом режиме, запуска запланированных задач, непрерывной обработки транзакций и потоков сообщений или выполнения параллельной обработки на большом количестве ядер. Преимуществом является то, что у пользователей есть возможность обработки по первому требованию и при этом очень большой параллелизм без каких-либо усилий.
Внутри сервис работает следующим образом: получает задачу из набора очередей, на конкретную VM устанавливает среду выполнения, загружает код задачи, а затем запускает процесс. Суть сервиса подразумевает, что все машины постоянно используются всеми клиентами. Мы не выделяем машину для конкретных приложений или клиентов на длительный период времени. Задачи, как правило, выполняются недолго. Некоторые работают в течение всего нескольких секунд или минут, а максимальное время работы ограничено шестьюдесятью минутами.
LXC работал как надо, но мы ломали голову над тем, как обновить или добавить что-либо к нашему существующему контейнеру, не ломая обратную совместимость и не используя безумное количество дискового пространства. Наши варианты казались довольно ограниченными и поэтому мы откладывали решение.
… и тогда пришел Docker
Впервые мы услышали о Docker’е более года назад. Мы помогли организовать GoSF MeetUp и Соломон Хайкс, создатель Docker’а посетил конференцию в марте 2013 и продемонстрировал свой новый проект Docker, который был написан на языке Go. На самом деле он опубликовал его именно в тот день, это был первый раз когда его кто-то увидел.
Демо было отличным, и более ста разработчиков в аудитории были впечатлены, тем что он и его команда сделали. (И сразу же, о чем свидетельствует один из его комментариев, Соломон начал новую методологию разработки под названием Shame Driven Development)
«Увы, он был слишком сырой», говорили мы днем ранее, «проект не был готов к продакшену, но действительно был достоен похвалы».
Соломон Хайкс и Трэвис Ридер на саммите OpenStack в 2013 году.
Месяцем позже, я встретился с Соломоном на OpenStack Summit в Портленде, чтобы поработать вместе и посмотреть, как мы могли бы использовать Docker для решения нашей задачи. (Я думал, что я пойду только на одну встречу, однако вместо этого мы провели очень много времени, работая с Соломоном и другими разработчиками).
Я играл с Docker’ом, а Соломон помогал мне понять, что он может делать и как работает. Это был не просто хороший проект, он решал трудную задачу хорошо спроектированным путем. И не делало его ущербным, то что он был первопроходцем написанным на языке Go и не имел приличного техничеcкого долга, по крайней мере с моей точки зрения.
Исследование и разработка
До Docker’a мы пробовали различные менеджеры пакетов в том числе попробовали поработать с Nix. Nix отличный проект и у него есть много хороших достоинств, но к сожалению, это было не совсем то, что нам было нужно. Nix поддерживает атомарные обновления, откаты и имеет декларативный подход к конфигурации системы.
К сожалению, было трудно поддерживать скрипты для различных программ и библиотек, которые мы используем в наших образах, а также было трудно добавить некоторые пользовательские пакеты и программы. Усилия, необходимые для интеграции скриптов, библиотек и прочего были больше похожи на патчи для нашей системы. Мы искали нечто иное, что могло бы приблизить нас к удовлетворению наших требований.
В начале были такие требования:
Обеспечить различные версии одних и тех же языков (то есть ruby 1.9 и ruby 2.1)
Иметь безопасный способ обновить одну часть системы, не нарушая другие части (например обновить только python-библиотеки и не трогать ruby-библиотеки)
Использовать декларативный подход к конфигурации системы (простые скрипты которые описывают, то что должно быть внутри образа)
Создать простой способ проведения обновлений и их отката
В процессе стало ясно, что есть несколько других преимуществ использования Docker’а, о существовании которых мы и не предпологали. К ним относятся:
Создание отдельных и изолированных сред для каждого runtime/языка
Получение поддержки для CoW файловой системы (что переводит нас на более безопасный и эффективный уровень в управлении образами).
Получение надежного способа переключения между различными средами выполнения на лету
Работа с Docker’ом
Работая с Docker’ом, было не трудно интегрировать его, так как мы уже использовали LXC. (Docker дополняет LXC высокоуровневым API, выполняющимся на уровне процесса. Ссылка на StackOverflow ниже.)
После того как мы мигрировали наши существующие shell скрипты в Dockerfiles и создали образы, все что нам оставалось сделать, чтобы перейти от непосредственного использования LXC — это «docker run» (вместо «lxc-execute ‘) и указать ID образа, необходимого для каждой задачи.
Команда для запуска LXC-образа:
lxc-execute -n VM_NAME -f CONFIG_FILE COMMAND
Команда для запуска Docker-образа:
docker run -i -name=VM_NAME worker:STACK_NAME COMMAND
Следует отметить что мы немного отходим от рекомендуемых подходов по созданию и установке контейнеров.
Стандартный подход — либо создавать образы во время выполнения с помощью Dockerfiles, либо хранить их в закрытых/открытых репозиториях в облаке. Вместо этого мы создаем образы, а затем делаем снепшоты из них и храним в EBS, прикрепленной к нашей системе. Это сделано потому, что система должна стартовать очень быстро. Создание образов во время выполнения было плохим вариантом, даже загрузка их из внешнего хранилища была бы слишком медленной.
Базовые образы плюс Diff’ы
Использование Docker’a также решило проблему дискового пространства, так как каждый образ это просто набор изменений (diff) от базового образа. А это значит, что мы можем иметь один базовый образ, содержащий операционную систему и библиотеки Linux которые мы используем во всех образах, и использовать его в качестве основы для множества других образов. Размер унаследованного образа включает в себя лишь размер отличий от базового образа.
Например если установить Ruby, в новом образе будут содержатся только файлы, которые были установлены с Ruby. Чтобы это не казалось запутанным, давайте думать об этом, как о репозитарии Git, содержащем все файлы на компьютере, где базовый образ является веткой master, а все другие образы — это различные ветки, произведённые от базового образа. Эта способность включать различия и создавать образы на основе существующих контейнеров очень полезна, поскольку это позвол нам постоянно выпускать новые версии, добавлять библиотеки, пакеты и больше концентрироваться на решении задач.
Некоторые проблемы
У нас было несколько проблем при создании и внедрении новых окружений при помощи Docker’а, но серьезных среди них не было.
У нас были некоторые трудности, связанные с удалением контейнеров после запуска задачи. Процесс удаления контейнера иногда падал, но мы нашли довольно чистое решение.
При настройке некоторых программных компонентов, мы обнаружили что Docker неправильно эмулириет некоторые низкоуровневые функции, такие как fuse. В результате, нам пришлось прибегнуть к некоторой магии, чтобы получить корректно работающий образ Java.
Ну вот и всё. Вопросы к разработчикам Docker’а, главным образом сводились к нескольким исправлениям А что касается нового функционала Docker’а — того что есть нам достаточно. (Мы до сих пор ничего не пытались добавить в функцинал, поскольку существующий набор функций довольно обширен).
LXC, Контейнеры и Docker
LXC (LinuX Containers) — система виртуализации на уровне операционной системы, которая обеспечивает безопасный способ изолировать один или несколько процессов от других процессов, запущенных в одной и той же системе Linux. При использовании контейнеров ресурсы могут быть изолированы, сервисы ограничены, а процессам выделяется изолированное пространство операционной системы с собственной структурой файловой системы и сетевых интерфейсов. Несколько контейнеров могут использовать одно и то же ядро, но каждый контейнер может быть ограничен использованием только определенного количество ресурсов, таких как CPU, память и операции ввода-вывода. В результате, приложения, задачи и другие процессы могут быть сконфигурированы так, чтобы запускаться в качестве нескольких легких, изолированных экземпляров Linux на одной машине.
Docker построен поверх LXC, что позволяет осуществлять управление образами и развертыванием. Вот статья на StackOverflow от Соломона о различиях и совместимости между LXC и Docker:
Если вы взгляните на особенности Docker, большинство из них уже предусмотрено в LXC. Итак, что же добавляет Docker? Зачем мне использовать Docker, а не простой LXC?
Docker не является заменой LXC. „LXC“ относится к возможностям ядра Linux (в частности пространства имен и контрольные группы), которые позволяют изолировать процессы друг от друга, и контролировать распределение их ресурсов.
Docker предлагает инструмент высокого уровня с несколькими мощными функциональными возможностями поверх низкоуровневых функций ядра.
Читать далее >>
Docker в продакшене
Docker — основа „стеков“ IronWorker’a
Мы в настоящее время используем Docker в продакшене в рамках сервиса IronWorker. Вы можете выбрать один из 10 различных „стеков“ (контейнеров) для ваших задач, установив параметр „стек“ при загрузке кода. Если задуматься, это удобная возможность — вы можете указать версию языка для краткосрочной задачи, которая будет выполняться на любом количество ядер.
Использование Docker для управления образами позволяет обновлять образы, не боясь повредить другие части системы. Другими словами мы можем обновить образ Ruby 1.9, не трогая образ Ruby 2.1. (Поддержание согласованности имеет первостепенное значение в любой крупномасштабной системе, особенно когда вы поддерживаете большой набор языков).
У нас также есть более автоматизированный процесс для обновления образов с помощью Dockerfiles, что позволяет нам разворачивать обновления по предсказуемому графику. Кроме того, у нас есть возможность создавать пользовательские образы. Они могут быть сформированы по конкретной версии языка и/или включать определённые фреймворки и библиотеки.
Заглядывая в будущее
Решение использовать Docker в продакшене не было чрезвычайно рискованным шагом. Год назад возможно так было, но сейчас это стабильный продукт. То, что это новый продукт, является в наших глазах преимуществом. Он имеет минимальный набор возможностей и построен для крупномасштабных и динамичных облачных сред как наша.
Мы взглянули на Docker изнутри и узнали людей стоящих за ним, но даже без этого, Docker был бы естественным выбором. Плюсов очень много, а минусов почти нет.
И, на правах совета, мы предлагаем использовать „готовые к использованию“ Dockerfiles, скрипты и обшедоступные образы. Там есть много полезного, с чего можно начать. На самом деле мы вероятно сделаем наши Dockerfiles и образы обшедоступными, что означает, что люди будут иметь возможность легко запускать свои воркеры локально, а также мы сделаем возможность отправлять pull reqeust’ы для их улучшения.
Обработка десятки тысяч часов процессорного времени и миллионы задач каждый день почти в каждом языке — это не просто. Docker позволил нам решить некоторые серьезные проблемы ценой небольших усилий. Это увеличило нашу способность к инновациям, а также к созданию новых возможностей для IronWorker. Но, что не менее важно, это позволяет нам сохранить и даже превзойти гарантированные условия обслуживания над улучшением которых мы много работаем.
Docker имеет большое будущее, и мы рады, что приняли решение включить его в наш стек технологий.