Как устроены блочные устройства в облаке: VM, SPDK, Ceph, локальные диски и control plane
Привет! Меня зовут Сергей Самойлов, я техлид Compute Storage в MWS Cloud Platform. Моя зона ответственности — хранение данных на виртуальных машинах, в том числе блочные устройства.
В этой статье я хочу пройти путь от вопроса «что вообще такое диск в облаке?» до архитектуры, в которой есть виртуальные машины, SPDK, Ceph, локальные NVMe-диски, control plane, образы и снимки.
Это не будет глубокий разбор Ceph, SPDK или QEMU до уровня исходников. Скорее это карта местности: какие компоненты участвуют в создании облачного диска, почему они нужны и как они складываются в работающую систему.
Из чего состоит базовое облако
Если сильно упростить, базовая облачная платформа держится на трёх крупных сущностях:
- Compute — виртуальные машины.
- Block Storage / NBS — блочные устройства, то есть диски.
- VPC — сеть, которая связывает виртуальные машины между собой.
Пользователь может взять эти примитивы и сам собрать на них PostgreSQL, Kafka, ClickHouse или любую другую систему. А может использовать готовый managed-сервис. Но под капотом managed-сервисы всё равно опираются на те же базовые сущности: виртуальные машины, сеть и диски.
При этом диск — одна из самых критичных частей облака. Если упала виртуальная машина, это плохо: сервис может быть недоступен. Если возникли проблемы с сетью, это тоже плохо. Но если потерялись данные на диске, это уже совсем другой уровень проблемы.
Поэтому к блочному хранилищу в облаке есть базовое требование: оно должно надёжно хранить данные, переживать отказы и при этом оставаться управляемым.
Что такое блочное устройство
NBS обычно расшифровывают как Network Block Storage.
С точки зрения Linux блочное устройство можно понимать как устройство, через которое мы читаем и пишем байты блоками. Диск — частный случай блочного устройства: операционная система перекладывает байты из оперативной памяти на физический носитель и обратно.
Если диск сетевой, путь становится длиннее:
- Приложение внутри виртуальной машины пишет данные.
- ОС видит обычное блочное устройство.
- Байты уходят из VM наружу.
- Дальше они попадают в сетевое хранилище.
- Уже там данные раскладываются по реальным физическим носителям.
Как виртуальная машина получает доступ к диску
Начнём с виртуальной машины.
В типичной Linux-инфраструктуре VM запускается через QEMU, а за эффективное взаимодействие с железом отвечает KVM. Упрощённо:
- есть физический Linux-хост;
- на нём работает QEMU;
- KVM выступает гипервизором;
- внутри запускаются виртуальные машины.
Но одной виртуализации недостаточно. Нам нужно ещё управлять жизненным циклом VM и подключать к ним диски.
Поэтому на хосте появляются агенты:
- Compute Agent управляет виртуальными машинами;
- Storage Agent отвечает за подключение блочных устройств.
Теоретически это мог бы быть один агент. На практике разделение удобно: Compute и Storage часто развиваются разными командами, имеют разные зоны ответственности и разные риски.
Где здесь SPDK
Storage Agent не обязан напрямую работать с ядром Linux. Для высокопроизводительного I/O удобно использовать SPDK — Storage Performance Development Kit.
SPDK — это набор библиотек для работы с хранилищами из user space. Он позволяет строить быстрый путь ввода-вывода без написания собственных модулей ядра.
Почему это важно:
- не нужно писать и сопровождать kernel-драйверы;
- логика подключения дисков живёт в обычном приложении;
- можно получить высокую производительность за счёт polling-модели и работы с устройствами из user space;
- появляется единый подход для разных типов блочных устройств.
У polling-модели есть цена: SPDK может активно использовать CPU даже тогда, когда нагрузка кажется небольшой. Поэтому под такие компоненты обычно выделяют отдельные ядра.
В итоге базовая схема выглядит так:
- Compute Agent запускает VM через QEMU/KVM.
- Storage Agent получает команду подключить диск.
- SPDK обеспечивает эффективный путь от VM к блочному устройству.
- Дальше этот путь ведёт либо в сетевое хранилище, либо к локальному NVMe.
Сетевые диски: независимость и отказоустойчивость
Сетевой диск в облаке должен обладать двумя важными свойствами.
Первое — независимость от виртуальной машины.
Пользователь может создать диск, изменить его размер, настроить производительность, удалить его и при этом ни разу не подключить к VM. Диск и виртуальная машина имеют разные жизненные циклы.
Да, у виртуальной машины обычно есть boot-диск, с которого она загружается. Но остальные диски можно подключать и отключать позже.
Второе — устойчивость к отказам.
Если умер физический сервер, отдельный процесс или диск, это не должно приводить к потере пользовательских данных. Для этого сетевые диски строятся поверх распределённого хранилища.
В нашем случае речь идёт о Ceph.
Ceph: программная СХД
Ceph — это программная система хранения данных. Её можно построить на обычных SSD или NVMe-дисках, без специализированного аппаратного RAID-контроллера.
Внутри Ceph данные представлены как RADOS-объекты. Эти объекты распределяются по нодам кластера, а для надёжности хранятся в нескольких копиях.
Если одна нода выходит из строя, Ceph это обнаруживает и переносит реплики в другие места. При достаточном факторе репликации кластер переживает потерю отдельных нод без потери данных.
Для понимания блочных устройств важны несколько сущностей Ceph:
- OSD — Object Storage Daemon. Демон, который обычно запускается поверх конкретного физического диска и отвечает за хранение данных на нём.
- Monitor — компонент, который хранит карту кластера и отвечает на вопросы вроде «какие OSD сейчас доступны?».
- Pool — логическое пространство внутри Ceph-кластера со своими настройками, например параметрами репликации.
- RBD — RADOS Block Device, блочное устройство поверх RADOS-объектов.
Приложение, которое хочет работать с блочным устройством в Ceph, обычно использует librbd. Эта библиотека знает, как из набора RADOS-объектов собрать для клиента абстракцию блочного устройства.
Для пользователя внутри VM всё выглядит просто: есть диск, в него можно писать. Но реально запись превращается в операции с RADOS-объектами, OSD, репликами и pool-ами.
Локальные диски: быстрее, но без гарантий сетевого хранилища
Кроме сетевых дисков бывают локальные.
У них другая модель:
- локальный диск зависит от VM;
- его нельзя свободно отсоединить и подключить к другой виртуальной машине;
- при удалении VM данные на локальном диске пропадают;
- отказ физического NVMe может привести к потере данных.
Звучит хуже, чем сетевой диск. Зачем тогда локальные диски нужны?
Ответ простой: скорость.
Локальный диск находится на том же физическом хосте, где запущена VM. Не нужно идти по сети в Ceph, не нужно ждать репликации, путь данных короче.
Под капотом это может быть NVMe-диск, разбитый на namespaces. Namespace — логическая часть NVMe-устройства. Пользователю выдаётся локальный диск, состоящий из одного или нескольких namespaces.
Здесь появляется интересная связь: чем больше локальный диск, тем больше доступная производительность.
Почему так? Если маленький локальный диск мог бы забрать весь I/O физического NVMe, один пользователь легко мешал бы другим. Поэтому производительность лимитируется на уровне выделенных namespaces. Больше namespaces — выше суммарный лимит IOPS.
Локальные диски хорошо подходят системам, которые сами умеют обеспечивать отказоустойчивость:
- Kafka;
- PostgreSQL-кластеры;
- ClickHouse;
- другие распределённые системы с собственной репликацией.
В такой архитектуре облако даёт быстрый локальный диск, а приложение само отвечает за репликацию данных между нодами.
Как всем этим управлять
Пользователь не должен заходить на физический хост и вручную подключать диск к виртуальной машине. Нужен управляющий слой — control plane.
Control plane принимает пользовательские запросы:
- создать диск;
- создать VM;
- подключить диск к VM;
- отключить диск;
- изменить параметры;
- создать снимок;
- восстановить диск из образа.
Но в реальной системе не один сервер и не одна зона доступности. Есть много хостов, много виртуальных машин, много дисков и несколько дата-центров.
Поэтому управляющий слой делится на уровни:
- Global Control Plane принимает запросы на уровне региона или всего облака.
- Zonal Control Plane управляет ресурсами внутри конкретной зоны доступности.
Пользователь говорит: «создай диск в такой-то зоне». Запрос попадает в global control plane, а тот маршрутизирует его в нужный zonal control plane. Уже зональный уровень знает, какие хосты есть в зоне, где живёт VM и куда нужно отправить команду Storage Agent.
Такое разделение важно и для отказоустойчивости. Global control plane нельзя положить в одну зону и считать задачу решённой: если эта зона упадёт, управление всем регионом будет деградировать. Поэтому глобальные компоненты тоже запускаются в нескольких зонах, а перед ними стоит балансировщик.
Проблема управления ресурсами
Создание VM с диском — это не одна операция.
На самом деле это набор зависимых ресурсов:
- Создать виртуальную машину.
- Создать диск.
- Создать attachment между VM и диском.
- Уведомить хост, что диск нужно подключить.
- Дождаться, пока Storage Agent приведёт фактическое состояние к нужному.
Если представить это как граф, то одни вершины зависят от других. Attachment не может быть полностью готов, пока не существуют VM и диск. VM не может начать полноценно использовать диск, пока хост не выполнил подключение.
В таких системах удобно не писать длинную синхронную процедуру вида «сделай шаг 1, потом шаг 2, потом шаг 3». Лучше описывать желаемое состояние и постепенно к нему приходить.
Декларативный API и реконсиляция
Подход похож на Kubernetes.
Пользователь задаёт спецификацию:
disk:
zone: ru-central-a
size: 100GiB
type: network-ssd
Система сохраняет эту спецификацию и дальше сама приводит ресурс к нужному состоянию.
Если диск ещё не создан — создаёт. Если метаданные есть, но физическое подключение не выполнено — отправляет задачу агенту. Если операция сорвалась — повторяет или переводит ресурс в ошибочное состояние.
Такой цикл называется реконсиляцией:
- Есть желаемое состояние.
- Есть фактическое состояние.
- Контроллер сравнивает их.
- Контроллер выполняет действия, которые приближают фактическое состояние к желаемому.
Этот подход хорошо ложится на облачные ресурсы, потому что многие операции асинхронные. Создание диска, подключение к VM, подготовка образа или восстановление снимка могут занимать время. Пользовательский API при этом не должен держать HTTP-запрос открытым до завершения всей цепочки.
Внутренний механизм задач
Для фоновой обработки часто нужен механизм задач. Один из рабочих вариантов — хранить задачи в базе и разбирать их несколькими воркерами.
Важная деталь: воркеров может быть много, и они не должны брать одну и ту же задачу одновременно. Для этого удобно использовать FOR UPDATE SKIP LOCKED.
Пример запроса:
WITH cte AS (
SELECT id
FROM task
WHERE phase = :readyPhase
OR (
phase = :processingPhase
AND updated_at <= now() - make_interval(secs => :deltaSeconds)
)
ORDER BY updated_at ASC
FOR UPDATE SKIP LOCKED
LIMIT 1
)
UPDATE task t
SET phase = :processingPhase,
updated_at = now()
FROM cte
WHERE t.id = cte.id
RETURNING t.*;
Что здесь происходит:
- воркер ищет задачу в состоянии
ready; - также он может подобрать зависшую
processing-задачу, если она давно не обновлялась; FOR UPDATE SKIP LOCKEDблокирует выбранную строку и позволяет другим воркерам не ждать её, а взять следующую;- затем задача атомарно переводится в
processing; - воркер получает её через
RETURNING.
Это не единственный способ построить task queue, но он хорошо показывает общий принцип: control plane не делает всё прямо в пользовательском запросе, а раскладывает работу на устойчивые фоновые процессы.
Образы, снимки и S3
Теперь перейдём к образам и снимкам.
Почти в любом облаке есть объектное хранилище. В этой статье будем называть его S3-хранилищем. Оно нужно для долговременного хранения объектов: файлов, архивов, наборов байт.
Блочное хранилище использует S3 для двух близких сущностей:
- образов;
- снимков.
На высоком уровне обе сущности сохраняют состояние диска. Сетевой диск лежит в Ceph, а его состояние нужно упаковать, сжать, зашифровать и положить в S3. Когда пользователь хочет восстановить диск, данные достаются из S3, распаковываются и записываются обратно в Ceph.
Но назначение у образов и снимков разное.
Образ как эталонный диск
Образ — это эталонное состояние диска.
Например, вы взяли Ubuntu, установили нужные пакеты, применили настройки безопасности, добавили агенты мониторинга и хотите дальше создавать VM именно из такого состояния. Для этого создаётся образ.
У образа могут быть бизнес-атрибуты:
- семейство, например
ubuntu-24-04; - статус: active, inactive, deprecated;
- признаки публичности или доступности в проекте.
Ключевое свойство образа — самостоятельность. Он должен содержать полное состояние, достаточное для создания нового диска. Образ не должен зависеть от цепочки предыдущих образов.
Снимок как backup
Снимок — это фиксация состояния диска в конкретный момент времени.
Снимки обычно создаются регулярно:
- раз в час;
- раз в день;
- перед опасной операцией;
- по расписанию backup policy.
В отличие от образа, снимок не обязан быть «идеальным» состоянием системы. Это просто точка восстановления.
Для снимков хорошо подходит дельта-кодирование.
Первый снимок содержит полное состояние диска. Второй хранит только изменения относительно первого. Третий — изменения относительно второго.
Чтобы восстановиться из третьего снимка, система применяет цепочку:
- полный первый снимок;
- дельту второго;
- дельту третьего.
Так экономится место в S3, потому что регулярные backup-и часто отличаются друг от друга не всем диском, а небольшой частью данных.
Почему образы не хранятся дельтами
Иногда возникает вопрос: если дельты так хорошо экономят место, почему бы не хранить образы так же?
Потому что у образа другая задача.
Снимки могут зависеть друг от друга. Это нормально: они образуют backup-цепочку. Образ должен быть независимой сущностью. Его можно использовать как шаблон, переносить между проектами, делать deprecated, публиковать, создавать из него диски без необходимости подтягивать чужую цепочку зависимостей.
Поэтому образ хранит полное состояние.
Почему диск из снимка нельзя сделать меньше исходного
Есть ещё один неочевидный момент.
Допустим, был диск на 1 TB. Пользователь записал на него всего 1 GB данных. Мы создали снимок, сжали его, и в S3 он занимает примерно 1 GB.
Можно ли восстановить из этого снимка диск на 20 GB?
В общем случае — нет.
Причина в том, что блочное хранилище не знает семантику данных внутри диска. Там может быть файловая система, база данных, LVM, шифрованный контейнер или что угодно ещё.
Файловая система может хранить метаданные не только в начале диска, но и в конце. Внутри могут быть указатели на области далеко за пределами первых 20 GB. Если восстановить такой снимок в диск меньшего размера, эти указатели станут некорректными, а файловая система может сломаться.
Как это складывается в одну систему
Если собрать всё вместе, путь выглядит так.
На хосте есть: Linux, QEMU/KVM, Compute Agent, Storage Agent, SPDK, виртуальные машины, иногда локальные NVMe-диски.
Для сетевых дисков есть: Ceph-кластер, OSD, monitor, pools, RBD images, proxy или другой сервисный слой для общения с Ceph.
Для управления есть: global control plane, zonal control plane, база метаданных, механизм задач, воркеры, реконсиляция.
Для долговременного хранения состояний дисков есть: S3, образы, снимки, сжатие, шифрование, восстановление обратно в Ceph.
Пользователь при этом видит простые операции: создать диск, подключить диск, сделать снимок, восстановить из образа. Внутри же за каждой такой операцией стоит довольно длинная цепочка компонентов.
Итоги
Блочное устройство в облаке — это не физический диск, который кто-то подключил к виртуальной машине. Это абстракция, собранная из нескольких уровней:
- виртуализация через QEMU/KVM;
- быстрый I/O через SPDK;
- сетевые диски поверх Ceph/RBD;
- локальные диски поверх NVMe namespaces;
- control plane и реконсиляция;
- task queue для асинхронных операций;
- S3 для образов и снимков;
- шифрование и управление ключами;
- зональная архитектура для отказоустойчивости.
Сетевые диски дают надёжность и независимый жизненный цикл. Локальные диски дают скорость, но требуют, чтобы отказоустойчивость обеспечивалась уровнем выше. Образы удобны как эталонные состояния, а снимки — как механизм восстановления.
Комментарии