1. 개요
쿠버네티스(Kubernetes) 오프라인 설치 테스트를 위해서는 외부 네트워크가 완전히 단절된 환경이 필요했다. 물론 별도 서버에 KVM이나 VirtualBox로 VM을 구성하고 네트워크를 단절시키는 방법도 있지만, 구성이 번거롭고 쿠버네티스 환경에 익숙해진 만큼 가급적 클러스터 내에서 모든 것을 해결하고 싶었다.
그러던 중 KubeVirt를 이용하면 쿠버네티스 클러스터에서 노드의 자원으로 VM을 생성하고 관리할 수 있다는 것을 발견했다. 또한, CiliumNetworkPolicy와 연계하면 오프라인 환경을 쉽게 구성할 수 있다고 판단했다.
2. KubeVirt란?
KubeVirt는 쿠버네티스에서 가상 머신(VM)을 관리할 수 있게 해주는 오픈소스 프로젝트다. 쿠버네티스의 기능을 확장하여, 기존의 VM 기반 워크로드를 컨테이너 기반 애플리케이션과 동일한 플랫폼에서 실행하고 관리할 수 있도록 지원한다.
KubeVirt는 사용자 정의 리소스 정의(CRD, Custom Resource Definitions)를 통해 쿠버네티스 API를 확장하는 방식으로 동작한다. 이를 통해 쿠버네티스는 파드(Pod)와 같은 네이티브 리소스처럼 VM 관련 객체를 이해하고 관리할 수 있게 된다. KubeVirt를 사용해 VM을 생성하면, VM은 사실상 KVM(Kernel-based Virtual Machine) 인스턴스를 포함하는 특별한 종류의 파드 내부에서 실행된다.
3. 사전 준비
VM을 생성하기 위해서는 먼저 하드웨어의 가상화 기능을 활성화해야 했다. 서버의 EFI(또는 BIOS) 설정에 진입하여 가상화 옵션(Intel VT-x, AMD-V 등)을 Enable로 변경했다. 이 옵션의 명칭은 CPU 및 메인보드 제조사에 따라 조금씩 다를 수 있다.
4. 설치 과정
KubeVirt를 클러스터에 설치하기 위해 필요한 컴포넌트는 다음과 같다.
- kubevirt-operator
- cdi-cr
- cdi-operator
- kubevirt-manager (선택사항)
모든 컴포넌트를 설치한 후, VM을 제어하기 위해 클라이언트 OS에 virtctl
CLI 도구를 추가로 설치해주었다.
5. 기본 이미지 생성
VM 디스크를 구성하는 몇 가지 방법 중, 재사용성을 고려하여 기반이 되는 DataVolume을 미리 구성하는 방식을 선택했다. 그리고 dataVolumeTemplates
를 정의하여 각 VM 인스턴스를 생성할 때마다 이 기반 DataVolume을 복제해서 사용하도록 했다.
아래는 NFS-CSI를 위한 StorageProfile
과 Rocky Linux, Ubuntu 클라우드 이미지를 기반으로 DataVolume을 생성하는 매니페스트다.
apiVersion: cdi.kubevirt.io/v1beta1
kind: StorageProfile
metadata:
name: nfs-csi
spec:
claimPropertySets:
- accessModes:
- ReadWriteMany
volumeMode: Filesystem
---
apiVersion: cdi.kubevirt.io/v1beta1
kind: DataVolume
metadata:
name: base-rocky
spec:
source:
http:
url: https://dl.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud-Base.latest.x86_64.qcow2
contentType: kubevirt
storage:
resources:
requests:
storage: 30Gi
---
apiVersion: cdi.kubevirt.io/v1beta1
kind: DataVolume
metadata:
name: base-ubuntu-2404
spec:
source:
http:
url: https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img
contentType: kubevirt
storage:
resources:
requests:
storage: 30Gi
6. VM 생성
기본 이미지가 준비된 후, VirtualMachine
리소스를 정의하여 VM을 생성했다. dataVolumeTemplates
를 사용해 앞서 만든 base-rocky
이미지를 복제하여 master1-rootdisk
라는 이름의 새 볼륨을 만들도록 설정했다.
NOTE
VM에
netpolicy: internal-egress-only
라벨을 추가했다. 이 라벨은 이후 네트워크 정책을 통해 외부 통신을 차단하는 데 사용된다.
아래는 VM 생성 매니페스트 예시다.
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
name: master1
spec:
runStrategy: Manual
dataVolumeTemplates:
- metadata:
name: master1-rootdisk
spec:
source:
pvc:
name: base-rocky
namespace: default
storage:
resources:
requests:
storage: 100Gi
template:
metadata:
labels:
kubevirt.io/domain: master1
# 외부 네트워크를 단절시키기 위한 라벨
netpolicy: internal-egress-only
spec:
domain:
devices:
disks:
- name: rootdisk
disk:
bus: virtio
- name: cloudinitdisk
disk:
bus: virtio
interfaces:
- name: default
bridge: {}
resources:
requests:
cpu: 2
memory: 4Gi
limits:
cpu: 2
memory: 4Gi
networks:
- name: default
pod: {}
volumes:
- name: rootdisk
dataVolume:
name: master1-rootdisk
- name: cloudinitdisk
cloudInitNoCloud:
userData: |
#cloud-config
users:
- name: user
groups: wheel
shell: /bin/bash
sudo: ALL=(ALL) NOPASSWD:ALL
chpasswd: { expire: False }
# openssl passwd -6 test
passwd: $6$vwoaX/.Bf7ZCxtzm$4Jkrpv3dEaDT8WoRmQ3tNi4.u7NfvU8TaEzMZsKVyNGotDTTnQloWQ72Qxu8qrJF1MqKd67g2baV9tQUpeIfV0
lock_passwd: false
ssh_pwauth: True
작성한 매니페스트를 클러스터에 적용한 뒤, virtctl start master1
명령어로 VM을 시작할 수 있다.
7. 네트워크 격리
CiliumNetworkPolicy를 활용하여 VM의 외부 네트워크 통신을 차단하고, 오프라인 환경을 구성했다. endpointSelector
를 통해 netpolicy: internal-egress-only
라벨이 붙은 파드(VM)를 타겟으로 지정했다.
그 후, egress
규칙을 설정하여 클러스터 내부 대역(10.0.0.0/8
)과 kube-apiserver
로의 통신만 허용하고, egressDeny
규칙으로 그 외의 모든 외부 나가는 통신(0.0.0.0/0
)을 차단했다.
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
name: internal-egress-only
namespace: default
spec:
endpointSelector:
matchLabels:
netpolicy: internal-egress-only
egress:
- toCIDR:
- 10.0.0.0/8 # 클러스터 내부 IP 대역
- toEntities:
- kube-apiserver
- cluster
egressDeny:
- toCIDR:
- 0.0.0.0/0
8. 회고
이번 쿠버네티스 클러스터 구축 프로젝트에서 KubeVirt가 핵심 과업은 아니었기에, 더 깊게 탐구해보지 못한 몇 가지 아쉬운 점이 남았다. 이 사항들은 추후 여유가 될 때 다시 시도해 볼 계획이다.
- GPU 할당 실패: 노드의 그래픽카드를 VM에 할당하고 싶었지만, 관련 지식 부족과 시간 제약으로 끝내 설정하지 못했다. 작업 시간이 조금 더 충분했다면 하는 아쉬움이 남는다.
- 고정 IP 할당: VM의 IP 주소를 고정하기 위해 인터페이스를 설정하는 데 실패했다. Multus CNI를 활용하면 가능하다는 정보를 접했지만, 설정이 예상보다 복잡하여 이번에는 적용하지 못했다.