инфраструктура · архитектура

Как устроены блочные устройства в облаке: VM, SPDK, Ceph, локальные диски и control plane

СС
Сергей Самойлов
techlead · compute storage @ mws cloud platform
ФорматОбзорная карта
Уровеньmiddle+ / архитекторы
СтекQEMU · SPDK · Ceph · S3
Чтение≈ 15 мин

Привет! Меня зовут Сергей Самойлов, я техлид Compute Storage в MWS Cloud Platform. Моя зона ответственности — хранение данных на виртуальных машинах, в том числе блочные устройства.

В этой статье я хочу пройти путь от вопроса «что вообще такое диск в облаке?» до архитектуры, в которой есть виртуальные машины, SPDK, Ceph, локальные NVMe-диски, control plane, образы и снимки.

Это не будет глубокий разбор Ceph, SPDK или QEMU до уровня исходников. Скорее это карта местности: какие компоненты участвуют в создании облачного диска, почему они нужны и как они складываются в работающую систему.

Из чего состоит базовое облако

Если сильно упростить, базовая облачная платформа держится на трёх крупных сущностях:

Пользователь может взять эти примитивы и сам собрать на них PostgreSQL, Kafka, ClickHouse или любую другую систему. А может использовать готовый managed-сервис. Но под капотом managed-сервисы всё равно опираются на те же базовые сущности: виртуальные машины, сеть и диски.

При этом диск — одна из самых критичных частей облака. Если упала виртуальная машина, это плохо: сервис может быть недоступен. Если возникли проблемы с сетью, это тоже плохо. Но если потерялись данные на диске, это уже совсем другой уровень проблемы.

Поэтому к блочному хранилищу в облаке есть базовое требование: оно должно надёжно хранить данные, переживать отказы и при этом оставаться управляемым.

Пользователь COMPUTE виртуальные машины BLOCK STORAGE NBS / диски VPC сеть ПРИЛОЖЕНИЯ и managed-сервисы
// базовые примитивы облака

Что такое блочное устройство

NBS обычно расшифровывают как Network Block Storage.

С точки зрения Linux блочное устройство можно понимать как устройство, через которое мы читаем и пишем байты блоками. Диск — частный случай блочного устройства: операционная система перекладывает байты из оперативной памяти на физический носитель и обратно.

Если диск сетевой, путь становится длиннее:

  1. Приложение внутри виртуальной машины пишет данные.
  2. ОС видит обычное блочное устройство.
  3. Байты уходят из VM наружу.
  4. Дальше они попадают в сетевое хранилище.
  5. Уже там данные раскладываются по реальным физическим носителям.
Ключевая мысль «Виртуальный диск» в облаке не обязан соответствовать одному физическому диску. Пользователь видит упорядоченный объём памяти, но под капотом он может быть собран из объектов, реплик, метаданных и физических дисков на разных серверах.
Приложение Гостевая ОС в VM Виртуальный диск network Сетевой storage backend Физические диски и реплики
// путь данных для сетевого диска

Как виртуальная машина получает доступ к диску

Начнём с виртуальной машины.

В типичной Linux-инфраструктуре VM запускается через QEMU, а за эффективное взаимодействие с железом отвечает KVM. Упрощённо:

Но одной виртуализации недостаточно. Нам нужно ещё управлять жизненным циклом VM и подключать к ним диски.

Поэтому на хосте появляются агенты:

Теоретически это мог бы быть один агент. На практике разделение удобно: Compute и Storage часто развиваются разными командами, имеют разные зоны ответственности и разные риски.

Где здесь SPDK

Storage Agent не обязан напрямую работать с ядром Linux. Для высокопроизводительного I/O удобно использовать SPDK — Storage Performance Development Kit.

SPDK — это набор библиотек для работы с хранилищами из user space. Он позволяет строить быстрый путь ввода-вывода без написания собственных модулей ядра.

Почему это важно:

