0. 지금까지, 그리고 5부에서 할 일
4부까지 오면서 노트북은 빈 Proxmox 서버 한 대가 됐다. 전원을 넣고 뚜껑을 닫아도 안 잠들고, 외장 SSD로 부팅되고, 데스크탑 브라우저에서 https://192.168.0.200:8006으로 관리된다. 그런데 정작 이 서버 위에는 아직 아무것도 안 올라가 있다.
5부에서 할 일은 하나다. 이 물리 노트북 한 대 위에 VM(가상머신) 세 대를 세우는 것. 이 세 대가 다음 편(6부)에서 k3s 클러스터의 노드 셋이 된다.
순서는 이렇게 잡았다. 먼저 왜 하필 VM이고 왜 하필 세 대인지 정리하고(§1), VM을 올리기 전에 Proxmox가 이미 깔아둔 토대를 들여다본 뒤(§2), 실제로 ISO를 올리고 VM을 만든다(§3–§5).
1. 왜 VM을, 왜 세 대인가
목표는 k3s를 HA(고가용성)로 띄우는 것이다. 노드 하나가 죽어도 클러스터 전체는 살아 있어야 한다. 그래야 "서버 한 대짜리 장난감"이 아니라 실제 운영에 가까운 구조가 된다.
그러려면 노드가 최소 세 대는 있어야 한다. k3s는 클러스터의 모든 상태(어떤 노드가 있고, 뭐가 어디서 돌고 있나)를 etcd라는 저장소에 두는데, 이 etcd가 과반수 합의로 동작하기 때문이다. 세 대 중 두 대만 살아 있으면 과반이라 계속 돌아가고, 한 대가 죽어도 버틴다. 두 대뿐이라면 한 대가 죽는 순간 과반이 깨져서 멈춘다. 그래서 홀수, 최소 셋이다.
문제는 내 물리 노트북이 한 대뿐이라는 것이다. 그래서 이 한 대 위에 VM 세 대를 올린다. 물리 한 대를 논리 세 대로 쪼개는 셈인데, 이게 1부에서 다룬 Proxmox(Type 1 하이퍼바이저)가 하는 일이다. 하드웨어(CPU·RAM·디스크)를 여러 VM에 나눠준다.
VM 하나가 곧 k3s 노드 하나다. 세 대 모두 server(control-plane 겸 worker) 역할을 맡는다. 규모가 작아서 워커 전용 노드를 따로 두지 않고, 세 대가 클러스터 관리(control-plane)와 실제 워크로드 실행을 같이 맡는다.
각 VM에 줄 몫은 이렇게 잡았다. 다만 이건 설계상의 시작값이지 확정 불변은 아니다. 클러스터가 뜬 뒤 실측(docker stats·kubectl top)으로 다시 조정할 출발점이다.
- vCPU 4개에 RAM 8GB로, 세 대 똑같이. 물리 노트북 RAM이 약 31GB라, 셋에 8GB씩 주고 나면 호스트(Proxmox 자신)와 나중에 세울 방화벽 VM(OPNsense) 몫이 남는다.
- 디스크는 지금은 부트용 40GB만 준다. 데이터용 디스크(MySQL·LGTM·MinIO가 쓸 것)는 다음 편(6부)에서 k3s를 세우기 직전에, 노드마다 역할에 맞게 따로 붙인다. 그래서 노드별 최종 디스크 크기는 서로 달라진다. 지금은 뼈대만 세우는 단계라 부트 디스크 하나로 충분하다.
이 편에서 할 일은 여기까지다. VM 세 대를 띄우고, 각각 Ubuntu를 깔고, 고정 IP(.201·.202·.203)를 잡는 것. 이 셋을 한 클러스터로 묶는 건 6부(k3s)에서 한다.
2. VM 올리기 전 — Proxmox 메뉴 한 바퀴
VM이 딛고 설 것은 셋이다. 통신할 네트워크, 디스크를 담을 저장소, 세 대를 감당할 호스트 자원. 셋 다 내가 만든 게 아니라 Proxmox가 설치 때 깔아둔 것이라, VM을 올리기 전에 실물을 확인했다.
웹UI 구조부터. 왼쪽 트리에서 관리할 대상을 고르면, 가운데 메뉴가 그 대상의 것으로 바뀐다.

트리를 풀면 이렇다.
Datacenter ← 최상위 관리 단위(서버 여러 대를 묶는 우산) — 우리는 1대뿐
└ pve ← 노드 = 이 노트북. 설치 때 넣은 hostname(pve.home.arpa)의 앞토막
├ localnetwork (pve) ← 네트워크 존 — vmbr0가 속한 묶음. 만질 일 없음
├ local (pve) ← 저장소① 파일용(ISO·백업) — 곧 여기에 Ubuntu ISO를 올린다
└ local-lvm (pve) ← 저장소② VM 디스크용(LVM-thin 풀) — VM 디스크가 여기 담긴다
전부 설치가 만든 것들이고, 우리는 이제 쓰기만 한다. 이제 pve(노드)를 클릭한다 — 가운데 메뉴가 노드 것으로 바뀐다. 위에서부터 훑으며 중요한 것만 짚는다.

pve (노드 메뉴 — 실제 순서)
├ Search 이 노드의 객체 목록 — 거의 안 씀
├ Summary 자원 여유 확인 — 이 절 끝에서
├ Notes 메모장 — 안 씀
├ Shell 브라우저 안 터미널 — 4부 명령 작업 전부 여기서 했다
├ System
│ ├ ★ Network vmbr0 = VM이 붙을 가상 스위치 (핵심, 바로 아래)
│ ├ Certificates 웹UI "주의 요함"의 정체(자체서명) — 그대로 둠
│ ├ DNS · Hosts 설치 때 넣은 값 확인만 (공유기 .1 / 서버 자기 이름표)
│ ├ Options 노드 잡다한 옵션 — 안 건드림
│ ├ Time 4부에서 맞춤(Asia/Seoul) — 확인만
│ └ System Log 시스템 로그 — 문제 생기면 보는 곳
├ Updates
│ └ Repositories 4부에서 무료판 전환 완료 — 지나감
├ ★ Firewall 내장 방화벽 — 지금 안 켬 (아래 설명)
├ ★ Disks 디스크 실물 — LVM-Thin이 local-lvm의 실체 (아래)
├ Ceph · Replication 물리 서버 여러 대용 — 노트북 한 대라 해당 없음
└ Task History · Subscription 작업 이력 / 유료 구독 — 무료판이라 구독 안 씀
System → Network — 여기가 핵심
네 줄이 떠 있다.

