Files

149 KiB
Raw Permalink Blame History

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부가 160, 제3부가 138, 제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 실행 경로를 크게 보면 다음과 같다.

사용자
  |
  | 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다.

예:

virsh start ubuntu-vm
virsh list
virsh shutdown ubuntu-vm

virsh start를 실행했다고 해서 virsh 프로세스가 VM을 계속 실행하는 것은 아니다.

개념적인 흐름은 다음과 같다.

virsh start ubuntu-vm
        |
        v
     libvirt
        |
        v
 QEMU Process 실행

명령 전달이 끝나면 virsh 자체는 종료될 수 있고, VM을 실제로 실행하는 QEMU 프로세스는 계속 살아 있다.

3.2 libvirt

libvirt는 VM lifecycle과 구성을 관리하는 계층이다.

예를 들어 VM 정의에 다음과 같은 정보가 있다.

RAM: 8 GiB
vCPU: 4
Disk: ...
Network: ...

libvirt는 이 정의를 바탕으로 QEMU를 적절한 옵션과 함께 실행하고 관리한다.

3.3 QEMU

QEMU는 Host userspace에서 실행되는 실제 프로세스다.

4 vCPU VM이라면 개념적으로 다음과 같은 구조가 만들어진다.

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에 접근한다.

QEMU
 |
 | open("/dev/kvm")
 | ioctl(...)
 v
/dev/kvm
 |
 v
KVM

대표적인 KVM API에는 다음과 같은 동작이 있다.

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 제조사에 독립적인 공통 부분과 제조사별 구현을 분리해서 볼 수 있다.

              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 커널 모듈이 아니다.

VMX = Intel CPU의 하드웨어 가상화 기능

VMX에서는 크게 다음 실행 영역을 구분한다.

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를 설정했다고 가정한다.

VM
 |
 +-- vCPU 0
 +-- vCPU 1
 +-- vCPU 2
 +-- vCPU 3

Guest OS는 이를 자신의 CPU처럼 인식한다.

Host에서는 각 vCPU의 실행 주체에 대응하는 QEMU vCPU thread가 존재한다.

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가 스케줄링 대상으로 보인다.

CPU0 CPU1 CPU2 CPU3 ... CPU11

QEMU vCPU thread도 다른 Host thread와 마찬가지로 Linux Scheduler가 실행할 logical CPU를 결정한다.

Chrome Thread ----+
Java Thread ------+--> Linux Scheduler --> CPU0 ... CPU11
QEMU vCPU Thread -+

따라서 시간에 따라 같은 vCPU thread가 서로 다른 logical CPU에서 실행될 수도 있다.

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을 요청한다.

개념적으로 다음과 같다.

ioctl(vcpu_fd, KVM_RUN, 0);

실행 흐름은 다음과 같다.

QEMU vCPU Thread
      |
      | KVM_RUN
      v
     KVM
      |
      | VM Entry
      v
Physical CPU
      |
      +--> Guest Code
      +--> Guest Code
      +--> Guest Code
      +--> ...

이 상태에서 Guest의 일반적인 명령어는 실제 CPU에서 직접 실행된다.

예:

ADD
MOV
SUB
CMP
JMP

일반 명령마다 QEMU까지 돌아갔다가 다시 실행하는 구조가 아니다.


7. VM Entry와 VM Exit

7.1 VM Entry

KVM이 CPU에게 Guest 실행을 시작하거나 재개하도록 하는 전환이다.

KVM
 |
 | VM Entry
 v
Guest 실행

7.2 VM Exit

VM Exit은 VM 종료가 아니다.

다음과 같은 의미다.

CPU가 VMX Non-Root에서 Guest를 실행하다가 Hypervisor가 개입해야 하는 조건을 만나 Guest 실행에서 빠져나와 VMX Root/KVM 쪽으로 제어권을 넘기는 것.

따라서 다음과는 다르다.

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을 사용한다면 다음과 같은 흐름이 가능하다.

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가 이를 가로채도록 설정할 수 있다.

예:

out 0x3f8, al

개념적으로:

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 정보를 가상화할 수 있다.

Guest
 |
 | CPUID
 v
VM Exit
 |
 v
KVM
 |
 | 가상 CPU 정보 처리
 v
VM Entry

8.4 Control Register 접근

Guest kernel도 CR0, CR3, CR4 등의 control register를 사용한다.

예를 들어 CR3는 페이지 테이블과 관련된 CPU 상태에 사용된다.

mov cr3, rax

하지만 모든 CR 접근이 항상 VM Exit을 발생시키는 것은 아니다.

VMX control을 통해 어떤 접근을 가로챌지 결정할 수 있으며, 현대 가상화에서는 성능을 위해 불필요한 Exit을 줄이는 것이 중요하다.

8.5 MSR 접근

CPU에는 MSR(Model-Specific Register)이 있으며 다음 명령으로 접근할 수 있다.

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을 확인한다.

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 상태에 들어갈 수 있다.

개념적인 흐름:

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를 다시 실행해야 할 이유가 생기면:

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가 있다고 하자.

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 시간을 두고 경쟁한다.

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은 다음 상황을 나타내는 중요한 단서다.

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:

grep -E 'vmx|svm' /proc/cpuinfo

Intel에서는 vmx, AMD에서는 svm flag를 확인할 수 있다.

14.2 KVM 모듈 확인

lsmod | grep kvm

Intel 환경에서는 일반적으로 다음 모듈을 확인할 수 있다.

kvm_intel
kvm

14.3 /dev/kvm 확인

ls -l /dev/kvm

QEMU가 KVM API에 접근하는 character device가 존재하는지 확인한다.

14.4 실행 중인 VM 확인

virsh list

14.5 QEMU 프로세스 확인

ps -ef | grep '[q]emu'

virsh가 아니라 QEMU 프로세스가 실제 VM lifecycle 동안 살아 있는 것을 확인할 수 있다.

14.6 QEMU thread 확인

ps -T -p <QEMU_PID>

또는:

top -H -p <QEMU_PID>

환경/QEMU 버전에 따라 이름은 다를 수 있지만 vCPU 관련 thread를 Host에서 관찰할 수 있다.

14.7 thread가 실행되는 Host CPU 확인

ps -eLo pid,tid,psr,pcpu,comm | grep qemu

PSR을 통해 thread가 최근 실행된 logical CPU를 관찰할 수 있다.

이는 vCPU가 물리 CPU에 영구 고정되어 있다는 의미가 아니며, pinning을 하지 않았다면 스케줄링에 따라 달라질 수 있다.

14.8 Guest의 steal time 확인

Guest 내부:

top

또는 CPU 통계를 제공하는 다른 Linux 도구에서 steal time을 확인한다.

14.9 KVM Exit 관찰

환경이 지원하면 perf kvm을 이용해 KVM 관련 runtime 통계를 확인할 수 있다.

예:

sudo perf kvm stat live

지원되는 명령과 표시되는 Exit reason은 kernel, perf 버전, CPU architecture 및 설정에 따라 다를 수 있으므로 실제 환경에서는 다음을 함께 확인한다.

perf kvm --help

필요하면 KVM tracepoint를 이용한 별도 tracing도 검토한다.


15. CPU 가상화 관점에서 장애를 보는 방법

VM 안의 application이 느릴 때 바로 application 문제라고 결론 내리지 않는다.

CPU 실행 경로를 기준으로 다음 계층을 분리한다.

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 경쟁의 원인은 아니다.

현재 원래 검증하려는 구조는 다음과 같다.

Client
   |
   v
Nginx / Load Balancer
   |
   v
K3s
   |
   +--> Keycloak Node 1
   |
   +--> Keycloak Node 2
              |
              v
      Session / Token State
              |
       +------+------+
       |             |
   PostgreSQL      Redis

테스트 환경에서는 이 구조 아래에 KVM 계층이 추가된다.

Physical Host
 |
 +-- Host Nginx
 |
 +-- VM 1
 |    |
 |    +-- K3s Node / Keycloak
 |
 +-- VM 2
      |
      +-- K3s Node / Keycloak

따라서 테스트 결과를 해석할 때 다음 원인을 분리해야 한다.

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%까지 밀 필요는 없다.

예:

Same User
Same Session
Same Refresh Token
       |
       +--> Request A --> Node 1
       |
       +--> Request B --> Node 2
                거의 동시에

핵심은 높은 전체 트래픽이 아니라 동일 상태에 대한 동시 접근이다.

사용자 한 명이라도 race condition은 발생할 수 있다.

사용자와 트래픽이 많아지면 이런 경쟁이 실제 운영에서 발생할 확률이 높아질 뿐이다.

17.2 Load / Stress Test

별도로 전체 부하를 증가시키면서 시스템의 자원 한계를 확인한다.

예:

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 경로는 다음과 같다.

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를 구성하면 다음 계층이 추가된다.

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 상태를 비교한다.

확인:

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 위에 있는지 확인한다.

구조에 따라 진단 지표가 달라진다.

Bare metal:
K3s -> Host Scheduler -> Physical CPU

