Files
keycloak-pattern/docs/lab-virtualization.md

16 KiB
Raw Permalink Blame History

실험대 가상화 계층 — 실측 기록

이 문서가 무엇인가

docs/guides/「무엇을 어떤 순서로 치는가」 를 적는다면, 이 문서는 「그때 실제로 어떤 값이 나왔는가」 를 적는다. 가이드에 이미 있는 절차는 반복하지 않는다.

여기 적힌 숫자는 전부 2026-09-10 에 test-server 에서 실제로 돌려 받은 출력이다. 추정값·예상값은 없다. 없는 값은 「미측정」이라고 쓴다.

이 문서가 답하는 것 가이드가 답하는 것
5120MB 를 줬는데 실제로 얼마를 쓰나 메모리를 얼마로 주나
20GB 오버레이가 디스크를 얼마나 먹나 오버레이를 어떻게 만드나
cloud-init 이 몇 초 걸리나 cloud-init 을 어떻게 쓰나
철거하면 무엇이 남나 무엇을 세우나

1. 측정 환경

lscpu | grep -E "^Model name|^CPU\(s\):|^Thread|^Core"
free -m | head -2
df -h /
virsh --version; qemu-system-x86_64 --version | head -1; uname -r

실측

CPU(s):                8
Model name:            11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz
Thread(s) per core:    2
Core(s) per socket:    4

               total        used        free      shared  buff/cache   available
Mem:           11648        5642        2599           4        3776        6005

/dev/nvme0n1p3  226G  9.9G  204G   5% /

12.7.0
QEMU emulator version 11.1.1
7.2.2-arch1-1

어디를 봐야 하는가Core(s) per socket 4 에 Thread(s) per core 2 라 논리 코어가 8 이다. VM 에 주는 vCPU 는 물리 코어가 아니라 논리 코어를 나눠 쓰는 것이고, 합이 8 을 넘어도 libvirt 는 막지 않는다(오버커밋). 이 실험대는 2 + 2 + 1 = 5 로 잡아 여유를 뒀다.

freeavailable(6005MB)이 free(2599MB)보다 훨씬 큰 것이 정상이다 — buff/cache 3776MB 는 필요하면 회수된다. VM 을 얼마나 더 띄울 수 있는지는 free 가 아니라 available 로 본다.

중첩 가상화

lscpu | grep Virtualization
cat /sys/module/kvm_intel/parameters/nested
Virtualization:   VT-x
Y

켜져 있지만 이 실험대는 쓰지 않는다. 일회용으로 만들려는 층(게스트)은 이미 일회용이고, 비싼 층(호스트의 인증서·DNS)은 중첩으로 해결되지 않는다. 그리고 A-6 의 지연 주입처럼 시간을 재는 실험에서 중첩은 측정값을 왜곡한다.


2. 자원 — 할당과 실사용은 다르다

VM 에 준 메모리는 상한이지 점유가 아니다. virtio-balloon 이 안 쓰는 만큼 호스트에 돌려준다.

for v in kc-lab-1 kc-lab-2; do
  printf "%-10s 할당 %sMB  실사용 %sMB\n" "$v" \
    "$(( $(virsh dommemstat $v | awk '/actual/{print $2}') / 1024 ))" \
    "$(( ($(virsh dommemstat $v | awk '/actual/{print $2}') \
        - $(virsh dommemstat $v | awk '/^unused/{print $2}')) / 1024 ))"
done

실측 — k3s 만 떠 있고 Keycloak 은 아직 안 올린 상태

kc-lab-1   할당 5120MB  실사용 353MB
kc-lab-2   할당 3120MB  실사용 301MB

어디를 봐야 하는가 — 두 가지다.

실사용이 할당의 7% 밖에 안 된다. k3s server 가 353MB 다. 「5GB 를 줬으니 5GB 를 쓴다」가 아니다.

kc-lab-2 의 할당이 4096 이 아니라 3120 이다. virt-install --memory 4096 으로 만들었는데 줄어 있다. virtio-balloon 이 회수해 간 것으로 보인다. dommemstatactual현재 할당이지 선언한 상한이 아니다. 상한은 virsh dominfoMax memory 에 있다 — 다만 이 실험대에서 그 값을 나란히 찍어 보지는 않았다(미측정). 둘을 헷갈리면 「메모리가 왜 줄었지」가 된다.

