--- kind: CONCEPT slug: a-cloud-image-is-an-installed-disk-and-cloud-init-fills-the-blanks title: 설치를 안 했는데 왜 뜨는가 — 클라우드 이미지는 설치가 끝난 디스크이고 cloud-init 이 빈칸을 채운다 topic: lab-environment-build topicName: 실험대 환경 구성 project: virtualization status: 게시 전 basisVersion: Debian 12 genericcloud 위의 cloud-init 22.4.2 · NoCloud 데이터소스 · 호스트는 QEMU 11.1.1 · libvirt 12.7.0 sourceRevision: 9465582b5d1630eb4ae7c4e078021486919bf6b6 source: - final/document.md#229-왜-os를-설치하지-않아도-vm이-뜨는가 - final/document.md#236-클라우드-이미지와-cloud-init - final/document.md#235-multipass-virt-install-virsh-무엇이-다른가 - final/document.md#214-전체-구조-한눈에-보기 - final/document.md#215-vm-한-대의-디스크-구성 - final/document.md#216-설정-파일이-게스트에-도달하는-경로 - final/document.md#217-부팅할-때-일어나는-일 --- # 설치를 안 했는데 왜 뜨는가 — 클라우드 이미지는 설치가 끝난 디스크이고 cloud-init 이 빈칸을 채운다 클라우드 이미지는 배포자가 설치를 한 번 끝내 놓은 디스크 파일이라 게스트를 만들 때 설치 단계가 없다. 대신 hostname 과 SSH 호스트키 같은 고유값을 비워 둔 채 배포하고, cloud-init 이 첫 부팅에 그 빈칸을 채운다. ## 관계 - **cloud-init 시드로 게스트 세 대를 만들고 SSH 가 키로 붙게 한다** 그 절차가 「base 이미지를 받아 오버레이로 게스트 셋을 만든다」로 시작한다. 왜 받기만 하고 설치하지 않는지를 이 글이 댄다. - **cloud-init 이 안 도는 원인은 넷인데 증상은 「SSH 가 안 붙는다」 하나였다** 그 원인 넷이 전부 「시드가 안 읽혔다」로 모인다. 시드가 무엇이고 어느 단계에서 읽히는지가 여기 있다. - **qcow2 파일 안 — 매핑표와 클러스터, 그리고 항목이 0 이면 바닥에 다시 묻는다** 받은 파일이 어떻게 생겼길래 3 GiB 짜리가 335MiB 인지를 그 글이 연다. - **qcow2 파일 한 장이 담는 것 — 매핑표와 데이터 클러스터가 같은 파일 안에 있고, 파일 밖을 가리키는 것은 백킹 파일 경로 하나다** 받은 바닥 이미지를 다른 호스트로 들고 갈 때 무엇이 따라가는지를 그 글이 가른다. - **가이드대로 다시 쳐서 이 실험대가 같은 상태로 서는가** 게스트를 지우고 다시 만드는 비용이 낮다는 것이 이 선택의 근거인데, 그 비용을 실제로 재는 것은 그 물음이다. ## 본문 ## 설치 프로그램이 만드는 것은 결국 파일 하나의 내용이다 VM 의 디스크는 호스트의 파일 하나다. `kc-lab-1.qcow2` 라는 파일이 게스트에게는 20GB 하드디스크로 보이고, 게스트는 그것이 파일인 줄 모르는데 QEMU 가 디스크인 척해 주기 때문이다. 그러면 「OS 를 설치한다」가 무슨 작업인지 풀어 본다. ```text label="설치 프로그램이 빈 디스크에 하는 일" 빈 디스크 │ 설치 프로그램이 수행하는 일 ├─ 파티션 테이블 작성 ├─ 파일시스템 생성 (ext4, vfat …) ├─ 패키지 수천 개를 풀어 배치 ├─ 부트로더 기록 └─ 초기 설정 작성 ▼ "부팅 가능한 특정 바이트 배열" 상태의 디스크 ``` 설치 과정은 수단이고 목적은 마지막 줄의 상태이며, 그 상태는 파일 하나의 내용으로 남는다. Debian 과 Ubuntu 는 자기 빌드 서버에서 이 설치를 한 번 수행하고 완성된 디스크를 qcow2 파일로 떠서 공개하고, 우리는 그 파일을 내려받아 붙인다. 소스를 직접 컴파일하는 대신 이미 빌드된 바이너리를 받아 쓰는 것과 같아서, 결과물은 같고 시간만 아낀다. ## 그대로 복제하면 식별자가 겹치므로 일부러 비워 둔다 디스크가 바이트 단위로 같으면 안에 적힌 식별자도 같아진다. | 값이 겹치면 | 무엇이 깨지나 | |---|---| | machine-id | systemd/DHCP가 두 기계를 같은 기계로 오인 | | SSH 호스트 키 | 두 서버가 같은 신원을 주장 → MITM 탐지 무력화 | | 파일시스템 UUID | `/etc/fstab`이 엉뚱한 디스크를 마운트 | | hostname | 로그·클러스터에서 노드 구분 불가 | 그래서 클라우드 이미지는 이 값들을 비워 둔 채 배포된다. | 배포본이 비워 두는 것 | 첫 부팅 전 상태 | |---|---| | hostname | 미설정 (`localhost`) | | 사용자 계정 | 없음 | | 비밀번호 | 없음 | | SSH 호스트 키 | 없음 — 첫 부팅에 새로 생성 | | machine-id | 비어 있음 | cloud-init 이 이 빈칸을 첫 부팅에 채우는 장치다. `user-data` 라는 YAML 을 읽어 계정을 만들고 SSH 키를 등록하고 패키지를 깔고 임의의 스크립트를 실행한다. ```text label="설치와 개인화를 누가 언제 하나" 전통적 설치 : [설치 + 개인화]를 부팅 전에 대화형으로 수행 클라우드 : [설치]는 배포자가 미리 완료 [개인화]만 첫 부팅에 cloud-init 이 자동 수행 ``` ## 격리는 실행 시점에 KVM/QEMU 가 만든다 「설치를 안 했으니 격리가 약한가」는 오해다. 격리는 실행 시점에 KVM/QEMU 가 만들지 설치 과정이 만들지 않는다. 게스트는 자기 커널로 부팅하고 자기 메모리 공간에서 돌며, 디스크 내용을 어떻게 얻었는지와 무관하다. ## 이 실험대가 cloud-init 을 고른 까닭은 재생성 비용이다 게스트에 계정과 키를 심는 방법은 셋이다. | 계정과 키를 심는 방법 | 무엇이 드나 | 다시 만들 때 | |---|---|---| | ISO로 정식 설치 | VM마다 대화형 설치 | 매번 처음부터 반복 | | 이미지를 미리 개조 (`virt-customize`, `guestfish`) | libguestfs 설치 + 이미지 마운트 | 개조본을 따로 관리해야 함 | | cloud-init | YAML 한 장 | 명령 한 줄 | 이 실험대는 `virsh destroy` 와 오버레이 삭제로 게스트를 반복해서 지우고 다시 만드는 것이 실험 그 자체다. 재생성 비용이 낮아야 실험이 굴러간다. 두 노드가 바이트 단위로 같은 초기 상태로 만들어져야 한다는 조건도 붙는다. 손으로 설치하면 미묘하게 달라지고 그 차이가 실험 결과를 오염시킨다. 이미지 종류도 그 축에서 고른다. Debian 은 같은 판을 여러 변종으로 배포한다. | Debian 변종 | 어디에 쓰나 | |---|---| | `genericcloud` | 가상화 환경 전용. virtio 드라이버만 담아 가볍다 → KVM에는 이걸 | | `generic` | 베어메탈 포함. 드라이버가 많아 더 크다 | | `nocloud` | cloud-init 없이 기본 계정이 박혀 있는 변종 | `genericcloud` 가 가벼운 까닭은 물리 하드웨어 드라이버를 뺐다는 데 있고, 시드를 SATA CD-ROM 으로 붙이면 게스트가 그 장치를 보지 못하는 함정도 같은 이유로 생긴다. `virt-install --cloud-init` 은 시드를 ``, 즉 SATA CD-ROM 으로 붙인다. 그래서 이 조합에서는 게스트가 시드를 아예 장치로 보지 못한다. §237 이 성공 판정을 `virsh domblklist` 의 장치 이름으로 잡아 둔 까닭이 여기 있다 — 시드가 `vdb` 로 보여야 하고, `sda` 로 보이면 게스트가 읽지 못한다. ## 게스트에게 디스크는 두 장이고, 시드는 OS 가 아니다 가장 자주 하는 오해는 시드 ISO 를 OS 이미지로 아는 것이다. 시드는 설정 데이터만 담은 370KB 짜리 별도 디스크다. | 게스트가 보는 디스크 | 크기 | 무엇이 들었나 | |---|---|---| | `vda` | 20G | ext4 루트. 여기서 부팅한다 | | `vdb` | 370K | `LABEL=CIDATA` 인 iso9660. 읽기 전용이고 마운트되지 않는다 | `vda` 는 `base.qcow2` 위의 오버레이라 바닥 한 벌을 두 게스트가 공유하고 각자 변경분만 쌓는다. `vdb` 는 원본 YAML 을 구워 만든 ISO 를 풀에 올린 것이다. ```text label="같은 설정이 존재하는 세 곳" kc-lab-1.yaml ──①──▶ seed-kc-lab-1.iso ──②③──▶ /var/lib/libvirt/images/seed-kc-lab-1.iso ``` ①은 `xorrisofs` 가 굽는 단계이고 여기서 파일 이름이 ISO 안의 `/user-data` 와 `/meta-data` 로 바뀌는데, 두 이름이 정확해야 인식된다. ②와 ③은 `virsh vol-create-as` 로 자리를 잡고 `virsh vol-upload` 로 내용을 붓는 단계다. 홈 디렉터리가 `700` 이라 qemu 가 못 읽어서 풀에 둔다. 같은 내용이 세 곳에 있어서 원본만 고치면 VM 에 반영되지 않는다. 셋을 한 번에 맞추는 것이 `deploy/lab/scripts/rebuild-seed.sh` 다. ## 부팅 다섯 단계 가운데 3번이 실패하면 조용히 끝난다 ```text label="첫 부팅에 일어나는 다섯 단계" 1. QEMU 가 vda 에서 부팅 → Debian 커널 시작 2. cloud-init 서비스 기동 → 모든 블록 장치를 스캔 3. vdb 에서 LABEL=CIDATA 발견 → 잠깐 마운트 4. user-data / meta-data 읽기 → 사용자·hostname·sudo·패키지 적용 5. 언마운트 → SSH 로그인 가능 ``` cloud-init 은 `cidata` 레이블을 가진 블록 장치를 찾지 못하면 데이터소스 없이 종료하기 때문에, 3번이 실패하면 hostname 이 `localhost` 로 남고 사용자가 생성되지 않는다. 오류 메시지는 어디에도 남지 않는다. `virsh screenshot` 으로 로그인 프롬프트만 봐도 즉시 판정할 수 있다. `user-data` 파일은 `#cloud-config` 로 시작해야 한다. 이 첫 줄이 없으면 cloud-init 이 YAML 로 인식하지 못하고 무시하며, 증상은 「부팅은 됐는데 계정이 없다」로 나타난다. §236 은 비상 접근 수단을 남기라는 항목 하나를 「실제로 겪은 교훈」이라고 따로 적어 두었다. `ssh_pwauth: false` 에 키 인증만 걸어 둔 상태로 3번이 실패하면 사용자가 생성되지 않아 키도 비밀번호도 없고, 콘솔에 붙어도 로그인할 수 없다. 실패 원인을 적어 둔 `/var/log/cloud-init.log` 를 읽을 방법이 그래서 사라지고, VM 을 지우고 다시 만드는 것 말고 남는 선택지가 없어진다. 콘솔 로그인용 비밀번호를 하나 넣어 두면 그 골목을 피하는데, 콘솔 로그인은 sshd 를 거치지 않으므로 `ssh_pwauth: false` 는 그대로 둔다. ## multipass 가 감춰 주던 것이 이 단계들이다 multipass 와 virt-install 과 virsh 는 서로 배포판이 다른 도구가 아니라 계층이 다른 도구다. multipass 도 리눅스에서는 QEMU/KVM 위에서 돌고, `multipass set local.driver=libvirt` 로 libvirt 를 쓰게 할 수도 있다. | multipass 가 자동으로 | 이번에 우리가 한 것 | |---|---| | Ubuntu 클라우드 이미지 자동 다운로드·캐시 | `curl`로 base.qcow2 내려받기 | | cloud-init user-data 자동 생성 | `kc-lab-1.yaml` 작성 | | 시드 ISO 생성·연결 | `xorrisofs` + virtio 디스크 연결 | | SSH 키 자동 생성·주입 | `ssh-keygen` + `ssh_authorized_keys` | | 네트워크·DHCP 구성 | `virbr0` + DHCP 예약 | | `multipass shell` 로 즉시 접속 | `~/.ssh/config` 별칭 작성 | multipass 를 쓰지 않은 까닭은 배포판이 아니라 범위에 있다. multipass 는 Ubuntu 이미지만 공식 지원해서 Debian 게스트를 띄울 수 없다. 그리고 이 실험대는 `virsh destroy` 로 노드를 죽이고 NetworkPolicy 로 포트를 막고 스냅샷으로 되돌리는 저수준 제어가 실험의 본체라 관리 계층이 필요했다. ## 이 설명이 걸려 있는 판올림 게스트에 깔린 cloud-init 은 22.4.2 이고 데이터소스는 NoCloud 다. 판올림이 바뀌면 같은 YAML 에 대한 스키마 검사기의 판정이 달라진다. 크기 값은 두 날짜에서 왔다. 시드 ISO 370KB 와 게스트가 보는 `vda` 20G · `vdb` 370K 는 2026-09-03 실측이고, 같은 절이 `base.qcow2` 를 333M 로 적는다. 2026-09-10 에 다시 잰 `disk size` 는 335MiB 다. 파일 안이 어떻게 생겼길래 3 GiB 가 335MiB 로 앉는지는 이 글이 다루지 않는다. 시드를 굽는 세 명령의 옵션별 뜻도 마찬가지로 절차 쪽에 있다.