- Published on
HW2. Apple Silicon에서 KVM으로 HTCondor용 VM 3대 만들기
- Authors

- Name
- seren-wib
Contents
- 0. 실습 환경
- 1. HTCondor 클러스터용 VM 만들기
- (a) 사설 고정 IP 지정
- (b) 부팅 타임아웃 0 (GRUB_TIMEOUT=0)
- (c) 명령어로 VM 3대 복제
- (d) 복제된 VM에 GRUB_TIMEOUT=0이 남아 있는지 확인
- (e) /etc/hosts에 세 호스트네임 등록
- (f) 호스트에서 세 VM으로 ping
- (g) VM끼리 호스트네임으로 ping
- (h) SSH로 복제된 VM에 접속
- 2. SSH 키 인증 (Ch04-Extra)
- (a) 호스트에서 개인키로 세 VM에 접속
- (b) VM끼리 개인키로 접속
- 복제하면서 겪은 문제
- 부록. 중첩 가상화에서 Rocky 부팅이 멈춘 문제
- 증상
- 먼저 해결한 두 문제
- 원인 분석
- 해결
- 재현에 필요한 설정
교재 4장 "VirtualBox 기반 HTCondor 클러스터 생성"을 따라 가상머신 3대를 만들고 SSH 키 인증까지 설정한 과제다. 문제는 내 노트북이 Apple M3(ARM64) 맥이라는 거였다. x86용 VirtualBox VM을 그대로 쓸 수 없어서 하이퍼바이저를 KVM/libvirt로 바꿔서 진행했다.
0. 실습 환경
macOS (Apple M3)
└─ UTM (Apple Virtualization, 중첩 가상화)
└─ Ubuntu 24.04 ARM64 (호스트 yoosung-vm, 강의의 "Ubuntu 호스트")
└─ KVM + libvirt (VirtualBox 대체)
├─ main Rocky Linux 10.2 192.168.56.101 main.cloud.org
├─ work1 Rocky Linux 10.2 192.168.56.102 work1.cloud.org
└─ work2 Rocky Linux 10.2 192.168.56.103 work2.cloud.org
교재(VirtualBox)와 바꾼 부분은 이렇다.
| 교재 (VirtualBox) | 이 환경 (KVM/libvirt) |
|---|---|
| Host-only 어댑터 vboxnet0 (192.168.56.0/24) | libvirt 격리 네트워크 cloud56 (브리지 virbr56, 호스트 192.168.56.1) |
| NAT 어댑터 | libvirt default NAT 네트워크 (192.168.122.0/24) |
VBoxManage clonevm | virt-clone --auto-clone |
| 인터페이스 enp0s3 / enp0s8 | enp1s0 (NAT) / enp2s0 (사설 고정 IP) |
커널 인자 pci=nomsi: 중첩 가상화에서는 게스트 virtio 장치의 MSI 인터럽트가 전달되지 않아 Rocky 부팅이 멈췄다. 커널 인자에 pci=nomsi를 넣어 예전 INTx 인터럽트를 쓰게 해서 해결했다(자세한 건 맨 아래). GRUB_TIMEOUT=0으로 바꾸면 GRUB 메뉴에서 커널 인자를 손으로 넣을 수 없게 되니까, 이 인자를 부트로더에 먼저 영구 반영하고 나서 타임아웃을 0으로 바꿨다.
작업 순서: 원본 VM rocky-base에 GRUB_TIMEOUT=0을 설정하고 → 3대로 복제한 다음 → 각 VM에 고정 IP·호스트네임과 /etc/hosts를 설정했다. 실제로는 (b) → (c) → (a) 순서였지만 아래는 과제 항목 순서대로 정리했다.
1. HTCondor 클러스터용 VM 만들기
(a) 사설 고정 IP 지정
사설 네트워크 인터페이스 enp2s0에 NetworkManager로 고정 IP(192.168.56.101~103/24)를 줬다. 외부로 안 나가는 내부망이라 게이트웨이는 지정하지 않았다. 호스트네임도 같이 바꿨다.
sudo nmcli con mod enp2s0 ipv4.method manual ipv4.addresses 192.168.56.101/24 connection.autoconnect yes
sudo nmcli con up enp2s0
sudo hostnamectl set-hostname main.cloud.org
work1, work2도 IP를 .102, .103으로, 호스트네임을 work1.cloud.org, work2.cloud.org로 바꿔서 똑같이 실행했다.