이 결과가 의미하는 것 — 합계 8240MB 를 할당했지만 실제 점유는 654MB 다. 그래서 11.6GB 짜리 호스트에서 VM 세 대가 무리 없이 돈다. 다만 이것은 지금 k3s 만 떠 있어서다 — Keycloak 2 파드 + PostgreSQL + Redis + Prometheus 가 올라가면 늘어난다. 그 시점의 값은 미측정이다.


3. 디스크 — 오버레이는 얼마나 쓰나

게스트 디스크는 base.qcow2 위의 오버레이다. 20GB 를 두 개 만들어도 바닥 이미지는 한 벌이고 변경분만 쌓인다.

qemu-img info /var/lib/libvirt/images/base.qcow2 | head -4
ls -l /var/lib/libvirt/images/

실측 — 엣지를 만들기 , k3s 2 노드만 있던 시점이다.

image: /var/lib/libvirt/images/base.qcow2
file format: qcow2
virtual size: 3 GiB (3221225472 bytes)
disk size: 335 MiB

-rw-r--r--  base.qcow2              351404032   (335 MiB)
-rw-------  kc-lab-1.qcow2         1521025024   (1.4 GiB)   ← 선언 20GB
-rw-------  kc-lab-2.qcow2          697499648   (665 MiB)   ← 선언 20GB
-rw-------  seed-kc-lab-1.iso          378880   (370 KiB)
-rw-------  seed-kc-lab-2.iso          378880   (370 KiB)

어디를 봐야 하는가base.qcow2virtual size 3GiB 와 disk size 335MiB 의 차이. 그리고 게스트 디스크가 20GB 로 선언됐는데 1.4GB 와 665MB 라는 것. k3s server 가 agent 보다 두 배 이상 쓰는 것은 컨트롤 플레인 바이너리와 SQLite 때문이다.

이 결과가 의미하는 것 — 20GB × 2 = 40GB 를 선언했지만 실제로는 2.1GB 를 썼다. 디스크는 병목이 아니다. df 로 본 사용량과 VM 에 선언한 크기를 같은 것으로 보면 안 된다.

스토리지 풀

virsh pool-info default
Name:           default
State:          running
Persistent:     yes      Autostart:  yes
Capacity:       225.31 GiB
Allocation:     7.84 GiB
Available:      217.46 GiB

Allocation풀 전체(호스트 루트 파일시스템)의 사용량이지 VM 만의 사용량이 아니다. VM 이 얼마를 쓰는지는 위 ls -l 로 본다.


4. 부팅 — cloud-init 은 얼마나 걸리나

for i in $(seq 1 30); do
  ssh -o BatchMode=yes -o ConnectTimeout=4 kc-lab-edge 'cloud-init status' 2>/dev/null \
    | grep -q 'status: done' && { echo "완료 (약 $((i*10))초)"; break; }
  sleep 10
done
ssh kc-lab-edge 'hostname; ip -4 -br addr show enp1s0; cloud-init status'

실측package_update: true 에 패키지 5개(curl nftables nginx certbot python3-certbot-dns-cloudflare)를 받는 게스트

완료 (약 50초)

kc-lab-edge
enp1s0           UP             192.168.122.10/24 metric 100
status: done

어디를 봐야 하는가cloud-init status 의 세 상태를 구분한다.

running 아직 진행 중. 기다린다
done 끝났다
error 실패. cloud-init status --long 으로 어느 모듈인지 본다

이 결과가 의미하는 것 — SSH 가 붙는 것과 cloud-init 이 끝난 것은 다르다. SSH 는 먼저 열리고 패키지 설치는 뒤에 이어진다. done 을 안 기다리고 다음 단계를 치면 「방금 깐 패키지가 없다」가 나온다.


5. 네트워크 — DHCP 예약의 실제 동작

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

실측

Updated network default persistent config and live state

어디를 봐야 하는가persistent configlive state 두 마디가 다 나오는가. --live 만 주면 앞의 것이, --config 만 주면 뒤의 것이 빠진다. 한쪽만 나온 것을 성공으로 읽으면 「재부팅하니 IP 가 바뀐다」가 된다.

예약을 먼저, VM 을 나중에

이 실험대는 예약을 넣고 나서 virt-install 했고, 게스트가 첫 부팅에서 바로 .10 을 받았다.

enp1s0           UP             192.168.122.10/24 metric 100

순서가 반대면 게스트가 동적 대역(192.168.122.2.254)에서 아무 주소나 받고, 예약이 먹으려면 리스 만료를 기다리거나 재부팅해야 한다.

리스는 예약과 별개로 남는다

