TT Lab
ブログ

한국어 English 日本語 中文

はじめる
ブログ

ブログ

学生VMが立ち上がるまで 1: 学生・バックエンド・KubeVirt・ストレージ、4つの層から見た全体像

ステータス: ドラフト(2026-10-16)、自社コード + 公式ドキュメント + 一部実測。 動作の説明は、TT Labのバックエンドコード(backend/app/vms.py, backend/app/main.py)と設計書にそのまま従っています。バージョンは執筆した日に読み込んで確認した値です(Kubernetes v1.34.10、KubeVirt v1.9.0、CDI v1.66.0)。時間の数値は実習を作成しながら計測した記録であり、サンプル数と日付を併記しています。 作成ルール: 実測データが基本であり、計測できなかったものは「未計測」、ドキュメントから見つからなかったものは「未確認」と表示します。

学生VMが立ち上がるまで 1: 4つの層から見た全体像

目次

1. なぜPodではなくVMなのか

TT Labの実習のほとんどは、KubernetesのPod1つで動作します。Podは数秒で起動し、イメージを変えるだけで環境が変わり、削除すれば痕跡が残りません。しかし、Podではどうしても模倣できない実習がありました。

そのため、このような実習のみ 仮想マシンで公開します。学生はVM内で本物のrootとなり、systemd・カーネル・ディスクを自由に操作できます。その代わり、そのrootがVMの外に出ないようにすること、そして使い終わったVMが確実に回収されるようにすることが本シリーズのテーマです。VMはKubernetes上でKubeVirtを使って起動します。実習用Podと同じクラスター、同じスケジューラー、同じストレージを使用するためです。

2. 4つの層

本シリーズでは、あえて4つの層のみを取り上げます。 学生(ブラウザ)、バックエンド(Web Pod)、KubeVirt、Kubernetesストレージです。ゲートウェイ・イメージレジストリ・モニタリングのようにその間に介在するものは図から除外しています。「VMが割り当てられる」という処理の骨組みがこの4つであるためです。

실습 VM 이 뜨기까지 — 네 층, 열 걸음 번호 순서로 진행한다. 실선은 만드는 길, 점선은 읽거나 잇는 길. 학생 · 브라우저 ① 「실습 시작」 누름 ⑩ 웹 터미널 — root 셸 백엔드 · 웹 파드 ② 자리 확인 1인 1세션 · VM 8대 상한 ③ cloud-init Secret 채점 에이전트 · VM별 토큰 ④ 루트 디스크 고르기 골든 PVC 읽기, 안 되면 대체 ⑤ VirtualMachine 만들기 세션 라벨 · 환경 라벨 ⑨ 콘솔 중계 웹소켓 ↔ console 서브리소스 ⑧ 준비 확인 VMI Running → 에이전트 응답 VM 에는 exec 가 없다. 준비 확인·채점은 게스트 안 에이전트(8899 포트)로 한다. KubeVirt ⑥ 파드를 만들고 QEMU 를 띄운다 VM 원하는 상태 VMI 실행 하나 virt-launcher 파드 하나 QEMU·KVM 게스트 OS 8899 쿠버네티스 스토리지 ⑦ 볼륨 붙이기 골든 PVC OS 마다 하나 · 공유 스토리지 RWX · 읽기 전용으로 붙임 운영자가 한 번 만든다 쓰기 층 disk.qcow2 노드 로컬 emptyDir VM 마다 하나 VM 이 끝나면 함께 사라짐 골든이 없거나 못 쓰면 ④ 가 컨테이너 디스크로 대체한다(3·4편). 회수는 5편.
図: 実習VMが立ち上がるまでの4つの層と10のステップ

層ごとの役割を一行にまとめると以下のようになります。

層 役割 本シリーズでの扱い
学生 ボタンを押し、WebターミナルでVM内のシェルを使用する 第1回
バックエンド 空きを確認し、VMに組み込むものを準備し、ルートディスクを選び、VMオブジェクトを作成する。使い終わったら削除する 第1・5・6回
KubeVirt VMオブジェクトを確認してPodを作成し、そのPod内でQEMUによりゲストを起動する 第2回
ストレージ ゲストのディスクとなるボリュームを接続する。1つのゴールデンPVCを複数のVMで共有して読み込む 第3・4回

