Использование Linux в SAN сети. Маленькая хитрость

7 Окт
2011

Всем доброго времени суток.
Я не думаю, что ситуация, описанная мной, типична для большинства, но тем не менее думаю это будет полезно знать. Мой рассказ о способе защиты «от дурака» в случае, когда у вас есть SAN сеть, есть несколько СХД(Система хранения данных), которые не поддерживают технологию ограничения доступа к определённым LUN (Logical Unit Number) и серверы на базе Linux (в нашем случае все действия будут проводиться с использованием ОС SLES 10SP1).

Предыстория


В 2008 году мне пришлось вводить в строй вычислительную инфраструктуру одного заказчика. Была построена небольшая SAN сеть с некоторым количеством стораджей (порядка 10 штук разного класса). Большая часть этих стораджей предназначалась для построения файловой системы вычислительного кластера, поэтому изначально проблема была не очевидна и об этом даже не задумывались.
А проблема заключается в том, что для некоторых инфраструктурных подсистем был выделен сторадж, который не умеет ограничивать доступ к определённым LUN с определённых хостов (у каждого производителя она называется по разному Port Masking, LUN MAPPING и т.п.), правильнее сказать оно умеет это делать, но требуется покупка доп. лицензии, а этого сделано не было. И вот тут встала проблема — все серверы, которые подключаются к данному стораджу видят все его разделы. Вроде бы в этом нет ничего страшного, но надо защитить заказчика от возможности форматирования чужого раздела, т.к. данные каждой подсистемы очень важны.
Итого мы имеем:
  • Систему хранения данных LSI(точнее одного из его партнёров)
  • Серверы на базе SLES10 SP1 с двухголовым FC адаптером
  • Огромное желание подстраховать себя и заказчика от ошибок.

Шаг первый, вступительный


Сразу оговорюсь для тех кто не имеет сильных познаний в теме:
Каждое устройство в SAN сети имеет уникальный адрес WWN(World Wide Name), это некий аналог MAC адреса сетевого адаптера, но, как и в случае Ethernet, коммутатор не знает что лежит внутри пакета, приходящего на коммутатор, поэтому сделать ограничение на коммутаторе (зонирование) нельзя, можно только ограничить область видимости Систем Хранения Данных для определённых узлов, что и было сделано.
Круг поиска решения ограничился следующими вариантами:
  1. Наличие в драйвере возможности использования только определённых LUN
  2. Использовать средства ОС для блокировки ненужных LUN

Первый вариант

Он отпал после того, как посмотрели информацию по данному модулю. В нем присутствует только опция для сканирования устройства и определяет максимальное кол-во LUN. Так что этот вариант отпадает
Второй вариант

Данный вариант сразу направляет нас «проводить земельные работы» в направление подсистемы udev. Именно она отвечает в linux за инициализацию устройств, присвоении им специализированных имен и прочих действий. Проще говоря, нам надо правильно описать правило для udev, которое создаст только необходимые нам устройства.
Забегая немного вперёд скажу, что данное решение ограничит только создание спец. устройств (вроде /dev/sdb, /dev/sdc и т.п.), но физически хост будет видеть все LUN, доступные ему.

Шаг второй, исследование