nic0 — 물리 유선 랜포트
- 노트북에 원래 박혀 있는 그 유선 포트다. Active=Yes — 케이블이 꽂혀 링크가 살아 있다.
- Alternative Names 칸에 이름이 두 개 더 있다(enp0s31f6, enx84...). 둘 다 같은 포트를 부르는 리눅스 기본 이름이다. enp는 슬롯 위치 기반, enx는 MAC 기반. nic0는 그 위에 씌운 고정 별칭이다 — 설치 때 고른 "Pin network interface names"가 만든 것이고, 덕분에 재부팅해도 이름이 안 흔들린다.
vmbr0 — Proxmox가 만든 가상 스위치
- 핵심은 Ports/Slaves 칸이다. 거기 nic0이 들어 있다. 이 가상 스위치가 물리 랜포트를 물고 있다는 뜻이다.
- 그래서 VM을 vmbr0에 붙이면, VM의 트래픽은 nic0에 꽂힌 랜선을 타고 내 방 공유기(Archer AX12)로 들어간다. 3부에서 개통한 그 사설망(192.168.0.x)에 VM도 데스크탑·노트북과 똑같이 기기 하나로 참여하는 것이다.
- CIDR 칸의 192.168.0.200/24 — 지금 데스크탑(노트북 서버와 같은 공유기에 꽂혀 있는)에서 웹UI 접속할 때 치는 바로 그 주소다. 호스트(Proxmox 자신)의 IP가 이 가상 스위치에 얹혀 있다는 뜻이다. Gateway 192.168.0.1은 공유기(AX12)다.
- Active·Autostart 둘 다 Yes — 부팅하면 알아서 뜬다.
나머지 둘 — 안 쓰는 것들
- nic1은 Active=No, 안 쓰는 슬롯이다. wlp0s20f3은 WiFi인데 꺼져 있다. 유선을 쓰니 이대로 두면 된다(헤드리스 서버는 유선이 정석).
- dev 단계는 이 구조 그대로 간다. VM이 데스크탑과 같은 망에 나란히 놓이는 형태고, 혼자 집 안에서만 쓰는 동안은 이게 제일 단순하다. 격리는 인터넷 공개 때의 일이다 — 아래 Firewall에서 잇는다.
Firewall — 있지만 안 켠다
Proxmox 내장 방화벽이다. 켜면 노드·VM 단위로 트래픽 규칙을 걸 수 있다.
지금은 안 켠다. 이 사설망 자체가 공유기 NAT 뒤라 걸러낼 트래픽이 없고, 켜면 k3s가 쓰는 포트(6443·etcd·kubelet 등)를 일일이 열어줘야 하고, 잘못 켜면 웹UI(8006)까지 막혀 내가 나를 잠근다.
격리가 진짜 필요해지는 건 서비스를 인터넷에 공개할 때다. 그때 가서 이 내장 방화벽과 OPNsense(방화벽 전용 VM을 세워, 클러스터를 전용 사설망(10.0.0.x)에 두고 바깥과의 통로를 그 VM 하나로 좁히는 방식 — 2부의 "섬")를 비교해서 보안 설계를 할 예정이다. 공개 편에서 다룬다.
Disks — 디스크 실물

이 서버에 물린 디스크 실물이 잡히는 곳이다. 핵심은 둘이다.
- /dev/nvme0n1 (EXT X200E, 1TB)은 내장 NVMe, 곧 Windows다. 파티션에 ntfs가 보인다. 절대 손대면 안 되는 디스크다 — 여길 밀면 Windows가 날아간다. Proxmox는 인식만 하고 안 건드린다.
- /dev/sda (GIGABYTE, 1TB)는 외장 SSD, Proxmox가 설치된 곳이다. sda1(부트)·sda2(EFI)는 부팅용 조각이고, sda3가 1TB 통째 LVM 영역이다. 이 안이 어떻게 나뉘었는지는 하위 메뉴에서 보인다.
Disks 하위 — LVM·LVM-Thin
- LVM — sda3 위에 설치기가 만든 볼륨 그룹(pve). 여기서 루트(약 94GB — Summary의 그 HD space)와 스왑(8GB)이 잘려 나갔고, 루트가 저장소 local의 바탕이다.
- LVM-Thin — 남은 대부분을 차지하는 thin 풀. 이게 local-lvm의 실체고, VM 디스크가 전부 여기 담긴다. (Directory·ZFS 메뉴는 우리가 안 쓰는 방식이라 비어 있다.)

정리하면 저장소는 두 칸이다. local은 파일용(ISO·백업), local-lvm은 VM 디스크용.
다음 절에서 local에 Ubuntu ISO를 올리고, VM을 만들면 그 디스크가 local-lvm에 담긴다.
Summary — 자원 확인
마지막으로 Summary. 이 노트북이 VM 세 대를 감당할 여유가 있는지 실물 수치로 확인했다.

- CPU 18스레드(Ultra 5 125H) · RAM 30.66GiB — §1의 "8GB씩 셋" 계산이 실물로 성립한다. 지금은 호스트 혼자라 CPU 0%·RAM 2GB로 놀고 있다.
- IO delay 0% — CPU가 디스크 응답을 기다리며 버리는 시간. 우리 디스크가 USB 외장 SSD라, 앞으로 부하가 걸리면 제일 먼저 지켜볼 값이다(오르면 디스크 병목 신호).
- SWAP 0/8GB — 4부에서 swappiness=10으로 낮춰놨으니, RAM이 진짜 부족해지기 전엔 0에 머무는 게 정상.
- HD space 93.93GB — "1TB 어디 갔나" 싶은데, 호스트 루트(/) 영역만이다. 나머지 대부분은 VM 디스크용 thin 풀(위 LVM-Thin)에 있다.
- Kernel 7.0.14-pve · Boot Mode EFI · Repository Status의 노란 경고 — 전부 4부에서 만든 상태 그대로다(업그레이드한 커널 · F12로 하는 UEFI 부팅 · 무료판 리포).
3. 첫 실작업 — Ubuntu ISO 올리기
VM은 결국 빈 컴퓨터다. OS를 깔려면 설치 미디어(ISO)가 있어야 한다. 3부에서 노트북에 설치 USB를 꽂아 Proxmox를 깔았듯, VM에는 ISO 파일을 물려서 그걸로 부팅해 OS를 깐다. 그 ISO를 local 저장소에 올려둔다.
OS는 Ubuntu Server LTS로 정했다. 이유는 두 가지다.
- Server 판(데스크탑 GUI가 없는 판)을 쓴다. 노드는 화면 없이 돌아가는 서버라, GUI는 RAM·디스크만 잡아먹는다.
- LTS(장기 지원)를 쓴다. 5년 보안 업데이트가 붙는다. k3s를 얹을 바닥은 자주 바뀌지 않는 안정판이어야 한다.
올리는 건 local (pve) → ISO Images에서 한다. Ubuntu 미러 주소를 넣어 바로 받는 Download from URL, 아니면 데스크탑에 받아둔 파일을 올리는 Upload 중 하나면 된다.
https://releases.ubuntu.com/24.04/
Ubuntu 24.04.4 (Noble Numbat)
Select an image Ubuntu is distributed on five types of images described below. Desktop image The desktop image allows you to try Ubuntu without changing your computer at all, and at your option to install it permanently later. This type of image is what mo
releases.ubuntu.com


4. VM 만들기 — Create VM 마법사

웹UI 우측 상단의 Create VM 버튼을 누른다. 4부 §0의 표에 이 버튼 뒤의 실물이 KVM + QEMU(가상화 엔진)라고 적어뒀는데, 이제 그걸 실제로 부르는 것이다.
버튼 옆의 Create CT는 컨테이너(LXC)를 만드는 버튼이다 — OS 전체를 돌리는 VM과 달리 호스트 커널을 빌려 쓰는 가벼운 격리 방식인데, 이번 구축에선 안 쓴다. 4부 끝에서 경고한 Create Cluster는 여기가 아니라 Datacenter 메뉴 안에 있다 — 여전히 손대지 않는다.
마법사의 정체 — 컴퓨터 한 대를 부품 단위로 조립하는 것
컴퓨터 한 대는 결국 부품 묶음이다. 연산(CPU), 메모리(RAM), 저장(디스크), 연결(NIC — 2부에서 정리한 그 랜카드), 그리고 이들을 한 몸으로 묶는 보드와 펌웨어. 화면 없이 굴릴 서버라면 하나 더 필요하다 — 밖에서 안을 들여다보고 지시할 관리 채널.
마법사의 여덟 탭이 이 목록과 거의 1:1로 맞는다. 순서도 임의가 아니다 — 앞 탭의 결정이 뒤 탭의 전제가 된다.

