udev で SCSI デバイス名を固定する

SERVER


デバイス名固定の必要性

SCSI や USB などのたくさんのデバイスを搭載できるインターフェイスでは、デバイスの抜き差しを行ったときに、Linux で認識されるデバイス名が変わってくる場合があるのが厄介です。

 

例えば SCSI の場合、Linux では一般に /dev/sda, /dev/sdb, /dev/sdc といったように、/dev/sd に続けて a から順にデバイス名が付けられて行きます。

SCSI にはデバイス毎に SCSI ID という番号を付けて管理するようになっていますが、この番号は連番である必要がないため、間が抜けたりすると、Linux での a から始まるアルファベットと、SCSI ID の 0 から始まる番号との順序が必ずしも一致しません。

単に一致しないだけならいいのですけど、途中でデバイスが不要になったり、故障で取り外す必要が出てきた場合に、これまでのデバイス名と実際の SCSI ID との対が狂ってくると、パーティションのマウントポイントを間違えたりなど、困ったことになったりもします。

この SCSI ID のデバイスは必ずこのデバイス名で扱えるというのが定まってくれれば、そういったマウントし違えるといった大きな問題は、きっと起こらなくなってくれるでしょう。

 

udev でデバイスに任意の名前を付ける

デバイスの名前を固定するには udev を使って実現することができます。

方法としては、デバイスの固有情報を確認して、それを条件にしたルールを "/dev/udev/rules.d" に用意することで、デバイスに好きな名前を設定して利用することができるようになる感じです。

 

もっとも CentOS 6.0 では、標準で "/dev/disk/by-id/" ディレクトリにシリアル ID によるデバイス名が、"/dev/disk/by-path/" ディレクトリに接続インターフェイスとその番号でのデバイス名が自動的に用意されるようでした。

ただ、今回の自分の環境は CentOS 6.0 を Windows 8 の Hyper-V で使用する で用意した Hyper-V サーバー上の CentOS 6.0 だったのですけど、Hyper-V の環境のためなのか、別のインターフェイスでも同じ SCSI ID の場合には、どれかひとつへ向けたリンクしか用意してくれない感じでした。

まあ Windows 8 の Hyper-V では 64 個までハードディスクを搭載できるようなので、SCSI インターフェイスの 1 つでもあれば十分そうな気もします。

そんな感じで、わざわざ自分で設定する必要もなさそうでしたけど、雰囲気を掴むためにも自分で設定してみようと思います。

 

デバイスの情報を確認する

udev でデバイスに名前をつけるために、まずは SCSI デバイスが CentOS 6.0 でどのように認識されているかを確認します。

udevadm info --query=all --name=/dev/sdb

例えばコマンドラインから、このように実行することで、下記のような "/dev/sda" のデバイス情報が画面に表示されました。

P: /devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:07/VMBus:00/vmbus_0_9/host2/target2:0:0/2:0:0:0/block/sdb

N: sdb

W: 40

S: block/8:16

S: disk/by-id/scsi-14d53465420202020367b577df7aca64f8cb643fe0b8431f4

S: disk/by-path/scsi-0:0:0:0

E: UDEV_LOG=3

E: DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:07/VMBus:00/vmbus_0_9/host2/target2:0:0/2:0:0:0/block/sdb

E: MAJOR=8

E: MINOR=16

E: DEVNAME=/dev/sdb

E: DEVTYPE=disk

E: SUBSYSTEM=block

E: ID_SCSI=1

E: ID_VENDOR=Msft

E: ID_VENDOR_ENC=Msft\x20\x20\x20\x20

E: ID_MODEL=Virtual_Disk

E: ID_MODEL_ENC=Virtual\x20Disk\x20\x20\x20\x20

E: ID_REVISION=1.0

E: ID_TYPE=disk

E: ID_SERIAL_RAW=14d53465420202020367b577df7aca64f8cb643fe0b8431f4

E: ID_SERIAL=14d53465420202020367b577df7aca64f8cb643fe0b8431f4

E: ID_SERIAL_SHORT=4d53465420202020367b577df7aca64f8cb643fe0b8431f4

E: ID_BUS=scsi

E: ID_PATH=scsi-0:0:0:0

E: ID_PART_TABLE_TYPE=do