virsh net-dhcp-leases default
 Expiry Time           MAC address         IP address          Hostname
 2026-09-10 09:59:54   52:54:00:aa:bb:11   192.168.122.11/24   kc-lab-1
 2026-09-10 09:58:46   52:54:00:aa:bb:12   192.168.122.12/24   kc-lab-2

net-dumpxml 의 예약은 줄 의도이고, net-dhcp-leases실제로 준 기록이다. 둘이 다를 수 있다.

virbr0 는 게스트가 없으면 내려간다

ip -br addr show virbr0

VM 세 대가 돌 때:

virbr0           UP             192.168.122.1/24

전부 철거한 뒤:

virbr0           DOWN           192.168.122.1/24

주소는 그대로 있고 상태만 DOWN 이다. 브리지에 붙은 tap 인터페이스가 하나도 없어서다. 네트워크 정의가 사라진 것이 아니므로 VM 을 다시 띄우면 자동으로 UP 이 된다. 이걸 고장으로 오독해 virsh net-start 를 찾아 헤매지 않는다.


6. 철거 — 실제 출력 전문

가이드에는 세우는 절차만 있고 철거가 없다. 2026-09-10 에 실제로 돌린 기록이다.

게스트

for v in kc-lab-edge kc-lab-2 kc-lab-1; do
  virsh destroy "$v"
  virsh undefine "$v" --remove-all-storage
done

실측 (한 대분)

Domain 'kc-lab-edge' destroyed
Domain 'kc-lab-edge' has been undefined
Volume 'vda'(/var/lib/libvirt/images/kc-lab-edge.qcow2) removed.
Volume 'vdb'(/var/lib/libvirt/images/seed-kc-lab-edge.iso) removed.

어디를 봐야 하는가Volume 줄이 두 개 나오는가. vda(오버레이 디스크)와 vdb(시드 ISO)다. --remove-all-storage 를 빠뜨리면 도메인만 사라지고 디스크 파일이 남아 다음 virt-install 이 「이미 있다」로 실패한다.

destroy 는 종료 신호가 아니라 전원을 뽑는 것이다. 정상 종료를 원하면 virsh shutdown 을 쓰고 꺼질 때까지 기다린다.

DHCP 예약

virsh net-update default delete ip-dhcp-host \
  "<host mac='52:54:00:aa:bb:10' name='kc-lab-edge' ip='192.168.122.10'/>" \
  --live --config

★ 삭제할 때도 mac·name·ip 세 속성을 다 준다. 하나라도 비면 이렇게 거부된다.

error: Failed to update network default
error: XML error: Cannot use host name '' in network 'default'

zsh 에서 루프로 돌리면 이 오류를 만난다. zsh 는 따옴표 없는 변수를 단어 분리하지 않는다. bash 에서 되던 set -- $entry 가 zsh 에서는 $1 에 문자열 전체를 넣고 $2·$3 을 비운다. 그래서 name="" 이 된다. 세 줄을 값 그대로 쓰는 편이 안전하다.

철거 후

### 남은 예약
(없음)

### dhcp 블록
    <dhcp>
      <range start='192.168.122.2' end='192.168.122.254'/>
    </dhcp>

동적 대역만 남는 것이 정상이다.

철거 전후 비교 — 실측

철거 전 철거 후
virsh list --all 3 대 running (없음)
virsh vol-list default 7 개 base.qcow2 1 개
DHCP 예약 3 줄 0 줄
df -h / 11G 7.9G
virbr0 UP DOWN

3.1GB 가 회수됐다. 내역은 kc-lab-1 1.4GB + kc-lab-2 665MB + 시드 ISO 3개(각 370KB)이고, kc-lab-edge 의 디스크 크기는 재 두지 않았다(미측정) — 합계에서 역산하면 1GB 안팎이다.

base.qcow2 335MB 는 남긴다 — 다음 재구축의 바닥이고, 다시 받으면 몇 분이다.


7. 실측으로 드러난 함정 셋

전부 아무 오류 없이 조용히 지나가거나, 엉뚱한 곳에서 증상이 나오는 유형이다.

① cloud-init sudo 는 리스트가 아니라 문자열

ssh kc-lab-1 'cloud-init schema -c ~/chk.yaml'

