7720 lines
149 KiB
Markdown
7720 lines
149 KiB
Markdown
# KVM/QEMU 가상화 SSOT — vCPU·메모리·네트워크·스토리지가 물리 자원에 닿기까지
|
||
|
||
이 문서가 프로젝트 `virtualization` 의 SSOT 다. 네 부로 나뉘고, 부마다 출발점이 다른 문서 한 편이었다.
|
||
|
||
| 부 | 절 | 무엇을 따라가는가 |
|
||
|---|---|---|
|
||
| 제1부 — CPU 가상화 | §1~§28 | vCPU 가 Host 의 물리 CPU 에서 실행되기까지 |
|
||
| 제2부 — 메모리 가상화 | §29~§88 | Guest 의 GVA 가 Host RAM 의 HPA 에 닿기까지 |
|
||
| 제3부 — 네트워크 가상화 | §89~§126 | Guest 의 패킷이 Physical NIC 로 나가고 되돌아오기까지 |
|
||
| 제4부 — 스토리지 가상화 | §127~§177 | Guest 의 `write()`·`fsync()` 가 물리 NVMe 에 닿기까지 |
|
||
|
||
절 번호는 문서 전체에서 이어진다. 제2·3·4부는 각각 다른 파일로 쓴 SSOT 를 반입하면서 heading 단계를 한 칸 내리고 절 번호를 이 문서의 번호로 옮긴 것이고, heading 이 아닌 줄은 한 글자도 바꾸지 않았다. 반입 전 번호는 제2부가 1~60, 제3부가 1~38, 제4부가 0~50 이었다.
|
||
|
||
여기에는 이 테스트 Host 에서 잰 값이 하나도 없다. 부마다 끝에 OPEN QUESTION 이 있고, 그 목록이 이 문서가 아직 확인하지 않은 것이다.
|
||
|
||
---
|
||
|
||
# 제1부 — CPU 가상화
|
||
|
||
## 1. 이 문서의 범위
|
||
|
||
이 문서는 KVM/QEMU 기반 가상화에서 **VM의 vCPU가 Host의 물리 CPU에서 실제로 실행되기까지의 CPU 가상화 경로**를 정리한다.
|
||
|
||
현재 목적은 Keycloak 멀티 노드 실험 환경을 만들기 위해 KVM 기반 VM을 사용하면서, 실험 결과가 Keycloak/저장소 문제인지 Host/가상화 자원 문제인지 구분할 수 있는 기반을 만드는 것이다.
|
||
|
||
제1부는 CPU 가상화만 다룬다. 메모리는 제2부, 네트워크는 제3부, 스토리지는 제4부에 있다. 셋은 이 부를 쓴 뒤에 따로 쓴 SSOT 를 반입한 것이라 서술의 출발점이 부마다 다르다.
|
||
|
||
다음 영역은 이 문서 어느 부에도 없다.
|
||
|
||
- PCIe / VFIO / IOMMU 상세
|
||
- K3s 네트워크 및 컨테이너 런타임 상세
|
||
|
||
---
|
||
|
||
## 2. 전체 구조
|
||
|
||
VM을 `virsh`로 시작했을 때 CPU 실행 경로를 크게 보면 다음과 같다.
|
||
|
||
```text
|
||
사용자
|
||
|
|
||
| virsh start <vm>
|
||
v
|
||
virsh
|
||
|
|
||
| libvirt API
|
||
v
|
||
libvirt
|
||
|
|
||
| QEMU 프로세스 실행/제어
|
||
v
|
||
QEMU Process
|
||
|
|
||
+-- main/control thread
|
||
+-- vCPU thread 0
|
||
+-- vCPU thread 1
|
||
+-- ...
|
||
|
|
||
| open("/dev/kvm"), ioctl()
|
||
v
|
||
/dev/kvm
|
||
|
|
||
v
|
||
KVM Core
|
||
|
|
||
v
|
||
kvm_intel
|
||
|
|
||
v
|
||
Intel VMX
|
||
|
|
||
v
|
||
Physical CPU / Logical CPU
|
||
```
|
||
|
||
핵심은 `virsh`가 VM의 CPU를 직접 실행하는 프로그램이 아니라는 점이다.
|
||
|
||
`virsh`는 VM을 관리하는 CLI이고, 실제 VM 실행은 QEMU 프로세스가 담당한다. QEMU는 `/dev/kvm`을 통해 Linux Kernel의 KVM 기능을 사용하고, KVM은 Intel 환경에서 `kvm_intel`을 통해 CPU의 VMX 기능을 사용한다.
|
||
|
||
---
|
||
|
||
## 3. 각 구성요소의 역할
|
||
|
||
### 3.1 virsh
|
||
|
||
`virsh`는 libvirt 기반 가상 머신을 관리하기 위한 CLI다.
|
||
|
||
예:
|
||
|
||
```bash
|
||
virsh start ubuntu-vm
|
||
virsh list
|
||
virsh shutdown ubuntu-vm
|
||
```
|
||
|
||
`virsh start`를 실행했다고 해서 `virsh` 프로세스가 VM을 계속 실행하는 것은 아니다.
|
||
|
||
개념적인 흐름은 다음과 같다.
|
||
|
||
```text
|
||
virsh start ubuntu-vm
|
||
|
|
||
v
|
||
libvirt
|
||
|
|
||
v
|
||
QEMU Process 실행
|
||
```
|
||
|
||
명령 전달이 끝나면 `virsh` 자체는 종료될 수 있고, VM을 실제로 실행하는 QEMU 프로세스는 계속 살아 있다.
|
||
|
||
### 3.2 libvirt
|
||
|
||
libvirt는 VM lifecycle과 구성을 관리하는 계층이다.
|
||
|
||
예를 들어 VM 정의에 다음과 같은 정보가 있다.
|
||
|
||
```text
|
||
RAM: 8 GiB
|
||
vCPU: 4
|
||
Disk: ...
|
||
Network: ...
|
||
```
|
||
|
||
libvirt는 이 정의를 바탕으로 QEMU를 적절한 옵션과 함께 실행하고 관리한다.
|
||
|
||
### 3.3 QEMU
|
||
|
||
QEMU는 Host userspace에서 실행되는 실제 프로세스다.
|
||
|
||
4 vCPU VM이라면 개념적으로 다음과 같은 구조가 만들어진다.
|
||
|
||
```text
|
||
QEMU Process
|
||
|
|
||
+-- Main / Control Thread
|
||
+-- vCPU Thread 0
|
||
+-- vCPU Thread 1
|
||
+-- vCPU Thread 2
|
||
+-- vCPU Thread 3
|
||
```
|
||
|
||
KVM 가속을 사용할 때 Guest의 일반 CPU 명령을 QEMU가 하나씩 소프트웨어로 번역해서 실행하는 것이 핵심 경로는 아니다.
|
||
|
||
QEMU의 vCPU thread가 KVM을 통해 Guest 실행을 요청하면 Guest 코드는 VMX를 이용해 실제 CPU에서 직접 실행된다.
|
||
|
||
### 3.4 /dev/kvm
|
||
|
||
`/dev/kvm`은 프로세스가 아니다.
|
||
|
||
Linux가 userspace 프로그램에 KVM API를 노출하는 character device 인터페이스다.
|
||
|
||
QEMU는 대략 다음과 같은 방식으로 KVM에 접근한다.
|
||
|
||
```text
|
||
QEMU
|
||
|
|
||
| open("/dev/kvm")
|
||
| ioctl(...)
|
||
v
|
||
/dev/kvm
|
||
|
|
||
v
|
||
KVM
|
||
```
|
||
|
||
대표적인 KVM API에는 다음과 같은 동작이 있다.
|
||
|
||
```text
|
||
KVM_CREATE_VM
|
||
KVM_CREATE_VCPU
|
||
KVM_SET_USER_MEMORY_REGION
|
||
KVM_RUN
|
||
```
|
||
|
||
즉 `/dev/kvm`은 QEMU와 Kernel KVM 사이의 진입점이다.
|
||
|
||
### 3.5 KVM Core
|
||
|
||
KVM Core는 Linux Kernel 내부의 공통 가상화 로직이다.
|
||
|
||
CPU 제조사에 독립적인 공통 부분과 제조사별 구현을 분리해서 볼 수 있다.
|
||
|
||
```text
|
||
KVM Core
|
||
|
|
||
+--------+--------+
|
||
| |
|
||
kvm_intel kvm_amd
|
||
| |
|
||
VMX SVM
|
||
| |
|
||
Intel CPU AMD CPU
|
||
```
|
||
|
||
### 3.6 kvm_intel
|
||
|
||
Intel CPU 환경에서 KVM이 Intel의 하드웨어 가상화 기능을 사용할 수 있게 하는 커널 모듈이다.
|
||
|
||
AMD 환경에서는 대응되는 `kvm_amd`가 사용된다.
|
||
|
||
### 3.7 VMX
|
||
|
||
VMX(Virtual Machine Extensions)는 Intel CPU 자체가 제공하는 하드웨어 가상화 기능이다.
|
||
|
||
VMX는 프로세스나 Linux 커널 모듈이 아니다.
|
||
|
||
```text
|
||
VMX = Intel CPU의 하드웨어 가상화 기능
|
||
```
|
||
|
||
VMX에서는 크게 다음 실행 영역을 구분한다.
|
||
|
||
```text
|
||
VMX Root Operation
|
||
Host / Hypervisor 측
|
||
|
||
VMX Non-Root Operation
|
||
Guest 측
|
||
```
|
||
|
||
여기서 `Root`는 Linux의 root 사용자와 관계가 없다.
|
||
|
||
Guest Linux에서 root 권한으로 프로그램을 실행하더라도 Guest 전체는 VMX 관점에서 여전히 Non-Root Operation에서 실행된다.
|
||
|
||
---
|
||
|
||
## 4. vCPU와 vCPU Thread
|
||
|
||
VM에 다음과 같이 4 vCPU를 설정했다고 가정한다.
|
||
|
||
```text
|
||
VM
|
||
|
|
||
+-- vCPU 0
|
||
+-- vCPU 1
|
||
+-- vCPU 2
|
||
+-- vCPU 3
|
||
```
|
||
|
||
Guest OS는 이를 자신의 CPU처럼 인식한다.
|
||
|
||
Host에서는 각 vCPU의 실행 주체에 대응하는 QEMU vCPU thread가 존재한다.
|
||
|
||
```text
|
||
Guest Host
|
||
|
||
vCPU 0 ------------> QEMU vCPU Thread 0
|
||
vCPU 1 ------------> QEMU vCPU Thread 1
|
||
vCPU 2 ------------> QEMU vCPU Thread 2
|
||
vCPU 3 ------------> QEMU vCPU Thread 3
|
||
```
|
||
|
||
중요한 점은 다음과 같다.
|
||
|
||
> VM에 4 vCPU를 할당한다는 것은 물리 CPU 4개를 VM 전용으로 떼어 놓는다는 의미가 아니다.
|
||
|
||
CPU pinning이나 별도의 CPU isolation을 하지 않은 일반적인 환경에서 vCPU thread는 Host Linux Scheduler의 스케줄링 대상이다.
|
||
|
||
---
|
||
|
||
## 5. Host Linux Scheduler와 실제 CPU
|
||
|
||
예를 들어 Host가 6 Core / 12 Thread라면 Linux에서는 일반적으로 12개의 logical CPU가 스케줄링 대상으로 보인다.
|
||
|
||
```text
|
||
CPU0 CPU1 CPU2 CPU3 ... CPU11
|
||
```
|
||
|
||
QEMU vCPU thread도 다른 Host thread와 마찬가지로 Linux Scheduler가 실행할 logical CPU를 결정한다.
|
||
|
||
```text
|
||
Chrome Thread ----+
|
||
Java Thread ------+--> Linux Scheduler --> CPU0 ... CPU11
|
||
QEMU vCPU Thread -+
|
||
```
|
||
|
||
따라서 시간에 따라 같은 vCPU thread가 서로 다른 logical CPU에서 실행될 수도 있다.
|
||
|
||
```text
|
||
T1: vCPU Thread 0 -> CPU7
|
||
T2: 다른 Thread -> CPU7
|
||
T3: vCPU Thread 0 -> CPU3
|
||
```
|
||
|
||
CPU pinning을 적용하면 특정 logical CPU 집합으로 실행 위치를 제한할 수 있다.
|
||
|
||
---
|
||
|
||
## 6. KVM_RUN과 Guest 실행
|
||
|
||
QEMU의 vCPU thread가 Guest vCPU를 실행하려면 KVM에 `KVM_RUN`을 요청한다.
|
||
|
||
개념적으로 다음과 같다.
|
||
|
||
```c
|
||
ioctl(vcpu_fd, KVM_RUN, 0);
|
||
```
|
||
|
||
실행 흐름은 다음과 같다.
|
||
|
||
```text
|
||
QEMU vCPU Thread
|
||
|
|
||
| KVM_RUN
|
||
v
|
||
KVM
|
||
|
|
||
| VM Entry
|
||
v
|
||
Physical CPU
|
||
|
|
||
+--> Guest Code
|
||
+--> Guest Code
|
||
+--> Guest Code
|
||
+--> ...
|
||
```
|
||
|
||
이 상태에서 Guest의 일반적인 명령어는 실제 CPU에서 직접 실행된다.
|
||
|
||
예:
|
||
|
||
```text
|
||
ADD
|
||
MOV
|
||
SUB
|
||
CMP
|
||
JMP
|
||
```
|
||
|
||
일반 명령마다 QEMU까지 돌아갔다가 다시 실행하는 구조가 아니다.
|
||
|
||
---
|
||
|
||
## 7. VM Entry와 VM Exit
|
||
|
||
### 7.1 VM Entry
|
||
|
||
KVM이 CPU에게 Guest 실행을 시작하거나 재개하도록 하는 전환이다.
|
||
|
||
```text
|
||
KVM
|
||
|
|
||
| VM Entry
|
||
v
|
||
Guest 실행
|
||
```
|
||
|
||
### 7.2 VM Exit
|
||
|
||
VM Exit은 VM 종료가 아니다.
|
||
|
||
다음과 같은 의미다.
|
||
|
||
> CPU가 VMX Non-Root에서 Guest를 실행하다가 Hypervisor가 개입해야 하는 조건을 만나 Guest 실행에서 빠져나와 VMX Root/KVM 쪽으로 제어권을 넘기는 것.
|
||
|
||
따라서 다음과는 다르다.
|
||
|
||
```text
|
||
VM Exit != VM shutdown
|
||
VM Exit != QEMU 종료
|
||
VM Exit != VM 메모리 제거
|
||
VM Exit != VM 환경 정리
|
||
```
|
||
|
||
VM은 그대로 살아 있고, 필요한 처리가 끝나면 다시 VM Entry를 통해 Guest 실행을 이어갈 수 있다.
|
||
|
||
---
|
||
|
||
## 8. 무엇이 실제로 VM Exit을 발생시키는가
|
||
|
||
Intel VMX에는 VMCS(Virtual Machine Control Structure)가 있으며, Hypervisor는 VM-Execution Control 등을 통해 어떤 동작을 가로챌지 설정한다.
|
||
|
||
따라서 "특권 명령이면 전부 VM Exit" 또는 "root가 실행하면 VM Exit" 같은 규칙은 맞지 않는다.
|
||
|
||
VM Exit 여부는 VMX control 설정과 해당 동작의 종류에 따라 결정된다.
|
||
|
||
### 8.1 HLT
|
||
|
||
Guest OS에 실행할 작업이 없으면 kernel idle path에서 `HLT` 계열 동작이 사용될 수 있다.
|
||
|
||
KVM/VMX가 HLT exiting을 사용한다면 다음과 같은 흐름이 가능하다.
|
||
|
||
```text
|
||
Guest Kernel
|
||
|
|
||
| HLT
|
||
v
|
||
VM Exit
|
||
|
|
||
v
|
||
KVM
|
||
|
|
||
+--> vCPU가 당장 할 일이 없음을 처리
|
||
```
|
||
|
||
vCPU thread를 block/sleep시킬 수 있으므로 Host의 logical CPU를 계속 점유할 필요가 없다.
|
||
|
||
### 8.2 I/O Port 접근 - IN / OUT
|
||
|
||
x86의 `IN`, `OUT` 명령으로 I/O port에 접근하는 경우 Hypervisor가 이를 가로채도록 설정할 수 있다.
|
||
|
||
예:
|
||
|
||
```asm
|
||
out 0x3f8, al
|
||
```
|
||
|
||
개념적으로:
|
||
|
||
```text
|
||
Guest
|
||
|
|
||
| OUT
|
||
v
|
||
VM Exit
|
||
|
|
||
v
|
||
KVM
|
||
|
|
||
| userspace device emulation이 필요하다면
|
||
v
|
||
KVM_RUN return
|
||
|
|
||
v
|
||
QEMU
|
||
```
|
||
|
||
QEMU가 필요한 가상 장치 동작을 처리한 뒤 다시 `KVM_RUN`을 호출할 수 있다.
|
||
|
||
### 8.3 CPUID
|
||
|
||
`CPUID`는 CPU vendor와 feature 등 CPU 정보를 조회하는 x86 명령이다.
|
||
|
||
Guest에게 보여줄 CPU 모델과 feature는 가상화 설정에 따라 Host CPU와 다를 수 있다.
|
||
|
||
따라서 CPUID 실행을 가로채서 Guest에 노출할 CPU 정보를 가상화할 수 있다.
|
||
|
||
```text
|
||
Guest
|
||
|
|
||
| CPUID
|
||
v
|
||
VM Exit
|
||
|
|
||
v
|
||
KVM
|
||
|
|
||
| 가상 CPU 정보 처리
|
||
v
|
||
VM Entry
|
||
```
|
||
|
||
### 8.4 Control Register 접근
|
||
|
||
Guest kernel도 CR0, CR3, CR4 등의 control register를 사용한다.
|
||
|
||
예를 들어 CR3는 페이지 테이블과 관련된 CPU 상태에 사용된다.
|
||
|
||
```asm
|
||
mov cr3, rax
|
||
```
|
||
|
||
하지만 모든 CR 접근이 항상 VM Exit을 발생시키는 것은 아니다.
|
||
|
||
VMX control을 통해 어떤 접근을 가로챌지 결정할 수 있으며, 현대 가상화에서는 성능을 위해 불필요한 Exit을 줄이는 것이 중요하다.
|
||
|
||
### 8.5 MSR 접근
|
||
|
||
CPU에는 MSR(Model-Specific Register)이 있으며 다음 명령으로 접근할 수 있다.
|
||
|
||
```text
|
||
RDMSR
|
||
WRMSR
|
||
```
|
||
|
||
특정 MSR 접근을 Hypervisor가 intercept하도록 설정했다면 VM Exit이 발생할 수 있다.
|
||
|
||
### 8.6 Exception
|
||
|
||
Page Fault, Breakpoint, Debug Exception 등의 CPU exception도 무조건 VM Exit하는 것은 아니다.
|
||
|
||
VMX의 Exception Bitmap 등의 설정에 따라 Guest가 직접 처리하게 할 수도 있고 Hypervisor가 가로챌 수도 있다.
|
||
|
||
### 8.7 External Interrupt
|
||
|
||
Guest가 명령을 실행하는 도중 Host가 처리해야 할 physical interrupt가 발생할 수도 있다.
|
||
|
||
VMX interrupt control 설정에 따라 Guest 실행에서 빠져나와 Host/KVM이 처리해야 하는 경우 VM Exit이 발생할 수 있다.
|
||
|
||
---
|
||
|
||
## 9. VM Exit 이후 처리
|
||
|
||
VM Exit이 발생하면 KVM은 Exit Reason을 확인한다.
|
||
|
||
```text
|
||
Guest
|
||
|
|
||
| VM Exit
|
||
v
|
||
KVM
|
||
|
|
||
| Exit Reason 확인
|
||
|
|
||
+-----------------------+
|
||
| |
|
||
| KVM에서 처리 가능 | QEMU 처리 필요
|
||
v v
|
||
KVM 처리 KVM_RUN return
|
||
| |
|
||
| QEMU
|
||
| |
|
||
| 필요한 처리
|
||
| |
|
||
| KVM_RUN
|
||
| |
|
||
+-----------+-----------+
|
||
|
|
||
v
|
||
VM Entry
|
||
|
|
||
v
|
||
Guest 실행 재개
|
||
```
|
||
|
||
중요한 점은 다음과 같다.
|
||
|
||
> VM Exit이 발생했다고 항상 QEMU userspace까지 돌아가는 것은 아니다.
|
||
|
||
KVM이 Kernel 안에서 처리할 수 있는 Exit은 처리 후 바로 Guest로 재진입할 수 있다.
|
||
|
||
QEMU의 userspace device emulation 등 userspace 처리가 필요한 경우에만 `KVM_RUN`이 반환되고 QEMU가 개입한다.
|
||
|
||
---
|
||
|
||
## 10. Guest가 idle이면 물리 CPU는 어떻게 되는가
|
||
|
||
VM에 4 vCPU를 설정했다고 해서 4개의 Host logical CPU가 계속 예약되는 것은 아니다.
|
||
|
||
Guest가 할 일이 없다면 vCPU가 idle 상태에 들어갈 수 있다.
|
||
|
||
개념적인 흐름:
|
||
|
||
```text
|
||
Guest에 실행할 작업 없음
|
||
|
|
||
v
|
||
Guest Kernel idle
|
||
|
|
||
v
|
||
HLT 등
|
||
|
|
||
v
|
||
VM Exit
|
||
|
|
||
v
|
||
KVM
|
||
|
|
||
v
|
||
vCPU Thread block/sleep
|
||
```
|
||
|
||
이때 Host Scheduler는 물리 CPU를 다른 Host workload에 사용할 수 있다.
|
||
|
||
나중에 timer, interrupt, I/O completion 등 vCPU를 다시 실행해야 할 이유가 생기면:
|
||
|
||
```text
|
||
vCPU wake-up
|
||
|
|
||
v
|
||
runnable
|
||
|
|
||
v
|
||
Host Linux Scheduler
|
||
|
|
||
v
|
||
Logical CPU에서 vCPU Thread 실행
|
||
|
|
||
v
|
||
KVM / VM Entry
|
||
|
|
||
v
|
||
Guest 실행 재개
|
||
```
|
||
|
||
따라서 VM이 idle인 동안 Host가 CPU 자원을 다른 작업에 사용하는 것이 가능하다.
|
||
|
||
---
|
||
|
||
## 11. VM의 4 vCPU는 정확히 무엇을 의미하는가
|
||
|
||
4 vCPU는 일반적으로 다음 의미에 가깝다.
|
||
|
||
> Guest OS가 최대 4개의 CPU 실행 흐름을 가질 수 있도록 4개의 가상 CPU 실행 컨텍스트를 제공한다.
|
||
|
||
다음 의미가 아니다.
|
||
|
||
> Host의 물리 CPU 4개를 VM이 영구적으로 소유한다.
|
||
|
||
Host CPU가 부족하면 QEMU vCPU thread와 다른 Host workload가 같은 logical CPU 자원을 두고 경쟁할 수 있다.
|
||
|
||
---
|
||
|
||
## 12. CPU contention과 overcommit
|
||
|
||
예를 들어 Host에 12 logical CPU가 있다고 하자.
|
||
|
||
```text
|
||
Host: 12 logical CPUs
|
||
|
||
VM A: 8 vCPU
|
||
VM B: 8 vCPU
|
||
VM C: 8 vCPU
|
||
VM D: 8 vCPU
|
||
```
|
||
|
||
총 32 vCPU가 12개의 logical CPU 위에서 실행될 수 있다.
|
||
|
||
모든 VM이 동시에 CPU를 많이 사용하면 vCPU thread끼리 Host CPU 시간을 두고 경쟁한다.
|
||
|
||
```text
|
||
32 vCPU threads
|
||
|
|
||
v
|
||
Linux Scheduler
|
||
|
|
||
v
|
||
12 logical CPUs
|
||
```
|
||
|
||
이런 상황에서는 Guest application이 느려졌더라도 원인이 application 자체가 아니라 Host CPU contention일 수 있다.
|
||
|
||
---
|
||
|
||
## 13. Steal Time
|
||
|
||
Guest Linux에서 `top` 등의 CPU 지표를 볼 때 `st`(steal time)를 확인할 수 있다.
|
||
|
||
개념적으로 steal time은 다음 상황을 나타내는 중요한 단서다.
|
||
|
||
```text
|
||
Guest vCPU는 실행할 작업이 있음
|
||
|
|
||
v
|
||
Host에서 vCPU Thread가 CPU를 필요로 함
|
||
|
|
||
v
|
||
다른 workload 때문에 즉시 실행되지 못함
|
||
```
|
||
|
||
높은 steal time은 가상화 환경에서 Host CPU contention이나 CPU overcommit을 의심할 수 있는 지표 중 하나다.
|
||
|
||
단, steal time 하나만으로 원인을 확정해서는 안 되며 Host CPU saturation, run queue, affinity, workload 등을 함께 확인해야 한다.
|
||
|
||
---
|
||
|
||
## 14. 실제 Linux에서 확인할 수 있는 것
|
||
|
||
### 14.1 VMX/SVM 지원 확인
|
||
|
||
Intel:
|
||
|
||
```bash
|
||
grep -E 'vmx|svm' /proc/cpuinfo
|
||
```
|
||
|
||
Intel에서는 `vmx`, AMD에서는 `svm` flag를 확인할 수 있다.
|
||
|
||
### 14.2 KVM 모듈 확인
|
||
|
||
```bash
|
||
lsmod | grep kvm
|
||
```
|
||
|
||
Intel 환경에서는 일반적으로 다음 모듈을 확인할 수 있다.
|
||
|
||
```text
|
||
kvm_intel
|
||
kvm
|
||
```
|
||
|
||
### 14.3 /dev/kvm 확인
|
||
|
||
```bash
|
||
ls -l /dev/kvm
|
||
```
|
||
|
||
QEMU가 KVM API에 접근하는 character device가 존재하는지 확인한다.
|
||
|
||
### 14.4 실행 중인 VM 확인
|
||
|
||
```bash
|
||
virsh list
|
||
```
|
||
|
||
### 14.5 QEMU 프로세스 확인
|
||
|
||
```bash
|
||
ps -ef | grep '[q]emu'
|
||
```
|
||
|
||
`virsh`가 아니라 QEMU 프로세스가 실제 VM lifecycle 동안 살아 있는 것을 확인할 수 있다.
|
||
|
||
### 14.6 QEMU thread 확인
|
||
|
||
```bash
|
||
ps -T -p <QEMU_PID>
|
||
```
|
||
|
||
또는:
|
||
|
||
```bash
|
||
top -H -p <QEMU_PID>
|
||
```
|
||
|
||
환경/QEMU 버전에 따라 이름은 다를 수 있지만 vCPU 관련 thread를 Host에서 관찰할 수 있다.
|
||
|
||
### 14.7 thread가 실행되는 Host CPU 확인
|
||
|
||
```bash
|
||
ps -eLo pid,tid,psr,pcpu,comm | grep qemu
|
||
```
|
||
|
||
`PSR`을 통해 thread가 최근 실행된 logical CPU를 관찰할 수 있다.
|
||
|
||
이는 vCPU가 물리 CPU에 영구 고정되어 있다는 의미가 아니며, pinning을 하지 않았다면 스케줄링에 따라 달라질 수 있다.
|
||
|
||
### 14.8 Guest의 steal time 확인
|
||
|
||
Guest 내부:
|
||
|
||
```bash
|
||
top
|
||
```
|
||
|
||
또는 CPU 통계를 제공하는 다른 Linux 도구에서 steal time을 확인한다.
|
||
|
||
### 14.9 KVM Exit 관찰
|
||
|
||
환경이 지원하면 `perf kvm`을 이용해 KVM 관련 runtime 통계를 확인할 수 있다.
|
||
|
||
예:
|
||
|
||
```bash
|
||
sudo perf kvm stat live
|
||
```
|
||
|
||
지원되는 명령과 표시되는 Exit reason은 kernel, perf 버전, CPU architecture 및 설정에 따라 다를 수 있으므로 실제 환경에서는 다음을 함께 확인한다.
|
||
|
||
```bash
|
||
perf kvm --help
|
||
```
|
||
|
||
필요하면 KVM tracepoint를 이용한 별도 tracing도 검토한다.
|
||
|
||
---
|
||
|
||
## 15. CPU 가상화 관점에서 장애를 보는 방법
|
||
|
||
VM 안의 application이 느릴 때 바로 application 문제라고 결론 내리지 않는다.
|
||
|
||
CPU 실행 경로를 기준으로 다음 계층을 분리한다.
|
||
|
||
```text
|
||
Application
|
||
|
|
||
v
|
||
Guest OS
|
||
|
|
||
v
|
||
vCPU
|
||
|
|
||
v
|
||
QEMU vCPU Thread
|
||
|
|
||
v
|
||
Host Linux Scheduler
|
||
|
|
||
v
|
||
KVM / VMX
|
||
|
|
||
v
|
||
Physical CPU
|
||
```
|
||
|
||
확인할 수 있는 관점은 다음과 같다.
|
||
|
||
#### Guest
|
||
|
||
- application CPU usage
|
||
- load average
|
||
- steal time
|
||
- vCPU 수
|
||
|
||
#### Host / QEMU
|
||
|
||
- QEMU vCPU thread CPU usage
|
||
- Host CPU saturation
|
||
- run queue
|
||
- vCPU thread scheduling
|
||
- CPU affinity / pinning
|
||
- CPU overcommit
|
||
|
||
#### KVM
|
||
|
||
- VM Exit 빈도
|
||
- Exit reason
|
||
- 특정 workload에서 Exit이 과도하게 증가하는지
|
||
|
||
#### Hardware
|
||
|
||
- VMX/SVM 활성화
|
||
- Host CPU topology
|
||
- 실제 logical CPU 수
|
||
|
||
---
|
||
|
||
## 16. 현재 Keycloak/K3s 실험과의 관계
|
||
|
||
이 CPU 가상화 자체가 Keycloak refresh token 경쟁의 원인은 아니다.
|
||
|
||
현재 원래 검증하려는 구조는 다음과 같다.
|
||
|
||
```text
|
||
Client
|
||
|
|
||
v
|
||
Nginx / Load Balancer
|
||
|
|
||
v
|
||
K3s
|
||
|
|
||
+--> Keycloak Node 1
|
||
|
|
||
+--> Keycloak Node 2
|
||
|
|
||
v
|
||
Session / Token State
|
||
|
|
||
+------+------+
|
||
| |
|
||
PostgreSQL Redis
|
||
```
|
||
|
||
테스트 환경에서는 이 구조 아래에 KVM 계층이 추가된다.
|
||
|
||
```text
|
||
Physical Host
|
||
|
|
||
+-- Host Nginx
|
||
|
|
||
+-- VM 1
|
||
| |
|
||
| +-- K3s Node / Keycloak
|
||
|
|
||
+-- VM 2
|
||
|
|
||
+-- K3s Node / Keycloak
|
||
```
|
||
|
||
따라서 테스트 결과를 해석할 때 다음 원인을 분리해야 한다.
|
||
|
||
```text
|
||
Keycloak refresh/session 동시성
|
||
PostgreSQL contention/locking
|
||
Redis 상태 관리
|
||
K3s resource scheduling
|
||
VM vCPU scheduling
|
||
Host CPU saturation
|
||
Nginx/LB
|
||
```
|
||
|
||
KVM CPU 가상화를 이해하는 목적은 refresh token 경쟁을 KVM으로 해결하기 위해서가 아니다.
|
||
|
||
> Keycloak 멀티 노드 실험에서 발생한 지연이나 실패가 application/storage 문제인지, VM/Host 자원 문제인지 구분할 수 있도록 실험 기반을 이해하기 위해서다.
|
||
|
||
---
|
||
|
||
## 17. 동시성 테스트와 부하 테스트를 분리해야 한다
|
||
|
||
### 17.1 동시성 테스트
|
||
|
||
Refresh token 경쟁이나 동일 세션의 상태 갱신 문제를 확인하려면 반드시 Host CPU를 100%까지 밀 필요는 없다.
|
||
|
||
예:
|
||
|
||
```text
|
||
Same User
|
||
Same Session
|
||
Same Refresh Token
|
||
|
|
||
+--> Request A --> Node 1
|
||
|
|
||
+--> Request B --> Node 2
|
||
거의 동시에
|
||
```
|
||
|
||
핵심은 높은 전체 트래픽이 아니라 **동일 상태에 대한 동시 접근**이다.
|
||
|
||
사용자 한 명이라도 race condition은 발생할 수 있다.
|
||
|
||
사용자와 트래픽이 많아지면 이런 경쟁이 실제 운영에서 발생할 확률이 높아질 뿐이다.
|
||
|
||
### 17.2 Load / Stress Test
|
||
|
||
별도로 전체 부하를 증가시키면서 시스템의 자원 한계를 확인한다.
|
||
|
||
예:
|
||
|
||
```text
|
||
100 RPS
|
||
|
|
||
500 RPS
|
||
|
|
||
1000 RPS
|
||
|
|
||
...
|
||
```
|
||
|
||
관찰 대상:
|
||
|
||
- Keycloak latency
|
||
- PostgreSQL latency/connection/lock
|
||
- Redis latency
|
||
- Host CPU
|
||
- Guest steal time
|
||
- K3s CPU throttling
|
||
- vCPU contention
|
||
|
||
동시성 문제와 자원 포화 문제를 같은 실험에서 동시에 발생시키면 원인을 분리하기 어려워진다.
|
||
|
||
---
|
||
|
||
## 18. Bare-metal K3s와 VM 기반 K3s의 차이
|
||
|
||
Host OS에 K3s를 직접 설치했다면 일반적인 container workload의 CPU 경로는 다음과 같다.
|
||
|
||
```text
|
||
Keycloak Container
|
||
|
|
||
v
|
||
K3s / Container Runtime
|
||
|
|
||
v
|
||
Host Linux Scheduler
|
||
|
|
||
v
|
||
Physical CPU
|
||
```
|
||
|
||
이 경우 해당 Host 위에 별도 VM이 없다면 workload가 QEMU -> `/dev/kvm` -> KVM -> VMX 경로를 타는 것은 아니다.
|
||
|
||
컨테이너의 프로세스는 Host kernel을 공유하며 Host scheduler의 직접적인 스케줄링 대상이다.
|
||
|
||
반면 VM 안에 K3s를 구성하면 다음 계층이 추가된다.
|
||
|
||
```text
|
||
Keycloak Container
|
||
|
|
||
Guest Linux / K3s
|
||
|
|
||
vCPU
|
||
|
|
||
QEMU vCPU Thread
|
||
|
|
||
Host Linux Scheduler
|
||
|
|
||
KVM / VMX
|
||
|
|
||
Physical CPU
|
||
```
|
||
|
||
따라서 동일한 부하 테스트라도 VM 기반 테스트 환경에서는 Host 가상화 자원 병목을 추가로 확인해야 한다.
|
||
|
||
---
|
||
|
||
## 19. 이 SSOT에서 파생될 CONCEPT
|
||
|
||
현재는 다음 내용을 하나의 CONCEPT로 관리하는 것이 적절하다.
|
||
|
||
### CONCEPT
|
||
|
||
**KVM에서 vCPU가 물리 CPU에서 실행되기까지**
|
||
|
||
포함 범위:
|
||
|
||
- virsh
|
||
- libvirt
|
||
- QEMU
|
||
- `/dev/kvm`
|
||
- KVM Core
|
||
- `kvm_intel`
|
||
- Intel VMX
|
||
- vCPU / vCPU thread
|
||
- Linux Scheduler
|
||
- KVM_RUN
|
||
- VM Entry / VM Exit
|
||
- 실제 VM Exit 조건
|
||
- Guest idle
|
||
- CPU contention / overcommit
|
||
- steal time
|
||
- 실제 Linux 명령을 통한 관찰
|
||
- Keycloak/K3s 실험 결과와 Host 자원 문제를 구분하는 기준
|
||
|
||
현재 단계에서는 이 실행 경로가 하나의 인과 흐름으로 연결되므로 여러 CONCEPT 문서로 과도하게 분할하지 않는다.
|
||
|
||
---
|
||
|
||
## 20. 이 CONCEPT에서 파생되는 OPEN QUESTION
|
||
|
||
개념을 이해했다고 실제 환경의 동작이 확정되는 것은 아니다.
|
||
|
||
따라서 다음 질문은 OPEN QUESTION으로 남기고 실제 실험으로 해소한다.
|
||
|
||
### OQ-1. 현재 테스트 Host에서 VM 두 대에 부하를 주면 vCPU contention이 실제로 발생하는가?
|
||
|
||
확인 대상:
|
||
|
||
- Host logical CPU 수
|
||
- 각 VM vCPU 수
|
||
- QEMU vCPU thread CPU 사용량
|
||
- Host run queue
|
||
- Guest steal time
|
||
|
||
### OQ-2. Keycloak 동시 refresh 실험 중 CPU 가상화 계층이 결과에 영향을 줄 정도로 포화되는가?
|
||
|
||
Refresh 경쟁 실험 중 다음을 동시에 관찰한다.
|
||
|
||
- Host CPU
|
||
- Guest CPU
|
||
- steal time
|
||
- Keycloak latency
|
||
- DB/Redis latency
|
||
|
||
목적은 refresh 경쟁과 Host resource contention을 분리하는 것이다.
|
||
|
||
### OQ-3. Guest가 idle일 때 vCPU thread는 실제 테스트 환경에서 어떻게 보이는가?
|
||
|
||
Guest idle 상태와 CPU workload 상태를 비교한다.
|
||
|
||
확인:
|
||
|
||
```bash
|
||
top -H -p <QEMU_PID>
|
||
ps -eLo pid,tid,psr,pcpu,stat,comm
|
||
```
|
||
|
||
### OQ-4. 실제 workload에서 어떤 VM Exit이 주로 발생하는가?
|
||
|
||
환경이 지원한다면 `perf kvm` 또는 KVM tracepoint를 이용해 확인한다.
|
||
|
||
비교 후보:
|
||
|
||
- idle
|
||
- CPU-bound workload
|
||
- I/O-heavy workload
|
||
- Keycloak 정상 요청
|
||
- Keycloak 부하 테스트
|
||
|
||
### OQ-5. CPU pinning을 하지 않은 상태에서 vCPU thread는 Host logical CPU 사이를 실제로 이동하는가?
|
||
|
||
`PSR`, scheduler tracing 등을 통해 관찰한다.
|
||
|
||
### OQ-6. 현재 운영 서버는 CPU 가상화 계층의 영향을 받는 구조인가?
|
||
|
||
운영 서버가 bare-metal Host에 직접 K3s를 설치한 것인지, 상위 Hypervisor/Cloud VM 위에 있는지 확인한다.
|
||
|
||
구조에 따라 진단 지표가 달라진다.
|
||
|
||
```text
|
||
Bare metal:
|
||
K3s -> Host Scheduler -> Physical CPU
|
||
|
||
VM:
|
||
K3s -> Guest -> vCPU -> Hypervisor -> Physical CPU
|
||
```
|
||
|
||
---
|
||
|
||
## 21. OPEN QUESTION에서 CASE가 만들어지는 흐름
|
||
|
||
현재 문서 체계에서는 다음 관계를 사용한다.
|
||
|
||
```text
|
||
SSOT
|
||
|
|
||
v
|
||
CONCEPT
|
||
|
|
||
| 이해하면서 검증이 필요한 질문 발생
|
||
v
|
||
OPEN QUESTION
|
||
|
|
||
| 실제 구성 / 명령 / 부하 / 관찰
|
||
v
|
||
CASE
|
||
|
|
||
| 결과에서 새로운 의문 발견
|
||
+------------------> OPEN QUESTION
|
||
```
|
||
|
||
즉 OPEN QUESTION은 CASE에서만 나오는 것이 아니다.
|
||
|
||
```text
|
||
CONCEPT -> OPEN QUESTION
|
||
CASE -> OPEN QUESTION
|
||
```
|
||
|
||
둘 다 가능하다.
|
||
|
||
그리고 OPEN QUESTION을 실제 실험으로 해소하는 과정에서 새로운 CASE가 만들어질 수 있다.
|
||
|
||
예:
|
||
|
||
```text
|
||
CONCEPT
|
||
"KVM vCPU는 Host Scheduler의 스케줄링 대상이다"
|
||
|
|
||
v
|
||
OPEN QUESTION
|
||
"VM 2대에 동시에 부하를 주면 현재 Host에서
|
||
실제 steal time이 증가하는가?"
|
||
|
|
||
v
|
||
CASE
|
||
"VM 2대 CPU contention 재현 및 steal time 측정"
|
||
```
|
||
|
||
이 구조를 사용하면 개념 문서에 실험 결과를 억지로 섞지 않으면서도 개념 -> 질문 -> 검증의 추적성을 유지할 수 있다.
|
||
|
||
---
|
||
|
||
## 22. 현재 단계의 핵심 Claim
|
||
|
||
### Claim 1
|
||
|
||
`virsh`는 VM 실행 자체를 담당하는 프로세스가 아니라 libvirt 기반 VM 관리 CLI다.
|
||
|
||
### Claim 2
|
||
|
||
KVM 가속 환경에서 실제 VM lifecycle 동안 QEMU 프로세스가 살아 있으며, vCPU에 대응하는 Host thread가 존재한다.
|
||
|
||
### Claim 3
|
||
|
||
QEMU는 `/dev/kvm`을 통해 Kernel의 KVM API를 사용한다.
|
||
|
||
### Claim 4
|
||
|
||
Intel 환경에서 KVM은 `kvm_intel`을 통해 CPU의 VMX 하드웨어 가상화 기능을 사용한다.
|
||
|
||
### Claim 5
|
||
|
||
VM에 N개의 vCPU를 설정하는 것은 Host의 N개 physical/logical CPU를 영구 예약한다는 의미가 아니다.
|
||
|
||
### Claim 6
|
||
|
||
vCPU thread는 기본적으로 Host Linux Scheduler의 스케줄링 대상이며, pinning하지 않았다면 실행되는 logical CPU가 달라질 수 있다.
|
||
|
||
### Claim 7
|
||
|
||
vCPU thread가 `KVM_RUN`을 호출하면 KVM이 VM Entry를 통해 Guest 실행을 시작하며 Guest의 일반 CPU 명령은 실제 CPU에서 실행된다.
|
||
|
||
### Claim 8
|
||
|
||
VM Exit은 VM 종료가 아니라 Guest 실행에서 Hypervisor/KVM으로 CPU 제어권이 전환되는 동작이다.
|
||
|
||
### Claim 9
|
||
|
||
VM Exit은 Linux root 권한 여부로 결정되지 않는다. VMX execution control에 의해 intercept되는 명령, exception, interrupt 등의 조건에 따라 발생한다.
|
||
|
||
### Claim 10
|
||
|
||
모든 VM Exit이 QEMU까지 전달되는 것은 아니다. KVM이 Kernel 내부에서 처리할 수 있는 경우 Guest로 바로 재진입할 수 있다.
|
||
|
||
### Claim 11
|
||
|
||
Guest가 idle이면 vCPU thread가 block/sleep될 수 있으며, 이때 Host는 해당 CPU 시간을 다른 workload에 사용할 수 있다.
|
||
|
||
### Claim 12
|
||
|
||
높은 Host CPU contention과 vCPU overcommit은 Guest application 성능에 영향을 줄 수 있으며 steal time은 이를 조사할 때 유용한 지표 중 하나다.
|
||
|
||
### Claim 13
|
||
|
||
Keycloak refresh token 경쟁은 KVM CPU 가상화 문제와 동일한 문제가 아니다. 다만 VM 기반 실험 환경의 CPU contention이 실험 결과를 왜곡할 수 있으므로 두 문제를 분리해서 측정해야 한다.
|
||
|
||
### Claim 14
|
||
|
||
Refresh token 경쟁 검증을 위한 concurrency test와 시스템 자원 한계를 확인하기 위한 load/stress test는 목적이 다르므로 분리해서 수행하는 것이 원인 분석에 유리하다.
|
||
|
||
---
|
||
|
||
## 23. 다음 단계
|
||
|
||
CPU 가상화에 대해서는 이 SSOT를 기준으로 실제 테스트 Host에서 명령을 실행해 다음을 검증한다.
|
||
|
||
```text
|
||
VMX/SVM
|
||
->
|
||
KVM modules
|
||
->
|
||
/dev/kvm
|
||
->
|
||
virsh VM
|
||
->
|
||
QEMU process
|
||
->
|
||
vCPU threads
|
||
->
|
||
Host logical CPU scheduling
|
||
->
|
||
Guest idle/load 비교
|
||
->
|
||
steal time
|
||
->
|
||
VM Exit 관찰
|
||
```
|
||
|
||
검증 과정에서 아직 답하지 못한 항목은 OPEN QUESTION으로 유지한다.
|
||
|
||
실험 결과가 확보되면 각각 CASE로 기록한다.
|
||
|
||
그 이후 원래 Keycloak 멀티 노드 실험에 필요한 다음 기반 영역인 **네트워크 가상화**로 이동한다.
|
||
|
||
---
|
||
|
||
## 24. CPU 가상화 계층에서 발생할 수 있는 문제
|
||
|
||
CPU 가상화 구조를 이해하는 목적 중 하나는 VM 안의 애플리케이션이 느려졌을 때 어느 계층에서 문제가 발생했는지 구분하는 것이다.
|
||
|
||
```text
|
||
Application / Keycloak
|
||
|
|
||
v
|
||
K3s / cgroup
|
||
|
|
||
v
|
||
Guest Linux
|
||
|
|
||
v
|
||
vCPU
|
||
|
|
||
v
|
||
QEMU vCPU Thread
|
||
|
|
||
v
|
||
Host Linux Scheduler
|
||
|
|
||
v
|
||
KVM / VMX
|
||
|
|
||
v
|
||
Physical CPU / NUMA
|
||
```
|
||
|
||
같은 "CPU가 느리다"는 현상도 실제 원인은 서로 다를 수 있다.
|
||
|
||
### 24.1 Guest CPU Saturation
|
||
|
||
Guest 내부의 애플리케이션이 실제로 할당된 vCPU를 모두 사용하고 있는 경우다.
|
||
|
||
```text
|
||
Keycloak / Application
|
||
|
|
||
v
|
||
Guest vCPU 100%
|
||
```
|
||
|
||
이 경우 Host에 CPU 여유가 있더라도 Guest에 할당한 vCPU 수나 애플리케이션 자체의 CPU 사용 특성이 병목일 수 있다.
|
||
|
||
확인 대상:
|
||
|
||
- Guest `top`
|
||
- process/thread별 CPU 사용량
|
||
- load average
|
||
- Guest에 할당된 vCPU 수
|
||
|
||
이 문제는 Host CPU contention과 구분해야 한다.
|
||
|
||
### 24.2 CPU Overcommit
|
||
|
||
Host가 실제로 동시에 실행할 수 있는 logical CPU보다 많은 vCPU를 여러 VM에 할당하는 구성이다.
|
||
|
||
예:
|
||
|
||
```text
|
||
Host: 12 logical CPUs
|
||
|
||
VM1: 8 vCPU
|
||
VM2: 8 vCPU
|
||
VM3: 8 vCPU
|
||
|
||
Total: 24 vCPU
|
||
```
|
||
|
||
Overcommit 자체가 바로 장애라는 의미는 아니다. VM들이 대부분 idle이라면 문제가 없을 수 있다.
|
||
|
||
문제는 여러 VM의 vCPU가 동시에 runnable 상태가 될 때 나타난다.
|
||
|
||
```text
|
||
많은 runnable vCPU threads
|
||
|
|
||
v
|
||
Host Scheduler
|
||
|
|
||
v
|
||
제한된 logical CPUs
|
||
```
|
||
|
||
이때 CPU contention과 scheduling latency가 증가할 수 있다.
|
||
|
||
### 24.3 CPU Contention
|
||
|
||
여러 runnable thread가 같은 Host CPU 자원을 두고 경쟁하는 상태다.
|
||
|
||
경쟁 대상은 QEMU vCPU thread만이 아니다.
|
||
|
||
```text
|
||
QEMU vCPU threads ---+
|
||
Nginx ---------------+
|
||
Host K3s ------------+--> Linux Scheduler --> Physical CPUs
|
||
DB / Redis ----------+
|
||
기타 Host process ---+
|
||
```
|
||
|
||
따라서 Host에 Nginx를 직접 설치하고 VM 두 대를 실행하는 테스트 환경에서는 VM 외부의 Host workload도 CPU 경쟁에 포함된다.
|
||
|
||
확인 대상:
|
||
|
||
- Host CPU utilization
|
||
- per-CPU utilization
|
||
- run queue
|
||
- load average
|
||
- QEMU vCPU thread CPU usage
|
||
|
||
### 24.4 Steal Time 증가
|
||
|
||
Guest에서는 실행할 작업이 있지만 Hypervisor/Host가 해당 vCPU thread를 즉시 실행시키지 못한 시간을 Guest가 steal time으로 관찰할 수 있다.
|
||
|
||
```text
|
||
Guest workload runnable
|
||
|
|
||
v
|
||
vCPU 실행 필요
|
||
|
|
||
v
|
||
Host CPU를 즉시 받지 못함
|
||
|
|
||
v
|
||
Steal Time 증가
|
||
```
|
||
|
||
Guest에서 `top` 등의 `%st`를 확인할 수 있다.
|
||
|
||
높은 steal time은 Host CPU contention 또는 overcommit을 조사해야 한다는 중요한 단서지만, 단독으로 원인을 확정하는 지표는 아니다.
|
||
|
||
### 24.5 vCPU Scheduling Latency
|
||
|
||
vCPU thread가 runnable 상태가 되었더라도 Host Scheduler가 실제 logical CPU에 배치할 때까지 기다릴 수 있다.
|
||
|
||
```text
|
||
vCPU Thread
|
||
runnable
|
||
|
|
||
| wait
|
||
v
|
||
Host Scheduler
|
||
|
|
||
v
|
||
Logical CPU
|
||
```
|
||
|
||
Host가 포화될수록 이 대기 시간이 커질 수 있으며 Guest에서는 application latency 증가로 보일 수 있다.
|
||
|
||
### 24.6 vCPU 과다 할당
|
||
|
||
특정 VM에 vCPU를 많이 할당한다고 항상 성능이 좋아지는 것은 아니다.
|
||
|
||
Guest workload가 실제로 그만큼의 병렬성을 사용하지 못하거나 Host 전체 CPU에 비해 지나치게 많은 vCPU를 할당하면 scheduling 대상만 증가할 수 있다.
|
||
|
||
따라서 `vCPU 수가 많다 = 항상 빠르다`로 판단하지 않는다.
|
||
|
||
실제 workload의 병렬성과 Host capacity를 함께 확인해야 한다.
|
||
|
||
### 24.7 잘못된 CPU Affinity / Pinning
|
||
|
||
CPU pinning을 사용하면 특정 vCPU thread를 특정 Host logical CPU에 제한할 수 있다.
|
||
|
||
적절하게 사용하면 scheduling 변동을 줄일 수 있지만 잘못 설정하면 특정 CPU에 workload가 집중될 수 있다.
|
||
|
||
```text
|
||
vCPU0 --+
|
||
vCPU1 --+--> CPU2
|
||
Host X -+
|
||
|
||
CPU3, CPU4, CPU5 ... 상대적으로 idle
|
||
```
|
||
|
||
따라서 pinning 여부만 보는 것이 아니라 실제 per-CPU utilization과 affinity를 함께 확인해야 한다.
|
||
|
||
### 24.8 CPU Throttling
|
||
|
||
K3s/Kubernetes 환경에서는 VM CPU 자원과 별개로 container cgroup의 CPU limit 때문에 application이 제한될 수 있다.
|
||
|
||
```text
|
||
Physical CPU
|
||
|
|
||
Host / Hypervisor
|
||
|
|
||
Guest Linux
|
||
|
|
||
K3s
|
||
|
|
||
cgroup CPU limit
|
||
|
|
||
Keycloak Pod
|
||
```
|
||
|
||
이 경우 Host CPU에 여유가 있어도 Keycloak Pod는 설정된 CPU quota 때문에 실행이 제한될 수 있다.
|
||
|
||
따라서 다음 두 문제를 구분해야 한다.
|
||
|
||
```text
|
||
Host CPU를 받지 못함
|
||
-> contention / steal / scheduling 문제
|
||
|
||
Pod가 자신의 CPU quota를 초과함
|
||
-> cgroup CPU throttling 문제
|
||
```
|
||
|
||
CPU throttling 자체는 KVM 문제가 아니지만 VM 안에서 K3s를 운영하는 현재 실험에서는 같은 application latency로 관찰될 수 있으므로 진단 경계에 포함한다.
|
||
|
||
### 24.9 과도한 VM Exit
|
||
|
||
VM Exit은 정상적인 가상화 동작이다.
|
||
|
||
따라서 VM Exit이 존재한다는 것 자체는 문제가 아니다.
|
||
|
||
다만 특정 workload에서 Hypervisor가 개입해야 하는 Exit이 지나치게 빈번하고 그 처리 비용이 커진다면 성능에 영향을 줄 수 있다.
|
||
|
||
```text
|
||
VM Entry
|
||
|
|
||
Guest
|
||
|
|
||
VM Exit
|
||
|
|
||
KVM / QEMU 처리
|
||
|
|
||
VM Entry
|
||
|
|
||
Guest
|
||
|
|
||
VM Exit
|
||
...
|
||
```
|
||
|
||
확인할 때는 단순 Exit 횟수만 보는 것이 아니라 다음을 같이 봐야 한다.
|
||
|
||
- Exit reason
|
||
- workload 종류
|
||
- Exit 처리 위치가 KVM인지 QEMU userspace인지
|
||
- application latency와 Exit 증가가 함께 나타나는지
|
||
|
||
`VM Exit이 많다 = 장애`로 바로 판단하지 않는다.
|
||
|
||
### 24.10 Host 자체의 CPU Saturation
|
||
|
||
VM만 관찰하면 놓치기 쉬운 문제다.
|
||
|
||
현재 테스트 Host에서 Nginx와 여러 Host process가 함께 동작한다면 다음과 같은 경쟁이 가능하다.
|
||
|
||
```text
|
||
Host
|
||
|
|
||
+-- Nginx
|
||
+-- QEMU VM1
|
||
+-- QEMU VM2
|
||
+-- monitoring
|
||
+-- SSH / shell
|
||
+-- 기타 process
|
||
```
|
||
|
||
Host CPU 자체가 포화되면 VM 내부에서는 Keycloak이나 K3s가 느려진 것처럼 보일 수 있다.
|
||
|
||
따라서 Guest 지표만으로 결론 내리지 않고 Host와 Guest를 동시에 관찰해야 한다.
|
||
|
||
### 24.11 NUMA Locality 문제
|
||
|
||
멀티소켓 또는 NUMA 구조의 Host에서는 CPU가 실행되는 NUMA node와 VM memory가 위치한 NUMA node의 관계가 성능에 영향을 줄 수 있다.
|
||
|
||
개념적으로:
|
||
|
||
```text
|
||
NUMA Node 0
|
||
CPU + Local Memory
|
||
|
||
NUMA Node 1
|
||
CPU + Local Memory
|
||
```
|
||
|
||
vCPU가 Node 0의 CPU에서 실행되는데 필요한 memory가 주로 Node 1에 배치되어 있다면 remote memory access가 발생할 수 있다.
|
||
|
||
NUMA는 CPU와 메모리 가상화의 경계에 걸쳐 있으므로 이 문서에서는 문제의 존재와 CPU affinity와의 관계까지만 기록한다. 상세한 memory placement와 NUMA tuning은 메모리 가상화 CONCEPT에서 다룬다.
|
||
|
||
---
|
||
|
||
## 25. CPU 문제를 계층별로 구분하는 진단표
|
||
|
||
| 문제 | 주된 계층 | 대표적인 현상 | 우선 확인할 것 |
|
||
|---|---|---|---|
|
||
| Guest CPU saturation | Guest | Guest CPU가 지속적으로 높음 | Guest CPU, process/thread, load |
|
||
| CPU throttling | K3s / cgroup | Pod가 CPU를 더 쓰고 싶어도 quota로 제한 | CPU limit, throttled time |
|
||
| vCPU 과다 할당 | VM 구성 | vCPU 증가 대비 성능 향상 없음 또는 scheduling 부담 | vCPU 수, workload 병렬성 |
|
||
| CPU overcommit | Host 구성 | 여러 VM 부하시 지연 증가 | total vCPU, Host logical CPU |
|
||
| CPU contention | Host Scheduler | runnable workload 증가, latency 증가 | Host CPU, run queue, per-CPU usage |
|
||
| Steal time 증가 | Guest에서 관측 | Guest가 CPU를 제때 받지 못함 | `%st`, Host contention |
|
||
| Scheduling latency | Host Scheduler | runnable vCPU 실행 지연 | run queue, scheduler 관찰 |
|
||
| 잘못된 pinning | Host / VM 설정 | 특정 CPU만 과도하게 사용 | affinity, per-CPU usage |
|
||
| 과도한 VM Exit | KVM / VMX | 특정 workload에서 virtualization overhead 증가 가능 | Exit count/reason, workload |
|
||
| Host CPU saturation | Host | VM 전체가 동시에 느려짐 | Host CPU/load/run queue |
|
||
| NUMA locality | Hardware / Memory | CPU는 여유가 있는데 memory access 비용 증가 가능 | NUMA topology, CPU/memory placement |
|
||
|
||
이 표의 목적은 하나의 지표로 장애 원인을 확정하는 것이 아니라, **어느 계층부터 조사해야 하는지 범위를 줄이는 것**이다.
|
||
|
||
---
|
||
|
||
## 26. 현재 Keycloak 실험에서 CPU 문제를 오판하지 않기 위한 기준
|
||
|
||
Keycloak refresh token 경쟁 실험에서 요청 실패나 latency가 증가했다고 해서 바로 refresh token 또는 저장소 경쟁 문제라고 판단하지 않는다.
|
||
|
||
최소한 다음 경계를 분리한다.
|
||
|
||
```text
|
||
[Application / Auth]
|
||
Refresh Token 경쟁
|
||
Session 상태 경쟁
|
||
Keycloak 내부 처리
|
||
|
|
||
v
|
||
[Storage]
|
||
PostgreSQL lock / latency
|
||
Redis latency / consistency
|
||
|
|
||
v
|
||
[K3s]
|
||
Pod CPU throttling
|
||
Pod scheduling/resource limit
|
||
|
|
||
v
|
||
[Guest]
|
||
Guest CPU saturation
|
||
|
|
||
v
|
||
[Virtualization]
|
||
vCPU scheduling
|
||
Steal time
|
||
VM Exit overhead
|
||
|
|
||
v
|
||
[Host]
|
||
CPU contention
|
||
CPU overcommit
|
||
Host saturation
|
||
```
|
||
|
||
따라서 refresh 경쟁을 검증하는 첫 실험에서는 가능하면 CPU 자원을 여유 있게 유지한다.
|
||
|
||
그 상태에서 동일 session/token에 대한 동시 요청을 만들어 concurrency 문제를 먼저 확인한다.
|
||
|
||
그 다음 별도의 load/stress CASE에서 트래픽을 증가시키며 CPU/DB/Redis/K3s 자원 포화를 관찰한다.
|
||
|
||
이렇게 해야 다음 두 결과를 분리할 수 있다.
|
||
|
||
```text
|
||
"동일 상태에 동시에 접근해서 발생한 문제"
|
||
|
||
vs
|
||
|
||
"시스템 자원이 부족해져서 발생한 문제"
|
||
```
|
||
|
||
---
|
||
|
||
## 27. 문제 영역에서 파생되는 추가 OPEN QUESTION
|
||
|
||
### OQ-7. VM 두 대를 동시에 CPU-bound 상태로 만들면 Guest steal time은 실제로 얼마나 증가하는가?
|
||
|
||
Host CPU utilization, run queue, QEMU vCPU thread, 각 Guest의 `%st`를 함께 측정한다.
|
||
|
||
### OQ-8. vCPU 수를 늘릴수록 현재 테스트 Host에서 Keycloak 처리량도 계속 증가하는가?
|
||
|
||
예를 들어 2 vCPU / 4 vCPU / 8 vCPU 구성을 비교해 vCPU 추가가 실제 처리량과 latency에 어떤 영향을 주는지 확인한다.
|
||
|
||
### OQ-9. K3s CPU limit으로 발생한 throttling과 Host vCPU contention을 지표로 구분할 수 있는가?
|
||
|
||
동일한 application latency 증가를 각각 의도적으로 재현하고 Guest/Host/K3s 지표 차이를 비교한다.
|
||
|
||
### OQ-10. CPU pinning 전후로 Keycloak latency와 vCPU scheduling 변동이 달라지는가?
|
||
|
||
pinning이 현재 workload에서 실제 이점을 주는지는 실험으로 확인한다.
|
||
|
||
### OQ-11. Keycloak workload에서 VM Exit 분포는 idle/CPU-bound/I/O-bound workload와 어떻게 다른가?
|
||
|
||
가능하면 `perf kvm` 또는 KVM tracepoint를 사용해 Exit reason 분포를 비교한다.
|
||
|
||
### OQ-12. 현재 Host의 NUMA topology가 VM 성능을 고려해야 할 정도의 구조인가?
|
||
|
||
Host가 단일 NUMA node라면 현재 실험에서 우선순위를 낮추고, 다중 NUMA node라면 vCPU/memory placement를 별도 CASE 후보로 올린다.
|
||
|
||
---
|
||
|
||
## 28. CONCEPT -> OPEN QUESTION -> CASE 적용 기준
|
||
|
||
CPU 가상화 CONCEPT에서는 다음 수준까지만 확정한다.
|
||
|
||
```text
|
||
구조적으로 어떤 문제가 발생할 수 있는가?
|
||
어떤 지표로 그 문제를 의심할 수 있는가?
|
||
어느 계층에서 확인해야 하는가?
|
||
```
|
||
|
||
현재 테스트 서버에서 실제로 발생하는지는 CONCEPT에서 사실로 확정하지 않는다.
|
||
|
||
예:
|
||
|
||
```text
|
||
CONCEPT
|
||
CPU overcommit 상황에서는 여러 vCPU thread가 Host CPU를 두고 경쟁할 수 있다.
|
||
|
|
||
v
|
||
OPEN QUESTION
|
||
현재 VM1 + VM2 구성에서도 부하 시 contention이 실제 발생하는가?
|
||
|
|
||
v
|
||
CASE
|
||
VM 두 대 동시 CPU 부하에서 Host run queue와 Guest steal time을 측정했다.
|
||
```
|
||
|
||
반대로 CASE를 수행하다 예상하지 못한 현상이 발견되면 다시 OPEN QUESTION을 생성한다.
|
||
|
||
```text
|
||
CASE
|
||
|
|
||
+--> 예상과 다른 결과
|
||
|
|
||
v
|
||
OPEN QUESTION
|
||
|
|
||
v
|
||
다음 CASE
|
||
```
|
||
|
||
따라서 현재 문서 체계에서 OPEN QUESTION은 CONCEPT와 CASE 사이를 한 방향으로만 연결하는 단계가 아니라, **아직 검증되지 않은 사실을 명시적으로 보관하고 다음 검증을 만드는 연결점**으로 사용한다.
|
||
|
||
# 제2부 — 메모리 가상화
|
||
> 목적: KVM/QEMU 기반 VM에서 Guest 프로세스의 메모리 접근이 실제 Host RAM까지 도달하는 경로를 하나의 기준 문서로 정리한다.
|
||
> 범위: GVA/GPA/HPA, Guest Page Table, MMU/TLB, EPT, QEMU/KVM memory backing, Page Fault/EPT Violation, Huge Page/THP/HugeTLB, Memory Overcommit, Reclaim/Swap, Ballooning/OOM, NUMA 및 실제 관측 지점.
|
||
> 원칙: **Guest가 보는 메모리 상태와 Host가 실제로 관리하는 메모리 상태를 분리해서 본다.**
|
||
|
||
---
|
||
|
||
## 29. 이 문서에서 먼저 고정할 전체 구조
|
||
|
||
KVM/QEMU VM의 메모리 접근을 가장 단순하게 표현하면 다음과 같다.
|
||
|
||
```text
|
||
Guest Application
|
||
│
|
||
│ Guest Virtual Address (GVA)
|
||
▼
|
||
Guest Page Table
|
||
│
|
||
│ Guest Physical Address (GPA)
|
||
▼
|
||
EPT (Intel) / NPT (AMD)
|
||
│
|
||
│ Host Physical Address (HPA)
|
||
▼
|
||
Physical RAM
|
||
```
|
||
|
||
여기서 세 주소를 먼저 구분해야 한다.
|
||
|
||
| 주소 | 의미 |
|
||
|---|---|
|
||
| GVA | Guest 프로세스가 사용하는 Virtual Address |
|
||
| GPA | Guest OS가 물리 메모리라고 생각하는 주소 |
|
||
| HPA | 실제 Host 서버 RAM의 Physical Address |
|
||
|
||
예를 들어 Guest 안에서 실행되는 Keycloak이 어떤 변수를 읽는다고 하자.
|
||
|
||
```text
|
||
Keycloak
|
||
│
|
||
│ GVA 0x7f001234
|
||
▼
|
||
Guest Page Table
|
||
│
|
||
│ GPA 0x00101234
|
||
▼
|
||
EPT
|
||
│
|
||
│ HPA 0x8a101234
|
||
▼
|
||
Physical RAM
|
||
```
|
||
|
||
Guest Linux는 GPA를 자신의 실제 물리 주소라고 생각한다. 하지만 VM이므로 그 GPA가 실제 서버의 HPA와 같을 필요는 없다. KVM/CPU 가상화 계층이 이 둘을 분리한다.
|
||
|
||
---
|
||
|
||
## 30. 일반 Linux의 Virtual Memory부터 시작한다
|
||
|
||
메모리 가상화의 첫 단계는 KVM 고유 기능이 아니다. 일반적인 Linux 프로세스도 실제 RAM 주소를 직접 사용하지 않는다.
|
||
|
||
Guest 안에 다음 프로세스가 있다고 하자.
|
||
|
||
```text
|
||
Guest VM
|
||
|
||
├─ Keycloak
|
||
├─ PostgreSQL
|
||
├─ nginx
|
||
└─ systemd
|
||
```
|
||
|
||
각 프로세스에는 독립적인 Virtual Address Space가 있다.
|
||
|
||
```text
|
||
Keycloak Process
|
||
|
||
Virtual Address Space
|
||
┌─────────────────────────┐
|
||
│ 0x1000 │
|
||
│ 0x2000 │
|
||
│ 0x3000 │
|
||
│ ... │
|
||
└─────────────────────────┘
|
||
|
||
|
||
PostgreSQL Process
|
||
|
||
Virtual Address Space
|
||
┌─────────────────────────┐
|
||
│ 0x1000 │
|
||
│ 0x2000 │
|
||
│ 0x3000 │
|
||
│ ... │
|
||
└─────────────────────────┘
|
||
```
|
||
|
||
두 프로세스가 모두 `0x1000`이라는 주소를 사용할 수 있다. 같은 Virtual Address라도 서로 다른 physical frame으로 매핑할 수 있기 때문이다.
|
||
|
||
```text
|
||
Keycloak
|
||
Virtual 0x1000
|
||
↓
|
||
Physical Frame A
|
||
|
||
PostgreSQL
|
||
Virtual 0x1000
|
||
↓
|
||
Physical Frame F
|
||
```
|
||
|
||
VM 내부에서 이 physical address는 정확히는 **Guest Physical Address**다.
|
||
|
||
---
|
||
|
||
## 31. Page와 Physical Frame
|
||
|
||
Linux는 메모리를 주소 하나씩 매핑하지 않는다. 일정 크기의 단위로 나누어 관리한다.
|
||
|
||
x86-64 Linux에서 흔히 사용하는 기본 page 크기는 4 KiB다.
|
||
|
||
```text
|
||
Virtual Memory
|
||
|
||
0x0000 ┌───────────────┐
|
||
│ Page 0 │ 4 KiB
|
||
0x1000 ├───────────────┤
|
||
│ Page 1 │ 4 KiB
|
||
0x2000 ├───────────────┤
|
||
│ Page 2 │ 4 KiB
|
||
0x3000 ├───────────────┤
|
||
│ Page 3 │ 4 KiB
|
||
0x4000 └───────────────┘
|
||
```
|
||
|
||
Physical Memory도 page-sized frame 단위로 생각할 수 있다.
|
||
|
||
```text
|
||
Guest Physical Memory
|
||
|
||
┌───────────────┐
|
||
│ Frame 0 │
|
||
├───────────────┤
|
||
│ Frame 1 │
|
||
├───────────────┤
|
||
│ Frame 2 │
|
||
├───────────────┤
|
||
│ Frame 3 │
|
||
└───────────────┘
|
||
```
|
||
|
||
따라서 Page Table의 핵심 역할은 다음과 같다.
|
||
|
||
```text
|
||
Virtual Page
|
||
↓
|
||
Page Table
|
||
↓
|
||
Physical Frame
|
||
```
|
||
|
||
---
|
||
|
||
## 32. Virtual Address = Page + Offset
|
||
|
||
예를 들어 기본 page 크기가 4 KiB(`0x1000`)이고 프로세스가 `0x1234`에 접근한다고 하자.
|
||
|
||
```text
|
||
Virtual Address
|
||
0x1234
|
||
|
||
┌──────────────┬─────────────┐
|
||
│ Virtual Page │ Offset │
|
||
│ 1 │ 0x234 │
|
||
└──────────────┴─────────────┘
|
||
```
|
||
|
||
Page Table에 다음 mapping이 있다고 가정한다.
|
||
|
||
```text
|
||
Virtual Page 1
|
||
↓
|
||
Guest Physical Frame 7
|
||
```
|
||
|
||
그러면 주소 변환 후에도 page 내부 offset `0x234`는 유지된다.
|
||
|
||
```text
|
||
Virtual Page 1
|
||
┌──────────────────────────┐
|
||
│ X │
|
||
└──────────────┬───────────┘
|
||
│ offset 0x234
|
||
▼
|
||
Page Table
|
||
│
|
||
▼
|
||
Physical Frame 7
|
||
┌──────────────────────────┐
|
||
│ X │
|
||
└──────────────────────────┘
|
||
```
|
||
|
||
즉 Page Table은 핵심적으로 **어느 physical frame으로 갈 것인가**를 결정한다.
|
||
|
||
---
|
||
|
||
## 33. Guest Page Table
|
||
|
||
Guest Linux Kernel은 각 프로세스의 virtual-memory mapping을 관리한다.
|
||
|
||
단순화한 예:
|
||
|
||
```text
|
||
Keycloak Page Table
|
||
|
||
Virtual Page Guest Physical Frame
|
||
|
||
Page 1 ─────→ Frame 7
|
||
Page 2 ─────→ Frame 12
|
||
Page 3 ─────→ Frame 31
|
||
```
|
||
|
||
Guest Kernel은 프로세스 생성, `mmap()`, page allocation, permission 변경, COW 등의 상황에서 page table을 생성하거나 변경한다.
|
||
|
||
하지만 CPU가 메모리에 접근할 때마다 Guest Kernel 코드가 직접 table을 하나씩 검색하는 것은 아니다.
|
||
|
||
---
|
||
|
||
## 34. MMU: 실제 주소 변환을 수행하는 CPU 하드웨어
|
||
|
||
주소 변환의 핵심 실행 주체는 CPU의 MMU(Memory Management Unit)다.
|
||
|
||
```text
|
||
CPU
|
||
│
|
||
│ Virtual Address
|
||
▼
|
||
MMU
|
||
│
|
||
│ Page Table 기반 translation
|
||
▼
|
||
Physical Address
|
||
```
|
||
|
||
현재 Guest 내부 단계만 보면:
|
||
|
||
```text
|
||
Guest Virtual Address
|
||
↓
|
||
MMU
|
||
│
|
||
│ Guest Page Table
|
||
▼
|
||
Guest Physical Address
|
||
```
|
||
|
||
역할을 나누면 다음과 같다.
|
||
|
||
```text
|
||
Guest Linux Kernel
|
||
│
|
||
│ Page Table 구성/관리
|
||
▼
|
||
Page Table
|
||
▲
|
||
│ 사용
|
||
│
|
||
MMU
|
||
│
|
||
│ 주소 변환
|
||
▼
|
||
Memory Access
|
||
```
|
||
|
||
---
|
||
|
||
## 35. TLB: 주소 변환 결과의 CPU Cache
|
||
|
||
매 memory access마다 전체 page-table walk를 수행하면 비용이 크다. CPU는 최근 translation 결과를 TLB(Translation Lookaside Buffer)에 cache한다.
|
||
|
||
```text
|
||
Virtual Address
|
||
↓
|
||
TLB
|
||
┌──┴──┐
|
||
│ │
|
||
HIT MISS
|
||
│ │
|
||
│ ▼
|
||
│ Page Table Walk
|
||
│ │
|
||
└──┬──┘
|
||
▼
|
||
Physical Address
|
||
```
|
||
|
||
예를 들어:
|
||
|
||
```text
|
||
Virtual Page 1 → Physical Frame 7
|
||
```
|
||
|
||
이라는 translation이 TLB에 있다면 같은 page의 다음 접근에서 전체 page-table walk를 피할 수 있다.
|
||
|
||
#### TLB Miss와 Page Fault는 다르다
|
||
|
||
TLB Miss:
|
||
|
||
```text
|
||
TLB에 translation cache가 없음
|
||
↓
|
||
Page Table을 조회
|
||
↓
|
||
정상 mapping 존재
|
||
↓
|
||
계속 실행
|
||
```
|
||
|
||
Page Fault:
|
||
|
||
```text
|
||
Page Table 상태상
|
||
현재 접근을 정상 완료할 수 없음
|
||
```
|
||
|
||
따라서:
|
||
|
||
```text
|
||
TLB Miss ≠ Page Fault
|
||
```
|
||
|
||
다.
|
||
|
||
---
|
||
|
||
## 36. Bare Metal과 VM의 차이
|
||
|
||
Bare-metal Linux에서는 개념적으로 다음으로 끝난다.
|
||
|
||
```text
|
||
Process Virtual Address
|
||
↓
|
||
Page Table
|
||
↓
|
||
Host Physical Address
|
||
↓
|
||
Physical RAM
|
||
```
|
||
|
||
VM에서는 Guest가 얻은 physical address가 실제 Host physical address가 아니다.
|
||
|
||
```text
|
||
Guest Virtual Address
|
||
↓
|
||
Guest Page Table
|
||
↓
|
||
Guest Physical Address
|
||
↓
|
||
???
|
||
↓
|
||
Host Physical Address
|
||
↓
|
||
Physical RAM
|
||
```
|
||
|
||
이 `GPA → HPA` 두 번째 translation을 위해 Intel에서는 EPT를 사용한다.
|
||
|
||
---
|
||
|
||
## 37. EPT(Extended Page Tables)
|
||
|
||
EPT는 Intel의 second-level address translation 기술이다. AMD에는 대응되는 NPT 계열 기능이 있다.
|
||
|
||
```text
|
||
Guest가 관리
|
||
|
||
GVA
|
||
│
|
||
│ Guest Page Table
|
||
▼
|
||
GPA
|
||
|
||
Hypervisor 측
|
||
|
||
GPA
|
||
│
|
||
│ EPT
|
||
▼
|
||
HPA
|
||
```
|
||
|
||
합치면:
|
||
|
||
```text
|
||
GVA
|
||
│
|
||
│ Guest Page Table
|
||
▼
|
||
GPA
|
||
│
|
||
│ EPT
|
||
▼
|
||
HPA
|
||
│
|
||
▼
|
||
Physical RAM
|
||
```
|
||
|
||
핵심 역할은 다음과 같다.
|
||
|
||
| 구조 | 변환 | 주요 관리 주체 |
|
||
|---|---|---|
|
||
| Guest Page Table | GVA → GPA | Guest OS |
|
||
| EPT | GPA → HPA | KVM/Host virtualization 계층 |
|
||
| 실제 runtime translation | 두 translation 계층 활용 | CPU MMU |
|
||
|
||
Guest Page Table과 EPT는 같은 table이 아니다.
|
||
|
||
---
|
||
|
||
## 38. 왜 EPT가 필요한가
|
||
|
||
VM1과 VM2가 각각 8 GiB RAM을 가진다고 하자.
|
||
|
||
둘 다 Guest 입장에서는 동일한 GPA를 사용할 수 있다.
|
||
|
||
```text
|
||
VM1: GPA 0x1000
|
||
VM2: GPA 0x1000
|
||
```
|
||
|
||
그러나 실제 Host RAM에서는 서로 다른 위치로 연결할 수 있어야 한다.
|
||
|
||
```text
|
||
VM1
|
||
GPA 0x1000
|
||
↓ EPT
|
||
HPA 0xA001000
|
||
|
||
VM2
|
||
GPA 0x1000
|
||
↓ EPT
|
||
HPA 0xF501000
|
||
```
|
||
|
||
따라서 Guest가 보는 physical-memory address space를 실제 Host RAM에서 격리하여 구현할 수 있다.
|
||
|
||
---
|
||
|
||
## 39. Shadow Page Table과 EPT의 의미
|
||
|
||
하드웨어 second-level translation이 없던 방식에서는 hypervisor가 Guest page-table 변경을 추적하면서 GVA에서 실제 Host memory까지 연결되는 shadow mapping을 관리하는 방식이 사용될 수 있었다.
|
||
|
||
개념적으로:
|
||
|
||
```text
|
||
Guest가 원하는 것
|
||
|
||
GVA
|
||
↓
|
||
Guest Page Table
|
||
↓
|
||
GPA
|
||
|
||
|
||
실제 하드웨어에 필요한 것
|
||
|
||
GVA
|
||
↓
|
||
HPA
|
||
```
|
||
|
||
Guest page table이 바뀔 때마다 hypervisor가 관련 mapping을 유지해야 하므로 관리 비용과 복잡성이 커질 수 있다.
|
||
|
||
EPT/NPT는 CPU가 두 단계 translation을 하드웨어로 지원하게 한다.
|
||
|
||
---
|
||
|
||
## 40. QEMU는 Guest RAM을 어떻게 준비하는가
|
||
|
||
VM에 8 GiB RAM을 설정했다고 하자.
|
||
|
||
QEMU는 Host userspace process다. 따라서 QEMU 자신도 Host Virtual Address Space를 갖는다.
|
||
|
||
```text
|
||
QEMU Process
|
||
|
||
Host Virtual Address Space
|
||
|
||
┌──────────────────────────────┐
|
||
│ │
|
||
│ Guest RAM Backing │
|
||
│ 8 GiB │
|
||
│ │
|
||
└──────────────────────────────┘
|
||
```
|
||
|
||
QEMU가 직접 "물리 주소 X부터 8 GiB를 달라"고 RAM hardware를 제어하는 것이 아니다.
|
||
|
||
QEMU memory도 일반 Host process memory처럼:
|
||
|
||
```text
|
||
QEMU Host Virtual Address
|
||
↓
|
||
Host Page Table
|
||
↓
|
||
Host Physical Address
|
||
```
|
||
|
||
로 관리된다.
|
||
|
||
---
|
||
|
||
## 41. KVM_SET_USER_MEMORY_REGION
|
||
|
||
QEMU는 자신이 마련한 Host userspace memory 영역과 Guest GPA 범위의 관계를 KVM에 등록한다.
|
||
|
||
대표 ioctl:
|
||
|
||
```text
|
||
KVM_SET_USER_MEMORY_REGION
|
||
```
|
||
|
||
개념적으로 전달하는 정보:
|
||
|
||
```text
|
||
Guest GPA Range
|
||
↕
|
||
QEMU Host Virtual Address Range
|
||
```
|
||
|
||
예:
|
||
|
||
```text
|
||
Guest GPA
|
||
|
||
0x00000000
|
||
│
|
||
│ 8 GiB
|
||
▼
|
||
...
|
||
|
||
↕ backing
|
||
|
||
QEMU HVA
|
||
|
||
0x7f0000000000
|
||
│
|
||
│ 8 GiB
|
||
▼
|
||
...
|
||
```
|
||
|
||
역할을 정리하면:
|
||
|
||
```text
|
||
QEMU
|
||
→ Guest RAM을 위한 Host userspace backing 제공
|
||
|
||
KVM
|
||
→ Guest memory region 및 virtualization mapping 관리
|
||
|
||
CPU
|
||
→ 실제 runtime address translation 수행
|
||
```
|
||
|
||
QEMU가 매 memory access마다 EPT를 software로 검색하는 것이 아니다.
|
||
|
||
---
|
||
|
||
## 42. Configured Memory와 실제 Physical RAM 사용량은 같지 않을 수 있다
|
||
|
||
VM에 16 GiB를 설정했다고 해서 모든 일반 구성에서 시작 순간 실제 Host RAM 16 GiB가 반드시 모두 즉시 물리적으로 점유되는 것은 아니다.
|
||
|
||
```text
|
||
Configured Memory
|
||
≠
|
||
Guest가 현재 실제 사용하는 Memory
|
||
≠
|
||
Host에서 현재 resident한 Physical Memory
|
||
```
|
||
|
||
Host의 demand paging, backing 종류, HugeTLB, memory locking, preallocation, overcommit 정책 등에 따라 실제 physical backing 시점과 방식이 달라질 수 있다.
|
||
|
||
따라서 "VM RAM 16GiB = Host RAM에서 고정된 연속 16GiB"라고 단순화하면 안 된다.
|
||
|
||
---
|
||
|
||
## 43. Guest Page Table 자체도 메모리에 있다
|
||
|
||
Nested translation에서 중요한 점이다.
|
||
|
||
```text
|
||
GVA
|
||
↓
|
||
Guest Page Table
|
||
↓
|
||
GPA
|
||
↓
|
||
EPT
|
||
↓
|
||
HPA
|
||
```
|
||
|
||
그런데 Guest Page Table 자체도 Guest Physical Memory에 저장된 자료구조다.
|
||
|
||
따라서 CPU가 Guest page-table entry를 읽는 과정에서도 그 entry가 저장된 GPA를 실제 HPA로 변환해야 한다.
|
||
|
||
개념적으로:
|
||
|
||
```text
|
||
GVA
|
||
↓
|
||
Guest Page Table Walk
|
||
│
|
||
│ Page Table 자체가 Guest Memory에 존재
|
||
└────→ EPT를 이용해 실제 RAM에서 entry를 읽음
|
||
↓
|
||
GPA 획득
|
||
↓
|
||
EPT
|
||
↓
|
||
HPA
|
||
```
|
||
|
||
그래서 nested page-table walk는 비용이 있고 TLB가 중요하다.
|
||
|
||
---
|
||
|
||
## 44. 정상 Memory Access는 매번 VM Exit하지 않는다
|
||
|
||
CPU 가상화에서 VM Exit을 배웠다고 해서 Guest RAM 접근을 다음처럼 생각하면 안 된다.
|
||
|
||
```text
|
||
잘못된 이해
|
||
|
||
Guest Memory Access
|
||
↓
|
||
VM Exit
|
||
↓
|
||
KVM
|
||
↓
|
||
RAM
|
||
```
|
||
|
||
정상 mapping이 존재하면 CPU hardware가 직접 translation을 수행한다.
|
||
|
||
```text
|
||
Guest instruction
|
||
↓
|
||
CPU MMU / TLB
|
||
↓
|
||
Guest Page Table + EPT
|
||
↓
|
||
HPA
|
||
↓
|
||
Physical RAM
|
||
```
|
||
|
||
따라서 정상적인 Guest RAM 접근마다 QEMU/KVM userspace/kernel software 경로를 왕복하지 않는다.
|
||
|
||
---
|
||
|
||
## 45. Guest Page Fault
|
||
|
||
Guest Page Fault는 첫 번째 translation 단계에서 발생한다.
|
||
|
||
```text
|
||
GVA
|
||
↓
|
||
Guest Page Table
|
||
↓
|
||
현재 접근을 완료할 수 없음
|
||
↓
|
||
Guest #PF
|
||
↓
|
||
Guest Kernel Page Fault Handler
|
||
```
|
||
|
||
예를 들어 Guest process가 아직 physical page가 붙지 않은 virtual-memory 영역에 처음 접근할 수 있다.
|
||
|
||
```text
|
||
Keycloak
|
||
↓
|
||
새 Virtual Memory 영역에 첫 접근
|
||
↓
|
||
Guest Page Table
|
||
↓
|
||
현재 usable physical mapping 없음
|
||
↓
|
||
Page Fault
|
||
↓
|
||
Guest Kernel
|
||
↓
|
||
Page 확보 / mapping 갱신
|
||
↓
|
||
Instruction 재시도
|
||
```
|
||
|
||
Page Fault 자체가 프로그램 오류를 뜻하지 않는다.
|
||
|
||
---
|
||
|
||
## 46. Page Fault의 대표적인 원인
|
||
|
||
#### 46.1 Demand Paging
|
||
|
||
```text
|
||
Virtual Memory 영역 존재
|
||
↓
|
||
아직 physical page가 필요하지 않았음
|
||
↓
|
||
첫 실제 접근
|
||
↓
|
||
Page Fault
|
||
↓
|
||
Guest Kernel이 page 준비
|
||
```
|
||
|
||
#### 46.2 Swap-in
|
||
|
||
```text
|
||
필요한 page가 Guest RAM에 없음
|
||
↓
|
||
Page Fault
|
||
↓
|
||
Guest Kernel
|
||
↓
|
||
Guest Swap에서 읽음
|
||
↓
|
||
RAM 복원
|
||
↓
|
||
Page Table 갱신
|
||
```
|
||
|
||
#### 46.3 Permission Fault
|
||
|
||
Page Table Entry에는 mapping뿐 아니라 permission도 있다.
|
||
|
||
```text
|
||
Physical Frame: 1234
|
||
Present: 1
|
||
Writable: 0
|
||
Executable: 0
|
||
```
|
||
|
||
read-only page에 write하면 fault가 발생할 수 있다.
|
||
|
||
#### 46.4 Copy-on-Write
|
||
|
||
write fault를 의도적으로 이용하여 page를 복제하고 새로운 writable mapping을 만드는 메커니즘도 존재한다.
|
||
|
||
#### 46.5 Invalid Access
|
||
|
||
Guest Kernel이 정상적인 mapping으로 해결할 수 없는 잘못된 process access라면 `SIGSEGV` 등으로 이어질 수 있다.
|
||
|
||
```text
|
||
Invalid GVA
|
||
↓
|
||
Page Fault
|
||
↓
|
||
Guest Kernel
|
||
↓
|
||
해결 불가
|
||
↓
|
||
SIGSEGV
|
||
```
|
||
|
||
따라서:
|
||
|
||
```text
|
||
Page Fault ≠ Segmentation Fault
|
||
```
|
||
|
||
다.
|
||
|
||
---
|
||
|
||
## 47. EPT Violation
|
||
|
||
이번에는 Guest Page Table translation은 성공했다고 하자.
|
||
|
||
```text
|
||
GVA
|
||
↓
|
||
Guest Page Table
|
||
↓
|
||
GPA
|
||
```
|
||
|
||
그런데 해당 GPA에 대한 second-stage 접근을 현재 EPT 조건으로 완료할 수 없다.
|
||
|
||
```text
|
||
GPA
|
||
↓
|
||
EPT
|
||
↓
|
||
Violation
|
||
```
|
||
|
||
이것이 EPT Violation이다.
|
||
|
||
```text
|
||
GVA
|
||
↓
|
||
Guest Page Table
|
||
↓
|
||
GPA ← Guest translation 성공
|
||
↓
|
||
EPT
|
||
↓
|
||
EPT Violation
|
||
↓
|
||
VM Exit
|
||
↓
|
||
KVM
|
||
```
|
||
|
||
EPT Violation은 Guest Page Fault와 발생 계층이 다르다.
|
||
|
||
---
|
||
|
||
## 48. Guest Page Fault와 EPT Violation 비교
|
||
|
||
| 항목 | Guest Page Fault | EPT Violation |
|
||
|---|---|---|
|
||
| 문제 위치 | GVA → GPA | GPA → HPA |
|
||
| 관련 table | Guest Page Table | EPT |
|
||
| 기본 관점 | Guest Virtual Memory | Virtualization Memory Mapping |
|
||
| 주요 처리 계층 | Guest Kernel | VM Exit 후 KVM 측 |
|
||
| 앱 오류를 뜻하는가 | 반드시 아님 | 반드시 아님 |
|
||
|
||
핵심:
|
||
|
||
```text
|
||
Guest Page Fault
|
||
→ Guest가 자기 virtual memory를 처리하는 사건
|
||
|
||
EPT Violation
|
||
→ second-stage virtualization translation에서 hypervisor 처리가 필요한 사건
|
||
```
|
||
|
||
---
|
||
|
||
## 49. Host Page Fault도 별도로 존재한다
|
||
|
||
QEMU도 Host의 일반 userspace process이므로 QEMU memory backing에는 Host virtual-memory 관리가 적용된다.
|
||
|
||
```text
|
||
QEMU Host Virtual Address
|
||
↓
|
||
Host Page Table
|
||
↓
|
||
Host Physical Address
|
||
```
|
||
|
||
따라서 Host 측에서도 demand allocation, reclaim/swap 등의 이유로 page fault가 발생할 수 있다.
|
||
|
||
```text
|
||
QEMU / Guest RAM Backing
|
||
↓
|
||
Host Virtual Memory
|
||
↓
|
||
Host Page Fault
|
||
↓
|
||
Host Kernel
|
||
↓
|
||
필요한 Host page 처리
|
||
```
|
||
|
||
즉 VM 메모리 분석에서는 적어도 다음을 구분해야 한다.
|
||
|
||
```text
|
||
Guest Page Fault
|
||
Host Page Fault
|
||
EPT-related virtualization event
|
||
```
|
||
|
||
---
|
||
|
||
## 50. Huge Page가 필요한 이유
|
||
|
||
8 GiB를 모두 4 KiB page 단위로 표현하면:
|
||
|
||
```text
|
||
8 GiB / 4 KiB
|
||
= 2,097,152 pages
|
||
```
|
||
|
||
2 MiB page라면:
|
||
|
||
```text
|
||
8 GiB / 2 MiB
|
||
= 4,096 pages
|
||
```
|
||
|
||
1 GiB page라면:
|
||
|
||
```text
|
||
8 GiB / 1 GiB
|
||
= 8 pages
|
||
```
|
||
|
||
큰 page는 더 적은 mapping으로 넓은 memory range를 표현할 수 있다.
|
||
|
||
---
|
||
|
||
## 51. Huge Page와 TLB Coverage
|
||
|
||
TLB entry 하나가 표현하는 page가 커지면 하나의 cached translation으로 더 넓은 주소 범위를 커버할 수 있다.
|
||
|
||
단순 예:
|
||
|
||
```text
|
||
4 KiB page × 512 mappings
|
||
= 2 MiB coverage
|
||
|
||
2 MiB page × 512 mappings
|
||
= 1 GiB coverage
|
||
```
|
||
|
||
실제 CPU는 page size별 TLB 구조와 entry 수가 다르므로 이 숫자를 특정 CPU의 실제 TLB 용량으로 해석하면 안 된다.
|
||
|
||
핵심은:
|
||
|
||
```text
|
||
Page Size ↑
|
||
↓
|
||
한 translation이 cover하는 범위 ↑
|
||
↓
|
||
TLB pressure 감소 가능
|
||
```
|
||
|
||
이다.
|
||
|
||
추가로 page-table entry 수와 page-table walk 부담도 줄어들 가능성이 있다.
|
||
|
||
---
|
||
|
||
## 52. VM에서 Huge Page를 볼 때 주의할 점
|
||
|
||
VM에는 두 translation 단계가 있다.
|
||
|
||
```text
|
||
GVA
|
||
│ Guest Page Table
|
||
▼
|
||
GPA
|
||
│ EPT
|
||
▼
|
||
HPA
|
||
```
|
||
|
||
따라서 "Huge Page를 사용한다"는 말만으로는 부족하다.
|
||
|
||
- Guest page-table 단계에서 큰 page를 사용하는가?
|
||
- Host backing이 Huge Page인가?
|
||
- EPT mapping에서 큰 mapping을 활용하는가?
|
||
|
||
등을 구분해야 한다.
|
||
|
||
Guest와 Host의 page-size 선택을 하나의 동일한 설정으로 취급하면 안 된다.
|
||
|
||
---
|
||
|
||
## 53. THP: Transparent Huge Pages
|
||
|
||
THP는 Linux가 가능한 memory 영역에 대해 Huge Page를 투명하게 활용하려는 기능이다.
|
||
|
||
```text
|
||
Application
|
||
↓
|
||
일반 malloc()/mmap()
|
||
↓
|
||
Linux Kernel
|
||
↓
|
||
조건이 맞으면 Huge Page 활용 시도
|
||
```
|
||
|
||
상태 확인:
|
||
|
||
```bash
|
||
cat /sys/kernel/mm/transparent_hugepage/enabled
|
||
```
|
||
|
||
예:
|
||
|
||
```text
|
||
always [madvise] never
|
||
```
|
||
|
||
현재 정책은 kernel/distribution/Host 설정에 따라 다르므로 실제 시스템에서 확인한다.
|
||
|
||
---
|
||
|
||
## 54. THP의 Trade-off
|
||
|
||
Huge Page에는 큰 contiguous physical-memory 영역이 필요하다.
|
||
|
||
2 MiB는 4 KiB page 512개 크기다.
|
||
|
||
```text
|
||
4 KiB × 512 = 2 MiB
|
||
```
|
||
|
||
memory fragmentation이 심하면 Kernel이 compaction 등의 작업을 수행할 수 있다.
|
||
|
||
```text
|
||
Huge Page 필요
|
||
↓
|
||
큰 contiguous memory 필요
|
||
↓
|
||
Fragmentation
|
||
↓
|
||
Compaction 가능
|
||
↓
|
||
Latency 영향 가능
|
||
```
|
||
|
||
따라서 THP는 항상 성능을 높인다고 단정할 수 없다. 특히 latency-sensitive workload에서는 측정이 필요하다.
|
||
|
||
---
|
||
|
||
## 55. HugeTLB
|
||
|
||
HugeTLB는 명시적인 Huge Page pool을 사용할 수 있는 Linux 메커니즘이다.
|
||
|
||
THP:
|
||
|
||
```text
|
||
Application
|
||
↓
|
||
일반 Memory Allocation
|
||
↓
|
||
Kernel이 자동적으로 Huge Page 활용
|
||
```
|
||
|
||
HugeTLB:
|
||
|
||
```text
|
||
관리자가 Huge Page Pool 준비
|
||
↓
|
||
Application / VM이 명시적으로 사용
|
||
```
|
||
|
||
예:
|
||
|
||
```text
|
||
Physical RAM
|
||
|
||
┌──────────────────────────┐
|
||
│ Normal Memory │
|
||
├──────────────────────────┤
|
||
│ HugeTLB Pool │
|
||
│ 2 MiB │
|
||
│ 2 MiB │
|
||
│ 2 MiB │
|
||
│ ... │
|
||
└──────────────────────────┘
|
||
```
|
||
|
||
사전 확보를 통해 예측 가능성을 높일 수 있지만 일반 memory allocation의 유연성이 감소하는 trade-off가 있다.
|
||
|
||
---
|
||
|
||
## 56. THP와 HugeTLB 비교
|
||
|
||
| 항목 | THP | HugeTLB |
|
||
|---|---|---|
|
||
| 관리 | Kernel의 투명한 활용 | 명시적 pool |
|
||
| 애플리케이션 개입 | 상대적으로 적음 | 명시적 구성 가능 |
|
||
| 유연성 | 상대적으로 높음 | 상대적으로 낮음 |
|
||
| 사전 예약 | 핵심 방식 아님 | 가능 |
|
||
| compaction 영향 | 발생 가능 | 사전 확보로 일부 상황 회피 가능 |
|
||
| VM RAM backing | 사용 가능 | 명시적으로 사용 가능 |
|
||
|
||
Host 확인:
|
||
|
||
```bash
|
||
grep -i huge /proc/meminfo
|
||
cat /sys/kernel/mm/transparent_hugepage/enabled
|
||
```
|
||
|
||
`AnonHugePages`와 `HugePages_Total`은 같은 의미가 아니다.
|
||
|
||
---
|
||
|
||
## 57. Memory Overcommit
|
||
|
||
예를 들어:
|
||
|
||
```text
|
||
Host Physical RAM = 32 GiB
|
||
|
||
VM1 configured = 16 GiB
|
||
VM2 configured = 16 GiB
|
||
VM3 configured = 16 GiB
|
||
|
||
Total configured = 48 GiB
|
||
```
|
||
|
||
Guest configured memory 총량이 Host physical RAM보다 크다.
|
||
|
||
이 구성이 가능할 수 있는 이유는 configured capacity와 현재 실제 working set/resident memory가 같지 않을 수 있기 때문이다.
|
||
|
||
예:
|
||
|
||
```text
|
||
VM1 configured 16G → actual working set 약 5G
|
||
VM2 configured 16G → actual working set 약 4G
|
||
VM3 configured 16G → actual working set 약 3G
|
||
|
||
Total working set 약 12G
|
||
```
|
||
|
||
하지만 모든 VM의 실제 demand가 동시에 증가하면 문제가 발생한다.
|
||
|
||
---
|
||
|
||
## 58. CPU Overcommit과 Memory Overcommit의 차이
|
||
|
||
CPU:
|
||
|
||
```text
|
||
CPU 부족
|
||
↓
|
||
Scheduler가 execution time을 나눔
|
||
↓
|
||
Runnable task가 기다림
|
||
```
|
||
|
||
Memory:
|
||
|
||
```text
|
||
RAM 부족
|
||
↓
|
||
"현재 존재해야 하는 page를 어디에 둘 것인가?"
|
||
```
|
||
|
||
따라서 Memory pressure에서는 reclaim, swap, ballooning, OOM 등의 추가 메커니즘이 필요하다.
|
||
|
||
Memory Overcommit은 CPU Overcommit과 동일한 성격의 자원 공유가 아니다.
|
||
|
||
---
|
||
|
||
## 59. Host Memory Pressure와 Reclaim
|
||
|
||
Host RAM 수요가 실제 available physical memory에 접근하면 Linux는 memory reclaim을 시도한다.
|
||
|
||
```text
|
||
Memory Pressure 증가
|
||
↓
|
||
Reclaim
|
||
↓
|
||
회수 가능한 cache/page 처리
|
||
↓
|
||
필요하면 anonymous memory swap
|
||
↓
|
||
그래도 부족
|
||
↓
|
||
심각한 pressure / OOM 가능
|
||
```
|
||
|
||
#### File-backed clean page
|
||
|
||
원본이 storage에 있으므로 RAM에서 버리고 필요할 때 다시 읽을 수 있다.
|
||
|
||
```text
|
||
Clean File-backed Page
|
||
↓
|
||
Reclaim
|
||
↓
|
||
RAM에서 제거
|
||
↓
|
||
나중에 Storage에서 다시 읽음
|
||
```
|
||
|
||
dirty page라면 필요한 writeback 과정이 먼저 필요할 수 있다.
|
||
|
||
#### Anonymous page
|
||
|
||
heap/stack 등의 anonymous memory는 backing file의 원본을 단순히 다시 읽을 수 없으므로 swap 같은 backing이 필요할 수 있다.
|
||
|
||
---
|
||
|
||
## 60. Host Swap이 VM에 미치는 영향
|
||
|
||
Guest RAM backing의 Host physical page가 swap-out될 수 있는 구성이라고 하자.
|
||
|
||
Guest는 단순히 RAM에 접근한다고 생각한다.
|
||
|
||
```text
|
||
Keycloak
|
||
↓
|
||
Guest Memory Load
|
||
```
|
||
|
||
하지만 Host에서는:
|
||
|
||
```text
|
||
Guest Memory Access
|
||
↓
|
||
필요한 Host backing page가 RAM에 없음
|
||
↓
|
||
Host Page Fault
|
||
↓
|
||
Swap-in I/O
|
||
↓
|
||
Physical RAM으로 복원
|
||
↓
|
||
Guest 실행 계속
|
||
```
|
||
|
||
가 될 수 있다.
|
||
|
||
즉 Guest 관점의 RAM access가 Host에서는 storage I/O를 기다리는 상황으로 바뀔 수 있다.
|
||
|
||
---
|
||
|
||
## 61. Guest Swap과 Host Swap
|
||
|
||
Guest Swap:
|
||
|
||
```text
|
||
Guest Application
|
||
↓
|
||
Guest Memory Pressure
|
||
↓
|
||
Guest Kernel
|
||
↓
|
||
Guest Swap
|
||
↓
|
||
/dev/vda
|
||
↓
|
||
virtio-blk
|
||
↓
|
||
QEMU
|
||
↓
|
||
Host Storage
|
||
```
|
||
|
||
Host Swap:
|
||
|
||
```text
|
||
Guest RAM
|
||
↓
|
||
QEMU Memory Backing
|
||
↓
|
||
Host Memory Pressure
|
||
↓
|
||
Host Kernel
|
||
↓
|
||
Host Swap
|
||
```
|
||
|
||
따라서:
|
||
|
||
```text
|
||
Guest Swap ≠ Host Swap
|
||
```
|
||
|
||
이다.
|
||
|
||
Guest가 메모리 여유가 있어 보이는데 Host에서 swap/reclaim이 심할 수도 있다.
|
||
|
||
---
|
||
|
||
## 62. Memory Pressure와 Storage Contention의 연결
|
||
|
||
Guest와 Host가 동시에 memory pressure를 겪으면 다음 I/O가 한 storage device로 몰릴 수 있다.
|
||
|
||
```text
|
||
Guest Swap I/O ────────┐
|
||
Host Swap I/O ─────────┼──→ Physical NVMe
|
||
Database I/O ──────────┤
|
||
Filesystem Writeback ──┘
|
||
```
|
||
|
||
따라서:
|
||
|
||
```text
|
||
Host Memory Pressure
|
||
↓
|
||
Reclaim / Swap
|
||
↓
|
||
Storage I/O 증가
|
||
↓
|
||
Storage Contention
|
||
↓
|
||
DB latency 증가
|
||
↓
|
||
Application latency 증가
|
||
```
|
||
|
||
가 가능하다.
|
||
|
||
CPU 사용률이 낮다고 해서 memory/storage 문제가 없는 것은 아니다.
|
||
|
||
---
|
||
|
||
## 63. Swap Used만 보고 장애를 판단하면 안 된다
|
||
|
||
예:
|
||
|
||
```text
|
||
Swap Used = 2 GiB
|
||
```
|
||
|
||
만으로 현재 memory pressure가 심하다고 단정할 수 없다. 과거에 swap-out된 cold page가 남아 있을 수도 있다.
|
||
|
||
더 중요한 질문:
|
||
|
||
```text
|
||
현재 swap-in/out이 지속되는가?
|
||
reclaim pressure가 증가하는가?
|
||
major fault가 증가하는가?
|
||
storage latency가 같이 증가하는가?
|
||
```
|
||
|
||
Guest와 Host를 동시에 확인해야 한다.
|
||
|
||
```bash
|
||
free -h
|
||
vmstat 1
|
||
```
|
||
|
||
---
|
||
|
||
## 64. Ballooning이 필요한 이유
|
||
|
||
Host는 QEMU의 Guest RAM backing을 볼 수 있지만 Guest 내부에서 어떤 memory가 중요한지 완전히 알지 못한다.
|
||
|
||
Guest는 다음 semantics를 알고 있다.
|
||
|
||
```text
|
||
Guest Memory
|
||
|
||
├─ Application Working Set
|
||
├─ JVM Heap
|
||
├─ Page Cache
|
||
├─ Free
|
||
└─ 기타
|
||
```
|
||
|
||
Host가 무작정 Guest backing을 swap-out하기보다 Guest Kernel과 협력해 불필요한 memory를 반환받는 것이 유리할 수 있다.
|
||
|
||
대표적인 메커니즘이 `virtio-balloon`이다.
|
||
|
||
---
|
||
|
||
## 65. virtio-balloon 구조
|
||
|
||
```text
|
||
Guest VM
|
||
|
||
Guest Kernel
|
||
│
|
||
virtio-balloon Driver
|
||
│
|
||
virtqueue
|
||
|
||
════════ VM Boundary ════════
|
||
|
||
│
|
||
QEMU virtio-balloon Device
|
||
│
|
||
▼
|
||
Host Memory Management
|
||
```
|
||
|
||
`virtio-balloon`은 Guest RAM 자체를 제공하는 장치가 아니다. 이미 존재하는 Guest RAM backing을 Host/Guest가 협력하여 회수/반환하는 데 사용하는 가상 장치다.
|
||
|
||
---
|
||
|
||
## 66. Balloon Inflate
|
||
|
||
Host가 Guest memory를 회수하려고 할 때 balloon을 inflate한다.
|
||
|
||
```text
|
||
Host/QEMU
|
||
│
|
||
│ Balloon target 조정
|
||
▼
|
||
virtio-balloon
|
||
│
|
||
════════ VM Boundary ═══════
|
||
│
|
||
▼
|
||
Guest Balloon Driver
|
||
│
|
||
│ Guest pages 확보
|
||
▼
|
||
Guest usable memory 감소
|
||
```
|
||
|
||
Guest 안의 balloon이 커지기 때문에 Guest가 사용할 수 있는 RAM이 줄어든다.
|
||
|
||
```text
|
||
Before
|
||
|
||
┌──────────────────────────┐
|
||
│ Guest Usable │
|
||
│ Memory │
|
||
└──────────────────────────┘
|
||
|
||
|
||
After Inflate
|
||
|
||
┌──────────────────────────┐
|
||
│ Guest Usable │
|
||
│ Memory │
|
||
├──────────────────────────┤
|
||
│ Balloon │
|
||
└──────────────────────────┘
|
||
```
|
||
|
||
개념:
|
||
|
||
```text
|
||
Balloon Inflate
|
||
→ Guest usable memory ↓
|
||
→ Host가 회수할 수 있는 backing memory ↑
|
||
```
|
||
|
||
---
|
||
|
||
## 67. Balloon Page 반환의 의미
|
||
|
||
Guest balloon driver는 Guest pages를 확보하고 관련 정보를 Host 측에 전달한다.
|
||
|
||
```text
|
||
Guest
|
||
|
||
GPA Page A
|
||
GPA Page B
|
||
GPA Page C
|
||
│
|
||
▼
|
||
Balloon Driver
|
||
│
|
||
│ virtio
|
||
══════╪════════════
|
||
▼
|
||
QEMU / Host
|
||
│
|
||
▼
|
||
해당 backing memory를
|
||
회수할 기회
|
||
```
|
||
|
||
정확한 Host-side release 동작은 QEMU/KVM 버전, backing 종류 및 설정에 따라 달라질 수 있다.
|
||
|
||
핵심은 Guest가 **이 page들을 일반적인 Guest workload가 사용하지 않도록 확보하고 Host에 그 사실을 알려준다**는 것이다.
|
||
|
||
---
|
||
|
||
## 68. Balloon Deflate
|
||
|
||
Host가 Guest에게 memory를 다시 제공할 수 있으면 balloon target을 줄인다.
|
||
|
||
```text
|
||
Host/QEMU
|
||
↓
|
||
Balloon target 감소
|
||
↓
|
||
Guest Balloon Driver
|
||
↓
|
||
Balloon pages 반환
|
||
↓
|
||
Guest usable memory 증가
|
||
```
|
||
|
||
따라서:
|
||
|
||
```text
|
||
Inflate = Guest usable memory 감소
|
||
Deflate = Guest usable memory 증가
|
||
```
|
||
|
||
다.
|
||
|
||
---
|
||
|
||
## 69. Ballooning을 과도하게 하면 Guest가 압박을 받는다
|
||
|
||
Guest application working set이 큰데 balloon을 과도하게 inflate하면:
|
||
|
||
```text
|
||
Balloon Inflate
|
||
↓
|
||
Guest Available Memory 감소
|
||
↓
|
||
Guest Memory Pressure
|
||
↓
|
||
Guest Reclaim
|
||
↓
|
||
Page Cache 회수
|
||
↓
|
||
Guest Swap
|
||
↓
|
||
심하면 Guest OOM
|
||
```
|
||
|
||
이 될 수 있다.
|
||
|
||
Host RAM을 확보하려는 조치가 Guest storage I/O와 application latency를 증가시킬 수 있다는 뜻이다.
|
||
|
||
---
|
||
|
||
## 70. Ballooning과 Memory Hotplug
|
||
|
||
Ballooning:
|
||
|
||
```text
|
||
기존 Guest Memory Capacity
|
||
↓
|
||
그 범위에서 Host/Guest 간
|
||
usable memory를 회수/반환
|
||
```
|
||
|
||
Memory Hotplug:
|
||
|
||
```text
|
||
기존 Guest RAM
|
||
+
|
||
추가 Memory Device/Region
|
||
↓
|
||
Guest가 추가 capacity 인식
|
||
```
|
||
|
||
따라서:
|
||
|
||
```text
|
||
Ballooning ≠ Memory Hotplug
|
||
```
|
||
|
||
다.
|
||
|
||
현대 가상화에서는 `virtio-mem` 같은 다른 동적 memory 관리 방식도 존재하므로 모든 동적 VM memory 관리를 ballooning 하나로 일반화하면 안 된다.
|
||
|
||
---
|
||
|
||
## 71. OOM
|
||
|
||
Linux가 memory allocation을 만족시키지 못하고 reclaim 등의 방법으로도 필요한 memory를 확보하지 못하면 OOM 상황이 발생할 수 있다.
|
||
|
||
```text
|
||
Memory Allocation 필요
|
||
↓
|
||
Reclaim 등 시도
|
||
↓
|
||
충분한 Memory 확보 실패
|
||
↓
|
||
OOM
|
||
↓
|
||
OOM Killer
|
||
↓
|
||
Process 선택/종료 가능
|
||
↓
|
||
Memory 확보
|
||
```
|
||
|
||
---
|
||
|
||
## 72. Guest OOM과 Host OOM
|
||
|
||
Guest OOM:
|
||
|
||
```text
|
||
Guest RAM 부족
|
||
↓
|
||
Guest Kernel OOM
|
||
↓
|
||
Guest Process Kill
|
||
|
||
예: Keycloak process 종료
|
||
```
|
||
|
||
Host OOM:
|
||
|
||
```text
|
||
Host Physical RAM 부족
|
||
↓
|
||
Host Kernel OOM
|
||
↓
|
||
Host Process Kill 가능
|
||
```
|
||
|
||
Host OOM에서 QEMU가 victim이 되면:
|
||
|
||
```text
|
||
QEMU process killed
|
||
↓
|
||
해당 VM 전체가 중단
|
||
```
|
||
|
||
될 수 있다.
|
||
|
||
따라서:
|
||
|
||
```text
|
||
Guest OOM ≠ Host OOM
|
||
```
|
||
|
||
이다.
|
||
|
||
또한 cgroup memory limit이 있는 환경에서는 Host 전체 RAM이 남아 있어도 해당 cgroup boundary에서 OOM이 발생할 수 있으므로 OOM의 **발생 계층**을 확인해야 한다.
|
||
|
||
---
|
||
|
||
## 73. NUMA
|
||
|
||
지금까지는 RAM을 하나의 균일한 자원처럼 표현했다. multi-socket/NUMA 시스템에서는 어느 CPU가 어느 RAM에 접근하느냐에 따라 비용이 달라질 수 있다.
|
||
|
||
```text
|
||
NUMA Node 0 NUMA Node 1
|
||
|
||
CPU Socket 0 CPU Socket 1
|
||
├─ Cores ├─ Cores
|
||
└─ Local RAM └─ Local RAM
|
||
|
||
Interconnect
|
||
```
|
||
|
||
NUMA = Non-Uniform Memory Access.
|
||
|
||
---
|
||
|
||
## 74. Local Memory와 Remote Memory
|
||
|
||
Local:
|
||
|
||
```text
|
||
NUMA Node 0
|
||
|
||
CPU
|
||
│
|
||
▼
|
||
Node 0 RAM
|
||
```
|
||
|
||
Remote:
|
||
|
||
```text
|
||
NUMA Node 0 NUMA Node 1
|
||
|
||
CPU
|
||
│
|
||
└──────── Interconnect ───────→ RAM
|
||
```
|
||
|
||
일반적으로 remote access는 local access와 동일한 비용이라고 가정할 수 없으며 추가 latency/bandwidth 비용이 있을 수 있다.
|
||
|
||
---
|
||
|
||
## 75. vCPU와 NUMA의 연결
|
||
|
||
Guest vCPU는 Host에서 QEMU의 vCPU thread다.
|
||
|
||
```text
|
||
Guest vCPU
|
||
↓
|
||
QEMU vCPU Thread
|
||
↓
|
||
Host Linux Scheduler
|
||
↓
|
||
Host Logical CPU
|
||
```
|
||
|
||
VM1의 vCPU thread가 Node 0 CPU에서 실행되는데 VM1의 Host physical backing page가 Node 1에 있다면:
|
||
|
||
```text
|
||
Node 0 CPU
|
||
│
|
||
│ Remote Access
|
||
▼
|
||
Node 1 RAM
|
||
```
|
||
|
||
이 될 수 있다.
|
||
|
||
Guest에서는 단순한 memory load지만 실제 hardware에서는 NUMA interconnect를 건널 수 있다.
|
||
|
||
---
|
||
|
||
## 76. vCPU Pinning만으로는 NUMA 최적화가 끝나지 않는다
|
||
|
||
예:
|
||
|
||
```text
|
||
VM1 vCPU
|
||
↓
|
||
Node 0 CPU에 Pinning
|
||
|
||
VM1 RAM
|
||
↓
|
||
Node 1에 주로 배치
|
||
```
|
||
|
||
이면 pinning 이후에도 remote memory access가 많아질 수 있다.
|
||
|
||
따라서:
|
||
|
||
```text
|
||
vCPU Placement
|
||
+
|
||
Memory Placement/Binding
|
||
↓
|
||
NUMA Locality
|
||
```
|
||
|
||
를 함께 봐야 한다.
|
||
|
||
이상적인 예:
|
||
|
||
```text
|
||
NUMA Node 0
|
||
|
||
CPU 0 ← VM1 vCPU0
|
||
CPU 1 ← VM1 vCPU1
|
||
CPU 2 ← VM1 vCPU2
|
||
CPU 3 ← VM1 vCPU3
|
||
|
||
VM1 Memory Backing
|
||
→ Node 0 RAM
|
||
```
|
||
|
||
---
|
||
|
||
## 77. Guest NUMA
|
||
|
||
큰 VM에서는 Guest에게 NUMA topology 자체를 노출할 수 있다.
|
||
|
||
예:
|
||
|
||
```text
|
||
Guest VM
|
||
|
||
Guest NUMA Node 0
|
||
├─ vCPU 0~7
|
||
└─ RAM 32 GiB
|
||
|
||
Guest NUMA Node 1
|
||
├─ vCPU 8~15
|
||
└─ RAM 32 GiB
|
||
```
|
||
|
||
Host:
|
||
|
||
```text
|
||
Host NUMA Node 0
|
||
├─ Physical CPUs
|
||
└─ RAM
|
||
|
||
Host NUMA Node 1
|
||
├─ Physical CPUs
|
||
└─ RAM
|
||
```
|
||
|
||
가능하면 Guest가 인식하는 topology와 실제 Host placement가 합리적으로 대응되도록 구성할 수 있다.
|
||
|
||
```text
|
||
Guest NUMA 0 → Host NUMA 0
|
||
Guest NUMA 1 → Host NUMA 1
|
||
```
|
||
|
||
---
|
||
|
||
## 78. NUMA는 실제 장비 topology부터 확인한다
|
||
|
||
Host가 NUMA node 1개라면 cross-node remote-memory 문제가 주요 이슈가 아닐 수 있다.
|
||
|
||
```bash
|
||
lscpu
|
||
```
|
||
|
||
예:
|
||
|
||
```text
|
||
NUMA node(s): 2
|
||
NUMA node0 CPU(s): 0-7
|
||
NUMA node1 CPU(s): 8-15
|
||
```
|
||
|
||
추가:
|
||
|
||
```bash
|
||
numactl --hardware
|
||
```
|
||
|
||
QEMU process별 memory distribution:
|
||
|
||
```bash
|
||
numastat -p <QEMU_PID>
|
||
```
|
||
|
||
vCPU placement:
|
||
|
||
```bash
|
||
virsh vcpupin <VM_NAME>
|
||
virsh vcpuinfo <VM_NAME>
|
||
```
|
||
|
||
실제 환경에서는 먼저 topology를 측정하고 NUMA 최적화 필요성을 판단한다.
|
||
|
||
---
|
||
|
||
## 79. 전체 Memory Virtualization 실행 경로
|
||
|
||
최종적으로 Guest application의 memory access는 다음 구조로 이해할 수 있다.
|
||
|
||
```text
|
||
Guest
|
||
|
||
Keycloak / PostgreSQL
|
||
│
|
||
│ GVA
|
||
▼
|
||
TLB
|
||
┌────┴────┐
|
||
│ │
|
||
HIT MISS
|
||
│ │
|
||
│ Page-table walk
|
||
│ │
|
||
└────┬────┘
|
||
▼
|
||
Guest Page Table
|
||
│
|
||
Guest #PF 가능
|
||
│
|
||
▼
|
||
GPA
|
||
│
|
||
════════════════════ VM Boundary ════════════════════
|
||
│
|
||
EPT
|
||
│
|
||
EPT Violation 가능
|
||
│
|
||
▼
|
||
HPA
|
||
│
|
||
▼
|
||
Host Physical Page
|
||
│
|
||
┌──────┴──────┐
|
||
│ │
|
||
NUMA Node 0 NUMA Node 1
|
||
RAM RAM
|
||
```
|
||
|
||
정상 mapping/TLB 상태에서는 memory access마다 QEMU나 KVM software가 직접 데이터 경로를 처리하지 않는다. CPU MMU가 hardware-assisted translation을 수행한다.
|
||
|
||
---
|
||
|
||
## 80. 전체 Memory Virtualization 관리 경로
|
||
|
||
실행 경로와 관리 경로를 분리해야 한다.
|
||
|
||
```text
|
||
User
|
||
↓
|
||
virsh
|
||
↓
|
||
libvirt
|
||
↓
|
||
QEMU
|
||
│
|
||
├─ Guest RAM backing
|
||
├─ QEMU HVA
|
||
├─ virtio-balloon device
|
||
│
|
||
└─ ioctl(KVM_SET_USER_MEMORY_REGION)
|
||
↓
|
||
KVM
|
||
│
|
||
├─ Guest memory slots/regions 관리
|
||
└─ EPT 관련 virtualization mapping 관리
|
||
↓
|
||
CPU
|
||
```
|
||
|
||
즉:
|
||
|
||
- `virsh/libvirt`: VM configuration/management
|
||
- QEMU: Guest RAM Host userspace backing 및 device 구성
|
||
- KVM: Guest memory region과 hardware virtualization 연계
|
||
- CPU MMU/EPT hardware: runtime translation
|
||
|
||
으로 구분한다.
|
||
|
||
---
|
||
|
||
## 81. CPU / Network / Storage / Memory 연결
|
||
|
||
VM을 전체적으로 보면:
|
||
|
||
```text
|
||
VM
|
||
|
||
Guest Application
|
||
│
|
||
┌──────────────┼──────────────┐
|
||
│ │ │
|
||
CPU Network Storage
|
||
│ │ │
|
||
vCPU virtio-net virtio-blk
|
||
│ virtqueue virtqueue
|
||
│ │ │
|
||
══════════╪══════════════╪══════════════╪══════════
|
||
│ │ │
|
||
QEMU/KVM vhost/QEMU QEMU Block
|
||
│ │ │
|
||
▼ TAP qcow2/raw
|
||
Host CPU │ │
|
||
Bridge Host Block
|
||
│
|
||
▼
|
||
NVMe
|
||
```
|
||
|
||
Memory는 이 모든 실행을 받친다.
|
||
|
||
```text
|
||
Guest GVA
|
||
↓
|
||
Guest Page Table
|
||
↓
|
||
GPA
|
||
↓
|
||
EPT
|
||
↓
|
||
HPA
|
||
↓
|
||
Host RAM / NUMA
|
||
```
|
||
|
||
그리고 memory pressure는 storage path까지 영향을 줄 수 있다.
|
||
|
||
```text
|
||
Memory Pressure
|
||
↓
|
||
Reclaim / Swap
|
||
↓
|
||
Storage I/O
|
||
↓
|
||
Storage Contention
|
||
↓
|
||
Application Latency
|
||
```
|
||
|
||
CPU placement는 NUMA memory locality와 연결된다.
|
||
|
||
```text
|
||
vCPU Pinning
|
||
+
|
||
Memory Placement
|
||
↓
|
||
Local / Remote Memory Access
|
||
```
|
||
|
||
---
|
||
|
||
## 82. 핵심 Claim Registry
|
||
|
||
### CLAIM-MEM-01
|
||
Guest application은 일반적으로 Host physical address를 직접 사용하지 않는다.
|
||
|
||
```text
|
||
GVA → GPA → HPA
|
||
```
|
||
|
||
두 단계의 translation을 거친다.
|
||
|
||
### CLAIM-MEM-02
|
||
Guest Page Table은 GVA → GPA mapping을 Guest OS 관점에서 관리한다.
|
||
|
||
### CLAIM-MEM-03
|
||
Intel EPT는 GPA → HPA second-stage translation을 hardware-assisted virtualization으로 지원한다.
|
||
|
||
### CLAIM-MEM-04
|
||
정상적인 Guest RAM access마다 VM Exit이나 QEMU userspace 처리가 발생하는 것은 아니다.
|
||
|
||
### CLAIM-MEM-05
|
||
QEMU는 Guest RAM을 위한 Host userspace backing을 마련하고 KVM에 Guest memory region을 등록한다.
|
||
|
||
### CLAIM-MEM-06
|
||
Configured Guest RAM과 Host에서 현재 실제 resident한 physical memory는 항상 동일하지 않다.
|
||
|
||
### CLAIM-MEM-07
|
||
TLB Miss와 Page Fault는 다른 사건이다.
|
||
|
||
### CLAIM-MEM-08
|
||
Guest Page Fault와 EPT Violation은 서로 다른 translation 단계에서 발생한다.
|
||
|
||
### CLAIM-MEM-09
|
||
Page Fault 자체는 프로그램 오류를 의미하지 않는다. Demand paging/COW/swap-in 등 정상 memory management에서도 발생할 수 있다.
|
||
|
||
### CLAIM-MEM-10
|
||
Huge Page는 더 넓은 memory range를 하나의 mapping으로 표현하여 TLB/page-table 효율을 개선할 가능성이 있다.
|
||
|
||
### CLAIM-MEM-11
|
||
THP와 HugeTLB는 같은 방식이 아니다. THP는 투명한 활용을 지향하고 HugeTLB는 명시적인 huge-page pool을 제공한다.
|
||
|
||
### CLAIM-MEM-12
|
||
Memory Overcommit은 CPU Overcommit과 성격이 다르다. RAM pressure에서는 reclaim/swap/ballooning/OOM이 개입할 수 있다.
|
||
|
||
### CLAIM-MEM-13
|
||
Guest Swap과 Host Swap은 서로 다른 계층에서 발생한다.
|
||
|
||
### CLAIM-MEM-14
|
||
Host memory pressure는 swap/writeback을 통해 storage contention과 application latency를 악화시킬 수 있다.
|
||
|
||
### CLAIM-MEM-15
|
||
virtio-balloon은 Guest와 Host가 memory 회수/반환에 협력하기 위한 가상 장치이며 RAM 자체를 제공하는 장치는 아니다.
|
||
|
||
### CLAIM-MEM-16
|
||
Balloon inflate가 과도하면 Guest reclaim/swap/OOM을 유발할 수 있다.
|
||
|
||
### CLAIM-MEM-17
|
||
Guest OOM과 Host OOM은 영향 범위가 다르다. Host OOM에서 QEMU가 종료되면 VM 전체가 중단될 수 있다.
|
||
|
||
### CLAIM-MEM-18
|
||
NUMA 시스템에서는 vCPU placement와 memory placement를 함께 봐야 한다.
|
||
|
||
---
|
||
|
||
## 83. 실제 환경에서 확인할 OPEN QUESTION
|
||
|
||
아래 항목은 개념적으로 단정하지 않고 실제 테스트 서버에서 확인해야 한다.
|
||
|
||
### OQ-1. Host의 실제 NUMA topology는 무엇인가?
|
||
|
||
```bash
|
||
lscpu
|
||
numactl --hardware
|
||
```
|
||
|
||
확인할 것:
|
||
|
||
- NUMA node 수
|
||
- node별 CPU
|
||
- node별 memory
|
||
- node distance
|
||
|
||
---
|
||
|
||
### OQ-2. 각 VM의 configured/current memory는 얼마인가?
|
||
|
||
```bash
|
||
virsh dominfo <VM_NAME>
|
||
virsh dumpxml <VM_NAME>
|
||
virsh dommemstat <VM_NAME>
|
||
```
|
||
|
||
Guest:
|
||
|
||
```bash
|
||
free -h
|
||
cat /proc/meminfo
|
||
```
|
||
|
||
Host의 QEMU process 상태와 비교한다.
|
||
|
||
---
|
||
|
||
### OQ-3. QEMU process의 Host resident memory는 어떻게 분포하는가?
|
||
|
||
```bash
|
||
ps -ef | grep qemu
|
||
ps -o pid,rss,vsz,cmd -p <QEMU_PID>
|
||
```
|
||
|
||
필요하면:
|
||
|
||
```bash
|
||
cat /proc/<QEMU_PID>/status
|
||
cat /proc/<QEMU_PID>/smaps_rollup
|
||
```
|
||
|
||
configured memory와 RSS/anonymous/huge-page 상태를 비교한다.
|
||
|
||
---
|
||
|
||
### OQ-4. Host THP 정책은 무엇인가?
|
||
|
||
```bash
|
||
cat /sys/kernel/mm/transparent_hugepage/enabled
|
||
grep -i huge /proc/meminfo
|
||
```
|
||
|
||
확인할 것:
|
||
|
||
- THP policy
|
||
- AnonHugePages
|
||
- HugePages_Total
|
||
- HugePages_Free
|
||
- Hugepagesize
|
||
|
||
---
|
||
|
||
### OQ-5. VM RAM이 HugeTLB로 명시적으로 backing되어 있는가?
|
||
|
||
```bash
|
||
virsh dumpxml <VM_NAME>
|
||
```
|
||
|
||
libvirt memory backing 관련 설정을 확인하고 Host `/proc/meminfo`, QEMU `smaps` 계열과 교차 검증한다.
|
||
|
||
---
|
||
|
||
### OQ-6. Guest와 Host에서 현재 swap이 발생하는가?
|
||
|
||
Guest:
|
||
|
||
```bash
|
||
free -h
|
||
vmstat 1
|
||
```
|
||
|
||
Host:
|
||
|
||
```bash
|
||
free -h
|
||
vmstat 1
|
||
```
|
||
|
||
단순 swap-used 값보다 현재 swap-in/out activity와 memory pressure를 함께 본다.
|
||
|
||
---
|
||
|
||
### OQ-7. Host memory pressure가 Guest latency에 영향을 주는가?
|
||
|
||
실험 개념:
|
||
|
||
```text
|
||
Baseline
|
||
↓
|
||
Guest Application Latency 측정
|
||
↓
|
||
Host Memory Pressure 유도
|
||
↓
|
||
Host reclaim/swap 관측
|
||
↓
|
||
Guest latency 재측정
|
||
```
|
||
|
||
동시에 CPU와 storage도 관측한다.
|
||
|
||
---
|
||
|
||
### OQ-8. virtio-balloon이 VM에 구성되어 있는가?
|
||
|
||
```bash
|
||
virsh dumpxml <VM_NAME>
|
||
```
|
||
|
||
Guest에서도 관련 driver/device 상태를 확인한다.
|
||
|
||
환경에 따라 driver 이름과 표시 방식이 달라질 수 있으므로 실제 장비에서 검증한다.
|
||
|
||
---
|
||
|
||
### OQ-9. Balloon target 변화가 Guest available memory에 어떻게 반영되는가?
|
||
|
||
관측:
|
||
|
||
```text
|
||
Host/libvirt memory setting
|
||
↓
|
||
Guest free -h / /proc/meminfo
|
||
↓
|
||
Guest reclaim/swap 변화
|
||
```
|
||
|
||
과도한 ballooning 시 Guest latency/swap/OOM 가능성을 별도 실험한다.
|
||
|
||
---
|
||
|
||
### OQ-10. VM vCPU는 어느 Host CPU에 배치되어 있는가?
|
||
|
||
```bash
|
||
virsh vcpuinfo <VM_NAME>
|
||
virsh vcpupin <VM_NAME>
|
||
```
|
||
|
||
CPU 가상화 SSOT의 pinning/overcommit 관측과 연결한다.
|
||
|
||
---
|
||
|
||
### OQ-11. QEMU memory는 어느 NUMA node에 배치되어 있는가?
|
||
|
||
```bash
|
||
numastat -p <QEMU_PID>
|
||
```
|
||
|
||
vCPU placement와 비교한다.
|
||
|
||
```text
|
||
vCPU → Node 0
|
||
Memory → Node 0
|
||
```
|
||
|
||
인지,
|
||
|
||
```text
|
||
vCPU → Node 0
|
||
Memory → Node 1
|
||
```
|
||
|
||
인지 확인한다.
|
||
|
||
---
|
||
|
||
### OQ-12. NUMA remote access가 실제 workload latency에 의미 있는 영향을 주는가?
|
||
|
||
NUMA node가 2개 이상인 경우에만 우선순위를 높인다.
|
||
|
||
```text
|
||
Local placement baseline
|
||
↓
|
||
Latency / throughput / memory metrics
|
||
↓
|
||
Remote-heavy placement
|
||
↓
|
||
동일 workload 비교
|
||
```
|
||
|
||
단순 topology만 보고 성능 문제라고 단정하지 않는다.
|
||
|
||
---
|
||
|
||
### OQ-13. Guest Page Fault가 workload 변화와 함께 증가하는가?
|
||
|
||
Guest에서 page-fault 관련 지표를 관측하고 다음을 분리한다.
|
||
|
||
```text
|
||
정상 demand paging?
|
||
COW?
|
||
Guest swap-in?
|
||
application working-set 증가?
|
||
```
|
||
|
||
Page Fault 증가만으로 오류라고 판단하지 않는다.
|
||
|
||
---
|
||
|
||
### OQ-14. Host Page Fault/major fault와 storage latency가 상관되는가?
|
||
|
||
Host memory pressure 실험 시:
|
||
|
||
```text
|
||
Host Fault
|
||
+
|
||
Swap activity
|
||
+
|
||
Storage latency
|
||
+
|
||
Guest application latency
|
||
```
|
||
|
||
를 같은 시간축으로 비교한다.
|
||
|
||
---
|
||
|
||
## 84. 권장 실험 순서
|
||
|
||
개념 검증은 다음 순서가 좋다.
|
||
|
||
```text
|
||
1. Host Physical Memory / NUMA 확인
|
||
↓
|
||
2. VM configured memory 확인
|
||
↓
|
||
3. Guest free/meminfo 확인
|
||
↓
|
||
4. QEMU RSS/HVA backing 상태 확인
|
||
↓
|
||
5. THP/HugeTLB 상태 확인
|
||
↓
|
||
6. Guest/Host vmstat 동시 관측
|
||
↓
|
||
7. Balloon device/config 확인
|
||
↓
|
||
8. vCPU placement 확인
|
||
↓
|
||
9. QEMU NUMA memory distribution 확인
|
||
↓
|
||
10. Memory pressure 실험
|
||
↓
|
||
11. Guest/Host swap 및 storage latency 비교
|
||
↓
|
||
12. 필요 시 NUMA locality 실험
|
||
```
|
||
|
||
---
|
||
|
||
## 85. 실험 시 반드시 같이 기록할 것
|
||
|
||
각 실험은 다음 조건을 남긴다.
|
||
|
||
```text
|
||
Host
|
||
├─ CPU model
|
||
├─ Core / Thread 수
|
||
├─ RAM
|
||
├─ NUMA topology
|
||
├─ Swap 설정
|
||
├─ Kernel version
|
||
├─ THP policy
|
||
└─ Physical storage
|
||
|
||
VM
|
||
├─ vCPU
|
||
├─ Configured RAM
|
||
├─ Current RAM
|
||
├─ Memory backing 설정
|
||
├─ Balloon device
|
||
├─ Guest swap
|
||
└─ Guest kernel
|
||
|
||
Workload
|
||
├─ Application
|
||
├─ Heap/Memory 설정
|
||
├─ Request concurrency
|
||
├─ DB workload
|
||
└─ 측정 시간
|
||
```
|
||
|
||
조건을 남기지 않으면 "Memory pressure에서 느려졌다"는 결과를 다른 환경에 재사용하기 어렵다.
|
||
|
||
---
|
||
|
||
## 86. 문제를 진단할 때의 분류
|
||
|
||
Memory latency 또는 OOM이 보이면 한 번에 "메모리 부족"이라고 결론내리지 않는다.
|
||
|
||
```text
|
||
문제
|
||
│
|
||
├─ Guest Virtual Memory?
|
||
│ ├─ Page Fault
|
||
│ ├─ Guest reclaim
|
||
│ ├─ Guest swap
|
||
│ └─ Guest OOM
|
||
│
|
||
├─ Virtualization Translation?
|
||
│ ├─ EPT-related event
|
||
│ ├─ TLB pressure
|
||
│ └─ Huge-page/mapping 특성
|
||
│
|
||
├─ Host Memory?
|
||
│ ├─ Host reclaim
|
||
│ ├─ Host swap
|
||
│ ├─ Host major fault
|
||
│ └─ Host OOM
|
||
│
|
||
├─ Dynamic Memory?
|
||
│ ├─ Balloon target
|
||
│ ├─ Guest pressure
|
||
│ └─ Hotplug/virtio-mem 여부
|
||
│
|
||
└─ NUMA?
|
||
├─ vCPU placement
|
||
├─ memory placement
|
||
└─ remote access
|
||
```
|
||
|
||
---
|
||
|
||
## 87. 최종 기준 그림
|
||
|
||
Memory Virtualization을 한 장으로 기억할 때는 다음 그림을 기준으로 한다.
|
||
|
||
```text
|
||
[Guest Userspace]
|
||
|
||
Keycloak / PostgreSQL
|
||
│
|
||
│ GVA
|
||
▼
|
||
|
||
[Guest Kernel]
|
||
|
||
Guest TLB
|
||
│
|
||
TLB Miss 가능
|
||
│
|
||
▼
|
||
Guest Page Table
|
||
│
|
||
Guest #PF 가능
|
||
│
|
||
▼
|
||
GPA
|
||
|
||
══════════════════════ VM Boundary ══════════════════════
|
||
|
||
│
|
||
▼
|
||
|
||
[KVM / CPU]
|
||
|
||
EPT
|
||
│
|
||
EPT Violation 가능
|
||
│
|
||
▼
|
||
HPA
|
||
|
||
[Host RAM]
|
||
|
||
Host Physical Memory
|
||
│
|
||
┌────────┴────────┐
|
||
│ │
|
||
NUMA Node 0 NUMA Node 1
|
||
│ │
|
||
└────────┬────────┘
|
||
│
|
||
Physical RAM
|
||
```
|
||
|
||
관리 경로는 별도로 기억한다.
|
||
|
||
```text
|
||
virsh
|
||
↓
|
||
libvirt
|
||
↓
|
||
QEMU
|
||
│
|
||
│ Guest RAM backing
|
||
│ KVM_SET_USER_MEMORY_REGION
|
||
▼
|
||
KVM
|
||
│
|
||
│ EPT 관련 mapping 관리
|
||
▼
|
||
CPU MMU
|
||
```
|
||
|
||
그리고 자원 압박 경로:
|
||
|
||
```text
|
||
Host Memory Pressure
|
||
│
|
||
├─ Reclaim
|
||
├─ Swap
|
||
├─ Ballooning
|
||
│ ↓
|
||
│ Guest Pressure
|
||
│ ↓
|
||
│ Guest Swap / OOM
|
||
│
|
||
└─ Host OOM
|
||
|
||
Memory Pressure
|
||
↓
|
||
Storage I/O 증가 가능
|
||
↓
|
||
Storage Contention
|
||
↓
|
||
Application Latency
|
||
```
|
||
|
||
---
|
||
|
||
## 88. 결론
|
||
|
||
KVM/QEMU Memory Virtualization을 이해할 때 핵심은 **"VM에 RAM을 몇 GB 줬다"를 하나의 단순한 물리 RAM 할당으로 보지 않는 것**이다.
|
||
|
||
실제 구조에는 다음 계층이 있다.
|
||
|
||
```text
|
||
Guest Process
|
||
↓
|
||
GVA
|
||
↓
|
||
Guest Page Table
|
||
↓
|
||
GPA
|
||
↓
|
||
EPT
|
||
↓
|
||
HPA
|
||
↓
|
||
Host Physical RAM
|
||
```
|
||
|
||
Guest OS는 자신의 virtual-memory와 GPA 공간을 관리하고, QEMU는 Guest RAM의 Host userspace backing을 마련하며, KVM은 이를 virtualization memory region과 연결한다. 정상 runtime translation은 CPU MMU와 EPT hardware가 수행한다.
|
||
|
||
성능과 장애를 볼 때는 그 위에 다음 요소가 추가된다.
|
||
|
||
```text
|
||
TLB / Page-table Walk
|
||
Huge Page / THP / HugeTLB
|
||
Guest Page Fault
|
||
EPT Violation
|
||
Host Page Fault
|
||
Memory Overcommit
|
||
Reclaim
|
||
Guest Swap / Host Swap
|
||
virtio-balloon
|
||
Guest OOM / Host OOM
|
||
NUMA Locality
|
||
```
|
||
|
||
따라서 실제 테스트 서버에서는 Guest 하나의 `free -h`만 보고 메모리 상태를 판단하지 않는다.
|
||
|
||
**Guest → QEMU → Host → NUMA → Storage 영향**을 같은 시간축에서 관측해야 한다.
|
||
|
||
이 문서의 개념 부분은 SSOT로 고정하고, 실제 서버에 종속되는 설정과 동작은 OQ-1~OQ-14를 실험하여 CASE로 전환한다.
|
||
|
||
# 제3부 — 네트워크 가상화
|
||
## 89. 문서 목적
|
||
|
||
이 문서는 KVM/QEMU 기반 VM 환경에서 **Guest 애플리케이션의 네트워크 요청이 Guest Kernel, virtio-net, virtqueue, vhost-net, TAP, Linux Bridge/라우팅, Physical NIC를 거쳐 외부 네트워크로 나가고 다시 들어오는 구조**를 SSOT로 정리한다.
|
||
|
||
현재 실험 목적은 Host에 VM 2대를 구성하고 각 VM 안의 K3s/Keycloak 노드를 이용해 다음을 검증하기 위한 기반을 만드는 것이다.
|
||
|
||
- Keycloak 멀티 노드 구성
|
||
- 동일 세션/동일 Refresh Token의 동시 갱신
|
||
- Refresh Token 경쟁
|
||
- 세션/토큰 상태를 PostgreSQL 또는 Redis에 공유할 때의 동작
|
||
- 단일 저장소를 여러 Keycloak 노드가 공유할 때의 경합과 일관성
|
||
- Host Nginx → VM → K3s → Keycloak 요청 경로
|
||
- 네트워크 계층 문제와 애플리케이션/저장소 문제의 분리
|
||
|
||
이 문서는 **네트워크 가상화 자체**에 초점을 둔다.
|
||
|
||
---
|
||
|
||
## 90. virsh / libvirt / virtio 구분
|
||
|
||
### 90.1 virsh
|
||
|
||
`virsh`는 사용자가 libvirt에 VM 관리 명령을 전달하는 CLI다.
|
||
|
||
```bash
|
||
virsh list --all
|
||
virsh start vm1
|
||
virsh shutdown vm1
|
||
virsh domiflist vm1
|
||
virsh net-list --all
|
||
```
|
||
|
||
`virsh`는 packet datapath에 직접 참여하지 않는다.
|
||
|
||
```text
|
||
User
|
||
↓
|
||
virsh
|
||
↓
|
||
libvirt
|
||
↓
|
||
QEMU
|
||
```
|
||
|
||
### 90.2 libvirt
|
||
|
||
libvirt는 VM lifecycle 및 configuration을 관리하는 소프트웨어/API 계층이다.
|
||
|
||
관리 대상 예:
|
||
|
||
```text
|
||
vCPU
|
||
Memory
|
||
Disk
|
||
NIC model
|
||
MAC address
|
||
Virtual network
|
||
Bridge
|
||
QEMU arguments
|
||
```
|
||
|
||
### 90.3 virtio
|
||
|
||
`virtio`는 명령어가 아니다.
|
||
|
||
또한 하나의 단일 프로그램이나 단일 커널 모듈을 의미하지 않는다.
|
||
|
||
> Virtio는 Guest와 Host/Hypervisor가 가상 I/O 장치를 효율적으로 사용하기 위한 표준화된 인터페이스/프로토콜이다.
|
||
|
||
대표적인 virtio 장치:
|
||
|
||
```text
|
||
virtio-net Network
|
||
virtio-blk Block I/O
|
||
virtio-scsi SCSI
|
||
virtio-balloon Memory Balloon
|
||
```
|
||
|
||
이 문서에서는 `virtio-net`을 다룬다.
|
||
|
||
---
|
||
|
||
## 91. virtio-net은 정확히 어디에 있는가
|
||
|
||
`virtio-net`을 하나의 위치에 존재하는 하나의 프로세스로 보면 안 된다.
|
||
|
||
가상 NIC를 성립시키는 구현이 Guest와 Host에 나누어져 있다.
|
||
|
||
### Guest 측
|
||
|
||
```text
|
||
Guest Kernel
|
||
├─ TCP/IP Stack
|
||
├─ virtio-net Frontend Driver
|
||
└─ virtqueue
|
||
```
|
||
|
||
### Host 측
|
||
|
||
```text
|
||
Host Userspace
|
||
└─ QEMU virtio-net Device Model
|
||
|
||
Host Kernel
|
||
├─ vhost-net (사용하는 경우)
|
||
├─ TAP
|
||
├─ Linux Bridge / Routing / NAT
|
||
└─ Physical NIC Driver
|
||
```
|
||
|
||
따라서 virtio는 특정 "커널 계층" 자체가 아니라 Guest frontend와 Host backend 사이의 **I/O 계약**이다.
|
||
|
||
---
|
||
|
||
## 92. Frontend와 Backend
|
||
|
||
```text
|
||
Guest Host
|
||
|
||
virtio-net Frontend
|
||
Driver
|
||
│
|
||
↓
|
||
virtqueue
|
||
│
|
||
│ Virtio protocol
|
||
│
|
||
└──────────────→ Backend
|
||
├─ QEMU
|
||
└─ vhost-net
|
||
```
|
||
|
||
- Frontend: Guest Kernel의 `virtio-net` driver
|
||
- Backend: Guest가 전달한 packet buffer를 Host 쪽에서 처리하는 구현
|
||
- Backend는 QEMU userspace 또는 vhost-net kernel backend가 될 수 있다.
|
||
|
||
---
|
||
|
||
## 93. Guest OS는 왜 QEMU가 아니라 virtio-net을 사용하는가
|
||
|
||
물리 서버에서는:
|
||
|
||
```text
|
||
Application
|
||
↓
|
||
Linux TCP/IP Stack
|
||
↓
|
||
Physical NIC Driver
|
||
↓
|
||
Physical NIC
|
||
```
|
||
|
||
VM에서는:
|
||
|
||
```text
|
||
Application
|
||
↓
|
||
Guest TCP/IP Stack
|
||
↓
|
||
virtio-net Driver
|
||
↓
|
||
Virtual NIC
|
||
```
|
||
|
||
이다.
|
||
|
||
Guest는 "QEMU를 호출한다"가 아니라 "내 NIC를 사용한다"고 동작한다.
|
||
|
||
VM 시작 시 QEMU가 Guest에게 virtio 방식의 virtual NIC를 노출한다.
|
||
|
||
```text
|
||
QEMU
|
||
↓
|
||
Virtual PCI Bus에 virtio NIC 노출
|
||
↓
|
||
Guest Linux
|
||
↓
|
||
virtio device 발견
|
||
↓
|
||
virtio-net driver bind
|
||
↓
|
||
ens3 / eth0 형태의 network interface 생성
|
||
```
|
||
|
||
Guest에서 확인:
|
||
|
||
```bash
|
||
lspci
|
||
ip link
|
||
ip addr
|
||
```
|
||
|
||
---
|
||
|
||
## 94. 전체 네트워크 계층
|
||
|
||
가장 기본적인 `virtio-net + vhost-net + TAP + Linux Bridge` 구조를 기준으로 한다.
|
||
|
||
### 수신 방향
|
||
|
||
```text
|
||
Internet / Client
|
||
↓
|
||
Physical NIC
|
||
↓
|
||
Physical NIC Driver
|
||
↓
|
||
Linux Bridge / Routing / NAT
|
||
↓
|
||
TAP
|
||
↓
|
||
vhost-net
|
||
↓
|
||
RX virtqueue
|
||
↓
|
||
virtio-net Frontend Driver
|
||
↓
|
||
Guest TCP/IP Stack
|
||
↓
|
||
Socket
|
||
↓
|
||
Keycloak
|
||
```
|
||
|
||
### 송신 방향
|
||
|
||
```text
|
||
Keycloak
|
||
↓
|
||
Socket
|
||
↓
|
||
Guest TCP/IP Stack
|
||
↓
|
||
virtio-net Frontend Driver
|
||
↓
|
||
TX virtqueue
|
||
↓
|
||
vhost-net
|
||
↓
|
||
TAP
|
||
↓
|
||
Linux Bridge / Routing / NAT
|
||
↓
|
||
Physical NIC Driver
|
||
↓
|
||
Physical NIC
|
||
↓
|
||
Network
|
||
```
|
||
|
||
실제 환경은 Bridge, NAT, Routed Network, macvtap, SR-IOV, VFIO passthrough, Open vSwitch, Kubernetes CNI 등에 따라 달라질 수 있다.
|
||
|
||
---
|
||
|
||
## 95. Physical NIC의 역할
|
||
|
||
NIC는 Network Interface Card다.
|
||
|
||
Physical NIC는 실제 네트워크 링크와 서버를 연결하는 하드웨어다.
|
||
|
||
```text
|
||
Network
|
||
↓
|
||
Physical NIC
|
||
↓
|
||
NIC Driver
|
||
↓
|
||
Linux Kernel
|
||
```
|
||
|
||
Linux에서:
|
||
|
||
```bash
|
||
ip link
|
||
```
|
||
|
||
등으로 `enp3s0`, `eno1`, `eth0` 같은 interface를 확인할 수 있다.
|
||
|
||
주의:
|
||
|
||
```text
|
||
Physical NIC hardware
|
||
≠
|
||
Linux interface object
|
||
```
|
||
|
||
NIC hardware를 Host Kernel의 NIC driver가 제어하고 Linux가 network interface로 노출한다.
|
||
|
||
---
|
||
|
||
## 96. Linux Bridge의 역할
|
||
|
||
Linux Bridge는 Host Kernel 안의 **L2 software switch**다.
|
||
|
||
```text
|
||
VM1 TAP ──┐
|
||
│
|
||
VM2 TAP ──┼── br0 ── Physical NIC
|
||
│
|
||
Host NIC ─┘
|
||
```
|
||
|
||
Bridge는 Ethernet frame의 Destination MAC을 보고 어느 port로 전달할지 결정한다.
|
||
|
||
핵심 역할:
|
||
|
||
```text
|
||
L2 forwarding
|
||
MAC learning
|
||
Frame forwarding
|
||
Multiple virtual/physical ports 연결
|
||
```
|
||
|
||
확인:
|
||
|
||
```bash
|
||
bridge link
|
||
bridge fdb show
|
||
ip link show type bridge
|
||
```
|
||
|
||
---
|
||
|
||
## 97. Routing의 역할
|
||
|
||
Routing은 Bridge와 다르다.
|
||
|
||
```text
|
||
Bridge
|
||
→ L2
|
||
→ MAC 기반
|
||
→ 같은 Ethernet network 연결
|
||
|
||
Routing
|
||
→ L3
|
||
→ IP 기반
|
||
→ 서로 다른 IP network 사이 연결
|
||
```
|
||
|
||
Linux routing table 확인:
|
||
|
||
```bash
|
||
ip route
|
||
```
|
||
|
||
Routing은 destination IP를 보고 어느 interface 또는 next-hop으로 packet을 보낼지 결정한다.
|
||
|
||
---
|
||
|
||
## 98. NAT의 역할
|
||
|
||
NAT는 packet의 IP/Port 정보를 변환한다.
|
||
|
||
예:
|
||
|
||
```text
|
||
VM
|
||
192.168.122.10
|
||
↓
|
||
Host NAT
|
||
↓
|
||
203.0.113.10
|
||
↓
|
||
Internet
|
||
```
|
||
|
||
VM이 private subnet을 쓰는 경우 Host가 NAT gateway처럼 동작할 수 있다.
|
||
|
||
따라서 실제 VM network를 분석할 때 다음을 구분해야 한다.
|
||
|
||
```text
|
||
Bridge 기반인가?
|
||
Routing 기반인가?
|
||
NAT 기반인가?
|
||
```
|
||
|
||
---
|
||
|
||
## 99. TAP의 역할
|
||
|
||
TAP은 Host Linux Kernel이 제공하는 **가상 Ethernet network interface**다.
|
||
|
||
물리 장치가 아니다.
|
||
|
||
예:
|
||
|
||
```text
|
||
tap0
|
||
vnet0
|
||
```
|
||
|
||
역할:
|
||
|
||
> VM의 Ethernet frame과 Host Linux networking을 연결하는 접점
|
||
|
||
```text
|
||
Guest Virtual NIC
|
||
↓
|
||
virtio backend
|
||
↓
|
||
TAP
|
||
↓
|
||
Host Linux Network
|
||
```
|
||
|
||
수신:
|
||
|
||
```text
|
||
Linux Bridge
|
||
↓
|
||
TAP
|
||
↓
|
||
VM
|
||
```
|
||
|
||
송신:
|
||
|
||
```text
|
||
VM
|
||
↓
|
||
TAP
|
||
↓
|
||
Linux Bridge
|
||
```
|
||
|
||
확인:
|
||
|
||
```bash
|
||
ip link
|
||
ip tuntap show
|
||
bridge link
|
||
virsh domiflist <domain>
|
||
```
|
||
|
||
---
|
||
|
||
## 100. virtqueue의 역할
|
||
|
||
virtqueue는 NIC가 아니며 Linux network interface도 아니다.
|
||
|
||
> virtqueue는 Guest와 Host backend가 I/O buffer를 주고받기 위한 descriptor 기반 shared queue 구조다.
|
||
|
||
네트워크에서는 보통 TX/RX queue를 사용한다.
|
||
|
||
```text
|
||
TX virtqueue
|
||
Guest → Host
|
||
|
||
RX virtqueue
|
||
Host → Guest
|
||
```
|
||
|
||
개념:
|
||
|
||
```text
|
||
Guest RAM
|
||
|
||
Packet Buffer
|
||
↑
|
||
│ descriptor
|
||
│
|
||
virtqueue
|
||
│
|
||
↓
|
||
Host Backend
|
||
```
|
||
|
||
핵심은 packet payload를 매번 전통적인 userspace API 호출로 전달하는 방식이 아니라 Guest memory buffer와 descriptor를 효율적으로 공유/참조하도록 설계되어 있다는 점이다.
|
||
|
||
---
|
||
|
||
## 101. Guest TCP/IP Stack의 역할
|
||
|
||
Guest TCP/IP Stack은 Guest Linux Kernel의 실제 network stack이다.
|
||
|
||
VM이라고 해서 TCP/IP stack이 가짜인 것은 아니다.
|
||
|
||
Guest Kernel에는 실제로 다음이 존재한다.
|
||
|
||
```text
|
||
Socket
|
||
TCP
|
||
UDP
|
||
IP
|
||
Routing
|
||
Neighbor/ARP
|
||
Firewall
|
||
Network Driver
|
||
```
|
||
|
||
### 101.1 Socket
|
||
|
||
Application과 Kernel network stack 사이의 인터페이스다.
|
||
|
||
대표 API:
|
||
|
||
```text
|
||
socket()
|
||
bind()
|
||
listen()
|
||
accept()
|
||
connect()
|
||
send()
|
||
recv()
|
||
```
|
||
|
||
Keycloak은 Ethernet frame이나 virtqueue를 직접 다루지 않는다.
|
||
|
||
### 101.2 TCP
|
||
|
||
TCP의 대표 책임:
|
||
|
||
```text
|
||
Connection 관리
|
||
Port
|
||
Sequence
|
||
순서 보장
|
||
재전송
|
||
중복 처리
|
||
Flow Control
|
||
Congestion Control
|
||
```
|
||
|
||
예:
|
||
|
||
```text
|
||
Source Port: 53021
|
||
Destination Port: 8080
|
||
```
|
||
|
||
### 101.3 IP
|
||
|
||
IP 계층은 IP 주소와 routing을 담당한다.
|
||
|
||
예:
|
||
|
||
```text
|
||
Source IP: 192.168.122.10
|
||
Destination IP: 192.168.122.20
|
||
```
|
||
|
||
확인:
|
||
|
||
```bash
|
||
ip addr
|
||
ip route
|
||
```
|
||
|
||
### 101.4 Ethernet / Link Layer
|
||
|
||
NIC에 가까운 계층에서는 Ethernet frame과 MAC address를 다룬다.
|
||
|
||
확인:
|
||
|
||
```bash
|
||
ip neigh
|
||
```
|
||
|
||
---
|
||
|
||
## 102. Packet이 Keycloak까지 올라오는 과정
|
||
|
||
```text
|
||
Ethernet Frame
|
||
↓
|
||
IP Packet
|
||
↓
|
||
TCP Segment / Stream
|
||
↓
|
||
Socket
|
||
↓
|
||
HTTP
|
||
↓
|
||
Keycloak
|
||
```
|
||
|
||
Keycloak은 다음을 직접 알 필요가 없다.
|
||
|
||
```text
|
||
virtqueue
|
||
vhost-net
|
||
TAP
|
||
Bridge
|
||
Physical NIC
|
||
```
|
||
|
||
Keycloak은 Guest Linux가 제공하는 TCP socket 위에서 HTTP 요청을 처리한다.
|
||
|
||
---
|
||
|
||
## 103. QEMU virtio Device Model의 역할
|
||
|
||
QEMU의 `virtio Device Model`은 **Host Userspace의 QEMU process 내부**에 존재한다.
|
||
|
||
여기서 역할을 두 개로 분리해야 한다.
|
||
|
||
### 역할 A. 장치 생성/설정/관리
|
||
|
||
```text
|
||
QEMU
|
||
↓
|
||
virtio-net Device Model 생성
|
||
↓
|
||
Guest에게 device 노출
|
||
↓
|
||
feature negotiation
|
||
↓
|
||
virtqueue 설정
|
||
↓
|
||
backend 연결
|
||
```
|
||
|
||
이 역할은 QEMU가 담당한다.
|
||
|
||
### 역할 B. 실제 Packet Datapath 처리
|
||
|
||
#### QEMU backend를 직접 사용하는 경우
|
||
|
||
```text
|
||
TAP
|
||
↓
|
||
QEMU virtio backend
|
||
↓
|
||
virtqueue
|
||
↓
|
||
Guest
|
||
```
|
||
|
||
#### vhost-net을 사용하는 경우
|
||
|
||
```text
|
||
TAP
|
||
↓
|
||
vhost-net
|
||
↓
|
||
virtqueue
|
||
↓
|
||
Guest
|
||
```
|
||
|
||
반복적인 packet I/O를 Host Kernel에서 처리하고 QEMU userspace를 우회한다.
|
||
|
||
---
|
||
|
||
## 104. 왜 `TAP → vhost-net → QEMU → virtqueue`라고 일반화하면 안 되는가
|
||
|
||
다음 그림:
|
||
|
||
```text
|
||
TAP
|
||
↓
|
||
vhost-net
|
||
↓
|
||
QEMU
|
||
↓
|
||
virtqueue
|
||
```
|
||
|
||
은 모든 packet이 `vhost-net → QEMU` 순으로 반드시 지나가는 것처럼 보인다.
|
||
|
||
하지만 `vhost-net`의 중요한 목적 중 하나는 **packet datapath에서 QEMU userspace를 우회하는 것**이다.
|
||
|
||
vhost-net 사용 시 fast path는 다음처럼 이해한다.
|
||
|
||
```text
|
||
TAP
|
||
↓
|
||
vhost-net
|
||
↓
|
||
virtqueue
|
||
↓
|
||
Guest
|
||
```
|
||
|
||
QEMU는 사라지는 것이 아니라 device lifecycle과 configuration을 관리한다.
|
||
|
||
---
|
||
|
||
## 105. Control Path와 Data Path
|
||
|
||
### Control / Setup Path
|
||
|
||
```text
|
||
virsh
|
||
↓
|
||
libvirt
|
||
↓
|
||
QEMU
|
||
↓
|
||
virtio-net Device Model
|
||
↓
|
||
feature negotiation
|
||
virtqueue setup
|
||
vhost-net setup
|
||
```
|
||
|
||
여기서 `control`은 Kubernetes Control Plane을 뜻하지 않는다.
|
||
|
||
일반적인 시스템 용어로 **설정/제어 경로**라는 의미다.
|
||
|
||
### Data Path
|
||
|
||
실제 packet이 반복적으로 흐르는 경로다.
|
||
|
||
vhost-net 사용 시:
|
||
|
||
```text
|
||
Physical NIC
|
||
↓
|
||
Bridge / Routing
|
||
↓
|
||
TAP
|
||
↓
|
||
vhost-net
|
||
↓
|
||
virtqueue
|
||
↓
|
||
virtio-net Frontend
|
||
↓
|
||
Guest TCP/IP
|
||
↓
|
||
Application
|
||
```
|
||
|
||
---
|
||
|
||
## 106. QEMU가 Userspace인데 packet이 QEMU를 안 거칠 수 있는 이유
|
||
|
||
QEMU가 장치의 생성/관리 주체라는 것과 모든 packet의 runtime datapath를 QEMU가 처리한다는 것은 다른 의미다.
|
||
|
||
CPU 가상화와 비교하면 이해하기 쉽다.
|
||
|
||
### CPU
|
||
|
||
```text
|
||
QEMU
|
||
↓
|
||
vCPU 생성/관리
|
||
|
||
실제 Guest instruction 실행
|
||
↓
|
||
KVM / VMX
|
||
```
|
||
|
||
QEMU가 vCPU를 만든다고 Guest의 `ADD`, `MOV`, `SUB`를 전부 QEMU가 실행하는 것은 아니다.
|
||
|
||
### Network
|
||
|
||
```text
|
||
QEMU
|
||
↓
|
||
virtio-net 생성/관리
|
||
|
||
실제 반복 packet I/O
|
||
↓
|
||
vhost-net / virtqueue
|
||
```
|
||
|
||
QEMU가 virtual NIC를 만든다고 packet 100만 개를 반드시 QEMU가 하나씩 처리할 필요는 없다.
|
||
|
||
---
|
||
|
||
## 107. vhost-net 최적화
|
||
|
||
QEMU userspace가 packet마다 I/O를 처리하면 다음 전환 비용이 누적될 수 있다.
|
||
|
||
```text
|
||
Host Kernel
|
||
↓
|
||
QEMU Userspace
|
||
↓
|
||
Host Kernel
|
||
↓
|
||
...
|
||
```
|
||
|
||
Packet rate가 높아질수록 userspace/kernel transition, scheduling, copy, notification 비용이 커질 수 있다.
|
||
|
||
### QEMU userspace backend
|
||
|
||
```text
|
||
TAP
|
||
↓
|
||
QEMU
|
||
↓
|
||
virtqueue
|
||
```
|
||
|
||
### vhost-net kernel backend
|
||
|
||
```text
|
||
TAP
|
||
↓
|
||
vhost-net
|
||
↓
|
||
virtqueue
|
||
```
|
||
|
||
핵심 최적화 방향:
|
||
|
||
```text
|
||
Packet마다 QEMU userspace 개입
|
||
↓
|
||
Kernel backend로 hot path 이동
|
||
↓
|
||
Context switch / userspace overhead 감소
|
||
```
|
||
|
||
---
|
||
|
||
## 108. vhost-net은 QEMU를 제거하지 않는다
|
||
|
||
vhost-net 사용 시에도 QEMU는 필요하다.
|
||
|
||
QEMU의 역할:
|
||
|
||
```text
|
||
VM lifecycle
|
||
Virtual hardware model
|
||
virtio device 생성
|
||
Feature negotiation
|
||
Queue configuration
|
||
Backend 연결
|
||
Device reset
|
||
Control/configuration handling
|
||
```
|
||
|
||
따라서:
|
||
|
||
```text
|
||
vhost-net != QEMU 제거
|
||
```
|
||
|
||
정확히는:
|
||
|
||
```text
|
||
vhost-net
|
||
=
|
||
QEMU가 담당하던 반복적인 virtio packet datapath의 상당 부분을
|
||
Host Kernel로 offload
|
||
```
|
||
|
||
라고 이해한다.
|
||
|
||
---
|
||
|
||
## 109. Fast Path와 Slow/Control Path
|
||
|
||
### Fast Path
|
||
|
||
빈번하게 반복되는 packet forwarding/data transfer 경로다.
|
||
|
||
예:
|
||
|
||
```text
|
||
TAP
|
||
↓
|
||
vhost-net
|
||
↓
|
||
virtqueue
|
||
```
|
||
|
||
### Control/Slow Path
|
||
|
||
상대적으로 빈도가 낮고 설정/예외 처리를 담당한다.
|
||
|
||
예:
|
||
|
||
```text
|
||
Device 초기화
|
||
Feature negotiation
|
||
Queue setup
|
||
Configuration change
|
||
Device reset
|
||
```
|
||
|
||
QEMU는 이 영역에 계속 중요한 역할을 한다.
|
||
|
||
---
|
||
|
||
## 110. Data Copy 최적화
|
||
|
||
네트워크 성능에서 중요한 비용 중 하나는 packet data copy다.
|
||
|
||
virtio/virtqueue/vhost 구조는 buffer descriptor를 이용해 불필요한 copy와 context switch를 줄이는 방향으로 설계되어 있다.
|
||
|
||
단, 이를 **항상 zero-copy**라고 일반화하면 안 된다.
|
||
|
||
실제 copy 여부는 다음에 따라 달라질 수 있다.
|
||
|
||
```text
|
||
Kernel version
|
||
QEMU version
|
||
vhost configuration
|
||
offload
|
||
NIC capability
|
||
packet path
|
||
GSO/GRO/TSO
|
||
```
|
||
|
||
---
|
||
|
||
## 111. Interrupt / Notification 최적화
|
||
|
||
Guest와 Host는 queue에 새로운 packet/buffer가 있음을 서로 알려야 한다.
|
||
|
||
단순화:
|
||
|
||
```text
|
||
Guest TX
|
||
↓
|
||
virtqueue descriptor 등록
|
||
↓
|
||
Host backend notification
|
||
↓
|
||
backend 처리
|
||
```
|
||
|
||
수신:
|
||
|
||
```text
|
||
Host RX
|
||
↓
|
||
virtqueue에 buffer/data 반영
|
||
↓
|
||
Guest notification
|
||
↓
|
||
Guest driver 처리
|
||
```
|
||
|
||
Packet마다 과도한 interrupt/notification이 발생하면 overhead가 커질 수 있다.
|
||
|
||
따라서 batching, interrupt moderation, queueing이 중요하다.
|
||
|
||
---
|
||
|
||
## 112. Multi-Queue 최적화
|
||
|
||
하나의 queue만 사용하면 특정 vCPU/processing path에 부하가 몰릴 수 있다.
|
||
|
||
virtio-net은 multi-queue를 사용할 수 있다.
|
||
|
||
```text
|
||
RX Queue 0 → vCPU 0
|
||
RX Queue 1 → vCPU 1
|
||
RX Queue 2 → vCPU 2
|
||
RX Queue 3 → vCPU 3
|
||
```
|
||
|
||
목적:
|
||
|
||
```text
|
||
Packet processing 병렬화
|
||
Single queue bottleneck 완화
|
||
Multi-core 활용
|
||
```
|
||
|
||
효과는 workload, CPU affinity, IRQ placement, queue configuration에 따라 달라진다.
|
||
|
||
---
|
||
|
||
## 113. Offload 최적화
|
||
|
||
대표적인 offload:
|
||
|
||
```text
|
||
TSO - TCP Segmentation Offload
|
||
GSO - Generic Segmentation Offload
|
||
GRO - Generic Receive Offload
|
||
Checksum Offload
|
||
```
|
||
|
||
목적:
|
||
|
||
```text
|
||
작은 packet을 하나씩 처리하는 CPU overhead 감소
|
||
Segmentation / aggregation 비용 절감
|
||
```
|
||
|
||
주의:
|
||
|
||
> offload가 활성화되어 있으면 tcpdump에서 보이는 packet size나 checksum이 실제 wire에서 보이는 것과 다르게 보일 수 있다.
|
||
|
||
---
|
||
|
||
## 114. Linux Bridge가 항상 Host TCP/IP Stack을 거치는 것은 아니다
|
||
|
||
Bridge가 단순 L2 forwarding을 수행하는 경우 frame이 반드시 Host의 일반적인 L3 TCP/IP stack을 거치는 것은 아니다.
|
||
|
||
예:
|
||
|
||
```text
|
||
VM1 TAP
|
||
↓
|
||
Linux Bridge
|
||
↓
|
||
VM2 TAP
|
||
```
|
||
|
||
반면 Host가 다음 역할을 하면 L3/Netfilter 경로가 개입한다.
|
||
|
||
```text
|
||
Routing
|
||
NAT
|
||
Host-local termination
|
||
Firewall
|
||
```
|
||
|
||
따라서 다음을 고정된 packet path로 보면 안 된다.
|
||
|
||
```text
|
||
Physical NIC
|
||
↓
|
||
Host TCP/IP Stack
|
||
↓
|
||
Bridge
|
||
```
|
||
|
||
실제 경로는 bridge/routing/NAT 구성에 따라 달라진다.
|
||
|
||
---
|
||
|
||
## 115. Host Physical NIC로 나갈 때 virtio를 다시 거치지 않는다
|
||
|
||
```text
|
||
Guest
|
||
virtio-net
|
||
↓
|
||
vhost-net
|
||
↓
|
||
TAP
|
||
↓
|
||
Linux Bridge
|
||
↓
|
||
Intel NIC Driver
|
||
↓
|
||
Intel Physical NIC
|
||
```
|
||
|
||
즉:
|
||
|
||
```text
|
||
Guest virtio
|
||
→ Host virtio
|
||
→ Physical NIC
|
||
```
|
||
|
||
구조가 아니다.
|
||
|
||
virtio는 Guest virtual I/O device와 Host backend 사이의 인터페이스다.
|
||
|
||
---
|
||
|
||
## 116. 현재 Keycloak/K3s 테스트 환경과 연결
|
||
|
||
```text
|
||
Client
|
||
↓
|
||
Host Physical NIC
|
||
↓
|
||
Host Nginx
|
||
↓
|
||
Host Network
|
||
↓
|
||
VM1 / VM2
|
||
↓
|
||
K3s
|
||
↓
|
||
Keycloak Node 1 / 2
|
||
```
|
||
|
||
VM network까지 펼치면:
|
||
|
||
```text
|
||
Client
|
||
↓
|
||
Physical NIC
|
||
↓
|
||
Host Network Stack / Bridge / Route / NAT
|
||
↓
|
||
TAP(vm1) / TAP(vm2)
|
||
↓
|
||
vhost-net
|
||
↓
|
||
virtqueue
|
||
↓
|
||
virtio-net
|
||
↓
|
||
Guest Network Stack
|
||
↓
|
||
K3s networking
|
||
↓
|
||
Keycloak
|
||
```
|
||
|
||
이후 K3s 내부에는 CNI, Service, Pod network가 추가되므로 별도 계층으로 분석한다.
|
||
|
||
---
|
||
|
||
## 117. 이 구조에서 발생할 수 있는 문제
|
||
|
||
### 117.1 TAP/Bridge 연결 오류
|
||
|
||
증상:
|
||
|
||
```text
|
||
VM 외부 통신 불가
|
||
Host ↔ VM 통신 불가
|
||
특정 VM만 통신 불가
|
||
```
|
||
|
||
확인:
|
||
|
||
```bash
|
||
ip link
|
||
bridge link
|
||
bridge fdb show
|
||
virsh domiflist <vm>
|
||
```
|
||
|
||
### 117.2 Routing 오류
|
||
|
||
증상:
|
||
|
||
```text
|
||
같은 subnet은 통신되지만 다른 subnet은 안 됨
|
||
gateway까진 되지만 외부 통신 실패
|
||
```
|
||
|
||
확인:
|
||
|
||
```bash
|
||
ip route
|
||
ip rule
|
||
```
|
||
|
||
### 117.3 NAT/Firewall 오류
|
||
|
||
증상:
|
||
|
||
```text
|
||
VM → Internet 실패
|
||
외부 → VM 접근 실패
|
||
특정 port만 실패
|
||
```
|
||
|
||
확인 대상:
|
||
|
||
```text
|
||
nftables
|
||
iptables
|
||
NAT rules
|
||
IP forwarding
|
||
```
|
||
|
||
### 117.4 vhost-net 미사용 또는 비효율적 datapath
|
||
|
||
높은 packet rate에서 QEMU userspace가 datapath를 직접 처리하면 CPU overhead가 커질 수 있다.
|
||
|
||
관찰:
|
||
|
||
```text
|
||
QEMU CPU usage
|
||
vhost thread
|
||
packet rate
|
||
latency
|
||
context switch
|
||
```
|
||
|
||
### 117.5 Single Queue Bottleneck
|
||
|
||
하나의 queue/vCPU에 packet processing이 집중될 수 있다.
|
||
|
||
확인 대상:
|
||
|
||
```text
|
||
virtio multi-queue
|
||
IRQ distribution
|
||
per-vCPU CPU usage
|
||
RSS/RPS/XPS
|
||
```
|
||
|
||
### 117.6 Offload 때문에 packet capture가 예상과 다르게 보임
|
||
|
||
원인 후보:
|
||
|
||
```text
|
||
GSO
|
||
GRO
|
||
TSO
|
||
Checksum offload
|
||
```
|
||
|
||
### 117.7 Host CPU Contention으로 network latency 증가
|
||
|
||
vhost-net, QEMU thread, softirq도 Host CPU를 사용한다.
|
||
|
||
따라서 network 문제처럼 보여도 CPU scheduling 문제일 수 있다.
|
||
|
||
---
|
||
|
||
## 118. 실제 Linux에서 확인할 명령어
|
||
|
||
### Physical NIC
|
||
|
||
```bash
|
||
ip link
|
||
ip addr
|
||
ethtool <interface>
|
||
```
|
||
|
||
### Linux Bridge
|
||
|
||
```bash
|
||
ip link show type bridge
|
||
bridge link
|
||
bridge fdb show
|
||
```
|
||
|
||
### TAP / vnet
|
||
|
||
```bash
|
||
ip link
|
||
ip tuntap show
|
||
```
|
||
|
||
### libvirt VM NIC
|
||
|
||
```bash
|
||
virsh domiflist <domain>
|
||
```
|
||
|
||
### libvirt network
|
||
|
||
```bash
|
||
virsh net-list --all
|
||
virsh net-info <network>
|
||
virsh net-dumpxml <network>
|
||
```
|
||
|
||
### Routing
|
||
|
||
```bash
|
||
ip route
|
||
ip rule
|
||
```
|
||
|
||
### Guest NIC
|
||
|
||
```bash
|
||
ip link
|
||
ip addr
|
||
ip route
|
||
ip neigh
|
||
```
|
||
|
||
### virtio 장치
|
||
|
||
```bash
|
||
lspci
|
||
lsmod | grep virtio
|
||
```
|
||
|
||
### vhost
|
||
|
||
```bash
|
||
lsmod | grep vhost
|
||
```
|
||
|
||
---
|
||
|
||
## 119. 실제 packet path 추적
|
||
|
||
Host:
|
||
|
||
```bash
|
||
sudo tcpdump -ni <physical-nic>
|
||
sudo tcpdump -ni <bridge>
|
||
sudo tcpdump -ni <tap-or-vnet>
|
||
```
|
||
|
||
Guest:
|
||
|
||
```bash
|
||
sudo tcpdump -ni <guest-interface>
|
||
```
|
||
|
||
예:
|
||
|
||
```text
|
||
Physical NIC O
|
||
Bridge O
|
||
TAP X
|
||
```
|
||
|
||
이면 Guest 내부보다 먼저 Host Bridge/TAP mapping을 의심한다.
|
||
|
||
```text
|
||
TAP O
|
||
Guest NIC X
|
||
```
|
||
|
||
이면 virtio/vhost/Guest NIC 계층을 의심한다.
|
||
|
||
```text
|
||
Guest NIC O
|
||
Socket X
|
||
```
|
||
|
||
이면 Guest routing/firewall/listen 상태를 의심한다.
|
||
|
||
---
|
||
|
||
## 120. Keycloak Refresh Token 실험과의 관계
|
||
|
||
Refresh Token 경쟁 자체는 virtio-net 문제가 아니다.
|
||
|
||
하지만 다음 경로를 공유하므로 network virtualization 문제가 실험 결과에 영향을 줄 수 있다.
|
||
|
||
```text
|
||
Client
|
||
↓
|
||
Nginx
|
||
↓
|
||
VM1 / VM2
|
||
↓
|
||
K3s
|
||
↓
|
||
Keycloak
|
||
↓
|
||
PostgreSQL / Redis
|
||
```
|
||
|
||
예:
|
||
|
||
```text
|
||
Node1 요청만 지연
|
||
VM2 packet loss
|
||
Host bridge misconfiguration
|
||
NAT/conntrack issue
|
||
Host CPU contention으로 vhost 처리 지연
|
||
```
|
||
|
||
이런 문제를 Refresh Token 경쟁이나 DB lock으로 오해하지 않도록 network path를 별도로 검증한다.
|
||
|
||
---
|
||
|
||
## 121. 이 SSOT에서 파생될 CONCEPT
|
||
|
||
### CONCEPT
|
||
|
||
**KVM/QEMU에서 Guest packet이 Host Physical NIC까지 이동하는 과정**
|
||
|
||
포함 범위:
|
||
|
||
```text
|
||
virsh
|
||
libvirt
|
||
QEMU
|
||
virtio
|
||
virtio-net
|
||
Frontend / Backend
|
||
virtqueue
|
||
QEMU virtio Device Model
|
||
vhost-net
|
||
TAP
|
||
Linux Bridge
|
||
Routing
|
||
NAT
|
||
Physical NIC
|
||
Guest TCP/IP Stack
|
||
Socket
|
||
Data Path / Control Path
|
||
Fast Path
|
||
Multi-Queue
|
||
Offload
|
||
Packet tracing
|
||
```
|
||
|
||
현재 단계에서는 이 요소들이 하나의 packet 실행 경로를 설명하므로 하나의 CONCEPT로 관리한다.
|
||
|
||
---
|
||
|
||
## 122. OPEN QUESTION
|
||
|
||
### OQ-1. 현재 VM network는 Bridge, NAT, Routing 중 어떤 구조인가?
|
||
|
||
```bash
|
||
virsh net-list --all
|
||
virsh net-dumpxml <network>
|
||
ip link
|
||
bridge link
|
||
ip route
|
||
```
|
||
|
||
### OQ-2. VM1/VM2의 TAP/vnet interface는 무엇인가?
|
||
|
||
```bash
|
||
virsh domiflist vm1
|
||
virsh domiflist vm2
|
||
ip link
|
||
bridge link
|
||
```
|
||
|
||
### OQ-3. 현재 환경에서 vhost-net이 실제 사용되는가?
|
||
|
||
확인 후보:
|
||
|
||
```bash
|
||
lsmod | grep vhost
|
||
```
|
||
|
||
추가로 QEMU arguments와 libvirt domain XML을 확인한다.
|
||
|
||
### OQ-4. QEMU backend와 vhost-net의 성능 차이가 현재 Host에서 관찰 가능한가?
|
||
|
||
비교:
|
||
|
||
```text
|
||
Latency
|
||
Throughput
|
||
QEMU CPU
|
||
Host CPU
|
||
Context Switch
|
||
Packet rate
|
||
```
|
||
|
||
### OQ-5. Multi-queue가 현재 virtio-net에 활성화되어 있는가?
|
||
|
||
확인 대상:
|
||
|
||
```text
|
||
QEMU/libvirt NIC configuration
|
||
Guest ethtool
|
||
queue count
|
||
IRQ distribution
|
||
```
|
||
|
||
### OQ-6. Host Nginx에서 VM1/VM2 Keycloak까지 실제 packet path는 무엇인가?
|
||
|
||
Host NIC, Bridge, TAP, Guest NIC에서 `tcpdump`로 추적한다.
|
||
|
||
### OQ-7. Keycloak load test 시 network virtualization이 latency에 영향을 줄 정도로 Host CPU를 사용하는가?
|
||
|
||
관찰:
|
||
|
||
```text
|
||
QEMU CPU
|
||
vhost thread
|
||
softirq
|
||
Host CPU
|
||
Guest CPU
|
||
network latency
|
||
```
|
||
|
||
---
|
||
|
||
## 123. OPEN QUESTION → CASE
|
||
|
||
```text
|
||
SSOT
|
||
↓
|
||
CONCEPT
|
||
↓
|
||
OPEN QUESTION
|
||
↓
|
||
실제 packet capture / configuration 확인 / load test
|
||
↓
|
||
CASE
|
||
```
|
||
|
||
예:
|
||
|
||
```text
|
||
CONCEPT
|
||
"vhost-net은 QEMU userspace를 우회해 packet datapath를 처리할 수 있다"
|
||
↓
|
||
OPEN QUESTION
|
||
"현재 테스트 Host에서 vhost-net이 실제 활성화되어 있는가?"
|
||
↓
|
||
CASE
|
||
"libvirt/QEMU virtio-net backend 구성 확인 및 vhost-net 사용 검증"
|
||
```
|
||
|
||
---
|
||
|
||
## 124. 핵심 Claim
|
||
|
||
1. `virsh`는 VM/network management CLI이며 packet datapath에 직접 참여하지 않는다.
|
||
2. libvirt는 VM lifecycle 및 NIC/network configuration을 관리한다.
|
||
3. virtio는 단일 process나 단일 kernel module이 아니라 Guest frontend와 Host backend 사이의 가상 I/O 표준이다.
|
||
4. virtio-net frontend driver는 Guest Kernel에 존재한다.
|
||
5. QEMU virtio Device Model은 Host Userspace의 QEMU process 내부에서 virtual NIC의 생성, configuration, negotiation, lifecycle에 관여한다.
|
||
6. virtqueue는 Guest와 Host backend가 I/O buffer를 주고받는 descriptor 기반 shared queue 구조다.
|
||
7. vhost-net을 사용하지 않으면 QEMU userspace가 virtio network datapath backend 역할을 할 수 있다.
|
||
8. vhost-net을 사용하면 반복적인 packet datapath의 상당 부분을 Host Kernel에서 처리해 QEMU userspace를 우회할 수 있다.
|
||
9. 따라서 `TAP → vhost-net → QEMU → virtqueue`를 모든 환경의 일반적인 packet 경로로 표현하면 안 된다.
|
||
10. TAP은 VM의 Ethernet frame과 Host Linux networking을 연결하는 Host-side virtual network interface다.
|
||
11. Linux Bridge는 L2 software switch 역할을 하며 MAC 기반으로 frame을 forwarding한다.
|
||
12. Routing은 L3/IP 기반으로 서로 다른 network 사이의 packet 경로를 결정한다.
|
||
13. Host Physical NIC로 나갈 때 다시 virtio를 거치는 것이 아니라 Physical NIC의 실제 driver를 사용한다.
|
||
14. Guest TCP/IP Stack은 실제 Guest Linux Kernel의 network stack이며 TCP, IP, routing, socket 등을 처리한다.
|
||
15. Keycloak은 virtio/TAP/Bridge를 직접 알 필요가 없으며 Guest socket을 통해 network를 사용한다.
|
||
16. vhost-net의 목적은 QEMU를 제거하는 것이 아니라 반복적인 hot datapath를 Kernel로 offload해 userspace/kernel 전환 및 packet processing overhead를 줄이는 것이다.
|
||
17. Network virtualization 문제와 Keycloak Refresh Token 경쟁 문제는 별개지만 동일한 실험 환경에서 서로 비슷한 증상으로 보일 수 있으므로 계층별 관측이 필요하다.
|
||
|
||
---
|
||
|
||
## 125. 최종 기준 구조
|
||
|
||
### Control / Setup
|
||
|
||
```text
|
||
User
|
||
↓
|
||
virsh
|
||
↓
|
||
libvirt
|
||
↓
|
||
QEMU
|
||
↓
|
||
virtio-net Device Model
|
||
├─ virtual NIC 생성
|
||
├─ Guest 노출
|
||
├─ feature negotiation
|
||
├─ virtqueue 설정
|
||
└─ vhost-net backend 설정
|
||
```
|
||
|
||
### Data Path - vhost-net 사용
|
||
|
||
```text
|
||
Internet / Client
|
||
↓
|
||
Physical NIC
|
||
↓
|
||
Physical NIC Driver
|
||
↓
|
||
Linux Bridge / Routing / NAT
|
||
↓
|
||
TAP
|
||
↓
|
||
vhost-net
|
||
↓
|
||
virtqueue
|
||
↓
|
||
virtio-net Frontend Driver
|
||
↓
|
||
Guest TCP/IP Stack
|
||
↓
|
||
Socket
|
||
↓
|
||
Keycloak
|
||
```
|
||
|
||
### Data Path - QEMU backend 사용
|
||
|
||
```text
|
||
Internet / Client
|
||
↓
|
||
Physical NIC
|
||
↓
|
||
Physical NIC Driver
|
||
↓
|
||
Linux Bridge / Routing / NAT
|
||
↓
|
||
TAP
|
||
↓
|
||
QEMU virtio backend
|
||
↓
|
||
virtqueue
|
||
↓
|
||
virtio-net Frontend Driver
|
||
↓
|
||
Guest TCP/IP Stack
|
||
↓
|
||
Socket
|
||
↓
|
||
Keycloak
|
||
```
|
||
|
||
---
|
||
|
||
## 126. 다음 실습 순서
|
||
|
||
```text
|
||
1. Physical NIC 확인
|
||
2. libvirt virtual network 확인
|
||
3. Bridge/NAT/Route 확인
|
||
4. VM별 TAP/vnet 확인
|
||
5. virtio-net device 확인
|
||
6. vhost-net 사용 여부 확인
|
||
7. Guest NIC / route 확인
|
||
8. Host Nginx → VM packet path tcpdump
|
||
9. VM1 ↔ VM2 packet path 확인
|
||
10. Keycloak 요청 시 packet flow 확인
|
||
11. 부하 발생 시 QEMU/vhost CPU usage 비교
|
||
12. multi-queue / offload 확인
|
||
```
|
||
|
||
검증되지 않은 항목은 OPEN QUESTION으로 남기고 실제 결과가 확보되면 CASE로 전환한다.
|
||
|
||
그 다음에는 이 네트워크 가상화 위에 추가되는 **K3s/CNI/Service/Pod network 계층**을 연결한다.
|
||
|
||
# 제4부 — 스토리지 가상화
|
||
## 127. 문서 목적
|
||
|
||
이 문서는 QEMU/KVM 기반 VM에서 **Guest 애플리케이션의 `write()`/`fsync()`가 실제 Host의 물리 SSD/NVMe까지 어떻게 내려가는지**를 하나의 일관된 경로로 설명한다.
|
||
|
||
핵심 대상은 다음과 같다.
|
||
|
||
- Guest VFS / ext4·XFS
|
||
- Guest Page Cache / Writeback
|
||
- Guest Block I/O Layer
|
||
- `/dev/vda`
|
||
- `virtio-blk` / `virtqueue`
|
||
- QEMU virtio device/backend
|
||
- qcow2 / RAW / Host block device
|
||
- Host Page Cache / Direct I/O
|
||
- Host Filesystem / Block Layer / blk-mq
|
||
- I/O Scheduler
|
||
- NVMe Driver / Physical NVMe
|
||
- `write()`, `fsync()`, FLUSH
|
||
- QEMU cache mode
|
||
- Storage contention
|
||
|
||
이 문서는 Storage 가상화의 **핵심 실행 경로와 운영상 중요한 문제**를 다룬다. qcow2 내부 L1/L2 table, blk-mq tag allocator, NVMe submission/completion queue 같은 세부 구현은 필요 시 별도 문서에서 다룬다.
|
||
|
||
---
|
||
|
||
## 128. 전체 구조
|
||
|
||
```text
|
||
[Guest Userspace]
|
||
|
||
PostgreSQL / Keycloak
|
||
│
|
||
read / write
|
||
fsync / sync
|
||
▼
|
||
|
||
[Guest Kernel]
|
||
|
||
VFS
|
||
↓
|
||
ext4 / XFS
|
||
↓
|
||
Guest Page Cache
|
||
│
|
||
writeback
|
||
↓
|
||
Guest Block Layer
|
||
│
|
||
WRITE / FLUSH / etc.
|
||
↓
|
||
/dev/vda
|
||
↓
|
||
virtio-blk Frontend
|
||
↓
|
||
virtqueue
|
||
|
||
════════════════════ VM Boundary ════════════════════
|
||
|
||
[Host Userspace]
|
||
|
||
QEMU
|
||
│
|
||
virtio device/backend
|
||
↓
|
||
QEMU Block Layer
|
||
↓
|
||
┌────────────┼─────────────┐
|
||
↓ ↓ ↓
|
||
qcow2 RAW Block Device
|
||
│ │ │
|
||
└────────────┼─────────────┘
|
||
↓
|
||
|
||
[Host Kernel]
|
||
|
||
Host Page Cache
|
||
(cache mode에 따라)
|
||
↓
|
||
Host Filesystem
|
||
↓
|
||
Host Block Layer
|
||
↓
|
||
blk-mq
|
||
↓
|
||
I/O Scheduler
|
||
↓
|
||
NVMe Driver
|
||
↓
|
||
|
||
[Hardware]
|
||
|
||
NVMe Controller
|
||
↓
|
||
Device-side Cache
|
||
↓
|
||
Non-volatile Media
|
||
```
|
||
|
||
핵심 문장은 다음과 같다.
|
||
|
||
> Guest는 `/dev/vda`를 실제 block device처럼 보지만, Host에서는 그 disk가 qcow2 파일, RAW 파일, 또는 실제 block device에 연결되어 있을 수 있다.
|
||
|
||
---
|
||
|
||
## 129. Guest Application: `read()` / `write()`에서 시작
|
||
|
||
VM 안의 PostgreSQL이나 Keycloak 같은 process는 SSD나 `virtio-blk`를 직접 다루지 않는다.
|
||
|
||
예를 들어 PostgreSQL이 파일에 데이터를 기록하면 개념적으로 다음 system call을 사용한다.
|
||
|
||
```c
|
||
write(fd, buffer, size);
|
||
```
|
||
|
||
```text
|
||
[Guest Userspace]
|
||
|
||
PostgreSQL
|
||
│
|
||
│ write()
|
||
▼
|
||
|
||
════════ System Call ════════
|
||
|
||
[Guest Kernel]
|
||
|
||
VFS
|
||
```
|
||
|
||
즉 애플리케이션은 저장장치를 직접 조작하는 것이 아니라 Guest Linux Kernel에 파일 연산을 요청한다.
|
||
|
||
대표적인 파일 관련 system call:
|
||
|
||
```text
|
||
open()
|
||
read()
|
||
write()
|
||
close()
|
||
fsync()
|
||
```
|
||
|
||
이 시점에는 아직 QEMU, qcow2, Host NVMe가 등장하지 않는다.
|
||
|
||
---
|
||
|
||
## 130. VFS: 공통 파일 인터페이스 계층
|
||
|
||
VFS(Virtual File System)는 Linux Kernel 내부에서 여러 filesystem을 동일한 API로 사용할 수 있도록 연결하는 공통 계층이다.
|
||
|
||
Guest가 ext4라면:
|
||
|
||
```text
|
||
PostgreSQL
|
||
↓
|
||
write()
|
||
↓
|
||
VFS
|
||
↓
|
||
ext4
|
||
```
|
||
|
||
XFS라면:
|
||
|
||
```text
|
||
PostgreSQL
|
||
↓
|
||
write()
|
||
↓
|
||
VFS
|
||
↓
|
||
XFS
|
||
```
|
||
|
||
VFS의 핵심 역할:
|
||
|
||
```text
|
||
이 fd가 어떤 파일인가?
|
||
↓
|
||
이 파일은 어떤 filesystem에 속하는가?
|
||
↓
|
||
해당 filesystem 구현으로 연산 전달
|
||
```
|
||
|
||
> VFS는 애플리케이션의 공통 파일 연산을 실제 filesystem 구현으로 연결한다.
|
||
|
||
---
|
||
|
||
## 131. Filesystem(ext4/XFS): 파일 세계를 block 공간에 배치
|
||
|
||
SSD는 `/var/lib/postgresql/data` 같은 디렉터리 구조를 모른다.
|
||
|
||
저장장치 입장에서는 결국 block 단위 공간이다.
|
||
|
||
```text
|
||
Block 0
|
||
Block 1
|
||
Block 2
|
||
Block 3
|
||
...
|
||
```
|
||
|
||
하지만 사용자는 다음과 같이 파일과 디렉터리를 본다.
|
||
|
||
```text
|
||
/
|
||
├── etc
|
||
├── home
|
||
└── var
|
||
└── lib
|
||
└── postgresql
|
||
└── data
|
||
```
|
||
|
||
이 논리 구조를 제공하고 관리하는 것이 ext4/XFS 같은 filesystem이다.
|
||
|
||
Filesystem이 관리하는 대표 정보:
|
||
|
||
- 파일 이름과 디렉터리 구조
|
||
- 파일 크기
|
||
- owner / permission
|
||
- timestamp
|
||
- inode / metadata
|
||
- 파일 데이터가 저장될 block
|
||
- free space
|
||
- filesystem consistency
|
||
|
||
개념적으로:
|
||
|
||
```text
|
||
사람/프로그램이 보는 세계
|
||
|
||
/var/lib/postgresql/data/users
|
||
│
|
||
▼
|
||
ext4/XFS
|
||
│
|
||
▼
|
||
저장장치가 보는 세계
|
||
|
||
Block 8142
|
||
Block 8143
|
||
Block 9201
|
||
...
|
||
```
|
||
|
||
---
|
||
|
||
## 132. inode
|
||
|
||
inode는 Linux filesystem에서 파일 metadata와 저장 위치 정보를 관리하는 핵심 자료구조다.
|
||
|
||
```text
|
||
"users.db"
|
||
↓
|
||
Directory Entry
|
||
↓
|
||
inode #1234
|
||
│
|
||
├─ owner
|
||
├─ permission
|
||
├─ size
|
||
├─ timestamps
|
||
└─ file data가 저장된 block 정보
|
||
```
|
||
|
||
파일 이름 자체와 inode는 같은 것이 아니다.
|
||
|
||
Storage 가상화를 이해하기 위해 inode 내부 구현까지 파고들 필요는 없지만, filesystem이 파일과 block을 연결한다는 점은 알아야 한다.
|
||
|
||
---
|
||
|
||
## 133. Page Cache: `write()`가 바로 SSD write는 아니다
|
||
|
||
일반적인 buffered I/O에서는 `write()`가 호출될 때마다 물리 SSD까지 즉시 내려갈 필요가 없다.
|
||
|
||
```text
|
||
Application
|
||
│
|
||
│ write()
|
||
▼
|
||
Linux Kernel
|
||
│
|
||
▼
|
||
Page Cache (RAM)
|
||
│
|
||
│ 나중에 writeback
|
||
▼
|
||
Filesystem / Block Layer
|
||
↓
|
||
SSD
|
||
```
|
||
|
||
예를 들어 storage에는 현재 `ABC`가 있는데 애플리케이션이 `DEF`를 추가했다고 하자.
|
||
|
||
```text
|
||
Page Cache (RAM)
|
||
┌──────────────┐
|
||
│ ABCDEF │ ← 최신 상태, dirty
|
||
└──────────────┘
|
||
|
||
SSD
|
||
┌──────────────┐
|
||
│ ABC │ ← 아직 이전 상태
|
||
└──────────────┘
|
||
```
|
||
|
||
storage보다 최신인 Page Cache page를 **dirty page**라고 한다.
|
||
|
||
이후 kernel writeback이 실제 storage 쪽으로 내려간다.
|
||
|
||
```text
|
||
Dirty Page
|
||
↓
|
||
Filesystem
|
||
↓
|
||
Block Layer
|
||
↓
|
||
Storage
|
||
```
|
||
|
||
따라서:
|
||
|
||
```text
|
||
write() 성공
|
||
≠
|
||
Physical SSD 영속화 완료
|
||
```
|
||
|
||
이다.
|
||
|
||
---
|
||
|
||
## 134. Guest Block I/O Layer
|
||
|
||
현재 위치:
|
||
|
||
```text
|
||
PostgreSQL
|
||
↓
|
||
write()
|
||
↓
|
||
VFS
|
||
↓
|
||
ext4
|
||
↓
|
||
Page Cache / Writeback
|
||
↓
|
||
Guest Block I/O Layer
|
||
↓
|
||
virtio-blk Driver
|
||
```
|
||
|
||
Filesystem은 파일과 block allocation을 관리하고, Linux Block I/O subsystem은 그 요청을 아래 block device driver가 처리할 수 있는 I/O 요청으로 전달·관리한다.
|
||
|
||
```text
|
||
Filesystem 세계
|
||
|
||
/users/data.db
|
||
offset 8192에 4KB write
|
||
│
|
||
▼
|
||
──────────────────────
|
||
Block I/O Layer
|
||
──────────────────────
|
||
│
|
||
▼
|
||
Block Device 세계
|
||
|
||
/dev/vda의 특정 위치에
|
||
READ / WRITE / FLUSH
|
||
```
|
||
|
||
대표 요청:
|
||
|
||
```text
|
||
READ
|
||
WRITE
|
||
FLUSH
|
||
DISCARD
|
||
```
|
||
|
||
실제 Linux 내부에는 `bio`, request, queue, `blk-mq` 등이 존재한다.
|
||
|
||
---
|
||
|
||
## 135. `/dev/vda`: Guest가 보는 가상 Block Device
|
||
|
||
물리 머신에서는:
|
||
|
||
```text
|
||
/dev/sda
|
||
/dev/nvme0n1
|
||
```
|
||
|
||
같은 block device가 보일 수 있다.
|
||
|
||
virtio-blk를 사용하는 VM에서는 흔히:
|
||
|
||
```text
|
||
/dev/vda
|
||
/dev/vdb
|
||
```
|
||
|
||
처럼 보인다.
|
||
|
||
Guest에서:
|
||
|
||
```bash
|
||
lsblk
|
||
```
|
||
|
||
예시:
|
||
|
||
```text
|
||
NAME SIZE TYPE MOUNTPOINT
|
||
vda 100G disk
|
||
├─vda1 1G part /boot
|
||
└─vda2 99G part /
|
||
```
|
||
|
||
Guest Linux는 `/dev/vda`를 하나의 block device로 인식한다. 하지만 그것이 Host의 실제 SSD라는 뜻은 아니다.
|
||
|
||
---
|
||
|
||
## 136. `/dev/vda`와 Filesystem 관계
|
||
|
||
```text
|
||
/dev/vda ← Virtual Block Device
|
||
│
|
||
└─ /dev/vda2 ← Partition
|
||
│
|
||
└─ ext4 ← Filesystem
|
||
│
|
||
└─ /
|
||
```
|
||
|
||
위에서 아래로 보면:
|
||
|
||
```text
|
||
/
|
||
↓
|
||
ext4
|
||
↓
|
||
/dev/vda2
|
||
↓
|
||
/dev/vda
|
||
```
|
||
|
||
`cd /var/lib/postgresql`은 filesystem 세계를 보는 것이고, `lsblk`에서 `vda`를 보는 것은 block device 세계를 보는 것이다.
|
||
|
||
---
|
||
|
||
## 137. virtio-blk: Guest의 가상 Block Device Driver
|
||
|
||
```text
|
||
Guest Kernel
|
||
|
||
ext4
|
||
↓
|
||
Block I/O Layer
|
||
↓
|
||
/dev/vda
|
||
↓
|
||
virtio-blk Driver
|
||
```
|
||
|
||
구분:
|
||
|
||
- `/dev/vda` = Guest Linux에 보이는 block device
|
||
- `virtio-blk` = 해당 virtual block device를 제어하는 Guest Kernel driver
|
||
|
||
Network와 비교:
|
||
|
||
```text
|
||
Network
|
||
ens3
|
||
↓
|
||
virtio-net
|
||
|
||
Storage
|
||
/dev/vda
|
||
↓
|
||
virtio-blk
|
||
```
|
||
|
||
---
|
||
|
||
## 138. virtio-blk와 virtqueue
|
||
|
||
Guest Block Layer에서 다음과 같은 요청이 내려왔다고 하자.
|
||
|
||
> `/dev/vda`의 특정 위치에 이 데이터를 WRITE하라.
|
||
|
||
virtio-blk driver는 이를 Virtio block request로 구성하고 virtqueue에 게시한다.
|
||
|
||
```text
|
||
Guest Kernel
|
||
|
||
ext4
|
||
↓
|
||
Block I/O Layer
|
||
↓
|
||
/dev/vda
|
||
↓
|
||
virtio-blk
|
||
↓
|
||
virtqueue
|
||
```
|
||
|
||
Network에서:
|
||
|
||
```text
|
||
TCP/IP Stack
|
||
↓
|
||
virtio-net
|
||
↓
|
||
virtqueue
|
||
```
|
||
|
||
였던 구조가 Storage에서도 반복된다.
|
||
|
||
---
|
||
|
||
## 139. virtqueue의 실제 의미
|
||
|
||
virtqueue를 단순한 "데이터 파이프"로 보면 부정확하다.
|
||
|
||
Guest memory에 I/O buffer가 있고 descriptor가 그 buffer를 가리킨다.
|
||
|
||
```text
|
||
Guest RAM
|
||
|
||
┌────────────────────────┐
|
||
│ Write할 Data Buffer │
|
||
│ "HELLO..." │
|
||
└────────────────────────┘
|
||
▲
|
||
│
|
||
virtqueue descriptor
|
||
│
|
||
▼
|
||
┌────────────────────────┐
|
||
│ Virtio Block Request │
|
||
│ Operation: WRITE │
|
||
│ Sector: ... │
|
||
│ Data Buffer: ... │
|
||
└────────────────────────┘
|
||
```
|
||
|
||
의미는 대략:
|
||
|
||
> `/dev/vda`의 이 위치에 Guest RAM의 이 buffer를 기록해라.
|
||
|
||
이다.
|
||
|
||
처리가 끝나면 backend는 completion을 Guest에 돌려준다.
|
||
|
||
---
|
||
|
||
## 140. VM Boundary를 넘으면 QEMU가 등장
|
||
|
||
기본적인 QEMU 경로:
|
||
|
||
```text
|
||
Guest
|
||
────────────────────────────
|
||
/dev/vda
|
||
↓
|
||
virtio-blk
|
||
↓
|
||
virtqueue
|
||
│
|
||
════════ VM Boundary ════════
|
||
│
|
||
▼
|
||
Host Userspace
|
||
────────────────────────────
|
||
QEMU
|
||
│
|
||
├─ virtio-blk Device Model
|
||
└─ Block Backend
|
||
↓
|
||
vm1.qcow2
|
||
↓
|
||
Host Kernel
|
||
────────────────────────────
|
||
Host Filesystem
|
||
↓
|
||
Host Block Layer
|
||
↓
|
||
NVMe Driver
|
||
↓
|
||
Physical NVMe
|
||
```
|
||
|
||
QEMU는 Guest에게 virtual block device를 노출하고 Guest의 virtual I/O를 Host backend에 연결한다.
|
||
|
||
---
|
||
|
||
## 141. QEMU가 물리 SSD를 직접 제어하는 것은 아니다
|
||
|
||
backend가 qcow2 파일이라면 QEMU는 결국 Host Linux에 파일 I/O를 요청한다.
|
||
|
||
```text
|
||
QEMU
|
||
│
|
||
│ pread/pwrite 등
|
||
▼
|
||
Host Kernel
|
||
│
|
||
▼
|
||
Host Filesystem
|
||
│
|
||
▼
|
||
Host Block Layer
|
||
│
|
||
▼
|
||
NVMe Driver
|
||
│
|
||
▼
|
||
Physical NVMe
|
||
```
|
||
|
||
즉 Guest storage stack 아래에 Host storage stack이 한 번 더 존재할 수 있다.
|
||
|
||
---
|
||
|
||
## 142. qcow2: Host에서는 파일, Guest에서는 디스크
|
||
|
||
예를 들어 Host에:
|
||
|
||
```text
|
||
/var/lib/libvirt/images/keycloak-node1.qcow2
|
||
```
|
||
|
||
라는 파일이 있다고 하자.
|
||
|
||
Host 관점:
|
||
|
||
```text
|
||
keycloak-node1.qcow2
|
||
"파일 하나"
|
||
```
|
||
|
||
Guest 관점:
|
||
|
||
```text
|
||
/dev/vda
|
||
├─ /dev/vda1
|
||
└─ /dev/vda2
|
||
```
|
||
|
||
즉:
|
||
|
||
```text
|
||
Host 관점
|
||
────────────────────
|
||
vm1.qcow2
|
||
"파일"
|
||
|
||
Guest 관점
|
||
────────────────────
|
||
/dev/vda
|
||
"디스크"
|
||
```
|
||
|
||
둘 다 맞다.
|
||
|
||
---
|
||
|
||
## 143. qcow2 Virtual Size와 실제 Host 사용량
|
||
|
||
qcow2는 가상 disk size와 실제 Host 할당량이 다를 수 있다.
|
||
|
||
```text
|
||
Guest가 보는 공간
|
||
|
||
/dev/vda
|
||
┌──────────────────────────────────────┐
|
||
│ 100 GB │
|
||
└──────────────────────────────────────┘
|
||
|
||
Host 실제 할당 공간
|
||
|
||
vm1.qcow2
|
||
┌──────┐
|
||
│ 3GB │
|
||
└──────┘
|
||
```
|
||
|
||
Guest가 데이터를 기록하면서:
|
||
|
||
```text
|
||
처음
|
||
Virtual 100GB
|
||
Actual 1GB
|
||
|
||
↓ Guest 데이터 기록
|
||
|
||
Virtual 100GB
|
||
Actual 10GB
|
||
|
||
↓ 더 기록
|
||
|
||
Virtual 100GB
|
||
Actual 40GB
|
||
```
|
||
|
||
처럼 실제 사용량이 늘 수 있다.
|
||
|
||
확인:
|
||
|
||
```bash
|
||
qemu-img info vm1.qcow2
|
||
```
|
||
|
||
`virtual size`와 실제 allocation을 구분해서 봐야 한다.
|
||
|
||
---
|
||
|
||
## 144. RAW Image
|
||
|
||
RAW는 qcow2보다 구조가 단순하다.
|
||
|
||
```text
|
||
qcow2
|
||
|
||
Guest Block
|
||
↓
|
||
QEMU qcow2 mapping/metadata 처리
|
||
↓
|
||
qcow2 File I/O
|
||
|
||
RAW
|
||
|
||
Guest Block
|
||
↓
|
||
상대적으로 직접적인 offset 대응
|
||
↓
|
||
RAW File I/O
|
||
```
|
||
|
||
qcow2는 Copy-on-Write, sparse allocation, snapshot 등에 유리하지만 metadata/mapping 처리가 존재한다.
|
||
|
||
RAW는 상대적으로 단순하다.
|
||
|
||
다만:
|
||
|
||
```text
|
||
RAW = 무조건 빠름
|
||
qcow2 = 무조건 느림
|
||
```
|
||
|
||
으로 일반화하면 안 된다.
|
||
|
||
실제 성능은 cache mode, storage backend, workload pattern, queue depth, snapshot chain, underlying filesystem, physical device 등에 영향을 받는다.
|
||
|
||
---
|
||
|
||
## 145. Host Block Device를 직접 backend로 사용 가능
|
||
|
||
반드시 파일일 필요는 없다.
|
||
|
||
```text
|
||
Guest /dev/vda
|
||
↓
|
||
virtio-blk
|
||
↓
|
||
QEMU
|
||
↓
|
||
Host /dev/nvme0n1p3
|
||
```
|
||
|
||
따라서 `Guest에 /dev/vda가 있다`는 정보만으로 backend 구조를 알 수 없다.
|
||
|
||
```text
|
||
/dev/vda
|
||
↓
|
||
|
||
┌─────────────┬─────────────┬──────────────────┐
|
||
↓ ↓ ↓
|
||
qcow2 RAW Host Block Device
|
||
file file /dev/...
|
||
```
|
||
|
||
---
|
||
|
||
## 146. 실제 연결 확인
|
||
|
||
Guest:
|
||
|
||
```bash
|
||
lsblk
|
||
```
|
||
|
||
Host:
|
||
|
||
```bash
|
||
virsh domblklist <VM_NAME>
|
||
```
|
||
|
||
예시:
|
||
|
||
```text
|
||
Target Source
|
||
-----------------------------------------------
|
||
vda /var/lib/libvirt/images/vm1.qcow2
|
||
```
|
||
|
||
그러면:
|
||
|
||
```text
|
||
Guest Host
|
||
|
||
/dev/vda
|
||
│
|
||
│ virtio-blk
|
||
▼
|
||
QEMU
|
||
│
|
||
▼
|
||
/var/lib/libvirt/images/vm1.qcow2
|
||
```
|
||
|
||
관계가 확인된다.
|
||
|
||
---
|
||
|
||
## 147. VM에서는 Page Cache가 두 번 나타날 수 있다
|
||
|
||
Guest buffered I/O + Host file-backed disk + Host Page Cache를 함께 사용하면:
|
||
|
||
```text
|
||
Guest
|
||
|
||
PostgreSQL
|
||
↓
|
||
Guest ext4
|
||
↓
|
||
Guest Page Cache ← 첫 번째
|
||
↓
|
||
Guest Block Layer
|
||
↓
|
||
virtio-blk
|
||
↓
|
||
virtqueue
|
||
|
||
══════════ VM Boundary ══════════
|
||
|
||
Host
|
||
|
||
QEMU
|
||
↓
|
||
vm1.qcow2
|
||
↓
|
||
Host Page Cache ← 두 번째
|
||
↓
|
||
Host ext4/XFS
|
||
↓
|
||
Host Block Layer
|
||
↓
|
||
NVMe
|
||
```
|
||
|
||
같은 데이터가 Guest RAM과 Host RAM 양쪽에 cache될 수 있다.
|
||
|
||
---
|
||
|
||
## 148. `write()` 완료와 영속화는 다르다
|
||
|
||
```text
|
||
PostgreSQL
|
||
↓
|
||
Guest Page Cache ✓
|
||
↓
|
||
virtio ✓
|
||
↓
|
||
Host Page Cache ✓
|
||
|
||
───────── Host 전원 장애 ─────────
|
||
|
||
Physical SSD ✗
|
||
```
|
||
|
||
가능성이 있다.
|
||
|
||
따라서:
|
||
|
||
```text
|
||
write() 완료
|
||
≠
|
||
writeback 완료
|
||
≠
|
||
fsync/flush 완료
|
||
≠
|
||
전원 장애에도 안전한 durability
|
||
```
|
||
|
||
이다.
|
||
|
||
---
|
||
|
||
## 149. Direct I/O
|
||
|
||
Buffered I/O:
|
||
|
||
```text
|
||
QEMU
|
||
↓
|
||
Host Page Cache
|
||
↓
|
||
Host Filesystem
|
||
↓
|
||
Block Layer
|
||
↓
|
||
SSD
|
||
```
|
||
|
||
Direct I/O:
|
||
|
||
```text
|
||
QEMU
|
||
↓
|
||
Host Filesystem / Block I/O Path
|
||
↓
|
||
Block Layer
|
||
↓
|
||
SSD
|
||
```
|
||
|
||
Linux의 `O_DIRECT`가 대표적으로 관련된다.
|
||
|
||
중요한 구분:
|
||
|
||
```text
|
||
Direct I/O
|
||
≠
|
||
자동 durability 보장
|
||
```
|
||
|
||
Direct I/O의 핵심은 Page Cache 우회다.
|
||
|
||
---
|
||
|
||
## 150. `fsync()`가 필요한 이유
|
||
|
||
```c
|
||
write(fd, data, size);
|
||
```
|
||
|
||
성공만으로 정전 이후 생존을 보장하지 않는다.
|
||
|
||
필요한 시점에:
|
||
|
||
```c
|
||
fsync(fd);
|
||
```
|
||
|
||
를 통해 변경 내용을 필요한 영속성 경계까지 반영하도록 요청한다.
|
||
|
||
VM에서는:
|
||
|
||
```text
|
||
PostgreSQL
|
||
│
|
||
fsync()
|
||
▼
|
||
Guest Filesystem
|
||
│
|
||
▼
|
||
Guest Block Layer
|
||
│
|
||
FLUSH 등
|
||
▼
|
||
virtio-blk
|
||
│
|
||
▼
|
||
QEMU / Backend
|
||
│
|
||
▼
|
||
Host Storage Stack
|
||
│
|
||
▼
|
||
Physical Storage
|
||
```
|
||
|
||
처럼 전체 stack으로 의미가 전달되어야 한다.
|
||
|
||
---
|
||
|
||
## 151. FLUSH
|
||
|
||
단순화하면:
|
||
|
||
```text
|
||
WRITE
|
||
↓
|
||
"이 데이터를 써라"
|
||
|
||
FLUSH
|
||
↓
|
||
"앞서 쓴 데이터를 필요한 영속성 경계까지
|
||
반영하고 완료 상태를 보장해라"
|
||
```
|
||
|
||
이다.
|
||
|
||
실제 ordering/durability semantics는 더 복잡하지만 Storage 가상화에서는 이 구분이 핵심이다.
|
||
|
||
---
|
||
|
||
## 152. 가장 위험한 상황: 거짓 완료
|
||
|
||
Guest가:
|
||
|
||
```text
|
||
WRITE
|
||
↓
|
||
FLUSH
|
||
```
|
||
|
||
를 요청했는데 실제 상태가:
|
||
|
||
```text
|
||
Host RAM
|
||
┌──────────────┐
|
||
│ Data │
|
||
└──────────────┘
|
||
|
||
Physical Storage
|
||
┌──────────────┐
|
||
│ Old Data │
|
||
└──────────────┘
|
||
```
|
||
|
||
인데 Guest에게 `FLUSH 완료`라고 응답하면 문제가 된다.
|
||
|
||
PostgreSQL은 durability가 확보되었다고 판단할 수 있고, 직후 Host 전원이 나가면 RAM의 data가 사라진다.
|
||
|
||
이것은 성능 문제가 아니라 **durability contract가 깨지는 correctness 문제**다.
|
||
|
||
---
|
||
|
||
## 153. QEMU Cache Mode
|
||
|
||
QEMU/libvirt disk에서 대표적으로 볼 수 있는 설정:
|
||
|
||
```text
|
||
cache=none
|
||
cache=writeback
|
||
```
|
||
|
||
이름만 보고:
|
||
|
||
```text
|
||
none = cache 자체가 없음
|
||
writeback = 무조건 위험
|
||
```
|
||
|
||
이라고 해석하면 부정확하다.
|
||
|
||
핵심은 QEMU가 Host Page Cache와 write completion/flush semantics를 어떤 방식으로 사용할 것인가다.
|
||
|
||
---
|
||
|
||
## 154. `cache=none`
|
||
|
||
개념적으로 Host Page Cache를 우회하는 방향의 I/O 구성이다.
|
||
|
||
```text
|
||
Guest Page Cache
|
||
↓
|
||
virtio
|
||
↓
|
||
QEMU
|
||
↓
|
||
Direct I/O 계열
|
||
↓
|
||
Host Filesystem / Block Path
|
||
↓
|
||
Storage
|
||
```
|
||
|
||
이중 caching을 줄일 수 있다.
|
||
|
||
하지만:
|
||
|
||
```text
|
||
Host Page Cache 우회
|
||
≠
|
||
무조건 즉시 durable media 반영
|
||
```
|
||
|
||
이다.
|
||
|
||
---
|
||
|
||
## 155. `cache=writeback`
|
||
|
||
Host Page Cache를 사용할 수 있는 구성이다.
|
||
|
||
```text
|
||
Guest
|
||
↓
|
||
virtio
|
||
↓
|
||
QEMU
|
||
↓
|
||
Host Page Cache
|
||
↓
|
||
writeback
|
||
↓
|
||
Physical Storage
|
||
```
|
||
|
||
일반 write는 Host RAM에서 빠르게 completion될 수 있다.
|
||
|
||
```text
|
||
QEMU
|
||
↓
|
||
Host RAM에 기록
|
||
↓
|
||
WRITE completion
|
||
|
||
...
|
||
|
||
나중에
|
||
|
||
Host RAM
|
||
↓
|
||
Storage
|
||
```
|
||
|
||
하지만 `cache=writeback` 자체가 Guest의 `fsync()`/FLUSH를 무시한다는 뜻은 아니다.
|
||
|
||
정상적인 stack이라면:
|
||
|
||
```text
|
||
Guest fsync / FLUSH
|
||
↓
|
||
virtio FLUSH
|
||
↓
|
||
QEMU/backend
|
||
↓
|
||
Host sync/flush
|
||
↓
|
||
Storage
|
||
↓
|
||
필요한 완료 확인
|
||
↓
|
||
Guest completion
|
||
```
|
||
|
||
으로 durability 요구가 전달되어야 한다.
|
||
|
||
---
|
||
|
||
## 156. `writeback = 위험`이라고 단정하면 안 되는 이유
|
||
|
||
정확한 표현:
|
||
|
||
> writeback caching에서는 volatile cache가 존재할 수 있으므로, Guest의 flush/fsync semantics가 전체 backend/storage stack에서 올바르게 보존되는지가 중요하다.
|
||
|
||
```text
|
||
Guest가 요구한 durability
|
||
│
|
||
▼
|
||
Guest Filesystem
|
||
│
|
||
▼
|
||
Guest Block Layer
|
||
│
|
||
▼
|
||
virtio
|
||
│
|
||
▼
|
||
QEMU/backend
|
||
│
|
||
▼
|
||
Host Storage
|
||
│
|
||
▼
|
||
Device
|
||
```
|
||
|
||
전체 chain에서 의미가 깨지지 않아야 한다.
|
||
|
||
---
|
||
|
||
## 157. Device-side Cache
|
||
|
||
Host Page Cache를 우회했다고 끝이 아니다.
|
||
|
||
```text
|
||
QEMU
|
||
↓
|
||
Direct I/O
|
||
↓
|
||
Host Block Layer
|
||
↓
|
||
NVMe Driver
|
||
↓
|
||
NVMe Controller
|
||
↓
|
||
Device-side Cache
|
||
↓
|
||
Flash
|
||
```
|
||
|
||
Storage controller/device가 volatile write cache를 가질 수 있다.
|
||
|
||
따라서:
|
||
|
||
```text
|
||
RAM에서 나갔다
|
||
≠
|
||
Device에 command가 전달됐다
|
||
≠
|
||
전원이 끊겨도 살아남는 상태가 됐다
|
||
```
|
||
|
||
이다.
|
||
|
||
실제 운영에서는 device flush/FUA semantics와 power-loss protection 여부도 중요할 수 있다.
|
||
|
||
---
|
||
|
||
## 158. Host Block Layer
|
||
|
||
qcow2/RAW file I/O는 Host Filesystem을 거쳐 실제 Host block I/O가 된다.
|
||
|
||
```text
|
||
QEMU
|
||
↓
|
||
vm1.qcow2
|
||
↓
|
||
Host ext4/XFS
|
||
↓
|
||
Host Block Layer
|
||
↓
|
||
/dev/nvme0n1
|
||
```
|
||
|
||
Host Block Layer는 해당 I/O가 VM PostgreSQL에서 시작했는지 Host process에서 시작했는지를 본질적으로 구분해서 처리하는 계층이 아니다. 모두 Host block request다.
|
||
|
||
---
|
||
|
||
## 159. 여러 VM이 하나의 NVMe를 공유하면
|
||
|
||
```text
|
||
VM1 QEMU ──┐
|
||
│
|
||
VM2 QEMU ──┼──→ Host Block Layer → NVMe
|
||
│
|
||
Nginx ─────┤
|
||
│
|
||
Host 기타 ─┘
|
||
```
|
||
|
||
여러 source에서 동시에 I/O가 들어올 수 있다.
|
||
|
||
```text
|
||
VM1
|
||
WRITE X
|
||
READ Y
|
||
WRITE Z
|
||
|
||
VM2
|
||
READ A
|
||
WRITE B
|
||
|
||
Host Process
|
||
READ C
|
||
```
|
||
|
||
이 요청들은 Host Block Layer queue에서 관리되고 device로 dispatch된다.
|
||
|
||
---
|
||
|
||
## 160. blk-mq: Multi-Queue Block Layer
|
||
|
||
현대 Linux에서는 `blk-mq`가 중요하다.
|
||
|
||
```text
|
||
CPU0 ──→ Queue 0 ──┐
|
||
CPU1 ──→ Queue 1 ──┤
|
||
CPU2 ──→ Queue 2 ──┼──→ NVMe
|
||
CPU3 ──→ Queue 3 ──┘
|
||
```
|
||
|
||
NVMe는 높은 병렬성과 queue depth를 지원하기 때문에 여러 CPU가 병렬로 block I/O를 처리할 수 있는 구조가 중요하다.
|
||
|
||
Storage 처리 역시 CPU scheduling과 완전히 독립된 세계는 아니다.
|
||
|
||
---
|
||
|
||
## 161. I/O Scheduler
|
||
|
||
여러 I/O request가 있다고 해서 항상 들어온 순서 그대로 device에 전달되는 것은 아니다.
|
||
|
||
```text
|
||
READ A
|
||
WRITE B
|
||
READ C
|
||
WRITE D
|
||
READ E
|
||
↓
|
||
|
||
┌─────────────────────┐
|
||
│ I/O Scheduler │
|
||
│ 요청 dispatch 정책 │
|
||
└──────────┬──────────┘
|
||
↓
|
||
Device Driver
|
||
```
|
||
|
||
대표적으로 볼 수 있는 scheduler:
|
||
|
||
```text
|
||
none
|
||
mq-deadline
|
||
bfq
|
||
```
|
||
|
||
scheduler마다 목적과 정책이 다르다.
|
||
|
||
---
|
||
|
||
## 162. `none`
|
||
|
||
`none`은 복잡한 scheduling 정책을 최소화해서 비교적 직접 device 쪽으로 dispatch하는 방향이다.
|
||
|
||
NVMe처럼 device 자체가 강한 병렬성과 queueing 기능을 가진 경우 이러한 단순한 정책이 적합할 수 있다.
|
||
|
||
단:
|
||
|
||
```text
|
||
none = block layer가 아무 일도 하지 않음
|
||
```
|
||
|
||
은 아니다.
|
||
|
||
---
|
||
|
||
## 163. 실제 I/O Scheduler 확인
|
||
|
||
Host:
|
||
|
||
```bash
|
||
cat /sys/block/nvme0n1/queue/scheduler
|
||
```
|
||
|
||
예시:
|
||
|
||
```text
|
||
[none] mq-deadline
|
||
```
|
||
|
||
대괄호 안이 현재 선택된 scheduler다.
|
||
|
||
SATA/SCSI device라면:
|
||
|
||
```bash
|
||
cat /sys/block/sda/queue/scheduler
|
||
```
|
||
|
||
처럼 확인한다.
|
||
|
||
---
|
||
|
||
## 164. NVMe Driver와 Physical Device
|
||
|
||
```text
|
||
Host Block Layer
|
||
↓
|
||
I/O Scheduler
|
||
↓
|
||
NVMe Driver
|
||
↓
|
||
NVMe Controller
|
||
↓
|
||
Physical Storage
|
||
```
|
||
|
||
`NVMe Driver`는 Host Linux Kernel의 device driver다.
|
||
|
||
Network에서 physical NIC driver가 하드웨어를 제어하는 것과 동일한 계층적 위치다.
|
||
|
||
---
|
||
|
||
## 165. NVMe와 SSD 구분
|
||
|
||
SSD는 저장장치의 넓은 종류이고, NVMe는 PCIe 기반 non-volatile storage를 위한 protocol/interface다.
|
||
|
||
```text
|
||
SSD
|
||
├─ SATA SSD
|
||
│ └─ SATA/AHCI
|
||
│
|
||
└─ NVMe SSD
|
||
└─ PCIe + NVMe
|
||
```
|
||
|
||
NVMe SSD:
|
||
|
||
```text
|
||
Linux NVMe Driver
|
||
↓
|
||
PCIe
|
||
↓
|
||
NVMe Controller
|
||
↓
|
||
Flash
|
||
```
|
||
|
||
---
|
||
|
||
## 166. Storage I/O Completion
|
||
|
||
WRITE 요청은 아래로 내려가고, 완료는 반대 방향으로 올라온다.
|
||
|
||
Request:
|
||
|
||
```text
|
||
Guest
|
||
│
|
||
│ WRITE
|
||
▼
|
||
virtio-blk
|
||
↓
|
||
virtqueue
|
||
↓
|
||
QEMU/backend
|
||
↓
|
||
Host Block Layer
|
||
↓
|
||
NVMe Driver
|
||
↓
|
||
NVMe
|
||
```
|
||
|
||
Completion:
|
||
|
||
```text
|
||
NVMe
|
||
│
|
||
│ completion
|
||
▼
|
||
NVMe Driver
|
||
↓
|
||
Host Block Layer
|
||
↓
|
||
QEMU/backend
|
||
↓
|
||
virtqueue completion
|
||
↓
|
||
virtio-blk
|
||
↓
|
||
Guest Block Layer
|
||
```
|
||
|
||
따라서 virtqueue는 request뿐 아니라 completion 전달 구조까지 포함해서 이해해야 한다.
|
||
|
||
---
|
||
|
||
## 167. Storage Contention
|
||
|
||
여러 VM이 동일한 Physical NVMe를 사용하면 storage resource 경쟁이 발생할 수 있다.
|
||
|
||
```text
|
||
VM1 PostgreSQL
|
||
│
|
||
├────────┐
|
||
│ │
|
||
VM2 Keycloak │
|
||
│ │
|
||
├────────┤
|
||
│ ▼
|
||
│ Host Block Layer
|
||
│ ↓
|
||
│ I/O Queue
|
||
│ ↓
|
||
└──────→ NVMe
|
||
```
|
||
|
||
VM1에서 대량 I/O가 발생하면 VM2의 storage latency가 증가할 수 있다.
|
||
|
||
```text
|
||
CPU Contention
|
||
→ Host logical CPU 실행 시간 경쟁
|
||
|
||
Storage Contention
|
||
→ IOPS / bandwidth / queue / device 처리시간 경쟁
|
||
```
|
||
|
||
둘은 다른 자원 경쟁이다.
|
||
|
||
---
|
||
|
||
## 168. CPU가 정상이어도 Storage 때문에 느릴 수 있다
|
||
|
||
```text
|
||
HTTP Request
|
||
↓
|
||
Keycloak
|
||
↓
|
||
PostgreSQL
|
||
↓
|
||
fsync()
|
||
↓
|
||
Storage
|
||
```
|
||
|
||
PostgreSQL이 storage completion을 기다리고 있으면 CPU usage가 높지 않을 수도 있다.
|
||
|
||
```text
|
||
CPU 30%
|
||
|
||
그런데
|
||
|
||
Request latency 2초
|
||
```
|
||
|
||
가 가능하다.
|
||
|
||
따라서 CPU 지표만으로 latency 원인을 판단하면 안 된다.
|
||
|
||
---
|
||
|
||
## 169. Storage 관측 명령어
|
||
|
||
대표적인 device I/O 관측:
|
||
|
||
```bash
|
||
iostat -xz 1
|
||
```
|
||
|
||
확인 대상:
|
||
|
||
- read/write throughput
|
||
- IOPS
|
||
- request latency
|
||
- queue 상태
|
||
- device utilization 성격의 지표
|
||
|
||
어떤 process가 I/O를 발생시키는지 볼 때:
|
||
|
||
```bash
|
||
iotop
|
||
```
|
||
|
||
Guest:
|
||
|
||
```bash
|
||
lsblk
|
||
mount
|
||
df -h
|
||
cat /proc/mounts
|
||
iostat -xz 1
|
||
```
|
||
|
||
Host:
|
||
|
||
```bash
|
||
virsh domblklist <VM_NAME>
|
||
qemu-img info <disk-image>
|
||
lsblk
|
||
cat /sys/block/<device>/queue/scheduler
|
||
iostat -xz 1
|
||
iotop
|
||
```
|
||
|
||
---
|
||
|
||
## 170. PostgreSQL 예시: WAL과 Durability
|
||
|
||
예를 들어:
|
||
|
||
```sql
|
||
BEGIN;
|
||
|
||
UPDATE users
|
||
SET balance = 1000
|
||
WHERE id = 1;
|
||
|
||
COMMIT;
|
||
```
|
||
|
||
을 생각한다.
|
||
|
||
PostgreSQL은 WAL 등의 durability protocol을 사용하며 필요한 시점에 storage synchronization을 수행한다.
|
||
|
||
```text
|
||
PostgreSQL
|
||
│
|
||
│ WAL write
|
||
▼
|
||
Guest Page Cache
|
||
│
|
||
│ fsync 등
|
||
▼
|
||
Guest Filesystem
|
||
↓
|
||
Guest Block Layer
|
||
↓
|
||
virtio-blk
|
||
↓
|
||
QEMU
|
||
↓
|
||
Host Storage
|
||
↓
|
||
Physical Storage
|
||
│
|
||
│ completion
|
||
▼
|
||
PostgreSQL
|
||
|
||
"필요한 durability 조건 충족"
|
||
↓
|
||
COMMIT 성공 처리
|
||
```
|
||
|
||
VM storage layer가 flush/fsync semantics를 제대로 보존하지 않으면 PostgreSQL의 durability assumption과 실제 storage behavior가 어긋날 수 있다.
|
||
|
||
---
|
||
|
||
## 171. 성능과 Durability의 Trade-off
|
||
|
||
모든 write에서 storage synchronization을 기다리면 latency가 커질 수 있다.
|
||
|
||
```text
|
||
WRITE
|
||
↓
|
||
Storage까지 동기화
|
||
↓
|
||
completion 대기
|
||
```
|
||
|
||
특히 DB workload에서는 `fsync()` latency가 transaction latency와 연결될 수 있다.
|
||
|
||
```text
|
||
더 적극적인 caching
|
||
↓
|
||
write latency 개선 가능
|
||
|
||
하지만
|
||
|
||
durability semantics를 반드시 보존해야 함
|
||
```
|
||
|
||
`fsync()`를 없애서 빨라졌다면 그것이 최적화가 아니라 durability contract를 제거한 것일 수 있다.
|
||
|
||
---
|
||
|
||
## 172. Storage Virtualization Canonical Flow
|
||
|
||
```text
|
||
[Guest Userspace]
|
||
|
||
PostgreSQL / Keycloak
|
||
│
|
||
read()/write()
|
||
fsync()
|
||
▼
|
||
|
||
[Guest Kernel]
|
||
|
||
VFS
|
||
↓
|
||
ext4 / XFS
|
||
↓
|
||
Guest Page Cache
|
||
│
|
||
writeback
|
||
↓
|
||
Guest Block Layer
|
||
↓
|
||
/dev/vda
|
||
↓
|
||
virtio-blk Frontend
|
||
↓
|
||
virtqueue
|
||
|
||
════════════════════ VM Boundary ════════════════════
|
||
|
||
[Host Userspace]
|
||
|
||
QEMU
|
||
↓
|
||
QEMU Block Backend
|
||
↓
|
||
|
||
qcow2 / RAW / Host Block Device
|
||
↓
|
||
|
||
[Host Kernel]
|
||
|
||
Host Page Cache
|
||
(설정에 따라 우회 가능)
|
||
↓
|
||
Host Filesystem
|
||
↓
|
||
Host Block Layer
|
||
↓
|
||
blk-mq
|
||
↓
|
||
I/O Scheduler
|
||
↓
|
||
NVMe Driver
|
||
|
||
[Hardware]
|
||
|
||
NVMe Controller
|
||
↓
|
||
Device-side Cache
|
||
↓
|
||
Non-volatile Media
|
||
```
|
||
|
||
Completion:
|
||
|
||
```text
|
||
Physical Storage
|
||
↑
|
||
completion
|
||
↑
|
||
NVMe Driver
|
||
↑
|
||
Host Block Layer
|
||
↑
|
||
QEMU/backend
|
||
↑
|
||
virtqueue
|
||
↑
|
||
virtio-blk
|
||
↑
|
||
Guest Block Layer
|
||
↑
|
||
Filesystem
|
||
↑
|
||
Application
|
||
```
|
||
|
||
---
|
||
|
||
## 173. Network Virtualization과 비교
|
||
|
||
| Network | Storage |
|
||
|---|---|
|
||
| `virtio-net` | `virtio-blk` |
|
||
| packet | block I/O request |
|
||
| TX/RX virtqueue | I/O virtqueue |
|
||
| TAP / network backend | QEMU block backend |
|
||
| Linux Bridge/Route | Host filesystem/block stack |
|
||
| Physical NIC | Physical SSD/NVMe |
|
||
| Guest TCP/IP Stack | Guest VFS/Filesystem/Block Layer |
|
||
| send/recv | read/write/fsync |
|
||
|
||
이 표는 학습용 대응 관계이며 각 요소가 1:1로 같은 종류라는 뜻은 아니다.
|
||
|
||
---
|
||
|
||
## 174. 핵심 Claim
|
||
|
||
### Claim 1
|
||
Guest의 `/dev/vda`는 Guest가 보는 virtual block device다. 실제 Host backend는 qcow2, RAW, Host block device 등이 될 수 있다.
|
||
|
||
### Claim 2
|
||
`virtio-blk + virtqueue`가 Guest block I/O를 Host backend와 연결한다.
|
||
|
||
### Claim 3
|
||
qcow2가 Host filesystem 위의 파일이면 Guest filesystem 아래에 Host filesystem/storage stack이 한 번 더 존재한다.
|
||
|
||
### Claim 4
|
||
Guest와 Host 양쪽에 Page Cache가 존재할 수 있다. Direct I/O와 QEMU cache mode는 Host Page Cache 사용 방식과 연결된다.
|
||
|
||
### Claim 5
|
||
`write()` 완료와 durability는 같은 의미가 아니다.
|
||
|
||
```text
|
||
write()
|
||
≠
|
||
writeback
|
||
≠
|
||
fsync/flush 완료
|
||
≠
|
||
전원 장애에도 안전한 상태
|
||
```
|
||
|
||
### Claim 6
|
||
Storage 성능은 Guest 내부만으로 결정되지 않는다. QEMU/backend, Host block queue, I/O scheduler, NVMe, cache, 다른 VM의 storage load가 함께 영향을 준다.
|
||
|
||
---
|
||
|
||
## 175. 실제 테스트 서버에서 확인할 Open Questions
|
||
|
||
### OQ-1. VM의 `/dev/vda`는 어떤 Host backend에 연결되어 있는가?
|
||
|
||
Guest:
|
||
|
||
```bash
|
||
lsblk
|
||
```
|
||
|
||
Host:
|
||
|
||
```bash
|
||
virsh domblklist <VM_NAME>
|
||
```
|
||
|
||
### OQ-2. Backend는 qcow2인가 RAW인가?
|
||
|
||
```bash
|
||
qemu-img info /path/to/disk-image
|
||
```
|
||
|
||
### OQ-3. qcow2 Virtual Size와 실제 Host 사용량은 얼마나 다른가?
|
||
|
||
```bash
|
||
qemu-img info <image>
|
||
du -h <image>
|
||
ls -lh <image>
|
||
```
|
||
|
||
세 명령이 보여주는 의미가 서로 다를 수 있으므로 비교한다.
|
||
|
||
### OQ-4. QEMU disk cache mode는 무엇인가?
|
||
|
||
```bash
|
||
virsh dumpxml <VM_NAME>
|
||
```
|
||
|
||
disk driver 설정의 cache 관련 값을 확인한다.
|
||
|
||
### OQ-5. qcow2가 최종적으로 어느 Host block device 위에 있는가?
|
||
|
||
```bash
|
||
lsblk
|
||
findmnt
|
||
```
|
||
|
||
### OQ-6. Host I/O Scheduler는 무엇인가?
|
||
|
||
```bash
|
||
cat /sys/block/<device>/queue/scheduler
|
||
```
|
||
|
||
### OQ-7. VM1 Storage load가 VM2 latency에 영향을 주는가?
|
||
|
||
VM1에서 별도의 테스트 파일/디스크로 controlled I/O load를 발생시키고 VM2의 application latency와 Host storage 지표를 동시에 본다.
|
||
|
||
### OQ-8. Guest `fsync()` latency와 Host storage latency가 같이 증가하는가?
|
||
|
||
Guest application/DB latency와 Host `iostat`를 시간축으로 함께 관찰한다.
|
||
|
||
---
|
||
|
||
## 176. 권장 실습 흐름
|
||
|
||
```text
|
||
1. Guest에서 /dev/vda 확인
|
||
↓
|
||
2. Host에서 virsh domblklist로 backend 확인
|
||
↓
|
||
3. qemu-img info로 qcow2/RAW 확인
|
||
↓
|
||
4. Host filesystem → 실제 block device 추적
|
||
↓
|
||
5. I/O Scheduler 확인
|
||
↓
|
||
6. Guest/Host iostat 동시 관찰
|
||
↓
|
||
7. VM1 부하가 VM2 storage latency에 미치는 영향 확인
|
||
↓
|
||
8. DB fsync latency와 Host storage latency 상관관계 확인
|
||
```
|
||
|
||
---
|
||
|
||
## 177. 최종 요약
|
||
|
||
Storage 가상화에서 Guest application은 실제 SSD를 직접 다루지 않는다.
|
||
|
||
```text
|
||
Application
|
||
↓
|
||
Guest VFS
|
||
↓
|
||
Guest Filesystem
|
||
↓
|
||
Guest Page Cache
|
||
↓
|
||
Guest Block Layer
|
||
↓
|
||
virtio-blk
|
||
↓
|
||
virtqueue
|
||
```
|
||
|
||
VM 경계를 넘으면:
|
||
|
||
```text
|
||
QEMU
|
||
↓
|
||
qcow2 / RAW / Host Block Device
|
||
↓
|
||
Host Storage Stack
|
||
↓
|
||
Physical SSD/NVMe
|
||
```
|
||
|
||
로 이어진다.
|
||
|
||
이 경로에는 여러 cache, queue, scheduling 지점이 존재한다.
|
||
|
||
특히 DB workload에서는 다음을 항상 구분해야 한다.
|
||
|
||
```text
|
||
write 완료
|
||
≠
|
||
writeback 완료
|
||
≠
|
||
flush 완료
|
||
≠
|
||
전원 장애에도 살아남는 durability
|
||
```
|
||
|
||
Storage 문제를 분석할 때 CPU usage만 보지 말고 다음을 함께 본다.
|
||
|
||
```text
|
||
Guest I/O latency
|
||
Host I/O queue
|
||
Host storage latency
|
||
QEMU backend
|
||
cache mode
|
||
I/O Scheduler
|
||
NVMe
|
||
다른 VM의 Storage load
|
||
```
|
||
|
||
이것이 QEMU/KVM 기반 Storage Virtualization을 이해하기 위한 핵심 SSOT다.
|