Приступим к детальному изучению темы.
После беглого изучения документации, а так же тех правил, что есть в системы можно вычленить следующие блоки, которые нам интересны:
  1. Блок «SUBSYSTEM», который определяет подсистему. Подсистемы бывают трёх типов «block» (блочные устройства), «char» (символьные устройства) и «pipe» (устройства FIFO).
    man mknod

    Нас интересует подсистема «block», т.к. диски относятся именно к данному классу.
  2. udev может просматривать атрибуты устройства. В них обычно хранятся различные данные, такие как:
    • тип производителя,
    • наименование модели,
    • различные аппаратные данные.

    Чтобы выяснить нужные нам параметры потребудется:
    Утилита lssci — отображает все SCSI устройства, которые доступны в системы, а так же имя спец. устройства, которое закреплено за данным SCSI устройством
    Утилита udevinfo — показывает все необходимые данные по запрашиваемому устройству.
    Приступим:
    Для начала нам надо отобразить список всех устройств, которые доступны на данном сервер:
    # lssci
    SCSI ID TYPE VENDOR MODEL REV. device
    [5:0:0:10] disk XXX YYYYY 0619 /dev/sdr
    [5:0:0:20] disk XXX YYYYY 0619 /dev/sds
    [5:0:0:21] disk XXX YYYYY 0619 /dev/sdu
    ......
    [5:0:1:10] disk XXX YYYYY 0619 /dev/sdan
    [5:0:1:20] disk XXX YYYYY 0619 /dev/sdao
    [5:0:1:21] disk XXX YYYYY 0619 /dev/sdar

    Вывод данной команды будет большим, поэтому приведу только часть.
    Таким образом, теперь мы знаем:
    SCSI ID — идентификатор в формате H:B:T:L (Host:Bus:Target:Lun) Host — это порядковый номер адаптера, который отвечает за данное устройство, Bus(Channel) — номер SCSI канала на адаптере, Target — это номер FC порта на сторадже, LUN — идентификатор раздела, выделенного на сторадже.
    VENDOR — имя производителя стораджа
    MODEL — наименование модели
    REV. — версия FirmWare на сторадже
    device — спец. устройство доступное пользователю
    В данной операции нас интересовало поле «device» и «SCSI ID». Остальные поля игнорируем.
  3. Следующим шагом получим список атрибутов хранилища, которые нам необходимы:
    • имя драйвера
    • название производителя
    • название модели

    Для этого нам и понадобится утилита udevinfo, а так же файловая система sysfs (для версий ядра 2.6), т.к. именно в ней хранится все необходимая нам информация.
    # udevinfo -a -p /sys/block/sds
    looking at device '/block/sds':
    KERNEL=="sds"
    SUBSYSTEM=="block"
    SYSFS{stat}==" 151707 2355 3422965 430068 436954 12273 18585533 1098588 0 651944 1528756"
    SYSFS{size}=="2129838080"
    SYSFS{removable}=="0"
    SYSFS{range}=="16"
    SYSFS{dev}=="65:32"
    looking at device '/devices/pci0000:00/0000:00:04.0/0000:08:00.0/host5/rport-5:0-0/target5:0:0/5:0:0:20':
    ID=="5:0:0:20"
    BUS=="scsi"
    DRIVER=="sd"
    SYSFS{ioerr_cnt}=="0x7"
    SYSFS{iodone_cnt}=="0xec62d"
    SYSFS{iorequest_cnt}=="0xec62d"
    SYSFS{iocounterbits}=="32"
    SYSFS{retries}=="5"
    SYSFS{timeout}=="60"
    SYSFS{state}=="running"
    SYSFS{rev}=="0619"
    SYSFS{model}=="YYYYY "
    SYSFS{vendor}=="XXX "
    SYSFS{scsi_level}=="6"
    SYSFS{type}=="0"
    SYSFS{queue_type}=="simple"
    SYSFS{queue_depth}=="64"
    SYSFS{device_blocked}=="0"
    ....
    looking at device '/devices/pci0000:00/0000:00:04.0/0000:08:00.0':
    ID=="0000:08:00.0"
    BUS=="pci"
    DRIVER=="mptfc"
    SYSFS{modalias}=="pci:v00001000d00000646sv00001000sd00001020bc0Csc04i00"
    SYSFS{local_cpus}=="00000000,00000000,00000000,0000000f"
    SYSFS{irq}=="169"
    SYSFS{class}=="0x0c0400"
    SYSFS{subsystem_device}=="0x1020"
    SYSFS{subsystem_vendor}=="0x1000"
    SYSFS{device}=="0x0646"
    SYSFS{vendor}=="0x1000"

    Запрашивать информацию по каждому устройству нет необходимости, т.к. вывод «lsscsi» содержит дублирующие записи. Это пути до определённого LUN через разные устройства (например 1 порт FC на хосте и 3 Порт на хранилище), а так как это одно и тоже хранилище, то требуемые нам параметры будут у всех одинаковыми.

Краткий итог:
  1. DRIVER==«sd» — имя драйвера, который идентифицирует данное устройство. Почему именно «sd», а не драйвер FC «mptfc» — потому, как именно драйвер «sd» занимается определением дискового устройства, полученного с адаптеров SCSI/FC.
  2. SYSFS{vendor}==«XXX » — производитель хранилища. Если обратите внимание, то его имя не просто «XXX», а ещё и некоторое количество пробелов. Именно по этой причине вывод lsscsi не так информативен и в случае использования только «XXX» правило попросту не сработает.
  3. SYSFS{model}==«YYYYY » — наименование модели. Та же причина, что и в пункте выше.
  4. ID==«5:0:0:20» — идентификатор в формате H:B:T:L. Его тоже нужно запомнить, т.к. оно потребуется нам далее.
  5. BUS==«scsi» — тип шины. Нам потребуется для более точной идентификации устройства

Итак, теперь мы обладаем всей необходимой информацией для того, чтобы написать правило.