VM:
K3s -> Guest -> vCPU -> Hypervisor -> Physical CPU

21. OPEN QUESTION에서 CASE가 만들어지는 흐름

현재 문서 체계에서는 다음 관계를 사용한다.

SSOT
 |
 v
CONCEPT
 |
 | 이해하면서 검증이 필요한 질문 발생
 v
OPEN QUESTION
 |
 | 실제 구성 / 명령 / 부하 / 관찰
 v
CASE
 |
 | 결과에서 새로운 의문 발견
 +------------------> OPEN QUESTION

즉 OPEN QUESTION은 CASE에서만 나오는 것이 아니다.

CONCEPT -> OPEN QUESTION
CASE    -> OPEN QUESTION

둘 다 가능하다.

그리고 OPEN QUESTION을 실제 실험으로 해소하는 과정에서 새로운 CASE가 만들어질 수 있다.

예:

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에서 명령을 실행해 다음을 검증한다.

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 안의 애플리케이션이 느려졌을 때 어느 계층에서 문제가 발생했는지 구분하는 것이다.

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를 모두 사용하고 있는 경우다.

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에 할당하는 구성이다.

예:

Host: 12 logical CPUs

VM1: 8 vCPU
VM2: 8 vCPU
VM3: 8 vCPU

Total: 24 vCPU

Overcommit 자체가 바로 장애라는 의미는 아니다. VM들이 대부분 idle이라면 문제가 없을 수 있다.

문제는 여러 VM의 vCPU가 동시에 runnable 상태가 될 때 나타난다.

많은 runnable vCPU threads
          |
          v
Host Scheduler
          |
          v
제한된 logical CPUs

이때 CPU contention과 scheduling latency가 증가할 수 있다.

24.3 CPU Contention

여러 runnable thread가 같은 Host CPU 자원을 두고 경쟁하는 상태다.

경쟁 대상은 QEMU vCPU thread만이 아니다.

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으로 관찰할 수 있다.

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에 배치할 때까지 기다릴 수 있다.

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가 집중될 수 있다.

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이 제한될 수 있다.

Physical CPU
    |
Host / Hypervisor
    |
Guest Linux
    |
K3s
    |
cgroup CPU limit
    |
Keycloak Pod

이 경우 Host CPU에 여유가 있어도 Keycloak Pod는 설정된 CPU quota 때문에 실행이 제한될 수 있다.

따라서 다음 두 문제를 구분해야 한다.

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이 지나치게 빈번하고 그 처리 비용이 커진다면 성능에 영향을 줄 수 있다.

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가 함께 동작한다면 다음과 같은 경쟁이 가능하다.

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의 관계가 성능에 영향을 줄 수 있다.

개념적으로:

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 또는 저장소 경쟁 문제라고 판단하지 않는다.

최소한 다음 경계를 분리한다.

[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 자원 포화를 관찰한다.

이렇게 해야 다음 두 결과를 분리할 수 있다.

"동일 상태에 동시에 접근해서 발생한 문제"

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에서는 다음 수준까지만 확정한다.

구조적으로 어떤 문제가 발생할 수 있는가?
어떤 지표로 그 문제를 의심할 수 있는가?
어느 계층에서 확인해야 하는가?

현재 테스트 서버에서 실제로 발생하는지는 CONCEPT에서 사실로 확정하지 않는다.

예:

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을 생성한다.

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의 메모리 접근을 가장 단순하게 표현하면 다음과 같다.

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이 어떤 변수를 읽는다고 하자.

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 안에 다음 프로세스가 있다고 하자.

Guest VM

├─ Keycloak
├─ PostgreSQL
├─ nginx
└─ systemd

각 프로세스에는 독립적인 Virtual Address Space가 있다.

Keycloak Process

Virtual Address Space
┌─────────────────────────┐
│ 0x1000                  │
│ 0x2000                  │
│ 0x3000                  │
│ ...                     │
└─────────────────────────┘


PostgreSQL Process

Virtual Address Space
┌─────────────────────────┐
│ 0x1000                  │
│ 0x2000                  │
│ 0x3000                  │
│ ...                     │
└─────────────────────────┘

두 프로세스가 모두 0x1000이라는 주소를 사용할 수 있다. 같은 Virtual Address라도 서로 다른 physical frame으로 매핑할 수 있기 때문이다.

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다.

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 단위로 생각할 수 있다.

Guest Physical Memory

┌───────────────┐
│ Frame 0       │
├───────────────┤
│ Frame 1       │
├───────────────┤
│ Frame 2       │
├───────────────┤
│ Frame 3       │
└───────────────┘

따라서 Page Table의 핵심 역할은 다음과 같다.

Virtual Page
     ↓
Page Table
     ↓
Physical Frame

32. Virtual Address = Page + Offset

예를 들어 기본 page 크기가 4 KiB(0x1000)이고 프로세스가 0x1234에 접근한다고 하자.

Virtual Address
0x1234

┌──────────────┬─────────────┐
│ Virtual Page │   Offset    │
│      1       │    0x234    │
└──────────────┴─────────────┘

Page Table에 다음 mapping이 있다고 가정한다.

Virtual Page 1
       ↓
Guest Physical Frame 7

그러면 주소 변환 후에도 page 내부 offset 0x234는 유지된다.

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을 관리한다.

단순화한 예:

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)다.

CPU
 │
 │ Virtual Address
 ▼
MMU
 │
 │ Page Table 기반 translation
 ▼
Physical Address

현재 Guest 내부 단계만 보면:

Guest Virtual Address
        ↓
       MMU
        │
        │ Guest Page Table
        ▼
Guest Physical Address

역할을 나누면 다음과 같다.

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한다.

Virtual Address
      ↓
     TLB
   ┌──┴──┐
   │     │
 HIT    MISS
   │     │
   │     ▼
   │ Page Table Walk
   │     │
   └──┬──┘
      ▼
Physical Address

예를 들어:

Virtual Page 1 → Physical Frame 7

이라는 translation이 TLB에 있다면 같은 page의 다음 접근에서 전체 page-table walk를 피할 수 있다.

TLB Miss와 Page Fault는 다르다

TLB Miss:

TLB에 translation cache가 없음
        ↓
Page Table을 조회
        ↓
정상 mapping 존재
        ↓
계속 실행

Page Fault:

Page Table 상태상
현재 접근을 정상 완료할 수 없음

따라서:

TLB Miss ≠ Page Fault

다.


36. Bare Metal과 VM의 차이

Bare-metal Linux에서는 개념적으로 다음으로 끝난다.

Process Virtual Address
        ↓
Page Table
        ↓
Host Physical Address
        ↓
Physical RAM

VM에서는 Guest가 얻은 physical address가 실제 Host physical address가 아니다.

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 계열 기능이 있다.

              Guest가 관리

GVA
 │
 │ Guest Page Table
 ▼
GPA

           Hypervisor 측

GPA
 │
 │ EPT
 ▼
HPA

합치면:

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를 사용할 수 있다.

VM1: GPA 0x1000
VM2: GPA 0x1000

그러나 실제 Host RAM에서는 서로 다른 위치로 연결할 수 있어야 한다.

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을 관리하는 방식이 사용될 수 있었다.

개념적으로:

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를 갖는다.

QEMU Process

Host Virtual Address Space

┌──────────────────────────────┐
│                              │
│      Guest RAM Backing       │
│            8 GiB             │
│                              │
└──────────────────────────────┘

QEMU가 직접 "물리 주소 X부터 8 GiB를 달라"고 RAM hardware를 제어하는 것이 아니다.

QEMU memory도 일반 Host process memory처럼:

QEMU Host Virtual Address
        ↓
Host Page Table
        ↓
Host Physical Address

로 관리된다.


41. KVM_SET_USER_MEMORY_REGION

QEMU는 자신이 마련한 Host userspace memory 영역과 Guest GPA 범위의 관계를 KVM에 등록한다.

대표 ioctl:

KVM_SET_USER_MEMORY_REGION

개념적으로 전달하는 정보:

Guest GPA Range
      ↕
QEMU Host Virtual Address Range

예:

Guest GPA

0x00000000
      │
      │ 8 GiB
      ▼
...

       ↕ backing

QEMU HVA

0x7f0000000000
      │
      │ 8 GiB
      ▼
...

역할을 정리하면:

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가 반드시 모두 즉시 물리적으로 점유되는 것은 아니다.

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에서 중요한 점이다.

GVA
 ↓
Guest Page Table
 ↓
GPA
 ↓
EPT
 ↓
HPA

그런데 Guest Page Table 자체도 Guest Physical Memory에 저장된 자료구조다.

따라서 CPU가 Guest page-table entry를 읽는 과정에서도 그 entry가 저장된 GPA를 실제 HPA로 변환해야 한다.

개념적으로:

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 접근을 다음처럼 생각하면 안 된다.

잘못된 이해

Guest Memory Access
       ↓
VM Exit
       ↓
KVM
       ↓
RAM

정상 mapping이 존재하면 CPU hardware가 직접 translation을 수행한다.

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 단계에서 발생한다.

GVA
 ↓
