Работоспособность гарантируется только при использовании стоковых ядер, поставляемых вместе с поддерживаемыми дистрибутивами. При использовании нестандартных ядер или дистрибутивов поведение может быть непредсказуемым.

Контроллер оперирует двумя типами кастомных ресурсов Kubernetes:

  • BlockDevice — ресурс, представляющий блочное устройство;
  • LVMVolumeGroup — ресурс, описывающий группу томов Logical Volume Manager.

Работа с ресурсами BlockDevice

Создание ресурса BlockDevice

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

Критерии отбора устройства контроллером

  • Устройство не используется в качестве DRBD-тома (Distributed Replicated Block Device);
  • устройство не является loop-интерфейсом (виртуальным блочным устройством);
  • устройство является логическим томом (Logical Volume);
  • файловая система отсутствует или имеет маркировку LVM2_MEMBER (указывает на принадлежность устройству LVM);
  • устройство не содержит разделов (отсутствует partition table);
  • ёмкость устройства превышает 1 GiB;
  • для виртуальных дисков обязательно наличие серийного номера.

Сформированный ресурс BlockDevice служит источником данных для последующей работы с ресурсами LVMVolumeGroup.

Обновление ресурса BlockDevice

При изменении состояния блочного устройства (например, изменение размера, изменение метаданных или исчезновение устройства из системы) контроллер автоматически обнаруживает эти изменения и обновляет поля ресурса BlockDevice, обеспечивая актуальность информации. Пользовательская правка ресурса BlockDevice запрещена и будет перезаписана контроллером.

Удаление ресурса BlockDevice

Контроллер удаляет ресурс BlockDevice только при выполнении условий:

  • Ресурс находится в состоянии Consumable (доступен для потребления в Logical Volume Manager).
  • Блочное устройство более не доступно в системе (устройство было удалено или отключено).
  • Блочное устройство не входит в состав группы томов с лейблом storage.deckhouse.io/enabled=true (такая группа томов управляется LVMVolumeGroup и удаление BlockDevice не производится).

Удаление ресурса BlockDevice происходит без участия пользователя. При ручном удалении ресурса контроллер повторно создаст его при следующем сканировании, если устройство по-прежнему удовлетворяет критериям.

Работа с ресурсами LVMVolumeGroup

Ресурс LVMVolumeGroup объединяет несколько ресурсов BlockDevice в единую группу томов Logical Volume Manager (LVM) на узле и отображает текущее состояние этой группы.

На текущий момент поддерживается только тип Local (локальные группы томов).

Создание ресурса LVMVolumeGroup

Существует два сценария формирования ресурса LVMVolumeGroup:

  • Автоматическое создание ресурса:
    • Контроллер сканирует список активных групп томов LVM на каждом узле.
    • Если у обнаруженной группы томов присутствует лейбл storage.deckhouse.io/enabled=true и нет соответствующего ресурса LVMVolumeGroup, контроллер автоматически создаёт ресурс.
    • При этом контроллер заполняет секцию spec, за исключением поля thinPools. Все остальные параметры (имена блочных устройств, имя Volume Group и т.д.) подтягиваются автоматически из состояния системы.
    • Для управления пулами малого размера (thin pool) пользователь может вручную добавить информацию о них в секцию spec после создания ресурса.
  • Ручное создание ресурса:
    • Пользователь формирует YAML-манифест, указывая минимальный набор полей:
      • metadata.name — уникальное имя ресурса LVMVolumeGroup;
      • spec — имя узла, на котором будет существовать группа томов.
    • После валидации контроллер создаёт группу томов на узле и добавляет в кастомный ресурс актуальную информацию о состоянии созданной LVM Volume Group.

    Пример создания локальной Volume Group без thin pool:

    apiVersion: storage.deckhouse.io/v1alpha1
    kind: LVMVolumeGroup
    metadata:
      name: "vg-0-on-node-0"
    spec:
      type: Local
      local:
        nodeName: "node-0"
      blockDeviceSelector:
        matchExpressions:
          - key: kubernetes.io/metadata.name
            operator: In
            values:
              - dev-07ad52cef2348996b72db262011f1b5f896bb68f
              - dev-e90e8915902bd6c371e59f89254c0fd644126다7
      actualVGNameOnTheNode: "vg-0"
    
    apiVersion: storage.deckhouse.io/v1alpha1
    kind: LVMVolumeGroup
    metadata:
      name: "vg-0-on-node-0"
    spec:
      type: Local
      local:
        nodeName: "node-0"
      blockDeviceSelector:
        matchLabels:
          kubernetes.io/hostname: node-0
      actualVGNameOnTheNode: "vg-0"
    

    Пример создания локальной Volume Group с thin pool (250 GiB):

    apiVersion: storage.deckhouse.io/v1alpha1
    kind: LVMVolumeGroup
    metadata:
      name: "vg-0-on-node-0"
    spec:
      type: Local
      local:
        nodeName: "node-0"
      blockDeviceSelector:
        matchExpressions:
          - key: kubernetes.io/metadata.name
            operator: In
            values:
              - dev-07ad52cef2348996b72db262011f1b5f896bb68f
              - dev-e90e8915902bd6c371e59f89254c0fd644126다7
      actualVGNameOnTheNode: "vg-0"
      thinPools:
        - name: thin-1
          size: 250Gi
    
    apiVersion: storage.deckhouse.io/v1alpha1
    kind: LVMVolumeGroup
    metadata:
      name: "vg-0-on-node-0"
    spec:
      type: Local
      local:
        nodeName: "node-0"
      blockDeviceSelector:
        matchLabels:
          kubernetes.io/hostname: node-0
      actualVGNameOnTheNode: "vg-0"
      thinPools:
        - name: thin-1
          size: 250Gi
    