리스트 형태(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

문자열 형태(sudo: "ALL=(ALL) NOPASSWD:ALL")일 때:

Valid cloud-config: /home/donghyeon/chk.yaml

어느 키가 문제인지 안 알려 준다. users.0 블록을 통째로 찍고 「어느 스키마에도 안 맞는다」고만 한다. 그리고 리스트 형태도 부팅은 된다kc-lab-1·kc-lab-2 가 그 상태로 NOPASSWD sudo 가 멀쩡히 돌았다. 검사기만 거부하는 것이라 「검사는 실패인데 왜 되지」로 헷갈린다.

게스트 cloud-init 버전: 22.4.2 (Debian 12 genericcloud).

② nginx http2 on; 은 배포판에 따라 없다

엣지 (Debian 12):   nginx version: nginx/1.22.1
lab host (Arch):    nginx version: nginx/1.30.4

http2 on; 은 nginx 1.25.1 이상이다. Arch 에서 쓰던 설정을 Debian 게스트로 그대로 옮기면:

[emerg] 1339#1339: unknown directive "http2" in /etc/nginx/sites-enabled/keycloak-lab:29
nginx: configuration file /etc/nginx/nginx.conf test failed

listen 443 ssl http2; 형태는 1.22 와 1.30 양쪽에서 다 돈다.

③ Debian 기본 사이트가 default_server 를 먹고 있다

Debian 계열은 /etc/nginx/sites-enabled/default 가 처음부터 붙어 있고 :80default_server 로 선언돼 있다. 실험대 설정의 listen 80 default_server충돌한다. 심볼릭 링크를 걸 때 같이 지운다.

sudo rm -f /etc/nginx/sites-enabled/default

Arch 는 sites-available 관례 자체가 없어서 이 함정이 없는 대신, nginx.confinclude /etc/nginx/sites-enabled/*; 를 직접 넣어야 한다. 배포판이 바뀌면 함정도 바뀐다.


8. 재구축할 때 무엇이 남아 있나

철거해도 남는 것과 사라지는 것을 구분해 둔다. 이걸 모르면 재구축이 「왜 이건 이미 있지」와 「왜 이건 없지」의 반복이 된다.

상태
base.qcow2 (335MB) 남는다 다음 오버레이의 바닥
패키지 (libvirt·qemu·nginx·certbot·kubectl) 남는다 재설치가 무의미
~/workspace/cloud/kc-lab-{1,2}.yaml 남는다 키와 비밀번호가 들어 있다
~/.ssh/configkc-lab-* 항목 남는다 재구축해도 IP 가 같다
libvirt default 네트워크 정의 남는다 예약만 지웠다
/etc/letsencrypt/ 남긴다 (정책) 아래 참고
게스트 디스크·시드 ISO 사라진다 --remove-all-storage
DHCP 예약 사라진다 net-update delete
k3s·Keycloak·모든 워크로드 사라진다 게스트와 함께

인증서를 지우지 않는 이유

Let's Encrypt 는 같은 이름 조합에 대해 주당 중복 인증서 5장 제한이 있다. 그리고 이 실험대의 이름 셋은 tailnet 주소를 가리킨다.

dig +short auth.hyeonworks.com
100.83.212.4

100.64.0.0/10 은 CGNAT 용 예약 대역이라 공개 인터넷에서 라우팅되지 않는다. HTTP-01 검증은 Let's Encrypt 가 우리 서버로 들어오는 방식이므로, 이 주소로는 검증이 성립하지 않는다. 지우면 되살리기 전에 검증 방식 문제부터 풀어야 한다. 백업만 뜨고 파일은 남긴다.

sudo tar czf ~/letsencrypt-backup-$(date +%Y%m%d-%H%M%S).tgz -C /etc letsencrypt

복원은 반대로 한 줄이다.

sudo tar xzf ~/letsencrypt-backup-<stamp>.tgz -C /etc

미측정 — 이 실험대의 certbot 이 HTTP-01 인지 DNS-01 인지 아직 확인하지 않았다. 호스트의 sudo 가 비밀번호를 요구해 비대화식으로 읽지 못했다. 다음 한 줄이 답이다.

sudo grep -H authenticator /etc/letsencrypt/renewal/*.conf

webroot·standalone 이면 HTTP-01 이라 위 문제가 실재하고, dns-cloudflare 면 DNS-01 이라 tailnet 안에서도 재발급·갱신이 된다. 플러그인은 이미 깔려 있다 — certbot pluginsdns-cloudflare 가 보인다.


관련 문서

문서 무엇
guides/01-vms/ VM 세 대를 세우는 절차
guides/00-lab-host/ 호스트 가상화 준비
session-lab-concepts.md 여기 나온 개념의 정의
deploy/lab/host/teardown-host.sh 호스트 계층 철거