恐怖のphantom Atari partition
はじめに
本記事は最近Rookで顕在化したデータ破壊問題について解説します。根本原因は思わぬところにあってRook以外でも起こりうること、および、かなり昔から根本原因は存在していたものの偶然に偶然が重なって最近Rookがとばっちりを受けたという事実が興味深かったので、世の中こういうこともあるというのを共有するために書きました。あとAtariという言葉を2021年にもなって歴史的文脈以外で見たのが面白かったというのも理由です。
本記事はRookの公式情報に書かれていることに加えて、Rookについて知識が無い人に向けた情報を付け加えて再構成しました。
用語集
- Ceph: OSSの分散ストレージ
- Rook: Kubernetes上で動作するCephのオーケストレーション。これもOSS
- OSD: Cephを構成するディスク上に存在するデータ構造
- OSD on disk: Rook上にOSDを作る方式の1つ。Rookの設定に直接デバイスのパスなどを書く。詳細は後述
- Atari partition: かつて存在したAtari STというコンピュータで使われていたパーティションの形式
問題要旨
- 事象
- OSDのデータが壊れる
- 原因要旨
- OSDが存在するディスクにAtariパーティションがあると誤認され、その(存在しない)パーティション上にRookがOSDを作ってしまう
- 発生条件
- Rook v1.6.0からv1.6.7を使っている
- パーティションを切っていないディスク上にOSD on diskを作っている
- 回避方法
- Rook v1.6.8以上、あるいはRook v1.7にアップデートする
- データ破壊からの復旧方法
- 無い。壊れたOSDを保持するディスク上にこの手順でOSDを作り直すしかない
機序
ここではRookが/dev/sdbというディスク上にOSDを作ろうとしたとします
- Rookが動作して/dev/sdb上にOSDを作る
- Rookが次回動作した際に/dev/sdb上にOSDではなくAtari Partition Tableがあり、かつ、いくつかOSD作成に使える空のAtari Partitionがあると誤認する
- step2で書いた偽の空パーティション上にOSDを作ってしまい、データ破壊発生
問題詳細
問題を詳細に理解するためにはRookやCeph, Atari Partitionについての知識がいくつか必要になってきます。このため、まずは前提知識についていくつか説明した上で、実際の問題発生までの流れを説明します。
RookにおけるOSD on deviceの設定方法
RookでOSDを作る際にはCephCluster Custom Resource(CR)というものに「こういう条件でOSDを作ってほしい」という設定を書きます。OSD on deviceを作るためには次のような指定をします。
- OSDを作りたいデバイスのパス("/dev/sdb"など)
- デバイスにマッチさせる正規表現("/dev/sd.*"など)
- 「使われていない全デバイス上に作る、という指定"useAllDevice: true"」
詳細については公式ドキュメントをご覧ください。
Rookが動作するときには以下のようにCeph Cluster CRを参考にしながら各デバイス上にOSDを作ります。
- Rookクラスタを構成するnode上Rookが、Cephが提供するceph-volumeというコマンドを実行する
- ceph-volumeコマンドはシステムに存在するデバイスをリストし、かつ、OSDを作れる状態、つまり空であるかどうかを表示する
- Rookは空であり、かつ、Ceph Clsuter CRの設定に合致するデバイス上にOSDを作る
CephにおけるOSDの形式
CephはデバイスにOSDを作ると、デバイス上にOSDのメタデータを書き込みます。CephにはOSDの形式が2つあり、それぞれメタデータを書き込む場所が異なります。
- lvm mode OSD: デバイス上にLVMのVolume Group(VG)を作り、かつ、その中にLogical Volume(LV)を作り、当該LV上の先頭領域にOSDのメタデータを書く
- raw mode OSD: デバイスの先頭領域にOSDのメタデータを書く
もとからあったのがlvm modeなのですが、最近になってシンプルで管理がしやすいraw mode OSDが導入されました。Rookにおいてもv1.6.0からはOSD on deviceにおいてraw mode OSDを作るようになりました。
OSDのモードについてはこの記事も参照してください。
LinuxカーネルにおけるAtari Partitionの認識方法
LinuxカーネルにおけるAtari Partitionの認識方法は他のパーティションに比べるとかなりいい加減です。根本的な問題がLinuxカーネルにあるのかAtari partitionの仕様がそもそもおかしいのを確認するにはAtari Partitionの仕様を見る必要があるのですが、見つからなかったのでソース調査にとどめています。
ディスクがAtari Partitionかどうかの判定には、ディスクの先頭領域に1つ以上パーティション情報が存在しているかどうかによって判定しています。パーティションは最大4個[1])あり、その確認方法はVALID_PARTITION()
というマクロです。
rs = read_part_sector(state, 0, §);
if (!rs)
return -1;
/* Verify this is an Atari rootsector: */
hd_size = get_capacity(state->disk);
if (!VALID_PARTITION(&rs->part[0], hd_size) &&
!VALID_PARTITION(&rs->part[1], hd_size) &&
!VALID_PARTITION(&rs->part[2], hd_size) &&
!VALID_PARTITION(&rs->part[3], hd_size)) {
/*
* if there's no valid primary partition, assume that no Atari
* format partition table (there's no reliable magic or the like
* :-()
*/
put_dev_sector(sect);
return 0;
}
コメントを読む限りは他のパーティションテーブルには当然のように存在するマジックナンバーのようなものは存在しないようです。
VALID_PARTITION()
の定義は次の通りです。
/* check if a partition entry looks valid -- Atari format is assumed if at
least one of the primary entries is ok this way */
#define VALID_PARTITION(pi,hdsiz) \
(((pi)->flg & 1) && \
isalnum((pi)->id[0]) && isalnum((pi)->id[1]) && isalnum((pi)->id[2]) && \
be32_to_cpu((pi)->st) <= (hdsiz) && \
be32_to_cpu((pi)->st) + be32_to_cpu((pi)->siz) <= (hdsiz))
たったこれだけの緩い条件を満たせばディスクがパーティションと誤認されてしまうというのはなかなか恐ろしいところです。
問題発生までの流れ
ふたたび/dev/sdb上にOSDを作ろうとした場合を考えます。簡単のためRookの設定においてすべての空きデバイス上にOSDを作る設定だったと仮定します。
まずはRookが動作して/dev/sdb上にOSDを作ります。ここまではOKです。v1.6.0~v1.6.7まではここでraw mode OSDを作るためにディスク先頭にOSDのメタデータが書き込まれます。このOSDのメタデータが不幸にもAtari Partition Tableと誤認されやすいビットパターンなのです[2]。
誤認が発生したデバイスはlsblkコマンドの結果から以下のように確認できます。
vdb 252:16 0 3T 0 disk
├─vdb2 252:18 0 48G 0 part ★ phantom Atari Partition
└─vdb3 252:19 0 6.1M 0 part ★ 同上
面白いことにlsblk, blkid, udevadm, and partedなどのツールはAtari Partitionを認識できないために、vdb2やvdb3が存在しないように見えたりpartition tableのtypeがunknownになったりとユーザや開発者を困惑させてきました。誤認されたパーティションが"phantom"と呼ばれるゆえんです。
この後、何かの理由によってRookが次回動作するとceph-volumeコマンドが動作し、このコマンドは/dev/sdb上にOSDではなくAtari Partition Tableがあり、かつ、いくつかのphantom Partitionがあると誤認します。ここでは仮に/dev/sdb2がphantom partitionだとします。この誤認によって、/dev/sdbは使用中だが/dev/sdb2上は空でありOSDが作れると報告します。
最後にRookは「空きデバイス上にはOSDを作ってよい」という指示をユーザから受けているので/dev/sdb2上にOSDを新規作成します。これによって、もともと/dev/sdb上にあったOSDのデータの一部が破壊されます。
問題への対処の履歴
この問題には関係者がRook, Ceph, そしてLinux Kernelという3人がいます。このような問題の修正においては「どのレイヤで修正しうるか」「どのレイヤで修正すべきか」などが重要になっています。
紆余曲折あって様々なworkaroundが提案されましたが、小手先ではどうしようもないと判断されて、現在はRookにおいてOSD on diskの作成においてはlvm mode OSDを作るという修正が取り込まれています。
それとは別にCephに対してもphantom Atari partitionを無視するためのceph-volumeの修正がすでに入っており、近日リリースされるv16.2.6において問題が修正される見込みです。v16.2.6がリリースされたら再びRookで当該バージョンを使っている場合はOSD on diskにおいてraw modeを使うようになるのではないかと予測しています。
また、LinuxカーネルについてもUbuntuにおいて大手クラウドベンダ環境用カーネルにおいてAtari Partitionサポートを切るという修正が最近されています。どのような問題を契機として修正したかは書かれていないのでRookやCephとは関係ない文脈で出てきたものかもしれません。
おわりに
なかなか壮絶な問題でしたが、こういうときにどういうソフトウェアを修正するか、どのような告知をするか、などを学ぶにはいい題材だと思います。さらに知りたいかたは関連issueなどを追いかけて深掘りしていただくのがよいかと思います。
Discussion
空きデバイスと報告されるのは /dev/sdb2 という話でしたが、/dev/sdb1 が使用されるのはなぜですか?
書き間違えでした。修正しました。