У polling-модели есть цена: SPDK может активно использовать CPU даже тогда, когда нагрузка кажется небольшой. Поэтому под такие компоненты обычно выделяют отдельные ядра.

В итоге базовая схема выглядит так:

  1. Compute Agent запускает VM через QEMU/KVM.
  2. Storage Agent получает команду подключить диск.
  3. SPDK обеспечивает эффективный путь от VM к блочному устройству.
  4. Дальше этот путь ведёт либо в сетевое хранилище, либо к локальному NVMe.
ФИЗИЧЕСКИЙ LINUX-ХОСТ Compute Agent запускает VM Storage Agent подключает диски QEMU / KVM SPDK VM guest OS Ceph / сетевой диск или локальный NVMe
// архитектура на хосте

Сетевые диски: независимость и отказоустойчивость

Сетевой диск в облаке должен обладать двумя важными свойствами.

Первое — независимость от виртуальной машины.

Пользователь может создать диск, изменить его размер, настроить производительность, удалить его и при этом ни разу не подключить к VM. Диск и виртуальная машина имеют разные жизненные циклы.

Да, у виртуальной машины обычно есть boot-диск, с которого она загружается. Но остальные диски можно подключать и отключать позже.

Второе — устойчивость к отказам.

Если умер физический сервер, отдельный процесс или диск, это не должно приводить к потере пользовательских данных. Для этого сетевые диски строятся поверх распределённого хранилища.

В нашем случае речь идёт о Ceph.

Ceph: программная СХД

Ceph — это программная система хранения данных. Её можно построить на обычных SSD или NVMe-дисках, без специализированного аппаратного RAID-контроллера.

Внутри Ceph данные представлены как RADOS-объекты. Эти объекты распределяются по нодам кластера, а для надёжности хранятся в нескольких копиях.

Если одна нода выходит из строя, Ceph это обнаруживает и переносит реплики в другие места. При достаточном факторе репликации кластер переживает потерю отдельных нод без потери данных.

Для понимания блочных устройств важны несколько сущностей Ceph:

Приложение, которое хочет работать с блочным устройством в Ceph, обычно использует librbd. Эта библиотека знает, как из набора RADOS-объектов собрать для клиента абстракцию блочного устройства.

Важно понимать Ceph на нижнем уровне не хранит «диск» как единый физический объект. Он хранит объекты. А блочное устройство формируется клиентской библиотекой и метаданными.

Для пользователя внутри VM всё выглядит просто: есть диск, в него можно писать. Но реально запись превращается в операции с RADOS-объектами, OSD, репликами и pool-ами.

VM / клиент librbd RBD image / pool Monitors карта кластера RADOS-объекты и placement logic OSD 1 · OSD 2 · OSD 3 · ... реплики объектов на физических дисках
// Ceph для блочного диска

Локальные диски: быстрее, но без гарантий сетевого хранилища

Кроме сетевых дисков бывают локальные.

У них другая модель:

Звучит хуже, чем сетевой диск. Зачем тогда локальные диски нужны?

Ответ простой: скорость.

Локальный диск находится на том же физическом хосте, где запущена VM. Не нужно идти по сети в Ceph, не нужно ждать репликации, путь данных короче.

Под капотом это может быть NVMe-диск, разбитый на namespaces. Namespace — логическая часть NVMe-устройства. Пользователю выдаётся локальный диск, состоящий из одного или нескольких namespaces.

Здесь появляется интересная связь: чем больше локальный диск, тем больше доступная производительность.

Почему так? Если маленький локальный диск мог бы забрать весь I/O физического NVMe, один пользователь легко мешал бы другим. Поэтому производительность лимитируется на уровне выделенных namespaces. Больше namespaces — выше суммарный лимит IOPS.

Локальные диски хорошо подходят системам, которые сами умеют обеспечивать отказоустойчивость:

В такой архитектуре облако даёт быстрый локальный диск, а приложение само отвечает за репликацию данных между нодами.