설정 전 main. enp2s0은 UP이지만 IP가 없다.

main: enp2s0 = 192.168.56.101/24, hostname = main.cloud.org

work1: enp2s0 = 192.168.56.102/24

work2: enp2s0 = 192.168.56.103/24
(b) 부팅 타임아웃 0 (GRUB_TIMEOUT=0)
복제 전에 원본 VM(rocky-base)에서 /etc/default/grub의 GRUB_TIMEOUT을 0으로 바꾸고 grub2-mkconfig로 /boot/grub2/grub.cfg를 다시 만들었다.
/etc/default/grub은 grub.cfg를 만들 때 쓰는 설정 파일이고, 부팅할 때 GRUB이 실제로 읽는 건 grub.cfg다. 그래서 고친 다음 꼭 다시 생성해야 한다.
sudo sed -i 's/^GRUB_TIMEOUT=.*/GRUB_TIMEOUT=0/' /etc/default/grub
sudo grub2-mkconfig -o /boot/grub2/grub.cfg
sudo reboot

GRUB_TIMEOUT=0, 커널 인자에 pci=nomsi 유지

grub2-mkconfig로 grub.cfg 재생성

재부팅하면 GRUB 메뉴 없이 곧바로 커널 부팅이 시작된다.

부팅 완료 후 로그인 프롬프트

/proc/cmdline: 재부팅 후에도 pci=nomsi가 적용돼 있다.

생성된 grub.cfg의 67·71행 set timeout=0
(c) 명령어로 VM 3대 복제
VBoxManage clonevm 대신 libvirt의 virt-clone을 썼다. 원본을 끈 상태에서 실행해야 한다. --auto-clone은 디스크 파일(qcow2)과 UEFI 변수 파일(VARS.fd)을 새 이름으로 복사하고 MAC 주소를 새로 만든다.
sudo virsh shutdown rocky-base
sudo virt-clone --original rocky-base --name main --auto-clone
sudo virt-clone --original rocky-base --name work1 --auto-clone
sudo virt-clone --original rocky-base --name work2 --auto-clone
virsh list --all

virt-clone으로 main·work1·work2 생성, virsh list --all에 4대가 보인다.
진행 표시에서 main_VARS.fd가 "0 B"로 나오지만, 아래 ls -l을 보면 4개 파일 모두 67,108,864바이트(64MB)로 제대로 복사됐다. 진행률 표시만 0으로 찍힌 거다.
(d) 복제된 VM에 GRUB_TIMEOUT=0이 남아 있는지 확인
복제는 디스크 전체를 복사하니까 GRUB 설정도 그대로 따라온다. 세 VM 모두에서 설정 파일과 실제 부트 설정을 확인했다.
grep GRUB_TIMEOUT /etc/default/grub
sudo grep -n "set timeout=" /boot/grub2/grub.cfg

main: GRUB_TIMEOUT=0, grub.cfg set timeout=0

work1: 동일

work2: 동일
160행의 ${menu_show_once_timeout}은 "다음 부팅 한 번만 메뉴 표시" 기능용 변수라 평소 부팅에는 적용되지 않는다.
(e) /etc/hosts에 세 호스트네임 등록
세 VM 모두 /etc/hosts 끝에 세 줄을 추가했다. 각 줄은 IP, FQDN, 짧은 별칭 순서다.
sudo tee -a /etc/hosts > /dev/null <<'EOF'
192.168.56.101 main.cloud.org main
192.168.56.102 work1.cloud.org work1
192.168.56.103 work2.cloud.org work2
EOF

main의 /etc/hosts (8~10행)

work1의 /etc/hosts

work2의 /etc/hosts
(f) 호스트에서 세 VM으로 ping
호스트(Ubuntu)의 가상 브리지 virbr56(192.168.56.1)에서 세 VM의 IP로 ping을 보냈다. 세 대 모두 패킷 손실 0%였다.