General 이름표·기동 방침 ID 201 = IP .201 = k3s-1 / 호스트 켜지면 자동 시작
OS 설치 미디어 Ubuntu Server ISO를 가상 CD롬에
System 부품 구현 방식·관리채널 VirtIO 컨트롤러 + Qemu Agent
Disks 저장 부트 40GB — 회수(Discard)·분리(IO thread)·완료 보고(No cache)
CPU 연산 4코어 / 물리 CPU 그대로 노출(host)
Memory 메모리 8GB 고정 — kubelet의 전제
Network 연결 vmbr0 = 내 골목에 기기로 참여
Confirm 조립 명세서 검수 실수는 여기서 잡힌다 (실제로 잡았다)
AWS와 비교하면 이 마법사의 의미가 선명해진다. EC2에선 CPU·Memory가 "인스턴스 타입"(t3.large처럼 CPU·RAM 조합이 미리 정해진 규격) 한 칸으로 합쳐지고, System 탭 전체와 OS 설치 과정은 하이퍼바이저와 AMI(OS 설치가 이미 끝난 디스크 이미지)가 대신 정한다. 여덟 탭의 절반이 클라우드에선 남이 정한 값이라는 뜻이다.
그 가려진 절반을 직접 정해보려고 하이퍼바이저부터 깐 것이니, 탭마다 "EC2였다면 어디로 숨었을 결정인가"를 한 줄씩 같이 적는다. (EC2를 안 써봤다면 그 줄은 건너뛰어도 본문 이해엔 지장 없다.)
그리고 조립 기준은 처음부터 끝까지 하나다. 이 VM은 범용 PC가 아니라 k3s 노드다. 위에 뭐가 올라갈지 이미 안다 — etcd는 합의 기록마다, MySQL은 커밋마다 디스크 보장(fsync — 실물 디스크에 내려앉았음을 확인받는 쓰기)을 요구하고, Kafka는 로그를 쉼 없이 쌓고, kubelet은 노드 자원을 재서 파드를 배치한다. 탭마다의 선택은 전부 여기서 역산된다.
General — 이름과 기동 방침

- Node: pve — 어느 물리 서버에 만들지 고르는 칸. 서버가 한 대라 선택지도 하나다.
- VM ID: 201(2,3) — Proxmox가 VM을 구분하는 내부 번호(100 미만은 예약이라 못 쓴다). 노드가 받을 IP 끝자리와 맞춰 "201 = 192.168.0.201 = k3s-1"이 한 줄로 읽히게 정했다 — 안 묶으면 VM이 늘수록 대조표를 뒤지게 된다.
- Name: k3s-1 — Proxmox 화면에 보일 이름. 게스트(VM 안에서 돌 OS = Ubuntu. 반대쪽이 호스트 = Proxmox) 안의 hostname과는 별개 값이지만, §5에서 hostname도 같게 맞춘다. 이름의 축은 둘이다:
- 한 VM 안에서는 — VM 이름 = hostname = k3s 노드 이름으로 같게 묶는다(k3s에선 hostname이 곧 노드 이름이다).
- 세 VM끼리는 — 서로 달라야 한다. hostname이 겹치면 클러스터 조인이 충돌한다. 그래서 k3s-1·k3s-2·k3s-3.
- ★Start at boot: 체크 — 하단 Advanced를 켜면 나온다. 호스트가 부팅되면 이 VM도 자동으로 켠다는 옵션이다. 이 서버는 필요할 때 전원을 넣는 방식이라(4부 §A-8), 이게 있어야 "전원만 넣으면 클러스터가 스스로 복귀"가 성립한다. 안 켜면 전원 넣을 때마다 웹UI에서 세 대를 손으로 켜야 한다.
- 나머지는 전부 기본값, 한 줄씩:
- Resource Pool 빈칸 — VM을 묶어 권한을 나누는 그룹. 관리자가 나 혼자라 불필요.
- Add to HA 미체크 — 물리 서버 여러 대일 때 죽은 서버의 VM을 다른 서버에서 되살리는 기능. 우리 HA는 이 층이 아니라 k3s 층의 etcd 3대가 만든다(§1).
- Start/Shutdown order any · Tags 없음.
- EC2 비교: 이름은 Name 태그, ID는 자동 발급(i-0abc…)이라 ID와 IP를 묶는 설계가 성립하지 않는다. Start at boot 개념도 없다 — 호스트 전원은 고객의 층이 아니다.
OS — 설치 미디어 꽂기

- Use CD/DVD disc image file → Storage local → §3에서 받아둔 ubuntu-24.04.4-live-server-amd64.iso(3.17GiB) 선택. 물리 컴퓨터라면 설치 USB를 꽂을 자리에, VM은 ISO 파일을 가상 CD롬으로 물린다 — 3부에서 노트북에 USB를 꽂아 Proxmox를 깔던 구도가 한 층 아래로 반복되는 것이다.
- Guest OS: Type Linux, Version 기본값 그대로 — 장식 칸이 아니다. 마법사가 이 힌트를 보고 뒤 탭들의 기본값을 채운다(Linux라서 디스크 컨트롤러·NIC가 처음부터 VirtIO로 온다). "그대로 둔다"도 선택인 셈이다.
- EC2 비교: 이 탭 자체가 없다. AMI는 OS 설치가 끝난 디스크라, "미디어로 부팅해 OS를 깐다"는 과정 전체가 클라우드에선 생략돼 있다.
System — 부품의 구현 방식, 그리고 관리 채널
이 탭을 읽으려면 VM의 부품이 뭘로 만들어지는지부터 잡아야 한다. VM의 부품은 전부 QEMU가 소프트웨어로 구현한다. QEMU는 VM 한 대마다 가상 컴퓨터 한 세트(보드·디스크 컨트롤러·그래픽·NIC)를 만들어 돌리는 프로그램이고, 호스트에서 보면 VM 하나 = QEMU 프로세스 하나다.
연산만은 KVM(리눅스 커널의 가상화 모듈)이 맡는다 — CPU의 가상화 기능을 써서 VM의 연산을 물리 CPU에서 거의 그대로 돌린다. 부품은 QEMU, 연산은 KVM. 4부 표의 "Create VM 버튼 = KVM + QEMU"가 이 분담이었다.
그리고 가상 부품의 구현엔 두 방식이 있다. 이 탭과 뒤의 Disks·Network를 관통하는 구분이라 여기서 한 번만 정리한다.
- 실물 재현 — 실존했던 하드웨어(옛 SCSI 카드, 인텔 랜카드)의 동작을 그대로 흉내 낸다. 게스트 OS는 자기가 VM인 줄 모르고 실물을 다루듯 절차를 전부 밟고, QEMU는 그걸 하나하나 가로채서 처리한다. 아무 OS나 드라이버 없이 인식하는 대신, 가로채기가 많아 느리다.
- VirtIO — 처음부터 가상 전용으로 설계된 장치 규격. 게스트의 virtio 드라이버(리눅스 커널에 내장)와 QEMU가 공유 메모리 큐로 명령을 직접 주고받는다. 재현할 절차가 없으니 가로채기가 적고 빠르다. 디스크 컨트롤러(이 탭)와 NIC(Network 탭)를 전부 이걸로 간다.

