Files
document-haness/docs/virtualization/tech-log-studio/lab-environment-build/decision/decision-fix-guest-addresses-with-a-dhcp-reservation.md
T
DongHyeonkaandClaude Opus 5 2109f726fe feat(pipeline): keycloak-session-store 25편·virtualization 59편을 S3→S5→S6 으로 돌린다
기록 84편을 계약 에이전트로 다시 썼다. 기존 71편(kss 25 · virt 46)과, 계약에만
있고 안 쓰여 있던 새 글감 13편이다. 원장 84개를 열어 단계마다 스킬 영수증과 관문
종료 코드를 적었고 verify-pipeline-run.py 가 error 0 으로 닫는다.

SSOT 결함 둘을 고쳤다.

- kss 의 `약 58일` 이 반입 중 `약 59일` 로 바뀌어 있었다. 원 증거 파일이
  「남은 일수: 88일 … 실제 갱신까지 약 58일」로 산수를 직접 적는다. D-4a 쪽
  `약 59일` 은 강제 갱신 뒤(`VALID: 89 days`)라 맞는 값이라 그대로 뒀다.
- virt §198 의 `11.6GB` 는 §178 의 원 측정 `Mem: 11648`(MiB)과 어긋나는데
  원 가이드의 표기 그대로라 고치지 않고 쓰이는 자리에 대조를 적었다.

기록의 수치 오류 셋을 고쳤다 — CASE 요약의 「게스트 셋에 8240MB」(5120+3120 은
둘이다), k3s 편이 같은 것을 여섯·일곱·여덟로 세던 것, no-docker 편의 「셋을 더
든다」(§281 의 표는 네 행이고 디스크 행이 빠져 있었다).

계약을 셋 고쳤다.

