TT Lab
요금제

한국어

시작하기
블로그

블로그

학생 VM 이 뜨기까지 1: 학생·백엔드·KubeVirt·스토리지, 네 층으로 본 전체 그림

상태: 초안 (2026-10-16), 우리 코드 + 공식 문서 + 일부 실측. 동작 설명은 TT Lab 백엔드 코드(backend/app/vms.py, backend/app/main.py)와 설계 문서를 그대로 따랐습니다. 버전은 글을 쓴 날 읽어서 확인한 값입니다(쿠버네티스 v1.34.10, KubeVirt v1.9.0, CDI v1.66.0). 시간 숫자는 실습을 만들면서 잰 기록이고, 표본 수와 날짜를 함께 적었습니다. 작성 규칙: 실측 데이터가 기본이고, 못 잰 것은 「미실측」, 문서에서 찾지 못한 것은 「미확인」으로 표시합니다.

학생 VM 이 뜨기까지 1: 네 층으로 본 전체 그림

목차

1. 왜 파드가 아니라 VM 인가

TT Lab 의 실습은 대부분 쿠버네티스 파드 하나에서 돕니다. 파드는 몇 초 만에 뜨고, 이미지만 바꾸면 환경이 바뀌고, 지우면 흔적이 남지 않습니다. 그런데 파드로는 끝내 흉내 낼 수 없는 실습이 있었습니다.

그래서 이런 실습만 가상머신으로 엽니다. 학생은 VM 안에서 진짜 root 이고 systemd·커널·디스크를 마음대로 다룹니다. 대신 그 root 가 VM 밖으로 나가지 못하게, 그리고 다 쓴 VM 이 반드시 회수되게 만드는 것이 이 시리즈의 주제입니다. VM 은 쿠버네티스 위에서 KubeVirt 로 띄웁니다. 실습 파드와 같은 클러스터, 같은 스케줄러, 같은 스토리지를 쓰기 때문입니다.

2. 네 층

이 시리즈는 일부러 네 층만 봅니다. 학생(브라우저), 백엔드(웹 파드), KubeVirt, 쿠버네티스 스토리지입니다. 게이트웨이·이미지 레지스트리·모니터링처럼 그 사이에 끼어 있는 것은 그림에서 뺐습니다. 「VM 이 할당된다」 는 일의 뼈대가 이 넷이기 때문입니다.

실습 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 이 뜨기까지의 네 층과 열 걸음

층마다 하는 일을 한 줄로 줄이면 이렇습니다.

층 하는 일 이 시리즈에서
학생 버튼을 누르고, 웹 터미널로 VM 안의 셸을 쓴다 1편
백엔드 자리를 확인하고, VM 에 심을 것을 준비하고, 루트 디스크를 고르고, VM 객체를 만든다. 다 쓰면 지운다 1·5·6편
KubeVirt VM 객체를 보고 파드를 만들고, 그 파드 안에서 QEMU 로 게스트를 띄운다 2편
스토리지 게스트의 디스크가 될 볼륨을 붙인다. 골든 PVC 하나를 여러 VM 이 나눠 읽는다 3·4편

3. 열 걸음

① 「실습 시작」

학생이 버튼을 누르면 브라우저가 POST /api/sessions 를 보냅니다. 레슨이 runtime: vm 이면 이 세션은 파드가 아니라 VM 을 만듭니다. 세션 기록에는 처음부터 runtime 이 들어갑니다. 회수기가 나중에 「무엇을 지울지」 를 이 값 하나로 정하기 때문입니다. 여기서 종류를 잘못 알면 파드는 지웠다고 기록되고 VM 은 메모리 4GiB 를 계속 붙잡습니다.

② 자리 확인

두 가지를 봅니다. 하나는 1인 1세션입니다. 다른 실습이 열려 있으면 무엇이 열려 있는지 알려 주고 새로 만들지 않습니다. 사용자별 잠금 안에서 확인과 기록을 함께 하므로 버튼을 두 번 눌러도 세션은 하나입니다.