호스트 → 192.168.56.101~103 ping, 모두 0% packet loss
(g) VM끼리 호스트네임으로 ping
각 VM에서 ping -c 2 main / work1 / work2를 실행했다. 별칭이 /etc/hosts대로 main.cloud.org(192.168.56.101) 등으로 변환돼서 응답이 왔다.
자기 자신한테는 약 0.10.4ms, 다른 VM으로는 약 39ms가 나왔다. 중첩 가상화를 거치는 만큼 VM 사이 지연이 더 크다.

main에서 hostname으로 ping

work1에서 hostname으로 ping

work2에서 hostname으로 ping
(h) SSH로 복제된 VM에 접속
호스트에서 세 VM에 비밀번호로 SSH 접속하고 hostname으로 맞는 VM인지 확인했다. 첫 접속이라 호스트 키 확인 질문이 나와서 yes로 known_hosts에 등록했다.

호스트 → main·work1·work2 SSH 접속 (비밀번호 인증)
세 VM의 호스트 키가 똑같다. ED25519 호스트 키 지문이 모두
SHA256:C+o0I1…로 같다. virt-clone은 디스크를 그대로 복사하니까/etc/ssh/ssh_host_*키까지 복제되기 때문이다. 그래서 두 번째 접속부터 "이 키는 다른 이름으로도 알려져 있다"는 메시지가 나온다. 실습엔 문제없지만 실제 운영에서는 복제 후 호스트 키를 다시 만들어야 서버를 구별할 수 있다.
2. SSH 키 인증 (Ch04-Extra)
(a) 호스트에서 개인키로 세 VM에 접속
호스트에서 ED25519 키 쌍을 만들고, ssh-copy-id로 공개키를 세 VM의 ~/.ssh/authorized_keys에 등록했다. 이후 접속은 호스트의 개인키(~/.ssh/id_ed25519)로 인증되니까 비밀번호를 묻지 않는다. 개인키는 호스트 밖으로 안 나가고 VM에는 공개키만 복사된다.
ssh-keygen -t ed25519
ssh-copy-id yoosung@192.168.56.101
ssh-copy-id yoosung@192.168.56.102
ssh-copy-id yoosung@192.168.56.103

호스트에서 ed25519 키 쌍 생성 (개인키 id_ed25519, 공개키 id_ed25519.pub)

ssh-copy-id로 공개키 등록. VM마다 "Number of key(s) added: 1"

호스트 → 세 VM 키 인증 접속. 비밀번호 없이 각 hostname이 출력된다.
(b) VM끼리 개인키로 접속
세 VM에서 각자 ED25519 키 쌍을 만들고, 나머지 두 VM에 호스트네임으로 공개키를 등록했다. 총 6방향(3대 × 2)이다. VM마다 자기 개인키를 따로 가지니까 한 대의 키가 유출돼도 다른 VM의 키는 영향을 안 받는다.
ssh-keygen -t ed25519
ssh-copy-id work1
ssh-copy-id work2
main 기준이고, work1은 main·work2, work2는 main·work1로 대상만 바꿔서 실행했다.

main → work1, work2 (비밀번호 없이 접속)

work1 → main, work2