Как всем этим управлять

Пользователь не должен заходить на физический хост и вручную подключать диск к виртуальной машине. Нужен управляющий слой — control plane.

Control plane принимает пользовательские запросы:

Но в реальной системе не один сервер и не одна зона доступности. Есть много хостов, много виртуальных машин, много дисков и несколько дата-центров.

Поэтому управляющий слой делится на уровни:

Пользователь говорит: «создай диск в такой-то зоне». Запрос попадает в global control plane, а тот маршрутизирует его в нужный zonal control plane. Уже зональный уровень знает, какие хосты есть в зоне, где живёт VM и куда нужно отправить команду Storage Agent.

Такое разделение важно и для отказоустойчивости. Global control plane нельзя положить в одну зону и считать задачу решённой: если эта зона упадёт, управление всем регионом будет деградировать. Поэтому глобальные компоненты тоже запускаются в нескольких зонах, а перед ними стоит балансировщик.

Пользователь Load Balancer Global Control Plane API, маршрутизация, общая логика Zonal Control Plane · A Zonal Control Plane · B Хосты, VM, Storage Agent диски, Ceph, локальный IO Хосты, VM, Storage Agent диски, Ceph, локальный IO
// уровни управления

Проблема управления ресурсами

Создание VM с диском — это не одна операция.

На самом деле это набор зависимых ресурсов:

  1. Создать виртуальную машину.
  2. Создать диск.
  3. Создать attachment между VM и диском.
  4. Уведомить хост, что диск нужно подключить.
  5. Дождаться, пока Storage Agent приведёт фактическое состояние к нужному.

Если представить это как граф, то одни вершины зависят от других. Attachment не может быть полностью готов, пока не существуют VM и диск. VM не может начать полноценно использовать диск, пока хост не выполнил подключение.

В таких системах удобно не писать длинную синхронную процедуру вида «сделай шаг 1, потом шаг 2, потом шаг 3». Лучше описывать желаемое состояние и постепенно к нему приходить.

Декларативный API и реконсиляция

Подход похож на Kubernetes.

Пользователь задаёт спецификацию:

disk:
  zone: ru-central-a
  size: 100GiB
  type: network-ssd

Система сохраняет эту спецификацию и дальше сама приводит ресурс к нужному состоянию.

Если диск ещё не создан — создаёт. Если метаданные есть, но физическое подключение не выполнено — отправляет задачу агенту. Если операция сорвалась — повторяет или переводит ресурс в ошибочное состояние.

Такой цикл называется реконсиляцией:

  1. Есть желаемое состояние.
  2. Есть фактическое состояние.
  3. Контроллер сравнивает их.
  4. Контроллер выполняет действия, которые приближают фактическое состояние к желаемому.

Этот подход хорошо ложится на облачные ресурсы, потому что многие операции асинхронные. Создание диска, подключение к 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.*;

Что здесь происходит:

Это не единственный способ построить task queue, но он хорошо показывает общий принцип: control plane не делает всё прямо в пользовательском запросе, а раскладывает работу на устойчивые фоновые процессы.

Образы, снимки и S3

Теперь перейдём к образам и снимкам.

Почти в любом облаке есть объектное хранилище. В этой статье будем называть его S3-хранилищем. Оно нужно для долговременного хранения объектов: файлов, архивов, наборов байт.

Блочное хранилище использует S3 для двух близких сущностей:

На высоком уровне обе сущности сохраняют состояние диска. Сетевой диск лежит в Ceph, а его состояние нужно упаковать, сжать, зашифровать и положить в S3. Когда пользователь хочет восстановить диск, данные достаются из S3, распаковываются и записываются обратно в Ceph.

Но назначение у образов и снимков разное.

Сетевой диск в Ceph / RBD Сжатие / шифрование упаковка состояния S3 хранилище Восстановление диска обратно в Ceph / RBD
// общий поток для образов и снимков

