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>
This commit is contained in:
DongHyeonka
2026-09-07 16:48:10 +09:00
co-authored by Claude Opus 5
parent cdac9b8178
commit 88b7bd4bf0
8 changed files with 1453 additions and 0 deletions
+232
View File
@@ -0,0 +1,232 @@
# 01 — VM 두 대
## 이 단계가 끝나면
`kc-lab-1`·`kc-lab-2` 두 게스트가 뜨고, 호스트에서 SSH 가 키로 붙는다.
## 전제
[00](../00-lab-host/) 이 끝나 `virsh list` 가 sudo 없이 돈다.
## 왜 VM 두 대인가
이 실험대의 질문 전부가 **「인스턴스가 둘 이상이고 요청이 어느 쪽으로 갈지
모른다」** 를 전제한다. 한 대면 세션 공유도 분단도 노드 상실도 실험이 되지
않는다. 그리고 호스트에 직접 깔면 **되돌릴 수가 없다** — 이 실험대는 노드를
죽이고 DB 를 crash 시키는 것이 목적이라 망가뜨릴 수 있는 층이 필요하다.
---
## 1. base 이미지를 받는다
OS 를 설치하지 않는다. 클라우드 이미지는 **이미 설치가 끝난 디스크**이고,
첫 부팅에서 cloud-init 이 사용자·SSH 키·호스트명을 채운다.
**하기**
```bash
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
```
**확인**
```bash
qemu-img info /var/lib/libvirt/images/base.qcow2
```
## 2. cloud-init 을 쓴다
게스트마다 하나씩 만든다. 템플릿은
[`deploy/lab/cloud-init/kc-lab.yaml.example`](../../../deploy/lab/cloud-init/kc-lab.yaml.example).
```yaml
#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]
```
### 세 값을 어디서 가져오나
자리표시자 셋을 실제 값으로 바꿔야 한다. **찾는 명령이 있다.**
```bash
# ① 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
```
셋을 넣는다.
```bash
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 이 그대로 넣는다
```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")'
```
마지막 줄이 중요하다. **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` 라는 **정확한
이름**의 파일이 있는 볼륨을 찾는다.
**하기**
```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
```
`instance-id` 에 타임스탬프를 넣는 이유가 있다. cloud-init 은 **인스턴스마다
한 번만** 초기화 모듈을 돌린다. id 가 같으면 이미 한 것으로 보고 건너뛰므로,
user-data 를 고쳐도 반영되지 않는다.
## 4. VM 을 만든다
**하기**
```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
```
`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 를 예약한다.
**하기**
```bash
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`
만 주면 지금 반영되지 않는다.
**확인**
```bash
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. 붙어 본다
**확인**
```bash
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](../00-lab-host/) 로 |
게스트에 못 들어갈 때는 **화면을 직접 뜬다.**
```bash
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`](../../session-lab-concepts.md) 13층에 있다.