この情報を udev で指定して、目的のデバイスに任意の名前を付けて行く感じになります。

 

着目すると良さそうなところとしては、まず、SUBSYSTEM が "block" となっていて、ID_BUS が "scsi" になっています。

このあたりから、このデバイスが SCSI デバイスであると思って良さそうです。

 

そして、ID_SCSI には "1" が設定されていますが、Windows 8 Hyper-V の SCSI では、どのインターフェイスでも、どの SCSI ID の機器でも "1" に設定される様子でした。

SCSI ID を見るには ID_PATH の最後の数字を見る感じになりそうです。ただし、複数インターフェイスが搭載されている場合、同じ SCSI ID が割り当てられた機器であれば、この ID_PATH の値が同じになるようでした。

インターフェイスの違いを確認できるとすると、DEVPATH の途中に現れる "/host0/" というところでしょうか。2 つ目のインターフェイスであれば "/host1/" に、3 つ目であれば "/host2/" になるので、この数字を以って判断することはできそうです。

 

他にも "ID_SERIAL" という値で、接続されているデバイス固有の番号を確認できるので、これを使えばどの SCSI ID が割り当てられている機器かではなく、デバイスごとに固有の名前を付けることもできます。

ただし Hyper-V などの仮想ハードディスクの場合、VHD ファイルをコピーして使いまわしていたりした場合には、この "ID_SERIAL" の値が同じになってしまうので注意が必要です。

 

ちなみに余談になりますけれど、SCSI ディスクの固有の ID は、次のコマンドを使っても取得できます。

scsi_id --page=0x83 --whitelisted --device=/dev/sda

今回は使用しませんけど、スクリプト等で SCSI デバイスの固有の ID を取得したい場合などに役に立つかもしれません。

 

udev のルールを作成する

udev は、予め設定されたルールに基づいてデバイス名を設定します。

CentOS 6.0 の場合は "/etc/udev/rules.d/" に保存されたルールファイルに従って、ファイル名順に順次適用して行くようになっているので、デバイスに任意の名前を付けるためのルールファイルをここに作成します。

SCSI ID 毎に名前を付ける

たとえば、次のようなルールを用意することで、SCSI ID 毎に名前を付けることができました。

まず、ルールファイル "/etc/udev/rules.d/80-scsi.rules" を以下の内容で作成します。

SUBSYSTEM=="block", ENV{ID_BUS}=="scsi", ENV{ID_VENDOR}=="Msft", ENV{ID_MODEL}=="Virtual_Disk", ENV{ID_TYPE}=="disk", DEVPATH=="*/host*/target*/*/*", PROGRAM="/etc/udev/rules.d/scsi.sh '%n'", NAME="scsi/%c"

もっと簡略化できるような気もしますけど、とりあえず Windows 8 Developer Preview に付属の Hyper-V で実装した SCSI ハードディスクに名前を付けたい場合には、このような条件で設定すれば大丈夫そうでした。

そして、この中で実行している名前決め用のスクリプト "/etc/udev/rules.d/scsi.sh" は次の内容にしてみています。

#!/bin/sh

 

NUMBER="$1"

BASENAME=$(/bin/echo "$DEVPATH" | /bin/sed -e 's/^.*\/host\([0-9]\+\)\/target[^\/]\+\/\([0-9]\+:\)\{3\}\([0-9]\+\)\/.*$/host\1\/id\3/')

 

if [ "$NUMBER" = "" ]

then

echo $BASENAME

else

echo ${BASENAME}p${NUMBER}

fi

今回のスクリプトの保存場所は、ルールと同じ場所にしています。udev は、"/etc/udev/rules.d/" ディレクトリの中の拡張子が "*.rules" となっているファイルしか読み込まないので、同じ場所に置いても大丈夫です。

 

最初は、できればルールとスクリプトを分けないように書こうと考えていたのですけど、どうやらルール内の PROGRAM での指定ではパイプが機能しないようで、DEVPATH の値を sed へ渡すことができませんでした。

また、呼び出し先の環境変数に、カーネル番号を示す変数 ($number, %n) が渡されないようだったので、それについては引数を経由してルールから受け取るようにしています。

実行スクリプトには udev デバイスに関連する情報が環境変数で渡されるので、例えばここでは $DEVPATH を参照して、検査対象のデバイス名を取得しています。

 

