В современном мире информационных технологий часто упоминают виртуальные машины и контейнеры. Вы могли слышать, как разработчики говорят: “Запусти сервис в контейнере Docker” или “Разверни ещё одну виртуалку на сервере”. Что же скрывается за этими терминами и почему они так важны? Давайте разберёмся простыми словами, в чём разница между виртуальными машинами и контейнерами, и почему эти технологии появились.
Представьте, что у вас есть один мощный физический компьютер, но вам нужно использовать его для нескольких задач одновременно, причём так, чтобы эти задачи не мешали друг другу. Можно, конечно, поставить несколько отдельных компьютеров, но это дорого и громоздко. Виртуальные машины (Virtual Machines, VM) и контейнеры (Containers) – два разных подхода к решению этой проблемы. Оба позволяют разделить ресурсы одного компьютера между несколькими “изолированными” средами, но делают это по-разному.
Главная интрига: виртуальная машина создаёт полноценный “компьютер внутри компьютера” с собственной операционной системой, а контейнер изолирует приложение без полной эмуляции железа, используя общую операционную систему хоста.
Что такое виртуальная машина?
Виртуальная машина (ВМ) – это, по сути, программный эмулятор целого компьютера. Иногда говорят, что ВМ – это “компьютер внутри компьютера”. Представьте себе кино про сны внутри сна – здесь похожая идея: на одном физическом компьютере мы запускаем программу, которая ведёт себя как отдельный компьютер со своей операционной системой. Например, можно на ноутбуке с Windows запустить виртуальную машину с Linux или даже с другой Windows. Каждая виртуалка думает, что у неё собственный процессор, память, диск и так далее, хотя на самом деле они виртуализированы – предоставлены специальной программой, называемой гипервизором.
Вот простая аналогия: виртуальная машина – как отдельный дом. У каждого дома есть своя инфраструктура: собственное отопление, водопровод, электричество. На одной улице (физическом сервере) можно построить несколько домов (виртуальных машин). Каждый дом полностью автономен: если в одном случится пожар или авария с электричеством, соседи в других домах этого не почувствуют. Однако за такую самостоятельность приходится платить ценой ресурсов – каждый дом строится “с нуля” и требует материалов (в мире ВМ – выделенной памяти, процессора и образа целой ОС).
Рис. 1: Схематическое сравнение архитектуры виртуальных машин и контейнеров.
На рисунке видно главное различие: виртуальные машины виртуализируют весь компьютер, включая аппаратное обеспечение, тогда как контейнеры виртуализируют только программную среду поверх существующей операционной системы.
Преимущества виртуальных машин
- Полная изоляция и безопасность. Виртуальные машины работают полностью обособленно, как самостоятельные системы. Приложения внутри ВМ изолированы от программ в других виртуалках: сбой или взлом в одной не затронет другие. Это как раз случай с домами: обрушение одного дома не приводит к обрушению соседних. Поэтому ВМ часто используют для запуска потенциально опасного кода – например, компьютерных вирусов – в песочнице, чтобы проверить их поведение без риска для основной системы. Если вредоносная программа "сломает" виртуалку, ваш реальный компьютер останется невредим.
- Собственная операционная система на выбор. Каждая ВМ включает свою ОС, поэтому вы не ограничены одной системой. Можно на одном физическом сервере параллельно запустить, скажем, Windows, Linux и macOS. Это удобно, если разные приложения требуют разные операционные системы. Для бизнеса это означает гибкость: можно переносить готовую виртуальную машину с одной площадки на другую, не беспокоясь, какая ОС стоит на физическом сервере – ведь ВМ несёт свою собственную среду.
- Поддержка устаревших или специфичных приложений. Виртуальные машины позволяют запускать старое программное обеспечение. Например, у вас есть приложение, работающее только на Windows XP – вы можете создать виртуалку с Windows XP на современном компьютере и спокойно использовать программу там. Аналогично, ВМ полезны для тестирования новых операционных систем или разных конфигураций без риска для основной системы. Можно сделать снапшот (снимок состояния) ВМ, попробовать что-то внутри (например, установить сомнительную программу), а затем откатиться обратно, если что-то пошло не так.
- Простота переноса и масштабирования в облаке. Виртуальные машины легко создавать, удалять и переносить между серверами. В облачных сервисах именно виртуалки лежат в основе: компании создают новые ВМ по мере нагрузки и выключают лишние, когда они не нужны. Это даёт гибкое управление ресурсами – добавлять мощности можно за счёт запуска дополнительных ВМ, и наоборот, остановив и освободив ресурсы, когда нагрузка спадает. В результате бизнес получает масштабируемость: платить только за те ресурсы, что нужны сейчас.
Недостатки виртуальных машин
- Большое потребление ресурсов. За счёт полной изоляции и отдельной ОС каждая виртуальная машина требует немало оперативной памяти, CPU и дискового пространства. Образ одной ВМ может занимать десятки гигабайт, тогда как образ контейнера – обычно десятки мегабайт. То есть один сервер может “потянуть” гораздо меньше ВМ, чем контейнеров, просто из-за их тяжеловесности. Кроме того, запущенная ВМ тянет часть мощности процессора постоянно (ведь работает целая ОС), в то время как контейнеры потребляют ресурсов меньше.
- Медленный запуск и масштабирование. Вспомните, сколько времени грузится ваш компьютер при включении – ведь нужно поднять операционную систему. Виртуальная машина при старте проделывает то же самое: ей нужно запустить свою ОС, что может занять несколько минут. В противоположность этому контейнеры запускаются за считанные секунды, а то и миллисекунды. Поэтому масштабировать систему, быстро запуская десятки новых экземпляров, на основе ВМ довольно трудно – они просто не успеют подняться достаточно быстро, чтобы моментально отреагировать на выросшую нагрузку.
- Избыточность и расходы на лицензии. Поскольку в каждой виртуалке своя ОС, это может приводить к дублированию функций. Например, если у вас 5 виртуальных машин с Windows, на всех пяти работают одинаковые системные службы, занимая ресурсы. К тому же, коммерческие ОС (например, Windows Server) требуют отдельной лицензии на каждую виртуалку – расходы возрастают. В некоторых сценариях, где нужно множество изолированных сред, использование ВМ окажется неоправданно дорогим и сложным в поддержке.
- Сложность администрирования. Управлять множеством ВМ – задача нетривиальная. Требуется следить за обновлениями ОС внутри каждой виртуалки, мониторить их состояние, выделять достаточно ресурсов. Виртуализация на уровне ВМ существует давно, и для неё есть развитые инструменты, но всё равно администратору прибавляется работы, ведь фактически надо заботиться о “зоопарке” из множества мини-компьютеров.
Несмотря на эти недостатки, виртуальные машины стали стандартом де-факто в корпоративной среде за последние ~20 лет. Их надёжность и изоляция обеспечили бизнесу новые возможности, поэтому ВМ повсеместно используются там, где нужна проверенная временем стабильность и безопасность.
Что такое контейнер?
Теперь представим другой подход: нам по-прежнему нужно изолировать приложения, но без лишних накладных расходов. Контейнер – это способ упаковки приложения со всеми его зависимостями (библиотеками, настройками) в лёгкий, автономный блок. Главное отличие: контейнер не содержит в себе отдельной операционной системы, он используют ядро ОС, на которой запущен хост, совместно с другими контейнерами. Проще говоря, если виртуальная машина – это целый дом, то контейнер – это отдельная квартира (или комната) в большом доме. Все жильцы в одном доме пользуются общей инфраструктурой (фундамент, крыша, центральное отопление). Так же и контейнеры: они разделяют ресурсы одной операционной системы.
Представьте большую квартиру, где живут несколько людей. У каждого своя комната (свой контейнер), где он может навести любой порядок и хранить свои вещи (запустить своё приложение с нужными библиотеками). Комнаты изолированы: кто-то может слушать музыку в своей – соседи за стеной не слышат. Но все соседи зависят от состояния квартиры в целом – если протекает общая крыша или отключили электроэнергию, пострадают все. Подобно этому, контейнеры изолированы друг от друга на уровне приложения, но разделяют общую операционную систему, поэтому сбой ОС повлияет на все контейнеры сразу.
Контейнеры стали популярны благодаря технологии Docker (знаменитый кит – логотип Docker – наверняка вам встречался). Однако сама идея контейнеризации появилась раньше: в Linux давно существуют LXC (Linux Containers) и другие механизмы изоляции процессов. Docker же сделал контейнеры удобными: с его помощью можно упаковать приложение в образ контейнера и запускать где угодно – на ноутбуке разработчика, на сервере в облаке, на разных машинах – и оно всюду будет работать одинаково. Это похоже на стандартные морские контейнеры в логистике: вы упаковали груз в контейнер стандартного размера, а дальше его можно погрузить на корабль, поезд или грузовик – не важно, транспортная инфраструктура подстроена под контейнеры. Также и Docker-контейнер можно запустить практически на любом сервере с Docker-движком – портативность потрясающая.
Как выглядит архитектура контейнеров? На физический сервер устанавливается операционная система (чаще всего Linux в случае Docker) – она называется ОС хоста. Поверх неё запускается движок контейнеров (Container Engine) – например, Docker Engine, containerd или podman. Этот движок выполняет роль надсистемы, аналогичной гипервизору, только для контейнеров – он создаёт и управляет контейнерами. Однако, в отличие от гипервизора, контейнерный движок не эмулирует железо, а использует возможности ядра ОС (изоляция через cgroups, namespaces и пр.). Каждый контейнер содержит ваше приложение и необходимые ему библиотеки, но не включает собственное ядро ОС. Благодаря этому контейнеры получаются очень лёгкими.
Важно понимать: все контейнеры на одном хосте разделяют ядро операционной системы. Поэтому обычно на одном хосте все контейнеры – это Linux-контейнеры (если хост под Linux). Существуют технологии контейнеризации под Windows, и даже запуск Linux-контейнеров под Windows через прослойки, но базовый принцип – общее ядро – сохраняется. Это накладывает ограничения: если приложение требует специфичных модулей ядра или нестандартной ОС, контейнер может не подойти – тогда берут виртуальную машину.
Преимущества контейнеров
- Лёгковесность и экономия ресурсов. Контейнеры гораздо меньше по размеру и требовательности, чем виртуальные машины. Поскольку контейнер не тянет за собой целую ОС, его образ может весить мегабайты. Вы можете запустить десятки и сотни контейнеров там, где поместилось бы всего несколько ВМ. Кроме того, контейнеры расходуют меньше оперативной памяти и CPU, так как используют общие системные ресурсы эффективнее. В результате на одном сервере реально запустить больше приложений в контейнерах, максимально используя мощность железа без сильной перегрузки.
- Высокая скорость запуска и масштабирование. Контейнеры стартуют практически мгновенно. Добавить новый контейнер с приложением – дело секунд, ведь не нужно загружать отдельную ОС. Это делает контейнеры идеальными для сценариев, где требуется быстрое масштабирование или аварийное восстановление. Например, если один экземпляр веб-приложения в контейнере выходит из строя, система оркестрации (о ней ниже) может за миллисекунды запустить новый контейнер взамен упавшего. В итоге приложение будет более устойчивым к нагрузкам и сбоям: контейнеры позволяют легко нарастить их число под наплывом пользователей и также легко убрать лишние, когда нагрузка спадёт.
- Портативность и консистентность среды. Разработчики любят контейнеры, потому что они решают проблему "но у меня на компьютере работало!". Если приложение упаковано в контейнер, оно содержит внутри все нужные зависимости, и можно быть уверенным, что на любом другом сервере (с поддержкой Docker) этот контейнер запустится так же, как у разработчика на ноутбуке. Это упрощает перенос приложений между разными средами – разработка, тестирование, продакшн – без классических проблем несовместимости. Контейнерный образ – стандартизованный пакет, который можно перемещать и запускать где угодно. Виртуалки тоже переносимы, но контейнеры гораздо более универсальны в этом плане и требуют меньше места.
- Богатая экосистема и готовые образы. Ещё один плюс – наличие публичных репозиториев контейнеров (например, Docker Hub), где сообщество выкладывает тысячи готовых образов приложений. Нужно поднять базу данных или веб-сервер? Не надо устанавливать всё вручную – достаточно скачать готовый контейнер с PostgreSQL или Nginx из репозитория и запустить за пару секунд. Это экономит время и силы. Экосистема контейнеров очень активная: существует множество инструментов и сообществ, упрощающих работу с ними.
- Удобство для микросервисов и CI/CD. Контейнеры отлично подходят для современной разработки, где монолитные приложения разбивают на микросервисы. В микросервисной архитектуре каждый компонент (например, сервис авторизации, сервис платежей, база данных) работает в своём контейнере. Это позволяет независимо обновлять и масштабировать части системы. Также контейнеры используются в конвейерах непрерывной интеграции и доставки (CI/CD) – тесты, сборка и развёртывание приложения могут происходить внутри контейнеров, что гарантирует одинаковую среду на каждом этапе разработки.
Недостатки контейнеров
- Ограниченная изоляция и уязвимость общего ядра. За счёт использования общей операционной системы, контейнеры обеспечивают меньшую изоляцию, чем виртуальные машины. Если злоумышленник найдёт уязвимость в ядре ОС, он теоретически может атаковать все контейнеры разом или “выбраться” из контейнера во внешнюю систему. ВМ в этом плане более защищены: скомпрометировать сразу несколько ВМ сложнее, так как у каждой своё ядро. Кроме того, сбой в ОС хоста остановит все контейнеры, что аналогично примеру с общей квартирой – если отключили свет или сломался водопровод, страдают все комнаты сразу. Таким образом, для критичных с точки зрения безопасности приложений контейнеры могут требовать дополнительных мер защиты (например, запуск в разных виртуальных машинах или использование технологий типа sandboxing для контейнеров).
- Зависимость от ОС и ограниченная гибкость. Контейнер не содержит собственной ОС, поэтому все контейнеры на хосте зависят от типа ОС хоста. Вы не сможете запустить Linux-контейнер на Windows-хосте напрямую (и наоборот) без специальных прослоек. Если ваше приложение требует другой операционной системы или нестандартной версии ядра, контейнеризация может не подойти. В таких случаях приходят на помощь виртуальные машины. Проще говоря, контейнеры не позволяют одновременно использовать разные ОС на одном сервере – тут они уступают виртуализации на уровне ВМ.
- Сложность управления на большом масштабе. Запустить десяток контейнеров несложно, но представьте, что у вас их сотни и тысячи (что не редкость в крупной компании). Координировать их работу вручную становится трудно. Появляется задача оркестрации – автоматического управления контейнерами: их распределения по серверам, перезапуска при падении, балансировки нагрузки и т.д. Для этого существуют системы оркестрации контейнеров, самый известный – Kubernetes. Однако их использование добавляет новый уровень сложности и требует навыков. Таким образом, хотя сами контейнеры облегчают запуск приложений, инфраструктура вокруг них может быть непростой.
- Проблемы с сохранением данных (состояния). По дизайну контейнеры часто создаются как недолговечные и неизменяемые: их легко уничтожить и пересоздать заново. Это здорово для приложений, но порождает вопрос: где хранить данные, чтобы не потерять их при перезапуске контейнера? Обычно данные выносят вне контейнера (например, подключают тома, базы данных и пр.). Если этого не делать, то при удалении контейнера вы потеряете всё, что было внутри него. Разработчикам приходится продумывать эту часть: контейнеры без состояния идеальны для веб-сервисов, а вот, скажем, для СУБД нужно аккуратно организовать хранение на постоянном диске. Виртуальные машины в этом плане более привычны – каждая имеет свой виртуальный диск, где данные сохраняются по умолчанию.
- Не всегда подходят для монолитных приложений. Если у вас большое монолитное приложение, которое тяжело разбить на части, засунуть его в контейнер может быть не оптимально. Контейнеры лучше работают с “разделяй и властвуй” подходом – когда приложение уже разделено на небольшие сервисы. Большие же приложения иногда проще продолжать запускать на виртуальной машине или физическом сервере, особенно если им нужна тонкая настройка ОС, специфические драйверы и т.п. Многие предприятия комбинируют подходы: часть нагрузок держат на ВМ, а часть – в контейнерах, в зависимости от особенностей приложения.
Области применения контейнеров
Контейнеры – более новая технология, и сферы их применения во многом продиктованы современными потребностями разработки и эксплуатации:
- Микросервисы и облачные приложения. Как уже было сказано, контейнеры идеально подходят для микросервисной архитектуры. Большие веб-сервисы вроде Netflix, Amazon, Spotify и т.д. состоят из множества микросервисов, каждый из которых крутится в своих контейнерах. Это позволяет командам разработчиков работать независимо над разными компонентами, развёртывать обновления несколько раз в день и масштабировать только те части, которые испытывают нагрузку. Контейнеры стали одним из столпов концепции cloud-native – то есть приложений, изначально спроектированных для работы в облаке (в связке с оркестраторами типа Kubernetes).
- CI/CD и DevOps. В практиках DevOps контейнеризация играет огромную роль. При непрерывной интеграции и доставке (CI/CD) каждое новое изменение кода автоматически тестируется и разворачивается. Чтобы эти процессы были надёжными, используют контейнеры: приложения запускаются в стандартных контейнерах на этапе тестирования, затем тот же образ контейнера идёт в продуктив. Это гарантирует, что “если прошло тесты в контейнере, то и в проде в таком же контейнере будет работать”. Плюс, контейнеры изолируют окружение: параллельно можно на одном сервере запускать контейнеры для разных проектов, и они не повлияют друг на друга (разные версии библиотек, например, не конфликтуют). Это повышает эффективность конвейера разработки.
- Быстрое масштабирование сервисов. Контейнеры позволяют динамически наращивать мощности в ответ на всплеск пользователей. Например, интернет-магазин проводит распродажу 11.11: трафик возрастает в 5 раз на пару дней. С контейнерами масштабирование сервисов происходит плавно – система оркестрации может автоматически запустить дополнительные копии нужных контейнеров перед распродажей. После окончания акции лишние контейнеры просто удалят, освобождая ресурсы. Всё это без ручного развёртывания новых серверов – экономика времени и ресурсов налицо.
- Гибридные облака и переносимость. Многие компании используют гибридные стратегии, комбинируя свои датацентры и публичные облака. Контейнеры тут очень кстати, потому что приложение, упакованное в контейнер, легко перенести из своего датацентра в облако или между облачными провайдерами. Нет привязки к конкретной инфраструктуре – важен лишь Docker (или другой runtime) на приемлемой ОС. Это снижает зависимость от одного вендора (что называют multi-cloud стратегии). Сегодня можно встретить ситуации, когда разработчики пишут код, проверяют его локально в контейнере, затем вы выкатываете контейнер в тестовый кластер на своей инфраструктуре, а в итоге деплой идёт, например, в Amazon ECS или Kubernetes-кластер в Google Cloud – и на всех этапах используется один и тот же контейнерный образ.
- Изоляция в пользовательских приложениях. Контейнеры применяются не только на серверной стороне. Например, Google Chrome по сути запускает каждую вкладку в некотором аналогe контейнера (с использованием схожих технологий изоляции процессов) – так сбой на одной вкладке не “положит” весь браузер. На сервере хостинг-провайдеры могут предоставлять контейнеры как услугу, где пользователи получают изолированное окружение для своих приложений, но без накладных расходов полноценной ВМ. Это называется Container as a Service (CaaS). Хотя здесь граница с виртуальными машинами несколько размывается, идея в том, чтобы клиенту дать ровно столько окружения, сколько нужно для его приложения, и не больше.
- Специализированные вычисления и функции. С появлением технологии serverless (бессерверные вычисления) и функций как услуги (Function as a Service, например AWS Lambda), контейнеры стали использовать для мгновенного запуска изолированных короткоживущих процессов. Когда вы вызываете облачную функцию, она, как правило, исполняется внутри контейнера, который поднимается за миллисекунды, выполняет задачу и уничтожается. Здесь на первый план выходит именно скорость старта и малый размер контейнерного образа – чтобы масштабировать вызовы функций под нагрузкой моментально.
Можно сказать, что контейнеры – это про эффективность и скорость в современном IT. Они позволяют компаниям быстро адаптироваться к изменениям нагрузки и разворачивать приложения с высокой частотой обновлений. Однако контейнеры не вытеснили полностью виртуальные машины – зачастую они работают вместе. Например, в облаке никто не мешает запустить кластер Kubernetes внутри виртуальных машин: ВМ обеспечивают базовую изоляцию между клиентами, а внутри каждой ВМ крутятся десятки контейнеров с микросервисами. Такой многоуровневый подход объединяет лучшее из обоих миров.