Guest Page Table
 ↓
현재 접근을 완료할 수 없음
 ↓
Guest #PF
 ↓
Guest Kernel Page Fault Handler

예를 들어 Guest process가 아직 physical page가 붙지 않은 virtual-memory 영역에 처음 접근할 수 있다.

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

Virtual Memory 영역 존재
 ↓
아직 physical page가 필요하지 않았음
 ↓
첫 실제 접근
 ↓
Page Fault
 ↓
Guest Kernel이 page 준비

46.2 Swap-in

필요한 page가 Guest RAM에 없음
 ↓
Page Fault
 ↓
Guest Kernel
 ↓
Guest Swap에서 읽음
 ↓
RAM 복원
 ↓
Page Table 갱신

46.3 Permission Fault

Page Table Entry에는 mapping뿐 아니라 permission도 있다.

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 등으로 이어질 수 있다.

Invalid GVA
 ↓
Page Fault
 ↓
Guest Kernel
 ↓
해결 불가
 ↓
SIGSEGV

따라서:

Page Fault ≠ Segmentation Fault

다.


47. EPT Violation

이번에는 Guest Page Table translation은 성공했다고 하자.

GVA
 ↓
Guest Page Table
 ↓
GPA

그런데 해당 GPA에 대한 second-stage 접근을 현재 EPT 조건으로 완료할 수 없다.

GPA
 ↓
EPT
 ↓
Violation

이것이 EPT Violation이다.

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 측
앱 오류를 뜻하는가 반드시 아님 반드시 아님

핵심:

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 관리가 적용된다.

QEMU Host Virtual Address
        ↓
Host Page Table
        ↓
Host Physical Address

따라서 Host 측에서도 demand allocation, reclaim/swap 등의 이유로 page fault가 발생할 수 있다.

QEMU / Guest RAM Backing
        ↓
Host Virtual Memory
        ↓
Host Page Fault
        ↓
Host Kernel
        ↓
필요한 Host page 처리

즉 VM 메모리 분석에서는 적어도 다음을 구분해야 한다.

Guest Page Fault
Host Page Fault
EPT-related virtualization event

50. Huge Page가 필요한 이유

8 GiB를 모두 4 KiB page 단위로 표현하면:

8 GiB / 4 KiB
= 2,097,152 pages

2 MiB page라면:

8 GiB / 2 MiB
= 4,096 pages

1 GiB page라면:

8 GiB / 1 GiB
= 8 pages

큰 page는 더 적은 mapping으로 넓은 memory range를 표현할 수 있다.


51. Huge Page와 TLB Coverage

TLB entry 하나가 표현하는 page가 커지면 하나의 cached translation으로 더 넓은 주소 범위를 커버할 수 있다.

단순 예:

4 KiB page × 512 mappings
= 2 MiB coverage

2 MiB page × 512 mappings
= 1 GiB coverage

실제 CPU는 page size별 TLB 구조와 entry 수가 다르므로 이 숫자를 특정 CPU의 실제 TLB 용량으로 해석하면 안 된다.

핵심은:

Page Size ↑
    ↓
한 translation이 cover하는 범위 ↑
    ↓
TLB pressure 감소 가능

이다.

추가로 page-table entry 수와 page-table walk 부담도 줄어들 가능성이 있다.


52. VM에서 Huge Page를 볼 때 주의할 점

VM에는 두 translation 단계가 있다.

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를 투명하게 활용하려는 기능이다.

Application
 ↓
일반 malloc()/mmap()
 ↓
Linux Kernel
 ↓
조건이 맞으면 Huge Page 활용 시도

상태 확인:

cat /sys/kernel/mm/transparent_hugepage/enabled

예:

always [madvise] never

현재 정책은 kernel/distribution/Host 설정에 따라 다르므로 실제 시스템에서 확인한다.


54. THP의 Trade-off

Huge Page에는 큰 contiguous physical-memory 영역이 필요하다.

2 MiB는 4 KiB page 512개 크기다.

4 KiB × 512 = 2 MiB

memory fragmentation이 심하면 Kernel이 compaction 등의 작업을 수행할 수 있다.

Huge Page 필요
      ↓
큰 contiguous memory 필요
      ↓
Fragmentation
      ↓
Compaction 가능
      ↓
Latency 영향 가능

따라서 THP는 항상 성능을 높인다고 단정할 수 없다. 특히 latency-sensitive workload에서는 측정이 필요하다.


55. HugeTLB

HugeTLB는 명시적인 Huge Page pool을 사용할 수 있는 Linux 메커니즘이다.

THP:

Application
 ↓
일반 Memory Allocation
 ↓
Kernel이 자동적으로 Huge Page 활용

HugeTLB:

관리자가 Huge Page Pool 준비
 ↓
Application / VM이 명시적으로 사용

예:

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 확인:

grep -i huge /proc/meminfo
cat /sys/kernel/mm/transparent_hugepage/enabled

AnonHugePagesHugePages_Total은 같은 의미가 아니다.


57. Memory Overcommit

예를 들어:

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가 같지 않을 수 있기 때문이다.

예:

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:

CPU 부족
 ↓
Scheduler가 execution time을 나눔
 ↓
Runnable task가 기다림

Memory:

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을 시도한다.

Memory Pressure 증가
        ↓
Reclaim
        ↓
회수 가능한 cache/page 처리
        ↓
필요하면 anonymous memory swap
        ↓
그래도 부족
        ↓
심각한 pressure / OOM 가능

File-backed clean page

원본이 storage에 있으므로 RAM에서 버리고 필요할 때 다시 읽을 수 있다.

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에 접근한다고 생각한다.

Keycloak
 ↓
Guest Memory Load

하지만 Host에서는:

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:

Guest Application
      ↓
Guest Memory Pressure
      ↓
Guest Kernel
      ↓
Guest Swap
      ↓
/dev/vda
      ↓
virtio-blk
      ↓
QEMU
      ↓
Host Storage

Host Swap:

Guest RAM
   ↓
QEMU Memory Backing
   ↓
Host Memory Pressure
   ↓
Host Kernel
   ↓
Host Swap

따라서:

Guest Swap ≠ Host Swap

이다.

Guest가 메모리 여유가 있어 보이는데 Host에서 swap/reclaim이 심할 수도 있다.


62. Memory Pressure와 Storage Contention의 연결

Guest와 Host가 동시에 memory pressure를 겪으면 다음 I/O가 한 storage device로 몰릴 수 있다.

Guest Swap I/O ────────┐
Host Swap I/O ─────────┼──→ Physical NVMe
Database I/O ──────────┤
Filesystem Writeback ──┘

따라서:

Host Memory Pressure
       ↓
Reclaim / Swap
       ↓
Storage I/O 증가
       ↓
Storage Contention
       ↓
DB latency 증가
       ↓
Application latency 증가

가 가능하다.

CPU 사용률이 낮다고 해서 memory/storage 문제가 없는 것은 아니다.


63. Swap Used만 보고 장애를 판단하면 안 된다

예:

Swap Used = 2 GiB

만으로 현재 memory pressure가 심하다고 단정할 수 없다. 과거에 swap-out된 cold page가 남아 있을 수도 있다.

더 중요한 질문:

현재 swap-in/out이 지속되는가?
reclaim pressure가 증가하는가?
major fault가 증가하는가?
storage latency가 같이 증가하는가?

Guest와 Host를 동시에 확인해야 한다.

free -h
vmstat 1

64. Ballooning이 필요한 이유

Host는 QEMU의 Guest RAM backing을 볼 수 있지만 Guest 내부에서 어떤 memory가 중요한지 완전히 알지 못한다.

Guest는 다음 semantics를 알고 있다.

Guest Memory

├─ Application Working Set
├─ JVM Heap
├─ Page Cache
├─ Free
└─ 기타

Host가 무작정 Guest backing을 swap-out하기보다 Guest Kernel과 협력해 불필요한 memory를 반환받는 것이 유리할 수 있다.

대표적인 메커니즘이 virtio-balloon이다.


65. virtio-balloon 구조

             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한다.

Host/QEMU
   │
   │ Balloon target 조정
   ▼
virtio-balloon
   │
════════ VM Boundary ═══════
   │
   ▼
Guest Balloon Driver
   │
   │ Guest pages 확보
   ▼
Guest usable memory 감소

Guest 안의 balloon이 커지기 때문에 Guest가 사용할 수 있는 RAM이 줄어든다.

Before

┌──────────────────────────┐
│       Guest Usable       │
│          Memory          │
└──────────────────────────┘


After Inflate

┌──────────────────────────┐
│       Guest Usable       │
│          Memory          │
├──────────────────────────┤
│         Balloon          │
└──────────────────────────┘

개념:

Balloon Inflate
→ Guest usable memory ↓
→ Host가 회수할 수 있는 backing memory ↑

67. Balloon Page 반환의 의미

Guest balloon driver는 Guest pages를 확보하고 관련 정보를 Host 측에 전달한다.

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을 줄인다.

Host/QEMU
   ↓
Balloon target 감소
   ↓
