virtualization 의 setup 9편은 같은 커밋(2109f72)에서 한꺼번에 생겼는데 그때
Studio 반입이 7편만 덮고 둘을 빠뜨렸다. 둘 다 readiness 가 READY 이고 관문도
통과하는데 frontmatter 에 id 도 studio 도 없었다 — Studio 문서가 아예 없었다는
뜻이다. 빼놓을 이유가 없어서 만들었다.
- setup-tear-down-the-lab-and-know-what-survives -> 87e0138d-…
- setup-power-cycle-the-lab-and-reallocate-guest-memory -> 8a9ca3d4-…
주제는 새로 만들지 않고 같은 폴더의 기존 편에서 topicId·projectId 를 읽어
같은 값을 썼다. 둘 다 저장까지만 하고 게시하지 않았다(currentPublication: null).
pinnedVersions 의 40자 상한
- power-cycle 의 「기준 배치」 값이 51자여서 서버가 422 로 거절했다
(size must be between 1 and 40)
- 그 칸에 문장이 들어가 있었다. CLAUDE.md 는 Setup 에 대해 「명령이 본문에
들어가고 버전만 pinnedVersions 에 남는다」고 적는다
- 잘라내기 전에 그 설명이 본문에 있는지 먼저 봤다. 셋 다 있었다 — 63행이
호스트 RAM 증설, 176행이 2026-09-03, 215행이 2026-09-10 과의 날짜 엇갈림.
그래서 날짜만 남겼고 잃은 내용이 없다
- 저장소 전체를 훑어 40자를 넘는 값은 이것 하나뿐이었다
기록 두 편의 diff 는 frontmatter 뿐이고, tech-log-tree.json 은 파생 칸
(publication · studioId) 넷만 바뀌었다. 「게시됨」은 Studio 에 있다는 뜻이지
공개됐다는 뜻이 아니다.
관문: check_body PASS · check_prose error 0 · check_evidence 문제 없음 ·
verify-tech-log-tree error 0 · verify-project-layout error 0 ·
check-studio-whitespace PASS
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
222 lines
14 KiB
Markdown
222 lines
14 KiB
Markdown
---
|
|
id: 8a9ca3d4-7d6e-4f38-862d-296b2640d288
|
|
kind: SETUP
|
|
slug: power-cycle-the-lab-and-reallocate-guest-memory
|
|
title: 실험대를 껐다 켜고 게스트 메모리를 다시 나눈다
|
|
topic: lab-environment-build
|
|
topicName: 실험대 환경 구성
|
|
project: virtualization
|
|
status: 게시 전
|
|
studio: "https://hyeonworks.com/studio/documents/8a9ca3d4-7d6e-4f38-862d-296b2640d288/edit"
|
|
pinnedVersions:
|
|
- name: libvirt
|
|
version: 12.7.0
|
|
- name: QEMU
|
|
version: 11.1.1
|
|
- name: k3s
|
|
version: v1.36.4+k3s1
|
|
- name: 게스트
|
|
version: Debian 12 genericcloud
|
|
- name: 기준 배치
|
|
version: 2026-09-03
|
|
source:
|
|
- final/document.md#332-vm-메모리-재배분-게스트를-다시-만들지-않는다
|
|
- final/document.md#333-안전한-종료-순서
|
|
- final/document.md#334-복구-순서-종료의-역순
|
|
- final/document.md#313-k3s-server와-agent-죽였을-때가-다르다
|
|
sourceRevision: 9465582b5d1630eb4ae7c4e078021486919bf6b6
|
|
---
|
|
|
|
# 실험대를 껐다 켜고 게스트 메모리를 다시 나눈다
|
|
|
|
게스트를 다시 만들지 않고 메모리를 재배분하고, 노트북을 끄기 전에 워크로드를 위에서부터 내리고, 켤 때 그 역순으로 되살리는 절차다. 순서를 틀리면 PostgreSQL 이 강제 종료되어 다음 기동에 crash recovery 가 돈다.
|
|
|
|
## 관계
|
|
|
|
- **실험대를 철거하고 무엇이 남는지 확인한다**
|
|
지우는 쪽이다. 거기서는 `virsh destroy` 로 전원을 뽑고, 여기서는 `virsh shutdown` 으로 정상 종료한다.
|
|
- **Keycloak 2노드와 PostgreSQL 을 k3s 에 올리고 클러스터가 묶였는지 확인한다**
|
|
여기서 내리고 올리는 워크로드를 그 절차가 세운다. 네임스페이스와 리소스 이름도 거기서 정해졌다.
|
|
- **k3s server 와 agent 를 게스트 두 대에 깔고 lab host 에서 kubectl 로 본다**
|
|
`kubectl` 이 lab host 에서 도는 까닭이 그 절차에 있다. 이 편의 명령도 전부 거기서 친다.
|
|
- **호스트에 직접 깔지 않고 게스트 VM 두 대로 간다 — 관측자가 실험 대상과 함께 죽으면 안 된다**
|
|
server 와 agent 를 죽였을 때가 왜 다른지, 그래서 관측 스택을 어디에 두는지를 그 결정이 받는다.
|
|
- **5120MB 를 줬는데 353MB 를 쓰고 있었다 — 선언한 양과 실제로 드는 양**
|
|
재배분한 값은 상한이지 점유가 아니다. 상한과 실제 점유를 가르는 방법이 그 기록에 있다.
|
|
- **이 호스트의 가상 머신들은 설정한 메모리와 지금 잡고 있는 메모리가 얼마나 다른가**
|
|
여기서 준 값이 그 물음이 견줄 설정 값이 된다.
|
|
|
|
## 본문
|
|
|
|
<!-- body:start -->
|
|
|
|
## 읽기 전에 — 어디서 치는가
|
|
|
|
전부 `[lab host]` 다. `virsh` 도 `kubectl` 도 거기서 돌고, 게스트 안을 봐야 할 때만 `ssh kc-lab-1 ...` 형태로 원격 실행한다. 게스트에 로그인해서 치지 않는다.
|
|
|
|
이 절차는 메모리 값을 바꾸고 워크로드를 올렸다 내리는 일뿐이라 설정 파일을 손대지 않고, 편집기를 여는 곳도 없다.
|
|
|
|
## 이 절차가 다루는 일 셋
|
|
|
|
세우는 편 일곱과 지우는 편 하나 사이에 있는 일이다. 실험대를 계속 쓰면 실제로 자주 하는 쪽이 이쪽이다.
|
|
|
|
| 언제 | 무엇을 |
|
|
|---|---|
|
|
| 호스트 RAM 을 늘렸거나 게스트가 좁을 때 | 게스트를 다시 만들지 않고 메모리를 재배분한다 |
|
|
| 노트북을 끄기 전에 | 워크로드와 게스트를 위에서부터 내린다 |
|
|
| 다시 켤 때 | 종료의 역순으로 되살린다 |
|
|
|
|
셋 다 게스트를 지우지 않아서, 디스크도 시드 ISO 도 그대로 두고 도메인의 값과 파드 수만 바꾼다.
|
|
|
|
## 실행 절차
|
|
|
|
### 1. 게스트 메모리를 다시 나눈다
|
|
|
|
**목적** — 게스트를 다시 만들지 않고 도메인이 쓸 메모리 상한과 현재 할당을 바꾼다.
|
|
|
|
상한을 먼저 올리고 그다음에 현재 할당을 맞춘 뒤, 도메인과 게스트 양쪽에서 값을 확인한다.
|
|
|
|
```bash label="[lab host] ① 상한을 올리고 현재 할당을 그 값에 맞춘다"
|
|
virsh setmaxmem kc-lab-1 5120M --config
|
|
virsh setmem kc-lab-1 5120M --config
|
|
```
|
|
|
|
```bash label="[lab host] ② 바꾼 값이 설정에 들어갔는지 본다"
|
|
virsh dumpxml kc-lab-1 --inactive | grep -E '<memory|<currentMemory'
|
|
```
|
|
|
|
```bash label="[lab host] ③ 지금 돌고 있는 값과 게스트가 인식한 값도 함께 본다"
|
|
virsh dominfo kc-lab-1 | grep -i memory
|
|
ssh kc-lab-1 free -m
|
|
```
|
|
|
|
**예상 결과** — ② 의 두 줄이 방금 준 값으로 바뀐다. ③ 의 `dominfo` 와 게스트의 `free -m` 은 **재부팅 전까지 옛 값 그대로**다.
|
|
|
|
**`dominfo` 로 판정하지 않는다.** `--config` 는 다음 기동부터 쓸 설정만 바꾸는데 `dominfo` 는 **지금 돌고 있는 값**을 보여 준다. 그래서 `setmaxmem … --config` 가 제대로 먹어도 `dominfo` 의 숫자는 안 바뀌고, 그 화면을 보고 「안 먹었다」로 읽게 된다. 2026-09-17 에 같은 순간을 두 명령으로 나란히 찍었다(observed).
|
|
|
|
```text
|
|
virsh dominfo Max memory: 5242880 KiB ← 돌고 있는 값
|
|
virsh dumpxml --inactive <memory unit='KiB'>4194304</memory> ← 방금 바꾼 설정
|
|
```
|
|
|
|
같은 도메인, 같은 시각, 다른 숫자다. **바꾼 것이 들어갔는지는 `--inactive` 가 답하고, `dominfo` 는 지금 무엇으로 돌고 있는지를 답한다.**
|
|
|
|
**왜 필요한가** — 두 명령이 다른 것을 바꾼다.
|
|
|
|
`setmaxmem` : 상한. 부팅할 때 게스트가 보는 총량
|
|
`setmem` : 현재 할당. 상한 이하여야 한다
|
|
|
|
현재값을 상한보다 크게 줄 수 없으므로 `setmaxmem` 이 먼저다. 거꾸로 치면 두 번째 명령이 상한을 넘는 값을 받아 거부된다. 플래그도 갈린다 — `--config` 는 영구 정의라 다음 부팅부터 먹고, `--live` 는 실행 중인 도메인에 즉시 먹는다. 다만 `setmaxmem --live` 는 게스트가 부팅할 때 메모리 맵을 정하기 때문에 대개 거부된다. 그래서 상한을 바꾸려면 게스트를 껐다 켠다.
|
|
|
|
**문제가 생기면** — ② 의 `free -m` 이 옛 값이면 아직 재부팅하지 않은 것이다. `dominfo` 쪽도 안 바뀌었으면 `--config` 를 빠뜨렸는지 본다.
|
|
|
|
### 2. 위에서부터 내린다
|
|
|
|
**목적** — 애플리케이션과 데이터베이스와 게스트와 호스트를 순서대로 멈춰서 다음 기동에 복구 절차가 돌지 않게 한다.
|
|
|
|
Keycloak 을 먼저 0 으로 내려 클러스터에서 정상 탈퇴시키고, PostgreSQL 을 그다음에 내리고, 게스트를 정상 종료한 뒤 호스트를 끈다.
|
|
|
|
```bash label="[lab host] ① Keycloak 을 0 으로 내리고 파드가 사라질 때까지 기다린다"
|
|
kubectl -n keycloak-lab scale statefulset/keycloak --replicas=0
|
|
kubectl -n keycloak-lab wait --for=delete pod -l app=keycloak --timeout=120s
|
|
```
|
|
|
|
```bash label="[lab host] ② PostgreSQL 을 마지막에, 충분한 시간을 주고 내린다"
|
|
kubectl -n keycloak-lab scale deployment/postgres --replicas=0
|
|
kubectl -n keycloak-lab wait --for=delete pod -l app=postgres --timeout=120s
|
|
```
|
|
|
|
```bash label="[lab host] ③ 게스트를 한 대씩 ACPI 정상 종료한다"
|
|
virsh shutdown kc-lab-1
|
|
virsh shutdown kc-lab-2
|
|
```
|
|
|
|
```bash label="[lab host] ④ 호스트를 끈다"
|
|
sudo systemctl poweroff
|
|
```
|
|
|
|
**예상 결과** — ① 과 ② 의 `wait` 가 각각 파드 삭제를 확인하고 돌아온다. ③ 뒤에는 `virsh list --all` 에서 두 게스트가 `shut off` 로 바뀐다.
|
|
|
|
**왜 필요한가** — ③ 의 `virsh shutdown` 은 게스트 systemd 가 k3s 를 멈추고 k3s 가 컨테이너에 SIGTERM 을 보내는 연쇄다. 유예 시간이 짧으면 PostgreSQL 이 강제 종료되어 다음 기동에 crash recovery 가 돈다. ① 과 ② 로 미리 내려 두면 그 연쇄가 데이터베이스까지 닿지 않는다. 순서를 뒤집어 PostgreSQL 을 먼저 내리면 Keycloak 이 데이터베이스 없이 남아 기동 실패와 재시작을 반복한다.
|
|
|
|
**③ 의 두 줄을 `&&` 로 잇지 않는다.** 두 게스트는 서로 앞뒤가 없고, `A && B` 는 A 가 성공했을 때만 B 를 실행한다. `kc-lab-1` 이 이미 `shut off` 면 `virsh shutdown` 이 `domain is not running` 으로 실패해 `kc-lab-2` 는 켜진 채로 남고, 바로 다음 ④ 가 그 게스트를 강제로 끈다 — 이 단계가 막으려던 바로 그 상태다. 이 실험대는 `virsh shutdown kc-lab-1 && virsh shutdown kc-lab-2` 로 쳤고(observed) 나눈 형태는 치지 않았다(unknown).
|
|
|
|
**문제가 생기면** — `wait` 가 `120s` 안에 안 끝나면 파드가 종료 중에 걸린 것이다. `kubectl -n keycloak-lab get pods -o wide` 로 어느 파드가 어느 노드에서 `Terminating` 인지 보고, 그 상태로 ③ 을 치지 않는다.
|
|
|
|
### 3. 종료의 역순으로 되살린다
|
|
|
|
**목적** — 게스트와 클러스터와 워크로드를 반대 순서로 올려 Keycloak 이 데이터베이스를 찾을 수 있게 한다.
|
|
|
|
게스트를 띄워 노드가 `Ready` 가 되기를 기다린 뒤, PostgreSQL 을 먼저 올리고 Keycloak 을 나중에 올린다.
|
|
|
|
```bash label="[lab host] ① 게스트를 한 대씩 띄운다"
|
|
virsh start kc-lab-1
|
|
virsh start kc-lab-2
|
|
```
|
|
|
|
```bash label="[lab host] ② 노드가 Ready 가 되기를 기다린다"
|
|
kubectl get nodes
|
|
```
|
|
|
|
```bash label="[lab host] ③ PostgreSQL 을 먼저 올리고 기동을 확인한다"
|
|
kubectl -n keycloak-lab scale deployment/postgres --replicas=1
|
|
kubectl -n keycloak-lab rollout status deployment/postgres
|
|
```
|
|
|
|
```bash label="[lab host] ④ Keycloak 을 두 벌로 올린다"
|
|
kubectl -n keycloak-lab scale statefulset/keycloak --replicas=2
|
|
```
|
|
|
|
**예상 결과** — ② 의 `kubectl get nodes` 에 두 노드가 `Ready` 로 나온다. ③ 의 `rollout status` 가 배포 완료로 돌아온 뒤에 ④ 를 친다.
|
|
|
|
**왜 필요한가** — PostgreSQL 이 먼저다. Keycloak 이 데이터베이스 없이 뜨면 기동에 실패하기 때문이다. 그리고 스케일을 0 으로 내려 둔 것은 자동으로 복구되지 않는다. 게스트를 켜고 노드가 `Ready` 가 돼도 파드 수는 0 그대로이므로 ③ 과 ④ 를 명시적으로 쳐야 한다. ① 을 두 줄로 나눈 까닭은 2번의 ③ 과 같다 — `kc-lab-1` 이 이미 `running` 이면 `virsh start` 가 `domain is already active` 로 실패해 `kc-lab-2` 가 안 뜨고, 그러면 ② 에 노드가 하나만 나와 게스트를 안 띄운 것인지 노드가 안 붙은 것인지 갈리지 않는다.
|
|
|
|
**문제가 생기면** — ② 에서 노드가 `Ready` 로 안 올라오면 게스트 안의 k3s 유닛부터 본다. ③ 의 `rollout status` 가 멈춰 있으면 PostgreSQL 이 crash recovery 중일 수 있으니 아래 확인 방법의 `postmaster.pid` 를 함께 본다.
|
|
|
|
## 구성 값
|
|
|
|
기준 배치는 2026-09-03 에 구축이 끝난 상태다.
|
|
|
|
| 게스트 | 역할 | vCPU · 메모리 |
|
|
|---|---|---|
|
|
| `kc-lab-1` | k3s server | 2 · 3584M |
|
|
| `kc-lab-2` | k3s agent | 2 · 2560M |
|
|
|
|
이 실험대는 그 뒤에 호스트를 8GB 에서 12GB 로 물리 증설했고, 위 1번의 방법으로 게스트 메모리를 다시 나눴다. 게스트를 다시 만들거나 디스크를 손댈 일은 전혀 없었다. 1번의 예에 쓴 `5120M` 이 그렇게 올린 값이다.
|
|
|
|
내리고 올리는 워크로드는 둘이다.
|
|
|
|
| 리소스 | 내릴 때 | 올릴 때 |
|
|
|---|---|---|
|
|
| `statefulset/keycloak` | 0 (먼저) | 2 (나중) |
|
|
| `deployment/postgres` | 0 (나중) | 1 (먼저) |
|
|
|
|
네임스페이스는 `keycloak-lab` 이고 `wait` 의 제한 시간은 양쪽 다 `120s` 다.
|
|
|
|
## 확인 방법
|
|
|
|
**무엇을 확인하는가** — 어느 게스트가 server 이고 어느 게스트가 agent 인지, 그리고 PostgreSQL 이 깨끗이 내려갔는지.
|
|
|
|
```bash label="[lab host] 노드의 control-plane 라벨을 열로 뽑는다"
|
|
kubectl get nodes -o custom-columns=\
|
|
'NODE:.metadata.name,CP:.metadata.labels.node-role\.kubernetes\.io/control-plane'
|
|
```
|
|
|
|
```bash label="[lab host] 게스트가 다시 뜬 뒤 postmaster.pid 가 있는지 본다"
|
|
ssh kc-lab-2 'sudo ls /var/lib/rancher/k3s/storage/*postgres-data*/pgdata/postmaster.pid'
|
|
```
|
|
|
|
**어디를 봐야 하는가** — 첫 명령에서 `kc-lab-1` 의 `CP` 열에 값이 있고 `kc-lab-2` 는 비어 있다. 둘째 명령은 파일이 없다고 나와야 한다. `postmaster.pid` 가 보이면 비정상 종료였고 다음 기동에 복구 절차가 실행된다.
|
|
|
|
**이 결과가 의미하는 것** — 둘째 명령은 게스트가 떠 있어야 읽을 수 있으므로 3번의 ① 뒤에 친다. 첫 명령이 필요한 까닭은 두 노드를 같은 것으로 다루면 안 되기 때문이다. `kc-lab-2` 를 죽이면 그 노드의 워크로드만 사라지고 클러스터 제어는 살아 있지만, `kc-lab-1` 을 죽이면 `kubectl` 이 안 되고 DNS 와 인그레스도 함께 사라진다. 노드 상실 실험은 agent 를 죽이는 것이고, server 를 죽이는 것은 컨트롤 플레인 상실이라 성격이 다르다.
|
|
|
|
## 이 절차가 감당하지 않는 것
|
|
|
|
관측 스택을 어느 노드에 둘지는 여기서 정하지 않는다. server 와 agent 가 왜 다른지만 위에서 한 번 적었고, 그래서 관측을 server 쪽에 두는 판단은 게스트 두 대를 고른 결정과 Prometheus 를 올리는 편이 받는다.
|
|
|
|
이 절차의 근거는 제9부의 개념 문서이고, 그 문서의 실측 스냅샷은 2026-09-03, 재배분 기록은 2026-09-11 이다. 제7부가 2026-09-10 에 잰 게스트 메모리 값과 날짜가 엇갈리므로, 게스트 메모리 수치는 각각 그 시점의 것으로 읽는다.
|
|
|
|
이 순서대로 다시 돌려 검증하지는 않았다. 위 명령들은 원본이 적어 둔 순서를 그대로 옮긴 것이고, 한 번 더 껐다 켜서 같은 출력이 나오는지는 확인하지 못했다.
|
|
|
|
<!-- body:end -->
|