TT Lab
ブログ

한국어 English 日本語 中文

はじめる
ブログ

ブログ

学生VMが起動するまで 5: 寿命と回収 — 何をどの順序で削除し、失敗しても停止させないか

ステータス: ドラフト (2026-10-16)、自社コード + 障害記録 + 一部実測値。 動作はTT Labバックエンドの reap_once·delete_vm_session と 設計・障害記録に従いました。クォータと上限は同日に読み取り専用で照会し、ストレージの値と一致していることを確認しました。 作成ルール: 実測データを基本とし、計測できなかったものは「未実測」、ドキュメントから見つからなかったものは「未確認」と表記します。

学生VMが起動するまで 5: 寿命と回収

目次

TT Labの1文の原則は「受講生に真のrootシェルを与えるが、外部への流出は防ぎ、 使用したリソースは必ず回収する」 です。VMにおいて最後のフレーズは特に重要です。実習用Podが1つ残れば数百MBが無駄になりますが、VMが1つ残ると4GiBが丸ごとロックされます。

세션의 수명과 지우는 순서 ① 시간표 (기본값) 시작 60분 만료 180분 연장 상한 연장(한 번에 60분까지) 회수기는 60초마다 돈다: 만료 · 고아(DB 에 없는 VM) · 유령(VM 없는 DB 행) 한 세션을 못 지우면 그 행만 건너뛰고 다음 바퀴에 다시 본다 ② 백엔드가 지우는 것은 둘뿐 VirtualMachine 삭제 cloud-init Secret 삭제 VMI 정지 KubeVirt virt-launcher 파드 종료 게스트 종료 신호 emptyDir 쓰기 층 삭제 쿠버네티스(파드와 함께) DataVolume 의 PVC VM 이 소유 → 가비지 컬렉션 (B 방식만) 골든 PVC 소유자 없음 · 세션 라벨 없음 → 지워지지 않는다 PVC 는 쓰는 파드가 있는 동안 지워지지 않는다(사용 중 보호). 그래서 VM → 파드 → 볼륨 순서가 자연스럽게 지켜진다.
図: セッションの寿命と削除の順序

1. 寿命: 60分、延長は最大3倍まで

2. 回収機能: 60秒ごとに3つの方向

バックエンドは60秒ごとに回収処理を1回実行します。管理者画面の「今すぐ回収」ボタンも同じ関数を使用しています。確認する対象は3つの方向です。

方向 対象 必要な理由
期限切れ 時間が経過したセッション → リソースを削除し、ステータスを expired に更新して終了 デフォルトのパス
孤立 クラスターには存在するが、DB上に有効なセッションが存在しないVM → 削除 バックエンドがVMを作成した直後にクラッシュするなど、記録とリソースの間に不整合が生じた場合
幽霊 DB上では実行中だが、実際にはVMが存在しないセッション → ステータスを failed に更新して終了 ノードの再起動やOOMによりVMが消失した場合。これがないと「1人1セッション」の制限により、学生が新しい実習を永遠に開始できなくなる

VMの孤立を個別にスキャンするのにも理由があります。回収機能は当初実習用Podのリストのみを確認していましたが、VMは 別のネームスペースに存在するため、そのリストには決して表示されませんでした。DBのレコードが消えたVMが4GiBのメモリを保持したまま誰からも見えない状態になる可能性がありました。

孤立したリソースを削除する直前に、DBをもう一度確認します。これは、回収機能がリストを読み込んだ後に作成されたばかりのセッションのVMを、孤立したリソースと誤認しないようにするためです。

障害1: 他環境の回収機能が学生VMを削除した

2026-09-12、本番環境において学生APIで開いたVMセッションが準備を完了できずに失敗しました。ログを突き合わせたところ、VMIが起動してから 26秒後 にVMが削除されていました。テストツールによって削除されたのではありませんでした。

原因はリソースの共有でした。開発環境と本番環境のバックエンドのDBは別々ですが、 VMのネームスペースは共通の1つを使用しています。 当時の回収機能はVMのネームスペース全体を読み取り、「自分のDBに存在しないものは孤立」と判断していました。そのため、開発環境の回収機能から見ると本番環境の学生のVMは孤立しているように見えました。同じ条件で行ったテストにおいて、本番環境のVMを削除する呼び出しが再現されました(Kubernetesの監査ログから実際の削除リクエストの送信者の身元まで特定することはしませんでした)。

修正方法はラベルの付与です。バックエンドはVMとSecretを作成する際に 自環境の名前をラベルとして付与し、孤立したリソースの回収ではそのラベルが自環境と一致するVMのみを対象とします。ラベルを持たない古いVMの所有者は推測しません。リスト自体は引き続き全体を参照するため、DBに登録されている古いVMを「存在しないリソース」と誤認することもありません。修正後に学生APIで再度開いたセッションは90.991秒で準備が完了し、終了後にVM・VMI・Secretがすべて消失していることを確認しました(1回)。

障害2: 1行の失敗が回収全体を停止させた

2026-10-14のサイトセキュリティ点検で発見された問題です。期限切れのセッションを削除するループ内で、1つの削除処理が例外を発生させると、 そのサイクル全体が停止していました。 同じ行が毎分同じ場所で失敗し続ける間、その背後にある他の期限切れセッションも、孤立したリソースも、幽霊セッションも、誰も回収されないまま蓄積されていきました。

現在は行ごとに失敗を分離して処理しています。

for r in rows:
    try:
        delete_session_resource(r)
    except Exception as exc:
        failed += 1            # 실패를 세고
        print(f"[reaper] 만료 세션 회수 실패 {str(r['id'])[:8]}: {type(exc).__name__}", flush=True)
        continue               # 기록은 그대로 두어 다음 바퀴에 다시 본다
    con.execute("update sessions set status='expired', finished_at=now() where id=%s", (r["id"],))

