우분투에서 kvm을 이용해 가상머신 생성하기
컴퓨터에 우분투를 깔아서 쿠버네티스 환경을 만들기 위해 가상머신 여러개를 만들어 클러스터로 묶는 작업을 해야한다 이전에는 윈도우 환경에서 virtual box를 이용해서 구성을 해봤지만 지금은 우분투에서 kvm + qemu + libvirt + terraform을 이용해서 한번에 가상머신을 구축해본다
개념
KVM: 리눅스 커널을 하이퍼바이저로
KVM (Kernel-based Virtual Machine)은 리눅스 커널에 내장된 타입 1 하이퍼바이저(Type 1 Hypervisor) 기술이다 여기서 타입 1 하이퍼바이저란, 운영체제가 하드웨어 위에서 직접 실행되는 것처럼, 하드웨어 위에서 바로 동작하여 가상 머신을 제어하는 방식을 의미한다

주요 특징
- 커널 모듈 (Kernel Module) KVM은 별도의 프로그램이 아니라 리눅스 커널의 일부로 동작하는 모듈(
kvm.ko)modprobe kvm-intel이나modprobe kvm-amd같은 명령어로 활성화된다 이 방식은 리눅스 커널이 가진 강력한 메모리 관리, 프로세스 스케줄링 등의 기능을 그대로 활용할 수 있어 매우 효율적이고 안정적임 즉, 리눅스 자체가 하이퍼바이저가 되는 것 - 하드웨어 가상화 지원 (Hardware-assisted Virtualization) KVM의 핵심 역할은 CPU 제조사(Intel, AMD)가 제공하는 하드웨어 가상화 기능(Intel VT-x, AMD-V)을 사용하는 것 KVM은 이 하드웨어 기능을 제어할 수 있는 통로(
/dev/kvm이라는 특수 장치 파일)를 만들고, 이 통로를 통해 가상 머신의 CPU 명령어를 실제 하드웨어 CPU가 직접, 그리고 안전하게 처리하도록 중계한다 - CPU와 메모리 중심 KVM은 가상화의 가장 핵심적인 부분, 즉 CPU와 메모리 가상화에만 집중 가상 디스크, 가상 네트워크 카드, USB 장치 등 주변 장치(I/O)가 어떻게 동작하는지는 KVM의 관심사가 아니라서 이 부분은 QEMU와 같은 다른 프로그램의 도움이 필요하다
QEMU: 다재다능한 가상 머신 에뮬레이터
QEMU (Quick EMUlator)는 본래 하드웨어 에뮬레이터로 시작했으며, 현재는 KVM의 가장 중요한 파트너로 활약하는 유저스페이스(userspace) 프로그램 커널이 아닌 일반 애플리케이션처럼 실행된다
핵심 역할과 두 가지 모드
- 순수 에뮬레이션 모드 (Full Emulation Mode) QEMU의 본래 기능 이 모드에서 QEMU는 Binary Translation중에서 TCG (Tiny Code Generator)라는 동적 번역기를 사용하여 한 종류의 CPU 아키텍처용 코드를 다른 종류의 CPU에서 실행할 수 있도록 실시간으로 번역한다 예를 들어, 인텔 x86 PC에서 ARM CPU용으로 만들어진 안드로이드 OS를 실행하는 것이 가능 이 방식은 매우 유연하지만, 모든 것을 소프트웨어로 처리하기 때문에 성능이 매우 느리다
- 가속기 모드 (Accelerator Mode with KVM) 오늘날 리눅스 가상화의 표준 방식 QEMU가 KVM의 존재를 감지하면, CPU와 메모리 관련 작업을 직접 처리하지 않고
/dev/kvm장치를 통해 KVM에 모두 위임한다 이 모드에서 QEMU의 역할은 다음과 같이 바뀐다- I/O 하드웨어 에뮬레이션: KVM이 신경 쓰지 않는 디스크 컨트롤러, 네트워크 카드(NIC), 그래픽 카드, USB 포트 등 모든 주변 장치를 에뮬레이션
- 가상 머신 생명주기 관리: 가상 머신의 BIOS 또는 UEFI를 제공하고, 전체적인 시스템을 구성하고 실행
- VirtIO 지원: 성능 향상을 위해 QEMU는 VirtIO라는 표준화된 고성능 가상 장치 드라이버를 제공 게스트 OS에 VirtIO 드라이버를 설치하면, 일반적인 하드웨어를 에뮬레이션하는 것보다 훨씬 빠른 속도로 디스크 및 네트워크 I/O를 처리할 수 있다
libvirt: 일관된 가상화 관리 계층
libvirt는 다양한 가상화 기술을 관리하기 위한 오픈소스 API, 데몬(daemon), 그리고 관리 도구의 집합 KVM+QEMU의 복잡성을 감추고 사용자에게 편리하고 일관된 인터페이스를 제공하는 것이 주 목적이다
주요 기능 및 구성요소
- 추상화 API (Abstraction API) libvirt의 가장 큰 가치는 추상화 사용자는 백엔드 기술이 KVM인지, Xen인지, 혹은 VirtualBox인지 신경 쓸 필요 없이 동일한 libvirt API와 명령어를 사용할 수 있다
- 데몬 (libvirtd) 백그라운드에서 항상 실행되며 API 요청을 받아 처리하는 핵심 서비스
virsh나virt-manager같은 클라이언트 도구로부터 명령을 받아서 실제 QEMU 프로세스를 실행하고 관리한다 - 관리 도구 (Management Tools) libvirt는 사용자가 상호작용할 수 있는 여러 도구를 제공
virsh: 가장 대표적인 커맨드 라인 인터페이스(CLI), VM의 시작, 중지, 상태 확인, 실시간 마이그레이션 등 거의 모든 관리가 가능virt-manager: 데스크톱 환경에서 사용하는 그래픽(GUI) 관리 도구virt-install: 커맨드 라인에서 VM 설치 및 생성을 도와주는 도구=
- XML 기반 설정 libvirt는 VM의 모든 설정(CPU 개수, 메모리 크기, 디스크 경로, 네트워크 연결 등)을 표준화된 XML 형식으로 정의하고 관리할 수 있다 이 XML 파일 덕분에 VM 설정을 쉽게 복제하거나, 버전 관리 시스템(Git 등)으로 추적하고, 다른 시스템으로 이전하는 것이 매우 용이하다 이과정을 또 다른 방법을 이용할 수 있는데 그 방법이 terraform이다
Terraform: 인프라를 코드로 관리(IaC)
인프라를 코드로 관리(IaC)하는 도구로, 명령어와 설정 파일(코드)을 통해 로컬 가상머신(VM)과 퍼블릭 클라우드 리소스를 모두 생성하고 관리할 수 있다
로컬 VM 환경에서 Terraform 사용법
로컬 환경(사용자 컴퓨터, 사내 서버 등)에서 Terraform을 사용할 때는 주로 KVM, VirtualBox, vSphere 같은 하이퍼바이저와 연동되는 ‘프로바이더(Provider)’를 이용한다 프로바이더는 Terraform이 특정 플랫폼의 API를 이해하고 제어할 수 있게 해주는 플러그인
작동 방식:
- 프로바이더 정의: 사용자는 설정 파일(
.tf)에 KVM과 같은 로컬 하이퍼바이저를 위한 프로바이더를 지정 - 리소스 정의: 동일한 파일에 생성할 VM의 사양(CPU, 메모리, 디스크 이미지 경로, 네트워크 등)을 코드로 작성
- 실행: 터미널에서
terraform apply명령어를 실행하면 Terraform이 해당 프로바이더를 통해 KVM의 API(내부적으로는 libvirt)를 호출하여 정의된 사양대로 VM을 생성.
이 방식은 Vagrant와 유사하게 개발 환경을 코드로 관리하고, 동일한 VM을 반복적으로 만들거나 팀원과 공유할 때 매우 유용하다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
terraform {
required_providers {
libvirt = {
source = "dmacvicar/libvirt"
}
}
}
# libvirt 프로바이더 설정
provider "libvirt" {
uri = "qemu:///system"
}
# KVM 가상머신 리소스 정의
resource "libvirt_domain" "my_vm" {
name = "terraform-vm-local"
memory = "2048"
vcpu = "2"
# ... 디스크 및 네트워크 설정
}
퍼블릭 클라우드 환경에서 Terraform 사용법
클라우드에서는 VM 하나를 넘어 네트워크, 스토리지, 데이터베이스, 로드 밸런서 등 인프라 전체를 코드로 다룰 수 있다
작동 방식:
- 프로바이더 정의: 설정 파일에 사용할 클라우드(예:
aws)의 프로바이더를 정의하고, API 접근을 위한 인증 정보(Access Key 등)를 설정 - 리소스 정의: VM(AWS에서는
EC2 인스턴스)뿐만 아니라, 가상 사설 네트워크(VPC), 서브넷, 보안 그룹(Security Group), 데이터베이스(RDS) 등 서비스에 필요한 모든 자원을 코드로 정의 - 실행:
terraform apply를 실행하면 Terraform이 해당 클라우드 제공자의 API 엔드포인트로 네트워크 요청을 보내 정의된 모든 리소스를 자동으로 생성하고 연결
이 방식은 복잡한 클라우드 아키텍처를 문서화하고, 버전 관리(Git)하며, 재사용할 수 있게 해줍니다. 클릭 몇 번의 실수로 장애가 발생하는 것을 막고, 언제나 동일한 구조의 인프라를 안정적으로 구축할 수 있게 됩니다.
앤서블(Ansible)
- 서버 설정 관리: 여러 서버에 동일한 설정을 적용하거나 특정 상태를 유지하도록 관리
- 소프트웨어 배포: 서버에 애플리케이션이나 여러 소프트웨어를 자동으로 설치하고 배포
- 작업 자동화: 반복적인 시스템 관리 작업을 스크립트(플레이북)로 만들어 자동화
앤서블의 가장 큰 특징은 Agentless이다 관리할 서버에 별도의 프로그램을 설치할 필요없이, SSH 연결만으로 모든 작업을 수행할 수 있어 매우 편리하다 YAML 형식의 플레이북(Playbook)을 사용하여 작업 순서를 정의하는데, 사람이 읽고 이해하기 쉬운 형태
테라폼과의 차이점:
- 테라폼: 클라우드 인프라(가상 머신, 네트워크, 스토리지 등)를 코드로 생성하고 관리 (집을 짓는 역할)
- 앤서블: 이미 생성된 인프라 위에 소프트웨어를 설치하고 설정하는 역할 (지어진 집에 가구와 가전을 배치하는 역할)
구성
1. KVM 및 관련 패키지 설치
1
2
sudo apt update
sudo apt install qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils virt-manager -y
qemu-kvm: 가상머신을 실행하는 핵심 패키지libvirt-*: 가상머신을 관리하고 제어하는 라이브러리 및 도구bridge-utils: 가상 네트워크를 설정하는 도구virt-manager: 가상머신을 쉽게 생성하고 관리할 수 있는 GUI(그래픽 인터페이스) 관리자
2. 사용자 계정 권한 설정
가상머신을 sudo 명령어 없이 편리하게 관리하려면 현재 사용자를 libvirt와 kvm 그룹에 추가
1
2
sudo adduser $USER libvirt
sudo adduser $USER kvm
권한 설정을 적용하려면 시스템을 완전히 재부팅하거나, 로그아웃 후 다시 로그인
3. KVM 서비스 상태 확인
패키지 설치와 권한 설정이 완료되면, libvirtd 서비스가 잘 실행되고 있는지 확인
1
systemctl is-active libvirtd
4. Terraform VM 생성하기
필수 패키지 설치 및 GPG 키 추가
먼저 Terraform 패키지 저장소를 신뢰하기 위한 GPG 키를 시스템에 추가한다
1
2
sudo apt-get update && sudo apt-get install -y gnupg software-properties-common
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
HashiCorp 저장소 추가
GPG 키를 확인한 후, 시스템에 HashiCorp 공식 저장소를 추가
1
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
Terraform 설치
이제 패키지 목록을 업데이트하고 Terraform을 설치
1
2
3
sudo apt update
sudo apt install terraform -y
sudo apt install genisoimage
genisoimage는 테라폼에서 cloud-init iso 이미지를 생성하는 동안 mkisofs 명령어를 찾을 수 없을때 발생 추가로 설치해야한다
미리 정의된 테라폼을 이용할것 클론github
main.tf 파일에 ssh 키를 넣어야 나중에 ansible을 원활하게 할 수 있다
#### Terraform 실행
1
2
3
4
5
6
#main.tf 가 존재하는 위치에서 실행한다
terraform init
terraform plan # 설정확인
terraform apply # 적용
terraform apply --var vm=4 # 가상머신 4개 생성 개수 변경 가능 기본값 3
tf 파일 실행시 권한 오류 해결

