상태: 초안 (2026-10-16), 공식 문서 + 일부 실측. KubeVirt·CDI·쿠버네티스 문서를 2026-10-16 에 열어 확인했습니다 (KubeVirt v1.9.0, CDI v1.66.0, 쿠버네티스 v1.34). 스토리지 클래스의 설정은 같은 날 읽기 전용으로 조회했고, 공개 글이라 저장소 제품과 드라이버 이름은 「공유 스토리지의 CSI 드라이버」, 「노드 로컬 클래스」 로 일반화했습니다. 작성 규칙: 실측 데이터가 기본이고, 못 잰 것은 「미실측」, 문서에서 찾지 못한 것은 「미확인」으로 표시합니다.
학생 VM 이 뜨기까지 3: 디스크는 PVC 다
목차
- 1. 볼륨의 종류
- 2. 컨테이너 디스크: 빠르지만 작다
- 3. DataVolume: 크지만 느리다
- 4. 접근 모드: 몇 노드가 동시에 붙을 수 있나
- 5. 볼륨 모드: 파일시스템이냐 블록이냐
- 6. 바인딩 시점과 StorageProfile
- 7. 정리: 세 갈래의 거래
- 참고
1편의 ⑦ 「볼륨 붙이기」 를 엽니다. 결론부터 말하면, KubeVirt 의 디스크는 **게스트에게 보이는 장치(disk)**와 **그 내용을 담는 쿠버네티스 볼륨(volume)**의 짝입니다. 매니페스트에서 둘은 이름으로 이어집니다.
domain:
devices:
disks:
- name: root # 게스트에게는 virtio 디스크(/dev/vda)
disk: {bus: virtio}
volumes:
- name: root # 내용은 이 볼륨에서 온다
containerDisk: {image: quay.io/containerdisks/ubuntu:24.04}
볼륨 자리에 무엇을 넣느냐에 따라 디스크의 성질이 완전히 달라집니다.
1. 볼륨의 종류
KubeVirt 사용자 안내서가 꼽는 볼륨 원본 가운데 실습 VM 에 쓰는 것은 다섯입니다.
| 원본 | 무엇 | 끄면 | 실습에서 |
|---|---|---|---|
containerDisk |
컨테이너 이미지의 /disk 안에 든 디스크 이미지 |
사라진다(임시) | 기본 루트 디스크 |
emptyDisk |
노드에 만드는 빈 희소 qcow2. 용량을 적어야 한다 | 게스트 재부팅에는 남고, VM 을 다시 만들면 사라진다 | 도커·쿠버네티스 실습의 스크래치 |
persistentVolumeClaim |
이미 있는 PVC | 남는다 | — |
dataVolume |
CDI 가 가져와 만든 PVC | 남는다(VM 이 소유하면 VM 과 함께 지워진다) | 영구 디스크 선택지 |
ephemeral |
PVC 를 읽기 전용 바탕으로 쓰고 쓰기는 로컬 쓰기 층으로 | 쓰기 층만 사라진다 | 골든 디스크(4편) |
cloudInitNoCloud |
cloud-init 자료(Secret 에서) | — | 첫 부팅 설정(1편 ③) |
2. 컨테이너 디스크: 빠르지만 작다
컨테이너 디스크는 OS 이미지를 컨테이너 이미지처럼 레지스트리에서 받습니다. 노드가 한 번 받아 두면 다음 VM 은 받지 않고 바로 씁니다. 디스크가 임시라서 「만들고, 망가뜨리고, 지운다」 는 실습에 잘 맞고, 그래서 기본값입니다. 1편의 실측대로 20초 안팎에 Running 이 됩니다.
임시라는 것은 바탕은 노드에 받아 둔 이미지 파일이고, 게스트의 쓰기는 따로 쌓인다는 뜻입니다. 9일째 돌던 운영자용 VM 의 virt-launcher 안을 읽어 보니, 루트는 바탕(노드에 받아 둔 디스크 이미지, 할당 약 596MiB) 위에 얹힌 qcow2 쓰기 층(할당 약 1.10GiB)이었습니다(실측, 2026-10-14). 4편에서 다룰 골든 디스크는 이 구조에서 바탕만 PVC 로 바꾼 것입니다.
단점은 크기입니다. 컨테이너 디스크의 루트는 바탕 이미지의 가상 크기, 우리가 쓰는 우분투 이미지로는 2.4GiB 뿐입니다(실측). 실습을 만들며 이것 때문에 두 번 막혔습니다.
- 도커 실습. 이미지 몇 장을 받고 빌드를 두어 번 하니 루트가 찼습니다. 그런데 오류는 전부 「이미지가 없습니다」 류라 원인이 공간이라는 것을 알아채기 어려웠습니다. 20GiB 짜리
emptyDisk를 스크래치로 붙여/var/lib/docker를 옮겼는데 또 찼습니다. 도커 29 는 containerd 의 이미지 저장소를 써서 레이어가/var/lib/containerd쪽에 쌓였기 때문입니다. 두 경로를 다 옮기고서야 풀렸습니다. - 스크래치 디스크의 이름. 스크래치를
/dev/vdb라고 적고 포맷하려 했는데, cloud-init 디스크가 그 자리를 가져가서 스크래치는/dev/vdc였습니다(실측). 이름을 틀리면 cloud-init 디스크를 포맷합니다. 그래서 이름 대신 성질(파일시스템도 파티션도 없고 5GB 보다 큰 디스크)로 고릅니다. - kubeadm 실습. 패키지와 컨트롤 플레인 이미지를 받다가
No space left on device로 죽었습니다. 스크래치로 옮기기 어려운 경로가 많아 루트 자체가 커야 했습니다.
3. DataVolume: 크지만 느리다
루트가 커야 하는 실습을 위해 persistent 선택지가 있습니다. 이때 백엔드는 VM 매니페스트에 dataVolumeTemplates 를 넣고, CDI(Containerized Data Importer)가 VM 마다 PVC 를 만들어 이미지를 가져옵니다.
CDI 가 레지스트리 이미지를 가져오는 순서는 이렇습니다(CDI 「Scratch space」 문서). 가져오기 파드가 이미지를 받아 레이어를 풀고 디스크 이미지를 찾은 뒤 qemu-img 로 바꿔 대상 PVC 에 씁니다. 이 과정에 대상과 같은 크기의 스크래치 PVC 가 잠깐 필요하고, 끝나면 돌려줍니다. 그래서 10Gi 디스크 하나를 가져오는 동안에는 PVC 가 둘 생깁니다.
VM 이 DataVolume 을 소유하므로 VM 을 지우면 PVC 도 함께 지워집니다. 문제는 시간입니다. 실습 세션은 매번 VM 을 새로 만드는 구조라, VM 을 만들 때마다 이미지를 새로 가져오고 첫 부팅이 최대 195초까지 갔습니다(실측, 실습 개발 중 최대값만 기록). 백엔드가 VM 준비를 기다리는 상한을 컨테이너 디스크(150초)와 DataVolume(300초)으로 따로 둔 것도 이 때문입니다.
크기에도 함정이 하나 있습니다. 파일시스템 PVC 에서 KubeVirt 는 디스크를 PVC 루트의 disk.img 라는 raw 파일로 두므로, 파일시스템 자체가 차지하는 몫만큼 공간이 빕니다. CDI 문서에 따르면 DataVolume 을 spec.pvc 가 아니라 spec.storage 로 적을 때만 CDI 가 이 몫(파일시스템 여분)을 계산해 PVC 를 더 크게 요청합니다. TT Lab 의 CDI 설정에서 여분은 6% 이므로(2026-10-16 확인), 10Gi 를 spec.storage 로 요청하면 게스트 디스크는 10Gi, PVC 는 그보다 조금 큰 크기가 될 것입니다(계산값, 미실측). 설계 문서는 「10Gi 골든의 게스트 루트가 약 9.4GiB 일 것」 이라고 적었는데 CDI 문서와 어긋납니다. 어느 쪽이 맞는지는 4편 끝의 측정으로 확인합니다. 또 KubeVirt 는 disk.img 가 요청보다 조금 작아도(기본 10% 까지) 받아들입니다(사용자 안내서).
4. 접근 모드: 몇 노드가 동시에 붙을 수 있나
PVC 의 접근 모드는 VM 에서 특히 중요합니다. 쿠버네티스 문서의 정의를 요약하면 이렇습니다.
| 모드 | 뜻 | VM 에서 |
|---|---|---|
| ReadWriteOnce (RWO) | 노드 하나가 읽고 쓰기로 붙는다. 같은 노드의 파드 여럿은 함께 쓸 수 있다 | VM 이 그 노드에 묶인다. 라이브 마이그레이션 불가 |
| ReadOnlyMany (ROX) | 여러 노드가 읽기 전용으로 붙는다 | 바탕 디스크에 맞아 보이지만, 아래 주의 |
| ReadWriteMany (RWX) | 여러 노드가 읽고 쓰기로 붙는다 | 라이브 마이그레이션 가능, 여러 노드가 같은 바탕을 공유 |
| ReadWriteOncePod (RWOP) | 클러스터 전체에서 파드 하나만 | CSI 볼륨에서만 |
두 가지를 짚어 둡니다.
접근 모드는 쓰기 보호가 아닙니다. 쿠버네티스 문서는 접근 모드가 붙은 뒤의 쓰기를 막지 않는다고, ROX 로 만든 볼륨도 읽기 전용이 보장되지 않는다고 분명히 적습니다. 진짜 보호는 볼륨을 읽기 전용으로 마운트하는 것입니다. 4편의 골든 디스크가 RWX 로 만들되 붙일 때 readOnly: true 를 빼지 않는 이유입니다.
라이브 마이그레이션은 RWX 를 요구합니다. KubeVirt 사용자 안내서는 PVC 를 쓰는 VM 은 접근 모드가 RWX 일 때만 옮길 수 있다고 적습니다. 옮길 수 있는지는 VMI 가 시작될 때 계산되어 LiveMigratable 조건으로 남고, 방법은 메모리만 옮기는 LiveMigration 과 디스크도 복사해야 하는 BlockMigration 으로 나뉩니다. 실제로 컨테이너 디스크와 emptyDisk 를 쓰는 VM 하나를 읽어 보니 LiveMigratable=True, 방법은 BlockMigration 이었습니다(2026-10-16 조회). 노드 로컬 디스크는 옮기려면 통째로 복사해야 한다는 뜻입니다. TT Lab 의 DataVolume 선택지는 RWO 로 만들므로 그 VM 은 옮길 수 없습니다. 실습 VM 은 어차피 옮기지 않으므로 받아들인 선택입니다.
5. 볼륨 모드: 파일시스템이냐 블록이냐
| 모드 | 파드에 보이는 모양 | KubeVirt 의 디스크 |
|---|---|---|
| Filesystem(기본) | 디렉터리로 마운트 | 그 안의 disk.img(raw) 파일. 소유자는 UID 107 이어야 한다 |
| Block | 파일시스템 없는 블록 장치 | 장치를 그대로 디스크로 |
쿠버네티스 문서는 블록 모드를 파드와 볼륨 사이에 파일시스템 층이 없는 가장 빠른 길로 설명합니다. 대신 쓰는 쪽이 블록 장치를 다룰 줄 알아야 하는데, QEMU 는 다룰 줄 압니다. TT Lab 의 공유 스토리지 클래스는 CDI 가 파일시스템 모드만 내준다고 보고합니다(아래 6절). 그래서 골든 디스크도 파일시스템 PVC 안의 disk.img 입니다.
6. 바인딩 시점과 StorageProfile
PVC 를 만들자마자 볼륨을 잡느냐(Immediate), 그 PVC 를 쓰는 파드가 노드에 앉을 때까지 미루느냐(WaitForFirstConsumer)도 VM 에서 차이를 만듭니다. 노드 로컬 클래스는 미루는 쪽이어야 합니다. 볼륨이 특정 노드의 디스크라서, 파드가 어디 앉을지 알기 전에 잡으면 엉뚱한 노드에 생기기 때문입니다.
CDI 는 이 미룸을 존중하는 기능 게이트(HonorWaitForFirstConsumer)를 둡니다. 그러면 가져오기도 첫 사용자(VM)가 앉을 때까지 미뤄집니다. 골든 디스크처럼 VM 이 오기 전에 다 가져와 있어야 하는 볼륨은 그래서 「바로 바인딩해 달라」 는 어노테이션(cdi.kubevirt.io/storage.bind.immediate.requested)을 답니다.
CDI 는 스토리지 클래스마다 StorageProfile 을 만들어, 그 클래스에 맞는 접근 모드·볼륨 모드·복제 전략을 채웁니다. 이름을 아는 프로비저너에는 권장값을 넣고, 모르는 프로비저너는 비워 둡니다. 비어 있는 클래스에 접근 모드 없이 DataVolume 을 만들면 실패합니다(CDI StorageProfile 문서). 글을 쓰는 날 TT Lab 의 클래스를 읽어 보니 이랬습니다.
| 클래스 | 바인딩 | 확장 | StorageProfile 이 내준 것 | 복제 전략 |
|---|---|---|---|---|
| 공유 스토리지(CSI 드라이버) | Immediate | 가능 | RWX · Filesystem | copy |
| 노드 로컬 | WaitForFirstConsumer | — | 비어 있음 | copy |
공유 스토리지 클래스만 여러 노드에 붙는 볼륨을 내줍니다. 그래서 골든 디스크는 이 클래스로 만듭니다. 복제 전략 copy 와 스냅샷 클래스가 없다는 사실은 4편에서 「왜 PVC 복제가 아니라 쓰기 층인가」 의 근거가 됩니다.
7. 정리: 세 갈래의 거래
| 컨테이너 디스크 | DataVolume | 골든 + 쓰기 층(4편) | |
|---|---|---|---|
| 루트 크기 | 2.4GiB(실측) | 원하는 크기 | 골든 크기(10Gi·20Gi 메뉴) |
| VM 을 만들 때 하는 일 | 노드에 이미지가 있으면 거의 없음 | 이미지를 새로 가져와 PVC 에 씀 | 빈 쓰기 층 하나 |
| 첫 부팅 | 약 20초(실측) | 최대 195초(실측) | 미실측 |
| VM 마다 생기는 PVC | 없음 | 하나(+가져오는 동안 스크래치 하나) | 없음 |
| 끄면 | 쓰기 층 사라짐 | PVC 남음(VM 을 지우면 함께) | 쓰기 층 사라짐 |
가운데 열의 「VM 마다 이미지를 새로 가져온다」 를 「한 번만 가져와 나눠 쓴다」 로 바꾼 것이 4편의 골든 디스크입니다.
티티 한마디. 접근 모드는 「몇 군데서 붙일 수 있나」 이지 「못 쓰게 막아 준다」 가 아니에요. 바탕 디스크를 지키고 싶으면 RWX 든 ROX 든 꼭 읽기 전용으로 붙이세요. 그리고 디스크가 차면 오류 메시지가 엉뚱한 말을 하니까, 이상하면
df -h부터!
참고
문서는 모두 2026-10-16 에 열람했습니다.
- KubeVirt 사용자 안내서, 디스크와 볼륨(볼륨 원본,
disk.img·UID 107,pvcTolerateLessSpaceUpToPercent, ephemeral, emptyDisk): https://kubevirt.io/user-guide/storage/disks_and_volumes/ - KubeVirt 사용자 안내서, 라이브 마이그레이션(RWX 요구,
LiveMigratable,BlockMigration): https://kubevirt.io/user-guide/compute/live_migration/ - 쿠버네티스 문서, 퍼시스턴트 볼륨(접근 모드, 「접근 모드는 쓰기 보호가 아니다」, 볼륨 모드): https://kubernetes.io/docs/concepts/storage/persistent-volumes/
- CDI v1.66.0, Scratch space: https://github.com/kubevirt/containerized-data-importer/blob/v1.66.0/doc/scratch-space.md
- CDI v1.66.0, StorageProfile: https://github.com/kubevirt/containerized-data-importer/blob/v1.66.0/doc/storageprofile.md
- CDI v1.66.0, DataVolumes(
spec.storage와 파일시스템 여분): https://github.com/kubevirt/containerized-data-importer/blob/v1.66.0/doc/datavolumes.md - TT Lab 코드와 기록:
backend/app/vms.py(DISK_SIZE·SCRATCH_SIZE·DOCKER_SCRATCH_PATHS주석,cloud_init의 스크래치 고르기),backend/app/main.py(VM_READY_SEC),docs/plans/2026-08-29-vm-labs-design.md,docs/plans/2026-10-14-vm-golden-overlay.md