확인할 것 — SCSI Controller: VirtIO SCSI single (기본값)
기본값으로 이미 선택돼 있어 확인만 했다. 이름 세 토막이 각각 의미다:
- SCSI — OS와 저장장치가 명령을 주고받는 표준 규격. 리눅스에서 디스크 이름이 /dev/sda처럼 sd로 시작하는 이유가 이 규격이다.
- 컨트롤러 — 그 명령을 받아 디스크로 중개하는 장치. QEMU가 구현한다.
- VirtIO / single — 구현 방식이 위의 VirtIO라는 것, 그리고 디스크마다 전용 컨트롤러를 하나씩 만든다는 것. single이 다음 탭에서 켤 IO thread의 전제다(전용 스레드가 컨트롤러 단위로 붙는다).
single은 "디스크를 하나만 붙일 수 있다"는 뜻이 아니다 — 6부에서 데이터 디스크를 추가하면 그 디스크의 컨트롤러가 하나 더 생길 뿐이다.
드롭다운의 나머지(LSI 53C895A·MegaRAID·VMware PVSCSI 등)는 virtio 드라이버가 없는 구형 OS나 다른 하이퍼바이저에서 옮겨온 VM용이다 — 새로 만드는 리눅스 VM엔 해당 없다.
행동 — ★Qemu Agent: 체크 (기본 꺼짐, 이 탭에서 유일하게 바꾼 것)
호스트가 게스트 OS 안의 일을 부탁할 수 있는 통신 채널이다. 웹UI에 VM의 실제 IP를 표시하고, Shutdown을 누르면 게스트 안에서 정상 종료 절차를 밟게 하고, 백업 때 파일시스템을 잠깐 얼려(fsfreeze) 정합성을 잡는다.
없으면 — VM의 IP를 알려면 콘솔(웹UI가 VM 화면을 브라우저에 띄워주는 창, §5에서 쓴다)로 들어가 직접 쳐야 하고, 종료는 가상 전원 버튼 신호에만 의존한다.
단, 이 체크는 절반이다. 게스트 안에도 에이전트 패키지를 깔아야(§5) 완성된다. 그 전까지는 웹UI Shutdown이 응답을 기다리다 지연될 수 있다 — 그땐 Stop이나 콘솔 안 종료를 쓴다.
나머지는 전부 기본값, 한 줄씩:
- Graphic card Default — §5에서 콘솔로 볼 화면을 그리는 가상 그래픽. GUI·GPU 용도에서나 바꾼다.
- Machine Default(i440fx) — 가상 보드(칩셋). 신형 q35는 PCIe 장치 통과 같은 용도에서나 필요하다.
- BIOS Default(SeaBIOS) — 펌웨어(전원 직후 OS보다 먼저 실행돼 부팅을 시작하는 프로그램)의 가상판. 호스트 노트북은 UEFI로 부팅하지만(3부 F12의 그 화면) VM은 SeaBIOS로 충분하다. UEFI(OVMF)는 Secure Boot 같은 요구가 있을 때나 고른다.
- Add TPM 미체크 — 암호 키 보관용 보안 칩의 가상판. Windows 11·디스크 암호화 용도다.
EC2 비교: 이 탭이 통째로 없다 — 장치 구성은 AWS가 정하고 드라이버는 AMI에 들어 있다.
Disks — 저장: 병목을 아는 채로 조립한다
System에서 디스크 명령이 지나갈 길(컨트롤러)을 정했으니, 이제 그 길 끝에 실제 디스크를 단다.
이 탭의 선택은 전부 우리 클러스터의 물리 사정에서 나온다. 노드 셋이 띄울 것들(§1의 그 워크로드)이 물리적으로는 외장 SSD 한 장 위다. CPU·RAM은 수량을 잘라 VM별로 나눠주면 되지만, 디스크만은 세 노드가 결국 같은 실물 한 장을 직접 때린다. 그래서 1순위 병목 후보다.
결정은 세 방향을 향한다: 회수(Discard·SSD emulation을 켠다), 분리(IO thread를 켠다), 완료 보고의 정직함(Cache는 기본값 유지가 곧 선택이다).

- Bus/Device: SCSI,— System 탭 컨트롤러의 0번 디스크라는 뜻. single이라 이 디스크 전용 컨트롤러가 하나 생기고, 게스트(Ubuntu) 안에선 /dev/sda로 보인다.
- scsi0, scsi1 = 디스크(가 꽂힌 자리)의 이름.
- 컨트롤러 = 그 디스크마다 뒤에서 딸려 생기는 처리 장치인데, UI에는 개별 이름이 안 보인다.
- IO thread를 조합하여 디스크마다 전용 IO Thread를 돌릴 수 있게 한다.(SCSI single + IO thread)
- single (우리 설정):
scsi0 큐 ─ 컨트롤러① ─ IO thread① ┐ scsi1 큐 ─ 컨트롤러② ─ IO thread② ┼ 서로 다른 CPU 코어에서 동시에 scsi2 큐 ─ 컨트롤러③ ─ IO thread③ ┘ - single 아닐 때 (그냥 VirtIO SCSI):
scsi0 큐 ─┐ scsi1 큐 ─┼─ 컨트롤러 하나 ─ IO thread 하나 scsi2 큐 ─┘
- single (우리 설정):
- 디스크는 부트용 이 한 장만 만든다
- Storage: local-lvm — 드롭다운에 Avail 875.49GB, §2에서 실측한 그 thin 풀이다. Disk size: 기본 32 → 40.
- 40의 근거 — 부트 디스크엔 OS(설치 직후 약 10GB)에 더해 k3s 바이너리·컨테이너 이미지 캐시·로그가 쌓인다. 데이터는 어차피 6부의 별도 디스크로 빠지니, 이미지 캐시가 자라는 몫만 여유로 얹은 값이다.
- thin이라 40GB가 지금 떼어지는 게 아니다 — "40까지"라는 한도만 기록되고, 실제 블록은 게스트가 쓰는 순간 풀에서 그때그때 할당된다. 세 대에 40씩 잡아도 풀이 당장 거의 그대로인 이유다.
Discard: 체크 (기본 꺼짐)
게스트가 파일을 지워도 풀은 그걸 모른다. 삭제는 게스트 파일시스템이 자기 목록(메타데이터)에서 항목을 지우는 것일 뿐, 블록 자체는 풀 입장에서 여전히 "사용 중"이다.
Discard는 게스트가 내려보내는 "이 블록 이제 안 씀"(TRIM) 명령을 풀까지 전달해 블록을 되돌려받는 길이다. 안 켜면 쓰고 지우기를 반복할수록 실사용량은 그대로인데 풀 점유만 자란다 — 875GB 풀 하나를 세 노드가 나눠 쓰는 구조에서 풀 고갈은 노드 하나가 아니라 클러스터 전체의 장애다.
길만 뚫는다고 회수가 되는 건 아니고, 게스트가 실제로 TRIM을 보내야 한다 — Ubuntu는 fstrim 타이머가 주기적으로 보낸다.
SSD emulation: 체크 (기본 꺼짐)
이 디스크를 게스트에 회전식 HDD가 아니라 SSD로 보고하는 옵션이다. 실물이 SSD인 것과는 별개인, "게스트에 뭐라고 보일까"의 문제다.
게스트 OS는 디스크가 회전식으로 보이면 TRIM 같은 SSD 전제 동작의 판단을 다르게 할 수 있다. 실물(외장 SSD)과 보이는 모습을 일치시켜, Discard와 같은 회수 목표를 거든다.
IO thread: 체크 (기본 꺼짐 — 직접 켰다)
QEMU는 기본적으로 장치 이벤트를 프로세스의 메인 루프 하나에서 처리한다. 이 옵션은 이 디스크(컨트롤러)의 IO 처리를 전용 스레드로 떼어낸다.
6부가 되면 한 VM 안에서 부트 IO·MySQL 커밋·Kafka 로그가 동시에 디스크를 두드린다. 디스크마다 전용 스레드가 있으면 한 디스크의 IO 폭주가 메인 루프와 다른 디스크 처리를 같이 끌고 내려가지 않는다. System 탭의 single(디스크당 컨트롤러 하나)이 전제였던 이유가 이것이다 — 스레드는 컨트롤러 단위로 붙는다.
한계도 적어둔다. 이건 QEMU 층의 분리일 뿐, 맨 밑 실물 SSD가 한 장이라는 물리 경합까지 없애주진 못한다. 그건 §2에서 복선을 깔아둔 IO delay 값으로, 부하테스트 때 실측할 몫이다.
Cache: Default (No cache) 그대로 — 유지가 곧 선택
캐시 모드는 "게스트에게 언제 쓰기 완료를 보고하나"의 문제다. No cache는 호스트의 페이지 캐시(쓰기를 RAM에 잠시 머금는 층)를 건너뛰고, 블록이 실물 저장장치의 쓰기 큐에 도달했을 때 완료를 보고한다.
Write back 계열은 호스트 RAM에 올라간 순간 완료 보고라 빨라 보이지만, 그 사이 정전·강제종료가 나면 게스트가 "썼다"고 믿는 데이터가 실물엔 없다. etcd·MySQL은 "커밋했다 = 디스크에 있다"는 보장 위에 설계된 소프트웨어다 — 그 보장을 하이퍼바이저 층에서 거짓말로 만들면 안 된다.
참고로 No cache가 캐시 전무라는 뜻은 아니다 — 디스크 자체의 쓰기 캐시는 남는다. 그것까지 끄는 모드(directsync)는 가장 안전한 대신 가장 느려서, 기본값이 안전과 속도의 균형점이다.
나머지는 한 줄씩: Async IO Default(io_uring — 리눅스의 최신 비동기 IO 인터페이스) 그대로 · Backup 체크 유지(백업 대상 표시) · Bandwidth unlimited(디스크 IO 속도 제한 — 안 건다).
EC2 비교: 이 탭 전체가 "EBS(인스턴스에 붙이는 네트워크 디스크) gp3 40GB" 한 줄로 접힌다 — 캐시·TRIM·IO 스레드는 AWS 안쪽의 일이다.
CPU — 연산: 물리 CPU를 그대로 노출

