Upgrade to Pro — share decks privately, control downloads, hide ads and more …

device mapperによるディスクI/O障害のエミュレーション

device mapperによるディスクI/O障害のエミュレーション

Linuxカーネルのdevice mapper機能を使ってブロックデバイスレベルでディスクI/O障害をエミュレーションをする方法について紹介しました。kernel/vm 北陸 part6で発表しました。
https://kernelvm.connpass.com/event/297033/?utm_campaign=event_participate_to_follower&utm_source=notifications&utm_medium=twitter

Satoru Takeuchi

December 02, 2023
Tweet

More Decks by Satoru Takeuchi

Other Decks in Technology

Transcript

  1. はじめに • はなすこと ◦ LinuxでディスクI/O障害をエミュレーションする方法を紹介 ◦ 自分で機能追加する方法も紹介 • 補足 ◦

    ブロックデバイスレベルの障害にフォーカス ◦ Ubuntu 22.04.3のカーネル(kernel 5.15.0-89-generic)を使って動作確認 2
  2. 壊れたときのためにエラー処理がある • I/Oのリトライ ◦ 一時的に起きてすぐ解消することもある • データの冗長化 ◦ RAID、レプリケーション、erasure coding

    • データの破壊検知、修復 • ユーザに対処を任せる 6 disk ソフトウェア 何らかの異常 対処 理想 • I/Oが最終的に成功する • I/Oが失敗を返して対処がゆだねられる
  3. device mapper • ブロックデバイスに機能追加するためのLinuxカーネルの仕組み ◦ ターゲット: device mapperの個々の機能 ▪ 暗号化、マルチパス化、ディスク障害エミュレーションなど

    ◦ dmデバイス: device mapperによって作るブロックデバイス • 基本はdmsetupコマンドで制御 10 /dev/sda /dev/mapper/<dm device name> 書き込み 書き込み+α 読み出し 読み出し+α
  4. dmsetupのつかいかた(1/2) • dmデバイスとマップ先デバイスを対応付けるテーブルを作る • dmデバイスを作る 11 $ cat table.txt <開始セクタ>

    <終了セクタ> <ターゲット名> <マップ先デバイス> [<パラメタ >...] ... $ sudo dmsetup create <dmデバイス名> <テーブルファイル> $ ls /dev/mapper/ … <dmデバイス名> … 1セクタ=512バイト
  5. dmsetupのつかいかた(2/2) • dmデバイスの挙動を動的に変更する • dmデバイスを削除する 12 $ sudo dmsetup message

    <dmデバイス名> 0 <コマンド> [<パラメタ>...] $ sudo dmsetup remove <dmデバイス名>
  6. dm-delay • I/Oを遅延させる • テーブルの形式 16 <開始セクタ> <終了セクタ> delay <マップ先デバイス>

    <マップ先開始セクタ > <遅延[ms]> dm-delay disk write 遅延を入れてからwrite read 遅延を入れてからread
  7. 使用例 17 $ sudo dd if=/dev/zero of=/dev/loop0 bs=1M count=1024 oflag=direct

    … 1073741824 bytes (1.1 GB, 1.0 GiB) copied, 0.615917 s, 1.7 GB/s 通常時のスループットを確認
  8. 使用例 18 $ sudo dd if=/dev/zero of=/dev/loop0 bs=1M count=1024 oflag=direct

    … 1073741824 bytes (1.1 GB, 1.0 GiB) copied, 0.615917 s, 1.7 GB/s 通常時のスループットを確認 $ cat test-delay.txt 0 2097152 delay /dev/loop0 0 10 $ sudo dmsetup create test-delay test-delay.txt dmデバイスの作成
  9. 使用例 19 $ sudo dd if=/dev/zero of=/dev/mapper/test-delay bs=1M count=1024 oflag=direct

    $ sudo dd if=/dev/zero of=/dev/loop0 bs=1M count=1024 oflag=direct … 1073741824 bytes (1.1 GB, 1.0 GiB) copied, 0.615917 s, 1.7 GB/s 通常時のスループットを確認 $ cat test-delay.txt 0 2097152 delay /dev/loop0 0 10 $ sudo dmsetup create test-delay test-delay.txt dmデバイスの作成 dmデバイスにwrite
  10. 使用例 20 $ sudo dd if=/dev/zero of=/dev/mapper/test-delay bs=1M count=1024 oflag=direct

    … 1073741824 bytes (1.1 GB, 1.0 GiB) copied, 16.4044 s, 65.5 MB/s $ sudo dd if=/dev/zero of=/dev/loop0 bs=1M count=1024 oflag=direct … 1073741824 bytes (1.1 GB, 1.0 GiB) copied, 0.615917 s, 1.7 GB/s 通常時のスループットを確認 $ cat test-delay.txt 0 2097152 delay /dev/loop0 0 10 $ sudo dmsetup create test-delay test-delay.txt dmデバイスの作成 dmデバイスにwrite すごく遅くなった
  11. dm-flakey • I/Oが成功する期間と失敗する期間を設定 • テーブルの形式 22 <開始セクタ> <終了セクタ> flakey <マップ対象デバイス>

    <マップ先開始セクタ> <成功期間> <失敗期間> dm-delay disk write write(たまに失敗) read read(たまに失敗)
  12. 使用例 23 $ cat test-flakey.txt 0 2097152 flakey /dev/loop0 0

    2 2 $ sudo dmsetup create test-flakey test-flakey.txt dmデバイスの作成 2秒間の正常動作後 2秒間I/Oエラーを出す
  13. 使用例 24 $ cat test-flakey.txt 0 2097152 flakey /dev/loop0 0

    2 2 $ sudo dmsetup create test-flakey test-flakey.txt $ sudo bash -c 'for ((i=0;i<10;i++)) ; do dd if=/dev/mapper/test-flakey of=/dev/null bs=128 count=1 sleep 1 done' dmデバイスの作成 dmデバイスから1秒に1回read
  14. 使用例 25 $ cat test-flakey.txt 0 2097152 flakey /dev/loop0 0

    2 2 $ sudo dmsetup create test-flakey test-flakey.txt $ sudo bash -c 'for ((i=0;i<10;i++)) ; do dd if=/dev/mapper/test-flakey of=/dev/null bs=128 count=1 sleep 1 done' ... 128 bytes copied, 0.000198435 s, 645 kB/s dd: error reading '/dev/mapper/test-flakey': Input/output error ... dd: error reading '/dev/mapper/test-flakey': Input/output error ... 128 bytes copied, 0.000169802 s, 754 kB/s ... 128 bytes copied, 0.000224296 s, 571 kB/s dd: error reading '/dev/mapper/test-flakey': Input/output error ... dmデバイスの作成 dmデバイスから1秒に1回read 仕様通りに動作
  15. dm-flakey上でファイルシステムを作ると… 26 $ sudo mkfs.ext4 -F /dev/loop0 $ sudo dmsetup

    create test-flakey test-flakey.txt $ mount /dev/mapper/test-flakey mnt ファイルシステムが存在するデバイス上にdmデバイスを作成
  16. dm-flakey上でファイルシステムを作ると… 27 $ for ((i=0;i<10;i++)) ; do sudo dd if=/dev/zero

    of=mnt/test.txt bs=1 count=1 oflag=direct sleep 1 done $ sudo mkfs.ext4 -F /dev/loop0 $ sudo dmsetup create test-flakey test-flakey.txt $ mount /dev/mapper/test-flakey mnt ファイルシステムが存在するデバイス上にdmデバイスを作成 ファイルシステム上のファイルに1秒に1回write
  17. dm-flakey上でファイルシステムを作ると… 28 $ for ((i=0;i<10;i++)) ; do sudo dd if=/dev/zero

    of=mnt/test.txt bs=1 count=1 oflag=direct sleep 1 done dd: failed to open 'mnt/test.txt': Input/output error dd: failed to open 'mnt/test.txt': Input/output error dd: failed to open 'mnt/test.txt': Read-only file system … ファイルシステムが存在するデバイス上にdmデバイスを作成 ファイルシステム上のファイルに1秒に1回write すぐにread onlyに $ sudo mkfs.ext4 -F /dev/loop0 $ sudo dmsetup create test-flakey test-flakey.txt $ mount /dev/mapper/test-flakey mnt
  18. カーネルログを見ると… 29 $ sudo dmesg … [10912.976026] EXT4-fs error (device

    dm-0): ext4_journal_check_start:83: comm dd: Detected aborted journal [10912.991408] EXT4-fs (dm-0): Remounting filesystem read-only • ext4はデータが存在するデバイスの挙動が怪しいとreadonlyでremountする ◦ 他のファイルシステムもこのようなフェイルセーフのしくみがある • 📝 dm-flakeyをファイルシステム上のI/Oエラーのエミュレーションに使うのは面倒 ジャーナル処理も失敗
  19. dm-dustの主なメッセージ • 機能の有効化/無効化 • 指定したセクタを疑似不良ブロックにする • 指定したブロックを疑似不良ブロックではなくする • 疑似不良ブロックのリスト ◦

    32 $ sudo dmsetup message <dmデバイス名> 0 addbadblock <ブロック番号> [修復前にI/Oが失敗する回数] $ sudo dmsetup message <dmデバイス名> 0 removebadblock <ブロック番号> $ sudo dmsetup message <dmデバイス名> 0 {enable,disable} $ sudo dmsetup message <dmデバイス名> 0 listbadblocks <ブロック番号>
  20. dm-dustを使う前の準備 • kernel 5.15.0-89-genericではdm-dustは無効化されている • 自前ビルドすれば使える 33 $ git clone

    https://github.com/satoru-takeuchi/kernelvm-hokuriku-part6 $ cd kernelvm-hokuriku-part6 $ make -C kernel-modules $ sudo insmod kernel-modules/dm-dust.ko
  21. 使用例 34 $ cat test-dust.txt 0 2097152 dust /dev/loop0 0

    512 $ sudo dmsetup create test-dust test-dust.txt $ sudo dmsetup message test-dust 0 enable $ sudo dmsetup message test-dust 0 addbadblock 0 1 dmデバイスの作成&設定 セクタ0にアクセスすると • readは失敗する • 1回目のwriteは失敗する • 2回目のwriteで不良セクタが消える
  22. 使用例 35 $ cat test-dust.txt 0 2097152 dust /dev/loop0 0

    512 $ sudo dmsetup create test-dust test-dust.txt $ sudo dmsetup message test-dust 0 enable $ sudo dmsetup message test-dust 0 addbadblock 0 1 dmデバイスの作成&設定 セクタ0にアクセスすると • readは失敗する • 1回目のwriteは失敗する • 2回目のwriteで不良セクタが消える $ sudo dd if=/dev/mapper/test-dust of=/dev/null bs=512 count=1 不良ブロックからread
  23. 使用例 36 $ cat test-dust.txt 0 2097152 dust /dev/loop0 0

    512 $ sudo dmsetup create test-dust test-dust.txt $ sudo dmsetup message test-dust 0 enable $ sudo dmsetup message test-dust 0 addbadblock 0 1 $ sudo dd if=/dev/mapper/test-dust of=/dev/null bs=512 count=1 dd: error reading '/dev/mapper/test-dust': Input/output error … dmデバイスの作成&設定 不良blockからのread セクタ0にアクセスすると • readは失敗する • 1回目のwriteは失敗する • 2回目のwriteで不良セクタが消える
  24. 使用例 37 $ cat test-dust.txt 0 2097152 dust /dev/loop0 0

    512 $ sudo dmsetup create test-dust test-dust.txt $ sudo dmsetup message test-dust 0 enable $ sudo dmsetup message test-dust 0 addbadblock 0 1 $ sudo dd if=/dev/mapper/test-dust of=/dev/null bs=512 count=1 dd: error reading '/dev/mapper/test-dust': Input/output error … $ sudo dd if=/dev/zero of=/dev/mapper/test-dust bs=512 count=1 oflag=direct,dsync dd: error writing '/dev/mapper/test-dust': Input/output error … dmデバイスの作成&設定 不良blockへのwrite セクタ0にアクセスすると • readは失敗する • 1回目のwriteは失敗する • 2回目のwriteで不良セクタが消える
  25. 使用例 38 $ cat test-dust.txt 0 2097152 dust /dev/loop0 0

    512 $ sudo dmsetup create test-dust test-dust.txt $ sudo dmsetup message test-dust 0 enable $ sudo dmsetup message test-dust 0 addbadblock 0 1 $ sudo dd if=/dev/mapper/test-dust of=/dev/null bs=512 count=1 dd: error reading '/dev/mapper/test-dust': Input/output error … $ sudo dd if=/dev/zero of=/dev/mapper/test-dust bs=512 count=1 oflag=direct,dsync dd: error writing '/dev/mapper/test-dust': Input/output error … $ sudo dd if=/dev/zero of=/dev/mapper/test-dust bs=512 count=1 oflag=direct,dsync … 512 bytes copied, 0.0134639 s, 38.0 kB/s dmデバイスの作成&設定 不良ブロックへの2度目のwrite セクタ0にアクセスすると • readは失敗する • 1回目のwriteは失敗する • 2回目のwriteで不良セクタが消える
  26. ioctl(FS_IOC_FIEMAP)を使えばわかる • ファイルとextent、extentとブロックの対応が得るためのioctl() • xfs_io -c fiemapコマンドを介して手軽に使える 40 $ xfs_io

    -c fiemap test.txt test.txt: 0: [0..7]: 266240..266247 test.txtの先頭領域を含むextentは ファイルシステムの下にあるブロックデバイスの セクタ266240~266247に存在する
  27. 使用例 41 $ sudo dmsetup message test-dust 0 enable $

    sudo xfs_io -c fiemap mnt/test.txt mnt/test.txt: 0: [0..7]: 266240..266247 $ sudo dmsetup message test-dust 0 addbadblock 266240 dmデバイス上の作成&設定 test.txtファイル先頭のデータに対応 するセクタを不良セクタにする /dev/loop0 /dev/mapper/test-dust filesystem test.txt
  28. 使用例 42 $ sudo dmsetup message test-dust 0 enable $

    sudo xfs_io -c fiemap mnt/test.txt mnt/test.txt: 0: [0..7]: 266240..266247 $ sudo dmsetup message test-dust 0 addbadblock 266240 $ sudo dd if=mnt/test.txt of=/dev/null bs=512 count=1 iflag=direct dmデバイス上の作成&設定 dmデバイス上のファイルからread /dev/loop0 /dev/mapper/test-dust filesystem test.txt
  29. 使用例 43 $ sudo dmsetup message test-dust 0 enable $

    sudo xfs_io -c fiemap mnt/test.txt mnt/test.txt: 0: [0..7]: 266240..266247 $ sudo dmsetup message test-dust 0 addbadblock 266240 $ sudo dd if=mnt/test.txt of=/dev/null bs=512 count=1 iflag=direct dd: error reading 'mnt/test-dust.txt': Input/output error … dmデバイス上の作成&設定 dmデバイス上のファイルからread /dev/loop0 /dev/mapper/test-dust filesystem test.txt
  30. • CoW型、ログ構造型のファイルシステムではうまく機能しない ◦ 例: Btrfs, NILFS ◦ writeするたびにextentとblockの対応関係が変化 dm-dustと相性の悪いファイルシステム 46

    Btrfs 旧データ 不良ブロック 新データ writeするとデータの場所が変わる dm-dust disk 📝 ファイルシステムはなんでもいいなら ext4などのin-placeな更新をするものを使う
  31. ものすごく簡単なdevice mapperの自作 • dm-simple ◦ dm-linearのコードから必要最小限の部分以外を削って 100行強にしたもの ◦ マップ先にあるデバイスに I/Oをそのまま受け渡すだけ

    • テーブルの形式 • ソース ◦ https://github.com/satoru-takeuchi/kernelvm-hokuriku-part6/blob/main/kernel-modules/dm-simple.c 52 <開始セクタ> <終了セクタ> simple <マップ先デバイス> write dm-simple disk write read read
  32. ターゲットをあらわす構造体を作る 53 static struct target_type simple_target = { .name =

    "simple", .version = {0, 0, 1}, .features = DM_TARGET_NOWAIT | DM_TARGET_PASSES_CRYPTO, .module = THIS_MODULE, .ctr = simple_ctr, .dtr = simple_dtr, .map = simple_map, .iterate_devices = simple_iterate_devices, }; dmデバイスのI/Oを下のデバイスに渡す
  33. マップ処理 54 static int simple_map(struct dm_target *ti, struct bio *bio)

    { struct simple_c *sc = ti->private; bio_set_dev(bio, sc->dev->bdev); return DM_MAPIO_REMAPPED; }
  34. マップ処理 55 static int simple_map(struct dm_target *ti, struct bio *bio)

    { struct simple_c *sc = ti->private; bio_set_dev(bio, sc->dev->bdev); return DM_MAPIO_REMAPPED; } 個々のI/Oを管理するデータ • I/Oに使うメモリ領域 • I/O対象デバイス
  35. マップ処理 56 static int simple_map(struct dm_target *ti, struct bio *bio)

    { struct simple_c *sc = ti->private; bio_set_dev(bio, sc->dev->bdev); return DM_MAPIO_REMAPPED; } I/O対象デバイスを マップ先デバイスに書き換え
  36. マップ処理 57 static int simple_map(struct dm_target *ti, struct bio *bio)

    { struct simple_c *sc = ti->private; bio_set_dev(bio, sc->dev->bdev); return DM_MAPIO_REMAPPED; } I/Oをマップ先デバイスに リマップしたことを呼び出し元に伝える
  37. 使用例 58 $ cat test-simple.txt 0 2097152 simple /dev/loop0 $

    sudo dmsetup create test-simple test-simple.txt dmデバイスの作成
  38. 使用例 59 $ cat test-simple.txt 0 2097152 simple /dev/loop0 $

    sudo dmsetup create test-simple test-simple.txt dmデバイスの作成 $ sudo dd if=test-simple.txt of=/dev/mapper/test-simple dmデバイスにwrite
  39. 使用例 60 $ cat test-simple.txt 0 2097152 simple /dev/loop0 $

    sudo dmsetup create test-simple test-simple.txt dmデバイスの作成 dmデバイスにwrite $ sudo dd if=test-simple.txt of=/dev/mapper/test-simple … $ sudo dmsetup remove test-simple $ sudo hexdump -c -n 128 /dev/loop0 0000000 0 2 0 9 7 1 5 2 s i m p l e 0000010 / d e v / l o o p 0 0 \n \0 \0 …
  40. dm-hello • writeしてくれた人にあいさつをする ◦ 📝 あいさつは社会人の基本 • テーブルの形式 • ソース

    ◦ https://github.com/satoru-takeuchi/kernelvm-hokuriku-part6/blob/main/kernel-modules/dm-shello.c 61 <開始セクタ> <終了セクタ> hello <マップ対象デバイス>
  41. マップ処理 62 static int hello_map(struct dm_target *ti, struct bio *bio)

    {   ... bio_for_each_segment(bvec, bio, iter) { char *segment; struct page *page = bio_iter_page(bio, iter); if (unlikely(page == ZERO_PAGE(0))) break; segment = bvec_kmap_local(&bvec); memcpy(segment, hello, sizeof(hello)); kunmap_local(segment); break; } bio_set_dev(bio, hc->dev->bdev); return DM_MAPIO_REMAPPED; }
  42. マップ処理 63 static int hello_map(struct dm_target *ti, struct bio *bio)

    {   ... bio_for_each_segment(bvec, bio, iter) { char *segment; struct page *page = bio_iter_page(bio, iter); if (unlikely(page == ZERO_PAGE(0))) break; segment = bvec_kmap_local(&bvec); memcpy(segment, hello, sizeof(hello)); kunmap_local(segment); break; } bio_set_dev(bio, hc->dev->bdev); return DM_MAPIO_REMAPPED; } bio内の全領域(データの断片)を走査
  43. マップ処理 64 static int hello_map(struct dm_target *ti, struct bio *bio)

    {   ... bio_for_each_segment(bvec, bio, iter) { char *segment; struct page *page = bio_iter_page(bio, iter); if (unlikely(page == ZERO_PAGE(0))) break; segment = bvec_kmap_local(&bvec); memcpy(segment, hello, sizeof(hello)); kunmap_local(segment); break; } bio_set_dev(bio, hc->dev->bdev); return DM_MAPIO_REMAPPED; } 先頭領域に”Hello!”という文字列を書き込む
  44. マップ処理 65 static int hello_map(struct dm_target *ti, struct bio *bio)

    {   ... bio_for_each_segment(bvec, bio, iter) { char *segment; struct page *page = bio_iter_page(bio, iter); if (unlikely(page == ZERO_PAGE(0))) break; segment = bvec_kmap_local(&bvec); memcpy(segment, hello, sizeof(hello)); kunmap_local(segment); break; } bio_set_dev(bio, hc->dev->bdev); return DM_MAPIO_REMAPPED; } 書き終わったら走査終了 先頭領域以外は触らない
  45. 使用例 66 $ cat test-hello.txt 0 2097152 hello /dev/loop0 $

    sudo dmsetup create test-hello test-hello.txt dmデバイスの作成
  46. 使用例 67 $ cat test-hello.txt 0 2097152 hello /dev/loop0 $

    sudo dmsetup create test-hello test-hello.txt $ sudo dd if=test-hello.txt of=/dev/mapper/test-hello … $ sudo hexdump -c -n 128 /dev/mapper/test-hello dmデバイスの作成 dmデバイスにwrite
  47. 使用例 68 $ cat test-hello.txt 0 2097152 hello /dev/loop0 $

    sudo dmsetup create test-hello test-hello.txt 元気にあいさつができて えらいね! $ sudo dd if=test-hello.txt of=/dev/mapper/test-hello … $ sudo hexdump -c -n 128 /dev/mapper/test-hello 0000000 H e l l o ! \n \0 2 h e l l o 0000010 / d e v / l o o p 0 0 \n \0 \0 \0 … dmデバイスの作成 dmデバイスにwrite データが書き換わった
  48. その他カーネルのディスクI/O障害エミュレーション機能 • fault injection ◦ カーネルの様々なコンポーネントに対応する汎用的な障害エミュレーション機能 ◦ 任意のブロックデバイスへの fault injection、NVMe

    SSDへのfault injectionなどもある ◦ kernel 5.15.0-89-genericでは無効化されている ◦ https://docs.kernel.org/fault-injection/index.html • scsi_debug ◦ SCSIコマンドの失敗をエミュレーション ◦ kernel 5.15.0-89-genericではモジュールとして提供されている ◦ https://sg.danny.cz/sg/scsi_debug.html • multiple devices(device mapperと似たような機能) ◦ md-faultというI/O障害エミュレーション用のサブ機能がある ◦ 色々できそうだが使ったことがない ◦ man mdadmに使い方が書いている 74