Files
document-haness/docs/virtualization/tech-log-studio/build-completion-judgment/case/case-cloud-init-failures-all-look-like-ssh-refused.md
T
DongHyeonkaandClaude Opus 5 2109f726fe feat(pipeline): keycloak-session-store 25편·virtualization 59편을 S3→S5→S6 으로 돌린다
기록 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>
2026-09-17 11:01:55 +09:00

197 lines
13 KiB
Markdown

---
kind: CASE
slug: cloud-init-failures-all-look-like-ssh-refused
title: cloud-init 이 안 도는 원인은 넷인데 증상은 「SSH 가 안 붙는다」 하나였다
topic: build-completion-judgment
topicName: 끝났다는 판정
project: virtualization
status: 게시 전
sourceRevision: 9465582b5d1630eb4ae7c4e078021486919bf6b6
source:
- final/document.md#187-단계-01-게스트-세-대
- final/document.md#186-단계-00-lab-host-가상화-준비
---
# cloud-init 이 안 도는 원인은 넷인데 증상은 「SSH 가 안 붙는다」 하나였다
SSH 가 안 붙는 원인은 넷이고 넷 다 게스트 밖에서 정해진다. 그래서 SSH 설정을 고치기 전에 호스트명 한 낱말을 본다. 시드를 붙인 방식, YAML 파싱, vol-upload 누락, 가상 네트워크 autostart 가 그 넷이고, cloud-init 은 어느 쪽이든 오류를 남기지 않는다.
## 관계
- **빈 토큰이 조용히 흘러갔다 — 설치 출력은 성공이었고 agent 만 5초마다 다시 죽었다**
바로 다음 단계에서 벌어진 같은 모양의 실패이고, 거기서도 성공한 출력이 실패를 덮었다.
- **도구가 낸 출력은 대상의 상태가 아니다 — 그 명령이 무엇을 세는지부터 가른다**
스키마 검사기의 통과와 거부를 게스트의 상태로 읽으면 양쪽 방향으로 다 틀린다.
- **가이드대로 다시 쳐서 이 실험대가 같은 상태로 서는가**
게스트를 만드는 명령은 다시 쳐 본 적이 없어 여기 적은 원인 넷도 그 물음에 걸린다.
- **qcow2 파일 한 장이 담는 것 — 매핑표와 데이터 클러스터가 같은 파일 안에 있고, 파일 밖을 가리키는 것은 백킹 파일 경로 하나다**
게스트 디스크가 base 이미지 위의 오버레이라 `virt-install` 이 10GB 를 즉시 할당한 것으로 찍힌다.
- **단계별 구축 문서는 작성 시점이 아니라 실행 순서로 검증한다**
시드를 만드는 순서와 DHCP 예약을 넣는 순서가 결과를 가르는 단계라 그 규칙이 받는다.
## 문제
게스트 세 대가 running 인데 lab host 에서 SSH 가 키로 붙지 않는다.
virsh list --all : kc-lab-1 · kc-lab-2 · kc-lab-edge 모두 running
ssh donghyeon@192.168.122.11 : Permission denied (publickey)
게스트의 hostname : localhost
cloud-init 이 남긴 오류 : x
cloud-init 이 돌지 않으면 사용자도 SSH 키도 들어가지 않는다. 그런데 cloud-init 은 데이터소스를 못 찾아도 YAML 파싱에 실패해도 오류를 남기지 않아서, 원인 넷이 전부 SSH 하나로만 나타난다.
## 결론
증상 하나 뒤에 원인이 넷이고 넷 다 게스트 밖에서 정해진다.
시드를 --cloud-init 으로 붙였다 : SATA CD-ROM 으로 붙는데 Debian genericcloud 이미지에는 그 드라이버가 없다. 디스크로, bus=virtio 로 붙인다
YAML 파싱에 실패했다 : cloud-init 이 아무 오류를 남기지 않는다
vol-upload 를 빠뜨렸다 : 목록에는 이름이 보이는데 안이 0 으로 채워져 있어 cidata 라벨을 못 찾는다
default 네트워크의 autostart 가 no 다 : 지금은 되고 호스트를 재부팅한 다음에야 세 게스트의 SSH 가 한꺼번에 실패한다
판정 : SSH 설정을 고치기 전에 호스트명 한 낱말을 본다. ssh kc-lab-edge 'hostname' 이 kc-lab-edge 를 내면 시드가 읽혔고, 시드가 읽혔으면 같은 파일에 있던 SSH 키도 들어갔다
못 들어가면 : virsh screenshot 으로 화면을 떠서 로그인 프롬프트 앞의 호스트명을 읽는다
키가 안 들어갔을 때의 탈출구 : cloud-init 의 plain_text_passwd 로 콘솔 로그인
반대 방향도 하나 있다. 게스트의 cloud-init 22.4.2 스키마 검사기는 sudo 를 리스트로 쓴 것을 거부한다. 그런데 그 표기로도 부팅은 되고, kc-lab-1 과 kc-lab-2 에서 그 상태로 NOPASSWD sudo 가 돌고 있다. 검사가 통과해도 안 도는 쪽이 넷이고 검사에 걸려도 도는 쪽이 하나라, 어느 방향이든 검사 결과를 게스트의 상태로 읽으면 틀린다.
## 검증 환경
호스트 : test-server, Arch Linux
RAM : 11,648MiB
QEMU : 11.1.1
libvirt : 12.7.0
base 이미지 : Debian 12 genericcloud amd64
게스트 OS : Debian GNU/Linux 12 (bookworm)
게스트 : kc-lab-edge 192.168.122.10 · kc-lab-1 192.168.122.11 · kc-lab-2 192.168.122.12
시드 : seed-<이름>.iso, CIDATA 라벨, bus=virtio 로 붙임
게스트의 cloud-init : 22.4.2
cloud-init 이 깐 패키지 : curl · nftables
running 에서 done 까지 : 약 50초
## 재현 조건
1. base 이미지를 받고 게스트마다 cloud-init user-data 를 쓴다.
2. 시드 iso 를 만들고 virsh vol-create-as 로 볼륨을 만든 뒤 virsh vol-upload 로 채운다.
3. DHCP 예약을 먼저 넣고 virt-install 로 게스트를 만든다. 시드는 bus=virtio 로 붙인다.
4. virsh list --all 로 세 게스트가 running 인지 본다.
5. lab host 에서 ssh kc-lab-edge 'hostname; ip -4 -br addr show enp1s0; cloud-init status' 를 친다.
6. 호스트명이 게스트 이름이고 cloud-init status 가 done 이면 통과다. localhost 가 나오면 2번과 3번을 다시 본다.
7. SSH 가 안 붙으면 virsh screenshot kc-lab-1 /tmp/kc1.ppm 으로 화면을 떠서 로그인 프롬프트 앞의 호스트명을 읽는다.
## 본문
<!-- body:start -->
## 시드가 게스트에 들어가는 길
cloud-init 이 게스트 안에서 사용자를 만들고 SSH 키를 넣으려면 먼저 데이터소스를 찾아야 한다. 이 실험대는 그것을 `cidata` 라벨이 붙은 볼륨으로 준다. 그 안에는 `user-data``meta-data` 라는 정확한 이름의 파일 둘이 들어 있다.
```bash
printf 'instance-id: kc-lab-1-%s\nlocal-hostname: kc-lab-1\n' "$(date +%s)" > meta-kc-lab-1
xorrisofs -quiet -output seed-kc-lab-1.iso -volid CIDATA -joliet -rock \
-graft-points /user-data=kc-lab-1.yaml /meta-data=meta-kc-lab-1
virsh vol-create-as default seed-kc-lab-1.iso "$(stat -c%s seed-kc-lab-1.iso)" --format raw
virsh vol-upload --pool default seed-kc-lab-1.iso seed-kc-lab-1.iso
```
볼륨을 만드는 것과 채우는 것이 다른 명령이다. `vol-create-as` 는 빈 볼륨을 만들 뿐이고 내용은 `vol-upload` 가 넣는다. 뒤엣것을 빠뜨리면 목록에는 이름이 보이는데 안이 0 으로 채워져 있고, cloud-init 은 `cidata` 라벨을 못 찾은 채 끝난다.
`instance-id` 에 타임스탬프를 넣는 것은 cloud-init 이 인스턴스마다 한 번만 초기화 모듈을 돌리기 때문이다. id 가 같으면 user-data 를 고쳐도 반영되지 않는다.
## 증상 하나에 원인 넷
| 무엇이 어긋났나 | 게스트에서 무엇으로 나타나나 |
|---|---|
| 시드를 `--cloud-init` 으로 붙였다 | SSH 가 `Permission denied (publickey)` · 호스트명이 `localhost` |
| YAML 파싱에 실패했다 | 같다 |
| `vol-upload` 를 빠뜨렸다 | 같다 |
| `default` 네트워크의 autostart 가 `no` 다 | 호스트를 재부팅한 뒤 세 게스트가 한꺼번에 |
넷 가운데 이 실험대에서 관측으로 적힌 것은 시드를 붙인 방식이다. `--cloud-init` 옵션은 시드를 SATA CD-ROM 으로 붙이는데, Debian `genericcloud` 이미지는 크기를 줄이려고 물리 하드웨어 드라이버를 뺐다. AHCI 장치가 보이지 않아 cloud-init 이 데이터소스를 못 찾고 조용히 끝난다. 그래서 시드를 디스크로, `bus=virtio` 로 붙인다.
```bash
virt-install --name kc-lab-1 --memory 5120 --vcpus 2 \
--disk size=20,backing_store=/var/lib/libvirt/images/base.qcow2 \
--disk vol=default/seed-kc-lab-1.iso,device=disk,bus=virtio,readonly=on \
--network network=default,mac=52:54:00:aa:bb:11 \
--import --os-variant debian12 --noautoconsole
```
마지막 원인은 한 단계 앞에서 만들어진다. lab host 를 준비할 때 `default` 네트워크의 autostart 를 켜지 않으면 지금은 세 게스트가 다 붙고, 호스트를 재부팅한 다음에야 SSH 가 한꺼번에 실패한다. 그때 원인을 게스트 안에서 찾게 된다.
## 호스트명 한 낱말이 시드와 SSH 를 가른다
게스트에 SSH 가 붙는다면 확인은 한 줄로 끝난다.
```bash
ssh kc-lab-edge 'hostname; ip -4 -br addr show enp1s0; cloud-init status'
```
```
kc-lab-edge
enp1s0 UP 192.168.122.10/24 metric 100
status: done
```
호스트명이 바뀌었다는 것은 시드가 읽혔다는 뜻이고, 시드가 읽혔으면 같은 파일에 있던 SSH 키도 들어갔다. `localhost` 가 나오면 SSH 설정을 고치지 말고 시드부터 의심한다. `cloud-init status``running` 이면 아직 패키지를 받는 중이고, 이 실험대에서는 `done` 까지 약 50초 걸렸다.
게스트에 못 들어가면 화면을 직접 뜬다.
```bash
virsh screenshot kc-lab-1 /tmp/kc1.ppm # 확장자와 무관하게 PNG 로 저장된다
```
로그인 프롬프트 앞의 호스트명 한 낱말만 읽는다. `localhost login:` 이면 cloud-init 이 아예 안 돌았으므로 SSH 쪽은 볼 것이 없다. `kc-lab-1 login:` 이면 cloud-init 은 돌았고 그 안의 사용자·키 단계에서 틀린 것이라 콘솔로 들어가 로그를 본다. 콘솔 로그인에 쓰는 비밀번호가 cloud-init 의 `plain_text_passwd` 이고, 키가 안 들어갔을 때 게스트로 들어가는 길이 이것 하나다.
순서를 이렇게 정한 까닭이 여기에 있다. 화면 한 장과 콘솔에서 보는 로그 두 줄이 「SSH 가 안 되는 이유」를 절반으로 줄인다. 호스트명이 그 절반을 가르므로, 어느 쪽 절반을 볼지가 정해지기 전에는 SSH 설정을 열 이유가 없다.
## 검사가 거부해도 부팅은 된다
게스트의 cloud-init `22.4.2` 스키마 검사기는 `sudo` 를 리스트로 쓴 것을 거부한다.
```yaml
sudo: ['ALL=(ALL) NOPASSWD:ALL'] # 거부된다
sudo: "ALL=(ALL) NOPASSWD:ALL" # 통과한다
```
```
Error: Cloud config schema errors: users.0: {'name': 'donghyeon', 'groups':
['sudo'], 'shell': '/bin/bash', ...} is not valid under any of the given schemas
```
어느 키가 문제인지는 알려 주지 않는다. `users.0` 전체를 통째로 찍고 어느 스키마에도 안 맞는다고만 한다. 그리고 리스트 표기로도 부팅은 된다 — `kc-lab-1``kc-lab-2` 가 그 표기로 만들어졌고 NOPASSWD sudo 가 멀쩡히 돌고 있다.
`NOPASSWD:ALL` 을 넣은 것은 k3s 설치와 장애 주입이 비대화식으로 돌아야 해서다. 비밀번호를 물으면 원격 실행이 거기서 멈춘다. 검사기가 거부한 줄이 바로 그 줄이다.
검사가 통과해도 cloud-init 이 안 도는 경로가 넷이고, 검사가 거부해도 도는 경로가 하나다.
## 시드를 만들기 전에 세 줄로 거른다
```bash
grep -c '__' kc-lab-1.yaml # 0 이어야 한다
grep -c 'ssh-ed25519\|ssh-rsa' kc-lab-1.yaml # 2 여야 한다
python3 -c 'import yaml,sys; yaml.safe_load(open("kc-lab-1.yaml")); print("YAML OK")'
```
`0``2``YAML OK` 셋이 맞아야 시드를 만든다. 셋이 맞아도 cloud-config 로 유효하지는 않다 — 키 이름을 `users` 대신 `user` 로 친 오타는 이 세 줄을 그냥 통과한다. 그래서 게스트가 한 대라도 떠 있으면 cloud-init 자신의 스키마 검사기를 쓴다.
```bash
ssh donghyeon@192.168.122.11 'umask 077; cat > ~/kc-lab-2.yaml' < kc-lab-2.yaml
ssh donghyeon@192.168.122.11 'cloud-init schema -c ~/kc-lab-2.yaml; rm -f ~/kc-lab-2.yaml'
```
이 파일에는 콘솔 비밀번호가 평문으로 들어 있어 `/tmp` 가 아니라 자기 홈에 `600` 으로 두고, 검사가 끝나면 바로 지운다.
## 확인하지 못한 것
넷 가운데 SSOT 가 관측으로 표시한 것은 시드를 SATA 로 붙였을 때 AHCI 장치가 보이지 않는 것과 스키마 검사기의 거부 문구뿐이다. YAML 파싱 실패와 `vol-upload` 누락과 네트워크 autostart 셋은 가이드가 막히면 표에 적어 둔 항목이라, 이 실험대에서 실제로 그 증상을 본 것인지 예상해 적은 것인지 SSOT 가 가르지 않았다. 이 글에서 그 셋은 그렇게 되는 구조까지이고 그렇게 됐다가 아니다.
명령 출력은 원문으로 남아 있지 않다. `Permission denied (publickey)``cloud-init status: done` 도 약 50초도 SSOT 본문이 근거다. `final/evidence/` 에 그 출력을 담은 파일이 없다. `virsh screenshot` 으로 뜬 화면도 저장해 두지 않았다.
만드는 명령은 재실행으로 검증되지 않았다. 게스트를 다시 만들면 돌고 있는 실험대가 없어지므로 시드와 관련된 셋은 다시 재현하기 어렵다. 네트워크 autostart 만은 호스트를 재부팅해 확인할 수 있는데 그 기록도 없다.
<!-- body:end -->