기록 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>
72 lines
7.4 KiB
Markdown
72 lines
7.4 KiB
Markdown
---
|
|
kind: PROJECT_DECISION
|
|
slug: fix-guest-addresses-with-a-dhcp-reservation
|
|
title: 게스트 주소는 libvirt DHCP 예약으로 고정한다 — 바꿀 수 없어서가 아니라 되돌리기가 가장 비싸서
|
|
topic: lab-environment-build
|
|
topicName: 실험대 환경 구성
|
|
project: virtualization
|
|
status: 게시 전
|
|
decisionStatus: ADOPTED
|
|
source:
|
|
- 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
|
|
sourceRevision: 9465582b5d1630eb4ae7c4e078021486919bf6b6
|
|
---
|
|
|
|
# 게스트 주소는 libvirt DHCP 예약으로 고정한다 — 바꿀 수 없어서가 아니라 되돌리기가 가장 비싸서
|
|
|
|
게스트 IP 를 libvirt 의 DHCP 예약으로 묶고 예약을 넣은 다음에 게스트를 만든다. 주소가 바뀌면 k3s 가 설정 파일과 인증서에 구워 둔 IP 부터 어긋나, 고치는 값이 한 곳이 아니라 재발급이나 재설치가 된다.
|
|
|
|
## 근거
|
|
|
|
- **cloud-init 시드로 게스트 세 대를 만들고 SSH 가 키로 붙게 한다**
|
|
이 예약을 실제로 넣는 명령이 그 절차에 있고, DHCP 예약이 `virt-install` 보다 먼저여야 한다는 순서도 거기서 지킨다.
|
|
- **k3s server 와 agent 를 게스트 두 대에 깔고 lab host 에서 kubectl 로 본다**
|
|
고정한 `192.168.122.11` 과 `192.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 --config` 이 `Updated 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 이 한 글자만 달라도 조용히 무시된다 : 예약의 `mac` 과 `virt-install --network` 에 준 `mac=` 이 정확히 같아야 한다. 다르면 오류 메시지 없이 동적 범위에서 아무 주소나 받고, 증상은 「왜 IP 가 다르지」로만 나타난다
|
|
플래그 둘을 다 줘야 한다 : `--live` 만 주면 재부팅에 사라지고 `--config` 만 주면 지금 반영이 안 된다. 성공 판정은 출력에 `persistent config` 와 `live state` 두 마디가 다 나오는 것이고, 한쪽만 나온 것을 성공으로 읽으면 「재부팅하니 IP 가 바뀐다」가 된다
|
|
|
|
예약을 조회하는 두 명령도 서로 다른 것을 말한다. `net-dumpxml` 이 보여 주는 예약은 dnsmasq 에게 준 의도이고, `net-dhcp-leases` 가 보여 주는 리스는 실제로 나간 기록이다. 둘이 다를 수 있으므로 예약을 넣었다는 것만으로 게스트가 그 주소를 받았다고 읽지 않는다.
|
|
|
|
동적 범위가 예약 주소를 품고 있는 것은 이대로 두었다. 현재 범위 `192.168.122.2`부터 `192.168.122.254` 안에 `192.168.122.11` 과 `192.168.122.12` 가 들어간다. 그래도 dnsmasq 는 정적으로 예약된 주소를 다른 클라이언트에게 내주지 않아 정상 동작한다. 더 방어적으로 가려면 범위를 `192.168.122.100`부터 `192.168.122.254` 로 좁혀 예약 대역과 나눈다.
|
|
|
|
네트워크가 비활성일 때는 `--live` 를 쓸 수 없다. 그때는 `--config` 만 주고 네트워크를 시작한다.
|