- Cores: 1 → 4. Sockets는 1 — 보드에 물리 CPU 칩을 꽂는 자리 수의 가상판으로, 다중 소켓 서버를 흉내 낼 때나 늘린다.
- vCPU = 게스트에 CPU 코어로 보이는 실행 단위. 실체는 물리 스레드(§2 Summary의 그 18개)의 시간을 나눠 받는 것이다. 세 대 합쳐 vCPU 12 < 물리 18.
- 단어 주의 — 여기서 '스레드'는 물리 CPU의 동시 실행 줄기다. 앞 탭 IO thread의 스레드(프로그램이 만드는 작업 흐름)와는 다른 층의 같은 단어.
- 왜 물리보다 적게 잡았나 — CPU는 시간을 잘게 쪼개 여러 VM이 번갈아 쓸 수 있어서(시분할), 물리보다 많이 배정(오버커밋)해도 돌아간다. RAM은 한 번 내준 공간을 게스트가 계속 점유하니 사정이 다르다(다음 탭). 다만 CPU 오버커밋의 대가는 경합 시 대기다 — 굳이 질 이유가 없어 안 넘기고, 호스트와 OPNsense(§1에서 예고한 방화벽 VM) 몫까지 여유를 남겼다.
- Type: 기본값(x86-64-v2-AES) → host — 이 탭에서 바꾼 둘째 값.
- 기본값의 정체 — 실물이 아니라 "이 세대 이후 CPU라면 어디든 있는 기능만" 노출하는 가상의 공통분모 CPU다. 존재 이유는 이주 — VM을 딴 서버로 옮겨도 죽지 않게 기능을 미리 깎아두는 보험이다.
- host — 물리 CPU(Ultra 5 125H)의 기능을 게스트에 그대로 노출한다. 가장 빠르다. 옮겨갈 서버가 없는 단일 호스트에선 그 보험의 값이 0이라, host가 정답.
- 나머지 기본값 한 줄: CPU units 100(호스트 CPU 경합 시 VM 간 배분 가중치 — 셋 다 같게) · NUMA 미체크·Extra CPU Flags Default(다중 소켓·특수 튜닝용).
- EC2 비교: vCPU 수는 인스턴스 타입에 묶이고, CPU를 어떤 모델로 보일지라는 선택지는 아예 없다.
Memory — 8GB 고정: kubelet의 전제
- Memory: 2048 → 8192 MiB — §1에서 잡은 배분 그대로다. 세 대 24GB에 호스트·OPNsense 몫이 남는 계산이고, 실측(kubectl top) 후 재조정할 시작값이다.
★Ballooning Device: 체크 해제 (기본은 체크)
벌루닝 = 호스트가 게스트 안의 벌룬 드라이버를 통해 게스트 메모리 일부를 회수해 다른 데 쓰는 동적 재분배 장치다.
Minimum memory 칸의 하한과 Memory 사이에서 오르내리게 하는 구조다.
문제는 개입 시점이다. 회수는 호스트 RAM 사용률이 기준(기본 80%)을 넘을 때 시작된다 — 하필 전체가 제일 바쁠 때 노드의 가용 RAM이 줄어드는 것이다.
그런데 kubelet은 노드 RAM 총량이 고정이라는 전제로 파드를 배치하고, 메모리가 몰리면 파드를 퇴출(eviction)시킨다. 총량 자체가 뒤에서 움직이면 그 판단이 전부 어긋난다 — 그래서 끈다. 끄면 min = max = 8GB 고정이 된다.
대가도 있다. 벌룬 장치는 고정 할당에서도 게스트의 실제 메모리 사용량을 호스트에 보고하는 역할을 하는데, 끄면 그 보고까지 사라진다. 어차피 k3s를 올린 뒤 kubectl top으로 볼 값이라 감수한다.
- Allow KSM: 체크 유지 — KSM(Kernel Samepage Merging)은 여러 VM의 같은 내용 메모리 페이지를 호스트가 물리 메모리 한 장으로 겹쳐 저장하는 중복 제거다. 같은 Ubuntu를 세 벌 돌리니 겹칠 게 많다. 벌루닝과의 결정적 차이 — 게스트 눈에 RAM은 그대로 8GB다. 호스트 쪽에서만 일어나는 투명한 최적화라 kubelet의 전제를 안 흔든다. (호스트가 여유로울 땐 병합을 안 시작해 절감 0 B가 정상 — §2 Summary의 그 0 B다.)
- EC2 비교: RAM도 인스턴스 타입에 묶인다 — 벌루닝·KSM 같은 하이퍼바이저 메모리 층은 화면에 없다.
Network — 내 골목에 기기 하나로
안쪽 부품은 다 조립했다. 마지막 부품은 바깥과의 연결이다.
- Bridge: vmbr0 — 기본값 그대로. §2에서 확인한, 물리 랜포트 nic0을 물고 있는 그 가상 스위치다. 여기 붙는 순간 이 VM은 3부에서 개통한 내 골목(192.168.0.x)에 데스크탑·노트북 서버와 나란히 기기 하나로 참여한다. VM의 트래픽이 VM 자신의 MAC으로 나가서 공유기가 별개 기기로 등록한다(2부에서 본 그 원리) — 데스크탑에서 각 노드로 ssh·kubectl이 바로 되는 근거가 이 한 칸이다.
- Model: VirtIO (paravirtualized) — 이것도 기본값 그대로다(OS 탭에서 Linux라고 해둔 덕에 미리 채워진 것). System 탭에서 정리한 구도가 NIC에서 반복된다 — 실물 재현 NIC(Intel E1000)는 게스트가 진짜 랜카드 다루듯 절차를 밟고 QEMU가 전부 가로채야 하지만, VirtIO NIC는 드라이버와 QEMU가 공유 메모리 큐로 패킷을 직접 주고받는다.
- 나머지는 한 줄씩:
- VLAN Tag 없음 — 스위치 하나를 논리적으로 여러 망으로 쪼개는 태그. 내 골목 하나뿐이라 나눌 일이 없다.
- MAC auto — 무작위 생성. 공유기가 이 MAC을 보고 DHCP로 첫 IP를 주고, §5에서 .201 고정으로 바꾼다(3부에서 .200부터를 고정 전용으로 비워둔 그 구간이다).
- Firewall 체크된 채 둔다 — "방화벽을 켜면 이 NIC도 적용받겠다"는 표시일 뿐이다. 노드 방화벽 자체를 안 켰으니(§2) 지금은 실효도 비용도 없고, 나중에 방화벽 층을 켤 때 VM마다 이 스위치를 다시 찾아다닐 필요가 없어진다.
- EC2 비교: 서브넷·보안그룹은 골라도 NIC 모델 선택지는 없다 — AWS 전용 장치(ENA) 고정이다.
같은 마법사를 두 번 더 — 그리고 클론이라는 대안
Finish를 누르면 왼쪽 트리에 201 (k3s-1,2,3)이 생긴다. 아직 전원이 꺼진, ISO만 물린 빈 컴퓨터다.
이제 같은 마법사를 202/k3s-2, 203/k3s-3으로 두 번 더 돌린다. 바뀌는 건 General 탭의 VM ID와 Name뿐이고, 나머지 탭은 이 글의 값 그대로다. Confirm에서 vmid·name 두 줄만 다르고 나머지가 위 체크리스트와 같은지 보면 검수도 끝난다.
202·203까지 올라오면 1부 아키텍처 그림에서 상자로만 그려뒀던 [VM 1][VM 2][VM 3] 자리가 트리에 실물로 찬다. 다만 몸통만이다 — 셋 다 꺼져 있고, 안은 비어 있다.

