この記事に出てくる HITSC 関連の用語、snapshot profile 名、設計上の呼び方は、執筆時点の暫定的なものです。実装や設計の進行に伴い、現在は変更されている場合があります。
Firecracker snapshot restore を使うと、起動済み VM から短時間で microVM を restore できる。
HITSC / hitto Troubleshooting Contest の検証用基盤でも、docker-iptables-001 の 3 ticket / 9 VM と 8 ticket / 24 VM で snapshot restore を試し、起動時間と PSS の両方で効果を確認できた。
snapshot restore の効果は実測済み
snapshot restore の効果自体は、すでに実測で確認できている。
3 ticket / 9 VM では、SSH ready までの平均時間が direct boot の約 15.7 秒から snapshot restore の約 2.5 秒まで短くなった。
after-settle の合計 PSS も、約 3725 MiB から約 1776 MiB まで下がった。
8 ticket / 24 VM でも同じ傾向だった。
SSH ready は約 43.7 秒から約 5.8 秒へ短縮し、after-settle の合計 PSS は約 7319 MiB から約 3299 MiB へ下がった。
つまり、snapshot restore によって「速く起動すること」と「同じ snapshot memory file 由来の page sharing が PSS 上で見えること」は確認できた。
ただし、ここで確認できたのは性能面の効果である。
Memory CoW の効果が見えたからといって、本番安全な clone が完成したわけではない。
次の大きな論点は、速く clone することではなく、安全に clone することである。
snapshot clone で何が複製されるのか
snapshot から復元した VM は、snapshot 時点の guest memory と device state を元に再開する。
そのため、起動時間は短くなるが、guest 内で既に作られていた一意値や userspace state も複製されうる。
複製されうるもの:
/etc/machine-id
SSH host key
/var/lib/systemd/random-seed
/proc/sys/kernel/random/boot_id
application token
userspace PRNG state
snapshot 前に生成済みの session/cache
たとえば machine-id や SSH host key が複製されると、複数の VM が同じ identity を持つことになる。
また、snapshot 前にアプリケーションが token や PRNG state を memory 内に持っていた場合、その状態も clone 先へ持ち越される可能性がある。
Firecracker 公式ドキュメントでも、snapshot clone 時の randomness や unique state の扱いは注意点として扱われている。
参考:
- Firecracker snapshot support: https://github.com/firecracker-microvm/firecracker/blob/main/docs/snapshotting/snapshot-support.md
- Firecracker random for clones: https://github.com/firecracker-microvm/firecracker/blob/main/docs/snapshotting/random-for-clones.md
- Firecracker entropy device: https://github.com/firecracker-microvm/firecracker/blob/main/docs/entropy.md
- Linux random(4): https://man7.org/linux/man-pages/man4/random.4.html
- Restoring Uniqueness in MicroVM Snapshots: https://arxiv.org/abs/2102.12892
一般解はない前提で考える
ここはかなり本質的に「一般解がない」寄りである。
Firecracker 公式ドキュメントでは、Linux kernel RNG については VMGenID や entropy device で改善できる一方で、unique identifiers、cached random numbers、cryptographic tokens などは同じ snapshot から復元すると複製されうる、と説明されている。
さらに random-for-clones.md では、userspace アプリケーションやライブラリが独自の PRNG state を持つ場合、現在のプログラミングモデルでは汎用的に解決する方法はない、とされている。
そのため HITSC での現実的な対処は、完全な一般解を目指すことではない。
snapshot 取得位置と reunique の責務を明確にし、どこまでを基盤で担い、どこからを問題側の hook に任せるかを決める必要がある。
snapshot profile を分ける
まず、direct boot は本番安全な標準経路として残す。
snapshot restore は高速化 backend として継続するが、safe-ish profile が整うまでは実験扱いにするのがよい。
整理するとこうなる。
direct boot:
本番安全な標準経路として維持する
snapshot restore:
高速化 backend
ただし safe-ish profile が整うまで実験扱い
safe-ish snapshot:
kernel/systemd/network までは起動済み
SSH host key / machine-id / app token 生成前で snapshot する
restore 後に guest 内 reunique を走らせる
一番避けたいのは、今の ssh_ready snapshot をそのまま本番安全扱いすることである。
ssh_ready まで進めると、sshd、systemd、Docker、アプリケーションがすでに一意値を作っている可能性がある。
これは速いが、危うい。
速度と安全性のトレードオフ
reunique_waiting 方式は、最速の snapshot restore を少し犠牲にして、安全側に寄せる設計である。
比較するとこうなる。
ssh_ready snapshot:
速い
sshd や systemd service が起動済み
restore 後すぐ SSH できる
ただし machine-id / SSH host key / random-seed / app token 複製リスクが大きい
reunique_waiting snapshot:
ssh_ready snapshot よりは少し遅い
restore 後に reunique 処理と sshd 起動または restart が必要
ただし一意性を回復しやすい
つまり、ssh_ready snapshot の「restore した瞬間ほぼ使える」という強さは少し失う。
失うのは主に以下である。
restore 後の SSH 即応性
sshd 起動済み状態
問題 service 起動済み状態
一方で、まだ短縮できる部分は多い。
kernel boot
initramfs なし boot
systemd 初期化の一部
udev 初期化
network device 認識
rootfs mount 後の基本初期化
package/service install 済み状態
位置づけとしては、次のようになる。
direct boot よりは速い
ssh_ready snapshot よりは遅い
HITSC はトラブルシューティング基盤なので、競技や練習の正しさを考えるなら、最終的には safe-ish profile を使うべきだと考えている。
ただし、ベンチマークや個人検証では unsafe-fast profile にも価値がある。
これは「速さの上限を見る」ための profile である。
当初案から変わった点
当初は、snapshot restore 後に単純な hitsc-reunique.service を一度動かす案を考えていた。
しかし、snapshot restore は再起動ではなく、途中状態からの再開である。
そのため、oneshot service が snapshot 前に完了していると、restore 後に再実行されない。
この点を踏まえると、restore 後に何かを一度実行するだけではなく、snapshot 取得点そのものを ssh_ready から reunique_waiting に寄せる方が安全だと考えるようになった。
reunique_waiting snapshot 案
良さそうなのは、guest 内に hitsc-reunique-wait.service を入れ、identity 注入待ちの状態で snapshot を取る方式である。
snapshot prepare 時:
guest boot
network 設定
hitsc-reunique-wait.service を起動
service は /var/lib/hitsc/reunique/identity.env を待って停止しない
sshd や問題 service はまだ起動させない、または reunique 後に起動する
この「待機中」の状態で snapshot を取る
ticket restore 時:
ticket rootfs へ identity.env を注入する
snapshot restore する
待機中 service が identity.env を見つける
machine-id / SSH host key / random-seed / hostname を処理する
sshd を起動または restart する
ready marker を書く
つまり、snapshot 取得点を ssh_ready ではなく、次に寄せる。
reunique_waiting
この流れにすると、worker 側は「restore した VM が reunique 済みか」を ready 判定に含められる。
各項目の扱い
machine-id や SSH host key のようにファイルとして存在するものは、比較的扱いやすい。
ただし、項目ごとに注意点は違う。
SSH host key:
restore 後に /etc/ssh/ssh_host_* を消して ssh-keygen -A
その後 sshd を起動または restart する
/var/lib/systemd/random-seed:
snapshot 前に削除する
restore 後にも必要なら新しい seed を作る
Firecracker entropy device / VMGenID も併用する
machine-id:
かなり厄介
systemd/dbus が起動後に使っているので、restore 後にファイルだけ変えても完全ではない可能性がある
安全寄りには、machine-id 依存 service を reunique 後に起動する
hostname:
ticket/node 別に設定してよい
/etc/hostname と runtime hostname を更新する
userspace PRNG/token:
一般解なし
snapshot 前に生成させない
生成済みなら該当 service を restore 後に restart/reinit する
特に machine-id は、ファイルを再生成すれば終わりとは言い切れない。
systemd や D-Bus が既にその値を前提に動いている場合、restore 後にファイルだけを差し替えても、実行中プロセスの内部状態までは戻せない可能性がある。
userspace PRNG や application token も同じで、基盤側で自動的に全てを直すことはできない。
HITSC 向けの方針
HITSC 向けには、次の方針にするのが現実的だと考えている。
1. direct boot は本番安全な標準経路として残す
2. snapshot restore は高速化 backend として継続する
3. snapshot profile を分ける
- unsafe-fast: ssh_ready snapshot。ベンチ/検証用
- safe-ish: reunique_waiting snapshot。本番候補
4. guest OS に hitsc-reunique-wait.service を入れる
5. worker は restore 前に ticket/node identity を rootfs へ注入する
6. reunique 完了後に SSH/採点を許可する
7. app 固有 token は問題作者 hook で扱う
次の実装単位としては、reunique 処理の詳細実装より先に、snapshot 取得点を reunique_waiting にできる設計を作るのが良さそうである。
ここを間違えると、unit を入れても「snapshot 時点で既に実行済み」になって効かない。
まだ決め切れていないこと
方針としては reunique_waiting に寄せるのがよさそうだが、実装の細部はまだ固定しきれていない。
特に、どこまでを HITSC の共通処理にして、どこからを問題作者 hook に任せるかは詰める必要がある。
identity.env の形式
ready marker の場所と検証方法
machine-id 依存 service の扱い
Docker や問題 service の起動順序
app 固有 token の再生成 hook
unsafe-fast / safe-ish / direct の使い分け
一般的な解決策や、実運用での知見があれば、hitsc-public リポジトリの Issue や Discussion で教えてほしい。
まとめ
Firecracker snapshot restore と Memory CoW により、VM の起動とメモリ使用量はかなり軽くできる。
ただし、snapshot clone は速さだけでなく、一意性と安全性をセットで考える必要がある。
HITSC では reunique_waiting snapshot と hitsc-reunique-wait.service を中心に、guest 内の一意性再生成、ready marker、snapshot 取得タイミングを整理していく。