現在、マルウェア検知の研究のために、CAPE Sandboxを用いて大量の検体を動的解析する基盤を構築しています。
物理メモリ32GBのマシンにKVMでWindows 10のVMをいくつか立てて並列解析しようとしたところ、「VMが勝手にPausedになる」「ホストOSごとフリーズする」「設定を変えても直らない」 という泥沼にハマりました。
その理由は気が付けば大したことではなかったのですが、システムを安定稼働させるための知見が得られたので、備忘録としてまとめます。
環境
- Host OS: Ubuntu 22.04 LTS
- Memory: 32GB
- Storage: NVMe SSD (System/VM images), HDD 18TB (Data)
- Hypervisor: KVM (QEMU/Libvirt)
- Sandbox: CAPEv2
- Guest: Windows 10
発生した問題
解析を開始すると、最初は順調に動くものの、数分経過すると以下の現象が発生し、システムが停止しました。
- VMが次々と
paused状態になるvirsh listで確認すると、稼働中のVMが勝手に一時停止してしまう。 - SSHが応答しなくなる ターミナル操作すら受け付けなくなる。
- 設定変更が効かない メモリを減らしたり設定を変えても、解析を再開すると元の重い設定に戻っている。
原因調査:何が起きていたのか
調査の結果、原因は複合的でしたが、最大のボトルネックは 「ディスクI/Oのパンク」 と 「KVMスナップショットの仕様」 だったと思います。
1. paused (migrating) の正体
停止したVMの状態を詳しく調べるため、以下のコマンドを実行しました。
virsh -c qemu:///system domstate win10_2 --reason
# 出力結果: paused (migrating)
これは「スナップショットからのメモリ復元(ロード)中」であることを示しています。 数台のVMが一斉にメモリ内容をディスクから読み込もうとした結果、NVMeの帯域が飽和。タイムアウト時間内に読み込みが終わらず、KVMが処理を中断(Pause)させていたことが主な原因だったと推測しています。
2. KVMスナップショットの「設定巻き戻し」
ドジすぎるのですが、これが一番のハマりポイントでした。
I/O負荷を下げるために virsh edit でメモリを4GB→2GBに減らしたのですが、解析が走るとなぜか4GBに戻って動作していました。
原因: KVMのスナップショットは、ディスクの状態だけでなく、「取得時点のXML設定(メモリ割当など)」も保存・復元します。 つまり、いくら設定ファイルを書き換えても、CAPEが解析開始時にスナップショットをロードした瞬間、設定もろとも「過去の状態」にタイムスリップしていたのです。(当たり前すぎる)
解決策:安定稼働のためのチューニング
以下の3つの対策を行うことで、現在は4並列で安定して解析を回せています。
対策1:VM設定の軽量化とI/O直結
ホストOSのメモリ不足(OOM)を防ぎ、I/Oを安定させるためにVMの設定をいくつか変更しました。
変更点:
- メモリ: 4GB → 2GB
- ディスクキャッシュ:
cache='none'を追加
<driver name='qemu' type='qcow2' discard='unmap' cache='none'/>
対策2:スナップショットの「正しい」更新手順
設定変更を永続化させるためには、スナップショット自体を撮り直す必要がありました。 全VMに対して以下の手順を実施しました。
- VM停止:
virsh destroy win10_x - 設定変更:
virsh edit win10_x(メモリ2GB化 & cache=‘none’) - VM起動:
virsh start win10_x - 待機: デスクトップが表示され、Agent(Python)が起動するまで待つ。
- 新スナップショット作成: GUIでスナップショット作成
この「撮り直し」をして初めて、軽量化設定が解析時に反映されるようになりました。
対策3:CAPE側の「設定変更」 (最重要)
恐らくこの対策が最も重要だったと思います。
ハードウェアリソース(特にディスクI/O)の限界を超えないよう、cuckoo.conf で制御をかけました。
# /opt/CAPEv2/conf/cuckoo.conf
[cuckoo]
# 解析の並列数(メモリ容量に合わせて調整)
# 32GBメモリならOS分を残して5〜6台が安全圏
max_machines_count = 5
# 【最重要】VMの起動(復元)を同時に何台行うか
# デフォルトのままだと一斉に復元が始まりディスクが死ぬので「1」にする
max_vmstartup_count = 1
[timeouts]
# I/O待ちでタイムアウトしないよう長めに確保
default = 300
vm_state = 300
特に max_vmstartup_count = 1 が効果絶大でした。
「5台同時に解析を実行する」としても、「起動(スナップショット復元)は1台ずつ順番に行う」 ことで、ディスクへのアクセス集中を分散させ、paused (migrating) エラーを完全に根絶できました。
まとめ
大規模な動的解析基盤を構築する際の教訓は以下の通りです。
- 設定変更時はスナップショットも撮り直す KVMにおいて「設定変更」と「スナップショット更新」はセットで行うこと。
- 「同時実行数」と「同時起動数」は別物
ストレージがボトルネックになる場合、
max_vmstartup_countを絞ることで、全体の処理速度を落とさずに安定性を劇的に向上できる。 - ホストメモリのキャッシュを過信しない
VMが大量に書き込みを行う場合、
cache='none'でホストOSを守る挙動の方が安定する可能性がある。
同じようなエラー(paused やフリーズ)に苦しんでいる方の参考になれば幸いです。