기록 84편을 계약 에이전트로 다시 썼다. 기존 71편(kss 25 · virt 46)과, 계약에만 있고 안 쓰여 있던 새 글감 13편이다. 원장 84개를 열어 단계마다 스킬 영수증과 관문 종료 코드를 적었고 verify-pipeline-run.py 가 error 0 으로 닫는다. SSOT 결함 둘을 고쳤다. - kss 의 `약 58일` 이 반입 중 `약 59일` 로 바뀌어 있었다. 원 증거 파일이 「남은 일수: 88일 … 실제 갱신까지 약 58일」로 산수를 직접 적는다. D-4a 쪽 `약 59일` 은 강제 갱신 뒤(`VALID: 89 days`)라 맞는 값이라 그대로 뒀다. - virt §198 의 `11.6GB` 는 §178 의 원 측정 `Mem: 11648`(MiB)과 어긋나는데 원 가이드의 표기 그대로라 고치지 않고 쓰이는 자리에 대조를 적었다. 기록의 수치 오류 셋을 고쳤다 — CASE 요약의 「게스트 셋에 8240MB」(5120+3120 은 둘이다), k3s 편이 같은 것을 여섯·일곱·여덟로 세던 것, no-docker 편의 「셋을 더 든다」(§281 의 표는 네 행이고 디스크 행이 빠져 있었다). 계약을 셋 고쳤다. - kss 의 sourceRepository 리비전이 cdac9b8 이었는데 그 커밋에는 docs/guides/** 28개가 아예 없다. 9465582b 로 바꾸고, 반입한 바이트가 어느 커밋과도 같지 않다는 것을 측정값과 함께 적었다 — 반입은 커밋이 아니라 그 시점의 작업 트리에서 떠 온 것이다(kss 297/306 · virt 12/14 가 작업 트리와 같고, 200 커밋을 거슬러 전수 대조했을 때 가장 가까운 커밋도 28개가 어긋났다). - virt 계약이 「2026-09-11 재배분」이라고 적는데 SSOT 는 재배분 날짜를 적지 않고 재배분 뒤 값은 이미 2026-09-10 측정에 찍혀 있다. - kss 후보 대장이 지나친 절 아홉에 처분을 적었다(warn 9 → 0). 새 글감은 0건이고 넷은 앵커가 h3 슬러그의 접두가 아니라 중간 토막이라 검사기가 못 본 것이었다. style_profile.mjs 의 결함 둘을 고쳤다 — frontmatter 가 문장으로 세어져 (실측 398자짜리 「문장」 하나) 평균 길이를 기준 안으로 밀어 올리고 있었고, engPerSent 의 분자는 목록을 포함한 글에서, 분모는 목록을 걷어낸 글에서 세고 있었다(Question 기록에서 11.94 → 3.86). verify-pipeline.py 전 항목 PASS · error 0 · unittest 334건 OK. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
94 lines
8.6 KiB
Markdown
94 lines
8.6 KiB
Markdown
---
|
|
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 로만 옮긴다.
|
|
|
|
## 본문
|
|
|
|
<!-- body:start -->
|
|
|
|
## 파일 한 장 안에 들어 있는 것
|
|
|
|
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 같은 세부 구현은 필요 시 별도 문서에서 다룬다」고 적었고, 이 글은 그 경계를 그대로 물려받았다.
|
|
|
|
온프렘 이미지를 클라우드로 올리는 절차도 이 글 밖이다. 원본 문서가 그 부분을 코드 관측이 아닌 외부 지식이라고 스스로 표시해 두었고 이 실험대에서 한 번도 해 보지 않아서, 원본 문서에만 두고 이 기록으로 옮기지 않았다.
|
|
|
|
<!-- body:end -->
|