Образ как эталонный диск

Образ — это эталонное состояние диска.

Например, вы взяли Ubuntu, установили нужные пакеты, применили настройки безопасности, добавили агенты мониторинга и хотите дальше создавать VM именно из такого состояния. Для этого создаётся образ.

У образа могут быть бизнес-атрибуты:

Ключевое свойство образа — самостоятельность. Он должен содержать полное состояние, достаточное для создания нового диска. Образ не должен зависеть от цепочки предыдущих образов.

Снимок как backup

Снимок — это фиксация состояния диска в конкретный момент времени.

Снимки обычно создаются регулярно:

В отличие от образа, снимок не обязан быть «идеальным» состоянием системы. Это просто точка восстановления.

Для снимков хорошо подходит дельта-кодирование.

Первый снимок содержит полное состояние диска. Второй хранит только изменения относительно первого. Третий — изменения относительно второго.

Чтобы восстановиться из третьего снимка, система применяет цепочку:

  1. полный первый снимок;
  2. дельту второго;
  3. дельту третьего.

Так экономится место в S3, потому что регулярные backup-и часто отличаются друг от друга не всем диском, а небольшой частью данных.

Почему образы не хранятся дельтами

Иногда возникает вопрос: если дельты так хорошо экономят место, почему бы не хранить образы так же?

Потому что у образа другая задача.

Снимки могут зависеть друг от друга. Это нормально: они образуют backup-цепочку. Образ должен быть независимой сущностью. Его можно использовать как шаблон, переносить между проектами, делать deprecated, публиковать, создавать из него диски без необходимости подтягивать чужую цепочку зависимостей.

Поэтому образ хранит полное состояние.

Почему диск из снимка нельзя сделать меньше исходного

Есть ещё один неочевидный момент.

Допустим, был диск на 1 TB. Пользователь записал на него всего 1 GB данных. Мы создали снимок, сжали его, и в S3 он занимает примерно 1 GB.

Можно ли восстановить из этого снимка диск на 20 GB?

В общем случае — нет.

Причина в том, что блочное хранилище не знает семантику данных внутри диска. Там может быть файловая система, база данных, LVM, шифрованный контейнер или что угодно ещё.

Файловая система может хранить метаданные не только в начале диска, но и в конце. Внутри могут быть указатели на области далеко за пределами первых 20 GB. Если восстановить такой снимок в диск меньшего размера, эти указатели станут некорректными, а файловая система может сломаться.

Следствие При восстановлении из снимка минимальный размер нового диска не может быть меньше размера исходного диска, с которого снимок был снят. Размер данных в S3 и логический размер блочного устройства — разные вещи.

Как это складывается в одну систему

Если собрать всё вместе, путь выглядит так.

На хосте есть: 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.

Пользователь при этом видит простые операции: создать диск, подключить диск, сделать снимок, восстановить из образа. Внутри же за каждой такой операцией стоит довольно длинная цепочка компонентов.

Пользователь Global Control Plane Zonal Control Plane Хост с VM Compute Agent → QEMU/KVM → VM Storage Agent → SPDK Ceph / RBD или локальный NVMe образы / снимки → S3
// сквозной путь запроса

Итоги

Блочное устройство в облаке — это не физический диск, который кто-то подключил к виртуальной машине. Это абстракция, собранная из нескольких уровней:

Сетевые диски дают надёжность и независимый жизненный цикл. Локальные диски дают скорость, но требуют, чтобы отказоустойчивость обеспечивалась уровнем выше. Образы удобны как эталонные состояния, а снимки — как механизм восстановления.

Главное Облачный диск выглядит простым только снаружи. Внутри это распределённая система с виртуализацией, storage backend-ом, управляющим слоем и большим количеством асинхронной логики.
END_OF_FILE

Комментарии

Замечания и обсуждение
Markdown не поддерживается, обычный текст.
loading...