ステータスを expired に変更するのは、 削除に成功した後です。順序が逆になると、削除できなかったVMが「終了したセッション」として記録され、二度と回収対象にならなくなります。失敗の件数は管理者画面に表示される回収ステータスと一緒に記録されます。

同じ点検において、実習用 Podにも期限(activeDeadlineSeconds = 60分 × 3 + 15分)を追加しました。Web PodとDBが同時に停止し、回収機能がまったく動作しなくなった場合でも、期限切れのrootシェルが永遠に残り続けなようにするためです。正常なセッションは延長上限(180分)よりも早く終了するため、この期限に達することはありません。

3. 何を、どの順序で削除するか

バックエンドが直接削除するものは 2つのみです。

  1. VirtualMachine オブジェクト
  2. cloud-init Secret(採点トークンが含まれているもの)

残りはKubernetesとKubeVirtが順番に削除します。順序は以下の通りです。

順序 対象 実行者 根拠
1 VMの削除 バックエンド delete_vm_session
2 VMIの停止・削除 KubeVirt VMがVMIを所有している
3 virt-launcher Podの終了 KubeVirt・Kubernetes Podはゲストに終了シグナルを渡し、VMがシャットダウンするまで待機する(猶予期間30秒)
4 書き込みレイヤー(emptyDir)の削除 Kubernetes emptyDirのライフサイクルはPodと同じである
5 DataVolumeとそのPVCの削除 Kubernetesガベージコレクター VMが dataVolumeTemplates で作成したDataVolumeを所有している(永続ディスク実習のみ)
— ゴールデンPVC 誰も削除しない 所有者がおらず、回収機能が選択するラベルもない

バックエンドが直接削除の順序を管理しなくてよい理由の1つとして、Kubernetesの PVC保護(Storage Object in Use Protection)が挙げられます。Kubernetesのドキュメントによると、Podで使用中のPVCは削除命令を出しても、そのPodが使用を停止するまで実際の削除が延期されます。そのため、VM → Pod → ボリュームの順序が自然に維持され、ゲストがまだ使用しているディスクが先に消えることはありません。ゴールデンを削除する際も同様の保護機能が働き、使用中のVMがすべて終了するまでゴールデンは「削除中」の状態にとどまります(パート4)。

3ノード構成のセッションでは、代表となるVMを1つ削除すると、残りのVM・Secret・セッション専用ネットワークのポリシーやサービスがラベルとともにまとめて削除されます。実測では、代表の1つを削除してから15秒後に3台のVM・3つのSecret・サービス・ポリシーがすべて0になり、2つ目のワーカーを作成中に失敗させした場合でも、すでに作成されたものはすべて回収されました(2026-09-24)。

Secret なぜ個別にもう一度削除するのか

cloud-init SecretにはVMを所有者として指定しているため、VMが削除されるとガベージコレクターが一緒に削除するはずです。しかし、 その所有者の関連付けに失敗することがあります。 VMを作成した後にSecretに所有者を設定するのは別の修正リクエストとなりますが、セキュリティ点検の記録によると、バックエンドのサービスアカウントにSecretの 修正権限がないため 、この関連付けが常に失敗しているように見えるということが分かりました。実際、VMが存在しない状態で32日間残り続けたcloud-init Secretが1つ発見されました。そのため、バックエンドはVMを削除した後、Secretを名前で再度削除します。残存していると、同じセッション名が再び割り当てられた際に古いcloud-initが復活してしまうためです。残ったSecretの整理と権限の修正は人間の手で行う作業として記録されています。

4. 上限: 被害の規模を制限する

回収機能がいかに堅牢であっても失敗することはあります。そのため、「最悪の場合にどれだけリソースがリークするか」を上限値によって制限しています。

上限 値 設定箇所
同時実習VM数 8台 バックエンド(クラスターに問い合わせてカウント)
VMネームスペースのクォータ — VM数 8 ResourceQuota
同クォータ — CPUリクエスト / メモリリクエスト 16 / 48Gi ResourceQuota
同クォータ — PVC数 12 ResourceQuota(ゴールデンおよび取得中のスクラッチのみ使用)

二重になっている理由は、バックエンドの上限でカウントが正確に行えない場合(KubeVirtの不安定化によりリストが取得できない場合、バックエンド側で上限をかけるとすべてのVM実習が同時に停止してしまうため、バックエンドでは制限をかけません)に、最後にクォータがブロックしてくれるためです。

数字の「8」を引き上げなかった理由も記録されています。複数のVMで構成されるセッションを作成した際(2026-09-24)、KVMを実際に提供するノードのメモリリクエストの空きを合算しても約17GiBであり、測定中に1つのノードがシステムのOOMにより約1分間NotReady状態に陥る現象も発生しました。 クォータを引き上げても、拒否の理由が「空き容量がない」からスケジューリングの失敗とノードへの負荷に変わるだけです。ノードのメモリが先に不足します。VMのメモリリクエストにはディスカウントがなく(パート2)、ノードがメモリ不足に陥った際に最初に終了するのはVMではなく同一ノード上の実習用Podである点も、上限を保守的に設定している理由です。

5. 残された隙間

TTの一言。 消去コードは短いほど良いです。バックエンドはたった2つだけ消去し、残りは「誰が誰を所有するか」と「使用中なら消去しない」に任せます。そして失敗した行はスキップして次の周回でもう一度!1つのせいで全てが止まってはいけないからです。

参考

ドキュメントはすべて2026-10-16に閲覧しました。

ログインするといいねできます

コメント

まだコメントはありません。

ログインするとコメントできます