다른 하나는 **동시에 떠 있는 VM 의 수(기본 8대)**입니다. 이 숫자는 DB 가 아니라 클러스터에 물어서 셉니다. DB 는 회수기가 놓친 유령 VM 을 세지 못하는데, 노드의 메모리를 실제로 잡고 있는 것은 클러스터 쪽이기 때문입니다. 상한에 걸리면 「지금 VM 실습이 8개 열려 있어 자리가 없습니다」 를 돌려줍니다. 왜 8인지는 5편에서 봅니다.

③ cloud-init Secret

VM 은 이미지를 미리 굽지 않고, 공용 클라우드 이미지에 첫 부팅 때 cloud-init 으로 필요한 것을 심습니다. 심는 것은 셋입니다.

이것을 VM 매니페스트에 바로 적지 않고 Secret 에 담는 데는 실측한 이유가 있습니다. KubeVirt 는 인라인 cloud-init 을 2048바이트로 막는데, 채점 에이전트를 실으면 7천 바이트가 넘습니다. 그래서 모든 실습 VM 이 같은 길(Secret)로 갑니다. 크기에 따라 길이 갈리면 에이전트를 넣는 순간에만 터지는 경로가 생기기 때문입니다.

④ 루트 디스크 고르기

여기가 이 시리즈의 중심입니다. 레슨이 vm_disk: "golden" 을 적었으면, 백엔드는 운영자가 미리 만들어 둔 골든 PVC 를 읽어(get 하나) 지금 써도 되는지 판정합니다. 쓸 수 있으면 그 PVC 를 읽기 전용 바탕으로 쓰고, VM 마다 얇은 쓰기 층만 얹습니다. 없거나 쓸 수 없으면(없음·권한 없음·가져오는 중·한 노드용 볼륨·다른 이미지·지우는 중) 오류를 내지 않고 기본 디스크(컨테이너 디스크)로 엽니다. 골든은 최적화이지 전제가 아니기 때문입니다. 디스크 세 갈래는 3편, 골든과 쓰기 층은 4편에서 자세히 봅니다.

⑤ VirtualMachine 만들기

백엔드가 쿠버네티스 API 에 VirtualMachine 객체 하나를 만듭니다. 이 객체에는 라벨이 붙습니다. 실습 세션이라는 표시, 세션 id, 그리고 어느 환경(개발·운영)의 백엔드가 만들었는지입니다. 마지막 라벨은 사고에서 나왔습니다. 개발과 운영 백엔드가 같은 VM 네임스페이스를 보는데, 한쪽 회수기가 자기 DB 에 없는 VM 을 고아로 보고 다른 쪽 학생의 VM 을 지웠습니다(5편).

cloud-init Secret 에는 이 VM 을 소유자로 적어, VM 이 지워지면 Secret 도 함께 가게 합니다. 다만 소유자 연결이 실패할 수 있어서, 지울 때 Secret 도 따로 한 번 더 지웁니다.

⑥ KubeVirt: VM → VMI → 파드 → QEMU

여기서부터는 KubeVirt 가 합니다. VM 을 보고 실행 중인 인스턴스(VMI)를 만들고, VMI 마다 virt-launcher 파드 하나를 만듭니다. 쿠버네티스 스케줄러가 그 파드를 노드에 앉히면, 그 노드의 virt-handler 가 파드 안의 libvirt 에게 「이 VM 을 켜라」 고 시킵니다. 게스트는 그 파드 안의 QEMU 프로세스로 돕니다. VM 이 파드 안에서 도는 까닭과 그 대가는 2편의 주제입니다.

⑦ 볼륨 붙이기

virt-launcher 파드가 만들어질 때 게스트의 디스크가 될 볼륨도 함께 붙습니다. 골든 실습이면 골든 PVC 가 읽기 전용으로 붙고, 그 위에 KubeVirt 가 파드의 임시 디렉터리(emptyDir)에 쓰기 층 파일을 만듭니다. 게스트에는 이 둘이 합쳐진 디스크 하나(/dev/vda)만 보입니다. 붙일 PVC 가 없으면 KubeVirt 는 파드를 아예 만들지 못하고 VM 은 영영 뜨지 않습니다. ④ 에서 미리 판정하는 이유입니다.

⑧ 준비 확인

