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>
233 lines
7.6 KiB
Markdown
233 lines
7.6 KiB
Markdown
# 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층에 있다.
|