시작하며
Kubernetes 위에서 VM을 실행할 수 있다는 KubeVirt 프로젝트를 처음 접했을 때, 호기심과 의구심이 동시에 들었습니다. 컨테이너 오케스트레이터 위에서 가상 머신을 돌린다는 것이 과연 실용적일지? 그리고 현대적인 클라우드 네이티브 환경에서 어떤 가치를 줄 수 있을지 궁금했습니다.
특히 VMware의 라이선스 정책 변화 등으로 인해 많은 조직이 KubeVirt와 같은 오픈소스 가상화 기술에 관심을 두고 있습니다. 저는 이 호기심을 바탕으로 Kubernetes 위에서 Windows Server 2022를 띄우고 RDP로 접속하는 것을 최종 목표로 삼아 간단하게 실험을 진행했습니다.
결론부터 성공했지만 그 과정은 결코 매끄럽지 않았습니다. 스토리지, 드라이버, 네트워크 등 여러 계층에서 발생하는 기술적 문제들을 해결해야 했으며, 특히 가상화 환경에서의 성능 최적화라는 중요한 과제를 마주했습니다.
이번 글에서는 제가 겪은 시행착오와 그 해결책을 기록으로 남기고자 합니다.
KubeVirt
VM을 Pod로 만드는 기술
KubeVirt는 Kubernetes 클러스터 위에서 가상 머신(VM)을 실행하고 관리할 수 있게 해주는 오픈소스 프로젝트입니다.
핵심 아이디어는 VM을 하나의 Pod처럼 다루는 것입니다. KubeVirt는 VM을 Kubernetes의 Pod로 래핑(Wrapping)합니다. 이를 통해 개발자와 운영자는 kubectl 명령어를 사용하거나, Kubernetes의 스케줄링, 네트워킹, 스토리지 클래스(PVC/PV), RBAC 등 기존의 컨테이너 관리 방식과 동일한 메커니즘으로 VM을 제어하고 운영할 수 있습니다.
왜 Kubernetes 위에서 VM을 돌려야 하는가?
컨테이너 기술이 비약적으로 발전했지만, 여전히 컨테이너화하기 어려운 워크로드가 존재하기 때문입니다. 예를 들어, Windows 기반 애플리케이션이나 특정 레거시 시스템 등은 컨테이너로 완전히 전환하기 어려울 수 있습니다. KubeVirt는 이러한 워크로드를 Kubernetes 환경에서 통합적으로 관리할 수 있게 해줍니다.
환경 구성
실험했던 환경은 다음과 같습니다.
- Kubernetes: v1.34.1 (멀티노드 클러스터)
- KubeVirt: v1.8.0
- StorageClass:
no-provisioner(로컬 경로 기반) - Workload: Windows Server 2022 Standard Evaluation
KubeVirt 설치는 virt-operator를 통해 구성되었으며, virtctl을 사용하여 VM을 관리합니다.
첫 번째 충돌: 스토리지와 CDI의 충돌
KubeVirt에서 ISO 이미지를 관리하기 위해 CDI(Containerized Data Importer)를 사용하려 했으나, 클러스터의 no-provisioner 스토리지 환경에서 문제가 발생했습니다.
CDI는 보통 동적 프로비저닝(Dynamic Provisioning)을 전제로 동작합니다. 하지만 no-provisioner 환경에서는 PVC를 생성해도 자동으로 PV(Persistent Volume)가 생성되지 않습니다. virtctl image-upload를 시도하면 다음과 같은 오류를 마주하게 됩니다.
cannot upload to DataVolume in requested phase, make sure the PVC is Bound, or use force-bind flag
해결: 수동 PV 생성과 claimRef 활용
이 문제를 해결하기 위해 동적 프로비저닝을 포기하고, 수동으로 PV를 생성하는 방식을 택했습니다. 수동으로 으로 생성할 때는 claimRef를 사용하여 특정 PV가 특정 PVC에만 바인딩되도록 고정하지 않으면 클러스터 내의 다른 PVC가 먼저 PV를 가로챌 수 있으니 주의해야 합니다.
두 번째 충돌: 부팅을 위한 ‘최소 사양’ 전략
Windows는 설치 초기 단계에서 VirtIO 드라이버를 가지고 있지 않습니다. 따라서 초기 설치 시에는 VirtIO 디스크나 NIC를 인식하지 못합니다.
아무 것도 인식하지 못함
전략: SATA와 e1000e로 우회하기
이를 해결하기 위해 VM의 디스크 버스를 sata로, 네트워크 인터페이스를 e1000e 모델로 설정했습니다. 이는 Windows가 추가 드라이버 없이도 기본적으로 인식할 수 있는 하드웨어 에뮬레이션 방식입니다.
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
name: windows-vm
namespace: default
spec:
running: false
template:
metadata:
labels:
kubevirt.io/vm: windows-vm
spec:
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
domain:
cpu:
cores: 4
resources:
requests:
memory: 8Gi
devices:
disks:
- name: windows-disk
disk:
bus: sata # virtio 대신 sata로 설정
bootOrder: 1
interfaces:
- name: default
masquerade: {}
model: e1000e # virtio 대신 e1000e로 설정
위와 같이 설정하면 Windows 설치 과정에서 디스크와 네트워크가 정상적으로 인식되어 설치를 진행할 수 있습니다.
정상적으로 인식
Windows VM에 RDP로 접근할 계획이라면 반드시 Desktop Experience가 포함된 에디션을 선택해야 합니다. 그렇지 않으면 GUI가 없는 Server Core 버전이 설치되어 RDP 접속 시 당황스러운 상황이 발생할 수 있습니다.
Non-Desktop Experience으로 설치하면 나오는 화면
세 번째 충돌: 드라이버 전환과 BSOD (더미 디스크 트릭)
SATA와 e1000e로 설치는 완료했지만 엄청난 성능 문제가 발생했습니다. 사실 설치 과정에서도 느꼈지만 실제 운영 환경에서는 도저히 사용할 수 없는 수준이었습니다.
할당된 자원은 4코어 CPU와 8GB RAM이었지만, I/O 지연으로 인해 간단한 명령어 입력조차도 수 초 이상 걸리는 상황이었습니다.
따라서 설치가 완료된 후, 성능 최적화를 위해 VirtIO 드라이버로 전환하기로 했습니다.
드라이버 설치
드라이버 설치 완료 후, 성능을 위해 VirtIO 드라이버 설치 후 디스크 버스를 sata에서 virtio로 변경하려고 시도했는데, Spec 변경 후 재부팅하니 다음과 같은 블루스크린(BSOD)이 떴습니다.
BSOD — Stop code: INACCESSIBLE_BOOT_DEVICE
이 오류는 Windows가 부팅 과정에서 VirtIO 드라이버를 로드하려고 시도하지만, 아직 해당 컨트롤러를 사용할 준비가 되지 않았을 때 발생합니다.
해결: 더미 디스크(Dummy Disk) 트릭
이 현상은 더미 디스크 트릭를 마운트하여 Windows가 VirtIO 드라이버를 완전히 로드하도록 유도하는 방식으로 해결할 수 있습니다.
- 더미 디스크 추가: 작은 용량의 VirtIO 디스크를 VM에 추가로 장착합니다.
- 부팅 및 드라이버 활성화: 메인 디스크는 여전히
sata로 두어 부팅을 성공시킨 뒤, 추가된 VirtIO 디스크를 통해 Windows가 컨트롤러를 인식하고 드라이버를 레지스트리에 등록하도록 유도합니다. - 메인 디스크 전환: 드라이버가 활성화된 것을 확인한 후, 메인 디스크의 버스를
virtio로 변경합니다.
이 과정을 통해 Windows는 정상적으로 VirtIO 드라이버를 탑재하고 부팅에 성공했습니다.
네 번째 충돌: RDP 접속 문제
Windows 설치가 완료되고 드라이버 전환도 성공했지만, RDP 접속 시 에러가 발생했습니다.
RDP 접속 시 에러
이를 해결하는 방법은 PowerShell을 통해 RDP 설정을 수동으로 조정하는 것입니다. Windowns 가성머신에 VNC로 RDP를 접속하여 활성화하고 NLA를 비활성화하여 원격 접속이 가능하도록 설정했습니다.
# RDP 활성화 및 NLA 비활성화
Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server' `
-Name "fDenyTSConnections" -Value 0
Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' `
-Name "UserAuthentication" -Value 0
성능에 관한 고찰: SATA와 VirtIO 사이의 간극
이 프로젝트를 진행하며 가장 인상적이었던 점은 하드웨어 호환성을 확보하기 위해 선택한 SATA 버스의 극심한 성능 저하였습니다.
초기 설치 단계의 고충
초기 설치를 위해 SATA 버스를 사용해야 하는 환경은 I/O 지연(Latency)이 매우 심각합니다. 텍스트 기반의 CMD 환경에서 명령어를 입력할 때조차 입력 지연(Input Lag)이 체감될 정도로 성능이 낮았습니다. 이는 운영 환경에서는 상상하기 힘든 수준의 답상함이었지만, 한편으로는 이를 골든 이미지(Golden Image) 생성을 위한 일시적인 과정으로 정의할 수 있습니다.
즉, 초기 설치 단계에서 확보한 안정적인 설정을 바탕으로 완벽한 이미지를 생성(Templating)해 둔다면, 이후 프로덕션 환경에서는 이러한 성능 저하를 회피할 수 있습니다.
드라이버 전환 이후의 한계
더 큰 과제는 드라이버를 virtio로 전환한 이후에도 발생하는 성능의 한계라고 느꼈습니다. 드라이버를 성공적으로 이식했음에도 불구하고, 가상화 환경 특유의 오버헤드 때문인지 기대만큼의 처리 속도가 나오지 않았습니다.
할당받은 자원이 부족해서 발생하는 문제인지, 아니면 가상화 레이어에서의 병목 현상인지는 명확하지 않지만, 이는 KubeVirt로 클라우드 네이티브 환경에서 Windows 워크로드를 운용할 때 반드시 고려해야 할 실질적인 제약 사항이 될 수 있습니다.
마무리
이번 KubeVirt를 통한 Windows 구축은 단순한 VM 생성을 넘어, 가상화 레이어와 OS 간의 상호작용을 깊이 이해해야 하는 과정이었습니다.
실사용 시 느낀 점
장점:
- 통합 관리: Kubernetes 환경 내에서 컨테이너와 VM을 동일한 오케스트레이션 체계로 관리할 수 있습니다.
- 유연한 리소스 활용: KubeVirt는 VM을 Pod로 추상화하여 Kubernetes의 스케줄링과 네트워크 기능을 그대로 활용하게 해줍니다.
한계 및 주의사항:
- 초기 설정의 높은 비용: 호환성을 위해 선택한 SATA 방식은 극심한 I/O 지연을 동반합니다. 이는 초기 이미지 빌드 과정에서의 고통을 수반합니다.
- 성능 최적화의 복잡성: 드라이버 전환 후에도 잔존하는 성능 병목 현상은 가상화 환경에서의 실질적인 운영 한계점으로 남습니다.