Files
keycloak-pattern/docs/guides/01-vms/README.md
T
DongHyeonkaandClaude Opus 5 88b7bd4bf0 docs(guides): hands-on guides for the seven setup stages
The experiment documents record what was found. These record what to type to
reproduce it, in folders per stage.

Two kinds of command are kept apart. 하기/확인 is what somebody actually types
at a terminal — short, one at a time. 근거를 재려면 is the long measuring form
this lab used to put evidence in a document, marked as not needed day to day.
The same split applies to curl: -I to look once, -w '%{http_code}' only when
comparing across repetitions.

No placeholders. Where a value is needed the command that produces it is
given, and secrets are checked by length rather than printed:

  TOKEN=$(ssh kc-lab-1 'sudo cat /var/lib/rancher/k3s/server/node-token')
  echo "${#TOKEN} 자"

Stage 05 verifies resources in layers, because a Secret existing and a pod
having received it are different facts: keys, then length, then the value
inside the container, then which env var came from which Secret. Same for
workloads — Deployment to ReplicaSet to Pod, with the seven ReplicaSets this
cluster actually carries as the worked example.

Two commands were wrong and re-running them caught it. kubectl get endpoints
prints a deprecation warning on v1.33+, so the guide uses describe svc and
EndpointSlice. And the Keycloak image has no curl, so reading metrics from
inside the container fails with exit 127 — the guide asks Prometheus instead,
or runs a throwaway curl pod.

Read-only checks were executed against the running lab and their output is
quoted verbatim. Creating commands could not be re-run without destroying the
lab, so they are the ones used at build time; the README says which is which.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 16:48:10 +09:00

7.6 KiB

01 — VM 두 대

이 단계가 끝나면

kc-lab-1·kc-lab-2 두 게스트가 뜨고, 호스트에서 SSH 가 키로 붙는다.

전제

00 이 끝나 virsh list 가 sudo 없이 돈다.

왜 VM 두 대인가

이 실험대의 질문 전부가 「인스턴스가 둘 이상이고 요청이 어느 쪽으로 갈지 모른다」 를 전제한다. 한 대면 세션 공유도 분단도 노드 상실도 실험이 되지 않는다. 그리고 호스트에 직접 깔면 되돌릴 수가 없다 — 이 실험대는 노드를 죽이고 DB 를 crash 시키는 것이 목적이라 망가뜨릴 수 있는 층이 필요하다.


1. base 이미지를 받는다

OS 를 설치하지 않는다. 클라우드 이미지는 이미 설치가 끝난 디스크이고, 첫 부팅에서 cloud-init 이 사용자·SSH 키·호스트명을 채운다.

하기

cd /var/lib/libvirt/images
sudo curl -LO https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2
sudo mv debian-12-genericcloud-amd64.qcow2 base.qcow2

확인

qemu-img info /var/lib/libvirt/images/base.qcow2

2. cloud-init 을 쓴다

게스트마다 하나씩 만든다. 템플릿은 deploy/lab/cloud-init/kc-lab.yaml.example.

#cloud-config
hostname: kc-lab-1
fqdn: kc-lab-1
manage_etc_hosts: true

users:
  - name: donghyeon
    groups: [sudo]
    shell: /bin/bash
    sudo: ['ALL=(ALL) NOPASSWD:ALL']
    lock_passwd: false
    plain_text_passwd: __CONSOLE_PW__
    ssh_authorized_keys:
      - __LAB_HOST_KEY__
      - __WORKSTATION_KEY__

ssh_pwauth: false
package_update: true
packages: [curl, nftables]

세 값을 어디서 가져오나

자리표시자 셋을 실제 값으로 바꿔야 한다. 찾는 명령이 있다.

# ① lab host 공개키 — 없으면 만든다
[ -f ~/.ssh/id_ed25519.pub ] || ssh-keygen -t ed25519 -N '' -f ~/.ssh/id_ed25519
cat ~/.ssh/id_ed25519.pub

# ② 워크스테이션 공개키 — 워크스테이션에서
cat ~/.ssh/id_ed25519.pub

# ③ 콘솔용 비밀번호 — 만들어서 보관한다
openssl rand -base64 18

셋을 넣는다.

sed -i \
  -e "s|__LAB_HOST_KEY__|$(cat ~/.ssh/id_ed25519.pub)|" \
  -e "s|__WORKSTATION_KEY__|<워크스테이션에서 복사해 온 값>|" \
  -e "s|__CONSOLE_PW__|$(openssl rand -base64 18)|" \
  kc-lab-1.yaml

확인 — 자리표시자가 남아 있으면 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")'

