Files
document-haness/docs/virtualization/tech-log-studio/build-completion-judgment/case/case-cloud-init-failures-all-look-like-ssh-refused.md
T

13 KiB

kind, slug, title, topic, topicName, project, status, sourceRevision, evidence, source
kind slug title topic topicName project status sourceRevision evidence source
CASE cloud-init-failures-all-look-like-ssh-refused cloud-init 이 안 도는 원인은 넷인데 증상은 「SSH 가 안 붙는다」 하나였다 build-completion-judgment 끝났다는 판정 virtualization 게시 전 import-head 9465582b5d1630eb4ae7c4e078021486919bf6b6 · uncommitted-working-tree snapshot
../../../final/evidence/raw/lab-state-before-rebuild/14-cloudinit-schema-check.txt
../../../final/evidence/raw/lab-state-before-rebuild/15-k3s-precheck.txt
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 으로 화면을 떠서 로그인 프롬프트 앞의 호스트명을 읽는다.

본문

시드가 게스트에 들어가는 길

cloud-init 이 게스트 안에서 사용자를 만들고 SSH 키를 넣으려면 먼저 데이터소스를 찾아야 한다. 이 실험대는 그것을 cidata 라벨이 붙은 볼륨으로 준다. 그 안에는 user-datameta-data 라는 정확한 이름의 파일 둘이 들어 있다.

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 로 붙인다.

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 가 붙는다면 확인은 한 줄로 끝난다.

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 statusrunning 이면 아직 패키지를 받는 중이고, 이 실험대에서는 done 까지 약 50초 걸렸다.

게스트에 못 들어가면 화면을 직접 뜬다.

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 를 리스트로 쓴 것을 거부한다.

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-1kc-lab-2 가 그 표기로 만들어졌고 NOPASSWD sudo 가 멀쩡히 돌고 있다.

NOPASSWD:ALL 을 넣은 것은 k3s 설치와 장애 주입이 비대화식으로 돌아야 해서다. 비밀번호를 물으면 원격 실행이 거기서 멈춘다. 검사기가 거부한 줄이 바로 그 줄이다.

검사가 통과해도 cloud-init 이 안 도는 경로가 넷이고, 검사가 거부해도 도는 경로가 하나다.

시드를 만들기 전에 세 줄로 거른다

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")'

02YAML OK 셋이 맞아야 시드를 만든다. 셋이 맞아도 cloud-config 로 유효하지는 않다 — 키 이름을 users 대신 user 로 친 오타는 이 세 줄을 그냥 통과한다. 그래서 게스트가 한 대라도 떠 있으면 cloud-init 자신의 스키마 검사기를 쓴다.

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'

이 두 줄은 당시 문서에 남은 historical command다. 둘째 줄은 스키마 검사가 실패해도 ; rm 때문에 파일을 지운다. 따라 하는 절차에서는 검사와 삭제를 분리하고, 검사 성공 여부를 본 뒤 삭제한다.

이 파일에는 콘솔 비밀번호가 평문으로 들어 있어 /tmp 가 아니라 자기 홈에 600 으로 두고, 검사가 끝나면 바로 지운다.

확인하지 못한 것

넷 가운데 SSOT 가 관측으로 표시한 것은 시드를 SATA 로 붙였을 때 AHCI 장치가 보이지 않는 것과 스키마 검사기의 거부 문구뿐이다. YAML 파싱 실패와 vol-upload 누락과 네트워크 autostart 셋은 가이드가 막히면 표에 적어 둔 항목이라, 이 실험대에서 실제로 그 증상을 본 것인지 예상해 적은 것인지 SSOT 가 가르지 않았다. 이 글에서 그 셋은 그렇게 되는 구조까지이고 그렇게 됐다가 아니다.

후속 원문이 생겼다. 14-cloudinit-schema-check.txt 는 cloud-init 22.4.2가 sudo 리스트 표기를 실제로 거부한 출력을 다시 담아 스키마 거부 claim을 직접 보강한다. 15-k3s-precheck.txtHost key verification failed. 는 2026-09-17 재구축 중 나온 후속 SSH 관측으로, 원래 사건의 Permission denied (publickey)를 재현한 것은 아니다. 원래 Permission denied (publickey), cloud-init status: done, 약 50초, virsh screenshot 화면은 여전히 SSOT 본문만 근거이고 직접 raw는 없다.

만드는 명령은 재실행으로 검증되지 않았다. 게스트를 다시 만들면 돌고 있는 실험대가 없어지므로 시드와 관련된 셋은 다시 재현하기 어렵다. 네트워크 autostart 만은 호스트를 재부팅해 확인할 수 있는데 그 기록도 없다.