기록 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>
176 lines
12 KiB
Markdown
176 lines
12 KiB
Markdown
---
|
|
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 파일 한 장이 담는 것 — 매핑표와 데이터 클러스터가 같은 파일 안에 있고, 파일 밖을 가리키는 것은 백킹 파일 경로 하나다**
|
|
받은 바닥 이미지를 다른 호스트로 들고 갈 때 무엇이 따라가는지를 그 글이 가른다.
|
|
- **가이드대로 다시 쳐서 이 실험대가 같은 상태로 서는가**
|
|
게스트를 지우고 다시 만드는 비용이 낮다는 것이 이 선택의 근거인데, 그 비용을 실제로 재는 것은 그 물음이다.
|
|
|
|
## 본문
|
|
|
|
<!-- body:start -->
|
|
|
|
## 설치 프로그램이 만드는 것은 결국 파일 하나의 내용이다
|
|
|
|
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` 은 시드를 `<target dev='sda' bus='sata'/>`, 즉 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 로 앉는지는 이 글이 다루지 않는다. 시드를 굽는 세 명령의 옵션별 뜻도 마찬가지로 절차 쪽에 있다.
|
|
|
|
<!-- body:end -->
|