마지막 줄이 중요하다. cloud-init 은 파싱에 실패해도 아무 오류를 남기지 않으므로, 넣기 전에 여기서 걸러야 한다.

세 가지가 의도적이다.

NOPASSWD:ALL k3s 설치와 장애 주입이 비대화식으로 돌아야 한다. 비밀번호를 물으면 멈춘다
plain_text_passwd cloud-init 이 실패했을 때의 유일한 탈출구. 키가 안 들어가면 SSH 가 막히므로 콘솔로 들어가 로그를 봐야 한다
키 두 개 lab host 에서 자동화가 돌고(에이전트 포워딩 없음), 워크스테이션에서는 ProxyJump 로 직접 붙는다

들여쓰기는 공백만. YAML 은 탭을 금지하고, cloud-init 은 파싱에 실패해도 아무 오류도 남기지 않는다. 게스트가 localhost 로 뜨고 로그인이 안 되는 것이 유일한 증상이다.

3. 시드 이미지를 만든다

cloud-init 은 cidata 라벨이 붙고 안에 user-data·meta-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

instance-id 에 타임스탬프를 넣는 이유가 있다. cloud-init 은 인스턴스마다 한 번만 초기화 모듈을 돌린다. id 가 같으면 이미 한 것으로 보고 건너뛰므로, user-data 를 고쳐도 반영되지 않는다.

4. VM 을 만든다

하기

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

kc-lab-2 는 이름·메모리(4096)·MAC(...:12)·시드만 바꾼다.

★ 시드를 --cloud-init 으로 붙이지 않는다. 그 옵션은 시드를 SATA CD-ROM 으로 붙이는데, Debian genericcloud 이미지는 크기를 줄이려고 물리 하드웨어 드라이버를 뺐다. AHCI 장치가 보이지 않아 cloud-init 이 데이터소스를 못 찾고 조용히 끝난다. bus=virtio 로 디스크로 붙여야 한다.

--disk size=20,backing_store=... 는 복사가 아니라 오버레이다. base 는 읽기 전용으로 두고 변경분만 새 파일에 쌓이므로, 20GB 를 두 개 만들어도 실제 디스크는 몇백 MB 만 쓴다.

5. DHCP 로 IP 를 고정한다

MAC 을 정해 두었으니 그 MAC 에 IP 를 예약한다.

하기

virsh net-update default add ip-dhcp-host \
  "<host mac='52:54:00:aa:bb:11' name='kc-lab-1' ip='192.168.122.11'/>" \
  --live --config

--live --config 둘 다 준다. --live 만 주면 재부팅에 사라지고, --config 만 주면 지금 반영되지 않는다.

확인

virsh net-dumpxml default | grep ip-dhcp-host -A3

실측

<range start='192.168.122.2' end='192.168.122.254'/>
<host mac='52:54:00:aa:bb:11' name='kc-lab-1' ip='192.168.122.11'/>
<host mac='52:54:00:aa:bb:12' name='kc-lab-2' ip='192.168.122.12'/>

6. 붙어 본다

확인

virsh list --all
ssh donghyeon@192.168.122.11 'hostname; cat /etc/os-release | head -1'

실측

 Id   Name       State
--------------------------
 3    kc-lab-2   running
 4    kc-lab-1   running

kc-lab-1
PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"

막히면

여기서 실제로 겪은 것들이다.

증상 원인 확인
SSH Permission denied (publickey) · hostname 이 localhost cloud-init 이 안 돌았다. 시드를 SATA 로 붙였거나 YAML 파싱 실패 아래 화면 캡처
user-data 를 고쳤는데 반영 안 됨 instance-id 가 같아 건너뜀 meta-data 의 id 확인
IP 가 매번 바뀐다 DHCP 예약이 --config 없이 들어감 net-dumpxml
VM 이 느리다 KVM 미사용 00

게스트에 못 들어갈 때는 화면을 직접 뜬다.

virsh screenshot kc-lab-1 /tmp/kc1.ppm    # 확장자와 무관하게 PNG 로 저장된다

localhost login: 이면 cloud-init 미실행, kc-lab-1 login: 이면 실행된 것이다. 이 한 장이 「SSH 가 안 되는 이유」를 절반으로 줄인다.


실측값

kc-lab-1   vCPU 2   메모리 5120MB   192.168.122.11
kc-lab-2   vCPU 2   메모리 4096MB   192.168.122.12
게스트 OS  Debian GNU/Linux 12 (bookworm)

메모리가 처음 만들 때(3584MB)와 다르다. 호스트가 12GB 뿐이라 실험을 늘리며 재배분했다. VM 을 다시 만들지 않고 바꾸는 방법은 session-lab-concepts.md 13층에 있다.