Пользователь может использовать любые допустимые селекторы (matchLabels или matchExpressions) для выбора BlockDevice:

  • matchLabels позволяет выбрать все устройства, у которых совпадает указанный лейбл (например, kubernetes.io/hostname=node-0);
  • matchExpressions позволяет задать более гибкие выражения, например, включение перечисленных имён устройств.

Поле spec.local.nodeName должно совпадать с именем узла, на котором создаётся Volume Group. В противном случае ресурс не будет запущен. Все выбираемые блочные устройства должны физически находиться на одном узле для ресурса с типом Local.

После применения манифеста контроллер автоматически создаст или обновит инфраструктуру LVM на узле, включая работу с physical volumes (pvcreate), созданием или изменением группы томов (vgcreate/vgextend) и настройкой thin pool (lvcreate).

Обновление ресурса LVMVolumeGroup

Для изменения конфигурации группы томов (добавление/удаление блочных устройств или изменение параметров thin pool) достаточно отредактировать секцию spec ресурса. Контроллер выполнит валидацию новых параметров и скорректирует состояние на узле (выполнит команды vgextend, lvcreate, lvresize и т.д.).

Секция status ресурса автоматически отражает актуальную информацию о состоянии Volume Group (список physical volumes, данные о thin pool, свободное/занятое пространство и т.д.).

  • Контроллер не изменяет spec автоматически — он только читает желаемое состояние и приводит фактическое состояние узла в соответствие. Все изменения желаемого состояния должны вноситься пользователем в spec.
  • Не рекомендуется вносить правки в status, так как этот раздел управляется контроллером.

Удаление ресурса LVMVolumeGroup

Контроллер удаляет ресурс автоматически, если на узле больше не существует Volume Group (например, все блочные устройства отключены или удалены). Для ручного удаления Volume Group и связанных с ней логических томов пользователь может вызвать следующую команду:

d8 k delete lvg <имя-ресурса>

После этого контроллер выполнит полномасштабное удаление физических и логических объектов LVM (lvremove, vgremove и т. д.).

Если в Volume Group присутствуют логические тома (включая thin pool), пользователь должен сначала удалить или переместить их (команды lvremove или pvmove + vgreduce). В противном случае контроллер не сможет удалить ресурс. Чтобы предотвратить случайное удаление ресурса, можно назначить аннотацию storage.deckhouse.io/deletion-protection — пока она присутствует, контроллер не будет инициировать удаление группы томов.

Вывод ресурса BlockDevice из LVMVolumeGroup

  1. Чтобы исключить конкретный BlockDevice из состава Volume Group, отредактируйте селектор spec.blockDeviceSelector ресурса LVMVolumeGroup или удалите соответствующий лейбл у ресурса BlockDevice.

  2. Вручную перенесите данные на узле (если в Volume Group есть тома) следующей командой:

    pvmove <исходное-устройство>
    
  3. Удалите физический том из группы:

    vgreduce <имя-VG> <исключаемое-устройство>
    
  4. Окончательно уберите метаданные физического тома:

    pvremove <исключаемое-устройство>
    