Guest Balloon Driver
   ↓
Balloon pages 반환
   ↓
Guest usable memory 증가

따라서:

Inflate = Guest usable memory 감소
Deflate = Guest usable memory 증가

다.


69. Ballooning을 과도하게 하면 Guest가 압박을 받는다

Guest application working set이 큰데 balloon을 과도하게 inflate하면:

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:

기존 Guest Memory Capacity
        ↓
그 범위에서 Host/Guest 간
usable memory를 회수/반환

Memory Hotplug:

기존 Guest RAM
      +
추가 Memory Device/Region
      ↓
Guest가 추가 capacity 인식

따라서:

Ballooning ≠ Memory Hotplug

다.

현대 가상화에서는 virtio-mem 같은 다른 동적 memory 관리 방식도 존재하므로 모든 동적 VM memory 관리를 ballooning 하나로 일반화하면 안 된다.


71. OOM

Linux가 memory allocation을 만족시키지 못하고 reclaim 등의 방법으로도 필요한 memory를 확보하지 못하면 OOM 상황이 발생할 수 있다.

Memory Allocation 필요
       ↓
Reclaim 등 시도
       ↓
충분한 Memory 확보 실패
       ↓
OOM
       ↓
OOM Killer
       ↓
Process 선택/종료 가능
       ↓
Memory 확보

72. Guest OOM과 Host OOM

Guest OOM:

Guest RAM 부족
     ↓
Guest Kernel OOM
     ↓
Guest Process Kill

예: Keycloak process 종료

Host OOM:

Host Physical RAM 부족
      ↓
Host Kernel OOM
      ↓
Host Process Kill 가능

Host OOM에서 QEMU가 victim이 되면:

QEMU process killed
      ↓
해당 VM 전체가 중단

될 수 있다.

따라서:

Guest OOM ≠ Host OOM

이다.

또한 cgroup memory limit이 있는 환경에서는 Host 전체 RAM이 남아 있어도 해당 cgroup boundary에서 OOM이 발생할 수 있으므로 OOM의 발생 계층을 확인해야 한다.


73. NUMA

지금까지는 RAM을 하나의 균일한 자원처럼 표현했다. multi-socket/NUMA 시스템에서는 어느 CPU가 어느 RAM에 접근하느냐에 따라 비용이 달라질 수 있다.

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:

NUMA Node 0

CPU
 │
 ▼
Node 0 RAM

Remote:

NUMA Node 0                    NUMA Node 1

CPU
 │
 └──────── Interconnect ───────→ RAM

일반적으로 remote access는 local access와 동일한 비용이라고 가정할 수 없으며 추가 latency/bandwidth 비용이 있을 수 있다.


75. vCPU와 NUMA의 연결

Guest vCPU는 Host에서 QEMU의 vCPU thread다.

Guest vCPU
    ↓
QEMU vCPU Thread
    ↓
Host Linux Scheduler
    ↓
Host Logical CPU

VM1의 vCPU thread가 Node 0 CPU에서 실행되는데 VM1의 Host physical backing page가 Node 1에 있다면:

Node 0 CPU
    │
    │ Remote Access
    ▼
Node 1 RAM

이 될 수 있다.

Guest에서는 단순한 memory load지만 실제 hardware에서는 NUMA interconnect를 건널 수 있다.


76. vCPU Pinning만으로는 NUMA 최적화가 끝나지 않는다

예:

VM1 vCPU
 ↓
Node 0 CPU에 Pinning

VM1 RAM
 ↓
Node 1에 주로 배치

이면 pinning 이후에도 remote memory access가 많아질 수 있다.

따라서:

vCPU Placement
      +
Memory Placement/Binding
      ↓
NUMA Locality

를 함께 봐야 한다.

이상적인 예:

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 자체를 노출할 수 있다.

예:

Guest VM

Guest NUMA Node 0
├─ vCPU 0~7
└─ RAM 32 GiB

Guest NUMA Node 1
├─ vCPU 8~15
└─ RAM 32 GiB

Host:

Host NUMA Node 0
├─ Physical CPUs
└─ RAM

Host NUMA Node 1
├─ Physical CPUs
└─ RAM

가능하면 Guest가 인식하는 topology와 실제 Host placement가 합리적으로 대응되도록 구성할 수 있다.

Guest NUMA 0 → Host NUMA 0
Guest NUMA 1 → Host NUMA 1

78. NUMA는 실제 장비 topology부터 확인한다

Host가 NUMA node 1개라면 cross-node remote-memory 문제가 주요 이슈가 아닐 수 있다.

lscpu

예:

NUMA node(s):          2
NUMA node0 CPU(s):     0-7
NUMA node1 CPU(s):     8-15

추가:

numactl --hardware

QEMU process별 memory distribution:

numastat -p <QEMU_PID>

vCPU placement:

virsh vcpupin <VM_NAME>
virsh vcpuinfo <VM_NAME>

실제 환경에서는 먼저 topology를 측정하고 NUMA 최적화 필요성을 판단한다.


79. 전체 Memory Virtualization 실행 경로

최종적으로 Guest application의 memory access는 다음 구조로 이해할 수 있다.

                       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 관리 경로

실행 경로와 관리 경로를 분리해야 한다.

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을 전체적으로 보면:

                         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는 이 모든 실행을 받친다.

Guest GVA
   ↓
Guest Page Table
   ↓
GPA
   ↓
EPT
   ↓
HPA
   ↓
Host RAM / NUMA

그리고 memory pressure는 storage path까지 영향을 줄 수 있다.

Memory Pressure
 ↓
Reclaim / Swap
 ↓
Storage I/O
 ↓
Storage Contention
 ↓
Application Latency

CPU placement는 NUMA memory locality와 연결된다.

vCPU Pinning
      +
Memory Placement
      ↓
Local / Remote Memory Access

82. 핵심 Claim Registry

CLAIM-MEM-01

Guest application은 일반적으로 Host physical address를 직접 사용하지 않는다.

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는 무엇인가?

lscpu
numactl --hardware

확인할 것:

  • NUMA node 수
  • node별 CPU
  • node별 memory
  • node distance

OQ-2. 각 VM의 configured/current memory는 얼마인가?

virsh dominfo <VM_NAME>
virsh dumpxml <VM_NAME>
virsh dommemstat <VM_NAME>

Guest:

free -h
cat /proc/meminfo

Host의 QEMU process 상태와 비교한다.


OQ-3. QEMU process의 Host resident memory는 어떻게 분포하는가?

ps -ef | grep qemu
ps -o pid,rss,vsz,cmd -p <QEMU_PID>

필요하면:

cat /proc/<QEMU_PID>/status
cat /proc/<QEMU_PID>/smaps_rollup

configured memory와 RSS/anonymous/huge-page 상태를 비교한다.


OQ-4. Host THP 정책은 무엇인가?

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되어 있는가?

virsh dumpxml <VM_NAME>

libvirt memory backing 관련 설정을 확인하고 Host /proc/meminfo, QEMU smaps 계열과 교차 검증한다.


OQ-6. Guest와 Host에서 현재 swap이 발생하는가?

Guest:

free -h
vmstat 1

Host:

free -h
vmstat 1

단순 swap-used 값보다 현재 swap-in/out activity와 memory pressure를 함께 본다.


OQ-7. Host memory pressure가 Guest latency에 영향을 주는가?

실험 개념:

Baseline
 ↓
Guest Application Latency 측정
 ↓
Host Memory Pressure 유도
 ↓
Host reclaim/swap 관측
 ↓
Guest latency 재측정

동시에 CPU와 storage도 관측한다.


OQ-8. virtio-balloon이 VM에 구성되어 있는가?

virsh dumpxml <VM_NAME>

Guest에서도 관련 driver/device 상태를 확인한다.

환경에 따라 driver 이름과 표시 방식이 달라질 수 있으므로 실제 장비에서 검증한다.


OQ-9. Balloon target 변화가 Guest available memory에 어떻게 반영되는가?

관측:

Host/libvirt memory setting
        ↓
Guest free -h / /proc/meminfo
        ↓
Guest reclaim/swap 변화

과도한 ballooning 시 Guest latency/swap/OOM 가능성을 별도 실험한다.


OQ-10. VM vCPU는 어느 Host CPU에 배치되어 있는가?

virsh vcpuinfo <VM_NAME>
virsh vcpupin <VM_NAME>

CPU 가상화 SSOT의 pinning/overcommit 관측과 연결한다.


OQ-11. QEMU memory는 어느 NUMA node에 배치되어 있는가?

numastat -p <QEMU_PID>

vCPU placement와 비교한다.

vCPU → Node 0
Memory → Node 0

인지,

vCPU → Node 0
Memory → Node 1

인지 확인한다.


OQ-12. NUMA remote access가 실제 workload latency에 의미 있는 영향을 주는가?

NUMA node가 2개 이상인 경우에만 우선순위를 높인다.

Local placement baseline
        ↓
Latency / throughput / memory metrics
        ↓
Remote-heavy placement
        ↓
동일 workload 비교