백엔드는 두 단계로 기다립니다. 먼저 VMI 가 Running 이 되기를(실습마다 정한 상한, 기본 150초), 그다음 게스트 안의 에이전트가 「준비됨」 이라고 답하기를 기다립니다.

KubeVirt 에는 파드의 exec 같은 「게스트 안에서 명령 돌리기」 가 없습니다. 서브리소스는 console·vnc·portforward 같은 것뿐이고, 의도된 설계입니다. 그래서 준비 확인도 채점도 ③ 에서 심은 에이전트로 합니다.

Running 만 보고 학생을 들여보내면 안 된다는 것도 실제로 겪었습니다. 에이전트는 cloud-init 앞쪽에서 뜨고, 실습 환경을 세우는 준비 스크립트는 그 뒤에 돕니다. 그 사이에 들여보내니 1단계부터 「파일이 없습니다」 로 실패했고, 채점은 8단계 중 4단계만 통과했습니다. 지금은 cloud-init 의 마지막 줄이 준비 표식을 찍고, 준비 스크립트가 실패하면 찍지 않습니다. 그러면 백엔드는 시간 초과로 정직하게 실패합니다.

⑨ 콘솔 중계와 ⑩ 웹 터미널

백엔드가 VMI 의 console 서브리소스에 웹소켓으로 붙고(⑨), 학생 브라우저의 웹 터미널과 그 사이를 중계합니다(⑩). 여기에도 함정이 둘 있었습니다.

웹소켓을 열 때 백엔드는 접속한 사람이 그 세션의 주인인지, 세션이 실행 중인지를 먼저 봅니다. 콘솔 서브리소스에 닿을 수 있는 쿠버네티스 신원은 백엔드 서비스 계정뿐입니다(6편).

4. 얼마나 걸리나 — 지금까지 잰 것

무엇 값 조건 표본
VMI 단계: Pending → Scheduled → Running 0초 → 10초 → 20초(10초 단위 기록) 우분투 24.04 컨테이너 디스크, 2 vCPU·4Gi, 2026-08-29 1회
게스트 안 cloud-init 완료 23.8초 같은 VM 1회
도커를 cloud-init 으로 설치하는 실습 약 76초(부팅 25 + apt 색인 14 + 설치 37) 2026-08 실습 개발 중 1회
학생 API 로 연 세션, 요청부터 준비까지 90.991초 Istio 실습 하나, 2026-09-12 1회
VM 세 대짜리 세션: 세 VM Running 17~23초 kubeadm 3노드 실습, 2026-09-23~24 시험 여러 번의 범위
같은 세션: 세 노드 준비 스크립트 끝 약 300초 같은 실습 같음
실습마다 정해 둔 준비 상한의 근거 k3s 82초 · Istio 94초 · k0s 150초 · Cilium 269초 각 실습의 정답지 검증 실습마다
VM 마다 디스크를 새로 가져오는 방식(DataVolume)의 첫 부팅 최대 195초 3편 최대값만 기록

표가 말해 주는 것은 두 가지입니다. VM 자체는 20초 안팎에 켜지고, 나머지는 게스트 안에서 실습을 세우는 시간입니다. 그리고 디스크를 VM 마다 새로 가져오면 그것만으로 몇 분이 듭니다. 4편의 골든 디스크가 노리는 것이 이 마지막 줄입니다.

한계도 분명합니다. 대부분 한 번씩 잰 값이고, 학생 세션 전체의 준비 시간 분포(중앙값과 흩어짐)는 미실측입니다. 세션 기록에 시작 시각과 준비 시각이 함께 남으므로 기록만 모으면 잴 수 있습니다.

5. 이 그림이 일부러 뺀 것

티티 한마디. 버튼 한 번 뒤에 열 걸음이 숨어 있어요. 그런데 VM 이 켜지는 건 20초면 끝나고, 나머지는 거의 「게스트 안에서 실습 준비하기」 와 「디스크 마련하기」 예요. 그러니 빨라지고 싶으면 디스크부터 보세요. 4편에서 만나요!

참고

문서는 모두 2026-10-16 에 열람했습니다.

로그인하면 좋아요를 누를 수 있습니다

댓글

아직 댓글이 없습니다.

로그인하면 댓글을 쓸 수 있습니다