- kss 의 sourceRepository 리비전이 cdac9b8 이었는데 그 커밋에는 docs/guides/**
  28개가 아예 없다. 9465582b 로 바꾸고, 반입한 바이트가 어느 커밋과도 같지 않다는
  것을 측정값과 함께 적었다 — 반입은 커밋이 아니라 그 시점의 작업 트리에서 떠 온
  것이다(kss 297/306 · virt 12/14 가 작업 트리와 같고, 200 커밋을 거슬러 전수
  대조했을 때 가장 가까운 커밋도 28개가 어긋났다).
- virt 계약이 「2026-09-11 재배분」이라고 적는데 SSOT 는 재배분 날짜를 적지 않고
  재배분 뒤 값은 이미 2026-09-10 측정에 찍혀 있다.
- kss 후보 대장이 지나친 절 아홉에 처분을 적었다(warn 9 → 0). 새 글감은 0건이고
  넷은 앵커가 h3 슬러그의 접두가 아니라 중간 토막이라 검사기가 못 본 것이었다.

style_profile.mjs 의 결함 둘을 고쳤다 — frontmatter 가 문장으로 세어져
(실측 398자짜리 「문장」 하나) 평균 길이를 기준 안으로 밀어 올리고 있었고,
engPerSent 의 분자는 목록을 포함한 글에서, 분모는 목록을 걷어낸 글에서 세고
있었다(Question 기록에서 11.94 → 3.86).

verify-pipeline.py 전 항목 PASS · error 0 · unittest 334건 OK.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 11:01:55 +09:00

7.4 KiB

kind, slug, title, topic, topicName, project, status, decisionStatus, source, sourceRevision
kind slug title topic topicName project status decisionStatus source sourceRevision
PROJECT_DECISION fix-guest-addresses-with-a-dhcp-reservation 게스트 주소는 libvirt DHCP 예약으로 고정한다 — 바꿀 수 없어서가 아니라 되돌리기가 가장 비싸서 lab-environment-build 실험대 환경 구성 virtualization 게시 전 ADOPTED
final/document.md#247-dhcp-예약-ip-dhcp-host-과-mac-52-54-00
final/document.md#201-네트워크-dhcp-예약의-실제-동작
final/document.md#245-libvirt-default-네트워크와-virbr0
final/document.md#246-dnsmasq-libvirt-내장-dhcp-dns
final/document.md#248---live---config
9465582b5d1630eb4ae7c4e078021486919bf6b6

게스트 주소는 libvirt DHCP 예약으로 고정한다 — 바꿀 수 없어서가 아니라 되돌리기가 가장 비싸서

게스트 IP 를 libvirt 의 DHCP 예약으로 묶고 예약을 넣은 다음에 게스트를 만든다. 주소가 바뀌면 k3s 가 설정 파일과 인증서에 구워 둔 IP 부터 어긋나, 고치는 값이 한 곳이 아니라 재발급이나 재설치가 된다.

근거

  • cloud-init 시드로 게스트 세 대를 만들고 SSH 가 키로 붙게 한다 이 예약을 실제로 넣는 명령이 그 절차에 있고, DHCP 예약이 virt-install 보다 먼저여야 한다는 순서도 거기서 지킨다.
  • k3s server 와 agent 를 게스트 두 대에 깔고 lab host 에서 kubectl 로 본다 고정한 192.168.122.11192.168.122.12 를 그대로 받아 쓰는 절차다. --node-ip--tls-san 과 agent 의 K3S_URL 이 그 주소를 담는다.
  • 엣지 게스트에 nginx 를 세우고 호스트 DNAT 으로 밖에서 들어오는 길을 연다 nginx 의 upstream 이 뒤쪽 게스트 주소를 적는 곳이다. 주소가 바뀌면 reload 전까지 502 가 이어진다.
  • 호스트에 직접 깔지 않고 게스트 VM 두 대로 간다 — 관측자가 실험 대상과 함께 죽으면 안 된다 이 결정이 서 있는 바닥이다. 게스트를 반복해 죽이는 실험이 전제라서 주소 고정이 필요해졌다.
  • 도구가 낸 출력은 대상의 상태가 아니다 — 그 명령이 무엇을 세는지부터 가른다 net-dumpxml 의 예약과 net-dhcp-leases 의 리스가 서로 다른 것을 말한다는 것을 그 기준이 일반화한다.
  • L7 이 두 겹인 이유와, 진입점 하나를 이중화하려 할 때 끝나지 않는 재귀 nginx 가 뒤쪽 노드를 주소로 가리키는 구조를 그 글이 설명한다. 여기서 고정한 주소가 그 upstream 에 적힌다.

결정문

게스트의 IP 는 libvirt default 네트워크의 DHCP 예약(ip-dhcp-host)으로 고정한다. 게스트 안에서 static IP 를 잡지 않고, MAC 은 QEMU/KVM 에 할당된 52:54:00 대역을 쓴다. 예약을 먼저 넣고 그다음에 virt-install 로 게스트를 만든다.

2026-09-10 에 실제로 돌린 결과가 그렇게 나왔다. virsh net-update default add ip-dhcp-host ... --live --configUpdated network default persistent config and live state 를 냈고, 예약을 먼저 넣고 만든 게스트가 첫 부팅에서 바로 192.168.122.10 을 받았다.

판단 이유

§247 이 인과 순서를 직접 못박아 두었다. upstream 에 IP 를 박으려고 예약을 거는 것이 아니다. 고정 주소가 필요한 이유가 여럿이고 그것을 충족하는 수단이 DHCP 예약이며, 그렇게 얻은 주소를 upstream 에도 적는 순서다.

고정이 필요한 이유를 §247 이 중요도 순으로 넷 든다.

k3s 가 IP 를 설정 파일과 인증서에 굽는다 : --node-ip--tls-san, agent 의 K3S_URL=https://192.168.122.11:6443, kubeconfig 의 server: 필드가 전부 IP 를 담는다. server 노드의 IP 가 바뀌면 agent 가 클러스터에 합류하지 못한다. API 서버 인증서의 SAN 도 어긋나서 재발급이나 재설치가 필요해진다. 되돌리기가 가장 비싼 항목이다 nginx 는 upstream 주소를 기동 시점에 한 번만 해석한다 : 오픈소스판은 upstream 블록의 이름을 설정 로드 때 풀고 런타임에 다시 조회하지 않는다. 그래서 뒤쪽 IP 가 바뀌면 reload 전까지 계속 502 다. 다시 조회하게 하려면 resolver 와 변수 조합을 쓰거나 상용판이 필요하다 VM 을 반복해서 죽이는 것이 실험 그 자체다 : virsh destroy 로 노드 상실을 재현하는데 되살릴 때마다 주소가 달라지면 실험이 성립하지 않는다 장애 주입 규칙이 주소 기반이다 : 「kc-lab-2 로 가는 7800 을 막아라」에서 IP 가 어긋나면 조용히 엉뚱한 것을 막는다. 실패가 드러나지 않아 특히 위험하다

수단은 셋을 견줬고 §247 이 표로 적었다. 게스트 안에서 static IP 를 설정하면 cloud-init 이 복잡해지고 libvirt 는 그 사실을 모르므로 주소 설정이 두 곳으로 흩어진다. upstream 에 호스트명을 쓰면 libvirt 의 dnsmasq 가 이름을 풀어 주기는 한다. 다만 호스트의 리졸버가 virbr0 를 바라봐야 하고, 기동 시 한 번만 해석한다는 둘째 문제는 이 방법으로 없어지지 않는다.

DHCP 예약을 고른 까닭은 주소 관리가 libvirt 한 곳에 모이기 때문이다. 그 한 곳이 libvirt 가 네트워크마다 하나씩 띄우는 dnsmasq 이고, 예약은 네트워크 정의 XML 의 <ip><dhcp><host> 요소에 들어간다. 게스트는 평범한 DHCP 클라이언트로 두면 되고, 게스트 쪽 설정 파일은 아무것도 손대지 않는다.

영향

값이 맞으면 조용히 되고 틀리면 더 조용히 틀린다. 그래서 치른 비용이 셋 다 「오류가 안 나는 실패」다.

순서가 결과를 바꾼다 : 예약을 넣고 나서 virt-install 해야 한다. 게스트를 먼저 만들면 동적 대역(192.168.122.2부터 192.168.122.254)에서 아무 주소나 받고, 예약이 먹으려면 리스 만료를 기다리거나 재부팅해야 한다 MAC 이 한 글자만 달라도 조용히 무시된다 : 예약의 macvirt-install --network 에 준 mac= 이 정확히 같아야 한다. 다르면 오류 메시지 없이 동적 범위에서 아무 주소나 받고, 증상은 「왜 IP 가 다르지」로만 나타난다 플래그 둘을 다 줘야 한다 : --live 만 주면 재부팅에 사라지고 --config 만 주면 지금 반영이 안 된다. 성공 판정은 출력에 persistent configlive state 두 마디가 다 나오는 것이고, 한쪽만 나온 것을 성공으로 읽으면 「재부팅하니 IP 가 바뀐다」가 된다

예약을 조회하는 두 명령도 서로 다른 것을 말한다. net-dumpxml 이 보여 주는 예약은 dnsmasq 에게 준 의도이고, net-dhcp-leases 가 보여 주는 리스는 실제로 나간 기록이다. 둘이 다를 수 있으므로 예약을 넣었다는 것만으로 게스트가 그 주소를 받았다고 읽지 않는다.

동적 범위가 예약 주소를 품고 있는 것은 이대로 두었다. 현재 범위 192.168.122.2부터 192.168.122.254 안에 192.168.122.11192.168.122.12 가 들어간다. 그래도 dnsmasq 는 정적으로 예약된 주소를 다른 클라이언트에게 내주지 않아 정상 동작한다. 더 방어적으로 가려면 범위를 192.168.122.100부터 192.168.122.254 로 좁혀 예약 대역과 나눈다.

네트워크가 비활성일 때는 --live 를 쓸 수 없다. 그때는 --config 만 주고 네트워크를 시작한다.