概要
EBSを複数アタッチしたEC2で、/etc/fstab を /dev/nvme1n1 のようなデバイス名で書いていると、再起動でマウント先が入れ替わることがあります。対策はタイトルどおり、fstabをデバイス名ではなくUUID(かラベル)で書くこと。それだけの話ですが、実際に踏んだときの調査の流れも含めて残しておきます。DBはIBMのDb2を使っています。
何が起きたか
カーネルパッチを当てて再起動した直後、データベースが起動しなくなりました。db2start は通るのに、activate でエラーになります。
SQL0293N Error accessing a table space container.
SQL1031N The database directory cannot be found on the indicated file system.
どちらもストレージが見えないときのエラーです。df を見るとマウント自体はされている。
ただ、データ用EBS(100GB, /data)とログ用EBS(20GB, /log)のマウントが入れ替わっていました。/data が20GB、/log が100GBになっています。
データベースは /data の下にコンテナを探しに行くので、別のボリュームが刺さっていれば当然見つかりません。アプリのエラーをたどっていった先が、ストレージのマウント逆転でした。
原因: NVMeのデバイス名は起動ごとに変わりうる
fstabがこう書かれていました。
# デバイス名で指定 — これが罠
/dev/nvme1n1 /data xfs defaults,nofail 0 0
/dev/nvme2n1 /log xfs defaults,nofail 0 0
ちなみに nofail はデバイスが見つからないときにブートを止めないためのオプションで、今回の入れ替わりは防げません。どちらのボリュームも存在していて、マウント自体は成功してしまうからです。
Nitro世代のインスタンスでは、EBSボリュームはNVMeデバイス(/dev/nvme0n1, /dev/nvme1n1, ...)として見えます。この番号の割り当てには、アタッチした順序と一致する保証がありません。起動時のデバイス検出で応答した順にデバイスノードが作られ、その応答順は起動のたびに変わりうるためです。つまり再起動を挟むと nvme1 と nvme2 の対応が入れ替わることがあります。
fstabをデバイス名で固定していると、同じ物理ボリュームが別のマウントポイントに付きます。今回は構築後3か月ほど再起動していなかったので、その間はずっと初期の割り当てのまま動いていました。パッチ適用の再起動で初めて番号が入れ替わり、表面化した、という経緯です。
確認と復旧: UUID指定への書き換え
まず、どのボリュームがどこに付いているかを実データで確かめます。UUIDは blkid で取れます。
# サイズと中身から、本来の対応を突き合わせる
lsblk -o NAME,SIZE,MOUNTPOINT
df -h /data /log
# 各デバイスのUUIDを確認
blkid /dev/nvme1n1 /dev/nvme2n1
# /dev/nvme1n1: UUID="11111111-1111-1111-1111-111111111111" TYPE="xfs"
# /dev/nvme2n1: UUID="22222222-2222-2222-2222-222222222222" TYPE="xfs"
今回はデータ用(100GB)とログ用(20GB)で容量が違ったので、サイズと中身を見れば対応が分かります。同じ容量のEBSを複数付けている場合はサイズでは区別できないので、EBSボリュームID(vol-...)で突き合わせます。ボリュームIDはNVMeデバイスのシリアルに載っていて、lsblk -o NAME,SERIAL か ls -l /dev/disk/by-id/(nvme-Amazon_Elastic_Block_Store_vol0abcd... の形で出ます)で確認できます。
UUIDはボリューム(ファイルシステム)に紐づく値なので、再起動やアタッチ順では変わりません。fstabが何を頼りにボリュームを特定するかを、変わりうる層から変わらない層に付け替える、というのが対策の中身です。
fstabを書き換えます。
# 1. DBを止めてからアンマウント
db2stop
cp /etc/fstab /etc/fstab.bak
umount /data /log
# UUIDで指定 — アタッチ順に依存しない
UUID=11111111-1111-1111-1111-111111111111 /data xfs defaults,nofail 0 0
UUID=22222222-2222-2222-2222-222222222222 /log xfs defaults,nofail 0 0
# 2. systemd環境ではfstab変更後にリロード
# (systemdはfstabからmountユニットを生成するので、古い定義が残ることがある)
systemctl daemon-reload
# 3. 書き換えた定義でマウントし直す
mount -a
df -h /data /log # 100GBが/data、20GBが/logに戻っていることを確認
/data に本来のディレクトリが戻ったことを確認して、db2start からデータベースの activate まで通れば復旧です。UUIDの代わりにファイルシステムラベル(LABEL=)でも同じことができます。
ひとつだけUUIDにも落とし穴があって、スナップショットから復元したボリュームは元のボリュームと同じUUIDを持ちます。復元元と復元先を同じインスタンスにアタッチするとUUIDが重複して、UUID指定では区別がつきません。この場合は /dev/disk/by-id/ のボリュームID指定を使います。普段の運用でこの構成になることはあまりないものの、リストア検証のときに踏みやすいポイントです。
まとめ
- Nitro(NVMe)世代のEC2では、
/dev/nvme*の番号はアタッチ順と一致せず、再起動で入れ替わりうる - fstabはデバイス名ではなく
UUID=(かラベル)で書く。UUIDはblkidで取る - fstab書き換え後は
systemctl daemon-reload→mount -aの順で反映する - スナップショット復元でUUIDが重複するケースだけは
/dev/disk/by-id/のボリュームID指定で対応する - デバイス名指定は初回再起動まで潜伏する。構成管理ツールで構築するなら、テンプレート側をUUID指定にしておく