3. 10のステップ

① 「実習開始」

学生がボタンを押すと、ブラウザが POST /api/sessions を送信します。レッスンが runtime: vm であれば、このセッションはPodではなくVMを作成します。セッションの履歴には最初から runtime が含まれます。回収機能が後から「何を削除すべきか」をこの値一つで判断するためです。ここで種類を間違えると、Podは削除されたと記録される一方で、VMは4GiBのメモリを占有し続けます。

② 空き確認

2つの点を確認します。1つ目は 1人1セッションです。他の実習が開いている場合は何が開いているかを通知し、新しく作成しません。ユーザーごとのロック内で確認と記録を同時に行うため、ボタンを2回押してもセッションは1つだけです。

もう1つは、**同時に起動しているVMの数(デフォルト8台)**です。この数値はDBではなく クラスターに問い合わせて 数えます。DBは回収機能が見逃した幽霊VMを数えることができませんが、ノードのメモリを実際に占有しているのはクラスター側だからです。上限に達している場合は、「現在VMの実習が8個開いているため空きがありません」と返します。なぜ8なのかは第5回で解説します。

③ cloud-init Secret

VMはイメージをあらかじめビルドせず、パブリッククラウドのイメージに対して 初回の起動時に cloud-init により 必要なものを組み込みます。組み込むものは次の3つです。

これらをVMのマニフェストに直接記述せずSecretに格納するのには、実測に基づく理由があります。KubeVirtはインラインのcloud-initを2048バイトに制限していますが、採点エージェントを含めると7千バイトを超えてしまいます。そのため、すべての実習VMが同じ経路(Secret)を通ります。サイズによって経路が分かれてしまうと、エージェントを組み込む瞬間だけに発生する不具合の温床になるためです。

④ ルートディスクの選択

ここが本シリーズの中心です。レッスンが vm_disk: "golden" を指定している場合、バックエンドはオペレーターが事前に作成した ゴールデンPVC を読み込み(get つの)、現在使用可能かどうかを判定します。使用可能な場合はそのPVCを 読み取り専用のベースとして使用し、VMごとに軽量な 書き込み層のみを重ねます。存在しない場合や使用できない場合(不在・権限なし・取得中・単一ノード用ボリューム・異なるイメージ・削除中)、エラーを出さずにデフォルトのディスク(コンテナディスク)で起動します。ゴールデンは最適化であって前提条件ではないためです。ディスクの3つの分岐については第3回、ゴールデンと書き込み層については第4回で詳しく解説します。

⑤ VirtualMachineの作成

バックエンドがKubernetes APIに VirtualMachine オブジェクトを1つ作成します。このオブジェクトにはラベルが付与されます。実習セッションであることを示す目印、セッションID、そして どの環境(開発・本番)のバックエンドが作成したかです。最後のラベルはトラブルから生まれました。開発と本番のバックエンドが同じVMのネームスペースを参照しており、一方の回収機能が自身のDBに存在しないVMを孤立していると誤認し、他方の学生のVMを削除してしまったためです(第5回)。

cloud-initのSecretにはこのVMを所有者として指定し、VMが削除されたときにSecretも一緒に削除されるようにします。ただし、所有者の関連付けに失敗する場合があるため、削除時にはSecretも個別に再度削除します。

⑥ KubeVirt: VM → VMI → Pod → QEMU

ここからはKubeVirtの処理です。VMをもとに実行中インスタンス(VMI)を作成し、VMIごとに virt-launcher Pod を1つ作成します。KubernetesのスケジューラーがそのPodをノードに配置すると、そのノードのvirt-handlerがPod内のlibvirtに対して「このVMを起動せよ」と指示します。ゲストは、そのPod内のQEMUプロセスとして動作します。VMがPod内で動作する理由とその代償は、第2回のテーマです。

⑦ ボリュームの接続