Шаг третий, контора пишет!


Теперь переходим непосредственно к написанию правила. Нам понадобится документация по udev.
Привожу конечное правило и описание опций:
# If not a block device (ex. tape) ignore this rule 
SUBSYSTEM!="block", GOTO="go_ignore_end"
# Phisical device section
BUS=="scsi", DRIVER=="sd", SYSFS{vendor}=="XXX ", SYSFS{model}=="YYYYY ", ID=="*:*:*:20", GOTO="go_ignore_end"
BUS=="scsi", DRIVER=="sd", SYSFS{vendor}=="XXX ", SYSFS{model}=="YYYYY ", ID=="*:*:*:21", GOTO="go_ignore_end"
BUS=="scsi", DRIVER=="sd", SYSFS{vendor}=="XXX ", SYSFS{model}=="YYYYY ", OPTIONS+="ignore_device,last_rule"
LABEL="go_ignore_end"

А теперь объяснения что к чему:
SUBSYSTEM!="block", GOTO="go_ignore_end" 

Данная опция указывает udev, что в случае если подсистема работает с любым неблочным устройством, то данное правило не применять.
BUS=="scsi", DRIVER=="sd", SYSFS{vendor}=="XXX ", SYSFS{model}=="YYYYY ", ID=="*:*:*:20", GOTO="go_ignore_end" 
BUS=="scsi", DRIVER=="sd", SYSFS{vendor}=="XXX ", SYSFS{model}=="YYYYY ", ID=="*:*:*:21", GOTO="go_ignore_end"

Это самая важная часть, собственно, ради этой части и писалась данная статья. В правиле задано поведение: если через шину BUS==«scsi» попадает устройство и оно использует драйвер DRIVER==«sd», так же имеем производителя SYSFS{vendor}==«XXX » и наименование модели SYSFS{model}==«YYYYY » и LUN данного устройства соответствует ID==»*:*:*:20″, то данное устройство должно проследовать всем остальным правилам и создать спец. устройство в каталоге /dev.
Запись ID==»*:*:*:20″ — это запись в формате H:B:T:L, которая описывает только LUN идентификатор, остальные поля могут быть любыми. Это позволяет нам пропустить все устройства с хранилища, которые имеют заданный LUN. Если нужно расширить список LUN’ов, то просто добавляются записи и в последнее поле записывается нужный нам идентификатор. Главное, чтобы они размещались перед следующим правилом.
BUS=="scsi", DRIVER=="sd", SYSFS{vendor}=="XXX ", SYSFS{model}=="YYYYY ", OPTIONS+="ignore_device,last_rule"

Данное правило применяет те же условия, что и предыдущее, только OPTIONS+=«ignore_device,last_rule» говорит udev, что нужно проигнорировать все дальнейшие действия с данным устройством. Т.е. все устройства, кроме заданных будут проигнорированы.
LABEL="go_ignore_end"

Данное правило является простой меткой конца файла. Оно должно быть последним в списке.

Шаг четвертый, последняя битва


Теперь нам осталось только сохранить данное правило и задействовать его.
Сохраняем данное правило в папке /etc/udev/rules.d в правильном формате. Я использовал имя «52-ignore-unused-devices.rules». После этого необходимо проделать ещё кое-что. Если драйвер адаптера находится в рамдиске, то его надо удалить оттуда и загружать только после того, как запустится подсистема udev, в противном случае данное правило не сработает, т.к. устройства будут созданы ещё до начала работы udev.
Для этого необходимо внести правки в файл /etc/sysconfig/kernel:
  1. Удалить в переменной INITRD_MODULES=»….» драйвер FC устройства.
  2. Добавить его в переменную MODULES_LOADED_ON_BOOT=»…»
  3. Перегенерировать рамдиск командой mkinitrd.

Теперь можно перезагрузить сервер и убедиться, что созданы только те устройства, которые нам нужны. Остальные, хотя и видны в системе, но не заданы спец. файлами, а следовательно пользователь не сможет сделать с ними ничего. Выглядеть это будет так:
# lssci
SCSI ID TYPE VENDOR MODEL REV. device
[5:0:0:10] disk XXX YYYYY 0619 -
[5:0:0:20] disk XXX YYYYY 0619 /dev/sds
[5:0:0:21] disk XXX YYYYY 0619 /dev/sdu
......
[5:0:1:10] disk XXX YYYYY 0619 -
[5:0:1:20] disk XXX YYYYY 0619 /dev/sdao
[5:0:1:21] disk XXX YYYYY 0619 /dev/sdar

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



загрузка...

Комментарии:

Наверх