위 권한 오류가 발생한다면 위 링크에 해결 방법이 있다
해당 파일 맨 아래줄을 확인
/etc/apparmor.d/abstractions/libvirt-qemu1
vi /etc/apparmor.d/abstractions/libvirt-qemu
아래 명령어가 없다면 추가한다 (맨 아래에는 없고 맨아래 주석위에 존재했음)
1
include if exists <abstractions/libvirt-qemu.d>폴더가 없다면 생성
/etc/apparmor.d/abstractions/libvirt-qemu.d1
sudo mkdir -p /etc/apparmor.d/abstractions/libvirt-qemu.d
해당 위치에 파일 생성
/etc/apparmor.d/abstractions/libvirt-qemu.d/override.1
sudo vi /etc/apparmor.d/abstractions/libvirt-qemu.d/override1 2 3
# /etc/apparmor.d/abstractions/libvirt-qemu.d/override /var/lib/libvirt/images/** rwk,
재시작
AppAmor1
sudo systemctl restart apparmor
위 명령어를 실행하면 정삭적으로 실행된다
libvirt에 ‘default’ 정의 안됨 오류

현재 스토리지 풀 확인
먼저 현재 시스템에 어떤 스토리지 풀이 있는지 확인
1
virsh pool-list --all아마 이 명령어의 결과가 비어 있거나, ‘default’ 풀이
inactive상태 비활성화 상태면 바로 활성화 단계로 이동풀이 없다면 생성한다
1
virsh pool-define-as default dir --target /var/lib/libvirt/images
풀 빌드
1
virsh pool-build default
풀 활성화
1 2 3 4 5
# 풀을 활성화(시작)합니다. sudo virsh pool-start default # 시스템 재부팅 시 풀이 자동으로 시작되도록 설정합니다. (매우 중요) sudo virsh pool-autostart default