virt-launcher Podが作成される際、ゲストのディスクとなるボリュームも一緒にアタッチされます。ゴールデン実習の場合、ゴールデンPVCが 読み取り専用でアタッチされ、その上にKubeVirtがPodの一時ディレクトリ(emptyDir)に書き込み層のファイルを作成します。ゲストにはこれらが統合された1つのディスク(/dev/vda)のみが見えます。アタッチすべきPVCが存在しない場合、KubeVirtはPod自体を作成できず、VMはいつまで経っても起動しません。④であらかじめ判定を行うのはそのためです。

⑧ 準備完了の確認

バックエンドは2段階で待機します。まずVMIが Running になるのを待ち(実習ごとに設定された上限、デフォルト150秒)、次にゲスト内のエージェントが「準備完了」と応答するのを待ちます。

KubeVirtにはPodの exec のような「ゲスト内でコマンドを実行する機能」はありません。サブリソースはconsole、vnc、portforwardなどのみであり、これは意図された設計です。そのため、準備確認も採点も③で組み込んだエージェントで行います。

Running のみを確認して学生をアクセスさせてはならないことも、実際に経験しました。エージェントはcloud-initの早い段階で起動し、実習環境を構築するセットアップスクリプトはその後に実行されます。その間にアクセスさせてしまったため、第1段階から「ファイルが存在しません」というエラーで失敗し、採点は8段階中4段階しか通過しませんでした。現在はcloud-initの 最終行で準備完了のフラグを作成し、セットアップスクリプトが失敗した場合は作成しないようにしています。これにより、バックエンドはタイムアウトにより 正直に 失敗します。

⑨ コンソールの仲介と ⑩ Webターミナル

バックエンドがVMIの console サブリソースにWebSocketで接続し(⑨)、学生のブラウザのWebターミナルとの間を仲介します(⑩)。ここにも2つの罠がありました。

WebSocketを開く際、バックエンドは接続したユーザーがそのセッションの所有者であるか、セッションが実行中であるかを事前に確認します。コンソールのサブリソースにアクセスできるKubernetesの権限は、バックエンドのサービスアカウントのみです(第6回)。

4. かかる時間 — これまでに計測したもの

項目 値 条件 サンプル数
VMIのステージ: Pending → Scheduled → Running 0秒 → 10秒 → 20秒(10秒単位での記録) Ubuntu 24.04 コンテナディスク、2 vCPU・4Gi、2026-08-29 1回
ゲスト内の cloud-init 完了 23.8秒 同一VM 1回
cloud-initによるDockerのインストール実習 約76秒(起動25 + aptインデックス14 + インストール37) 2026-08 実習開発中 1回
学生APIで開いたセッション、リクエストから準備まで 90.991秒 Istio実習単体、2026-09-12 1回
3台構成のVMセッション:3台のVMがRunning 17~23秒 kubeadm 3ノード実習、2026-09-23~24 複数回の試験の範囲
同じセッション:3ノードの準備スクリプト完了 約300秒 同じ実習 同上
実習ごとに定めた準備上限の根拠 k3s 82秒 · Istio 94秒 · k0s 150秒 · Cilium 269秒 各実習の正解検証 実習ごと
VMごとにディスクを新しく取得する方式(DataVolume)の初回起動 最大195秒 第3部 最大値のみ記録

表が物語っているのは次の2つです。 VM自体は20秒前後で起動し、残りはゲスト内で実習環境を構築する時間です。そして、ディスクをVMごとに新しく取得するとそれだけで数分かかります。第4部のゴールデンディスクが狙っているのは、この最後の行です。

限界も明確です。大半が1回ずつ測定した値であり、学生セッション全体の準備時間の分布(中央値とばらつき)は 未測定です。セッションの記録には開始時刻と準備時刻が一緒に残るため、記録を集めれば測定可能です。

5. この図であえて省いたもの

T-Tの一言。 ボタンを1回押した裏側には10のステップが隠されています。しかし、VMが起動するのは20秒で終わり、残りのほとんどは「ゲスト内での実習準備」と「ディスクの用意」です。そのため、速くしたいのであればまずディスクを見てください。第4部でお会いしましょう!

参考

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

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

コメント

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

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