TT Lab
Blog

한국어 English 日本語 中文

Get started
Blog

Blog

Until a Student VM Starts 1: The Big Picture Across Four Layers: Student, Backend, KubeVirt, and Storage

Status: Draft (2026-10-16), our code + official docs + partial measurements. Operational descriptions strictly follow the TT Lab backend code (backend/app/vms.py, backend/app/main.py) and design documents. Versions are values read and verified on the day of writing (Kubernetes v1.34.10, KubeVirt v1.9.0, CDI v1.66.0). Time values are records measured while building the labs, with sample sizes and dates noted together. Writing rule: Measured data is the default; unmeasured items are marked as "Unmeasured", and items not found in documentation are marked as "Unconfirmed".

Until a Student VM Starts 1: The Big Picture Across Four Layers

Table of Contents

1. Why VMs Instead of Pods

Most TT Lab labs run on a single Kubernetes pod. Pods start in seconds, changing environments is as simple as swapping images, and they leave no traces when deleted. However, there were some labs that pods could never replicate.

Therefore, only these kinds of labs are opened as virtual machines. Students have true root access inside the VM and can freely handle systemd, the kernel, and disks. Instead, preventing that root access from escaping the VM and ensuring that used VMs are reliably reclaimed are the main themes of this series. VMs are launched on Kubernetes using KubeVirt because they use the same cluster, scheduler, and storage as lab pods.

2. The Four Layers

This series deliberately focuses on only four layers: Student (Browser), Backend (Web Pod), KubeVirt, and Kubernetes Storage. Intermediaries sitting between them, such as gateways, image registries, and monitoring, are omitted from the diagram because these four form the backbone of the process "a VM is allocated".

실습 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편.
Figure: The Four Layers and Ten Steps Until a Student VM Starts

The role of each layer can be summarized in one line:

Layer Role Covered in This Series
Student Clicks buttons and uses the web terminal to access the shell inside the VM Part 1
Backend Checks capacity, prepares data to inject into the VM, selects the root disk, and creates the VM object. Deletes it when done Parts 1, 5, 6
KubeVirt Observes the VM object, creates a pod, and runs the guest via QEMU inside that pod Part 2
Storage Attaches volumes that will become the guest's disk. Multiple VMs share and read a single golden PVC Parts 3, 4

3. Ten Steps

① "Start Lab"

When a student clicks the button, the browser sends POST /api/sessions . If the lesson is runtime: vm , this session creates a VM instead of a pod. The session record includes runtime from the very beginning. This is because the reaper determines "what to delete" later using this single value. If the type is misidentified here, the pod is recorded as deleted while the VM continues to hold onto 4GiB of memory.

② Checking Capacity

Two things are checked. One is one session per person. If another lab is already open, it notifies the user of what is open and does not create a new one. Since checks and records are performed within per-user locks, clicking the button twice still results in only a single session.

The other is **the number of concurrently running VMs (default: 8)**. This number is counted by querying the cluster , not the database. The database cannot count ghost VMs missed by the reaper, whereas it is the cluster side that is actually holding the node's memory. When the limit is hit, it returns "There are currently 8 VM labs open, so no capacity is available." Why the limit is 8 will be covered in Part 5.

③ cloud-init Secret

Rather than pre-baking VM images, we use a public cloud image and inject what we need via cloud-init on first boot. We inject three things:

There is a practical reason for putting these in a Secret instead of writing them directly into the VM manifest. KubeVirt limits inline cloud-init to 2048 bytes, but including the grading agent pushes it past 7,000 bytes. Therefore, all practice VMs go through the same path (Secret). If the path diverged based on size, it would introduce a failure path that only triggers when adding the agent.

④ Choosing the Root Disk

This is the core of the series. When a lesson specifies vm_disk: "golden" , the backend reads the operator-prebuilt golden PVC (get ) to determine if it can be used right now. If it is usable, that PVC is used as a read-only base, and a thin writable layeris stacked on top for each VM. If it is missing or unusable (not found, unauthorized, in the process of being fetched, single-node volume, different image, or being deleted), it opens the default disk (container disk) without throwing an error. This is because the golden disk is an optimization, not a prerequisite. The three disk branches are covered in detail in Part 3, and the golden and writable layers in Part 4.

⑤ Creating the VirtualMachine

