본 가이드는 Harvester HCI 가상화 핵심 가이드 및 실전 아키텍처의 핵심 개념, 실전 구성 명령어 및 아키텍처 베스트 프랙티스를 다룹니다. 클라우드 네이티브 및 인프라 엔지니어링 실무에 즉시 활용 가능하도록 체계적으로 정리했습니다.
📑 Table of Contents
- 1. Installation
- 2. HCI: Host
- 3. HCI: Creation
- • SSH Keys 관리
- • Upload Image
- • Bootable Volume 생성
- • VM: Windows VDI Server Setup
- • VM: Ubuntu Server
- • Network
- 4. HCI: Management
- • VM: Resizing CPU / Memory
- • VM: Resizing Disk
- • VM: Volume Unmount 및 제거
- 5. Features
- • Standalone용 Storage Class 생성
- • Enable Rancher & Longhorn UI
- • Enable Vim Key Mapping
- • GitOps
- • Rancher Connection
- 6. CLI
- • Generate VM
- • Backup
- 7. Add-ons
- • Observability
Installation
HCI: Host
Hosts → \
Disk 마운트 및 Longhorn Join
- Storage → Add Disk 클릭 → Disk 선택 → 저장
- Longhorn 스케줄이 끝나면 로드됨
HCI: Creation
SSH Keys 관리
- 비밀키는 잘 가지고 있고, 공개키 등록하면 됨
Upload Image
Images → Create
Cloud Image repositories
- Windows
- Windows Server 2022 | Microsoft Evaluation Center
- Windows Server 2025 | Microsoft Evaluation Center
- Virtio-win
- Index of /groups/virt/virtio-win/direct-downloads
- Ubuntu
- ISO: ubuntu-releases
- QCow2: Ubuntu Cloud Images – the official Ubuntu images for public clouds, Openstack, KVM and LXD
- Talos
- Image Factory
- Nocloud – Sidero Documentation
- 시스템 확장
siderolabs/iscsi-toolssiderolabs/qemu-guest-agentsiderolabs/util-linux-tools- 커널 인수
talos.platform=kvm console=ttyS0,115200n8- customization-all
customization:
extraKernelArgs:
- talos.platform=kvm
- console=ttyS0,115200n8
systemExtensions:
officialExtensions:
- siderolabs/iscsi-tools
- siderolabs/qemu-guest-agent
- siderolabs/util-linux-tools
URL을 통한 이미지 업로드
-
.iso/.qcow/.qcow2/.raw/.img이미지 링크 복사 -
Harvester에 삽입 및
Name지정. (자동 라벨링) →Create -
Storage Class 선택
- 참고: Standalone용 Storage Class 생성
-
Download 확인
Bootable Volume 생성
호스트 kubernetes에 PVC로 생성됨 (pre-bounded)
VM: Windows VDI Server Setup
Windows는 VirtIo 타입 볼륨 드라이버가 기본이미지에 없으므로, virtio-win 이미지를 같이 마운트해준다. (Virtio-win)
Windows Server 2025 Installation
-
Basics
-
Volumes
-
WebVNC 접근
-
적당히 Windows 설정 하다가 에디션 선택창에서 Desktop Experience를 선택. (GUI)
- 이후
Location to Install Windows Server화면 등장함 -
Load Driver클릭 -
virtio-windisk → amd64 → 최신 드라이브 선택 -
Red Hat VirtIO SCSI controller선택 후Install -
이제 잘 조회되는 VirtIO 디스크에 Install
Network Setup
Get-PSDrive -PSProvider FileSystem #확인된 virtio drive=E:
pnputil /add-driver "E:\NetKVM\2k25\amd64\*.inf" /install
# 인터넷 연결및 ip할당 확인
ipconfig
ping 8.8.8.8
All-in-one Virtio Drivers Setup
virtio-win-gt-x64.msi설치
장치관리자에서 확인
Enable RDP
시작버튼 클릭 >설정(Settings)>시스템(System)> **원격 데스크톱(Remote Desktop)**으로 이동합니다.- ‘켬(On)’으로 토글을 바꿉니다.
서비스 노출 / 접근
RDP 기본 포트: 3389
기본 접근계정: Administrator
- LoadBalancer
부팅디스크 및 드라이버 디스크 제거
- VM: Volume Unmount 및 제거
VM: Ubuntu Server
Configuration
서비스 노출 / 접근
SSH 기본 포트: 22
기본 접근계정: ubuntu
디스크 마운트
# lvm없이 ext4
sudo mkfs.ext4 /dev/vdb
sudo mkdir /workspace
sudo mount /dev/vdb /workspace
Network
Cluster Network
Harvester Network Options 이해
- Cluster Network – Masquerade (L3)
-
마스커레이드 모드는 VM을 외부 네트워크에 노출하지 않기 때문에 외부에서 서비스 접근을 위해서는
LoadBalancer설정으로 NAT를 구성해야 함 -
Cluster Network – Bridge (L2)
- 브릿지 모드는 VM을 업스트림 라우터에 노출하여 DHCP를 할당받을 수 있음.
VM Network 생성
Harvester Network Options 이해
- VM Network – Bridge (L2)
- 브릿지 모드는 VM을 업스트림 라우터에 노출하여 DHCP를 할당받을 수 있음.
| Harvester 용어 | 표준 네트워크 용어 | 핵심 메커니즘 |
| Untagged | Standard L2 LAN | 패킷에 아무런 꼬리표 없이 물리 스위치(Access Port)와 직접 통신 |
| L2VLAN | 802.1Q VLAN | 패킷 헤더에 VLAN ID(Tag)를 붙여 물리 스위치(Trunk Port)와 통신 |
| Overlay | VXLAN | L2 패킷을 UDP(L3/L4) 패킷으로 감싸서(Encapsulation) 전송 |
- untagged: 아주 단순함
LoadBalancer
– DHCP: Host-dependent
– VM NetworksIP Pools: LoadBalancer ← IP Pools ← VM Networks (VLAN or Else)
-
서비스 포트 포워딩
-
대상 VM 라벨셀렉트
HCI: Management
VM: Resizing CPU / Memory
- edit config
VM: Resizing Disk
LVM구성된 경우에는 디스크를 새로 추가하여 논리적으로 결합하는게 좋음
VM: Volume Unmount 및 제거
-
Edit Config
-
Drive 이미지나 설치 이미지 제거
Features
Standalone용 Storage Class 생성
Harvester의 기본 Longhorn 구성은Replicas=3 으로 되어있으나, 싱글노드에서는 레플리카 스케쥴이 불가하므로 VM생성시 Volume degreaded가 발생함.
-
현재 Degreaded인 볼륨 즉시 복구
1. Longhorn Dashboard 접근
2. Volume의 설정에서 Update Replicas Count:1로 변경 -
기존
harvester-longhorn등의 SC는 수정불가이므로 신규 SC를 생성 -
Number of Replica =
1 -
set Default
Enable Rancher & Longhorn UI
디버깅 및 유효성 검사 목적으로만 내장된 Rancher 및 Longhorn 대시보드 사용을 지원
- Preference > Advanced Features >
Enable Extension developer features
- Support
Enable Vim Key Mapping
- Preference > YAML Editor Setting >
Vim
GitOps
Rancher Connection
-
Rancher
-
Harvester
Solution
상세 내용 펼치기
Get "https://rancher.mgnt.fredric18.uk/v3/import/6v5bg947vq7zm2tb8wjxbbxp9tgq8w6rcx7bskdd6hw4h2lt4r4gcp_c-dm6zk.yaml": context deadline exceeded (Client.Timeout exceeded while awaiting headers)
– In Harvester CLI DNS Setting
# Unbound 동작 확인
rancher@fredric18:~> nslookup rancher.mgnt.fredric18.uk 192.168.1.1
Server: 192.168.1.1
Address: 192.168.1.1#53
Name: rancher.mgnt.fredric18.uk
Address: 192.168.118.13
# CoreDNS에 Unbound 적용
k exec -it -n harvester-system deployments/harvester --container apiserver -- curl -kv https://rancher.mgnt.fredric18.uk/v3/import/6v5bg947vq7zm2tb8wjxbbxp9tgq8w6rcx7bskdd6hw4h2lt4r4gcp_c-dm6zk.yaml
>>>
* Could not resolve host: rancher.mgnt.fredric18.uk
* shutting down connection #0
curl: (6) Could not resolve host: rancher.mgnt.fredric18.uk
command terminated with exit code 6
<<<
k get cm -n kube-system -l k8s-app=kube-dns
k edit cm -n kube-system rke2-coredns-rke2-coredns
>>>edit
forward . 192.168.1.1 8.8.8.8
<<<
k delete pods -n kube-system -l k8s-app=kube-dns
k exec -it -n harvester-system deployments/harvester --container apiserver -- curl -kv https://rancher.mgnt.fredric18.uk/v3/import/6v5bg947vq7zm2tb8wjxbbxp9tgq8w6rcx7bskdd6hw4h2lt4r4gcp_c-dm6zk.yaml
>>>
* Host rancher.mgnt.fredric18.uk:443 was resolved.
* IPv6: (none)
* IPv4: 192.168.118.13
* Trying 192.168.118.13:443...
<<<
CLI
Generate VM
Masquerade 모드
SNAT vs Masquerade
- SNAT (Source Network Address Translation): 출발지 IP를 변경하는 모든 기술을 통칭하는 상위 개념입니다. 관리자가 변환할 목적지 IP를 명시적으로 지정하여 사용하는 것이 일반적입니다.
- Masquerade: SNAT의 동적(Dynamic) 버전입니다. 관리자가 일일이 IP를 지정하지 않아도, 패킷이 나가는 네트워크 인터페이스의 IP를 시스템이 자동으로 감지(Lookup)하여 출발지 IP로 적용합니다.
VMSet + LB for SSH
apiVersion: loadbalancer.harvesterhci.io/v1beta1
kind: LoadBalancer
metadata:
annotations:
loadbalancer.harvesterhci.io/namespace: harvester-public
loadbalancer.harvesterhci.io/network: ""
loadbalancer.harvesterhci.io/project: ""
finalizers:
- wrangler.cattle.io/harvester-lb-controller
name: cks-inkang-01
namespace: harvester-public
spec:
backendServerSelector:
harvesterhci.io/vmName:
- cks-inkang-01
healthCheck: {}
ipam: dhcp
listeners:
- backendPort: 22
name: ssh
port: 22
protocol: TCP
workloadType: vm
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
annotations:
volume.beta.kubernetes.io/storage-provisioner: driver.longhorn.io
volume.kubernetes.io/storage-provisioner: driver.longhorn.io
finalizers:
- kubernetes.io/pvc-protection
- wrangler.cattle.io/persistentvolumeclaim-controller
name: cks-inkang-01-root
namespace: harvester-public
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 100Gi
storageClassName: harvester-longhorn-standalone
volumeMode: Block
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
annotations:
harvesterhci.io/imageId: harvester-public/image-jb4b8
volume.beta.kubernetes.io/storage-provisioner: driver.longhorn.io
volume.kubernetes.io/storage-provisioner: driver.longhorn.io
finalizers:
- kubernetes.io/pvc-protection
- wrangler.cattle.io/persistentvolumeclaim-controller
name: cks-inkang-01-bootable
namespace: harvester-public
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
storageClassName: longhorn-image-jb4b8
volumeMode: Block
---
apiVersion: v1
data:
networkdata: ""
userdata: I2Nsb3VkLWNvbmZpZwpwYWNrYWdlX3VwZGF0ZTogdHJ1ZQpwYWNrYWdlczoKICAtIHFlbXUtZ3Vlc3QtYWdlbnQKcnVuY21kOgogIC0gLSBzeXN0ZW1jdGwKICAgIC0gZW5hYmxlCiAgICAtIC0tbm93CiAgICAtIHFlbXUtZ3Vlc3QtYWdlbnQuc2VydmljZQpzc2hfYXV0aG9yaXplZF9rZXlzOgogIC0gc3NoLWVkMjU1MTkKICAgIEFBQUFDM056YUMxbFpESTFOVEU1QUFBQUlLSkxpb0tESFZqRUtnTFBwbEpkdVh4YzlHR0xaa0RtaVUwNDlRRFFEYTd1CiAgICBhZG1pbkBtYWlsLmZyZWRyaWMxOC51awoKcGFzc3dvcmQ6ICJpbmthbmcxMjMjIgpjaHBhc3N3ZDoKICBsaXN0OiB8CiAgICByb290Omlua2FuZzEyMyMKICAgIHVidW50dTppbmthbmcxMjMjCiAgZXhwaXJlOiBGYWxzZQpzc2hfcHdhdXRoOiBUcnVlCg==
kind: Secret
metadata:
labels:
harvesterhci.io/cloud-init-template: harvester
name: cks-inkang-01
namespace: harvester-public
type: secret
---
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
annotations:
harvesterhci.io/vmRunStrategy: RerunOnFailure
kubevirt.io/latest-observed-api-version: v1
kubevirt.io/storage-observed-api-version: v1
network.harvesterhci.io/ips: '[]'
template-version.harvesterhci.io/customName: QK9h2nZlKw
creationTimestamp: "2026-04-17T06:03:53Z"
finalizers:
- kubevirt.io/virtualMachineControllerFinalize
- wrangler.cattle.io/VMController.CleanupPVCAndSnapshot
generation: 1
labels:
harvesterhci.io/creator: harvester
harvesterhci.io/os: ubuntu
template.harvesterhci.io/templateID: ubuntu-tiny
name: cks-inkang-01
namespace: harvester-public
spec:
runStrategy: RerunOnFailure
template:
metadata:
annotations:
harvesterhci.io/sshNames: '["harvester-public/admin"]'
creationTimestamp: null
labels:
harvesterhci.io/vmName: cks-inkang-01
harvesterhci.io/vmNamePrefix: cks-inkang
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- podAffinityTerm:
labelSelector:
matchLabels:
harvesterhci.io/vmNamePrefix: cks-inkang
topologyKey: kubernetes.io/hostname
weight: 1
architecture: amd64
domain:
cpu:
cores: 1
maxSockets: 1
sockets: 1
threads: 1
devices:
disks:
- bootOrder: 1
disk:
bus: virtio
name: bootable
- bootOrder: 2
disk:
bus: virtio
name: root
- disk:
bus: virtio
name: cloudinitdisk
inputs:
- bus: usb
name: tablet
type: tablet
interfaces:
- masquerade: {}
model: virtio
name: default
features:
acpi:
enabled: true
machine:
type: q35
memory:
guest: 4Gi
resources:
limits:
cpu: "1"
memory: 2Gi
requests:
cpu: 125m
memory: 2730Mi
evictionStrategy: LiveMigrateIfPossible
networks:
- name: default
pod: {}
terminationGracePeriodSeconds: 120
volumes:
- name: bootable
persistentVolumeClaim:
claimName: cks-inkang-01-bootable
- name: root
persistentVolumeClaim:
claimName: cks-inkang-01-root
- cloudInitNoCloud:
networkDataSecretRef:
name: cks-inkang-01
secretRef:
name: cks-inkang-01
name: cloudinitdisk
Bridge 모드
Bridge는 Harvester에서 VM IP를 관리하지 않고, 업스트림의 DHCP서버에 할당을 요청하므로 Harvester와 동일레벨의 IP를 할당하는것이 가능하다.
일반적으로 vLAN 인터페이스를 만들어 NetworkAttachmentDefinition 를 선언한다.
Upstream DHCP vLAN Interface
Bridge Network 구성 (NetworkAttachmentDefinition)
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: vlan1
namespace: harvester-public
annotations:
network.harvesterhci.io/clusternetwork: mgmt
spec:
config: '{
"cniVersion": "0.3.1",
"name": "vlan1",
"type": "bridge",
"bridge": "mgmt-br",
"vlan": 30
}'
– clusterNetwork (static)
apiVersion: v1
items:
- apiVersion: network.harvesterhci.io/v1beta1
kind: ClusterNetwork
metadata:
creationTimestamp: "2025-12-07T14:48:39Z"
finalizers:
- wrangler.cattle.io/harvester-network-manager-cn-controller
generation: 1
name: mgmt
resourceVersion: "4969"
uid: 1c602bce-a7c2-4fa5-8a28-95d17af5ca25
status:
conditions:
- lastUpdateTime: "2025-12-07T14:48:39Z"
status: "True"
type: ready
kind: List
metadata:
resourceVersion: ""
VMSet
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
annotations:
volume.beta.kubernetes.io/storage-provisioner: driver.longhorn.io
volume.kubernetes.io/storage-provisioner: driver.longhorn.io
finalizers:
- kubernetes.io/pvc-protection
- wrangler.cattle.io/persistentvolumeclaim-controller
name: cks-inkang-03-root
namespace: harvester-public
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 100Gi
storageClassName: harvester-longhorn-standalone
volumeMode: Block
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
annotations:
harvesterhci.io/imageId: harvester-public/image-jb4b8
volume.beta.kubernetes.io/storage-provisioner: driver.longhorn.io
volume.kubernetes.io/storage-provisioner: driver.longhorn.io
finalizers:
- kubernetes.io/pvc-protection
- wrangler.cattle.io/persistentvolumeclaim-controller
name: cks-inkang-03-bootable
namespace: harvester-public
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
storageClassName: longhorn-image-jb4b8
volumeMode: Block
---
apiVersion: v1
data:
networkdata: ""
userdata: I2Nsb3VkLWNvbmZpZwpwYWNrYWdlX3VwZGF0ZTogdHJ1ZQpwYWNrYWdlczoKICAtIHFlbXUtZ3Vlc3QtYWdlbnQKcnVuY21kOgogIC0gLSBzeXN0ZW1jdGwKICAgIC0gZW5hYmxlCiAgICAtIC0tbm93CiAgICAtIHFlbXUtZ3Vlc3QtYWdlbnQuc2VydmljZQpzc2hfYXV0aG9yaXplZF9rZXlzOgogIC0gc3NoLWVkMjU1MTkKICAgIEFBQUFDM056YUMxbFpESTFOVEU1QUFBQUlLSkxpb0tESFZqRUtnTFBwbEpkdVh4YzlHR0xaa0RtaVUwNDlRRFFEYTd1CiAgICBhZG1pbkBtYWlsLmZyZWRyaWMxOC51awoKcGFzc3dvcmQ6ICJpbmthbmcxMjMjIgpjaHBhc3N3ZDoKICBsaXN0OiB8CiAgICByb290Omlua2FuZzEyMyMKICAgIHVidW50dTppbmthbmcxMjMjCiAgZXhwaXJlOiBGYWxzZQpzc2hfcHdhdXRoOiBUcnVlCg==
kind: Secret
metadata:
labels:
harvesterhci.io/cloud-init-template: harvester
name: cks-inkang-03
namespace: harvester-public
type: secret
---
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
annotations:
harvesterhci.io/vmRunStrategy: RerunOnFailure
kubevirt.io/latest-observed-api-version: v1
kubevirt.io/storage-observed-api-version: v1
network.harvesterhci.io/ips: '[]'
template-version.harvesterhci.io/customName: QK9h2nZlKw
creationTimestamp: "2026-04-17T06:03:53Z"
finalizers:
- kubevirt.io/virtualMachineControllerFinalize
- wrangler.cattle.io/VMController.CleanupPVCAndSnapshot
generation: 1
labels:
harvesterhci.io/creator: harvester
harvesterhci.io/os: ubuntu
template.harvesterhci.io/templateID: ubuntu-tiny
name: cks-inkang-03
namespace: harvester-public
spec:
runStrategy: RerunOnFailure
template:
metadata:
annotations:
harvesterhci.io/sshNames: '["harvester-public/admin"]'
creationTimestamp: null
labels:
harvesterhci.io/vmName: cks-inkang-03
harvesterhci.io/vmNamePrefix: cks-inkang
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- podAffinityTerm:
labelSelector:
matchLabels:
harvesterhci.io/vmNamePrefix: cks-inkang
topologyKey: kubernetes.io/hostname
weight: 1
architecture: amd64
domain:
cpu:
cores: 1
maxSockets: 1
sockets: 1
threads: 1
devices:
disks:
- bootOrder: 1
disk:
bus: virtio
name: bootable
- bootOrder: 2
disk:
bus: virtio
name: root
- disk:
bus: virtio
name: cloudinitdisk
inputs:
- bus: usb
name: tablet
type: tablet
interfaces:
- bridge: {}
model: virtio
name: default
features:
acpi:
enabled: true
machine:
type: q35
memory:
guest: 4Gi
resources:
limits:
cpu: "1"
memory: 2Gi
requests:
cpu: 125m
memory: 2730Mi
evictionStrategy: LiveMigrateIfPossible
networks:
- name: default
multus:
networkName: harvester-public/vlan1
terminationGracePeriodSeconds: 120
volumes:
- name: bootable
persistentVolumeClaim:
claimName: cks-inkang-03-bootable
- name: root
persistentVolumeClaim:
claimName: cks-inkang-03-root
- cloudInitNoCloud:
networkDataSecretRef:
name: cks-inkang-03
secretRef:
name: cks-inkang-03
name: cloudinitdisk
VM Diffs with Masquerade VS Bridge
# ... (앞부분 생략)
spec:
# ...
networks:
- name: default
# pod: {} <-- 이 부분을 지우고 아래 multus를 추가합니다.
multus:
networkName: harvester-public/vlan1 # <-- 실제 사용하려는 네트워크 이름 (Namespace/Name)
domain:
# ...
devices:
interfaces:
- bridge: {} # <-- masquerade에서 bridge로 변경
model: virtio
name: default
# ... (뒷부분 생략)
Backup
# vm 목록
k get pods -n harvester-public
# virsh vm listing
k exec -it virt-launcher-management-00-kh82s -n harvester-public -- virsh list --all
# dump
k exec -it virt-launcher-management-00-kh82s -n harvester-public -- virsh dumpxml harvester-public_management-00 > harvester-public_management-00.xml
# 디스크 경로 확인
k exec -it virt-launcher-management-00-kh82s -n harvester-public -- virsh domblklist
Add-ons
Advanced → Add-ons
Observability
Logging
Monitoring
-
Prometheus UI로 이동
-
Grafana 대시보드로 이동
-
로그인 없이 조회하는 목적이나, 혹시 개발이 필요한 경우 초기비밀번호 확인
# Grafana admin 패스워드 확인
kubectl get secret -n cattle-monitoring-system rancher-monitoring-grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echo
- Change Password로 Harvester와 동일하게 변경
Leave a Reply