5. Ansible로 환경 구축
먼저 파이썬을 설치해야한다 파이썬 가상환경에서 Ansible을 사용하는 이유는 프로젝트를 진행하다 보면 각기 다른 버전의 Ansible이나 관련 라이브러리가 필요한 경우가 생기는데
- A 프로젝트: Ansible 2.9 버전과 특정 라이브러리 v1.0이 필요
- B 프로젝트: Ansible 2.12 버전과 동일 라이브러리 v2.0이 필요
가상환경(venv)은 이런 문제를 해결 할 수 있다
파이썬 설치와 가상환경 실행
1
2
sudo apt update
sudo apt install python3-pip python3.10-venv -y
이후 playbook이 있는 프로젝트 폴더로 이동
1
2
python3 -m venv .venv # 가상환경 생성
source .venv/bin/activate # 가상환경 활성화
Ansible 설치
Ansible은 Python 기반이므로, Python과 패키지 관리자인 pip가 설치되어 있어야 한다
1
2
pip3 install ansible
ansible --version #설치확인
Ansible 연결 테스트
플레이북을 실행하기 전에, Ansible이 모든 VM에 정상적으로 접속할 수 있는지 확인할 수 있다 ping 모듈을 사용하면 간단하게 테스트한다
1
ansible all -i inventory.ini -m ping
all: inventory.ini에 있는 모든 호스트를 대상-i inventory.ini: 사용할 인벤토리 파일을 지정-m ping: ping 모듈을 사용 (ICMP ping이 아닌, SSH 접속 후 간단한 스크립트를 실행하여 응답을 확인하는 방식)

Ansible 실행
1
make all
6. 클러스터 확인
마스터 노드에 SSH로 접속
1
ssh ubuntu@192.168.100.100 -i ~/.ssh/id_rsa(IP 주소는 inventory.ini에 지정한 마스터 노드 IP)
마스터 노드 터미널에서
kubectl명령어로 노드 상태를 확인1
kubectl get nodes -o wide