The backend creates a VirtualMachine object in the Kubernetes API. This object is labeled with a practice session indicator, the session ID, and which environment's backend created it (dev vs. prod). The last label stems from an incident. The development and production backends were watching the same VM namespace, and one side's garbage collector treated VMs not found in its own DB as orphans and deleted another student's VM (Part 5).

The cloud-init Secret lists this VM as its owner so that when the VM is deleted, the Secret is deleted along with it. However, since the owner reference link can sometimes fail, the Secret is also explicitly deleted one more time during cleanup.

⑥ KubeVirt: VM → VMI → Pod → QEMU

From here, KubeVirt takes over. It looks at the VM, creates a running VirtualMachineInstance (VMI), and creates one virt-launcher pod per VMI. When the Kubernetes scheduler places that pod onto a node, the virt-handler on that node instructs libvirt inside the pod to "turn on this VM." The guest runs as a QEMU process inside that pod. Why VMs run inside pods and the trade-offs involved are the topics of Part 2.

⑦ Attaching Volumes

When the virt-launcher pod is created, the volumes that will serve as the guest's disks are attached as well. If it is a golden practice session, the golden PVC is read-only, and KubeVirt creates a write-layer file on top of it in the pod's temporary directory (emptyDir). The guest only sees a single combined disk (/dev/vda). If there is no PVC to attach, KubeVirt cannot create the pod at all and the VM will never start. This is why step 4 performs a pre-check.

⑧ Readiness check

The backend waits in two stages. First, it waits for the VMI to become Running (with a limit defined per lab, defaulting to 150 seconds), and then it waits for the agent inside the guest to respond as "Ready".

KubeVirt does not have a feature like pod exec for "running commands inside the guest." The subresources are limited to things like console, vnc, and portforward, which is by design. Therefore, both the readiness check and grading are handled by the agent embedded in step ③.

Running We also learned from experience that we must not let students in based solely on . The agent starts near the beginning of cloud-init, and the preparation script that sets up the lab environment runs after it. Letting students in during that gap caused failures starting from stage 1 with "file not found," and grading only passed 4 out of 8 stages. Now, the last line of cloud-initwrites a readiness marker, and if the preparation script fails, it is not written. As a result, the backend times out and fails honestly .

⑨ Console relay and ⑩ Web terminal

The backend connects via WebSocket to the VMI's console subresource (⑨) and relays data between it and the student's browser web terminal (⑩). There were two traps here as well.

When opening a WebSocket, the backend first checks if the connected user is the owner of that session and whether the session is running. The only Kubernetes identity that can reach the console subresource is the backend service account (Part 6).

4. How long does it take — Measurements so far

What Value Condition Sample
VMI Phase: Pending → Scheduled → Running 0s → 10s → 20s (recorded in 10s intervals) Ubuntu 24.04 container disk, 2 vCPU · 4Gi, 2026-08-29 1 run
cloud-init completion inside guest 23.8s Same VM 1 run
Docker installation practice using cloud-init Approx. 76 sec (booting 25 + apt index 14 + installation 37) Lab development in progress, 2026-08 1 run
Session opened via student API, from request to preparation 90.991 sec Istio lab 1, 2026-09-12 1 run
Three-VM session: Three VMs Running 17-23 sec kubeadm 3-node lab, 2026-09-23~24 Range across multiple tests
Same session: Three-node prep script completed Approx. 300 sec Same lab Same
Basis for the preparation upper limit set for each lab k3s 82s · Istio 94s · k0s 150s · Cilium 269s Validation of answer sheets for each lab Per lab
First boot with the method of fetching a fresh disk per VM (DataVolume) Up to 195 sec Part 3 Maximum values recorded only

The table tells us two things. The VM itself powers on in around 20 seconds, and the rest is the time spent setting up the lab inside the guest. Also, if you fetch a new disk for each VM, that alone takes several minutes. The golden disk in Part 4 targets this very last line.

The limitations are also clear. Most values were measured just once, and the distribution of preparation times across all student sessions (median and variance) is unmeasured. Since session logs retain both the start time and preparation time, they can be calculated simply by gathering the logs.

5. What this illustration intentionally omitted

A word from Titi. Ten steps are hidden behind a single button click. However, the VM booting finishes in 20 seconds, and almost everything else consists of 「preparing the lab inside the guest」 and 「provisioning the disk」. Therefore, if you want to speed things up, look at the disk first. See you in Part 4!

References

All documents were accessed on 2026-10-16.

Sign in to like

Comments

No comments yet.

Sign in to leave a comment