work2 → main, work1
복제하면서 겪은 문제
- 시리얼 로그 경로 충돌: 복제된 VM이 원본의 시리얼 로그 경로(
/var/log/rocky-base-serial.log)까지 그대로 물려받아서, 두 번째 VM을 켤 때 "Device or resource busy"로 실패했다.virsh dumpxml로 설정을 꺼내 경로를 VM 이름별로 바꾸고virsh define으로 다시 등록해서 해결했다. - 같은 호스트 키: 위 (h)에서 말한 것처럼 디스크 복제 때문에 SSH 호스트 키가 세 대 모두 같다.
부록. 중첩 가상화에서 Rocky 부팅이 멈춘 문제
가장 오래 막혔던 건 Rocky Linux 게스트가 커널 부팅 7~8초 지점에서 멈추는 현상이었다. 결론부터 말하면 중첩 가상화 환경에서 MSI 인터럽트가 게스트에 전달되지 않아서 생긴 문제였고, 게스트 커널 인자 pci=nomsi 한 줄로 해결했다. KVM을 끄지 않고 해결해서 구성도 바꿀 필요가 없었다.
증상
Rocky Linux 10.2 aarch64 게스트를 KVM으로 올리면 커널 부팅 7~8초 지점에서 멈추고 더 진행하지 않았다. 도메인 상태는 running이고 cpu.time도 계속 올라갔다. VM은 켜져 있고 CPU도 쓰는데 부팅만 안 되는 상태다. 2코어로 돌리면 60초 뒤 RCU stall 경고까지 찍혔다.
[ 7.300568] pcieport 0000:00:01.1: PME: Signaling with IRQ 51
[ 67.306241] rcu: INFO: rcu_preempt detected stalls on CPUs/tasks:
[ 67.346763] rcu: Possible timer handling issue on cpu=0 timer-softirq=151
[ 67.354764] rcu: rcu_preempt kthread starved for 6002 jiffies!
[ 67.434740] rcu: Sending NMI from CPU 1 to CPUs 0:
그래픽 콘솔에서는 Display output is not active로만 보여서 원인을 알 수가 없었다. 시리얼 콘솔 출력을 파일로 남기도록 바꾸고 나서야 위 로그를 얻었다. 부팅이 어디까지 갔는지는 시리얼 로그 파일 크기로 비교했다. 정상 부팅이면 설치 프로그램 화면까지 로그가 계속 늘어난다.
먼저 해결한 두 문제
부팅 정지를 보기 전에 다른 문제 두 개가 먼저 나왔다.
① 시큐어 부트 펌웨어
virt-install에 --boot uefi만 주면 libvirt가 Microsoft 키가 등록된 AAVMF_CODE.ms.fd를 고른다. 이 펌웨어는 서명 안 된 커널을 거부해서 이런 오류가 났다.
error: ../../grub-core/loader/efi/linux.c:723:failed to install protocols.
Failed to boot both default and fallback entries.
시큐어 부트가 꺼진 펌웨어를 직접 지정해서 해결했다. 게스트 커널 로그에 secureboot: Secure boot disabled가 찍히는 것도 확인했다.
--boot loader=/usr/share/AAVMF/AAVMF_CODE.no-secboot.fd,loader.readonly=yes,loader.type=pflash,loader.secure=no,nvram.template=/usr/share/AAVMF/AAVMF_VARS.fd
② 그래픽 콘솔이 안 뜸
aarch64 게스트는 SPICE 콘솔에 화면이 안 뜨고 Display output is not active만 보인다. 게다가 --cdrom으로는 커널 인자를 넘길 수 없다. 설치 방식을 이렇게 바꿨다.
--location <ISO>: ISO에서 vmlinuz와 initrd.img를 꺼내 직접 부팅한다. GRUB을 안 거쳐서 ①의 오류도 같이 피한다.--graphics none+--extra-args "console=ttyAMA0,115200 inst.text": 설치 프로그램을 시리얼 텍스트 모드로 띄운다.--serial pty,log.file=<경로>: 시리얼 출력을 파일로 남긴다.virsh console은 접속 시점에 따라 초기 출력을 놓쳐서 빈 화면으로 보일 때가 많다. 파일로 남겨야 부팅 로그를 처음부터 볼 수 있었다.
이 두 개를 해결하고 나서야 7~8초에서 멈추는 문제를 제대로 볼 수 있었다.
원인 분석
조건을 하나씩 바꿔 가며 네 번 돌려서 원인을 좁혔다.
| 실험 | 바꾼 것 | 시리얼 로그 크기 | 결과 |
|---|---|---|---|
| 1 | vCPU 2 (기준) | 46,222 B | 7.30초 정지 |
| 2 | vCPU 1 | — | 8.22초 정지 |
| 3 | --virt-type qemu (TCG 에뮬레이션) | 115,139 B | 정상 진입 |
| 4 | KVM 유지 + pci=nomsi | 98,182 B | 정상 진입 |
가설 1. 중첩 환경의 타이머 문제 (틀림)
처음엔 RCU stall 메시지의 Possible timer handling issue on cpu=0을 보고 타이머 전달 실패를 의심했다. L1(Ubuntu)의 dmesg에서 kvm [1]: Hyp nVHE mode initialized successfully도 찾았다. 보통 ARM 하드웨어에서 KVM은 VHE 모드로 올라오는데 여기선 nVHE였다. Apple 가상화가 중첩용 EL2는 주지만 VHE 기능은 안 준다는 뜻이라 타이머에도 문제가 있을 수 있다고 봤다.
하지만 실험 1과 2는 멈춘 줄이 서로 달라서 특정 드라이버 하나의 문제로 보기 어려웠다. 그리고 두 번 다 멈추기 직전에 PCI 장치를 깨우고 있었다. 그래서 RCU stall은 인터럽트를 못 받은 뒤에 따라 나온 증상으로 보고 이 가설을 버렸다.
가설 2. 중첩 KVM 자체의 문제 (범위를 좁힘)
같은 ISO·커널·펌웨어에서 --virt-type qemu로 KVM 없이 TCG 에뮬레이션으로 돌렸더니 설치 프로그램까지 정상으로 들어갔다(실험 3). 바꾼 게 KVM 하나뿐이니 문제는 중첩 가상화 쪽이다. 다만 TCG는 10~20배 느려서 실습에는 못 쓰고 원인을 가리는 데만 썼다.
가설 3. MSI 인터럽트 전달 실패 (실제 원인)
두 번 다 PCI 장치에서 멈췄으니 인터럽트 경로를 의심했다. ARM에서 PCI 장치는 MSI-X로 인터럽트를 보내고 인터럽트 컨트롤러(GIC)가 받는다. L1의 dmesg에서 이걸 확인했다.
GICv2m: range[mem 0x1fff0000-0x1fff0fff], SPI[128:255]
kvm [1]: GICv3: no GICV resource entry
kvm [1]: disabling GICv2 emulation
L1에는 MSI를 처리하는 ITS가 없고 예전 방식인 GICv2m을 쓰고 있었다. 이 구성에서는 MSI가 게스트까지 안 가서, virtio-pci 장치가 보낸 인터럽트를 게스트가 못 받고 그 자리에서 멈춘 걸로 보인다.
해결
게스트 커널 인자에 pci=nomsi 하나를 추가했다. 이러면 게스트가 MSI 대신 예전 방식인 INTx로 인터럽트를 받아서 문제 있는 경로를 아예 안 쓴다.
--extra-args "console=ttyAMA0,115200 inst.text pci=nomsi"
| 조건 | 시리얼 로그 | 결과 |
|---|---|---|
| KVM, 인자 없음 | 46,222 B | 7~8초 정지 |
KVM, pci=nomsi | 98,182 B | 설치 프로그램 정상 진입 |
이 방법을 고른 이유
- KVM을 끄지 않아도 된다. TCG처럼 느려지지 않는다.
- 구성을 안 바꾼다. 추가한 건 게스트 커널 인자 한 줄뿐이다.
- 단점은 I/O가 조금 느려지는 정도다. INTx는 MSI보다 효율이 떨어지지만 VM 3대짜리 실습에서는 문제없었다.
재현에 필요한 설정
| 항목 | 값 | 이유 |
|---|---|---|
| 펌웨어 | AAVMF_CODE.no-secboot.fd | 시큐어 부트 펌웨어는 서명 없는 커널을 거부 |
| 부팅 방식 | --location <ISO> | 커널 인자를 넘기려고 직접 부팅 |
| 콘솔 | --graphics none + --serial pty,log.file= | aarch64는 그래픽 콘솔이 안 뜸, 초기 로그 보존 |
| 커널 인자 | pci=nomsi | 중첩 환경에서 MSI가 전달되지 않는 문제 회피 |
| 패키지 | qemu-efi-aarch64 포함 | aarch64는 BIOS가 없어서 UEFI 펌웨어가 필요 |