--- kind: CONCEPT slug: what-a-qcow2-file-carries title: qcow2 파일 한 장이 담는 것 — 매핑표와 데이터 클러스터가 같은 파일 안에 있고, 파일 밖을 가리키는 것은 백킹 파일 경로 하나다 topic: lab-environment-build topicName: 실험대 환경 구성 project: virtualization status: 게시 전 basisVersion: QEMU 11.1.1 · libvirt 12.7.0 위의 qcow2 · 게스트는 Debian 12 genericcloud · 크기 계산은 클러스터 64KiB · L2 항목 8B 기준 sourceRevision: 9465582b5d1630eb4ae7c4e078021486919bf6b6 source: - final/document.md#181-qcow2-가-담는-것과-담지-않는-것 - final/document.md#178-이-부의-출처와-범위 --- # qcow2 파일 한 장이 담는 것 — 매핑표와 데이터 클러스터가 같은 파일 안에 있고, 파일 밖을 가리키는 것은 백킹 파일 경로 하나다 qcow2 한 장에는 데이터 클러스터와 그것이 파일의 어느 오프셋에 놓였는지 적은 매핑표가 들어 있다. 파일 밖을 가리키는 것은 백킹 파일 경로 하나다. 실행 중인 프로세스와 안 내려간 dirty page, vCPU·RAM·NIC 를 적어 둔 VM 정의 XML 은 없다. 20GB 이미지가 2GB 로 보이는 것도 압축이 아니라 쓴 블록만 있기 때문이다. ## 관계 - **/dev/vda 뒤에 있는 것 — qcow2 · RAW · Host block device** 게스트의 가상 디스크 아래에 qcow2 와 RAW, 호스트 블록 장치 가운데 무엇이 올 수 있는지를 그 글이 가른다. 이 글은 그중 qcow2 갈래 하나만 이식 쪽으로 이어 적는다. - **Guest 의 write() 가 virtqueue 에 실리기까지 — VFS · Filesystem · Page Cache · Block Layer · virtio-blk** 게스트가 쓴 내용이 언제 디스크에 닿는지를 그 글이 설명한다. 아직 내려가지 않은 페이지 캐시가 qcow2 에 없는 이유가 거기에서 나온다. - **이 disk image 는 qcow2 인가 RAW 인가, 그리고 Guest 가 보는 크기와 Host 가 실제로 쓰는 크기는 얼마나 다른가** 이 글은 두 크기가 갈린다는 것까지만 적었다. 이 호스트의 이미지가 실제로 얼마인지는 그 물음이 확정한다. - **virsh save 의 RAM 덤프 크기와 소요 시간은 할당 메모리에 어떻게 비례하는가** 실행 상태를 파일로 내보내면 RAM 크기만큼 파일이 더 생긴다고만 적었다. 이 호스트에서 실제로 얼마가 되고 얼마나 걸리는지는 재지 않았다. - **WiFi 전용 호스트에서 대용량 qcow2 를 옮기는 데 실제로 몇 시간이 걸리는가** 희소 할당으로 실제 파일이 얼마나 커져 있는지가 그 시간을 정한다. 이 실험대에는 이더넷이 없어 WiFi 로만 옮긴다. ## 본문 ## 파일 한 장 안에 들어 있는 것 qcow2 는 가상 디스크 한 장의 블록을 담는 파일이다. 블록의 내용이 들어가는 데이터 클러스터와, 그 클러스터가 파일의 어느 오프셋에 놓였는지 적은 매핑표가 같은 파일 안에 함께 있다. 매핑표에 적히는 값은 호스트의 물리 주소가 아니라 파일 안의 오프셋이어서, 파일을 다른 디렉터리로 옮기든 다른 호스트로 복사하든 표가 가리키는 곳은 달라지지 않는다. 파일을 통째로 옮기기만 하면 매핑은 그대로 유효하다. 파일 밖을 가리키는 것은 백킹 파일 경로 하나다. qcow2 헤더에 절대경로 문자열로 적혀 있고, 이식할 때 확인할 외부 참조도 그 하나다. 이 실험대의 게스트 디스크가 전부 그 경우다. 세 대를 다 base 이미지 위의 오버레이(`backing_store=`)로 만들었기 때문에, 이식할 때 확인할 그 참조 하나가 세 장 모두에 들어 있다. ## 복사하면 따라가는 것과 따라가지 않는 것 게스트가 이미 디스크에 써 둔 것은 전부 따라간다. 파일시스템도 설치한 패키지도 설정과 DB 파일도 데이터 클러스터로 파일 안에 들어가 있다. 컨테이너 이미지와 apt 캐시처럼 디스크에 쓰인 캐시도, `machine-id` 와 SSH 호스트키도 게스트 파일시스템 위의 파일이다. 내부 스냅샷은 qcow2 가 자기 안에 보관한다. 따라가지 않는 것은 메모리에 있거나 게스트 디스크 밖에 있다. 실행 중인 프로세스는 프로세스 번호와 열린 파일 기술자, 소켓, JVM 힙이 전부 메모리에 있어서 디스크에 흔적이 없고, 페이지 캐시와 아직 디스크로 내려가지 않은 dirty page 도 같다. VM 정의 XML 에는 vCPU 개수와 RAM 크기, NIC(Network Interface Card, 네트워크 인터페이스 카드) 구성, machine type, CPU 모델이 적혀 있다. 이 파일은 게스트 디스크 밖에 있는 호스트 쪽 구성이라 이미지를 정확히 복사해도 함께 오지 않는다. UEFI NVRAM 과 백킹 파일, 그 밖의 호스트 쪽 구성도 같은 이유로 따라가지 않는다. | 따라가는 것 | 따라가지 않는 것 | |---|---| | 파일시스템 전체, 설치 패키지, 설정, DB 파일 | 실행 중인 프로세스 — PID·FD·소켓·JVM 힙 | | 디스크에 쓰인 캐시(컨테이너 이미지, apt 캐시) | 페이지 캐시와 안 내려간 dirty page | | `machine-id`, SSH 호스트키 | VM 정의 XML — vCPU·RAM·NIC·machine type·CPU 모델 | | 내부 스냅샷 | UEFI NVRAM, 백킹 파일, 호스트 쪽 구성 | ## 희소 할당이지 압축이 아니다 20GB 로 만든 이미지의 파일 크기가 2GB 로 보이는 것은 압축 때문이 아니라 쓴 블록만 파일에 존재하기 때문이다. 게스트가 계속 쓰면 파일도 그만큼 커지고, 1TB 를 채우면 1TB 파일이 된다. 이 실험대에서 그 성질이 눈에 보인 곳은 만드는 쪽이다. 10GB 로 만든 엣지 게스트의 생성 출력에서 `Allocating` 이 `00:00` 에 끝났다 — 오버레이라 10GB 를 실제로 쓰지 않는다. 매핑표가 차지하는 몫은 작다. 클러스터 64KiB 에 L2 항목 8B 를 기준으로 하면 메타데이터 오버헤드는 0.02% 미만이고, 1TiB 당 약 160MiB 다. 커지기만 하고 줄지는 않는다. 게스트 안에서 파일을 지워도 클러스터가 이미 할당된 상태이기 때문에 qcow2 파일 크기는 그대로다. 줄이려면 게스트에서 `fstrim` 을 돌리거나(디스크에 `discard='unmap'` 이 있어야 한다) 호스트에서 `qemu-img convert` 로 다시 쓴다. 여기 적은 크기는 전부 qcow2 형식의 성질이고 이 호스트에서 잰 값이 아니다. 20GB 에 2GB 도, 1TiB 당 160MiB 도 그렇다. 이 실험대의 게스트 세 대가 실제로 얼마를 쓰고 있는지는 아직 확인하지 않았다. ## 실행 상태는 파일에 없다 실행 상태까지 옮기려면 qcow2 복사로는 안 되고, 방법이 둘이다. `virsh save` 는 게스트를 멈추고 메모리 내용을 파일로 내보낸다. 옮기는 동안 게스트는 내려가 있고, 할당한 RAM 만큼 저장 공간이 더 필요하다. 파일을 복사한 뒤 `restore` 로 다시 살린다. `virsh migrate --live --copy-storage-all` 은 게스트를 켠 채로 옮긴다. 대신 두 호스트가 동시에 떠서 libvirt 끼리 붙어 있어야 하고, 게스트가 보던 CPU 모델이 옮겨 갈 호스트에서도 성립해야 한다. | 어떻게 옮기나 | 무엇을 요구하나 | |---|---| | `virsh save` 로 내보내고 복사한 뒤 `restore` | VM 이 멈추고, RAM 크기만큼 파일이 더 생긴다 | | `virsh migrate --live --copy-storage-all` | 두 호스트의 libvirt 가 붙어야 하고 CPU 모델이 호환돼야 한다 | 두 방법 다 이 호스트에서 돌려 보지 않았다. `virsh save` 의 덤프 크기와 걸리는 시간, WiFi 로 대용량 qcow2 를 옮기는 시간은 둘 다 재지 않았고 각각 열린 질문으로 걸어 두었다. ## 이 글이 다루지 않는 것 매핑표가 파일 안에서 어떤 구조로 나뉘는지는 이 글에서 다루지 않는다. 이식에 필요한 것은 표에 적히는 값이 파일 안의 오프셋이라는 데까지다. 그 아래를 미룬 것은 이 기록이 아니라 원본 문서다 — 제4부가 스토리지 경로를 세우면서 「qcow2 내부 L1/L2 table, blk-mq tag allocator, NVMe submission/completion queue 같은 세부 구현은 필요 시 별도 문서에서 다룬다」고 적었고, 이 글은 그 경계를 그대로 물려받았다. 온프렘 이미지를 클라우드로 올리는 절차도 이 글 밖이다. 원본 문서가 그 부분을 코드 관측이 아닌 외부 지식이라고 스스로 표시해 두었고 이 실험대에서 한 번도 해 보지 않아서, 원본 문서에만 두고 이 기록으로 옮기지 않았다.