Tags: linux device-maper
Categories: None

В этой небольшой заметке я хотел бы разобраться что такое Device-mapper, как он работает и для чего нужен.

Впервые столкнувшись с device-mapper при использовании cryptsetup и docker решил разобраться как это все работает.

Intro

Device-mapper - это подсистема ядра Linux, которая позволяет мэппить одно блочное устройство на другое или несколько других. Во время мэппинга можно делать разные операции с данными, например шифровать/дешифровать данные. Это позволяет реализовать объединение нескольких реальных устройств в одно виртуальное, создавать snapshot’ы, организовывать балансировку между устройствами, делать шифрованные тома и т.д.

Именно через device-mapper, в качестве низкоуровнего API, работают LVM, cryptsetup, Docker (Один из вариантов storage драйвера), dm-multipath и многие другие.

Как работает?

Юзерспейс приложения для взаимодействия с device-mapper используют библиотеку libdevmapper.so (Она работает через ioctl). Так же существует утилита dmsetup. С ее помощью можно вручную создавать, изменять, удалять dm-устройства. Это в общем по части конфигурации.

А вот сама работа dm в целом выглядит так. Блоки виртуального устройства ставятся в соответсвие блокам целевого устройства (или устройств) основываясь на таблице соответствия и типе преобразования.

Виртуальное устройство. Это новое виртуальное устройство, которое создает dm.
Целевое устройство или устройства. Это те устройства, на которые маппится виртуальное устройство.
Таблица соответсвия (Mapping table). В ней описываются параметры маппинга, с какого по какой сектор маппить и т.д.
Тип преобразования (Mapping target). При мэппинге происходит преобразование, например.

  • linear - собственно ничего не преобразуется, просто блоки одного устройства мэппятся ну другие.
  • error - эмулируются ошибки ввода-вывода.
  • zero - просто возвращает нули. В этом случае целевого устройства как такового нет.
  • snapshot - для реализации механизма copy-on-write.
  • Полный список целей можно посмотреть в документации ядра.
    После того как все настроено, новое виртуальное появляется в каталоге /dev/mapper.

dmsetup

Теперь обсудим как пользоваться утилитой dmsetup и как писать те самые таблицы.

Создаются новые устройства так:

dmsetup create DEVICE_NAME --table ’тут пишем таблицу’

Смотрим:

dmsetup ls

Удаляем:

dmsetup remove DEVICE_NAME

Таблицы соответствия

Таблицу представляет собой текстовые строки, разделенные символом переноса строки \n. Формат строки таблицы следующий:

start_sector sectors_length mapping_target target_parameters

  • start_sector - начальный сектор для мэппинга
  • sectors_length - длинна в секторах
  • mapping_target - тип мэппинга (linear, zero, snaphot и т.д.)
  • target_parameters - параметры, необходимые для mapping_target. Они уже у каждого target’а свои, например у linear надо указать путь до устройства, на которое будет происходить мэппинг (список аргументов для каждого target’а см. в документации к соответствующему модулю).

Для dmsetup таблицу можно указывать несколькими способами.
Например если это одна строка, можно так:

dmsetup create new_device --table ’0 2048 zero’

Если таблица состоит из нескольких строк, то можно передать пайпом:

echo -e ’
0 2048 linear /dev/sdb1 0
2048 2048 linear /dev/sdb2 0
4096 2048 linear /dev/sdb3 0
’ | dmsetup create new_device

И еще есть сбособ указать файл с таблицей. Это, и все остальные подробности о dmsetup хорошо описаны в man 8 dmsetup.
Теперь у нас есть полная картина и можно рассмотреть несколько примеров.

Объединяем несколько блочных устройств в одно

Создадим 3 файла под диски, которые будем объедлинять и сделаем из них loopback устройства (С таким же успехом можем брать «настоящие» устройства, например /dev/{sda,sdb,sdc,...})

dd if=/dev/zero of=disk0.img bs=1M count=1
dd if=/dev/zero of=disk1.img bs=1M count=1
dd if=/dev/zero of=disk2.img bs=1M count=1
losetup /dev/loop0 disk0.img
losetup /dev/loop1 disk1.img
losetup /dev/loop2 disk2.img

Создаем новое устройство disks:

echo -e ’
0 2048 linear /dev/loop0 0
2048 2048 linear /dev/loop1 0
4096 2048 linear /dev/loop2 0
’ | dmsetup create disks

В таблице мы указали, что устройство loop0 будет маппиться на устройство disks с 0-го сектора и будет иметь длинну 2048 секторов, loop2 c 2048-го сектора, loop3 с 4096-го сектора. Итого последовательно замэппили 3 устройства на одно.
Последний параметр 0, после пути до устройства - это смещение, но так как мы хотим объединять устройства от начала до конца, то указываем 0.

Посмотрим созданное устройство:

$ sudo dmsetup ls
disks (254:0)
$ ls /dev/mapper
control disks

Так как объединяли 3 устройства по мегабайту - disks должен иметь размер 3Мб:

$ sudo fdisk -l /dev/mapper/disks
Disk /dev/mapper/disks: 3 MiB, 3145728 bytes, 6144 sectors

Почистим за собой:

sudo dmsetup remove disks
sudo losetup -d /dev/loop{0,1,2}
rm -f disk{0,1,2}.img

Создадим зашифрованный том

Допустим у нас есть диск /dev/sdb1, который мы хотим зашифровать.

Создаем зашифрованное устройство:

dmsetup create crypt_disk --table ’0 2048 crypt aes-xts-plain64 babebabebabebabebabebabebabebabebabebabebabebabebabebabebabebabe 0 /dev/sdb1 0 1 allow_discards’

Тут с параметрами target’а я не разбирался, если интересно можно посмотреть тут.

Теперь можем пользоваться нашим /dev/mapper/crypt_disk с помощью cryptsetup:

cryptsetup plainOpen /dev/mapper/crypt_disk crypt
mkfs.ext3 /dev/mapper/crypt
mount /dev/mapper/crypt /mnt
echo "Hello, world" > /mnt/hello
umount /mnt
cryptsetup close /dev/mapper/crypt

Эмуляция ошибок ввода/вывода

Теперь создадим устройство, которое будет эмулировать ошибку ввода/вывода при обращении к определенному сектору.

echo ’
0 995 linear /dev/loop0 0
995 5 error
1000 1048 linear /dev/loop0 995’ | dmsetup create error

С 0-го по 995-й сектор мэппим на loop0
С 995-го пять секторов у нас будут сбойными
Начиная с 1000-го продолжаем обычный мэппинг, пропуская 995 секторов
В итоге получается устройство размеров 2048 секторов с 5-ю сбойными 995-1000

Проверяем:

cat /dev/mapper/error > test
cat /dev/mapper/error: Input/output error

Ссылки

comments powered by Disqus