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