Почему использование большого количества хостов обходится так дорого? Когда для каждого хоста нужен физический сервер, ответ вполне очевиден. Если вы работаете именно в такой обстановке, то вам, наверное, подойдет модель размещения нескольких сервисов на одном хосте, хотя не удивлюсь, если это станет еще более сложным препятствием. Но я подозреваю, что большинство из вас использует какую-либо виртуализацию. Виртуализация позволяет разбить физический сервер на несколько обособленных хостов, на каждом из которых могут запускаться разные программные средства. Следовательно, если нам нужно разместить по одному сервису на каждом хосте, можем ли мы просто разбить физическую инфраструктуру на все более мелкие и мелкие куски?
Ну кто-то это, вероятно, и может сделать. Но разбиение машины на постоянно увеличивающееся количество виртуальных машин не дается бесплатно. Представим нашу физическую машину ящиком для носков. Если поставить в ящике много деревянных перегородок, то больше или меньше носков мы сможем в нем хранить? Правильный ответ — меньше: разделители также занимают место! Нужно, чтобы с нашим ящиком было проще работать и проще наводить в нем порядок, и, возможно, теперь мы решим положить в него не только носки, но еще и футболки, но чем больше разделителей, тем меньше будет общее пространство.
В мире виртуализации у нас есть такие же издержки, как и перегородки в ящике для носков. Чтобы понять природу возникновения этих издержек, посмотрим, как чаще всего выполняется виртуализация. На рис. 6.9 для сравнения представлены два типа виртуализации. Слева показано использование нескольких различных уровней так называемой виртуализации второго типа, которая реализуется в AWS, VMWare, VSphere, Xen и KVM. (К виртуализации первого типа относятся технологии, при которых виртуальные машины запускаются непосредственно на оборудовании, а не в виде надстройки над другой операционной системой.) В нашей физической инфраструктуре имеется основная операционная система. Под управлением этой операционной системы запускается так называемый гипервизор, имеющий две основные задачи. Во-первых, он отображает ресурсы, подобные ресурсам центрального процессора и памяти, с виртуального хоста на физический хост. Во-вторых, он работает в качестве уровня управления, позволяющего манипулировать самими виртуальными машинами.
Внутри виртуальных машин мы получаем то, что похоже на совершенно различные хосты. На них можно запустить их собственные операционные системы с их собственными ядрами. Их можно рассматривать почти как загерметизированные машины, изолированные гипервизором от используемого физического хоста и от других виртуальных машин.
Проблема в том, что для выполнения своей работы гипервизору нужно выделять ресурсы. Это отнимает ресурсы центрального процессора, системы ввода-вывода и памяти, которые могли бы использоваться в других местах. Чем большим количеством хостов управляет гипервизор, тем больше ресурсов ему требуется. К определенному моменту эти издержки станут препятствовать дальнейшему разбиению физической инфраструктуры. На практике это означает, что часто при нарезке физической машины на все более мелкие части убывает отдача ресурсов, поскольку пропорционально этому все больше и больше этих ресурсов уходит на издержки гипервизора.
Рис. 6.9. Сравнение стандартной виртуализации второго типа и облегченных контейнеров
Vagrant представляет собой весьма полезную платформу развертывания, которая используется скорее для разработки и тестирования, чем для работы в производственном режиме. Vagrant предоставляет вам на вашем ноутбуке виpтуальное облако. Снизу она использует стандартную систему виртуализации (обычно VirtualBox, хотя может использовать и другие платформы). Она позволяет определять набор виртуальных машин в текстовом файле, а также определять, как виртуальные машины связаны по сети и на каких образах они должны быть основаны. Этот тестовый файл может проверяться и совместно использоваться сотрудниками команды.
Это упрощает для вас создание среды подобной той, что используется в производственном режиме на вашей локальной машине. Одновременно можно запускать несколько виртуальных машин, останавливать работу отдельных машин для тестирования сбойных режимов и отображать виртуальные машины на локальные каталоги, чтобы можно было вносить изменения и тут же отслеживать реакцию на них. Даже для тех команд, которые используют такие облачные платформы, выделяющие ресурсы по запросу, как AWS, более быстрая реакция от применения Vagrant может стать большим подспорьем в разработке.
Один из недостатков заключается в том, что запуск большого количества виртуальных машин может привести к перегрузке разработочной машины. Если у нас на каждой виртуальной машине имеется по одному сервису, то может исчезнуть возможность переноса всей системы на свою локальную машину. Это может привести к тому, что вам, чтобы добиться управляемости, придется ставить заглушки на некоторые зависимости, и это станет еще одной задачей, с которой нужно будет справиться, чтобы обеспечить качественное проведение развертывания и тестирования.
У пользователей Linux имеется альтернатива виртуализации. Вместо использования гипервизора для сегментирования и управления отдельными виртуальными хостами Linux-контейнеры создают обособленное пространство для процессов, в котором проходят остальные процессы.
В Linux процесс запускается конкретным пользователем и имеет конкретные возможности, основанные на особенностях настройки прав пользователей. Процессы могут порождать другие процессы. Например, если я запущу процесс в терминале, то этот процесс обычно считается дочерним процессом процесса терминала. Обслуживанием дерева процессов занимается ядро Linux.
Linux-контейнеры являются расширением этого замысла. Каждый контейнер, по сути, является поддеревом общесистемного дерева процессов. У этих контейнеров могут быть выделенные им физические ресурсы, и этим ядро управляет для нас. Этот общий подход был воплощен во многих формах, таких как Solaris Zones и OpenVZ, но наибольшую популярность снискал LXC-контейнер. LXC в готовом виде доступен во многих современных ядрах Linux.
Если посмотреть на блок-схему для хоста с работающим LXC-контейнером (см. рис. 6.9), можно увидеть несколько различий. Во-первых, нам не нужен гипервизор. Во-вторых, хотя каждый контейнер может запускать собственный дистрибутив операционной системы, он должен совместно использовать одно и то же ядро (поскольку процесс дерева запускается именно в ядре). Это означает, что в качестве основной операционной системы может быть запущена Ubuntu, а в контейнерах — CentOS, поскольку обе они могут совместно использовать одно и то же ядро.
Но мы не только получаем преимущества от ресурсов, сэкономленных за счет ненужности гипервизора. Мы также выигрываем из-за ускоренной обратной реакции. Linux-контейнеры предоставляются намного быстрее, чем полноценные виртуальные машины. Для виртуальных машин вполне в порядке вещей тратить несколько минут на запуск, а при использовании Linux-контейнеров запуск может занять всего несколько секунд. Вы приобретаете также более тонкий контроль над самими контейнерами в плане выделения им ресурсов, что существенно упрощает тонкую подстройку под получение большей отдачи от использующегося оборудования.
Благодаря более легким характеристикам контейнеров мы можем получить от них намного большую отдачу при запуске на одном и том же оборудовании, которую возможно было бы получить при использовании виртуальных машин. За счет развертывания по одному сервису в каждом контейнере (рис. 6.10) мы получаем определенную степень изолированности от других контейнеров (хотя ее нельзя назвать идеальной) и можем получить более высокий экономический эффект, чем тот, который был бы возможен, если бы нам захотелось запустить каждый сервис на его собственной виртуальной машине.
Контейнеры могут использоваться также с полноценной виртуализацией. Мне приходилось видеть не один проект с предоставлением довольно крупного экземпляра AWS EC2 и запуском на нем LXC-контейнеров для получения наилучших качеств, присущих обоим мирам: предоставляемую по запросу эфемерную вычислительную платформу в виде EC2 в сочетании с весьма гибкими и быстрыми контейнерами, запущенными на ее основе.
Рис. 6.10. Запуск сервисов в отдельных контейнерах
Но у Linux-контейнеров есть и свои проблемы. Представьте, что у меня имеется масса микросервисов, запущенных в их собственных контейнерах на хосте. Как они станут видны внешнему миру? Нужен некий способ, направляющий внешний мир через используемые контейнеры, то есть то, чем при обычной виртуализации занимаются многие гипервизоры. Я видел, как многие тратили уйму времени на настройку перенаправления портов с использованием для непосредственного показа контейнеров таблиц IPTables. Кроме этого, следует принять во внимание, что эти контейнеры нельзя рассматривать как абсолютно герметичные по отношению друг к другу. Существует множество задокументированных и известных способов, позволяющих процессу из одного контейнера сбежать и вступить во взаимодействие с другими контейнерами или с используемым хостом. Некоторые из этих проблем вызваны конструктивными особенностями, а часть из них связана с недочетами, находящимися в процессе устранения, но в любом случае, если не испытывается доверие к запускаемому коду, не ждите, что сможете запустить его в контейнере и при этом остаться в безопасности. Если нужна повышенная изолированность, то следует присмотреться к использованию виртуальных машин.
Docker представляет собой платформу, являющуюся надстройкой над облегченными контейнерами. Она вместо вас справляется с большим объемом работы по управлению контейнерами. В Docker ведется создание и развертывание приложений, что для мира ви