5. 게스트 설치 — 빈 VM에 Ubuntu 깔기
세 대를 다 만들었지만 아직 전원이 꺼진 빈 컴퓨터다. 한 대씩 켜서 Ubuntu Server를 깐다. 세 대가 완전히 같은 과정이고, 대마다 다른 값은 딱 둘 — hostname과 고정 IP다.
먼저 도착지부터 — 켜면 웹UI에 이렇게 뜬다
설치를 시작하기 전에, 이 절이 끝나면 노드가 어떤 모습이 되는지 보고 들어간다. VM을 켜고 웹UI에서 201을 클릭하면 Summary가 이렇게 뜬다.

여기서 미리 두 가지만 눈에 익혀두면 뒤가 안 헷갈린다.
- Memory usage가 "100.71% (8.06/8.00 GiB)"로 꽉 차 보인다. 하지만 노드가 꽉 찬 게 아니다 — 이건 호스트가 밖에서 본 점유량이고, §4에서 ballooning을 껐기 때문에 게스트가 실제로 얼마를 쓰든 8GB를 통째로 잡고 있는 것으로 나온다.
- 게스트 안에서 본 실사용은 뒤에 로그인하면 2%로 나온다(같은 메모리를 안과 밖에서 다르게 재는 것뿐이다. 100%를 살짝 넘는 건 QEMU 프로세스 자체의 관리용 메모리라 정상). 이 100%가 바로 "kubelet에 고정 RAM을 보인다"는 §4 결정의 실물 모습이다.
- IPs 칸에 "Guest Agent not running"이 떠 있다. §4에서 켠 Qemu Agent는 호스트 쪽 절반이라, 게스트 안에 패키지를 깔기 전(이 절 끝에서 한다)까지는 웹UI가 VM의 IP를 못 읽는다. 지금은 정상이다.
이제 이 상태에 이르는 과정을 밟는다. 설치기 화면이 여러 장 지나가는데 대부분은 기본값 그대로 Done이고, 손대거나 잠깐 멈춰 생각할 지점만 짚는다.
한 대 켜기 — Console로 들어가기
트리에서 201(k3s-1)을 골라 Start를 누르고 Console을 연다. noVNC — 웹UI가 VM의 화면을 브라우저 창에 그대로 띄워주는 것이라, 모니터를 꽂은 것처럼 설치 화면을 본다. 헤드리스 서버라 지금은 이 창이 유일한 화면이고, 설치가 끝나 ssh가 열리면 안 쓴다.
언어·설치기·판 — 서버 기본값으로
- 부팅하면 GRUB(ISO 안의 부트 선택 메뉴)이 뜬다. Try or Install Ubuntu Server로 들어간다.
- 언어는 English. 서버는 영어가 정석이다 — 에러·로그가 영어라야 검색이 바로 되고, 한글 로케일은 서버에서 득이 없다.
- "설치기 업데이트 있음" 화면은 Continue without updating. 우리 설치는 단순해 새 설치기가 필요 없고, 세 대를 같은 버전으로 깔아야 결과가 일치한다. (OS 자체 업데이트와는 별개 — 그건 설치 후 apt로 한다.)
- 설치 판은 Ubuntu Server(기본). minimized는 사람이 로그인하지 않는 환경용으로 기본 도구까지 뺀 판인데, 우리는 ssh로 들어가 관리·디버깅할 노드라 기본판이 맞다. third-party drivers는 미체크 — 실물 특수 하드웨어용이라 표준 부품뿐인 VM엔 무관하다.
네트워크 — 고정 IP를 여기서 박는다
설치기가 네트워크를 잡으면, ens18(게스트 안에서 보이는 그 VirtIO NIC)이 DHCP로 IP 하나를 이미 받아와 있다. 이건 확인이다 — §4에서 vmbr0에 붙인 게 실제로 작동해, VM이 자기 MAC으로 공유기에서 별개 기기로 주소를 받았다는 증거다.
다만 그 DHCP 주소를 그대로 두지 않고, 여기서 static .201로 바꿔 박는다. k3s는 노드 IP를 기준으로 클러스터 멤버십과 인증서를 맺어서, IP가 재부팅에 바뀌면 클러스터가 깨진다(§1). 설치기에서 박으면 처음부터 .201로 뜨고, 게스트의 netplan(Ubuntu의 네트워크 설정)에 그대로 써지니 나중에 따로 고칠 것도 없다.
ens18 → Edit IPv4 → Method를 Manual로 바꾸고 넣는다:
- Subnet 192.168.0.0/24
- Address 192.168.0.201 (202·203은 여기만 다르다)
- Gateway 192.168.0.1 — 공유기
- Name servers 192.168.0.1 — 이름을 공유기에 물어본다(호스트 DNS와 같은 값, §2에서 본 그것)
저장하면 목록의 ens18이 static 192.168.0.201/24로 바뀐다.
프록시·미러 — 둘 다 그대로
- 프록시는 빈칸. 인터넷에 직접 못 나가는 망에서 거쳐 갈 중계 서버 주소인데, 우리 골목은 공유기 통해 직행이라 없다.
- 미러(패키지를 받아올 서버)는 설치기가 kr.archive.ubuntu.com(한국)으로 잡아준다. 가까워서 빠르니 그대로 둔다.
디스크 — 게스트 안엔 LVM을 얹지 않는다
Use an entire disk를 고르면 §4에서 만든 40GB(게스트 눈엔 /dev/sda)가 잡힌다. 여기서 손댈 건 하나 — Set up this disk as an LVM group 체크를 끄는 것이다.
LVM은 디스크를 논리적으로 잘라 쓰는 층인데, 그 층은 이미 호스트에 있다. 이 VM 디스크 자체가 Proxmox의 thin 풀(875GB)에서 잘려 나온 조각이다(§2). 게스트 안에 LVM을 또 깔면 LVM 위의 LVM이 되어 관리 지점만 겹치고 얻는 게 없다. 크기 조절도 나중에 호스트 층에서 한다. (게다가 Ubuntu 가이드 LVM은 루트를 디스크 절반만 잡아둬서, 나중에 늘려주는 일을 따로 만든다.)
LUKS(암호화)도 끈다 — 켜면 부팅마다 암호를 쳐야 해서 "전원만 넣으면 복귀"가 깨진다. 위에꺼 끄면 알아서 비활성화됨.
끄고 나면 파티션이 단순해진다: 부트로더 자리(BIOS용 1MB 조각 — §4에서 VM 펌웨어를 SeaBIOS로 둔 것이 이 자리를 쓴다)와 나머지 통짜 ext4 루트, 둘뿐이다. 이 노드는 VM을 담지도 파일 저장소도 아닌 k3s 전용 일꾼이라 이 단순함이 맞다. 요약을 확인하고 넘기면 "디스크를 지운다"는 확인이 뜨는데, 지울 대상이 방금 만든 빈 가상 디스크라 부담 없이 Continue다.
6부에서 데이터 디스크(MySQL·Kafka용)를 붙일 때도 이 원칙이 이어진다 — 자르는 건 호스트(thin 풀에서 잘라 노드에 붙임), 노드는 포맷·마운트만. 상세는 그때 다룬다.
계정과 서버 이름 — hostname이 곧 노드 이름
- Your name은 표시용이라 아무거나 넣는다.
- Your server's name = k3s-1. 이게 hostname이 되고, k3s에서 hostname은 곧 노드 이름이다(202·203은 k3s-2·k3s-3). 여기가 대마다 달라야 하는 둘째 값이고, 겹치면 클러스터 조인이 충돌한다.
- username과 password는 세 노드를 같게 통일한다. 겹치면 안 되는 건 hostname이지 계정이 아니고, 오히려 같아야 세 노드 관리가 단순하다.
Ubuntu Pro·SSH
- Ubuntu Pro는 Skip. Canonical의 유료 구독(확장 보안·규제 인증)인데, LTS 기본 5년 지원이면 충분하다.
- ★Install OpenSSH server는 체크. 이걸 켜야 설치 후 데스크탑에서 ssh로 들어간다 — 안 켜면 매번 이 콘솔 창으로만 접속해야 한다.
- Allow password authentication은 켠 채로 둔다. 처음엔 비번으로 접속해야 뒤에서 공개키도 심을 수 있다. Import SSH key(GitHub에서 키를 당겨오는 기능)는 안 쓴다 — 우리는 데스크탑에서 직접 키를 밀어넣는 방식(ssh-copy-id)이라 GitHub 왕복이 불필요하다.
추천 snap — 하나도 깔지 않는다
인기 서버 패키지 목록이 뜬다. microk8s·etcd·prometheus처럼 익숙한 게 보이지만, 하나도 체크하지 않고 Done이다.