Если в процессе удаления остаются логические тома, удалите их заранее с помощью команды lvremove. Для защиты от непреднамеренного удаления можно использовать аннотацию storage.deckhouse.io/deletion-protection — до её снятия ресурс LVMVolumeGroup не будет удалён.

Защита от утечек данных между томами

При удалении файлов операционная система не стирает данные на диске: соответствующие блоки лишь помечаются как «свободные». Если при создании нового тома используются те же физические блоки, они могут содержать остаточные данные предыдущего пользователя.

Например:

  • Пользователь #1 создал том из StorageClass #1 на узле #1 (вне зависимости от режима Block или Filesystem) и записал в него файлы.
  • Пользователь #1 удалил файлы и удалил сам том.
  • Блоки, которые занимал этот том, были помечены как свободные, но не перезаписаны.
  • Пользователь #2 запросил новый том из StorageClass #1 на том же узле #1 в блоковом режиме.
  • Существует риск, что некоторые или все ранее освобождённые блоки будут вновь выделены пользователю #2.
  • В результате пользователь #2 может получить доступ к данным пользователя #1.

Thick volume

Для обеспечения конфиденциальности используется параметр volumeCleanup в конфигурации PersistentVolume. Доступные режимы:

  • Параметр не задан — при удалении тома никакие дополнительные операции не выполняются, блоки остаются неинициализированными, и данные могут быть доступны следующему пользователю.
  • RandomFillSinglePass — том перезаписывается одноразово случайными данными (рекомендуется избегать на твердотельных накопителях из-за износа).
  • RandomFillThreePass — том трижды перезаписывается случайными данными (максимальная надёжность, но высокая нагрузка на устройство).
  • Discard — вызовом TRIM все блочные устройства помечаются как свободные. Эффективно на большинстве современных твердотельных накопителей, но зависит от поддержки контроллера SSD (DRAT/RZAT).

Использование режима Discard

Большинство современных твердотельных накопителей поддерживает команду discard (TRIM), которая помечает освобождённые блоки как «ноль» и при последующем чтении не возвращает старые данные. Это делает опцию Discard наиболее надёжным способом предотвратить утечки данных на твердотельных накопителях. Однако следует учитывать следующие нюансы:

  • Команда discard инициирует очистку ячеек, но фактическое обнуление выполняется контроллером накопителя в фоновом режиме. До завершения этой операции некоторые блоки могут содержать старые данные.
  • Не все твердотельные накопители очищают конкретный физический блок по одиночке: многие устройства работают на уровне страниц и более крупных блоков, поэтому отдельные логические блоки могут оставаться неочищенными до тех пор, пока вся страница не будет полностью обработана.
  • Чтобы быть уверенным, что после discard блоки действительно читаются как нули, диск должен поддерживать DRAT (Deterministic TRIM) — предсказуемое поведение команды discard и RZAT (Deterministic Read Zero after TRIM) — гарантированное чтение «нулей» после discard. Если устройство не имеет подтверждённой поддержки DRAT и RZAT, нет гарантии, что освобождённые блоки больше никогда не вернут прежние данные.
  • Даже при заявленной поддержке DRAT/RZAT производители могут не соблюдать спецификации полностью. При отсутствии независимых тестов или сертификации твердотельных накопителей рекомендуется воздержаться от использования режима Discard для критичных задач, связанных с безопасностью данных.

Если накопитель не подтверждён как поддерживающий DRAT и RZAT (то есть не прошёл проверку на детерминированную очистку и чтение обнулённых блоков), использование опции Discard не рекомендуется, поскольку остаётся риск восстановления старых данных.

Thin volume

При освобождении блока thin volume команда discard из гостевой ОС передаётся непосредственно на устройство хранения. Если используется механический жёсткий диск или твердотельный накопитель без поддержки discard, освобождаемые блоки в thin pool остаются с предыдущими данными до момента их физической перезаписи. Однако клиентам предоставляются только thin volume, а не доступ к самому thin pool. При следующем выделении блока thin-LV LVM гарантированно заполняет соответствующий блок thin pool нулями (zero-fill), благодаря параметру thin_pool_zero=1. Это обеспечивает отсутствие остаточных данных между разными thin volume и предотвращает утечки информации между клиентами.