その他の使用できる環境変数についてみてみると、ざっと次のような項目が環境変数として参照できる様子でした。

ID_MODEL Virtual_Disk
ID_MODEL_ENC Virtual\x20Disk\x20\x20\x20\x20
ID_SERIAL_RAW 14d53465420202020367b577df7aca64f8cb643fe0b8431f4
ID_REVISION 1.0
DEVTYPE disk
ID_BUS scsi
SUBSYSTEM block
ID_SERIAL 14d53465420202020367b577df7aca64f8cb643fe0b8431f4
DEVPATH /devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:07/VMBus:00/vmbus_0_7/host0/target0:0:0/0:0:0:0/block/sda
ID_VENDOR_ENC Msft\x20\x20\x20\x20
MINOR 0
ID_SCSI 1
ACTION add
PWD /etc/udev/rules.d
UDEV_LOG 6
MAJOR 8
DEVLINKS /dev/block/8:0/dev/disk/by-id/scsi-14d53465420202020367b577df7aca64f8cb643fe0b8431f4/dev/disk/by-path/scsi-0:0:0:0
DEVNAME sda
SHLVL 1
ID_TYPE disk
ID_PART_TABLE_TYPE dos
ID_VENDOR Msft
ID_SERIAL_SHORT 4d53465420202020367b577df7aca64f8cb643fe0b8431f4
ID_PATH scsi-0:0:0:0

このような環境変数も使いながら、デバイス検出時の処理を実装して行きます。

 

今回はこのようなスクリプトを用意して、CentOS 起動時から、SCSI デバイスを次のような名前で使用することが可能になりました。

/dev/scsi/host0/id0

パーティションが切られている場合には、次のような名前になります。

/dev/scsi/host0/id0p1

この名前で /etc/fstab でマウントポイントを定義して、Linux 起動時にそれをマウントさせることも可能です。

 

ただ、Hyper-V の都合なのか、それとも CentOS 6.0 の起動のタイミングなのか、稀に SCSI インターフェイスの順番が違って認識される場合がありました。

udev に伝わる host 番号も変わってしまい、他の何か固有番号といったものもなさそうなので、そうなるとどうにも判断できない様子です。

そんなところから、可能であれば念のため Hyper-V で SCSI を使用する場合は、インターフェイスを 1 つまでにしておくのが安心なのかもしれないです。

 

ちなみにルールが正しくかけているかの確認は udevadmin コマンドで確認することができるようでした。

udevadm test /devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:07/VMBus:00/vmbus_0_7/host0/target0:0:0/0:0:0:0/block/sda

このように、DEVPATH の名前を test の後の引数に指定することで、このデバイスに適用するルールを確認して、表示してくれるような感じです。

ディスク固有の ID で名前を付ける

CentOS 6.0 では "/dev/disk/by-id/" ディレクトリに、ディスクごとに割り当てられた ID 名が付けられた名前が用意されているので、ID を使ってマウント指定をしたい場合は、それを使えば大丈夫そうです。

そこでここでは、ディスク固有の ID 名ではなく、分かりやすい名前を別にセットする感じで、udev のルールを作ってみたいと思います。

 

今回は、例えばディスクの ID が "14d53465420202020367b577df7aca64f8cb643fe0b8431f4" のデバイスに "/dev/backup" という名前を付ける感じにしようと思うので、これならばルールファイルだけで定義できます。

SUBSYSTEM=="block", ENV{ID_BUS}=="scsi", ENV{ID_SERIAL}=="14d53465420202020367b577df7aca64f8cb643fe0b8431f4", SYMLINK+="backup%n"

今回は SYMLINK を使って、リンクを作成するようにしてみました。

また、SYMLINK の名前で "%n" を付けているので、対象のハードディスクがパーティション分割されていたときにも、"/dev/backup1", "/dev/backup2" というように、カーネル番号を付けたリンクも正しく作成されます。

 

ここで注意点があるとすれば、Hyper-V のように仮想ハードディスクを使う場合、仮想ハードディスクのファイルをコピーして作成した別のハードディスクの場合、ID がまったく同じになってしまうところでしょうか。

同じ ID のデバイスが存在していた場合、そのなかの 1 つだけにしかリンクが作成されないので注意が必要です。