- microk8s는 다른 쿠버네티스 배포판이라 k3s와 충돌하고, etcd는 k3s가 내장한다.
- 우리가 올릴 건 컨테이너(k3s 위 워크로드)라, 노드 OS엔 순수 Ubuntu만 두고 나머지는 클러스터 위에 GitOps로 올린다. 노드는 얇게 유지하는 게 원칙이다("관리 포인트 최소").
설치 완료·재부팅
Done을 누르면 패키지를 받아 디스크에 쓰기 시작한다. 로그가 다 흐르면 Reboot Now가 뜬다. 재부팅 도중 "설치 미디어를 빼고 Enter" 안내와 함께 빨간 Failed unmounting cdrom 줄이 잠깐 뜨는데, 정상이다 — Proxmox가 재부팅하며 가상 CD롬을 알아서 정리하니 Enter만 치면 된다. ISO를 떼지 않아도 디스크 부팅이 우선이라 방금 깐 디스크로 뜬다.

잠시 뒤 검은 화면에 k3s-1 login:이 올라온다. 만든 계정으로 로그인하면 시스템 요약이 뜨는데, 여기가 §4·§5의 결정이 실물로 확인되는 지점이다.

- IPv4 address for ens18: 192.168.0.201 — static으로 박은 고정 IP 그대로.
- Usage of /: 17.0% of 39.07GB — LVM 없이 통짜로 잡은 40GB 디스크. OS가 약 6.6GB를 차지했다.
- Memory usage 2% — 8GB 중. 도입에서 본 웹UI의 100%와 대비되는 그 안쪽 값이다. 밖에선 8GB를 통째로 잡고 있고(ballooning off), 안에선 이렇게 거의 안 쓴다.
- Ubuntu 24.04.4 LTS, kernel 6.8 — 우리가 고른 그 판.
(요약에 "36 updates can be applied"가 뜨는데, OS 업데이트는 지금 하지 않고 6부에서 k3s를 얹기 직전 한 번에 한다.)
이 과정을 202·203에도 그대로 반복한다 — 바뀌는 값은 서버 이름(k3s-2·k3s-3)과 주소(.202·.203) 둘뿐이다.
설치 후 마무리 — 데스크탑을 세 노드의 관제탑으로
세 대의 로그인까지 확인되면, 각 노드는 온전한 서버로 섰다. 마지막으로 두 가지를 세팅한다. 지금까지 노드를 만질 땐 웹UI의 Console 창을 하나씩 열어야 했는데, 6부의 k3s 작업은 데스크탑에서 세 노드로 명령을 끊임없이 보내는 일이다. 그래서 데스크탑 한 자리에서 세 노드를 다루는 상태를 먼저 만든다. 이건 클러스터로 묶는 6부가 아니라, 5부의 마무리다.
① SSH — 데스크탑에서 노드로 비번 없이 접속하기
SSH(Secure Shell)는 네트워크 너머 다른 컴퓨터의 터미널에 암호화된 채널로 접속해 명령을 실행하는 방식이다. 설치할 때 각 노드에 OpenSSH server를 켰으니(§5), 노드는 이미 SSH 접속을 받을 준비가 돼 있다. 데스크탑에서 ssh subin@192.168.0.201이라고 치면 노드의 터미널이 데스크탑 창에 열린다.
다만 그때마다 비밀번호를 물으면 번거롭다(6부엔 명령을 수없이 보낸다). 그래서 공개키 인증으로 비번을 없앤다.
- 원리 — 열쇠 한 쌍(개인키 + 공개키)을 만든다. 개인키는 데스크탑에만 두고, 공개키는 각 노드에 심는다. 접속할 때 노드가 "이 공개키에 맞는 개인키를 가졌나"를 검사하고, 맞으면 비번 없이 통과시킨다. 개인키가 데스크탑을 떠나지 않으므로 비밀번호를 주고받는 것보다 안전하다.
작업은 전부 데스크탑 터미널에서 한다(노드 안이 아니다). Windows는 PowerShell을 쓴다.
먼저 열쇠 쌍을 만든다(딱 한 번):
ssh-keygen -t ed25519
묻는 것은 전부 엔터. ~/.ssh/에 개인키(id_ed25519)와 공개키(id_ed25519.pub)가 생긴다.
그다음 공개키를 세 노드에 심는다(IP만 바꿔 세 번):
type $env:USERPROFILE\.ssh\id_ed25519.pub | ssh subin@192.168.0.201 "umask 077; mkdir -p ~/.ssh; cat >> ~/.ssh/authorized_keys"
이 한 줄이 하는 일 — 데스크탑의 공개키 내용을 SSH로 노드에 보내, 노드의 ~/.ssh/authorized_keys(허용된 공개키 목록)에 덧붙인다. 처음 한 번은 노드 비밀번호를 묻는다. (리눅스·맥이라면 이 일을 ssh-copy-id 한 명령으로 하지만, Windows에는 그 명령이 없어 같은 동작을 위 형태로 푼 것이다.)
심고 나면 확인한다:
ssh subin@192.168.0.201
subin@k3s-1:~$ 프롬프트가 비번 없이 뜨면 성공이다(exit로 빠져나온다). 세 노드 다 이렇게 열리면, 데스크탑이 세 노드의 관제탑이 됐다.
② 게스트 에이전트 — 웹UI가 노드 안을 들여다보게
§4 System 탭에서 Qemu Agent를 켠 건 호스트(Proxmox) 쪽 절반 — "게스트와 대화할 채널을 열어둔다"까지였다. 나머지 절반은 게스트 안에서 그 채널에 응답할 프로그램인데, 이걸 각 노드에 깐다. 이게 있어야 웹UI가 노드의 실제 IP를 읽고, Shutdown이 게스트 안에서 정상 종료로 이어진다.
이제 SSH가 되니 Console 대신 데스크탑에서 노드에 붙어 설치한다. 각 노드마다:
ssh subin@192.168.0.201
붙은 다음, 노드 안에서:
sudo apt update
sudo apt install -y qemu-guest-agent
apt update로 패키지 목록을 새로 받고, install로 에이전트를 깐다. 이 패키지는 설치만 하면 끝이다 — 별도로 시작 명령을 줄 필요 없이, Ubuntu가 가상 통신 포트를 감지해 자동으로 실행한다(설치 로그에 "이 서비스는 systemctl로 켜고 끄는 종류가 아니다"라고 안내가 뜨는데, 정상이다). 마치면 exit로 나와 다음 노드로 넘어간다.
세 노드에 다 깔면 확인한다. 웹UI에서 노드 Summary를 보면, §5 도입에서 "Guest Agent not running"이던 IPs 칸에 이제 노드의 실제 IP(192.168.0.201 등)가 뜬다. 호스트와 게스트 양쪽이 연결됐다는 증거다.
이걸로 세 노드가 데스크탑에서 비번 없이 관리되고, 웹UI가 각 노드를 들여다보는 상태가 됐다. 노드 준비 완료다.
정리 — 밑단부터 노드까지, 지금 서 있는 전체 구조
1부부터 5부까지 쌓아온 것을 한 장으로 세우면 이렇게 된다. 맨 아래 물리 하드웨어에서 시작해, 그 위에 하이퍼바이저, 그 위에 VM 세 대가 올라간 모습이다. 아직 k3s를 얹기 직전 — 노드들은 서 있지만 서로 남남인 상태다.
┌───────────────────────────────────────────────────────────────┐
│ VM k3s-1 │ VM k3s-2 │ VM k3s-3 │ │
│ (게스트 계층) │ │ │ │
│ ┌────────────┐ │ ┌────────────┐ │ ┌────────────┐ │ │
│ │Ubuntu 24.04│ │ │Ubuntu 24.04│ │ │Ubuntu 24.04│ │ │
│ │ +OpenSSH │ │ │ +OpenSSH │ │ │ +OpenSSH │ │ │
│ │ +guest-ag │ │ │ +guest-ag │ │ │ +guest-ag │ │ │
│ ├────────────┤ │ ├────────────┤ │ ├────────────┤ │ │
│ │4vCPU·8GB고정│ │ │4vCPU·8GB고정│ │ │4vCPU·8GB고정│ │ │
│ │ 부트40G ext4│ │ │ 부트40G ext4│ │ │ 부트40G ext4│ │ │
│ └─────┬──────┘ │ └─────┬──────┘ │ └─────┬──────┘ │ │
│ .201 │ ens18 │ .202 │ ens18 │ .203 │ ens18 │ │
└────────┼─────────┴────────┼─────────┴────────┼─────────┘
└──────────────────┼──────────────────┘
┌──────┴──────┐
│ vmbr0 │ 가상 스위치 (Proxmox가 만든)
└──────┬──────┘
┌────────────────────────────┼──────────────────────────────────┐
│ Proxmox VE (호스트/하이퍼바이저) · 호스트 .200 │
│ KVM(연산) + QEMU(부품) │ local-lvm thin 풀 875G ──┐ │
│ │ (VM 디스크가 여기서 잘림) │ │
└──────────┬───────────────────────────────────────────┼────────┘
nic0 │ (물리 유선 랜포트) │
┌──────────┼───────────────────────────────────────────┼────────┐
│ 물리 하드웨어 (노트북 ThinkBook) │ │
│ 랜포트 ──┘ · 외장 USB SSD 1TB(Proxmox·thin이 삶) ─┘ │
│ · 내장 NVMe = Windows (안 건드림) │
└───────────────────────────┬───────────────────────────────────┘
nic0 │
┌──────┴──────┐
│ 공유기 AX12 │ 사설망 192.168.0.x · GW .1
│ (내 골목) │ → 데스크탑(콘솔)도 같은 골목
└─────────────┘
아래에서 위로 쌓인다.
- 물리 노트북(외장 SSD·랜포트)
- 그 위에 Proxmox(KVM+QEMU가 VM을 굴리고, thin 풀이 디스크를 자른다)
- vmbr0라는 가상 스위치
- 그 위에 VM 세 대. 각 VM은 4vCPU·8GB·부트 40GB에 Ubuntu가 깔렸고,
- ens18로 vmbr0에 붙어 골목의 .201/.202/.203을 받았다.
- 데스크탑은 같은 공유기에 붙은 콘솔로, 이 전체를 밖에서 관리한다.
세 대가 이 구조에서 hostname(k3s-2·k3s-3)과 IP(.202·.203) 둘만 다르고 나머지는 판박이다. 몸통은 다 섰지만 아직 서로 남남이다 — 6부에서 각 노드에 데이터 디스크(그림의 thin 풀에서 잘라 붙인다)를 얹고 k3s로 묶어, 이 셋을 "노드 하나 죽어도 사는" 한 클러스터로 만든다.