단순 topology만 보고 성능 문제라고 단정하지 않는다.


OQ-13. Guest Page Fault가 workload 변화와 함께 증가하는가?

Guest에서 page-fault 관련 지표를 관측하고 다음을 분리한다.

정상 demand paging?
COW?
Guest swap-in?
application working-set 증가?

Page Fault 증가만으로 오류라고 판단하지 않는다.


OQ-14. Host Page Fault/major fault와 storage latency가 상관되는가?

Host memory pressure 실험 시:

Host Fault
   +
Swap activity
   +
Storage latency
   +
Guest application latency

를 같은 시간축으로 비교한다.


84. 권장 실험 순서

개념 검증은 다음 순서가 좋다.

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. 실험 시 반드시 같이 기록할 것

각 실험은 다음 조건을 남긴다.

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이 보이면 한 번에 "메모리 부족"이라고 결론내리지 않는다.

문제
 │
 ├─ 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을 한 장으로 기억할 때는 다음 그림을 기준으로 한다.

                         [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

관리 경로는 별도로 기억한다.

virsh
 ↓
libvirt
 ↓
QEMU
 │
 │ Guest RAM backing
 │ KVM_SET_USER_MEMORY_REGION
 ▼
KVM
 │
 │ EPT 관련 mapping 관리
 ▼
CPU MMU

그리고 자원 압박 경로:

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 할당으로 보지 않는 것이다.

실제 구조에는 다음 계층이 있다.

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가 수행한다.

성능과 장애를 볼 때는 그 위에 다음 요소가 추가된다.

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다.

virsh list --all
virsh start vm1
virsh shutdown vm1
virsh domiflist vm1
virsh net-list --all

virsh는 packet datapath에 직접 참여하지 않는다.

User
 ↓
virsh
 ↓
libvirt
 ↓
QEMU

90.2 libvirt

libvirt는 VM lifecycle 및 configuration을 관리하는 소프트웨어/API 계층이다.

관리 대상 예:

vCPU
Memory
Disk
NIC model
MAC address
Virtual network
Bridge
QEMU arguments

90.3 virtio

virtio는 명령어가 아니다.

또한 하나의 단일 프로그램이나 단일 커널 모듈을 의미하지 않는다.

Virtio는 Guest와 Host/Hypervisor가 가상 I/O 장치를 효율적으로 사용하기 위한 표준화된 인터페이스/프로토콜이다.

대표적인 virtio 장치:

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 측

Guest Kernel
  ├─ TCP/IP Stack
  ├─ virtio-net Frontend Driver
  └─ virtqueue

Host 측

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

       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을 사용하는가

물리 서버에서는:

Application
 ↓
Linux TCP/IP Stack
 ↓
Physical NIC Driver
 ↓
Physical NIC

VM에서는:

Application
 ↓
Guest TCP/IP Stack
 ↓
virtio-net Driver
 ↓
Virtual NIC

이다.

Guest는 "QEMU를 호출한다"가 아니라 "내 NIC를 사용한다"고 동작한다.

VM 시작 시 QEMU가 Guest에게 virtio 방식의 virtual NIC를 노출한다.

QEMU
 ↓
Virtual PCI Bus에 virtio NIC 노출
 ↓
Guest Linux
 ↓
virtio device 발견
 ↓
virtio-net driver bind
 ↓
ens3 / eth0 형태의 network interface 생성

Guest에서 확인:

lspci
ip link
ip addr

94. 전체 네트워크 계층

가장 기본적인 virtio-net + vhost-net + TAP + Linux Bridge 구조를 기준으로 한다.

수신 방향

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

송신 방향

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는 실제 네트워크 링크와 서버를 연결하는 하드웨어다.

Network
 ↓
Physical NIC
 ↓
NIC Driver
 ↓
Linux Kernel

Linux에서:

ip link

등으로 enp3s0, eno1, eth0 같은 interface를 확인할 수 있다.

주의:

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다.

VM1 TAP ──┐
          │
VM2 TAP ──┼── br0 ── Physical NIC
          │
Host NIC ─┘

Bridge는 Ethernet frame의 Destination MAC을 보고 어느 port로 전달할지 결정한다.

핵심 역할:

L2 forwarding
MAC learning
Frame forwarding
Multiple virtual/physical ports 연결

확인:

bridge link
bridge fdb show
ip link show type bridge

97. Routing의 역할

Routing은 Bridge와 다르다.

Bridge
  → L2
  → MAC 기반
  → 같은 Ethernet network 연결

Routing
  → L3
  → IP 기반
  → 서로 다른 IP network 사이 연결

Linux routing table 확인:

ip route

Routing은 destination IP를 보고 어느 interface 또는 next-hop으로 packet을 보낼지 결정한다.


98. NAT의 역할

NAT는 packet의 IP/Port 정보를 변환한다.

예:

VM
192.168.122.10
 ↓
Host NAT
 ↓
203.0.113.10
 ↓
Internet

VM이 private subnet을 쓰는 경우 Host가 NAT gateway처럼 동작할 수 있다.

따라서 실제 VM network를 분석할 때 다음을 구분해야 한다.

Bridge 기반인가?
Routing 기반인가?
NAT 기반인가?

99. TAP의 역할

TAP은 Host Linux Kernel이 제공하는 가상 Ethernet network interface다.

물리 장치가 아니다.

예:

tap0
vnet0

역할:

VM의 Ethernet frame과 Host Linux networking을 연결하는 접점

Guest Virtual NIC
 ↓
virtio backend
 ↓
TAP
 ↓
Host Linux Network

수신:

Linux Bridge
 ↓
TAP
 ↓
VM

송신:

VM
 ↓
TAP
 ↓
Linux Bridge

확인:

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를 사용한다.

TX virtqueue
Guest → Host

RX virtqueue
Host → Guest

개념:

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에는 실제로 다음이 존재한다.

Socket
TCP
UDP
IP
Routing
Neighbor/ARP
Firewall
Network Driver

101.1 Socket

Application과 Kernel network stack 사이의 인터페이스다.

대표 API:

socket()
bind()
listen()
accept()
connect()
send()
recv()

Keycloak은 Ethernet frame이나 virtqueue를 직접 다루지 않는다.

101.2 TCP

TCP의 대표 책임:

Connection 관리
Port
Sequence
순서 보장
재전송
중복 처리
Flow Control
Congestion Control

예:

Source Port:      53021
Destination Port: 8080

101.3 IP

IP 계층은 IP 주소와 routing을 담당한다.

예:

Source IP:      192.168.122.10
Destination IP: 192.168.122.20

확인:

ip addr
ip route

NIC에 가까운 계층에서는 Ethernet frame과 MAC address를 다룬다.

확인:

ip neigh

102. Packet이 Keycloak까지 올라오는 과정

Ethernet Frame
 ↓
IP Packet
 ↓
TCP Segment / Stream
 ↓
Socket
 ↓
HTTP
 ↓
Keycloak

Keycloak은 다음을 직접 알 필요가 없다.

virtqueue
vhost-net
TAP
Bridge
Physical NIC

Keycloak은 Guest Linux가 제공하는 TCP socket 위에서 HTTP 요청을 처리한다.


103. QEMU virtio Device Model의 역할

QEMU의 virtio Device ModelHost Userspace의 QEMU process 내부에 존재한다.

여기서 역할을 두 개로 분리해야 한다.

역할 A. 장치 생성/설정/관리

QEMU
 ↓
virtio-net Device Model 생성
 ↓
Guest에게 device 노출
 ↓
feature negotiation
 ↓
virtqueue 설정
 ↓
backend 연결

이 역할은 QEMU가 담당한다.

역할 B. 실제 Packet Datapath 처리

QEMU backend를 직접 사용하는 경우

TAP
 ↓
QEMU virtio backend
 ↓
virtqueue
 ↓
Guest

vhost-net을 사용하는 경우

TAP
 ↓
vhost-net
 ↓
virtqueue
 ↓
Guest

반복적인 packet I/O를 Host Kernel에서 처리하고 QEMU userspace를 우회한다.


104. 왜 TAP → vhost-net → QEMU → virtqueue라고 일반화하면 안 되는가

다음 그림:

TAP
 ↓
vhost-net
 ↓
QEMU
 ↓
virtqueue

은 모든 packet이 vhost-net → QEMU 순으로 반드시 지나가는 것처럼 보인다.

하지만 vhost-net의 중요한 목적 중 하나는 packet datapath에서 QEMU userspace를 우회하는 것이다.

vhost-net 사용 시 fast path는 다음처럼 이해한다.

TAP
 ↓
vhost-net
 ↓
virtqueue
 ↓
Guest

QEMU는 사라지는 것이 아니라 device lifecycle과 configuration을 관리한다.


105. Control Path와 Data Path

Control / Setup Path

virsh
 ↓
libvirt
 ↓
QEMU
 ↓
virtio-net Device Model
 ↓
feature negotiation
virtqueue setup
vhost-net setup

여기서 control은 Kubernetes Control Plane을 뜻하지 않는다.

일반적인 시스템 용어로 설정/제어 경로라는 의미다.

Data Path

실제 packet이 반복적으로 흐르는 경로다.

vhost-net 사용 시:

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

QEMU
 ↓
vCPU 생성/관리

실제 Guest instruction 실행
 ↓
KVM / VMX

QEMU가 vCPU를 만든다고 Guest의 ADD, MOV, SUB를 전부 QEMU가 실행하는 것은 아니다.

Network

QEMU
 ↓
virtio-net 생성/관리

실제 반복 packet I/O
 ↓
vhost-net / virtqueue

QEMU가 virtual NIC를 만든다고 packet 100만 개를 반드시 QEMU가 하나씩 처리할 필요는 없다.


107. vhost-net 최적화

QEMU userspace가 packet마다 I/O를 처리하면 다음 전환 비용이 누적될 수 있다.

Host Kernel
 ↓
QEMU Userspace
 ↓
Host Kernel
 ↓
...

Packet rate가 높아질수록 userspace/kernel transition, scheduling, copy, notification 비용이 커질 수 있다.

QEMU userspace backend

TAP
 ↓
QEMU
 ↓
virtqueue

vhost-net kernel backend

TAP
 ↓
vhost-net
 ↓
virtqueue

핵심 최적화 방향:

Packet마다 QEMU userspace 개입
        ↓
Kernel backend로 hot path 이동
        ↓
Context switch / userspace overhead 감소

108. vhost-net은 QEMU를 제거하지 않는다

vhost-net 사용 시에도 QEMU는 필요하다.

QEMU의 역할:

VM lifecycle
Virtual hardware model
virtio device 생성
Feature negotiation
Queue configuration
Backend 연결
Device reset
Control/configuration handling

따라서:

vhost-net != QEMU 제거

정확히는:

vhost-net
=
QEMU가 담당하던 반복적인 virtio packet datapath의 상당 부분을
Host Kernel로 offload

라고 이해한다.


109. Fast Path와 Slow/Control Path

Fast Path

빈번하게 반복되는 packet forwarding/data transfer 경로다.

예:

TAP
 ↓
vhost-net
 ↓
virtqueue

Control/Slow Path

상대적으로 빈도가 낮고 설정/예외 처리를 담당한다.

예:

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 여부는 다음에 따라 달라질 수 있다.

Kernel version
QEMU version
vhost configuration
offload
NIC capability
packet path
GSO/GRO/TSO

111. Interrupt / Notification 최적화

Guest와 Host는 queue에 새로운 packet/buffer가 있음을 서로 알려야 한다.

단순화:

Guest TX
 ↓
virtqueue descriptor 등록
 ↓
Host backend notification
 ↓
backend 처리

수신:

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를 사용할 수 있다.

RX Queue 0 → vCPU 0
RX Queue 1 → vCPU 1
RX Queue 2 → vCPU 2
RX Queue 3 → vCPU 3

목적:

Packet processing 병렬화
Single queue bottleneck 완화
Multi-core 활용

효과는 workload, CPU affinity, IRQ placement, queue configuration에 따라 달라진다.


113. Offload 최적화

대표적인 offload:

TSO  - TCP Segmentation Offload
GSO  - Generic Segmentation Offload
GRO  - Generic Receive Offload
Checksum Offload

목적:

작은 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을 거치는 것은 아니다.

예:

VM1 TAP
 ↓
Linux Bridge
 ↓
VM2 TAP

반면 Host가 다음 역할을 하면 L3/Netfilter 경로가 개입한다.

Routing
NAT
Host-local termination
Firewall

따라서 다음을 고정된 packet path로 보면 안 된다.

Physical NIC
 ↓
Host TCP/IP Stack
 ↓
Bridge

실제 경로는 bridge/routing/NAT 구성에 따라 달라진다.


115. Host Physical NIC로 나갈 때 virtio를 다시 거치지 않는다

Guest
virtio-net
 ↓
vhost-net
 ↓
TAP
 ↓
Linux Bridge
 ↓
Intel NIC Driver
 ↓
Intel Physical NIC

즉:

Guest virtio
→ Host virtio
→ Physical NIC

구조가 아니다.

virtio는 Guest virtual I/O device와 Host backend 사이의 인터페이스다.


116. 현재 Keycloak/K3s 테스트 환경과 연결

Client
 ↓
Host Physical NIC
 ↓
Host Nginx
 ↓
Host Network
 ↓
VM1 / VM2
 ↓
K3s
 ↓
Keycloak Node 1 / 2

VM network까지 펼치면:

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 연결 오류

증상:

VM 외부 통신 불가
Host ↔ VM 통신 불가
특정 VM만 통신 불가

확인:

ip link
bridge link
bridge fdb show
virsh domiflist <vm>

117.2 Routing 오류

증상:

같은 subnet은 통신되지만 다른 subnet은 안 됨
gateway까진 되지만 외부 통신 실패

확인:

ip route
ip rule

117.3 NAT/Firewall 오류

증상:

VM → Internet 실패
외부 → VM 접근 실패
특정 port만 실패

확인 대상:

nftables
iptables
NAT rules
IP forwarding

117.4 vhost-net 미사용 또는 비효율적 datapath

높은 packet rate에서 QEMU userspace가 datapath를 직접 처리하면 CPU overhead가 커질 수 있다.

관찰:

QEMU CPU usage
vhost thread
packet rate
latency
context switch

117.5 Single Queue Bottleneck

하나의 queue/vCPU에 packet processing이 집중될 수 있다.

확인 대상:

virtio multi-queue
IRQ distribution
per-vCPU CPU usage
RSS/RPS/XPS

117.6 Offload 때문에 packet capture가 예상과 다르게 보임

원인 후보:

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

ip link
ip addr
ethtool <interface>

Linux Bridge

ip link show type bridge
bridge link
bridge fdb show

TAP / vnet

ip link
ip tuntap show

libvirt VM NIC

virsh domiflist <domain>

libvirt network

virsh net-list --all
virsh net-info <network>
virsh net-dumpxml <network>

Routing

ip route
ip rule

Guest NIC

ip link
ip addr
ip route
ip neigh

virtio 장치

lspci
lsmod | grep virtio

vhost

lsmod | grep vhost

119. 실제 packet path 추적

Host:

sudo tcpdump -ni <physical-nic>
sudo tcpdump -ni <bridge>
sudo tcpdump -ni <tap-or-vnet>

Guest:

sudo tcpdump -ni <guest-interface>

예:

Physical NIC  O
Bridge        O
TAP           X

이면 Guest 내부보다 먼저 Host Bridge/TAP mapping을 의심한다.

TAP           O
Guest NIC     X

이면 virtio/vhost/Guest NIC 계층을 의심한다.

Guest NIC     O
Socket        X

이면 Guest routing/firewall/listen 상태를 의심한다.


120. Keycloak Refresh Token 실험과의 관계

Refresh Token 경쟁 자체는 virtio-net 문제가 아니다.

하지만 다음 경로를 공유하므로 network virtualization 문제가 실험 결과에 영향을 줄 수 있다.

Client
 ↓
Nginx
 ↓
VM1 / VM2
 ↓
K3s
 ↓
Keycloak
 ↓
PostgreSQL / Redis

예:

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까지 이동하는 과정

포함 범위:

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 중 어떤 구조인가?

virsh net-list --all
virsh net-dumpxml <network>
ip link
bridge link
ip route

OQ-2. VM1/VM2의 TAP/vnet interface는 무엇인가?

virsh domiflist vm1
virsh domiflist vm2
ip link
bridge link

OQ-3. 현재 환경에서 vhost-net이 실제 사용되는가?

확인 후보:

lsmod | grep vhost

추가로 QEMU arguments와 libvirt domain XML을 확인한다.

OQ-4. QEMU backend와 vhost-net의 성능 차이가 현재 Host에서 관찰 가능한가?

비교:

Latency
Throughput
QEMU CPU
Host CPU
Context Switch
Packet rate

OQ-5. Multi-queue가 현재 virtio-net에 활성화되어 있는가?

확인 대상:

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를 사용하는가?

관찰:

QEMU CPU
vhost thread
softirq
Host CPU
Guest CPU
network latency

123. OPEN QUESTION → CASE

SSOT
 ↓
CONCEPT
 ↓
OPEN QUESTION
 ↓
실제 packet capture / configuration 확인 / load test
 ↓
CASE

예:

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

User
 ↓
virsh
 ↓
libvirt
 ↓
QEMU
 ↓
virtio-net Device Model
 ├─ virtual NIC 생성
 ├─ Guest 노출
 ├─ feature negotiation
 ├─ virtqueue 설정
 └─ vhost-net backend 설정

Data Path - vhost-net 사용

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 사용

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. 다음 실습 순서

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. 전체 구조

                    [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을 사용한다.

write(fd, buffer, size);
[Guest Userspace]

PostgreSQL
    │
    │ write()
    ▼

════════ System Call ════════

[Guest Kernel]

   VFS

즉 애플리케이션은 저장장치를 직접 조작하는 것이 아니라 Guest Linux Kernel에 파일 연산을 요청한다.

대표적인 파일 관련 system call:

open()
read()
write()
close()
fsync()

이 시점에는 아직 QEMU, qcow2, Host NVMe가 등장하지 않는다.


130. VFS: 공통 파일 인터페이스 계층

VFS(Virtual File System)는 Linux Kernel 내부에서 여러 filesystem을 동일한 API로 사용할 수 있도록 연결하는 공통 계층이다.

Guest가 ext4라면:

PostgreSQL
    ↓
write()
    ↓
VFS
    ↓
ext4

XFS라면:

PostgreSQL
    ↓
write()
    ↓
VFS
    ↓
XFS

VFS의 핵심 역할:

이 fd가 어떤 파일인가?
        ↓
이 파일은 어떤 filesystem에 속하는가?
        ↓
해당 filesystem 구현으로 연산 전달

VFS는 애플리케이션의 공통 파일 연산을 실제 filesystem 구현으로 연결한다.


131. Filesystem(ext4/XFS): 파일 세계를 block 공간에 배치

SSD는 /var/lib/postgresql/data 같은 디렉터리 구조를 모른다.

저장장치 입장에서는 결국 block 단위 공간이다.

Block 0
Block 1
Block 2
Block 3
...

하지만 사용자는 다음과 같이 파일과 디렉터리를 본다.

/
├── etc
├── home
└── var
    └── lib
        └── postgresql
            └── data

이 논리 구조를 제공하고 관리하는 것이 ext4/XFS 같은 filesystem이다.

Filesystem이 관리하는 대표 정보:

  • 파일 이름과 디렉터리 구조
  • 파일 크기
  • owner / permission
  • timestamp
  • inode / metadata
  • 파일 데이터가 저장될 block
  • free space
  • filesystem consistency

개념적으로:

사람/프로그램이 보는 세계

/var/lib/postgresql/data/users
             │
             ▼
          ext4/XFS
             │
             ▼
저장장치가 보는 세계

Block 8142
Block 8143
Block 9201
...

132. inode

inode는 Linux filesystem에서 파일 metadata와 저장 위치 정보를 관리하는 핵심 자료구조다.

"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까지 즉시 내려갈 필요가 없다.

Application
    │
    │ write()
    ▼
Linux Kernel
    │
    ▼
Page Cache (RAM)
    │
    │ 나중에 writeback
    ▼
Filesystem / Block Layer
    ↓
SSD

예를 들어 storage에는 현재 ABC가 있는데 애플리케이션이 DEF를 추가했다고 하자.

Page Cache (RAM)
┌──────────────┐
│ ABCDEF       │ ← 최신 상태, dirty
└──────────────┘

SSD
┌──────────────┐
│ ABC          │ ← 아직 이전 상태
└──────────────┘

storage보다 최신인 Page Cache page를 dirty page라고 한다.

이후 kernel writeback이 실제 storage 쪽으로 내려간다.

Dirty Page
    ↓
Filesystem
    ↓
Block Layer
    ↓
Storage

따라서:

write() 성공
    ≠
Physical SSD 영속화 완료

이다.


134. Guest Block I/O Layer

현재 위치:

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 요청으로 전달·관리한다.

Filesystem 세계

/users/data.db
offset 8192에 4KB write
        │
        ▼
──────────────────────
   Block I/O Layer
──────────────────────
        │
        ▼
Block Device 세계

/dev/vda의 특정 위치에
READ / WRITE / FLUSH

대표 요청:

READ
WRITE
FLUSH
DISCARD

실제 Linux 내부에는 bio, request, queue, blk-mq 등이 존재한다.


135. /dev/vda: Guest가 보는 가상 Block Device

물리 머신에서는:

/dev/sda
/dev/nvme0n1

같은 block device가 보일 수 있다.

virtio-blk를 사용하는 VM에서는 흔히:

/dev/vda
/dev/vdb

처럼 보인다.

Guest에서:

lsblk

예시:

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 관계

/dev/vda                 ← Virtual Block Device
  │
  └─ /dev/vda2           ← Partition
         │
         └─ ext4          ← Filesystem
               │
               └─ /

위에서 아래로 보면:

/
↓
ext4
↓
/dev/vda2
↓
/dev/vda

cd /var/lib/postgresql은 filesystem 세계를 보는 것이고, lsblk에서 vda를 보는 것은 block device 세계를 보는 것이다.


137. virtio-blk: Guest의 가상 Block Device Driver

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와 비교:

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에 게시한다.

Guest Kernel

ext4
 ↓
Block I/O Layer
 ↓
/dev/vda
 ↓
virtio-blk
 ↓
virtqueue

Network에서:

TCP/IP Stack
 ↓
virtio-net
 ↓
virtqueue

였던 구조가 Storage에서도 반복된다.


139. virtqueue의 실제 의미

virtqueue를 단순한 "데이터 파이프"로 보면 부정확하다.

Guest memory에 I/O buffer가 있고 descriptor가 그 buffer를 가리킨다.

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 경로:

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를 요청한다.

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에:

/var/lib/libvirt/images/keycloak-node1.qcow2

라는 파일이 있다고 하자.

Host 관점:

keycloak-node1.qcow2
"파일 하나"

Guest 관점:

/dev/vda
 ├─ /dev/vda1
 └─ /dev/vda2

즉:

Host 관점
────────────────────
vm1.qcow2
"파일"

Guest 관점
────────────────────
/dev/vda
"디스크"

둘 다 맞다.


143. qcow2 Virtual Size와 실제 Host 사용량

qcow2는 가상 disk size와 실제 Host 할당량이 다를 수 있다.

Guest가 보는 공간

/dev/vda
┌──────────────────────────────────────┐
│                100 GB                │
└──────────────────────────────────────┘

Host 실제 할당 공간

vm1.qcow2
┌──────┐
│ 3GB  │
└──────┘

Guest가 데이터를 기록하면서:

처음
Virtual 100GB
Actual 1GB

        ↓ Guest 데이터 기록

Virtual 100GB
Actual 10GB

        ↓ 더 기록

Virtual 100GB
Actual 40GB

처럼 실제 사용량이 늘 수 있다.

확인:

qemu-img info vm1.qcow2

virtual size와 실제 allocation을 구분해서 봐야 한다.


144. RAW Image

RAW는 qcow2보다 구조가 단순하다.

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는 상대적으로 단순하다.

다만:

RAW = 무조건 빠름
qcow2 = 무조건 느림

으로 일반화하면 안 된다.

실제 성능은 cache mode, storage backend, workload pattern, queue depth, snapshot chain, underlying filesystem, physical device 등에 영향을 받는다.


145. Host Block Device를 직접 backend로 사용 가능

반드시 파일일 필요는 없다.

Guest /dev/vda
      ↓
virtio-blk
      ↓
QEMU
      ↓
Host /dev/nvme0n1p3

따라서 Guest에 /dev/vda가 있다는 정보만으로 backend 구조를 알 수 없다.

/dev/vda
   ↓

 ┌─────────────┬─────────────┬──────────────────┐
 ↓             ↓             ↓
qcow2          RAW        Host Block Device
file           file       /dev/...

146. 실제 연결 확인

Guest:

lsblk

Host:

virsh domblklist <VM_NAME>

예시:

Target   Source
-----------------------------------------------
vda      /var/lib/libvirt/images/vm1.qcow2

그러면:

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를 함께 사용하면:

                 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() 완료와 영속화는 다르다

PostgreSQL
   ↓
Guest Page Cache       ✓
   ↓
virtio                 ✓
   ↓
Host Page Cache        ✓

───────── Host 전원 장애 ─────────

Physical SSD           ✗

가능성이 있다.

따라서:

write() 완료
     ≠
writeback 완료
     ≠
fsync/flush 완료
     ≠
전원 장애에도 안전한 durability

이다.


149. Direct I/O

Buffered I/O:

QEMU
 ↓
Host Page Cache
 ↓
Host Filesystem
 ↓
Block Layer
 ↓
SSD

Direct I/O:

QEMU
 ↓
Host Filesystem / Block I/O Path
 ↓
Block Layer
 ↓
SSD

Linux의 O_DIRECT가 대표적으로 관련된다.

중요한 구분:

Direct I/O
   ≠
자동 durability 보장

Direct I/O의 핵심은 Page Cache 우회다.


150. fsync()가 필요한 이유

write(fd, data, size);

성공만으로 정전 이후 생존을 보장하지 않는다.

필요한 시점에:

fsync(fd);

를 통해 변경 내용을 필요한 영속성 경계까지 반영하도록 요청한다.

VM에서는:

PostgreSQL
     │
   fsync()
     ▼
Guest Filesystem
     │
     ▼
Guest Block Layer
     │
   FLUSH 등
     ▼
virtio-blk
     │
     ▼
QEMU / Backend
     │
     ▼
Host Storage Stack
     │
     ▼
Physical Storage

처럼 전체 stack으로 의미가 전달되어야 한다.


151. FLUSH

단순화하면:

WRITE
  ↓
"이 데이터를 써라"

FLUSH
  ↓
"앞서 쓴 데이터를 필요한 영속성 경계까지
반영하고 완료 상태를 보장해라"

이다.

실제 ordering/durability semantics는 더 복잡하지만 Storage 가상화에서는 이 구분이 핵심이다.


152. 가장 위험한 상황: 거짓 완료

Guest가:

WRITE
 ↓
FLUSH

를 요청했는데 실제 상태가:

Host RAM
┌──────────────┐
│ Data         │
└──────────────┘

Physical Storage
┌──────────────┐
│ Old Data     │
└──────────────┘

인데 Guest에게 FLUSH 완료라고 응답하면 문제가 된다.

PostgreSQL은 durability가 확보되었다고 판단할 수 있고, 직후 Host 전원이 나가면 RAM의 data가 사라진다.

이것은 성능 문제가 아니라 durability contract가 깨지는 correctness 문제다.


153. QEMU Cache Mode

QEMU/libvirt disk에서 대표적으로 볼 수 있는 설정:

cache=none
cache=writeback

이름만 보고:

none = cache 자체가 없음
writeback = 무조건 위험

이라고 해석하면 부정확하다.

핵심은 QEMU가 Host Page Cache와 write completion/flush semantics를 어떤 방식으로 사용할 것인가다.


154. cache=none

개념적으로 Host Page Cache를 우회하는 방향의 I/O 구성이다.

Guest Page Cache
      ↓
virtio
      ↓
QEMU
      ↓
Direct I/O 계열
      ↓
Host Filesystem / Block Path
      ↓
Storage

이중 caching을 줄일 수 있다.

하지만:

Host Page Cache 우회
      ≠
무조건 즉시 durable media 반영

이다.


155. cache=writeback

Host Page Cache를 사용할 수 있는 구성이다.

Guest
 ↓
virtio
 ↓
QEMU
 ↓
Host Page Cache
 ↓
writeback
 ↓
Physical Storage

일반 write는 Host RAM에서 빠르게 completion될 수 있다.

QEMU
 ↓
Host RAM에 기록
 ↓
WRITE completion

       ...

나중에

Host RAM
 ↓
Storage

하지만 cache=writeback 자체가 Guest의 fsync()/FLUSH를 무시한다는 뜻은 아니다.

정상적인 stack이라면:

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에서 올바르게 보존되는지가 중요하다.

Guest가 요구한 durability
        │
        ▼
Guest Filesystem
        │
        ▼
Guest Block Layer
        │
        ▼
virtio
        │
        ▼
QEMU/backend
        │
        ▼
Host Storage
        │
        ▼
Device

전체 chain에서 의미가 깨지지 않아야 한다.


157. Device-side Cache

Host Page Cache를 우회했다고 끝이 아니다.

QEMU
 ↓
Direct I/O
 ↓
Host Block Layer
 ↓
NVMe Driver
 ↓
NVMe Controller
 ↓
Device-side Cache
 ↓
Flash

Storage controller/device가 volatile write cache를 가질 수 있다.

따라서:

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가 된다.

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를 공유하면

VM1 QEMU ──┐
           │
VM2 QEMU ──┼──→ Host Block Layer → NVMe
           │
Nginx ─────┤
           │
Host 기타 ─┘

여러 source에서 동시에 I/O가 들어올 수 있다.

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가 중요하다.

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에 전달되는 것은 아니다.

READ A
WRITE B
READ C
WRITE D
READ E
    ↓

┌─────────────────────┐
│    I/O Scheduler    │
│ 요청 dispatch 정책  │
└──────────┬──────────┘
           ↓
      Device Driver

대표적으로 볼 수 있는 scheduler:

none
mq-deadline
bfq

scheduler마다 목적과 정책이 다르다.


162. none

none은 복잡한 scheduling 정책을 최소화해서 비교적 직접 device 쪽으로 dispatch하는 방향이다.

NVMe처럼 device 자체가 강한 병렬성과 queueing 기능을 가진 경우 이러한 단순한 정책이 적합할 수 있다.

단:

none = block layer가 아무 일도 하지 않음

은 아니다.


163. 실제 I/O Scheduler 확인

Host:

cat /sys/block/nvme0n1/queue/scheduler

예시:

[none] mq-deadline

대괄호 안이 현재 선택된 scheduler다.

SATA/SCSI device라면:

cat /sys/block/sda/queue/scheduler

처럼 확인한다.


164. NVMe Driver와 Physical Device

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다.

SSD
├─ SATA SSD
│    └─ SATA/AHCI
│
└─ NVMe SSD
     └─ PCIe + NVMe

NVMe SSD:

Linux NVMe Driver
      ↓
PCIe
      ↓
NVMe Controller
      ↓
Flash

166. Storage I/O Completion

WRITE 요청은 아래로 내려가고, 완료는 반대 방향으로 올라온다.

Request:

Guest
  │
  │ WRITE
  ▼
virtio-blk
  ↓
virtqueue
  ↓
QEMU/backend
  ↓
Host Block Layer
  ↓
NVMe Driver
  ↓
NVMe

Completion:

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 경쟁이 발생할 수 있다.

VM1 PostgreSQL
      │
      ├────────┐
      │        │
VM2 Keycloak   │
      │        │
      ├────────┤
      │        ▼
      │   Host Block Layer
      │        ↓
      │    I/O Queue
      │        ↓
      └──────→ NVMe

VM1에서 대량 I/O가 발생하면 VM2의 storage latency가 증가할 수 있다.

CPU Contention
→ Host logical CPU 실행 시간 경쟁

Storage Contention
→ IOPS / bandwidth / queue / device 처리시간 경쟁

둘은 다른 자원 경쟁이다.


168. CPU가 정상이어도 Storage 때문에 느릴 수 있다

HTTP Request
 ↓
Keycloak
 ↓
PostgreSQL
 ↓
fsync()
 ↓
Storage

PostgreSQL이 storage completion을 기다리고 있으면 CPU usage가 높지 않을 수도 있다.

CPU 30%

그런데

Request latency 2초

가 가능하다.

따라서 CPU 지표만으로 latency 원인을 판단하면 안 된다.


169. Storage 관측 명령어

대표적인 device I/O 관측:

iostat -xz 1

확인 대상:

  • read/write throughput
  • IOPS
  • request latency
  • queue 상태
  • device utilization 성격의 지표

어떤 process가 I/O를 발생시키는지 볼 때:

iotop

Guest:

lsblk
mount
df -h
cat /proc/mounts
iostat -xz 1

Host:

virsh domblklist <VM_NAME>
qemu-img info <disk-image>
lsblk
cat /sys/block/<device>/queue/scheduler
iostat -xz 1
iotop

170. PostgreSQL 예시: WAL과 Durability

예를 들어:

BEGIN;

UPDATE users
SET balance = 1000
WHERE id = 1;

COMMIT;

을 생각한다.

PostgreSQL은 WAL 등의 durability protocol을 사용하며 필요한 시점에 storage synchronization을 수행한다.

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가 커질 수 있다.

WRITE
 ↓
Storage까지 동기화
 ↓
completion 대기

특히 DB workload에서는 fsync() latency가 transaction latency와 연결될 수 있다.

더 적극적인 caching
        ↓
write latency 개선 가능

하지만

durability semantics를 반드시 보존해야 함

fsync()를 없애서 빨라졌다면 그것이 최적화가 아니라 durability contract를 제거한 것일 수 있다.


172. Storage Virtualization Canonical Flow

                    [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:

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는 같은 의미가 아니다.

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:

lsblk

Host:

virsh domblklist <VM_NAME>

OQ-2. Backend는 qcow2인가 RAW인가?

qemu-img info /path/to/disk-image

OQ-3. qcow2 Virtual Size와 실제 Host 사용량은 얼마나 다른가?

qemu-img info <image>
du -h <image>
ls -lh <image>

세 명령이 보여주는 의미가 서로 다를 수 있으므로 비교한다.

OQ-4. QEMU disk cache mode는 무엇인가?

virsh dumpxml <VM_NAME>

disk driver 설정의 cache 관련 값을 확인한다.

OQ-5. qcow2가 최종적으로 어느 Host block device 위에 있는가?

lsblk
findmnt

OQ-6. Host I/O Scheduler는 무엇인가?

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. 권장 실습 흐름

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를 직접 다루지 않는다.

Application
 ↓
Guest VFS
 ↓
Guest Filesystem
 ↓
Guest Page Cache
 ↓
Guest Block Layer
 ↓
virtio-blk
 ↓
virtqueue

VM 경계를 넘으면:

QEMU
 ↓
qcow2 / RAW / Host Block Device
 ↓
Host Storage Stack
 ↓
Physical SSD/NVMe

로 이어진다.

이 경로에는 여러 cache, queue, scheduling 지점이 존재한다.

특히 DB workload에서는 다음을 항상 구분해야 한다.

write 완료
    ≠
writeback 완료
    ≠
flush 완료
    ≠
전원 장애에도 살아남는 durability

Storage 문제를 분석할 때 CPU usage만 보지 말고 다음을 함께 본다.

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다.