6. 다음
5부에서 빈 Proxmox 서버 한 대를 노드 세 대로 쪼갰다 — Ubuntu가 깔리고 고정 IP로 서서, 데스크탑에서 ssh로 다 붙는 상태다. 다만 아직은 따로 노는 서버 세 대일 뿐, 서로의 존재를 모른다.
6부에서 이 셋을 하나로 묶는다. 각 노드에 데이터 디스크를 붙이고(§1에서 미룬 그것), 그 위에 k3s를 세워 한 클러스터로 합친다. 그러면 etcd 세 대가 과반 합의를 이루고, §1에서 목표로 잡았던 "노드 하나가 죽어도 사는" HA가 실물로 성립한다.
'HomeLab' 카테고리의 다른 글
| 노트북 홈서버 k3s 구축기 - 7. 플랫폼 부트스트랩 (0) | 2026.07.29 |
|---|---|
| 노트북 홈서버 k3s 구축기 - 6. 데이터 디스크와 k3s 클러스터 (0) | 2026.07.21 |
| 노트북 홈서버 k3s 구축기 - 4. Proxmox 설치 직후 설정 (1) | 2026.07.19 |
| 노트북 홈서버 k3s 구축기 - 3. 내 골목 개통, 그리고 Proxmox 설치 (0) | 2026.07.19 |
| 노트북 홈서버 k3s 구축기 - 2. usb 굽기 및 홈 네트워크 분석 (0) | 2026.07.16 |